diff --git a/apps/admin-web/src/pages/StorePackageAuditsPage.tsx b/apps/admin-web/src/pages/StorePackageAuditsPage.tsx
index f728ce1..ecdcd9f 100644
--- a/apps/admin-web/src/pages/StorePackageAuditsPage.tsx
+++ b/apps/admin-web/src/pages/StorePackageAuditsPage.tsx
@@ -42,6 +42,31 @@ function imageSignature(pkg: StorePackageItemDto | StorePackageViewDto) {
return normalizeStorePackageImageUrls(pkg).join('|');
}
+type FieldChange = { label: string; old: string; now: string };
+
+/** 逐字段比较套餐内容,返回发生变化的字段明细(用于“变动的地方详细列出”) */
+function fieldChanges(
+ live: StorePackageItemDto | StorePackageViewDto,
+ proposed: StorePackageItemDto | StorePackageViewDto,
+): FieldChange[] {
+ const changes: FieldChange[] = [];
+ const text = (v: string | number | null | undefined) => (v ?? '').toString().trim();
+ const pushIf = (label: string, oldV: string, newV: string) => {
+ if (oldV !== newV) changes.push({ label, old: oldV || '(空)', now: newV || '(空)' });
+ };
+ pushIf('价格', `¥${text(live.price)}`, `¥${text(proposed.price)}`);
+ pushIf('套餐名称', text(live.name), text(proposed.name));
+ pushIf('菜品内容', text(live.dishes), text(proposed.dishes));
+ pushIf('可用时间', text(live.usableTime), text(proposed.usableTime));
+ pushIf('其他说明', text(live.otherNotes), text(proposed.otherNotes));
+ const liveImgs = normalizeStorePackageImageUrls(live);
+ const proposedImgs = normalizeStorePackageImageUrls(proposed);
+ if (imageSignature(live) !== imageSignature(proposed)) {
+ changes.push({ label: '图片', old: `${liveImgs.length} 张`, now: `${proposedImgs.length} 张` });
+ }
+ return changes;
+}
+
function diffPackages(live: StorePackageViewDto[], proposed: StorePackageItemDto[]) {
const liveMap = new Map(live.map((p, i) => [packageKey(p, i), p]));
const proposedMap = new Map(proposed.map((p, i) => [packageKey(p, i), p]));
@@ -51,6 +76,7 @@ function diffPackages(live: StorePackageViewDto[], proposed: StorePackageItemDto
change: 'added' | 'removed' | 'changed' | 'unchanged';
live?: StorePackageViewDto;
proposed?: StorePackageItemDto;
+ changes?: FieldChange[];
}> = [];
for (const key of keys) {
@@ -67,7 +93,13 @@ function diffPackages(live: StorePackageViewDto[], proposed: StorePackageItemDto
(l.usableTime ?? '') !== (p.usableTime ?? '') ||
(l.otherNotes ?? '') !== (p.otherNotes ?? '') ||
imageSignature(l) !== imageSignature(p);
- rows.push({ key, change: changed ? 'changed' : 'unchanged', live: l, proposed: p });
+ rows.push({
+ key,
+ change: changed ? 'changed' : 'unchanged',
+ live: l,
+ proposed: p,
+ changes: changed ? fieldChanges(l, p) : undefined,
+ });
}
}
return rows;
@@ -84,10 +116,12 @@ function PackageDetailCard({
title,
pkg,
change,
+ changes,
}: {
title?: string;
pkg: StorePackageItemDto | StorePackageViewDto;
change?: keyof typeof CHANGE_LABELS;
+ changes?: FieldChange[];
}) {
const images = normalizeStorePackageImageUrls(pkg);
const meta = change ? CHANGE_LABELS[change] : null;
@@ -149,6 +183,33 @@ function PackageDetailCard({
) : (