diff --git a/backed/internal/handler/leave_apply.go b/backed/internal/handler/leave_apply.go index b50eafa..8a66857 100644 --- a/backed/internal/handler/leave_apply.go +++ b/backed/internal/handler/leave_apply.go @@ -691,8 +691,8 @@ func fillXsgzFieldValues(fields []map[string]interface{}, req *ExecuteLeaveApply // 第一轮:按标签匹配 matched := false for _, f := range fields { - if xsgzUploadComponentTypes[mapStr(f, "componentType")] { - continue // 上传类字段不该被文本值覆盖 + if xsgzNonTextFillable(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, "说明") { @@ -736,6 +736,8 @@ func checkXsgzUnfilledRequired(fields []map[string]interface{}) []string { } // 上传类字段由 uploadFiles 承载,没有 value 属正常, // 不应算作「未填写」(本平台不代传附件,由学校端按需处理)。 + // 注意:地区类(regions)不能在此跳过——它就是要靠 value 承载, + // 我们没有数据就必然提交空值,必须拦下并告知用户。 if xsgzUploadComponentTypes[mapStr(f, "componentType")] { continue } @@ -761,6 +763,23 @@ var xsgzUploadComponentTypes = map[string]bool{ "smoothSignature": true, } +// xsgzRegionComponentType 地区选择组件。 +// +// 前端 Ie() 对它的处理是把「选中值」与「详情」拼接后写入 value: +// +// s = (e[fieldId+"_regionsDetail"] ?? e[fieldId+"_detail"])?.replace(/,/g,",")?.trim() || "" +// o.value = 选中值 ? (s ? `${选中值},${s}` : 选中值) : (s || n) +// +// 本平台不提供地区选择,也就没有 _regionsDetail/_detail 可拼接, +// 因此不能把「请假原因」当成地区值写进去——那会产生学校端无法解析的 +// 非法地区串。这类字段一律留空,由学校端校验或用户在学工系统内补填。 +const xsgzRegionComponentType = "regions" + +// xsgzNonTextFillable 判断字段是否不应被文本值(如请假原因)填充。 +func xsgzNonTextFillable(componentType string) bool { + return xsgzUploadComponentTypes[componentType] || componentType == xsgzRegionComponentType +} + // normalizeXsgzFields 按前端 Ie()(studentForm-Dx5Fpjfs.js)的语义净化字段定义。 // // 前端对每个字段按 componentType 分三支处理,直接把表单值映射到提交对象: @@ -785,6 +804,14 @@ func normalizeXsgzFields(fields []map[string]interface{}) { delete(f, "value") continue } + if ct == xsgzRegionComponentType { + // 地区类:前端 value 是「选中值,详情」的拼接串。 + // 我们既无选中值也无 _regionsDetail/_detail,若残留 value + // 会被学校端当作非法地区解析,故清空但保留键(空值由 + // xsgzFlatten 跳过,不会提交)。 + f["value"] = "" + continue + } // 其余:若 value 是数组,按前端规则 join(",")(多选拼接) if arr, ok := f["value"].([]interface{}); ok { parts := make([]string, 0, len(arr)) diff --git a/backed/internal/handler/leave_xsgz_fill_test.go b/backed/internal/handler/leave_xsgz_fill_test.go index 448d0e6..1b92223 100644 --- a/backed/internal/handler/leave_xsgz_fill_test.go +++ b/backed/internal/handler/leave_xsgz_fill_test.go @@ -186,3 +186,38 @@ func TestCheckUnfilledSkipsUpload(t *testing.T) { t.Fatalf("上传类不应算缺失,实际: %v", missing) } } + +// regions 地区字段:前端 value 是「选中值,详情」拼接串,本平台无此数据, +// 不能把请假原因写进去(会产生非法地区串)。 +func TestRegionsFieldNotFilledWithReason(t *testing.T) { + fields := []map[string]interface{}{ + {"fieldId": "r1", "componentType": "regions", "fieldName": "请假原因", "required": false}, + } + req := &ExecuteLeaveApplyRequest{LeaveReason: "感冒"} + fillXsgzFieldValues(fields, req) + if v, ok := fields[0]["value"]; ok && v != "" { + t.Fatalf("regions 字段不应被填入请假原因,实际 %v", v) + } +} + +// regions 字段残留的非法 value 应被清空。 +func TestNormalizeRegionsClearsValue(t *testing.T) { + f := map[string]interface{}{ + "fieldId": "r1", "componentType": "regions", "value": "山东省,泰安市,泰山区", + } + normalizeXsgzFields([]map[string]interface{}{f}) + if got := f["value"]; got != "" { + t.Fatalf("regions 的 value 应被清空,实际 %v", got) + } +} + +// regions 必填但没有可用数据时,应被预检拦下(而不是发出去被学校端拒绝)。 +func TestRegionsRequiredTriggersPrecheck(t *testing.T) { + fields := []map[string]interface{}{ + {"fieldId": "r1", "componentType": "regions", "fieldName": "籍贯", "required": true}, + } + missing := checkXsgzUnfilledRequired(fields) + if len(missing) != 1 || missing[0] != "籍贯" { + t.Fatalf("必填 regions 无值应被预检拦下,实际: %v", missing) + } +}