xsgz 字段提交按前端 Ie() 语义清洗:上传类去 value、多选 join 逗号

从 studentForm-Dx5Fpjfs.js 的 Ie() 看到字段有三条分支:

  regions:                                   value = 拼接地名
  imageUpload / fileUpload / smoothSignature: o.uploadFiles = 文件列表;不设 value
  其余:                                       o.value = Array.isArray(n) ? n.join(",") : n

我们的实现只有一条路——直接写 value,导致两个偏差:

1. 上传类字段(图片/文件/手写签名)被塞了文本 value,前端根本不会这样提交,
   且从未提供 uploadFiles。学校端若要求附件,行为不可预期。
2. 多选类字段若 value 是数组,xsgzFlatten 会展开成 fields[0].value[0]
   / fields[0].value[1],学校端按 value 取字符串时读不到;
   前端是 join(",") 成单个字符串。

改动:
- 新增 normalizeXsgzFields,按上述三分支净化字段定义:
  上传类删除 value(本平台不代传附件,不臆造 uploadFiles),
  数组 value 按前端规则 join(",")(跳过 nil 与空串);
- 请假、销假两条提交路径都接入;
- fillXsgzFieldValues 跳过上传类字段,避免把「请假原因」写进附件字段;
- checkXsgzUnfilledRequired 不把上传类必填字段算作缺失
  (它们由 uploadFiles 承载,无 value 属正常),避免误拦。

测试:三种上传类型去 value、多选 join、数组含 nil/空串、普通字段不受影响、
空输入不 panic、填充跳过上传类、预检不误拦上传类。
This commit is contained in:
becomer
2026-10-17 18:23:06 +08:00
parent 96de022871
commit bb4a1d2325
3 changed files with 143 additions and 0 deletions
+2
View File
@@ -535,6 +535,8 @@ func executeXsgzLeaveCancel(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeave
f["value"] = req.Address
}
}
// 按前端 Ie() 语义清洗:上传类去 value、多选数组 join(",")
normalizeXsgzFields(formFields)
// 提交前预检:必填项无法自动填写时明确指出,避免含糊失败
if missing := checkXsgzUnfilledRequired(formFields); len(missing) > 0 {
msg := "该校销假表单还有必填项无法自动填写:" + strings.Join(missing, "、") +
+58
View File
@@ -612,6 +612,8 @@ func executeXsgzLeaveApply(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeaveA
formFields = nil
}
fillXsgzFieldValues(formFields, req)
// 按前端 Ie() 语义清洗:上传类去 value、多选数组 join(",")
normalizeXsgzFields(formFields)
// 提交前预检:学校表单可能包含本平台无法提供值的必填字段
// (如"是否离校"单选)。与其提交后得到含糊失败,不如明确指出。
@@ -689,6 +691,9 @@ func fillXsgzFieldValues(fields []map[string]interface{}, req *ExecuteLeaveApply
// 第一轮:按标签匹配
matched := false
for _, f := range fields {
if xsgzUploadComponentTypes[mapStr(f, "componentType")] {
continue // 上传类字段不该被文本值覆盖
}
label := strings.ToLower(mapStr(f, "fieldZh") + mapStr(f, "fieldName") + mapStr(f, "label"))
if strings.Contains(label, "原因") || strings.Contains(label, "事由") || strings.Contains(label, "说明") {
f["value"] = req.LeaveReason
@@ -729,6 +734,11 @@ func checkXsgzUnfilledRequired(fields []map[string]interface{}) []string {
if !required {
continue
}
// 上传类字段由 uploadFiles 承载,没有 value 属正常,
// 不应算作「未填写」(本平台不代传附件,由学校端按需处理)。
if xsgzUploadComponentTypes[mapStr(f, "componentType")] {
continue
}
if v, ok := f["value"]; ok && v != nil && fmt.Sprintf("%v", v) != "" {
continue
}
@@ -743,3 +753,51 @@ func checkXsgzUnfilledRequired(fields []map[string]interface{}) []string {
}
return missing
}
// xsgzUploadComponentTypes 前端 Ie() 里走 uploadFiles 分支的组件类型。
var xsgzUploadComponentTypes = map[string]bool{
"imageUpload": true,
"fileUpload": true,
"smoothSignature": true,
}
// normalizeXsgzFields 按前端 Ie()(studentForm-Dx5Fpjfs.js)的语义净化字段定义。
//
// 前端对每个字段按 componentType 分三支处理,直接把表单值映射到提交对象:
//
// regions: value = 拼接后的地名(含 _regionsDetail / _detail 后缀处理)
// imageUpload / fileUpload / smoothSignature:
// o.uploadFiles = 文件列表(并 delete content/url),**不设 value**
// 其余: o.value = Array.isArray(n) ? n.join(",") : n
//
// 我们的实现只填了 value,存在两个问题:
// 1. 上传类字段被塞了 value(前端不会这样提交),且完全没有 uploadFiles;
// 2. 多选类字段若是数组,前端会 join(","),我们直接保留数组,
// 经 xsgzFlatten 展开成 fields[n].value[k] 这种学校端读不到的键。
//
// 本函数只做「清洗」:去掉不该出现的内容,不臆造用户未提供的数据。
func normalizeXsgzFields(fields []map[string]interface{}) {
for _, f := range fields {
ct := mapStr(f, "componentType")
if xsgzUploadComponentTypes[ct] {
// 上传类:value 不应存在(由 uploadFiles 承载)。
// 本平台不代传附件,保持为空即可。
delete(f, "value")
continue
}
// 其余:若 value 是数组,按前端规则 join(",")(多选拼接)
if arr, ok := f["value"].([]interface{}); ok {
parts := make([]string, 0, len(arr))
for _, v := range arr {
if v == nil {
continue
}
s := fmt.Sprintf("%v", v)
if s != "" {
parts = append(parts, s)
}
}
f["value"] = strings.Join(parts, ",")
}
}
}
@@ -103,3 +103,86 @@ func TestCheckXsgzUnfilledRequired(t *testing.T) {
}
})
}
// normalizeXsgzFields:按前端 Ie() 语义清洗,避免提交学校端读不到的键。
func TestNormalizeXsgzFields(t *testing.T) {
t.Run("上传类字段去掉 value", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f1", "componentType": "imageUpload", "value": "脏数据",
}
normalizeXsgzFields([]map[string]interface{}{f})
if _, ok := f["value"]; ok {
t.Fatal("上传类字段不应保留 value(前端走 uploadFiles)")
}
})
t.Run("三种上传类型都处理", func(t *testing.T) {
for _, ct := range []string{"imageUpload", "fileUpload", "smoothSignature"} {
f := map[string]interface{}{"fieldId": "x", "componentType": ct, "value": "v"}
normalizeXsgzFields([]map[string]interface{}{f})
if _, ok := f["value"]; ok {
t.Fatalf("%s 应去掉 value", ct)
}
}
})
t.Run("多选数组 join 成逗号串", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f2", "componentType": "checkbox",
"value": []interface{}{"a", "b", "c"},
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "a,b,c" {
t.Fatalf("多选应 join 为 a,b,c,实际 %v", got)
}
})
t.Run("数组中的 nil 与空串被跳过", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f3", "componentType": "checkbox",
"value": []interface{}{"a", nil, "", "b"},
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "a,b" {
t.Fatalf("应跳过 nil 与空串,实际 %v", got)
}
})
t.Run("普通字符串字段不受影响", func(t *testing.T) {
f := map[string]interface{}{
"fieldId": "f4", "componentType": "TEXT", "value": "感冒",
}
normalizeXsgzFields([]map[string]interface{}{f})
if got := f["value"]; got != "感冒" {
t.Fatalf("普通字段不应被改动,实际 %v", got)
}
})
t.Run("空输入不 panic", func(t *testing.T) {
normalizeXsgzFields(nil)
normalizeXsgzFields([]map[string]interface{}{})
})
}
// fillXsgzFieldValues 不应把请假原因写进上传类字段。
func TestFillSkipsUploadFields(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "componentType": "imageUpload", "fieldName": "请假原因附件", "required": true},
}
req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒"}
fillXsgzFieldValues(fields, req)
if _, ok := fields[0]["value"]; ok {
t.Fatalf("上传类字段不应被填充 value,实际 %v", fields[0]["value"])
}
}
// 上传类必填字段不应触发「必填未填」拦截。
func TestCheckUnfilledSkipsUpload(t *testing.T) {
fields := []map[string]interface{}{
{"fieldId": "f1", "componentType": "imageUpload", "fieldName": "附件", "required": true},
{"fieldId": "f2", "componentType": "TEXT", "fieldName": "请假原因", "required": true, "value": "感冒"},
}
if missing := checkXsgzUnfilledRequired(fields); len(missing) != 0 {
t.Fatalf("上传类不应算缺失,实际: %v", missing)
}
}