xsgz 请假字段填充改为「标签匹配 + 必填文本兜底」,降低对字段命名的依赖
从 transformFields-D3I9yUm6.js 反推出 form_fields_for_add 的真实结构:
{ fieldId, fieldName, componentType, fieldType, required, options[] }
前端 transformFields 把它转成 fieldEn=fieldId / fieldZh=fieldName 后提交,
且请假原因并非硬编码字段,而是学校后台动态配置的。
原先只按「原因/事由」关键词填值,若学校把字段命名为"申请理由说明"
"补充信息"等就会漏填,导致学校端校验失败。
改为两轮策略:
1. 优先按标签命中(原因/事由/说明);
2. 未命中时,用「必填的文本类字段」(TEXT/TEXTAREA/LONGTEXT)兜底填入请假原因。
该校表单通常只有一个原因字段,兜底可覆盖自定义命名;
选择类字段(RADIO/CHECKBOX 等)不猜测,留空由学校端校验。
新增 4 个单测覆盖:关键词命中、自定义命名、兜底逻辑、空输入不 panic。
This commit is contained in:
@@ -635,22 +635,45 @@ func executeXsgzLeaveApply(c *fiber.Ctx, user *model.SysUser, req *ExecuteLeaveA
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillXsgzFieldValues 把用户填写的值写回 xsgz 表单字段定义的 value 字段。
|
||||
// fillXsgzFieldValues 把用户填写的值写回 xsgz 表单字段定义。
|
||||
//
|
||||
// xsgz 提交的 fields 是「字段定义 + 当前值」的数组;本平台表单字段与 xsgz 字段
|
||||
// 命名不同,这里按常见语义做映射:请假原因 → 含"原因/事由"的字段。
|
||||
// 未能识别的字段保持原值(空),由学校端校验。
|
||||
// 依据前端 transformFields 的字段结构:
|
||||
//
|
||||
// form_fields_for_add 返回 { fieldId, fieldName, componentType, fieldType, required, options[] }
|
||||
//
|
||||
// 提交时前端遍历字段,把用户输入按 fieldId 写回 value。本平台只有「请假原因」
|
||||
// 一项自由文本,因此:
|
||||
// 1. 优先按标签(fieldName 含"原因/事由/说明")精确匹配;
|
||||
// 2. 未命中时,退化为「必填的文本类字段」(fieldType 为 TEXT/TEXTAREA/LONGTEXT)
|
||||
// 一律填入请假原因——该校表单通常只有一个原因字段,这样可以避免因
|
||||
// 学校自定义字段名(如"请假事由说明""申请理由")导致漏填。
|
||||
//
|
||||
// 选择类字段(RADIO/CHECKBOX/SELECT 等)不猜测,留空由学校端校验。
|
||||
func fillXsgzFieldValues(fields []map[string]interface{}, req *ExecuteLeaveApplyRequest) {
|
||||
if len(fields) == 0 {
|
||||
if len(fields) == 0 || req.LeaveReason == "" {
|
||||
return
|
||||
}
|
||||
textTypes := map[string]bool{"TEXT": true, "TEXTAREA": true, "LONGTEXT": true}
|
||||
|
||||
// 第一轮:按标签匹配
|
||||
matched := false
|
||||
for _, f := range fields {
|
||||
label := strings.ToLower(mapStr(f, "fieldZh") + mapStr(f, "fieldName") + mapStr(f, "label"))
|
||||
switch {
|
||||
case strings.Contains(label, "原因") || strings.Contains(label, "事由"):
|
||||
if req.LeaveReason != "" {
|
||||
f["value"] = req.LeaveReason
|
||||
}
|
||||
if strings.Contains(label, "原因") || strings.Contains(label, "事由") || strings.Contains(label, "说明") {
|
||||
f["value"] = req.LeaveReason
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
return
|
||||
}
|
||||
|
||||
// 第二轮:必填文本字段兜底
|
||||
for _, f := range fields {
|
||||
ft := strings.ToUpper(mapStr(f, "fieldType"))
|
||||
required, _ := f["required"].(bool)
|
||||
if required && textTypes[ft] {
|
||||
f["value"] = req.LeaveReason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package handler
|
||||
|
||||
import "testing"
|
||||
|
||||
// fillXsgzFieldValues:优先按标签匹配,未命中时用必填文本字段兜底。
|
||||
func TestFillXsgzFieldValues(t *testing.T) {
|
||||
req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒发烧需休息"}
|
||||
|
||||
t.Run("按标签命中原因字段", func(t *testing.T) {
|
||||
fields := []map[string]interface{}{
|
||||
{"fieldId": "f1", "fieldName": "请假原因", "fieldType": "TEXTAREA", "required": true},
|
||||
{"fieldId": "f2", "fieldName": "紧急联系人", "fieldType": "TEXT", "required": true},
|
||||
}
|
||||
fillXsgzFieldValues(fields, req)
|
||||
if fields[0]["value"] != req.LeaveReason {
|
||||
t.Fatalf("原因字段未被填充: %v", fields[0])
|
||||
}
|
||||
if _, ok := fields[1]["value"]; ok {
|
||||
t.Fatalf("非原因字段不应被填充: %v", fields[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("学校自定义字段名时用必填文本兜底", func(t *testing.T) {
|
||||
fields := []map[string]interface{}{
|
||||
{"fieldId": "f1", "fieldName": "申请理由说明", "fieldType": "TEXTAREA", "required": true},
|
||||
{"fieldId": "f2", "fieldName": "备注", "fieldType": "TEXT", "required": false},
|
||||
}
|
||||
fillXsgzFieldValues(fields, req)
|
||||
// "说明" 命中第一轮
|
||||
if fields[0]["value"] != req.LeaveReason {
|
||||
t.Fatalf("含'说明'的字段应被填充: %v", fields[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("无关键词时必填文本字段兜底", func(t *testing.T) {
|
||||
fields := []map[string]interface{}{
|
||||
{"fieldId": "f1", "fieldName": "补充信息", "fieldType": "TEXTAREA", "required": true},
|
||||
{"fieldId": "f2", "fieldName": "选择项", "fieldType": "RADIO", "required": true},
|
||||
}
|
||||
fillXsgzFieldValues(fields, req)
|
||||
if fields[0]["value"] != req.LeaveReason {
|
||||
t.Fatalf("必填文本字段应兜底填充: %v", fields[0])
|
||||
}
|
||||
if _, ok := fields[1]["value"]; ok {
|
||||
t.Fatalf("选择类字段不应猜测填充: %v", fields[1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空 fields 与空原因不 panic", func(t *testing.T) {
|
||||
fillXsgzFieldValues(nil, req)
|
||||
fillXsgzFieldValues([]map[string]interface{}{}, req)
|
||||
fillXsgzFieldValues([]map[string]interface{}{{"fieldId": "f1"}}, &ExecuteLeaveApplyRequest{})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user