feat(mpuniapp): 完善微官网访客在线提交商务预约与需求功能
这个提交包含在:
@@ -330,7 +330,6 @@
|
||||
import {
|
||||
BentoBlockPublic,
|
||||
CardProfile,
|
||||
MeetingInviteOptions,
|
||||
} from '../../common/types.uts';
|
||||
import {
|
||||
createPortalState,
|
||||
@@ -351,13 +350,8 @@ import {
|
||||
trackAppointmentAction,
|
||||
PortalQuickAction,
|
||||
} from './usePortal.uts';
|
||||
import {
|
||||
generateGoogleCalendarUrl,
|
||||
generateOutlookCalendarUrl,
|
||||
getMeetingIcsDownloadUrl,
|
||||
} from '../../common/calendar.uts';
|
||||
import { saveToContacts } from '../../common/vcard-export.uts';
|
||||
import { collectHallContact } from '../../common/api.uts';
|
||||
import { collectHallContact, submitLead } from '../../common/api.uts';
|
||||
import { getAuthToken } from '../../common/request.uts';
|
||||
import { setPendingCollectCard } from '../owner/hall/useHall.uts';
|
||||
import {
|
||||
@@ -641,82 +635,100 @@ function openAiChat(): void {
|
||||
});
|
||||
}
|
||||
|
||||
// 商务日程预约相关逻辑
|
||||
// 商务日程预约与需求提报相关逻辑
|
||||
type AppointmentFormState = {
|
||||
name: string;
|
||||
contact: string;
|
||||
company: string;
|
||||
preferredTime: string;
|
||||
notes: string;
|
||||
};
|
||||
|
||||
const isAppointmentSheetOpen = ref(false);
|
||||
const appointmentFormSubmitting = ref(false);
|
||||
const appointmentSubmitted = ref(false);
|
||||
|
||||
const appointmentForm = reactive<AppointmentFormState>({
|
||||
name: '',
|
||||
contact: '',
|
||||
company: '',
|
||||
preferredTime: '',
|
||||
notes: '',
|
||||
});
|
||||
|
||||
const appointmentTimeOptions = computed((): Array<{ key: string; label: string }> => [
|
||||
{ key: 'video', label: textOf('portal.appointment_time_video') },
|
||||
{ key: 'offline', label: textOf('portal.appointment_time_offline') },
|
||||
{ key: 'call', label: textOf('portal.appointment_time_call') },
|
||||
]);
|
||||
|
||||
function selectPreferredTime(val: string): void {
|
||||
appointmentForm.preferredTime = val;
|
||||
}
|
||||
|
||||
function openAppointmentSheet(): void {
|
||||
if (!state.card) return;
|
||||
if (!appointmentForm.preferredTime && appointmentTimeOptions.value.length > 0) {
|
||||
appointmentForm.preferredTime = appointmentTimeOptions.value[0].label;
|
||||
}
|
||||
isAppointmentSheetOpen.value = true;
|
||||
trackAppointmentAction(state);
|
||||
}
|
||||
|
||||
function closeAppointmentSheet(): void {
|
||||
isAppointmentSheetOpen.value = false;
|
||||
if (appointmentSubmitted.value) {
|
||||
appointmentForm.name = '';
|
||||
appointmentForm.contact = '';
|
||||
appointmentForm.company = '';
|
||||
appointmentForm.notes = '';
|
||||
appointmentSubmitted.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildCurrentMeetingOptions(): MeetingInviteOptions {
|
||||
const card = state.card!;
|
||||
const now = new Date();
|
||||
const startTime = new Date(now.getTime() + 24 * 3600 * 1000);
|
||||
const endTime = new Date(startTime.getTime() + 30 * 60 * 1000);
|
||||
async function submitAppointmentForm(): Promise<void> {
|
||||
if (appointmentFormSubmitting.value) return;
|
||||
if (!state.card || state.card == null) return;
|
||||
|
||||
return {
|
||||
summary: `${card.name} · ${textOf('portal.appointment')}`,
|
||||
title: `${card.name} · ${textOf('portal.appointment')}`,
|
||||
description: `与 ${card.name} (${card.company || ''} ${card.title || ''}) 的商务会谈。\n名片: ${card.slug || ''}`,
|
||||
location: card.company || '在线商务会议 (Google Meet / Zoom)',
|
||||
startTime,
|
||||
endTime,
|
||||
organizerName: card.name,
|
||||
organizerEmail: card.email || 'meeting@omnicard.ai',
|
||||
};
|
||||
}
|
||||
|
||||
function handleCalendarSync(channel: 'google' | 'outlook' | 'ics'): void {
|
||||
if (!state.card) return;
|
||||
const options = buildCurrentMeetingOptions();
|
||||
trackAppointmentAction(state);
|
||||
|
||||
if (channel === 'google') {
|
||||
const url = generateGoogleCalendarUrl(options);
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: textOf('portal.copied_meeting_link'),
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
const name = appointmentForm.name.trim();
|
||||
const contact = appointmentForm.contact.trim();
|
||||
if (!name || !contact) {
|
||||
uni.showToast({
|
||||
title: textOf('portal.appointment_fill_required'),
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel === 'outlook') {
|
||||
const url = generateOutlookCalendarUrl(options);
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: textOf('portal.copied_meeting_link'),
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
appointmentFormSubmitting.value = true;
|
||||
try {
|
||||
const isEmail = contact.includes('@');
|
||||
const fullNotes = `[商务预约] 期望形式: ${appointmentForm.preferredTime || '未指定'}${appointmentForm.notes.trim() ? `\n需求与议题: ${appointmentForm.notes.trim()}` : ''}`;
|
||||
|
||||
if (channel === 'ics') {
|
||||
const url = getMeetingIcsDownloadUrl(options);
|
||||
uni.setClipboardData({
|
||||
data: url,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: textOf('portal.copied_meeting_link'),
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
await submitLead({
|
||||
cardId: state.card!.id,
|
||||
visitorId: state.visitorId,
|
||||
name: name,
|
||||
company: appointmentForm.company.trim(),
|
||||
phone: isEmail ? '' : contact,
|
||||
email: isEmail ? contact : '',
|
||||
source: 'appointment',
|
||||
notes: fullNotes,
|
||||
submittedLead: true,
|
||||
});
|
||||
return;
|
||||
|
||||
await trackAppointmentAction(state, fullNotes);
|
||||
appointmentSubmitted.value = true;
|
||||
uni.showToast({
|
||||
title: textOf('portal.appointment_success_tip'),
|
||||
icon: 'none',
|
||||
});
|
||||
} catch (_e) {
|
||||
uni.showToast({
|
||||
title: textOf('common.error'),
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
appointmentFormSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1481,108 +1493,127 @@ onUnload(() => {
|
||||
font-size: 13px;
|
||||
color: var(--oc-text-2, #64748B);
|
||||
}
|
||||
.sheet-meeting-details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
background-color: var(--oc-bg, #F5F8FC);
|
||||
border-radius: 12px;
|
||||
padding: 10px 14px;
|
||||
margin-block-end: 16px;
|
||||
}
|
||||
.sheet-detail-row {
|
||||
/* 商务预约与需求提报表单样式 */
|
||||
.appointment-form-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sheet-detail-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #98A2B3;
|
||||
letter-spacing: 0.5px;
|
||||
.sheet-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-block-end: 12px;
|
||||
}
|
||||
.sheet-detail-val {
|
||||
.sheet-field-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--oc-text, #101828);
|
||||
margin-top: 2px;
|
||||
margin-block-end: 6px;
|
||||
}
|
||||
.calendar-channels-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.calendar-channel-item {
|
||||
.sheet-time-chips {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 14px;
|
||||
min-height: 44px;
|
||||
background-color: #FFFFFF;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.sheet-time-chip {
|
||||
padding: 8px 14px;
|
||||
min-height: 36px;
|
||||
border-radius: 9999px;
|
||||
background-color: var(--oc-bg, #F5F8FC);
|
||||
border: 1px solid var(--oc-border, #EBF1F7);
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: border-color 150ms ease;
|
||||
}
|
||||
.calendar-channel-item:active {
|
||||
background-color: #F8FAFC;
|
||||
border-color: #B2D7FF;
|
||||
}
|
||||
.channel-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.channel-icon-badge {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
.sheet-time-chip-active {
|
||||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||||
border-color: var(--oc-primary, #0088FF);
|
||||
}
|
||||
.sheet-time-chip-text {
|
||||
font-size: 13px;
|
||||
color: var(--oc-text-2, #64748B);
|
||||
font-weight: 500;
|
||||
}
|
||||
.sheet-time-chip-text-active {
|
||||
color: var(--oc-primary, #0088FF);
|
||||
font-weight: 600;
|
||||
}
|
||||
.sheet-field-input {
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
border: 1px solid var(--oc-border, #EBF1F7);
|
||||
border-radius: 12px;
|
||||
padding: 0 14px;
|
||||
font-size: 16px;
|
||||
color: var(--oc-text, #101828);
|
||||
background-color: #FFFFFF;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.sheet-field-textarea {
|
||||
min-height: 72px;
|
||||
height: 72px;
|
||||
border: 1px solid var(--oc-border, #EBF1F7);
|
||||
border-radius: 12px;
|
||||
padding: 10px 14px;
|
||||
font-size: 16px;
|
||||
color: var(--oc-text, #101828);
|
||||
background-color: #FFFFFF;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.appointment-submit-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
margin-top: 6px;
|
||||
border-radius: 9999px;
|
||||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||||
box-shadow: var(--oc-shadow-cta, 0 8px 20px rgba(0, 136, 255, 0.22));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.google-badge {
|
||||
background-color: #FEF3F2;
|
||||
.appointment-submit-btn-disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
.outlook-badge {
|
||||
background-color: #EFF8FF;
|
||||
}
|
||||
.ics-badge {
|
||||
background-color: #ECFDF3;
|
||||
}
|
||||
.channel-icon-text {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.google-badge .channel-icon-text {
|
||||
color: #D92D20;
|
||||
}
|
||||
.outlook-badge .channel-icon-text {
|
||||
color: #026AA2;
|
||||
}
|
||||
.ics-badge .channel-icon-text {
|
||||
color: #027A48;
|
||||
}
|
||||
.channel-texts {
|
||||
|
||||
/* 预约成功反馈卡片 */
|
||||
.sheet-success-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24px 0 12px;
|
||||
}
|
||||
.channel-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
.sheet-success-ring {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 28px;
|
||||
background-color: #ECFDF3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.sheet-success-check {
|
||||
width: 14px;
|
||||
height: 24px;
|
||||
border-bottom: 3px solid #12B76A;
|
||||
border-right: 3px solid #12B76A;
|
||||
transform: rotate(45deg);
|
||||
margin-top: -6px;
|
||||
}
|
||||
.sheet-success-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: var(--oc-text, #101828);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.channel-sub {
|
||||
font-size: 11px;
|
||||
.sheet-success-sub {
|
||||
font-size: 13px;
|
||||
color: var(--oc-text-2, #64748B);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.channel-action-arrow {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #98A2B3;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
在新工单中引用
屏蔽一个用户