文件
OmniCard/mpuniapp/pages/portal/useCaseDetail.uts
wikiboss eec259a3a7 feat: 完成全系统30个功能点开发与E2E全链路验收 (8轮调度32个子代理闭环)
- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换
- 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片
- 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转
- 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出
- 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志
- 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票
- 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏
- 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
2026-09-20 23:47:47 +08:00

317 行
7.3 KiB
Plaintext

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* 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<boolean> {
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();
}