fix: 修复批量添加部员时工作组识别及列表排序问题
Deploy to Server / deploy (push) Successful in 1m51s

- 批量解析:智能区分字段分隔符(Tab优先,否则中英逗号/分号),避免竖线误伤;支持工作组被切到多个字段后自动合并
- 工作组匹配:新增多种分隔符与忽略空格匹配;当义工号字段非纯数字且命中工作组时自动识别为工作组(兼容3列格式:班级,姓名,工作组)
- 列表展示:前端二次按班级locale排序(中文+数字感知)+ 姓名,与后端SQL排序互补
This commit is contained in:
Trae Assistant
2026-10-05 08:18:45 +08:00
parent f42121d0bb
commit 13dbff885d
+64 -15
View File
@@ -112,6 +112,11 @@ export default function Users() {
group_ids: x.group_ids ?? [],
group_names: x.group_names ?? [],
}));
filled.sort((a, b) => {
const c = (a.class || '').localeCompare(b.class || '', 'zh-Hans-CN', { numeric: true });
if (c !== 0) return c;
return (a.name || '').localeCompare(b.name || '', 'zh-Hans-CN');
});
setList(filled);
setGroups(g);
} finally {
@@ -188,36 +193,80 @@ export default function Users() {
const rows: BatchParsedRow[] = [];
const lines = batchText.split(/\r?\n/);
const groupMap = new Map<string, number>();
for (const g of groups) groupMap.set(g.name.trim(), g.id);
const groupNameNormalized = new Map<string, string>();
for (const g of groups) {
const key = g.name.trim();
groupMap.set(key, g.id);
groupNameNormalized.set(key, g.name);
groupMap.set(g.name, g.id);
groupNameNormalized.set(g.name, g.name);
const noSpace = key.replace(/\s+/g, '');
if (noSpace !== key) {
groupMap.set(noSpace, g.id);
groupNameNormalized.set(noSpace, g.name);
}
}
const splitGroupNames = (s: string): string[] => {
const result: string[] = [];
if (!s) return result;
const segs = s.split(/[\/、||,,;;\s\u3000]+/);
for (const seg of segs) {
const n = seg.trim().replace(/\s+/g, '');
if (n) result.push(n);
}
return result;
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line.trim()) continue;
const parts = line.split(/[,,\t|;;]/).map((p) => p.trim());
const [cls, name, volunteer_no = '', groupsStr = ''] = parts;
let parts: string[];
if (line.includes('\t')) {
parts = line.split(/\t/).map((p) => p.trim());
} else {
parts = line.split(/[,,;;]/).map((p) => p.trim());
}
const [cls = '', name = '', volunteer_no = '', ...rest] = parts;
const groupsStr = rest.join(',');
const errors: string[] = [];
if (!cls) errors.push('缺少班级');
if (!name) errors.push('缺少姓名');
const groupIds: number[] = [];
const groupNames: string[] = [];
if (groupsStr) {
const rawNames = groupsStr.split(/[/、|\s]+/).map((x) => x.trim()).filter(Boolean);
for (const n of rawNames) {
const gid = groupMap.get(n);
if (gid === undefined) {
errors.push(`工作组「${n}」不存在`);
} else {
if (!groupIds.includes(gid)) {
groupIds.push(gid);
groupNames.push(n);
}
const rawNames = splitGroupNames([volunteer_no, groupsStr].filter(Boolean).join('/'));
const volunteerLooksLikeNo = /^\d+$/.test(volunteer_no.trim());
const realRawNames: string[] = [];
if (volunteer_no.trim() && !volunteerLooksLikeNo) {
const probe = splitGroupNames(volunteer_no);
const allMatch = probe.length > 0 && probe.every((n) => groupMap.has(n));
if (allMatch) {
realRawNames.push(...probe);
} else {
realRawNames.push(...splitGroupNames(groupsStr));
}
} else {
realRawNames.push(...splitGroupNames(groupsStr));
}
for (const n of realRawNames) {
if (!n) continue;
const gid = groupMap.get(n);
if (gid === undefined) {
errors.push(`工作组「${n}」不存在`);
} else {
if (!groupIds.includes(gid)) {
groupIds.push(gid);
const display = groupNameNormalized.get(n) || n;
if (!groupNames.includes(display)) groupNames.push(display);
}
}
}
const vol = volunteerLooksLikeNo ? volunteer_no : '';
rows.push({
idx: i,
class: cls || '',
name: name || '',
volunteer_no,
volunteer_no: vol,
group_ids: groupIds,
group_names: groupNames,
raw: line,