/** * OmniCard AI (CRMmp) - 案例作品集详情业务驱动层 * 严格遵循 uts 强类型规范与 AGENTS.md 约束 */ import { CardPortalDetail, BentoBlockPublic } from '../../common/types.uts'; import { fetchPortalCard } from '../../common/api.uts'; export type CaseKpi = { label: string; value: string; }; type KpiLoose = { label?: string; name?: string; title?: string; value?: string | number; }; export type CaseMetaRecord = { images?: string[]; kpis?: KpiLoose[]; metrics?: KpiLoose[]; pdfUrl?: string; pdf?: string; youtubeUrl?: string; youtube?: string; url?: string; type?: string; }; export type CaseDetailState = { cardId: string; blockId: string; loading: boolean; empty: boolean; errorMessage: string; title: string; content: string; images: string[]; kpis: CaseKpi[]; pdfUrl: string; youtubeUrl: string; }; export type CaseDetailPageOptions = { cardId?: string; blockId?: string; id?: string; }; export const CASE_DETAIL_COPY = { empty: '未找到该案例', back: '返回', cta: '咨询 / 互换', pdf: 'PDF', youtube: 'YouTube', loading: '加载中', }; /** * 创建案例详情初始状态 */ export function createCaseDetailState(): CaseDetailState { return { cardId: '', blockId: '', loading: false, empty: false, errorMessage: '', title: '', content: '', images: [], kpis: [], pdfUrl: '', youtubeUrl: '', }; } /** * 从 onLoad options 读取 cardId / blockId */ export function applyCaseDetailOptions( state: CaseDetailState, options: CaseDetailPageOptions | null ): boolean { const cardIdRaw = options != null ? options.cardId || options.id || '' : ''; const blockIdRaw = options != null ? options.blockId || '' : ''; state.cardId = String(cardIdRaw).trim(); state.blockId = String(blockIdRaw).trim(); return state.cardId !== '' && state.blockId !== ''; } /** * 从门户聚合数据中选出 id 匹配且 type 为 case 的块;缺块返回 null */ export function pickCaseBlock( card: CardPortalDetail | null, blockId: string ): BentoBlockPublic | null { if (card == null || card.bentoBlocks == null || blockId === '') { return null; } const target = String(blockId); const blocks = card.bentoBlocks; for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; if (String(block.id) === target && block.type === 'case') { return block; } } return null; } /** * 解析 metaJson(字符串或对象) */ export function parseCaseMetaJson(raw: string | object | null | undefined): CaseMetaRecord { if (raw == null) { return {}; } if (typeof raw === 'string') { const trimmed = raw.trim(); if (trimmed === '') { return {}; } try { return JSON.parse(trimmed) as CaseMetaRecord; } catch (_e) { return {}; } } return raw as CaseMetaRecord; } function readTrimmed(value: string | undefined): string { return value != null ? String(value).trim() : ''; } function normalizeKpis(items: KpiLoose[] | undefined): CaseKpi[] { if (items == null || !Array.isArray(items)) { return []; } const result: CaseKpi[] = []; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item == null) { continue; } const label = readTrimmed(item.label) || readTrimmed(item.name) || readTrimmed(item.title); const value = item.value != null ? String(item.value).trim() : ''; if (label === '' && value === '') { continue; } result.push({ label: label !== '' ? label : value, value: value !== '' ? value : label, }); } return result; } function collectImages(mediaUrl: string, meta: CaseMetaRecord): string[] { const result: string[] = []; if (meta.images != null && Array.isArray(meta.images)) { for (let i = 0; i < meta.images.length; i++) { const src = String(meta.images[i]).trim(); if (src !== '') { result.push(src); } } } if (result.length === 0 && mediaUrl !== '') { result.push(mediaUrl); } return result; } function resolvePdfUrl(meta: CaseMetaRecord): string { const pdfUrl = readTrimmed(meta.pdfUrl); if (pdfUrl !== '') { return pdfUrl; } const pdf = readTrimmed(meta.pdf); if (pdf !== '') { return pdf; } const url = readTrimmed(meta.url); const kind = readTrimmed(meta.type).toLowerCase(); if (url !== '' && (kind === 'pdf' || url.toLowerCase().indexOf('.pdf') >= 0)) { return url; } return ''; } function resolveYoutubeUrl(meta: CaseMetaRecord): string { const youtubeUrl = readTrimmed(meta.youtubeUrl); if (youtubeUrl !== '') { return youtubeUrl; } const youtube = readTrimmed(meta.youtube); if (youtube !== '') { return youtube; } const url = readTrimmed(meta.url); const kind = readTrimmed(meta.type).toLowerCase(); const lower = url.toLowerCase(); if ( url !== '' && (kind === 'youtube' || lower.indexOf('youtube.com') >= 0 || lower.indexOf('youtu.be') >= 0) ) { return url; } return ''; } function clearCaseContent(state: CaseDetailState): void { state.title = ''; state.content = ''; state.images = []; state.kpis = []; state.pdfUrl = ''; state.youtubeUrl = ''; } function applyEmpty(state: CaseDetailState): void { state.loading = false; state.empty = true; clearCaseContent(state); } function fillFromBlock(state: CaseDetailState, block: BentoBlockPublic): void { const meta = parseCaseMetaJson(block.metaJson); const mediaUrl = block.mediaUrl != null ? block.mediaUrl.trim() : ''; state.empty = false; state.loading = false; state.errorMessage = ''; state.title = block.title != null ? block.title : ''; state.content = block.content != null ? block.content : ''; state.images = collectImages(mediaUrl, meta); const kpis = normalizeKpis(meta.kpis); state.kpis = kpis.length > 0 ? kpis : normalizeKpis(meta.metrics); state.pdfUrl = resolvePdfUrl(meta); state.youtubeUrl = resolveYoutubeUrl(meta); } /** * 用已拉取的门户数据填充案例详情;缺块返空 */ export function applyPortalCase( state: CaseDetailState, card: CardPortalDetail | null ): boolean { const block = pickCaseBlock(card, state.blockId); if (block == null) { applyEmpty(state); return false; } fillFromBlock(state, block); return true; } /** * 拉取门户聚合详情并选出 case 块 */ export async function loadCaseDetail(state: CaseDetailState): Promise { if (state.cardId === '' || state.blockId === '') { applyEmpty(state); return false; } state.loading = true; state.empty = false; state.errorMessage = ''; try { const card = await fetchPortalCard(state.cardId); return applyPortalCase(state, card); } catch (err) { applyEmpty(state); if (err instanceof Error) { state.errorMessage = err.message; } else { state.errorMessage = '获取案例详情失败'; } return false; } } /** * 咨询 / 互换页路径 */ export function buildExchangePath(cardId: string): string { return `/pages/portal/exchange?cardId=${cardId}`; } /** * 跳转无摩擦互换名片 */ export function openCaseExchange(cardId: string): void { const id = cardId != null ? cardId.trim() : ''; if (id === '') { return; } uni.navigateTo({ url: buildExchangePath(id), }); } /** * 空状态返回上一页 */ export function goCaseBack(): void { uni.navigateBack(); }