2026-07-09 16:54:59 +08:00
|
|
|
|
import React, { useEffect, useState } from "react";
|
2026-07-09 10:05:52 +08:00
|
|
|
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
2026-08-08 14:18:54 +08:00
|
|
|
|
import { message, Empty } from "antd";
|
2026-07-09 10:05:52 +08:00
|
|
|
|
import { NS_REGISTER } from "~/enumerate/namespace";
|
|
|
|
|
|
import auditLookImage from "~/enumerate/img/auditLook.png";
|
|
|
|
|
|
import auditBkImage from "~/enumerate/img/audit.png";
|
|
|
|
|
|
import successIcon from "~/enumerate/img/successIcon.png";
|
2026-08-08 14:18:54 +08:00
|
|
|
|
import { normalizeUrl } from "~/utils/validators";
|
|
|
|
|
|
import { resolveRegisterQueryParams } from "~/utils/resolveRegisterQueryParams";
|
2026-07-09 10:05:52 +08:00
|
|
|
|
import "./index.less";
|
|
|
|
|
|
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// ===== 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) 单列适配。
|
|
|
|
|
|
// 业务范围选项(原型固定 7 项,多选)
|
|
|
|
|
|
const BUSINESS_SCOPE_OPTIONS = [
|
|
|
|
|
|
"煤炭开采业",
|
|
|
|
|
|
"金属、非金属矿及其他矿采选业",
|
|
|
|
|
|
"石油和天然气开采业",
|
|
|
|
|
|
"石油加工业,化学原料、化学品及医药制造业",
|
|
|
|
|
|
"烟花爆竹制造业",
|
|
|
|
|
|
"民用爆破器材制造业",
|
|
|
|
|
|
"金属冶炼业",
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 初始表单(字段名对齐后端 OrgInfo)
|
|
|
|
|
|
const INITIAL_FORM = {
|
|
|
|
|
|
unitName: "",
|
|
|
|
|
|
creditCode: "",
|
|
|
|
|
|
attachmentUrls: [], // 营业执照(数组,提交时 JSON.stringify)
|
|
|
|
|
|
registerAddress: "",
|
|
|
|
|
|
businessAddress: "",
|
|
|
|
|
|
infoDisclosureUrl: "",
|
|
|
|
|
|
qualificationCertNo: "",
|
|
|
|
|
|
legalRepresentative: "",
|
|
|
|
|
|
legalRepresentativePhone: "",
|
|
|
|
|
|
fax: "",
|
|
|
|
|
|
contactNamePhone: "",
|
|
|
|
|
|
fixedAssetsTotal: "",
|
|
|
|
|
|
workplaceArea: "",
|
|
|
|
|
|
archiveRoomArea: "",
|
|
|
|
|
|
fulltimeEvaluatorCount: "",
|
|
|
|
|
|
registeredEngineerCount: "",
|
|
|
|
|
|
applyBusinessScope: [], // 多选,提交时 join(",")
|
|
|
|
|
|
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(),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 各数字字段的取值范围上限(与库 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 上限
|
|
|
|
|
|
if (required("applyBusinessScope", "请至少选择一项业务范围")) {
|
|
|
|
|
|
} else if (formData.applyBusinessScope.join(",").length > 500)
|
|
|
|
|
|
errors.applyBusinessScope = "业务范围总字数不能超过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;
|
|
|
|
|
|
};
|
2026-07-09 16:54:59 +08:00
|
|
|
|
|
2026-07-09 10:05:52 +08:00
|
|
|
|
const RegisterMore = (props) => {
|
|
|
|
|
|
const { orgInfoSave, register, syncUserToGBS } = props;
|
|
|
|
|
|
const [current, setCurrent] = useState(0);
|
2026-08-08 14:18:54 +08:00
|
|
|
|
const [formData, setFormData] = useState(INITIAL_FORM);
|
|
|
|
|
|
const [errors, setErrors] = useState({});
|
|
|
|
|
|
const [uploading, setUploading] = useState(false);
|
2026-07-09 10:05:52 +08:00
|
|
|
|
const { registerLoading, syncUserToGBSLoading } = register;
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// ===== 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 }));
|
|
|
|
|
|
};
|
2026-07-09 16:54:59 +08:00
|
|
|
|
|
|
|
|
|
|
const loadDetail = async () => {
|
|
|
|
|
|
const res = await props.orgInfoGet({
|
2026-08-08 14:18:54 +08:00
|
|
|
|
data: resolvedAccount,
|
2026-07-09 16:54:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
if (res?.data) {
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// 2026-08-08 回显:applyBusinessScope 拆为数组,attachmentUrls JSON.parse
|
|
|
|
|
|
// 同时把后端主键 id 写回 formData,供"缓存/提交"更新时精确匹配记录(规避二次映射)。
|
|
|
|
|
|
setFormData({
|
|
|
|
|
|
...INITIAL_FORM,
|
2026-07-09 16:54:59 +08:00
|
|
|
|
...res.data,
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// 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 兜底)
|
|
|
|
|
|
applyBusinessScope: res.data.applyBusinessScope
|
|
|
|
|
|
? String(res.data.applyBusinessScope).split(",").filter(Boolean)
|
|
|
|
|
|
: [],
|
2026-07-09 16:54:59 +08:00
|
|
|
|
attachmentUrls: res.data.attachmentUrls
|
2026-08-08 14:18:54 +08:00
|
|
|
|
? (() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const p = JSON.parse(res.data.attachmentUrls);
|
|
|
|
|
|
return Array.isArray(p) ? p : [];
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
})()
|
|
|
|
|
|
: [],
|
|
|
|
|
|
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("/"),
|
2026-07-09 16:54:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
loadDetail();
|
|
|
|
|
|
}, []);
|
2026-07-09 10:05:52 +08:00
|
|
|
|
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// ===== 营业执照上传(原生 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.applyBusinessScope);
|
|
|
|
|
|
if (set.has(val)) set.delete(val);
|
|
|
|
|
|
else set.add(val);
|
|
|
|
|
|
return { ...prev, applyBusinessScope: Array.from(set) };
|
|
|
|
|
|
});
|
|
|
|
|
|
setErrors((prev) => ({ ...prev, applyBusinessScope: undefined }));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 2026-08-08 暂存(draft)与提交(submit)区分流转:
|
|
|
|
|
|
// - 两者都会先校验(必填+常见格式:网址/电话等)并调用 orgInfoSave 落库;
|
|
|
|
|
|
// - draft 仅保存草稿,停留当前页可继续编辑,不调用 syncUserToGBS、不进入下一步;
|
|
|
|
|
|
// - submit 才调用 syncUserToGBS 同步并 setCurrent(1) 进入认证审核步骤。
|
2026-07-09 10:05:52 +08:00
|
|
|
|
const handleSave = async (type) => {
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// ===== 2026-08-08 提交前拦截:registerUserId 必须存在且为数字(变更原因) =====
|
|
|
|
|
|
// 变更理由:registerUserId 缺失/非法时直接拦截,禁止把错误 id 提交后端落库,
|
|
|
|
|
|
// 与后端 account 存在性校验形成前后端双保险。
|
|
|
|
|
|
if (!resolvedRegisterUserId || !/^\d+$/.test(String(resolvedRegisterUserId).trim())) {
|
|
|
|
|
|
message.error("注册用户标识缺失或非法,请从注册页重新进入");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-07-09 10:05:52 +08:00
|
|
|
|
if (!hasRegisterContext) {
|
|
|
|
|
|
message.error("缺少注册信息,请从注册页重新进入");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-08-08 14:18:54 +08:00
|
|
|
|
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;
|
2026-07-09 10:05:52 +08:00
|
|
|
|
const values = {
|
2026-08-08 14:18:54 +08:00
|
|
|
|
...formRest,
|
|
|
|
|
|
// 2026-08-08 后端 registerUserId 为 Long 类型(19位,超过 Number.MAX_SAFE_INTEGER),
|
|
|
|
|
|
// 用 Number() 转换会丢失末位精度(如 2085936982110887938 → 2085936982110888000)导致落库 id 与真实账户脱节。
|
|
|
|
|
|
// 故统一以字符串传递,由后端 Long 反序列化正确解析(与 syncUserToGBS 传参保持一致)。
|
|
|
|
|
|
registerUserId: String(resolvedRegisterUserId),
|
|
|
|
|
|
contactName,
|
|
|
|
|
|
contactPhone,
|
|
|
|
|
|
applyBusinessScope: formData.applyBusinessScope.join(","),
|
|
|
|
|
|
attachmentUrls: formData.attachmentUrls.length
|
|
|
|
|
|
? JSON.stringify(formData.attachmentUrls)
|
2026-07-09 10:05:52 +08:00
|
|
|
|
: null,
|
2026-08-08 14:18:54 +08:00
|
|
|
|
infoDisclosureUrl: formData.infoDisclosureUrl
|
|
|
|
|
|
? normalizeUrl(formData.infoDisclosureUrl)
|
2026-07-09 16:54:59 +08:00
|
|
|
|
: undefined,
|
2026-08-08 14:18:54 +08:00
|
|
|
|
fixedAssetsTotal: Number(formData.fixedAssetsTotal),
|
|
|
|
|
|
workplaceArea: Number(formData.workplaceArea),
|
|
|
|
|
|
archiveRoomArea: Number(formData.archiveRoomArea),
|
|
|
|
|
|
fulltimeEvaluatorCount: Number(formData.fulltimeEvaluatorCount),
|
|
|
|
|
|
registeredEngineerCount: Number(formData.registeredEngineerCount),
|
2026-07-09 10:05:52 +08:00
|
|
|
|
authStatusCode: type === "draft" ? 0 : 1,
|
|
|
|
|
|
authStatusName: type === "draft" ? "草稿" : "已提交",
|
|
|
|
|
|
};
|
2026-07-09 16:54:59 +08:00
|
|
|
|
const res1 = await orgInfoSave(values);
|
2026-08-08 14:18:54 +08:00
|
|
|
|
if (!res1.success) {
|
|
|
|
|
|
message.error(res1.message || "保存失败");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (type === "submit") {
|
|
|
|
|
|
// 提交:同步用户到 GBS 并进入下一步骤
|
2026-07-09 16:54:59 +08:00
|
|
|
|
const res2 = await syncUserToGBS({
|
2026-08-08 14:18:54 +08:00
|
|
|
|
account: resolvedAccount,
|
|
|
|
|
|
registerUserId: resolvedRegisterUserId,
|
2026-07-09 16:54:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
if (res2.success) {
|
2026-08-08 14:18:54 +08:00
|
|
|
|
message.success("提交成功,进入认证审核");
|
2026-07-09 16:54:59 +08:00
|
|
|
|
setCurrent(1);
|
|
|
|
|
|
}
|
2026-08-08 14:18:54 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 暂存:仅保存草稿,停留在当前页,保留表单可继续编辑,不跳转、不同步 GBS
|
|
|
|
|
|
message.success("已暂存草稿,可继续编辑");
|
2026-07-09 16:54:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-08 14:18:54 +08:00
|
|
|
|
const handleBack = () => {
|
|
|
|
|
|
if (window.history.length > 1) window.history.back();
|
2026-07-14 10:08:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-09 16:54:59 +08:00
|
|
|
|
const handleLogin = () => {
|
|
|
|
|
|
message.success("即将跳转到登录页面,请稍候...");
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
window.location.href =
|
|
|
|
|
|
"https://gbs-gateway.qhdsafety.com/login/client/ZQAQPJ";
|
|
|
|
|
|
}, 2000);
|
2026-07-09 10:05:52 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (current === 1) {
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
|
|
setCurrent(2);
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
}
|
2026-07-09 16:54:59 +08:00
|
|
|
|
if (current === 2) {
|
|
|
|
|
|
handleLogin();
|
|
|
|
|
|
}
|
2026-07-09 10:05:52 +08:00
|
|
|
|
}, [current]);
|
|
|
|
|
|
|
2026-08-08 14:18:54 +08:00
|
|
|
|
const stepTitles = ["填写信息", "认证审核中", "认证通过"];
|
|
|
|
|
|
|
2026-07-09 10:05:52 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="register-more">
|
2026-08-08 14:18:54 +08:00
|
|
|
|
{/* hero 头图区 */}
|
|
|
|
|
|
<div className="register-more-hero">
|
|
|
|
|
|
<h1>认证信息</h1>
|
2026-07-09 10:05:52 +08:00
|
|
|
|
</div>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<div className="register-more-wrap">
|
|
|
|
|
|
{/* 步骤条 */}
|
|
|
|
|
|
<div className="register-more-steps">
|
|
|
|
|
|
{stepTitles.map((t, i) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={t}
|
|
|
|
|
|
className={`register-more-step ${i <= current ? "active" : ""}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<i>{i + 1}</i>
|
|
|
|
|
|
<span>{t}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-09 10:05:52 +08:00
|
|
|
|
{current === 0 && (
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<div className="register-more-card">
|
|
|
|
|
|
<h2>申请单位基本情况</h2>
|
2026-07-09 10:05:52 +08:00
|
|
|
|
{!hasRegisterContext && (
|
|
|
|
|
|
<Empty
|
|
|
|
|
|
description="缺少注册信息,请从注册页重新进入"
|
|
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<div
|
|
|
|
|
|
className={`register-more-grid ${
|
|
|
|
|
|
!hasRegisterContext ? "is-disabled" : ""
|
|
|
|
|
|
}`}
|
2026-07-09 16:54:59 +08:00
|
|
|
|
>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
{/* 单位名称 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="单位名称"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.unitName}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
maxLength={200}
|
|
|
|
|
|
placeholder="请输入单位名称"
|
|
|
|
|
|
value={formData.unitName}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("unitName", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 统一社会信用代码 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="统一社会信用代码"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.creditCode}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
maxLength={18}
|
|
|
|
|
|
placeholder="请输入统一社会信用代码"
|
|
|
|
|
|
value={formData.creditCode}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("creditCode", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 营业执照(两列,与单位名称/信用代码同列;控件内联显示红星与提示) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="营业执照"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.attachmentUrls}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="register-more-license">
|
|
|
|
|
|
<label className="register-more-file-btn">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept=".pdf,.jpg,.jpeg,.png"
|
|
|
|
|
|
disabled={!hasRegisterContext || uploading}
|
|
|
|
|
|
onChange={handleFileUpload}
|
2026-07-09 10:05:52 +08:00
|
|
|
|
/>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<span className="register-more-file-text">
|
|
|
|
|
|
{uploading ? "上传中..." : "选择文件"}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<span className="register-more-optional-note">
|
|
|
|
|
|
请上传营业执照扫描件或清晰照片,支持 PDF、JPG、PNG
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div className="register-more-license-list">
|
|
|
|
|
|
{formData.attachmentUrls.map((f) => (
|
|
|
|
|
|
<span className="license-item" key={f.uid}>
|
|
|
|
|
|
<a href={f.url} target="_blank" rel="noreferrer">
|
|
|
|
|
|
{f.name || "营业执照"}
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="license-remove"
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onClick={() => removeAttachment(f.uid)}
|
|
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 注册地址(全宽单独成行) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="注册地址"
|
|
|
|
|
|
required
|
|
|
|
|
|
wide
|
|
|
|
|
|
alignStart
|
|
|
|
|
|
error={errors.registerAddress}
|
|
|
|
|
|
>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
placeholder="请输入注册地址"
|
|
|
|
|
|
value={formData.registerAddress}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("registerAddress", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 办公地址(全宽单独成行) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="办公地址"
|
|
|
|
|
|
required
|
|
|
|
|
|
wide
|
|
|
|
|
|
alignStart
|
|
|
|
|
|
error={errors.businessAddress}
|
|
|
|
|
|
>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
placeholder="请输入办公地址"
|
|
|
|
|
|
value={formData.businessAddress}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("businessAddress", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 信息公开网址 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="信息公开网址"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.infoDisclosureUrl}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="url"
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
placeholder="请输入信息公开网址"
|
|
|
|
|
|
value={formData.infoDisclosureUrl}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("infoDisclosureUrl", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 资质证书编号 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="资质证书编号"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.qualificationCertNo}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
maxLength={100}
|
|
|
|
|
|
placeholder="请输入资质证书编号"
|
|
|
|
|
|
value={formData.qualificationCertNo}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("qualificationCertNo", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 法定代表人 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="法定代表人"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.legalRepresentative}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
maxLength={50}
|
|
|
|
|
|
placeholder="请输入法定代表人"
|
|
|
|
|
|
value={formData.legalRepresentative}
|
2026-07-09 10:05:52 +08:00
|
|
|
|
disabled={!hasRegisterContext}
|
2026-08-08 14:18:54 +08:00
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("legalRepresentative", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 法定代表人电话 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="法定代表人电话"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.legalRepresentativePhone}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="tel"
|
|
|
|
|
|
maxLength={20}
|
|
|
|
|
|
placeholder="请输入法定代表人电话"
|
|
|
|
|
|
value={formData.legalRepresentativePhone}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("legalRepresentativePhone", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 传真 */}
|
|
|
|
|
|
<Field label="传真" required error={errors.fax}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="tel"
|
|
|
|
|
|
maxLength={20}
|
|
|
|
|
|
placeholder="请输入传真"
|
|
|
|
|
|
value={formData.fax}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("fax", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 联系人及电话(单输入框 + "/" 分隔,提交时拆分存储) */}
|
|
|
|
|
|
<Field label="联系人及电话" required error={errors.contactNamePhone}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
maxLength={100}
|
|
|
|
|
|
placeholder="请输入联系人及电话"
|
|
|
|
|
|
value={formData.contactNamePhone}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("contactNamePhone", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{/* 2026-08-08 输入框下方 12px 灰字提示:单输入框用 / 分隔联系人和电话 */}
|
|
|
|
|
|
<span className="register-more-optional-note">
|
|
|
|
|
|
请使用“/”分隔联系人和电话,如:张三/13800138000
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 固定资产总值(万元) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="固定资产总值(万元)"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.fixedAssetsTotal}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={99999999999999}
|
|
|
|
|
|
step={0.01}
|
|
|
|
|
|
placeholder="请输入固定资产总值"
|
|
|
|
|
|
value={formData.fixedAssetsTotal}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("fixedAssetsTotal", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 工作场所建筑面积(㎡) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="工作场所建筑面积(㎡)"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.workplaceArea}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={9999999999}
|
|
|
|
|
|
step={0.01}
|
|
|
|
|
|
placeholder="请输入工作场所建筑面积"
|
|
|
|
|
|
value={formData.workplaceArea}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("workplaceArea", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 档案室面积(㎡) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="档案室面积(㎡)"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.archiveRoomArea}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={9999999999}
|
|
|
|
|
|
step={0.01}
|
|
|
|
|
|
placeholder="请输入档案室面积"
|
|
|
|
|
|
value={formData.archiveRoomArea}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("archiveRoomArea", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
{/* 专职安全评价师数量 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="专职安全评价师数量"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.fulltimeEvaluatorCount}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
step={1}
|
|
|
|
|
|
placeholder="请输入专职安全评价师数量"
|
|
|
|
|
|
value={formData.fulltimeEvaluatorCount}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("fulltimeEvaluatorCount", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 注册安全工程师数量 */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="注册安全工程师数量"
|
|
|
|
|
|
required
|
|
|
|
|
|
error={errors.registeredEngineerCount}
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
step={1}
|
|
|
|
|
|
placeholder="请输入注册安全工程师数量"
|
|
|
|
|
|
value={formData.registeredEngineerCount}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
setField("registeredEngineerCount", e.target.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 拟申请的法定安全评价业务范围(全宽单独成行,多选) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="拟申请的法定安全评价业务范围"
|
|
|
|
|
|
required
|
|
|
|
|
|
wide
|
|
|
|
|
|
alignStart
|
|
|
|
|
|
error={errors.applyBusinessScope}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="register-more-scope">
|
|
|
|
|
|
{BUSINESS_SCOPE_OPTIONS.map((opt) => (
|
|
|
|
|
|
<label key={opt}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={formData.applyBusinessScope.includes(opt)}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={() => toggleScope(opt)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{opt}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 单位基本情况介绍(全宽单独成行,长文本) */}
|
|
|
|
|
|
<Field
|
|
|
|
|
|
label="单位基本情况介绍"
|
|
|
|
|
|
required
|
|
|
|
|
|
wide
|
|
|
|
|
|
alignStart
|
|
|
|
|
|
error={errors.orgIntro}
|
|
|
|
|
|
>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
className="register-more-large-textarea"
|
|
|
|
|
|
rows={6}
|
|
|
|
|
|
maxLength={2000}
|
|
|
|
|
|
placeholder="请输入单位基本情况介绍"
|
|
|
|
|
|
value={formData.orgIntro}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
onChange={(e) => setField("orgIntro", e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Field>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 底部提示 */}
|
|
|
|
|
|
<div className="register-more-tip">
|
|
|
|
|
|
请按机构资质申请材料如实填写,带 * 字段为必填项。
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 操作按钮 */}
|
|
|
|
|
|
<div className="register-more-actions">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="register-more-btn secondary"
|
|
|
|
|
|
onClick={handleBack}
|
|
|
|
|
|
disabled={!hasRegisterContext}
|
|
|
|
|
|
>
|
|
|
|
|
|
返回
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="register-more-btn secondary"
|
|
|
|
|
|
onClick={() => handleSave("draft")}
|
|
|
|
|
|
disabled={!hasRegisterContext || registerLoading || syncUserToGBSLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
暂存
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="register-more-btn primary"
|
|
|
|
|
|
onClick={() => handleSave("submit")}
|
|
|
|
|
|
disabled={!hasRegisterContext || registerLoading || syncUserToGBSLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
提交认证
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-07-09 10:05:52 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-08-08 14:18:54 +08:00
|
|
|
|
|
2026-07-09 10:05:52 +08:00
|
|
|
|
{current === 1 && (
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<div className="register-more-card">
|
|
|
|
|
|
<div className="register-more-state">
|
2026-07-09 10:05:52 +08:00
|
|
|
|
<div
|
|
|
|
|
|
className="audit-look"
|
2026-08-08 14:18:54 +08:00
|
|
|
|
style={{ backgroundImage: `url(${auditBkImage})` }}
|
2026-07-09 10:05:52 +08:00
|
|
|
|
>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<img src={auditLookImage} className="audit-look-img" alt="" />
|
2026-07-09 10:05:52 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>认证审核中,请耐心等待···</div>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
</div>
|
2026-07-09 10:05:52 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{current === 2 && (
|
2026-08-08 14:18:54 +08:00
|
|
|
|
<div className="register-more-card">
|
|
|
|
|
|
<div className="register-more-state">
|
|
|
|
|
|
<img src={successIcon} alt="" />
|
2026-07-09 10:05:52 +08:00
|
|
|
|
<div>审核通过</div>
|
2026-08-08 14:18:54 +08:00
|
|
|
|
</div>
|
2026-07-09 10:05:52 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-08 14:18:54 +08:00
|
|
|
|
// 字段行组件:label + 控件 + 错误提示,按原型 .field 布局
|
|
|
|
|
|
const Field = ({ label, required, wide, alignStart, error, children }) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={`register-more-field ${required ? "required" : ""} ${
|
|
|
|
|
|
wide ? "register-more-wide" : ""
|
|
|
|
|
|
} ${alignStart ? "align-start" : ""}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<label>{label}</label>
|
|
|
|
|
|
<div className="register-more-control">
|
|
|
|
|
|
{children}
|
|
|
|
|
|
{error && <span className="register-more-error">{error}</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-07-09 10:05:52 +08:00
|
|
|
|
export default Connect([NS_REGISTER], true)(RegisterMore);
|