/** * OmniCard AI (CRMmp) - 文章动态中心移动端业务逻辑驱动层 * 严格遵循 uts 强类型契约与 AGENTS.md 规范 */ import { CardArticle, OwnerPageData, OwnerPageQuery } from '../../../common/types.uts'; import { fetchCardArticlesPage, addCardArticle, updateCardArticle, deleteCardArticle, } from '../../../common/api.uts'; import { getCurrentLocale } from '../../../common/locale.uts'; export type ArticlesApi = { fetchPage: (query: OwnerPageQuery) => Promise>; addArticle: (data: Partial) => Promise; updateArticle: (data: Partial & { id: string | number }) => Promise; deleteArticle: (id: string | number) => Promise; }; export const defaultArticlesApi: ArticlesApi = { fetchPage: (query: OwnerPageQuery) => fetchCardArticlesPage(query), addArticle: (data: Partial) => addCardArticle(data), updateArticle: (data: Partial & { id: string | number }) => updateCardArticle(data), deleteArticle: (id: string | number) => deleteCardArticle(id), }; export type ArticlesState = { cardId: string | number; list: CardArticle[]; loading: boolean; empty: boolean; errorMessage: string; drawerVisible: boolean; editingArticle: Partial | null; saving: boolean; }; export function createArticlesState(cardId: string | number): ArticlesState { return { cardId, list: [], loading: false, empty: false, errorMessage: '', drawerVisible: false, editingArticle: null, saving: false, }; } export function extractArticleSummary(content?: string, maxLen: number = 120): string { if (content == null || content.trim() === '') { return ''; } const plain = content .replace(/<[^>]+>/g, '') // 剥离 HTML .replace(/!\[.*?\]\(.*?\)/g, '') // 剥离 Markdown 图片 .replace(/\[(.*?)\]\(.*?\)/g, '$1') // 提取 Markdown 链接文本 .replace(/[#*`_~>-]/g, '') // 移除 Markdown 常用标记 .replace(/\s+/g, ' ') // 压缩多余空格 .trim(); return plain.length > maxLen ? plain.slice(0, maxLen) : plain; } export function validateArticleForm(form: Partial): string | null { const title = form.title != null ? form.title.trim() : ''; if (title === '') { return resolveArticlesText('errTitleRequired'); } const content = form.content != null ? form.content.trim() : ''; if (content === '') { return resolveArticlesText('errContentRequired'); } return null; } export async function loadArticles( state: ArticlesState, api: ArticlesApi = defaultArticlesApi ): Promise { state.loading = true; state.errorMessage = ''; try { const res = await api.fetchPage({ page: 1, size: 50, cardId: state.cardId, }); const items = res.list != null ? res.list : []; state.list = items; state.empty = items.length === 0; } catch (err: any) { state.errorMessage = err.message || resolveArticlesText('errLoadFailed'); } finally { state.loading = false; } } export async function saveArticle( state: ArticlesState, form: Partial, api: ArticlesApi = defaultArticlesApi ): Promise { const err = validateArticleForm(form); if (err !== null) { state.errorMessage = err; return false; } state.saving = true; state.errorMessage = ''; try { const summary = (form.summary != null && form.summary!.trim() !== '') ? form.summary!.trim() : extractArticleSummary(form.content); const payload: Partial = { ...form, cardId: state.cardId, title: form.title!.trim(), summary, content: form.content!.trim(), isPublished: form.isPublished !== undefined ? form.isPublished : true, }; if (form.id != null) { await api.updateArticle({ ...payload, id: form.id, }); } else { await api.addArticle(payload); } state.drawerVisible = false; state.editingArticle = null; await loadArticles(state, api); return true; } catch (err: any) { state.errorMessage = err.message || resolveArticlesText('errSaveFailed'); return false; } finally { state.saving = false; } } export async function toggleArticlePublish( state: ArticlesState, article: CardArticle, api: ArticlesApi = defaultArticlesApi ): Promise { if (article.id == null) { return false; } state.saving = true; state.errorMessage = ''; try { const targetStatus = !article.isPublished; await api.updateArticle({ id: article.id, cardId: article.cardId, isPublished: targetStatus, }); article.isPublished = targetStatus; return true; } catch (err: any) { state.errorMessage = err.message || resolveArticlesText('errToggleFailed'); return false; } finally { state.saving = false; } } export async function deleteArticle( state: ArticlesState, id: string | number, api: ArticlesApi = defaultArticlesApi ): Promise { state.saving = true; state.errorMessage = ''; try { const ok = await api.deleteArticle(id); if (ok) { await loadArticles(state, api); return true; } return false; } catch (err: any) { state.errorMessage = err.message || resolveArticlesText('errDeleteFailed'); return false; } finally { state.saving = false; } } export type ArticlesCopyKey = | 'title' | 'subtitle' | 'publishBtn' | 'editBtn' | 'deleteBtn' | 'emptyTitle' | 'emptyDesc' | 'statusPublished' | 'statusDraft' | 'fieldTitle' | 'fieldSummary' | 'fieldCover' | 'fieldContent' | 'fieldPublish' | 'publishDesc' | 'phTitle' | 'phSummary' | 'phContent' | 'saveBtn' | 'cancelBtn' | 'saving' | 'savedToast' | 'statusUpdatedToast' | 'deletedToast' | 'deleteConfirmTitle' | 'deleteConfirmContent' | 'deleteConfirmOk' | 'errTitleRequired' | 'errContentRequired' | 'errLoadFailed' | 'errSaveFailed' | 'errDeleteFailed' | 'errToggleFailed'; export function resolveArticlesText(key: ArticlesCopyKey): string { const locale = getCurrentLocale(); const zh = locale === 'zh-CN'; switch (key) { case 'title': return zh ? '文章与商务动态' : 'Insights & Articles'; case 'subtitle': return zh ? '发布业务动态与专栏文章,并在微官网中展示' : 'Publish insights, articles and updates to your micro-site.'; case 'publishBtn': return zh ? '发布动态' : 'New Article'; case 'editBtn': return zh ? '编辑' : 'Edit'; case 'deleteBtn': return zh ? '删除' : 'Delete'; case 'emptyTitle': return zh ? '暂无文章动态' : 'No Articles Yet'; case 'emptyDesc': return zh ? '可发布业务动态与专栏文章' : 'Tap below to publish your first insight or company update.'; case 'statusPublished': return zh ? '已发布' : 'Published'; case 'statusDraft': return zh ? '草稿' : 'Draft'; case 'fieldTitle': return zh ? '文章标题' : 'Title'; case 'fieldSummary': return zh ? '摘要导读 (选填)' : 'Summary'; case 'fieldCover': return zh ? '封面图片' : 'Cover Image'; case 'fieldContent': return zh ? '文章正文 (支持富文本与 Markdown)' : 'Content'; case 'fieldPublish': return zh ? '公开发布至微官网' : 'Publish to Micro-site'; case 'publishDesc': return zh ? '开启后,全球访客可在您的名片微官网中直接浏览' : 'When enabled, visitors can view this insight on your micro-site'; case 'phTitle': return zh ? '输入文章或动态标题' : 'Enter article or insight title'; case 'phSummary': return zh ? '选填,不填将从正文自动提取' : 'Optional; auto-extracted from content if empty'; case 'phContent': return zh ? '输入正文,支持段落与 Markdown' : 'Write content; paragraphs and Markdown supported'; case 'saveBtn': return zh ? '确认发布' : 'Publish'; case 'cancelBtn': return zh ? '取消' : 'Cancel'; case 'saving': return zh ? '保存中...' : 'Saving...'; case 'savedToast': return zh ? '动态已保存' : 'Article Saved'; case 'statusUpdatedToast': return zh ? '状态已切换' : 'Status Updated'; case 'deletedToast': return zh ? '已删除' : 'Deleted'; case 'deleteConfirmTitle': return zh ? '确认删除' : 'Confirm Delete'; case 'deleteConfirmContent': return zh ? '确定要删除该文章动态吗?' : 'Are you sure you want to delete this article?'; case 'deleteConfirmOk': return zh ? '删除' : 'Delete'; case 'errTitleRequired': return zh ? '请输入文章或动态标题' : 'Please enter a title'; case 'errContentRequired': return zh ? '请输入文章正文内容' : 'Please enter article content'; case 'errLoadFailed': return zh ? '加载文章列表失败' : 'Failed to load articles'; case 'errSaveFailed': return zh ? '保存文章失败' : 'Failed to save article'; case 'errDeleteFailed': return zh ? '删除文章失败' : 'Failed to delete article'; case 'errToggleFailed': return zh ? '切换发布状态失败' : 'Failed to update publish status'; default: return ''; } }