文件
OmniCard/mpuniapp/pages/portal/usePortal.uts

257 行
5.6 KiB
Plaintext

/**
* OmniCard AI (CRMmp) - 微官网 Bento 首页与 Hero 身份区业务驱动层
* 严格遵循 uts 强类型规范与 AGENTS.md 约束
*/
import {
CardPortalDetail,
BentoBlockPublic,
RadarTrack,
} from '../../common/types.uts';
import {
fetchPortalCard,
recordRadarTrack,
} from '../../common/api.uts';
export type PortalState = {
card: CardPortalDetail | null;
loading: boolean;
errorMessage: string;
visitorId: string;
enterTime: number;
touchType: string;
isPlayingAudio: boolean;
audioContext: any | null;
};
const VISITOR_STORAGE_KEY = 'omni_visitor_id';
/**
* 获取或初始化访客唯一识别码 (持久化在本地 Storage)
*/
export function getOrCreateVisitorId(): string {
let visitorId = '';
try {
visitorId = uni.getStorageSync(VISITOR_STORAGE_KEY) as string;
} catch (e) {
visitorId = '';
}
if (!visitorId || visitorId.trim() === '') {
const randomPart = Math.random().toString(36).substring(2, 10);
visitorId = `vis_${Date.now()}_${randomPart}`;
try {
uni.setStorageSync(VISITOR_STORAGE_KEY, visitorId);
} catch (e) {}
}
return visitorId;
}
/**
* 创建微官网响应式状态初始对象
*/
export function createPortalState(): PortalState {
return {
card: null,
loading: false,
errorMessage: '',
visitorId: getOrCreateVisitorId(),
enterTime: Date.now(),
touchType: 'link',
isPlayingAudio: false,
audioContext: null,
};
}
/**
* 加载微官网名片聚合数据并上报首次触碰
*/
export async function loadCardPortal(
state: PortalState,
cardIdOrSlug: string,
source: string = 'link'
): Promise<CardPortalDetail | null> {
state.loading = true;
state.errorMessage = '';
state.touchType = source;
state.enterTime = Date.now();
try {
const cardData = await fetchPortalCard(cardIdOrSlug);
state.card = cardData;
state.loading = false;
// 首次载入成功后上报雷达初次触碰轨迹
if (cardData && cardData.id) {
recordRadarTrack({
cardId: cardData.id,
visitorId: state.visitorId,
touchType: state.touchType,
stayDurationMs: 0,
scrollDepth: 0,
}).catch((_e: any) => {
// 轨迹上报容错,不阻断主流程
});
}
return cardData;
} catch (err: any) {
state.loading = false;
state.card = null;
state.errorMessage = err?.message || '获取名片微官网详情失败';
return null;
}
}
/**
* 上报访客停留时长与阅读深度
*/
export async function reportPortalStay(
state: PortalState,
scrollDepth: number = 0
): Promise<RadarTrack | null> {
if (!state.card || !state.card.id) {
return null;
}
const stayDurationMs = Math.max(0, Date.now() - state.enterTime);
try {
const track = await recordRadarTrack({
cardId: state.card.id,
visitorId: state.visitorId,
touchType: state.touchType,
stayDurationMs,
scrollDepth,
});
return track;
} catch (e) {
return null;
}
}
/**
* 名字语音发音试听控制
*/
export function toggleAudioPronunciation(state: PortalState, audioUrl?: string): void {
if (!audioUrl || audioUrl.trim() === '') {
return;
}
if (state.isPlayingAudio && state.audioContext) {
try {
state.audioContext.stop();
} catch (e) {}
state.isPlayingAudio = false;
return;
}
if (!state.audioContext) {
state.audioContext = uni.createInnerAudioContext();
state.audioContext.onPlay(() => {
state.isPlayingAudio = true;
});
state.audioContext.onEnded(() => {
state.isPlayingAudio = false;
});
state.audioContext.onError(() => {
state.isPlayingAudio = false;
});
}
try {
state.audioContext.src = audioUrl;
state.audioContext.play();
state.isPlayingAudio = true;
} catch (e) {
state.isPlayingAudio = false;
}
}
/**
* 快捷动作条呼出处理
*/
export function triggerQuickAction(
type: 'whatsapp' | 'linkedin' | 'email' | 'phone',
value?: string
): boolean {
if (!value || value.trim() === '') {
uni.showToast({
title: '未设置该联系方式',
icon: 'none',
});
return false;
}
if (type === 'phone') {
uni.makePhoneCall({
phoneNumber: value,
});
return true;
}
if (type === 'whatsapp') {
const cleanNumber = value.replace(/[^0-9+]/g, '');
const waUrl = `https://wa.me/${cleanNumber.replace('+', '')}`;
uni.setClipboardData({
data: waUrl,
success: () => {
uni.showToast({
title: '已复制 WhatsApp 链接',
icon: 'none',
});
},
});
return true;
}
if (type === 'linkedin') {
uni.setClipboardData({
data: value,
success: () => {
uni.showToast({
title: '已复制 LinkedIn 链接',
icon: 'none',
});
},
});
return true;
}
if (type === 'email') {
uni.setClipboardData({
data: value,
success: () => {
uni.showToast({
title: '已复制邮箱地址',
icon: 'none',
});
},
});
return true;
}
return true;
}
/**
* 根据 colSpan 计算 Bento Grid 列宽样式类
*/
export function getBentoColSpanClass(colSpan: number): string {
if (colSpan === 2) {
return 'bento-col-2';
}
return 'bento-col-1';
}
/**
* 过滤非 Hero 的 Bento 卡片并按 sortOrder 升序排列
*/
export function filterVisibleBentoBlocks(blocks?: BentoBlockPublic[]): BentoBlockPublic[] {
if (!blocks || !Array.isArray(blocks)) {
return [];
}
return blocks
.filter((b) => b.type !== 'hero')
.sort((a, b) => a.sortOrder - b.sortOrder);
}