feat: 分类管理页开发

This commit is contained in:
R524809
2026-04-23 12:08:07 +08:00
parent b2ab123b4f
commit 5106dbbd7c
21 changed files with 1034 additions and 151 deletions
@@ -0,0 +1,259 @@
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<T> = {
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<string, CloudCategory>();
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<T>(
name: string,
data?: Record<string, unknown>,
): Promise<CloudFunctionResult<T>> {
const response = (await wx.cloud.callFunction({
name,
data: data || {},
})) as { result?: CloudFunctionResult<T> };
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<CloudCategory[]>('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<boolean>((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<CloudCategory>(
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: '' });
}
},
});