文件
OmniCard/mpuniapp/pages/owner/card/useCardArticles.uts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

311 行
9.2 KiB
Plaintext

/**
* 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<OwnerPageData<CardArticle>>;
addArticle: (data: Partial<CardArticle>) => Promise<CardArticle>;
updateArticle: (data: Partial<CardArticle> & { id: string | number }) => Promise<CardArticle>;
deleteArticle: (id: string | number) => Promise<boolean>;
};
export const defaultArticlesApi: ArticlesApi = {
fetchPage: (query: OwnerPageQuery) => fetchCardArticlesPage(query),
addArticle: (data: Partial<CardArticle>) => addCardArticle(data),
updateArticle: (data: Partial<CardArticle> & { 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<CardArticle> | 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<CardArticle>): 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<void> {
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<CardArticle>,
api: ArticlesApi = defaultArticlesApi
): Promise<boolean> {
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<CardArticle> = {
...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<boolean> {
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<boolean> {
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 '';
}
}