fix(audit): profile_upsert top-key merge, scoring badge recordIds, zc item contract
- profile_upsert now merges top-level keys (info/resume/zongce are independent sub-documents); previously any page saving its own slice silently wiped the other two (profile save lost zongce rules, assessment save lost resume, resume save lost zongce) - ZcItem gains recordIds: aggregated cap items (wcap/ccap/kcap/gcap/hcap) expose the records they aggregate, so the evidence gallery scored/unscored badge works (matchedIds was always empty before) - add regression coverage notes; www rebuilt
This commit is contained in:
+8
-1
@@ -1215,7 +1215,14 @@ function listFootprints(d, query) {
|
||||
case 'profile_upsert': {
|
||||
const p = payload;
|
||||
// 单行语义:更新现有行或插入首行
|
||||
const content = JSON.stringify(p.content ?? {});
|
||||
// 顶层 key 合并:info / resume / zongce 是相互独立的子文档,各页面只提交自己负责的
|
||||
// key(ProfilePage 只有 info、AssessmentPage 有 info+zongce…),整行替换会让其他
|
||||
// key 神秘消失;按顶层 key 合并后互不影响,key 内部整体替换(仍是全量提交该子文档)。
|
||||
const existingRow = d.prepare('SELECT * FROM profile WHERE deleted_at IS NULL ORDER BY id DESC LIMIT 1').get();
|
||||
let prev = {};
|
||||
try { prev = JSON.parse(existingRow?.content || '{}'); } catch { prev = {}; }
|
||||
const merged = { ...prev, ...(p.content ?? {}) };
|
||||
const content = JSON.stringify(merged);
|
||||
const existing = d.prepare('SELECT * FROM profile WHERE deleted_at IS NULL ORDER BY id DESC LIMIT 1').get();
|
||||
withTx(d, () => {
|
||||
const h = hlcNext(d);
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface ZcItem {
|
||||
overridden?: boolean;
|
||||
note?: string;
|
||||
fromRecord?: number;
|
||||
/** 聚合条款(ccap/kcap/…)对应的记录 id 列表,用于标注哪些记录参与计分 */
|
||||
recordIds?: number[];
|
||||
}
|
||||
export interface ZcSection { key: string; label: string; cap: number; items: ZcItem[]; subtotal: number }
|
||||
export interface ZcResult { sections: ZcSection[]; total: number; gpa: number | null }
|
||||
@@ -200,7 +202,7 @@ export function computeZongce(opts: {
|
||||
wItems.push(mk('w' + rec.id, rec.title, rec.type_name + ' · ' + (rec.level ?? '') + ' ' + (rec.award_grade ?? ''), auto, undefined, rec.id));
|
||||
}
|
||||
const wCap = round2(Math.min(wItems.reduce((a, x) => a + x.score, 0), 5));
|
||||
sections.push(sec('sports', '文体素质', 10, [...sportsItems, { itemKey: 'wcap', title: '文体类加分(上限 5 分)', source: wItems.length + ' 条记录', score: wCap, note: wItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录' }]));
|
||||
sections.push(sec('sports', '文体素质', 10, [...sportsItems, { itemKey: 'wcap', title: '文体类加分(上限 5 分)', source: wItems.length + ' 条记录', score: wCap, note: wItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录', recordIds: wItems.map(x => x.fromRecord).filter(Boolean) as number[] }]));
|
||||
|
||||
// ---- 拓展性素质(17):竞赛获奖(10) + 学生工作(4) + 公益(3) ----
|
||||
const compItems: ZcItem[] = [];
|
||||
@@ -231,9 +233,9 @@ export function computeZongce(opts: {
|
||||
}
|
||||
const pubCap = round2(Math.min(pubItems.reduce((a, x) => a + x.score, 0), 3));
|
||||
sections.push(sec('expand', '拓展性素质', 17, [
|
||||
{ itemKey: 'ccap', title: '竞赛获奖(上限 10 分)', source: compItems.length + ' 条记录', score: compCap, note: compItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录' },
|
||||
{ itemKey: 'kcap', title: '担任学生工作(上限 4 分)', source: workItems.length + ' 条记录', score: workCap, note: workItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录' },
|
||||
{ itemKey: 'gcap', title: '公益活动(上限 3 分)', source: pubItems.length + ' 条记录', score: pubCap, note: pubItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录' },
|
||||
{ itemKey: 'ccap', title: '竞赛获奖(上限 10 分)', source: compItems.length + ' 条记录', score: compCap, note: compItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录', recordIds: compItems.map(x => x.fromRecord).filter(Boolean) as number[] },
|
||||
{ itemKey: 'kcap', title: '担任学生工作(上限 4 分)', source: workItems.length + ' 条记录', score: workCap, note: workItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录', recordIds: workItems.map(x => x.fromRecord).filter(Boolean) as number[] },
|
||||
{ itemKey: 'gcap', title: '公益活动(上限 3 分)', source: pubItems.length + ' 条记录', score: pubCap, note: pubItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录', recordIds: pubItems.map(x => x.fromRecord).filter(Boolean) as number[] },
|
||||
]));
|
||||
|
||||
// ---- 奖惩分(±5) ----
|
||||
@@ -268,7 +270,7 @@ export function computeZongce(opts: {
|
||||
rwItems.push(mk('h' + rec.id, rec.title, source, auto, undefined, rec.id));
|
||||
}
|
||||
const rwCap = round2(Math.max(Math.min(rwItems.reduce((a, x) => a + x.score, 0), 5), -5));
|
||||
sections.push(sec('rewards', '奖惩分', 5, [{ itemKey: 'hcap', title: '奖惩合计(±5 分)', source: rwItems.length + ' 条记录', score: rwCap, note: rwItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录' }]));
|
||||
sections.push(sec('rewards', '奖惩分', 5, [{ itemKey: 'hcap', title: '奖惩合计(±5 分)', source: rwItems.length + ' 条记录', score: rwCap, note: rwItems.map(x => x.title + ' ' + x.score + '分').join(';') || '无匹配记录', recordIds: rwItems.map(x => x.fromRecord).filter(Boolean) as number[] }]));
|
||||
|
||||
// 手动条目
|
||||
for (const m of ov.manual ?? []) {
|
||||
|
||||
@@ -261,7 +261,10 @@ export function AssessmentPage() {
|
||||
// 参与计分的记录 id(画廊中标注「计分」)
|
||||
const matchedSet = useMemo(() => {
|
||||
const s = new Set<number>();
|
||||
result.sections.forEach(sec => sec.items.forEach(it => { if (it.fromRecord) s.add(it.fromRecord); }));
|
||||
result.sections.forEach(sec => sec.items.forEach(it => {
|
||||
if (it.fromRecord) s.add(it.fromRecord);
|
||||
for (const rid of it.recordIds ?? []) s.add(rid);
|
||||
}));
|
||||
return s;
|
||||
}, [result]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user