补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
837 行
20 KiB
Plaintext
837 行
20 KiB
Plaintext
/**
|
||
* OmniCard AI (CRMmp) - 深度洞见文章详情业务驱动层
|
||
* 严格遵循 uts 强类型规范与 AGENTS.md 约束
|
||
*/
|
||
|
||
import { BentoBlockPublic, CardPortalDetail, RadarTrack } from '../../common/types.uts';
|
||
import { fetchPortalCard, recordRadarTrack } from '../../common/api.uts';
|
||
import { getCurrentLocale, getOmniText, OmniLocale } 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;
|
||
authorName: string;
|
||
authorTitle: string;
|
||
authorCompany: string;
|
||
authorAvatar: string;
|
||
readTimeMinutes: number;
|
||
summaryText: string;
|
||
scrollDepth: number;
|
||
enterTime: number;
|
||
visitorId: string;
|
||
};
|
||
|
||
const BACK_LABEL = '返回';
|
||
const OG_DESC_MAX = 160;
|
||
const VISITOR_STORAGE_KEY = 'omni_visitor_id';
|
||
|
||
/**
|
||
* 创建文章详情初始状态
|
||
*/
|
||
export function createArticleDetailState(): ArticleDetailState {
|
||
return {
|
||
cardId: '',
|
||
blockId: '',
|
||
loading: false,
|
||
article: null,
|
||
title: '',
|
||
content: '',
|
||
mediaUrl: '',
|
||
linkUrl: '',
|
||
isHtmlContent: false,
|
||
empty: false,
|
||
errorMessage: '',
|
||
openGraph: {
|
||
title: '',
|
||
description: '',
|
||
image: '',
|
||
},
|
||
authorName: '',
|
||
authorTitle: '',
|
||
authorCompany: '',
|
||
authorAvatar: '',
|
||
readTimeMinutes: 1,
|
||
summaryText: '',
|
||
scrollDepth: 0,
|
||
enterTime: Date.now(),
|
||
visitorId: '',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 将详情状态复位为空
|
||
*/
|
||
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: '',
|
||
};
|
||
state.authorName = '';
|
||
state.authorTitle = '';
|
||
state.authorCompany = '';
|
||
state.authorAvatar = '';
|
||
state.readTimeMinutes = 1;
|
||
state.summaryText = '';
|
||
state.scrollDepth = 0;
|
||
}
|
||
|
||
/**
|
||
* 从 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 {
|
||
if (typeof getCurrentPages !== 'function') {
|
||
return buildArticlePagePath(state.cardId, state.blockId);
|
||
}
|
||
const pages = 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: resolveArticleDetailText('shareCta'),
|
||
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');
|
||
}
|
||
|
||
export type ArticleDetailTextKey =
|
||
| 'kicker'
|
||
| 'shareCta'
|
||
| 'shareSuccess'
|
||
| 'shareFailed'
|
||
| 'copyFailed'
|
||
| 'back'
|
||
| 'empty'
|
||
| 'emptyHint'
|
||
| 'summary'
|
||
| 'readTime'
|
||
| 'minRead'
|
||
| 'author'
|
||
| 'viewPortal';
|
||
|
||
/**
|
||
* 文章详情展示文案。严格遵循 AGENTS.md 多语言自然表达规范。
|
||
*/
|
||
export function resolveArticleDetailText(
|
||
key: ArticleDetailTextKey,
|
||
customLocale?: OmniLocale
|
||
): string {
|
||
const locale = customLocale != null ? customLocale : getCurrentLocale();
|
||
if (key === 'empty') {
|
||
return getOmniText(locale, 'common.empty');
|
||
}
|
||
|
||
if (locale === 'zh-CN') {
|
||
if (key === 'kicker') {
|
||
return '深度洞见';
|
||
}
|
||
if (key === 'summary') {
|
||
return '核心导读';
|
||
}
|
||
if (key === 'readTime') {
|
||
return '预计阅读';
|
||
}
|
||
if (key === 'minRead') {
|
||
return '分钟';
|
||
}
|
||
if (key === 'author') {
|
||
return '专栏作者';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return '查看微官网';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return '复制分享链接';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return '已分享';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return '分享失败';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return '复制失败';
|
||
}
|
||
if (key === 'back') {
|
||
return '返回';
|
||
}
|
||
return '返回上一页继续浏览';
|
||
}
|
||
|
||
if (locale === 'ar-SA') {
|
||
if (key === 'kicker') {
|
||
return 'رؤى متعمقة';
|
||
}
|
||
if (key === 'summary') {
|
||
return 'أبرز النقاط';
|
||
}
|
||
if (key === 'readTime') {
|
||
return 'وقت القراءة';
|
||
}
|
||
if (key === 'minRead') {
|
||
return 'دقائق';
|
||
}
|
||
if (key === 'author') {
|
||
return 'كاتب المقال';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'عرض الملف التعريفي';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return 'نسخ رابط المشاركة';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return 'تمت المشاركة';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return 'فشلت المشاركة';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'فشل النسخ';
|
||
}
|
||
if (key === 'back') {
|
||
return 'رجوع';
|
||
}
|
||
return 'ارجع للصفحة السابقة للمتابعة';
|
||
}
|
||
|
||
if (locale === 'es-ES') {
|
||
if (key === 'kicker') {
|
||
return 'Perspectivas';
|
||
}
|
||
if (key === 'summary') {
|
||
return 'Puntos clave';
|
||
}
|
||
if (key === 'readTime') {
|
||
return 'Tiempo de lectura';
|
||
}
|
||
if (key === 'minRead') {
|
||
return 'min';
|
||
}
|
||
if (key === 'author') {
|
||
return 'Autor';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'Ver perfil';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return 'Copiar enlace para compartir';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return 'Compartido';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return 'Error al compartir';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'Error al copiar';
|
||
}
|
||
if (key === 'back') {
|
||
return 'Volver';
|
||
}
|
||
return 'Vuelve para seguir leyendo';
|
||
}
|
||
|
||
if (locale === 'de-DE') {
|
||
if (key === 'kicker') {
|
||
return 'Brancheneinblick';
|
||
}
|
||
if (key === 'summary') {
|
||
return 'Kernaussagen';
|
||
}
|
||
if (key === 'readTime') {
|
||
return 'Lesezeit';
|
||
}
|
||
if (key === 'minRead') {
|
||
return 'Min.';
|
||
}
|
||
if (key === 'author') {
|
||
return 'Autor';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'Profil ansehen';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return 'Freigabelink kopieren';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return 'Geteilt';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return 'Teilen fehlgeschlagen';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'Kopieren fehlgeschlagen';
|
||
}
|
||
if (key === 'back') {
|
||
return 'Zurück';
|
||
}
|
||
return 'Gehen Sie zurück, um weiterzulesen';
|
||
}
|
||
|
||
if (locale === 'fr-FR') {
|
||
if (key === 'kicker') {
|
||
return 'Éclairage d’expert';
|
||
}
|
||
if (key === 'summary') {
|
||
return 'Points clés';
|
||
}
|
||
if (key === 'readTime') {
|
||
return 'Temps de lecture';
|
||
}
|
||
if (key === 'minRead') {
|
||
return 'min';
|
||
}
|
||
if (key === 'author') {
|
||
return 'Auteur';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'Voir le profil';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return 'Copier le lien de partage';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return 'Partagé';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return 'Échec du partage';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'Échec de la copie';
|
||
}
|
||
if (key === 'back') {
|
||
return 'Retour';
|
||
}
|
||
return 'Revenez en arrière pour continuer';
|
||
}
|
||
|
||
if (locale === 'ja-JP') {
|
||
if (key === 'kicker') {
|
||
return 'インサイト';
|
||
}
|
||
if (key === 'summary') {
|
||
return '要約';
|
||
}
|
||
if (key === 'readTime') {
|
||
return '読了目安';
|
||
}
|
||
if (key === 'minRead') {
|
||
return '分';
|
||
}
|
||
if (key === 'author') {
|
||
return '執筆者';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'プロフィールを見る';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return '共有リンクをコピー';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return '共有しました';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return '共有に失敗しました';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'コピーに失敗しました';
|
||
}
|
||
if (key === 'back') {
|
||
return '戻る';
|
||
}
|
||
return '前のページに戻って続きを見る';
|
||
}
|
||
|
||
if (key === 'kicker') {
|
||
return 'Thought Leadership';
|
||
}
|
||
if (key === 'summary') {
|
||
return 'Key Takeaways';
|
||
}
|
||
if (key === 'readTime') {
|
||
return 'Read time';
|
||
}
|
||
if (key === 'minRead') {
|
||
return 'min';
|
||
}
|
||
if (key === 'author') {
|
||
return 'Author';
|
||
}
|
||
if (key === 'viewPortal') {
|
||
return 'View Profile';
|
||
}
|
||
if (key === 'shareCta') {
|
||
return 'Copy share link';
|
||
}
|
||
if (key === 'shareSuccess') {
|
||
return 'Shared';
|
||
}
|
||
if (key === 'shareFailed') {
|
||
return 'Share failed';
|
||
}
|
||
if (key === 'copyFailed') {
|
||
return 'Copy failed';
|
||
}
|
||
if (key === 'back') {
|
||
return 'Back';
|
||
}
|
||
return 'Go back to keep reading';
|
||
}
|
||
|
||
function applyArticle(
|
||
state: ArticleDetailState,
|
||
article: BentoBlockPublic,
|
||
card?: CardPortalDetail | null
|
||
): 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);
|
||
|
||
const cleanText = stripHtmlToText(state.content);
|
||
state.readTimeMinutes = Math.max(1, Math.ceil(cleanText.length / 350));
|
||
state.summaryText = state.openGraph.description;
|
||
|
||
if (card != null) {
|
||
state.authorName = card.name != null ? String(card.name) : '';
|
||
state.authorTitle = card.title != null ? String(card.title) : '';
|
||
state.authorCompany = card.company != null ? String(card.company) : '';
|
||
state.authorAvatar = card.avatarUrl != null ? String(card.avatarUrl) : '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 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 = '';
|
||
state.enterTime = Date.now();
|
||
|
||
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, card);
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取或初始化访客唯一识别码 (持久化在本地 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;
|
||
}
|
||
|
||
/**
|
||
* 记录并更新滑屏深度 (0 - 100%)
|
||
*/
|
||
export function updateArticleScrollDepth(
|
||
state: ArticleDetailState,
|
||
scrollTop: number,
|
||
scrollHeight: number,
|
||
windowHeight: number
|
||
): number {
|
||
if (scrollHeight <= 0) {
|
||
return state.scrollDepth;
|
||
}
|
||
const maxScroll = Math.max(1, scrollHeight - windowHeight);
|
||
const currentRatio = Math.min(1, Math.max(0, scrollTop / maxScroll));
|
||
const depth = Math.round(currentRatio * 100);
|
||
if (depth > state.scrollDepth) {
|
||
state.scrollDepth = depth;
|
||
}
|
||
return state.scrollDepth;
|
||
}
|
||
|
||
/**
|
||
* 上报文章阅读停留时长与滑屏深度至访客雷达 CRM
|
||
*/
|
||
export async function reportArticleStay(
|
||
state: ArticleDetailState,
|
||
scrollDepth?: number
|
||
): Promise<RadarTrack | null> {
|
||
if (state.cardId === '' || state.blockId === '') {
|
||
return null;
|
||
}
|
||
const currentDepth = scrollDepth != null ? scrollDepth : state.scrollDepth;
|
||
const enter = state.enterTime > 0 ? state.enterTime : Date.now();
|
||
const stayDurationMs = Math.max(0, Date.now() - enter);
|
||
const visitorId = state.visitorId !== '' ? state.visitorId : getOrCreateVisitorId();
|
||
|
||
try {
|
||
const track = await recordRadarTrack({
|
||
cardId: state.cardId,
|
||
visitorId,
|
||
touchType: 'link',
|
||
stayDurationMs,
|
||
scrollDepth: currentDepth,
|
||
viewedBlocks: [state.blockId],
|
||
});
|
||
return track;
|
||
} catch (_e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 导航至专栏作者的名片主微官网
|
||
*/
|
||
export function navToAuthorPortal(state: ArticleDetailState): void {
|
||
if (state.cardId === '') {
|
||
return;
|
||
}
|
||
uni.navigateTo({
|
||
url: `/pages/portal/index?id=${state.cardId}`,
|
||
fail: () => {
|
||
uni.reLaunch({
|
||
url: `/pages/portal/index?id=${state.cardId}`,
|
||
});
|
||
},
|
||
});
|
||
}
|
||
|
||
type ShareWithSystemOption = {
|
||
type?: string;
|
||
summary?: string;
|
||
href?: string;
|
||
success?: () => void;
|
||
fail?: (err: { errCode?: number; errMsg?: string } | null) => void;
|
||
};
|
||
|
||
type UniWithSystemShare = {
|
||
shareWithSystem?: (options: ShareWithSystemOption) => void;
|
||
};
|
||
|
||
/**
|
||
* 是否具备系统分享 API(uni.shareWithSystem)
|
||
*/
|
||
export function hasArticleSystemShare(): boolean {
|
||
const uniShare = uni as UniWithSystemShare;
|
||
return uniShare.shareWithSystem != null;
|
||
}
|
||
|
||
/**
|
||
* 系统分享:仅 uni.shareWithSystem 明确 success 才提示「已分享」;
|
||
* 失败 / 用户取消不得提示已分享。
|
||
*/
|
||
export function shareArticleViaSystem(
|
||
state: ArticleDetailState,
|
||
pageUrl?: string
|
||
): Promise<boolean> {
|
||
const fallback = pageUrl != null ? pageUrl : getCurrentPageUrl(state);
|
||
const shareUrl = resolveShareUrl(state.linkUrl, fallback);
|
||
if (shareUrl === '') {
|
||
return Promise.resolve(false);
|
||
}
|
||
|
||
const uniShare = uni as UniWithSystemShare;
|
||
if (uniShare.shareWithSystem == null) {
|
||
return Promise.resolve(false);
|
||
}
|
||
|
||
const summary = state.title !== '' ? state.title : shareUrl;
|
||
|
||
return new Promise<boolean>(resolve => {
|
||
uniShare.shareWithSystem!({
|
||
type: 'text',
|
||
summary: summary,
|
||
href: shareUrl,
|
||
success: () => {
|
||
uni.showToast({
|
||
title: resolveArticleDetailText('shareSuccess'),
|
||
icon: 'none',
|
||
});
|
||
resolve(true);
|
||
},
|
||
fail: (err: { errCode?: number; errMsg?: string } | null) => {
|
||
// 取消(1310600)静默;其它失败只提示 shareFailed
|
||
const code = err != null && err.errCode != null ? err.errCode : 0;
|
||
if (code !== 1310600) {
|
||
uni.showToast({
|
||
title: resolveArticleDetailText('shareFailed'),
|
||
icon: 'none',
|
||
});
|
||
}
|
||
resolve(false);
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 主 CTA:复制 linkUrl 或当前页 URL。
|
||
* 仅剪贴板 success 才提示直链已复制(P22);失败只提示 copyFailed。
|
||
*/
|
||
export function copyArticleShareLink(state: ArticleDetailState, pageUrl?: string): boolean {
|
||
const fallback = pageUrl != null ? 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',
|
||
});
|
||
},
|
||
fail: () => {
|
||
uni.showToast({
|
||
title: resolveArticleDetailText('copyFailed'),
|
||
icon: 'none',
|
||
});
|
||
},
|
||
});
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 底栏主动作:有系统分享则走系统分享;否则回退复制链接。
|
||
* 分享失败/取消不回退复制,避免把失败伪装成已分享或已复制。
|
||
*/
|
||
export async function performArticleShareAction(
|
||
state: ArticleDetailState,
|
||
pageUrl?: string
|
||
): Promise<boolean> {
|
||
if (hasArticleSystemShare()) {
|
||
return await shareArticleViaSystem(state, pageUrl);
|
||
}
|
||
return copyArticleShareLink(state, pageUrl);
|
||
}
|
||
|
||
/**
|
||
* 空状态返回:优先回退上一页
|
||
*/
|
||
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}`,
|
||
});
|
||
},
|
||
});
|
||
}
|