import { CATEGORY_LIST } from '../../core/data/categories'; type CloudCategory = { _id: string; name: string; icon: string; color: string; sortOrder: number; parentId?: string | null; }; type CompareStatus = 'synced' | 'missing' | 'outdated'; type CompareItem = { id: string; name: string; icon: string; sortOrder: number; status: CompareStatus; statusText: string; diffText: string; actionText: string; cloudCategory: CloudCategory | null; }; type CloudFunctionResult = { success?: boolean; message?: string; data?: T; }; const DEFAULT_CATEGORY_COLORS = [ '#F7EE47', '#93D333', '#FFB703', '#8ECAE6', '#FF8FAB', '#CDB4DB', ]; function getDefaultCategoryColor(id: string, index: number): string { const hashBase = `${id}-${index}`; let hash = 0; for (let i = 0; i < hashBase.length; i++) { hash = (hash * 33 + hashBase.charCodeAt(i)) | 0; } return DEFAULT_CATEGORY_COLORS[ Math.abs(hash) % DEFAULT_CATEGORY_COLORS.length ]; } function buildCompareItems(cloudCategories: CloudCategory[]): { items: CompareItem[]; cloudOnlyCategories: CloudCategory[]; syncedCount: number; missingCount: number; outdatedCount: number; } { const cloudMap = new Map(); for (const item of cloudCategories) { cloudMap.set(item._id, item); } let syncedCount = 0; let missingCount = 0; let outdatedCount = 0; const items = CATEGORY_LIST.map((localCategory, index) => { const cloudCategory = cloudMap.get(localCategory.id) || null; const sortOrder = index; if (!cloudCategory) { missingCount += 1; return { id: localCategory.id, name: localCategory.name, icon: localCategory.icon, sortOrder, status: 'missing' as CompareStatus, statusText: '待新增', diffText: '', actionText: '新增', cloudCategory: null, }; } const diffFields: string[] = []; if (cloudCategory.name !== localCategory.name) diffFields.push('名称'); if (cloudCategory.icon !== localCategory.icon) diffFields.push('图标'); if ((cloudCategory.sortOrder ?? 0) !== sortOrder) diffFields.push('排序'); if (diffFields.length > 0) { outdatedCount += 1; return { id: localCategory.id, name: localCategory.name, icon: localCategory.icon, sortOrder, status: 'outdated' as CompareStatus, statusText: '待更新', diffText: `字段不一致:${diffFields.join('、')}。`, actionText: '更新', cloudCategory, }; } syncedCount += 1; return { id: localCategory.id, name: localCategory.name, icon: localCategory.icon, sortOrder, status: 'synced' as CompareStatus, statusText: '已同步', diffText: '', actionText: '', cloudCategory, }; }); const localIds = new Set(CATEGORY_LIST.map((item) => item.id)); const cloudOnlyCategories = cloudCategories.filter( (item) => !localIds.has(item._id), ); return { items, cloudOnlyCategories, syncedCount, missingCount, outdatedCount, }; } async function callCategoryFunction( name: string, data?: Record, ): Promise> { const response = (await wx.cloud.callFunction({ name, data: data || {}, })) as { result?: CloudFunctionResult }; return response.result || {}; } Page({ data: { loading: false, syncingId: '', compareItems: [] as CompareItem[], cloudOnlyCategories: [] as CloudCategory[], syncedCount: 0, missingCount: 0, outdatedCount: 0, }, onLoad() { void this.loadCategories(); }, async onPullDownRefresh() { await this.loadCategories(); wx.stopPullDownRefresh(); }, async loadCategories() { this.setData({ loading: true }); try { const result = await callCategoryFunction('categoriesQuery'); if (!result.success) { throw new Error(result.message || '获取分类失败'); } const compareData = buildCompareItems(result.data || []); this.setData({ loading: false, compareItems: compareData.items, cloudOnlyCategories: compareData.cloudOnlyCategories, syncedCount: compareData.syncedCount, missingCount: compareData.missingCount, outdatedCount: compareData.outdatedCount, }); } catch (error) { this.setData({ loading: false }); wx.showToast({ title: error instanceof Error ? error.message : '获取分类失败', icon: 'none', }); } }, async onTapSync(e: WechatMiniprogram.TouchEvent) { const id = e.currentTarget.dataset.id as string; if (!id || this.data.syncingId) return; const item = this.data.compareItems.find((entry) => entry.id === id); if (!item || !item.actionText) return; const confirmed = await new Promise((resolve) => { wx.showModal({ title: `${item.actionText}分类`, content: `确认将「${item.name}」同步到云端吗?`, success: (res) => resolve(!!res.confirm), fail: () => resolve(false), }); }); if (!confirmed) return; this.setData({ syncingId: id }); const payload = { id: item.id, name: item.name, icon: item.icon, color: item.cloudCategory?.color || getDefaultCategoryColor(item.id, item.sortOrder), sortOrder: item.sortOrder, parentId: item.cloudCategory?.parentId ?? null, }; try { const functionName = item.status === 'missing' ? 'categoriesCreate' : 'categoriesUpdate'; const result = await callCategoryFunction( functionName, payload, ); if (!result.success) { throw new Error(result.message || `${item.actionText}失败`); } wx.showToast({ title: `${item.actionText}成功`, icon: 'success', }); await this.loadCategories(); } catch (error) { wx.showToast({ title: error instanceof Error ? error.message : `${item.actionText}失败`, icon: 'none', }); } finally { this.setData({ syncingId: '' }); } }, });