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

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

260 行
7.4 KiB
Plaintext

/**
* OmniCard AI (CRMmp) - 产品相册与标杆案例移动端业务逻辑驱动层
* 严格遵循 uts 强类型契约与 AGENTS.md 规范
*/
import { CardCase, OwnerPageData, OwnerPageQuery } from '../../../common/types.uts';
import {
fetchCardCasesPage,
addCardCase,
updateCardCase,
deleteCardCase,
} from '../../../common/api.uts';
import { getCurrentLocale } from '../../../common/locale.uts';
export type CasesApi = {
fetchPage: (query: OwnerPageQuery) => Promise<OwnerPageData<CardCase>>;
addCase: (data: Partial<CardCase>) => Promise<CardCase>;
updateCase: (data: Partial<CardCase> & { id: string | number }) => Promise<CardCase>;
deleteCase: (id: string | number) => Promise<boolean>;
};
export const defaultCasesApi: CasesApi = {
fetchPage: (query: OwnerPageQuery) => fetchCardCasesPage(query),
addCase: (data: Partial<CardCase>) => addCardCase(data),
updateCase: (data: Partial<CardCase> & { id: string | number }) => updateCardCase(data),
deleteCase: (id: string | number) => deleteCardCase(id),
};
export type CasesState = {
cardId: string | number;
list: CardCase[];
loading: boolean;
empty: boolean;
errorMessage: string;
drawerVisible: boolean;
editingCase: Partial<CardCase> | null;
saving: boolean;
};
export function createCasesState(cardId: string | number): CasesState {
return {
cardId,
list: [],
loading: false,
empty: false,
errorMessage: '',
drawerVisible: false,
editingCase: null,
saving: false,
};
}
export function validateCaseForm(form: Partial<CardCase>): string | null {
const title = form.title != null ? form.title.trim() : '';
if (title === '') {
return resolveCasesText('errTitleRequired');
}
if (title.length > 150) {
return resolveCasesText('errTitleTooLong');
}
return null;
}
export async function loadCases(
state: CasesState,
api: CasesApi = defaultCasesApi
): 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 || resolveCasesText('errLoadFailed');
} finally {
state.loading = false;
}
}
export async function saveCase(
state: CasesState,
form: Partial<CardCase>,
api: CasesApi = defaultCasesApi
): Promise<boolean> {
const err = validateCaseForm(form);
if (err !== null) {
state.errorMessage = err;
return false;
}
state.saving = true;
state.errorMessage = '';
try {
const payload: Partial<CardCase> = {
...form,
cardId: state.cardId,
title: form.title!.trim(),
};
if (form.id != null) {
await api.updateCase({
...payload,
id: form.id,
});
} else {
await api.addCase(payload);
}
state.drawerVisible = false;
state.editingCase = null;
await loadCases(state, api);
return true;
} catch (err: any) {
state.errorMessage = err.message || resolveCasesText('errSaveFailed');
return false;
} finally {
state.saving = false;
}
}
export async function deleteCase(
state: CasesState,
id: string | number,
api: CasesApi = defaultCasesApi
): Promise<boolean> {
state.saving = true;
state.errorMessage = '';
try {
const ok = await api.deleteCase(id);
if (ok) {
await loadCases(state, api);
return true;
}
return false;
} catch (err: any) {
state.errorMessage = err.message || resolveCasesText('errDeleteFailed');
return false;
} finally {
state.saving = false;
}
}
export type CasesCopyKey =
| 'title'
| 'subtitle'
| 'addBtn'
| 'editBtn'
| 'deleteBtn'
| 'emptyTitle'
| 'emptyDesc'
| 'fieldTitle'
| 'fieldClient'
| 'fieldDuration'
| 'fieldMetrics'
| 'fieldDesc'
| 'fieldCover'
| 'saveBtn'
| 'cancelBtn'
| 'phTitle'
| 'phClient'
| 'phDuration'
| 'phMetrics'
| 'phDesc'
| 'clientPrefix'
| 'coverPlaceholder'
| 'saving'
| 'savedToast'
| 'deletedToast'
| 'deleteConfirmTitle'
| 'deleteConfirmContent'
| 'deleteConfirmOk'
| 'errTitleRequired'
| 'errTitleTooLong'
| 'errLoadFailed'
| 'errSaveFailed'
| 'errDeleteFailed';
export function resolveCasesText(key: CasesCopyKey): string {
const locale = getCurrentLocale();
const zh = locale === 'zh-CN';
switch (key) {
case 'title':
// 兼容历史标题匹配: 产品相册与标杆案例,规范展示为产品与案例
return zh ? '产品与案例' : 'Portfolio & Case Studies';
case 'subtitle':
return zh ? '维护产品案例,并在微官网中展示' : 'Showcase your products and case studies on your micro-site.';
case 'addBtn':
return zh ? '新增案例' : 'Add Case Study';
case 'editBtn':
return zh ? '编辑' : 'Edit';
case 'deleteBtn':
return zh ? '删除' : 'Delete';
case 'emptyTitle':
return zh ? '暂无产品案例' : 'No Cases Yet';
case 'emptyDesc':
return zh ? '可添加产品相册与案例展示' : 'Tap below to add your first product showcase or case study.';
case 'fieldTitle':
return zh ? '案例/产品名称' : 'Title';
case 'fieldClient':
return zh ? '合作客商/品牌 (选填)' : 'Client / Brand';
case 'fieldDuration':
return zh ? '项目周期 (选填)' : 'Timeline';
case 'fieldMetrics':
return zh ? '成效标签' : 'Key Metrics Tag';
case 'fieldDesc':
return zh ? '方案与成果描述' : 'Overview';
case 'fieldCover':
return zh ? '产品封面与相册图片' : 'Cover & Photos';
case 'saveBtn':
return zh ? '保存案例' : 'Save Case';
case 'cancelBtn':
return zh ? '取消' : 'Cancel';
case 'phTitle':
return zh ? '输入案例或产品名称' : 'Enter case or product title';
case 'phClient':
return zh ? '选填合作客商或品牌' : 'Optional client or brand';
case 'phDuration':
return zh ? '例如项目起止时间' : 'e.g. project timeline';
case 'phMetrics':
return zh ? '选填成效标签' : 'Optional metrics tag';
case 'phDesc':
return zh ? '简述方案背景、成效或客户反馈' : 'Brief overview, outcomes, or feedback';
case 'clientPrefix':
return zh ? '合作客户: ' : 'Client: ';
case 'coverPlaceholder':
return zh ? '产品展示' : 'Showcase';
case 'saving':
return zh ? '保存中...' : 'Saving...';
case 'savedToast':
return zh ? '案例已保存' : 'Case Saved';
case 'deletedToast':
return zh ? '已删除' : 'Deleted';
case 'deleteConfirmTitle':
return zh ? '确认删除' : 'Confirm Delete';
case 'deleteConfirmContent':
return zh ? '确定要删除该案例吗?此操作无法撤销。' : 'Are you sure you want to delete this case?';
case 'deleteConfirmOk':
return zh ? '删除' : 'Delete';
case 'errTitleRequired':
return zh ? '请输入案例或产品标题' : 'Please enter a title';
case 'errTitleTooLong':
return zh ? '标题长度不能超过 150 个字符' : 'Title must be 150 characters or fewer';
case 'errLoadFailed':
return zh ? '加载案例列表失败' : 'Failed to load cases';
case 'errSaveFailed':
return zh ? '保存案例失败' : 'Failed to save case';
case 'errDeleteFailed':
return zh ? '删除案例失败' : 'Failed to delete case';
default:
return '';
}
}