文件
OmniCard/mpuniapp/pages/portal/useArticleDetail.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

330 行
8.1 KiB
Plaintext

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

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

/**
* OmniCard AI (CRMmp) - 深度洞见文章详情业务驱动层
* 严格遵循 uts 强类型规范与 AGENTS.md 约束
*/
import { BentoBlockPublic } from '../../common/types.uts';
import { fetchPortalCard } from '../../common/api.uts';
import { getCurrentLocale, getOmniText } from '../../common/locale.uts';
export type ArticleDetailPageOptions = {
cardId?: string;
blockId?: string;
id?: string;
};
export type ArticleOpenGraph = {
title: string;
description: string;
image: string;
};
export type ArticleShareFields = {
ctaLabel: string;
copiedToast: string;
shareUrl: string;
title: string;
description: string;
image: string;
};
export type ArticleDetailState = {
cardId: string;
blockId: string;
loading: boolean;
article: BentoBlockPublic | null;
title: string;
content: string;
mediaUrl: string;
linkUrl: string;
isHtmlContent: boolean;
empty: boolean;
errorMessage: string;
openGraph: ArticleOpenGraph;
};
const SHARE_CTA_LABEL = '复制链接';
const BACK_LABEL = '返回';
const OG_DESC_MAX = 160;
/**
* 创建文章详情初始状态
*/
export function createArticleDetailState(): ArticleDetailState {
return {
cardId: '',
blockId: '',
loading: false,
article: null,
title: '',
content: '',
mediaUrl: '',
linkUrl: '',
isHtmlContent: false,
empty: false,
errorMessage: '',
openGraph: {
title: '',
description: '',
image: '',
},
};
}
/**
* 将详情状态复位为空
*/
export function applyEmptyArticle(state: ArticleDetailState): void {
state.article = null;
state.title = '';
state.content = '';
state.mediaUrl = '';
state.linkUrl = '';
state.isHtmlContent = false;
state.empty = true;
state.openGraph = {
title: '',
description: '',
image: '',
};
}
/**
* 从 onLoad options 写入 cardId / blockId
*/
export function applyArticleDetailOptions(
state: ArticleDetailState,
options: ArticleDetailPageOptions | 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();
if (state.cardId === '' || state.blockId === '') {
applyEmptyArticle(state);
return false;
}
return true;
}
/**
* 从 bentoBlocks 中选出指定 article 块
*/
export function pickArticleBlock(
blocks: BentoBlockPublic[] | null | undefined,
blockId: string
): BentoBlockPublic | null {
if (blocks == null || !Array.isArray(blocks) || blocks.length === 0) {
return null;
}
const targetId = blockId != null ? String(blockId).trim() : '';
if (targetId === '') {
return null;
}
for (let i = 0; i < blocks.length; i++) {
const block = blocks[i];
if (block != null && String(block.id) === targetId && block.type === 'article') {
return block;
}
}
return null;
}
/**
* 正文是否像 HTML,供 rich-text / text 分流
*/
export function looksLikeHtml(content: string): boolean {
if (content == null || content.trim() === '') {
return false;
}
return /<\s*\/?\s*[a-zA-Z][^>]*>/.test(content);
}
/**
* 去掉 HTML 标签,折叠空白
*/
export function stripHtmlToText(raw: string): string {
if (raw == null || raw === '') {
return '';
}
return raw.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}
/**
* 供后续 H5 meta 使用的 OpenGraph 三件套
*/
export function buildArticleOpenGraph(article: BentoBlockPublic | null): ArticleOpenGraph {
if (article == null) {
return {
title: '',
description: '',
image: '',
};
}
const desc = stripHtmlToText(article.content != null ? article.content : '');
let description = desc;
if (description.length > OG_DESC_MAX) {
description = description.substring(0, OG_DESC_MAX - 3) + '...';
}
return {
title: article.title != null ? article.title : '',
description,
image: article.mediaUrl != null ? article.mediaUrl : '',
};
}
/**
* 分享链接:优先块 linkUrl,否则当前页 URL
*/
export function resolveShareUrl(linkUrl: string, pageUrl: string): string {
const fromBlock = linkUrl != null ? linkUrl.trim() : '';
if (fromBlock !== '') {
return fromBlock;
}
return pageUrl != null ? pageUrl.trim() : '';
}
/**
* 构造当前页路径兜底
*/
export function buildArticlePagePath(cardId: string, blockId: string): string {
return `/pages/portal/article-detail?cardId=${cardId}&blockId=${blockId}`;
}
type UniStackPage = {
route?: string;
};
/**
* 读取当前页 URL:优先页面栈,否则构造本地路径
*/
export function getCurrentPageUrl(state: ArticleDetailState): string {
try {
const pages = uni.getCurrentPages();
if (pages != null && pages.length > 0) {
const page = pages[pages.length - 1] as UniStackPage;
const route = page != null && page.route != null ? String(page.route) : '';
if (route !== '') {
const path = route.startsWith('/') ? route : `/${route}`;
return `${path}?cardId=${state.cardId}&blockId=${state.blockId}`;
}
}
} catch (_e) {}
return buildArticlePagePath(state.cardId, state.blockId);
}
/**
* 分享文案与 OpenGraph 字段(CTA / Toast / URL / title / description / image)
*/
export function getArticleShareFields(
state: ArticleDetailState,
pageUrl: string
): ArticleShareFields {
const og = state.openGraph;
return {
ctaLabel: SHARE_CTA_LABEL,
copiedToast: getOmniText(getCurrentLocale(), 'portal.copied_link'),
shareUrl: resolveShareUrl(state.linkUrl, pageUrl),
title: og.title,
description: og.description,
image: og.image,
};
}
export function getArticleBackLabel(): string {
return BACK_LABEL;
}
export function getArticleEmptyMessage(): string {
return getOmniText(getCurrentLocale(), 'common.empty');
}
function applyArticle(state: ArticleDetailState, article: BentoBlockPublic): void {
state.article = article;
state.title = article.title != null ? article.title : '';
state.content = article.content != null ? article.content : '';
state.mediaUrl = article.mediaUrl != null ? article.mediaUrl : '';
state.linkUrl = article.linkUrl != null ? article.linkUrl : '';
state.isHtmlContent = looksLikeHtml(state.content);
state.empty = false;
state.openGraph = buildArticleOpenGraph(article);
}
/**
* fetchPortalCard 后从 bentoBlocks 选出 article
*/
export async function loadArticleDetail(
state: ArticleDetailState
): Promise<BentoBlockPublic | null> {
if (state.cardId === '' || state.blockId === '') {
applyEmptyArticle(state);
return null;
}
state.loading = true;
state.errorMessage = '';
try {
const card = await fetchPortalCard(state.cardId);
const blocks = card != null && card.bentoBlocks != null ? card.bentoBlocks : [];
const article = pickArticleBlock(blocks, state.blockId);
state.loading = false;
if (article == null) {
applyEmptyArticle(state);
return null;
}
applyArticle(state, article);
return article;
} catch (err) {
state.loading = false;
applyEmptyArticle(state);
if (err instanceof Error) {
state.errorMessage = err.message;
} else {
state.errorMessage = getOmniText(getCurrentLocale(), 'portal.unavailable');
}
return null;
}
}
/**
* 主 CTA:复制 linkUrl 或当前页 URL
*/
export function copyArticleShareLink(state: ArticleDetailState, pageUrl?: string): boolean {
const fallback = pageUrl != null && pageUrl !== '' ? pageUrl : getCurrentPageUrl(state);
const shareUrl = resolveShareUrl(state.linkUrl, fallback);
if (shareUrl === '') {
return false;
}
uni.setClipboardData({
data: shareUrl,
success: () => {
uni.showToast({
title: getOmniText(getCurrentLocale(), 'portal.copied_link'),
icon: 'none',
});
},
});
return true;
}
/**
* 空状态返回:优先回退上一页
*/
export function goBackFromArticle(state: ArticleDetailState): void {
uni.navigateBack({
delta: 1,
fail: () => {
const query = state.cardId !== '' ? `?id=${state.cardId}` : '';
uni.reLaunch({
url: `/pages/portal/index${query}`,
});
},
});
}