diff --git a/dev-stderr.log b/dev-stderr.log new file mode 100644 index 0000000..83d5032 --- /dev/null +++ b/dev-stderr.log @@ -0,0 +1,17 @@ +E:\works\projects\safety-eval-service-frontend\node_modules\@cqsjjb\scripts\node_modules\@rspack\dev-server\dist\server.js:1658 + throw error; + ^ + +Error: listen EADDRINUSE: address already in use 0.0.0.0:8081 + at Server.setupListenHandle [as _listen2] (node:net:1941:16) + at listenInCluster (node:net:1998:12) + at node:net:2207:7 + at process.processTicksAndRejections (node:internal/process/task_queues:89:21) { + code: 'EADDRINUSE', + errno: -4091, + syscall: 'listen', + address: '0.0.0.0', + port: 8081 +} + +Node.js v22.23.1 diff --git a/dev-stdout.log b/dev-stdout.log new file mode 100644 index 0000000..00f441c --- /dev/null +++ b/dev-stdout.log @@ -0,0 +1,17 @@ + +> micro-app@2.0.0 serve:development +> cross-env NODE_ENV=development npm run serve + + +> micro-app@2.0.0 serve +> node node_modules/@cqsjjb/scripts/rspack.dev.server.js + +14:19:46 [Module] [INFO] Babel 配置文件已加载: E:\works\projects\safety-eval-service-frontend//jjb.babel.js +14:19:46 [Config] [INFO] 正在加载配置文件... +14:19:46 [Config] [INFO] 配置文件已加载: E:\works\projects\safety-eval-service-frontend//jjb.config.js +14:19:46 [Config] [SUCCESS] 配置验证通过 +14:19:46 [Config] [INFO] 运行模式: development +14:19:46 [DevServer] [INFO] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +14:19:46 [DevServer] [INFO] 运行模式: development +14:19:47 [DevServer] [INFO] 正在启动 开发服务器... +14:19:47 [DevServer] [INFO] 正在编译... diff --git a/src/pages/Container/EnterpriseInfo/OrgInfo/index.js b/src/pages/Container/EnterpriseInfo/OrgInfo/index.js index b2e40fa..614d75b 100644 --- a/src/pages/Container/EnterpriseInfo/OrgInfo/index.js +++ b/src/pages/Container/EnterpriseInfo/OrgInfo/index.js @@ -1,39 +1,26 @@ import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Button, + Checkbox, Col, - DatePicker, Form, Input, InputNumber, message, Row, - Select, Space, - Flex, } from "antd"; -import AttachmentUpload from "~/components/AttachmentUpload"; -import BaiduMapPicker from "~/components/BaiduMapPicker"; -import dayjs from "dayjs"; import { useEffect, useState } from "react"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; -import { NATIONAL_COUNTIES } from "~/enumerate/constant"; -import { - ENTERPRISE_SCALE_OPTIONS, - ENTERPRISE_STATUS_OPTIONS, - REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS, - REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS, - QUALIFICATION_INDUSTRY_OPTIONS, - ECONOMY_INDUSTRY_OPTIONS, -} from "~/enumerate/enterpriseOptions"; +// 2026-08-08 变更:按原型 V1.6 institution.html 的 #mod-org-info 模块 1:1 还原机构基础信息表单。 +// 复用既有「安全生产监管行业类别」枚举作为「拟申请的法定安全评价业务范围」候选项, +// 二者取值集合完全一致(煤矿/金属非金属矿/陆地油气/油气管道/石油化工医药/烟花爆竹/金属冶炼),无需新增枚举。 +import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions"; import { NS_ORG_INFO } from "~/enumerate/namespace"; import { - coordinatePairRule, creditCodeRule, - latitudeRule, - longitudeRule, nonNegativeIntegerRule, normalizeUrl, phoneRule, @@ -41,13 +28,59 @@ import { urlRule, } from "~/utils/validators"; +/** + * 2026-08-08 新增:业务范围在后端为 varchar(500) 的「逗号分隔字符串」(apply_business_scope), + * 而原型要求多选交互,故在页面出入口做数组/字符串互转。 + * 不做字段名映射,仅做同名字段的值形态转换,保持前后端字段名一致。 + */ +const splitBusinessScope = (value) => { + if (Array.isArray(value)) return value; + if (!value) return undefined; + return String(value) + .split(",") + .map((item) => item.trim()) + .filter(Boolean); +}; + +const joinBusinessScope = (value) => + Array.isArray(value) ? value.join(",") : value || undefined; + +/** + * 2026-08-08 联系人及电话合并/拆分工具(交互约定见下方说明): + * 录入:单输入框,值格式「联系人/电话」,用 / 分隔。 + * 提交前:按首个 / 拆为 contactName(/前)+ contactPhone(/后)两字段,传给后端。 + * 回显:后端 contactName/contactPhone 用 / 合并回单输入框。 + * 后端落库:contact_name varchar(50)、contact_phone varchar(20);原 contact_name_phone 列保留不删、不再写入。 + * 复用要点:其他页面如需同样交互,保持「单输入框 + / 分隔 + 提交拆分存两列 + 回显合并」即可。 + * 注意:分隔符为单斜杠「/」(无空格),与录入示例"张三/13800138000"一致。 + */ +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); + if (idx >= 0) { + return { + // 按首个 / 拆分;/后全部作为电话(剔除首尾空白),多余字符交给校验/提交时处理 + contactName: String(value).slice(0, idx).trim(), + contactPhone: String(value).slice(idx + 1).trim(), + }; + } + // 无分隔符:整体作为联系人姓名,电话置空(由校验拦截) + return { contactName: String(value).trim(), contactPhone: undefined }; +}; + function OrgInfoPage(props) { const [form] = Form.useForm(); const { orgInfoLoading } = props.orgInfo; const [editing, setEditing] = useState(true); const [submitting, setSubmitting] = useState(false); const [detail, setDetail] = useState({}); - const [mapPickerVisible, setMapPickerVisible] = useState(false); /** 是否已存在机构数据(有 id 视为已入库,只能修改) */ const [hasExistingData, setHasExistingData] = useState(false); @@ -58,12 +91,13 @@ function OrgInfoPage(props) { setDetail(res.data); form.setFieldsValue({ ...res.data, - productionDate: res.data.productionDate - ? dayjs(res.data.productionDate) - : undefined, - attachmentUrls: res.data.attachmentUrls - ? JSON.parse(res.data.attachmentUrls) - : null, + // 2026-08-08 业务范围回填为数组以适配多选控件 + applyBusinessScope: splitBusinessScope(res.data.applyBusinessScope), + // 2026-08-08 联系人及电话:后端 contactName/contactPhone 两列合并为单字段展示 + contactNamePhone: mergeContactNamePhone( + res.data.contactName, + res.data.contactPhone, + ), }); setHasExistingData(true); setEditing(false); @@ -91,14 +125,23 @@ function OrgInfoPage(props) { const handleSave = async (submitType) => { try { const formValues = await form.validateFields(); + // 2026-08-08 联系人及电话:单字段拆分为后端 contactName / contactPhone 两列提交 + const split = splitContactNamePhone(formValues.contactNamePhone); + // 剔除多余字符:姓名/电话做 trim 与空白清理(校验已保证格式,此处仅兜底) + const contactName = split.contactName + ? split.contactName.replace(/\s+/g, "") + : undefined; + const contactPhone = split.contactPhone + ? split.contactPhone.replace(/\s+/g, "") + : undefined; const values = { ...formValues, - productionDate: formValues.productionDate - ? dayjs(formValues.productionDate).format("YYYY-MM-DD") - : undefined, - attachmentUrls: formValues.attachmentUrls - ? JSON.stringify(formValues.attachmentUrls) - : null, + contactName, + contactPhone, + // 移除仅用于展示的合并字段,避免提交冗余 + contactNamePhone: undefined, + // 2026-08-08 多选业务范围按后端 varchar(500) 约定以逗号拼接提交 + applyBusinessScope: joinBusinessScope(formValues.applyBusinessScope), infoDisclosureUrl: formValues.infoDisclosureUrl ? normalizeUrl(formValues.infoDisclosureUrl) : undefined, @@ -132,18 +175,14 @@ function OrgInfoPage(props) { setSubmitting(false); } }; - const handleMapConfirm = ({ lng, lat }) => { - form.setFieldsValue({ longitude: lng, latitude: lat }); - setMapPickerVisible(false); - }; - return ( + {/* 2026-08-08 标题/副标题对齐原型 institution.html #mod-org-info */} 基础信息管理
- 新成立或首次使用系统的安全评价机构,可通过系统提供的引导页面,详细填写机构的基本信息。 + 机构注册认证信息,可在此持续维护并提交审核。
} @@ -160,19 +199,33 @@ function OrgInfoPage(props) { ) } > -
- + {/* + 2026-08-08 布局对齐原型 institution.html: + 原型 .form-grid 为两列网格、label 在控件上方(纵向),gap 1rem。 + 此处使用 antd Form layout="vertical" + Row/Col 两列还原,整行字段 span=24。 + 2026-08-08 按原型 .info-card 增加带边框容器并整体居中(border-radius 8px、padding 1.25rem、maxWidth 960px 居中)。 + */} +
+ + + {/* 2026-08-08 字段顺序严格对齐原型 institution.html 434-450 行 */} - + @@ -188,180 +241,59 @@ function OrgInfoPage(props) { /> - - - { - form.setFieldValue("districtName", option?.label || ""); - }} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {/* 原型:注册地址占整行(full) */} + - + - + {/* 原型:办公地址占整行(full),TextArea rows=3 */} + - - - - - + typeof value === "string" ? value.trim() : value + } > - - + {/* 2026-08-08 标签对齐原型「法定代表人电话」 */} typeof value === "string" ? value.trim() : value } > + + + + + + + + {/* + 2026-08-08 联系人及电话(交互约定): + 单输入框录入,格式「联系人/电话」(用 / 分隔),如:张三/13800138000; + 提交前按首个 / 拆为 contactName(/前)+ contactPhone(/后)存两列; + 回显时后端 contactName/contactPhone 用 / 合并回填。 + 校验:拆分后 contactName 非空且≤50、contactPhone 非空且匹配手机/座机格式且≤20。 + */} + + { + const { contactName, contactPhone } = + splitContactNamePhone(value); + if (!contactName) { + return Promise.reject(new Error("联系人不能为空")); + } + if (contactName.length > 50) { + return Promise.reject(new Error("联系人不能超过50字")); + } + if (!contactPhone) { + return Promise.reject( + new Error("请使用/分隔联系人和电话,并填写电话"), + ); + } + // 复用既有 PHONE_PATTERN:手机11位 / 固话10~12位(去空白后) + if ( + !/^(1[3-9]\d{9}|\d{10,12})$/.test( + String(contactPhone).replace(/\s+/g, ""), + ) + ) { + return Promise.reject( + new Error("电话格式不正确(手机11位或固话区号+号码)"), + ); + } + if (contactPhone.length > 20) { + return Promise.reject(new Error("电话不能超过20字")); + } + return Promise.resolve(); + }, + }, + ]} + extra="请使用/分隔联系人和电话,如:张三/13800138000" + > + { + if (value === undefined || value === null || value === "") { + return Promise.resolve(); + } + if (Number(value) < 0) { + return Promise.reject( + new Error("固定资产总值应为非负数字"), + ); + } + return Promise.resolve(); + }, + }, + ]} > - - - - - - typeof value === "string" ? value.trim() : value - } - > - - - - - - - - - - - typeof value === "string" ? value.trim() : value - } - > - - - - - - - typeof value === "string" ? value.trim() : value - } - > - - - - - - - - - - - - - - - - typeof value === "string" ? value.trim() : value - } - > - - - - { - form.setFieldValue( - "enterpriseScaleName", - option?.label || "", - ); - }} - /> - - - - - - { - form.setFieldValue( - "filingRecordStatusName", - option?.label || "", - ); - }} - /> - - - - - - - - - setMapPickerVisible(false)} - onConfirm={handleMapConfirm} - /> - {editing && ( -
- - - {!hasExistingData && ( - - )} - - -
- )} + + {QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => ( + + {opt.label} + + ))} + + + + {/* 原型:单位基本情况介绍(可附页),TextArea rows=7 */} + + + + + + + {editing && ( +
+ + + {!hasExistingData && ( + + )} + + +
+ )} + +
); }