diff --git a/src/pages/Container/RegisterMore/index.js b/src/pages/Container/RegisterMore/index.js index ca7b34d..3264f2c 100644 --- a/src/pages/Container/RegisterMore/index.js +++ b/src/pages/Container/RegisterMore/index.js @@ -1,360 +1,141 @@ import React, { useEffect, useState } from "react"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; -import { message, Empty } from "antd"; +import { + Button, + Checkbox, + Col, + Empty, + Form, + Input, + InputNumber, + message, + Row, + Space, + Upload, +} from "antd"; +import { UploadOutlined } from "@ant-design/icons"; import { NS_REGISTER } from "~/enumerate/namespace"; +import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions"; import auditLookImage from "~/enumerate/img/auditLook.png"; import auditBkImage from "~/enumerate/img/audit.png"; import successIcon from "~/enumerate/img/successIcon.png"; -import { normalizeUrl } from "~/utils/validators"; +import { + creditCodeRule, + nonNegativeIntegerRule, + normalizeUrl, + phoneRule, + positiveNumberRule, + urlRule, +} from "~/utils/validators"; import { resolveRegisterQueryParams } from "~/utils/resolveRegisterQueryParams"; import "./index.less"; -// ===== 2026-08-08 依据原型 V1.6 register.html「认证信息填报页」1:1 还原 ===== -// 变更原因:原型为原生 HTML/CSS 页面,需按原型的样式、布局、边框、字体、颜色、按钮、响应式做 1:1 还原。 -// 变更逻辑: -// 1) 前端改用原生受控表单(formData state + 原生 input/textarea/checkbox + 自定义校验), -// 字段名直接对齐后端 OrgInfo 字段,规避二次映射; -// 2) 布局采用原型 .grid 两列 + .wide 全宽字段,营业执照/拟申请业务范围/单位基本情况介绍单独成行居左; -// 3) 所有字段均加红星必填标注(含资质证书编号); -// 4) 边框容器 .card 居中、内容居左;保留 Steps 三步流程与 orgInfoSave/syncUserToGBS 提交逻辑。 -// 变更理由:最小侵入,样式与原型一一对应;响应式 @media(max-width:760px) 单列适配。 -// 业务范围选项(与后端枚举 code 严格匹配:OrgBusinessScopeDeviceRefDO 等定义的行业 code) -// 2026-08-08 改为 {value,label}:value 用后端枚举 code(存储/渲染的值),label 仅作展示, -// 不再以中文名作为存储值,保证与后端枚举一致。 -const BUSINESS_SCOPE_OPTIONS = [ - { value: "COAL_MINING", label: "煤炭开采业" }, - { value: "METAL_NONMETAL_MINING", label: "金属、非金属矿及其他矿采选业" }, - { value: "ONSHORE_OIL_GAS", label: "陆地石油和天然气开采业" }, - { value: "ONSHORE_OIL_GAS_PIPELINE", label: "陆上油气管道运输业" }, - { - value: "PETROCHEMICAL_CHEMICAL_PHARMACEUTICAL", - label: "石油加工业,化学原料、化学品及医药制造业", - }, - { value: "FIREWORKS_FIRECRACKERS", label: "烟花爆竹制造业" }, - { value: "METAL_SMELTING", label: "金属冶炼" }, -]; +// 联系人及电话:单输入框「联系人/电话」,提交时拆为 contactName + contactPhone +const CONTACT_SEP = "/"; + +const mergeContactNamePhone = (name, phone) => { + if (!name && !phone) return undefined; + return [name, phone].filter(Boolean).join(CONTACT_SEP); +}; + +const splitContactNamePhone = (value) => { + if (!value) return { contactName: undefined, contactPhone: undefined }; + const idx = String(value).indexOf(CONTACT_SEP); + const raw = + idx >= 0 + ? { + name: String(value).slice(0, idx), + phone: String(value).slice(idx + 1), + } + : { name: String(value), phone: "" }; + // 去除所有空白(联系人/电话内部不应有空格) + return { + contactName: raw.name.replace(/\s+/g, "") || undefined, + contactPhone: raw.phone.replace(/\s+/g, "") || undefined, + }; +}; + +// 业务范围值清洗:兼容历史已存的中文 label,回显时统一转换为后端枚举 code +const labelToValue = Object.fromEntries( + QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => [opt.label, opt.value]), +); -// 2026-08-08 业务范围值清洗:兼容历史已存的中文 label,提交/回显时统一转换为后端枚举 code。 -// 原因:早期数据 apply_business_scope 存储的是中文名,现改为存 code;若历史中文与 code 混存, -// 提交前必须清洗为 code,避免请求体中出现 label 导致后端只应接收枚举值。 function normalizeBusinessScope(arr) { if (!Array.isArray(arr)) return []; - const labelToValue = Object.fromEntries( - BUSINESS_SCOPE_OPTIONS.map((opt) => [opt.label, opt.value]), - ); return arr - .filter((v) => v != null && String(v).trim() !== "") - .map((v) => { - const trimmed = String(v).trim(); - return labelToValue[trimmed] || trimmed; - }); + .map((v) => String(v).trim()) + .filter(Boolean) + .map((v) => labelToValue[v] || v); } -// 初始表单(字段名对齐后端 OrgInfo) -const INITIAL_FORM = { - unitName: "", - creditCode: "", - attachmentUrls: [], // 营业执照(数组,提交时 JSON.stringify) - registerAddress: "", - businessAddress: "", - infoDisclosureUrl: "", - qualificationCertNo: "", - legalRepresentative: "", - legalRepresentativePhone: "", - fax: "", - contactNamePhone: "", - fixedAssetsTotal: "", - workplaceArea: "", - archiveRoomArea: "", - fulltimeEvaluatorCount: "", - registeredEngineerCount: "", - safetyIndustryCategoryCode: [], // 多选,提交时传数组 - orgIntro: "", - id: undefined, -}; - -// ===== 自定义校验(不依赖 antd rules,贴合原型原生表单)===== -// 2026-08-08 校验规则与后端库表字段类型/长度对齐(见 OrgInfoAddCmd 的 Bean Validation 注解): -// 字符串字段长度取库 varchar 长度;decimal 字段整数/小数位取库精度;int 字段取 32 位有符号上限。 -const PHONE_RE = /^(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})$/; -// 2026-08-08 联系人及电话为「联系人姓名 + 电话」组合字段,要求串中包含合法手机/座机号 -const HAS_PHONE_RE = /(1[3-9]\d{9}|0\d{2,3}-?\d{7,8})/; -// 2026-08-08 严格 URL 校验:必须为 http(s):// 开头且含「域名.后缀」结构(如 https://www.example.com), -// 拦截纯数字/无域名等非法输入(如 "123" 经 normalizeUrl 补全后为 "https://123",无 "." 域名,不匹配)。 -const URL_RE = - /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+([\/?#][\w\-._~:/?#[\]@!$&'()*+,;=.\-%]*)?$/; -const CREDIT_RE = /^[0-9A-Z]{18}$/; -const NUMBER_RE = /^\d+(\.\d+)?$/; -const INT_RE = /^\d+$/; - -// 2026-08-08 联系人与电话采用「单输入框 + "/" 分隔」录入模式:用户输入 "联系人/电话"。 -// 提交时按首个 "/" 拆分为 contactName / contactPhone 两个后端字段(与 OrgInfoDO 拆分字段对齐)。 -// 兼容无 "/" 的情形:整段视为联系人、电话置空,由校验拦截提示。 -function splitContact(input) { - if (!input) return { name: "", phone: "" }; - const idx = input.indexOf("/"); - if (idx === -1) return { name: input.trim(), phone: "" }; - return { - name: input.slice(0, idx).trim(), - phone: input.slice(idx + 1).trim(), - }; +// 营业执照附件:后端 JSON 字符串 <-> 数组 +function parseAttachmentUrls(raw) { + if (!raw) return []; + try { + const p = JSON.parse(raw); + return Array.isArray(p) ? p : []; + } catch { + return []; + } } -// 各数字字段的取值范围上限(与库 decimal/int 精度对齐,详见 OrgInfoAddCmd 注解) -const MAX_AREA_INT = 9999999999; // decimal(12,2) 整数最多10位 -const MAX_AREA_FRAC = 2; // decimal(12,2) 小数最多2位 -const MAX_FIXED_INT = 99999999999999; // decimal(16,4) 整数最多12位 -const MAX_FIXED_FRAC = 4; // decimal(16,4) 小数最多4位 -const MAX_INT32 = 2147483647; // int 32位有符号上限 - -// 小数位数校验:拆分整数/小数部分分别与库精度比对 -const exceedsScale = (val, maxFrac) => { - const s = String(val); - const dot = s.indexOf("."); - if (dot === -1) return false; - return s.length - dot - 1 > maxFrac; -}; - -const validate = (formData, registerUserId) => { - const errors = {}; - const required = (f, msg) => { - const v = formData[f]; - if (v == null || v === "" || (Array.isArray(v) && v.length === 0)) { - errors[f] = msg; - return true; - } - return false; - }; - const len = (f, max, msg) => { - if (String(formData[f]).length > max) errors[f] = msg; - }; - - if (required("unitName", "请输入单位名称")) { - } else len("unitName", 200, "单位名称不能超过200字"); - if (required("creditCode", "请输入统一社会信用代码")) { - } else if (!CREDIT_RE.test(formData.creditCode)) - errors.creditCode = "统一社会信用代码为18位字母或数字"; - if (required("attachmentUrls", "请上传营业执照")) { - } - if (required("registerAddress", "请输入注册地址")) { - } else len("registerAddress", 500, "注册地址不能超过500字"); // varchar(500) - if (required("businessAddress", "请输入办公地址")) { - } else len("businessAddress", 500, "办公地址不能超过500字"); // varchar(500) - if (required("infoDisclosureUrl", "请输入信息公开网址")) { - } else if (!URL_RE.test(normalizeUrl(formData.infoDisclosureUrl))) - errors.infoDisclosureUrl = "请输入合法网址(如 https://www.example.com)"; - else len("infoDisclosureUrl", 500, "网址不能超过500字"); // varchar(500) - if (required("qualificationCertNo", "请输入资质证书编号")) { - } else len("qualificationCertNo", 100, "资质证书编号不能超过100字"); // varchar(100) - if (required("legalRepresentative", "请输入法定代表人")) { - } else len("legalRepresentative", 50, "法定代表人不能超过50字"); // varchar(50) - if (required("legalRepresentativePhone", "请输入法定代表人电话")) { - } else if (!PHONE_RE.test(formData.legalRepresentativePhone)) - errors.legalRepresentativePhone = "请输入正确的电话(手机或座机)"; - else len("legalRepresentativePhone", 20, "电话不能超过20字"); // varchar(20) - if (required("fax", "请输入传真")) { - } else if (!PHONE_RE.test(formData.fax)) errors.fax = "请输入正确的传真(手机或座机)"; - else len("fax", 20, "传真不能超过20字"); // varchar(20) - // 2026-08-08 联系人与电话:单输入框 "联系人/电话",提交时拆分;此处按 "/" 分割后分别校验。 - // contactName varchar(50),contactPhone varchar(20)(与 OrgInfoDO / OrgInfoAddCmd 一致)。 - if (required("contactNamePhone", "请输入联系人及电话(用/分隔联系人和电话)")) { - } else { - const { name, phone } = splitContact(formData.contactNamePhone); - if (!name) errors.contactNamePhone = "请输入联系人(/前)"; - else if (name.length > 50) errors.contactNamePhone = "联系人不能超过50字"; // varchar(50) - else if (!phone) errors.contactNamePhone = "请输入联系电话(/后)"; - else if (!HAS_PHONE_RE.test(phone)) - errors.contactNamePhone = "请输入正确的联系电话(手机或座机)"; - else if (phone.length > 20) errors.contactNamePhone = "联系电话不能超过20字"; // varchar(20) - } - // 2026-08-08 数值范围/精度校验:与后端库表 decimal 精度对齐,避免提交后数据库写入时 - // 因整数位或小数位超出 decimal 精度而抛 "Out of range" 异常。 - if (required("fixedAssetsTotal", "请输入固定资产总值")) { - } else if (!NUMBER_RE.test(formData.fixedAssetsTotal) || Number(formData.fixedAssetsTotal) < 0) - errors.fixedAssetsTotal = "请输入非负数字"; - else if (Number(formData.fixedAssetsTotal) > MAX_FIXED_INT) - errors.fixedAssetsTotal = "数值过大,整数部分不能超过12位"; // decimal(16,4) - else if (exceedsScale(formData.fixedAssetsTotal, MAX_FIXED_FRAC)) - errors.fixedAssetsTotal = "小数位最多4位"; // decimal(16,4) - if (required("workplaceArea", "请输入工作场所建筑面积")) { - } else if (!NUMBER_RE.test(formData.workplaceArea) || Number(formData.workplaceArea) < 0) - errors.workplaceArea = "请输入非负数字"; - else if (Number(formData.workplaceArea) > MAX_AREA_INT) - errors.workplaceArea = "数值过大,整数部分不能超过10位"; // decimal(12,2) - else if (exceedsScale(formData.workplaceArea, MAX_AREA_FRAC)) - errors.workplaceArea = "小数位最多2位"; // decimal(12,2) - if (required("archiveRoomArea", "请输入档案室面积")) { - } else if (!NUMBER_RE.test(formData.archiveRoomArea) || Number(formData.archiveRoomArea) < 0) - errors.archiveRoomArea = "请输入非负数字"; - else if (Number(formData.archiveRoomArea) > MAX_AREA_INT) - errors.archiveRoomArea = "数值过大,整数部分不能超过10位"; // decimal(12,2) - else if (exceedsScale(formData.archiveRoomArea, MAX_AREA_FRAC)) - errors.archiveRoomArea = "小数位最多2位"; // decimal(12,2) - if (required("fulltimeEvaluatorCount", "请输入专职安全评价师数量")) { - } else if (!INT_RE.test(formData.fulltimeEvaluatorCount)) - errors.fulltimeEvaluatorCount = "请输入非负整数"; - else if (Number(formData.fulltimeEvaluatorCount) > MAX_INT32) - errors.fulltimeEvaluatorCount = "数值过大"; // int 上限 - if (required("registeredEngineerCount", "请输入注册安全工程师数量")) { - } else if (!INT_RE.test(formData.registeredEngineerCount)) - errors.registeredEngineerCount = "请输入非负整数"; - else if (Number(formData.registeredEngineerCount) > MAX_INT32) - errors.registeredEngineerCount = "数值过大"; // int 上限 - // 2026-08-08 校验前先清洗为 code(兼容历史中文 label 混存),避免以 label 长度误判 - const normalizedScope = normalizeBusinessScope(formData.safetyIndustryCategoryCode); - if (normalizedScope.length === 0) { - errors.safetyIndustryCategoryCode = "请至少选择一项业务范围"; - } else if (normalizedScope.join(",").length > 500) - errors.safetyIndustryCategoryCode = "业务范围总字数不能超过500字"; // varchar(500) - if (required("orgIntro", "请输入单位基本情况介绍")) { - } else if (formData.orgIntro.length > 2000) - errors.orgIntro = "单位基本情况介绍不能超过2000字"; - - // 2026-08-08 注册用户标识校验:来自统一解析(路由参数或会话缓存),后端 registerUserId 为 Long, - // 此处校验其存在且为数字字符串,避免非数字值(如测试占位符)提交后端时 - // 触发 JSON 反序列化异常(Jackson 无法将非数字字符串解析为 Long)。 - if (!registerUserId || !/^\d+$/.test(String(registerUserId).trim())) - errors.registerUserId = "注册用户标识缺失或非法"; - - return errors; -}; - const RegisterMore = (props) => { const { orgInfoSave, register, syncUserToGBS } = props; + const [form] = Form.useForm(); const [current, setCurrent] = useState(0); - const [formData, setFormData] = useState(INITIAL_FORM); - const [errors, setErrors] = useState({}); - const [uploading, setUploading] = useState(false); + const [submitting, setSubmitting] = useState(false); const { registerLoading, syncUserToGBSLoading } = register; - // ===== 2026-08-08 注册用户标识统一解析(变更原因) ===== - // 变更逻辑:registerUserId 仅从"路由参数"或"会话缓存"获取(resolveRegisterQueryParams), - // 禁止直接取 router.query 散落值,避免手改 URL 瞎写非法 id 落库。 - // 变更理由:此前出现过 create_id 与真实账户脱节(前端传了错误的 registerUserId), - // 现统一来源 + 保存前校验(前端拦截 + 后端 account 存在性校验)双保险。 + + // 注册用户标识统一解析(路由参数或会话缓存) const resolvedParams = resolveRegisterQueryParams(); const resolvedAccount = resolvedParams.account; const resolvedRegisterUserId = resolvedParams.registerUserId; const hasRegisterContext = Boolean(resolvedAccount && resolvedRegisterUserId); - const setField = (field, value) => { - setFormData((prev) => ({ ...prev, [field]: value })); - setErrors((prev) => ({ ...prev, [field]: undefined })); - }; + const loading = registerLoading || syncUserToGBSLoading || submitting; const loadDetail = async () => { const res = await props.orgInfoGet({ data: resolvedAccount, }); - if (res?.data) { - // 2026-08-08 回显:safetyIndustryCategoryCode 拆为数组,attachmentUrls JSON.parse - // 同时把后端主键 id 写回 formData,供"缓存/提交"更新时精确匹配记录(规避二次映射)。 - setFormData({ - ...INITIAL_FORM, - ...res.data, - // 2026-08-08 主键 id 以字符串保留:id 为后端 Long(19位),超过 JS Number.MAX_SAFE_INTEGER, - // 用 Number() 转换会丢失末位精度(如 ...87938 → ...88000),故保持字符串,由后端 Long 反序列化解析。 - id: res.data.id != null ? String(res.data.id) : undefined, // 写回主键,使保存走更新分支更精确(后端仍按 registerUserId 兜底) - // 2026-08-08 回显时清洗历史中文 label 为 code,保证 formData 中只存枚举值 - safetyIndustryCategoryCode: normalizeBusinessScope( - res.data.safetyIndustryCategoryCode - ? String(res.data.safetyIndustryCategoryCode).split(",").filter(Boolean) - : [], - ), - attachmentUrls: res.data.attachmentUrls - ? (() => { - try { - const p = JSON.parse(res.data.attachmentUrls); - return Array.isArray(p) ? p : []; - } catch { - return []; - } - })() + if (!res?.data) return; + const { + contactName, + contactPhone, + attachmentUrls, + safetyIndustryCategoryCode, + id, + ...rest + } = res.data; + form.setFieldsValue({ + ...rest, + // 主键 id 为后端 Long(19位),保持字符串避免精度丢失 + id: id != null ? String(id) : undefined, + // 回显时清洗历史中文 label 为 code + safetyIndustryCategoryCode: normalizeBusinessScope( + safetyIndustryCategoryCode + ? String(safetyIndustryCategoryCode).split(",").filter(Boolean) : [], - fixedAssetsTotal: res.data.fixedAssetsTotal ?? "", - workplaceArea: res.data.workplaceArea ?? "", - archiveRoomArea: res.data.archiveRoomArea ?? "", - fulltimeEvaluatorCount: res.data.fulltimeEvaluatorCount ?? "", - registeredEngineerCount: res.data.registeredEngineerCount ?? "", - // 2026-08-08 后端拆分字段 contactName/contactPhone 合并回单输入框("/" 分隔)回显 - contactNamePhone: [res.data.contactName, res.data.contactPhone] - .filter(Boolean) - .join("/"), - }); - } + ), + attachmentUrls: parseAttachmentUrls(attachmentUrls), + // 后端拆分字段合并回单输入框回显 + contactNamePhone: mergeContactNamePhone(contactName, contactPhone), + }); }; useEffect(() => { loadDetail(); }, []); - // ===== 营业执照上传(原生 input,对接后端 /safetyEval/images/upload)===== - const handleFileUpload = async (e) => { - const file = e.target.files?.[0]; - e.target.value = ""; - if (!file) return; - const accept = [".pdf", ".jpg", ".jpeg", ".png"]; - const ext = "." + file.name.split(".").pop().toLowerCase(); - if (!accept.includes(ext)) { - message.error("仅支持 pdf/jpg/jpeg/png 格式文件"); - return; - } - if (file.size / 1024 / 1024 > 10) { - message.error("文件大小不能超过 10MB"); - return; - } - setUploading(true); - try { - const fd = new FormData(); - fd.append("file", file); - const host = window.process?.env?.app?.API_HOST || ""; - const resp = await fetch(`${host}/safetyEval/images/upload`, { - method: "POST", - body: fd, - }); - const json = await resp.json(); - const url = json?.data?.url; - if (!url) throw new Error("上传失败"); - setFormData((prev) => ({ - ...prev, - attachmentUrls: [ - ...prev.attachmentUrls, - { url, name: file.name, uid: `${Date.now()}`, status: "done" }, - ], - })); - setErrors((prev) => ({ ...prev, attachmentUrls: undefined })); - } catch { - message.error("上传失败,请重试"); - } finally { - setUploading(false); - } - }; - - const removeAttachment = (uid) => { - setFormData((prev) => ({ - ...prev, - attachmentUrls: prev.attachmentUrls.filter((f) => f.uid !== uid), - })); - }; - - const toggleScope = (val) => { - setFormData((prev) => { - const set = new Set(prev.safetyIndustryCategoryCode); - if (set.has(val)) set.delete(val); - else set.add(val); - return { ...prev, safetyIndustryCategoryCode: Array.from(set) }; - }); - setErrors((prev) => ({ ...prev, safetyIndustryCategoryCode: undefined })); - }; - - // 2026-08-08 暂存(draft)与提交(submit)区分流转: - // - 两者都会先校验(必填+常见格式:网址/电话等)并调用 orgInfoSave 落库; + // 暂存(draft)与提交(submit)区分流转: // - draft 仅保存草稿,停留当前页可继续编辑,不调用 syncUserToGBS、不进入下一步; // - submit 才调用 syncUserToGBS 同步并 setCurrent(1) 进入认证审核步骤。 const handleSave = async (type) => { - // ===== 2026-08-08 提交前拦截:registerUserId 必须存在且为数字(变更原因) ===== - // 变更理由:registerUserId 缺失/非法时直接拦截,禁止把错误 id 提交后端落库, - // 与后端 account 存在性校验形成前后端双保险。 - if (!resolvedRegisterUserId || !/^\d+$/.test(String(resolvedRegisterUserId).trim())) { + // 提交前拦截:registerUserId 必须存在且为数字 + if ( + !resolvedRegisterUserId || + !/^\d+$/.test(String(resolvedRegisterUserId).trim()) + ) { message.error("注册用户标识缺失或非法,请从注册页重新进入"); return; } @@ -362,60 +143,54 @@ const RegisterMore = (props) => { message.error("缺少注册信息,请从注册页重新进入"); return; } - const errs = validate(formData, resolvedRegisterUserId); - setErrors(errs); - if (Object.keys(errs).length > 0) { - message.error("请完善表单必填项或检查格式(网址/电话等)"); - return; - } - // 2026-08-08 数字字段转 number,业务范围 join(","),附件 JSON.stringify - // 联系人与电话:单输入框 contactNamePhone 按 "/" 拆分为 contactName + contactPhone 两个后端字段。 - const { name: contactName, phone: contactPhone } = splitContact( - formData.contactNamePhone, - ); - const { contactNamePhone, ...formRest } = formData; - const values = { - ...formRest, - // 2026-08-08 后端 registerUserId 为 Long 类型(19位,超过 Number.MAX_SAFE_INTEGER), - // 用 Number() 转换会丢失末位精度(如 2085936982110887938 → 2085936982110888000)导致落库 id 与真实账户脱节。 - // 故统一以字符串传递,由后端 Long 反序列化正确解析(与 syncUserToGBS 传参保持一致)。 - registerUserId: String(resolvedRegisterUserId), - contactName, - contactPhone, - // 2026-08-08 提交前清洗历史中文 label 为 code,以数组形式提交 - safetyIndustryCategoryCode: normalizeBusinessScope(formData.safetyIndustryCategoryCode), - attachmentUrls: formData.attachmentUrls.length - ? JSON.stringify(formData.attachmentUrls) - : null, - infoDisclosureUrl: formData.infoDisclosureUrl - ? normalizeUrl(formData.infoDisclosureUrl) - : undefined, - fixedAssetsTotal: Number(formData.fixedAssetsTotal), - workplaceArea: Number(formData.workplaceArea), - archiveRoomArea: Number(formData.archiveRoomArea), - fulltimeEvaluatorCount: Number(formData.fulltimeEvaluatorCount), - registeredEngineerCount: Number(formData.registeredEngineerCount), - authStatusCode: type === "draft" ? 0 : 1, - authStatusName: type === "draft" ? "草稿" : "已提交", - }; - const res1 = await orgInfoSave(values); - if (!res1.success) { - message.error(res1.message || "保存失败"); - return; - } - if (type === "submit") { - // 提交:同步用户到 GBS 并进入下一步骤 - const res2 = await syncUserToGBS({ - account: resolvedAccount, - registerUserId: resolvedRegisterUserId, - }); - if (res2.success) { - message.success("提交成功,进入认证审核"); - setCurrent(1); + try { + const formValues = await form.validateFields(); + // 联系人及电话:单字段拆分为后端 contactName / contactPhone 两列提交 + const { contactName, contactPhone } = splitContactNamePhone( + formValues.contactNamePhone, + ); + const { contactNamePhone, ...formRest } = formValues; + const values = { + ...formRest, + // registerUserId 为 Long 类型,以字符串传递避免精度丢失 + registerUserId: String(resolvedRegisterUserId), + contactName, + contactPhone, + attachmentUrls: formValues.attachmentUrls?.length + ? JSON.stringify(formValues.attachmentUrls) + : null, + infoDisclosureUrl: formValues.infoDisclosureUrl + ? normalizeUrl(formValues.infoDisclosureUrl) + : undefined, + authStatusCode: type === "draft" ? 0 : 1, + authStatusName: type === "draft" ? "草稿" : "已提交", + }; + setSubmitting(true); + const res1 = await orgInfoSave(values); + if (!res1.success) { + message.error(res1.message || "保存失败"); + return; } - } else { - // 暂存:仅保存草稿,停留在当前页,保留表单可继续编辑,不跳转、不同步 GBS - message.success("已暂存草稿,可继续编辑"); + if (type === "submit") { + // 提交:同步用户到 GBS 并进入下一步骤 + const res2 = await syncUserToGBS({ + account: resolvedAccount, + registerUserId: resolvedRegisterUserId, + }); + if (res2.success) { + message.success("提交成功,进入认证审核"); + setCurrent(1); + } + } else { + // 暂存:仅保存草稿,停留在当前页 + message.success("已暂存草稿,可继续编辑"); + } + } catch (err) { + if (err?.errorFields) { + message.error("请完善表单必填项或检查格式(网址/电话等)"); + } + } finally { + setSubmitting(false); } }; @@ -473,382 +248,419 @@ const RegisterMore = (props) => { style={{ marginBottom: 24 }} /> )} -
- {/* 单位名称 */} - - setField("unitName", e.target.value)} - /> - - {/* 统一社会信用代码 */} - - setField("creditCode", e.target.value)} - /> - - - {/* 营业执照(两列,与单位名称/信用代码同列;控件内联显示红星与提示) */} - -
- - - 请上传营业执照扫描件或清晰照片,支持 PDF、JPG、PNG - -
- {formData.attachmentUrls.map((f) => ( - - - {f.name || "营业执照"} - - - - ))} -
-
-
+ + + + + + + - {/* 注册地址(全宽单独成行) */} - -