feat(form): 表单支持多选接收对象 / 附件上传 / 全字段渲染
build / build (push) Successful in 2m50s

配合后端「发布表单与模块页对齐」:前端补齐三类控件与序列化。

- FormCard: 新增 multiselect 与 scopes 多选控件(「全部 XX」哨兵恒为首项,
  选中后仍可叠加具体项;提交时若只有哨兵则按 scopeType 折成
  [{scopeType:1,scopeId:0,scopeName}]);新增 file 附件控件(带登录态 POST
  multipart 到 uploadPath,遇登录跳转先预热模块会话再重试)
- FormCard: allFields 的表单渲染全部字段(不再只渲染缺失必填);file 控件
  显示已传文件名并可移除
- types: widget 联合加 multiselect/file,FormField 补 joinSeparator/uploadPath/
  multiple/allToken/allTokenLabel/scopeType,FormRequest 补 allFields
- module-session: parseOptionItems 支持 labelKey 用「+」拼多字段
  (如 buildingName+roomNo →「1 号楼 101」),组织树自动拍平到叶子

验证:.scratch/verify-module-publish-form.mjs 真机断言三个发布表单
(notice 24/24、dorm 35/35、activity 33/33 通过),npm run build 通过
This commit is contained in:
lijing
2026-10-12 22:05:16 +08:00
parent 2b114829ab
commit da347f21b5
3 changed files with 262 additions and 12 deletions
+22 -2
View File
@@ -147,11 +147,20 @@ export function parseOptionItems(
const vk = valueKey || "id";
const lk = labelKey || "name";
const out: { value: string; label: string }[] = [];
for (const item of arr) {
// 组织树(年级→班级 / 学部→年级→班级)要拍平成叶子,否则下拉里只会出现「年级」这一层的 id。
// 判定:任一项带非空 children 数组即视为树;否则按平铺字典处理(多数字典接口是平铺的)。
const flat = arr.some(it => it && Array.isArray((it as any).children) && (it as any).children.length)
? flattenLeaves(arr)
: arr;
for (const item of flat) {
if (item == null || typeof item !== "object") continue;
const v = (item as any)[vk];
if (v === undefined || v === null || String(v) === "") continue;
const l = (item as any)[lk];
// labelKey 支持用「+」拼多个字段(如房间的 buildingName+roomNo →「1号楼 305」),
// 因为这类 VO 里没有现成的展示名,靠单一字段会出现「同名房间分不清楼栋」。
const l = lk.includes("+")
? lk.split("+").map(k => (item as any)[k.trim()]).filter(V => V !== undefined && V !== null && String(V) !== "").join(" ")
: (item as any)[lk];
out.push({
value: String(v),
label: l === undefined || l === null || String(l) === "" ? String(v) : String(l),
@@ -160,6 +169,17 @@ export function parseOptionItems(
return out;
}
/** 把「组→子」的树拍平成叶子列表(叶子 = 没有非空 children 的节点) */
function flattenLeaves(nodes: any[]): any[] {
const out: any[] = [];
for (const n of nodes || []) {
const kids = n && Array.isArray(n.children) ? n.children : null;
if (kids && kids.length) out.push(...flattenLeaves(kids));
else out.push(n);
}
return out;
}
/** 递归找第一个数组(限深,容忍 data 外面再包一层 list/records/rows) */
function findArray(node: any, depth: number): any[] | null {
if (node == null || depth > 3) return null;