dev-tmp1
tangjie 2026-08-14 17:32:18 +08:00
parent 8eeb411021
commit 61333696b4
3 changed files with 283 additions and 93 deletions

View File

@ -4,7 +4,7 @@ import { PlusOutlined } from "@ant-design/icons";
const isImage = (url) =>
/\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
export default function AttachmentUpload({ name, label, disabled = false, maxCount, accept, extra ,rules, style }) {
export default function AttachmentUpload({ name, label, disabled, maxCount, accept, extra ,rules, style }) {
const [previewImage, setPreviewImage] = useState("");
return (

View File

@ -18,6 +18,7 @@ import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
// 二者取值集合完全一致(煤矿/金属非金属矿/陆地油气/油气管道/石油化工医药/烟花爆竹/金属冶炼),无需新增枚举。
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import { NS_ORG_INFO } from "~/enumerate/namespace";
import AttachmentUpload from "~/components/AttachmentUpload";
import {
creditCodeRule,
@ -73,8 +74,13 @@ function OrgInfoPage(props) {
if (res?.data?.id) {
sessionStorage.setItem("orgInfo", JSON.stringify(res.data));
setDetail(res.data);
let attachmentUrls = res.data.attachmentUrls;
if (typeof attachmentUrls === "string" && attachmentUrls) {
try { attachmentUrls = JSON.parse(attachmentUrls); } catch {}
}
form.setFieldsValue({
...res.data,
attachmentUrls,
// 2026-08-08 联系人及电话:后端 contactName/contactPhone 两列合并为单字段展示
contactNamePhone: mergeContactNamePhone(
res.data.contactName,
@ -122,6 +128,9 @@ function OrgInfoPage(props) {
contactPhone,
// 移除仅用于展示的合并字段,避免提交冗余
contactNamePhone: undefined,
attachmentUrls: formValues.attachmentUrls
? JSON.stringify(formValues.attachmentUrls)
: undefined,
infoDisclosureUrl: formValues.infoDisclosureUrl
? normalizeUrl(formValues.infoDisclosureUrl)
: undefined,
@ -198,6 +207,17 @@ function OrgInfoPage(props) {
/>
</Form.Item>
</Col>
{/* 营业执照附件 */}
<Col span={24}>
<AttachmentUpload
name="attachmentUrls"
label="营业执照"
accept=".pdf,.jpg,.jpeg,.png"
maxCount={5}
extra="支持上传营业执照扫描件限定pdf和图片最多5个。"
rules={[{ required: true, message: "请上传营业执照" }]}
/>
</Col>
{/* 原型注册地址占整行full */}
<Col span={24}>
<Form.Item

View File

@ -1,5 +1,6 @@
import {
Button,
Checkbox,
DatePicker,
Descriptions,
Form,
@ -26,14 +27,20 @@ import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import { isOrgAccountEnabled } from "~/utils/enterpriseInfo/adapter";
import {
CHONGQING_DISTRICTS,
ENTERPRISE_SCALE_OPTIONS,
ORG_ACCOUNT_STATE_OPTIONS,
ENTERPRISE_STATUS_OPTIONS,
ENTERPRISE_STATUS_OPTIONS_MAP,
QUALIFICATION_INDUSTRY_OPTIONS_MAP
} from "~/enumerate/enterpriseOptions";
import { NS_ORG_INFO } from "~/enumerate/namespace";
import AttachmentUpload from "~/components/AttachmentUpload";
import { safeListRequest, safeRequest } from "~/utils";
import {
creditCodeRule,
nonNegativeIntegerRule,
normalizeUrl,
phoneRule,
positiveNumberRule,
urlRule,
} from "~/utils/validators";
const { router } = tools;
@ -123,8 +130,13 @@ function OrgAccountPage(props) {
setEditOpen(true);
const res = await loadDetail(id);
if (res?.data) {
let attachmentUrls = res.data.attachmentUrls;
if (typeof attachmentUrls === "string" && attachmentUrls) {
try { attachmentUrls = JSON.parse(attachmentUrls); } catch {}
}
editForm.setFieldsValue({
...res.data,
attachmentUrls,
contactNamePhone: mergeContactNamePhone(
res.data.contactName,
res.data.contactPhone,
@ -214,11 +226,23 @@ function OrgAccountPage(props) {
const onEditSubmit = async () => {
const formValues = await editForm.validateFields();
const split = splitContactNamePhone(formValues.contactNamePhone);
const contactName = split.contactName
? split.contactName.replace(/\s+/g, "")
: undefined;
const contactPhone = split.contactPhone
? split.contactPhone.replace(/\s+/g, "")
: undefined;
const values = {
...formValues,
contactName: split.contactName,
contactPhone: split.contactPhone,
contactName,
contactPhone,
contactNamePhone: undefined,
attachmentUrls: formValues.attachmentUrls
? JSON.stringify(formValues.attachmentUrls)
: undefined,
infoDisclosureUrl: formValues.infoDisclosureUrl
? normalizeUrl(formValues.infoDisclosureUrl)
: undefined,
id: currentId,
};
setEditLoading(true);
@ -234,9 +258,17 @@ function OrgAccountPage(props) {
};
const columns = [
{ title: "机构名称", dataIndex: "unitName", ellipsis: true },
{ title: "账号", dataIndex: "account", ellipsis: true },
{ title: "属地", dataIndex: "districtName", width: 260 },
{ title: "单位名称", dataIndex: "unitName", ellipsis: true },
{
title: "拟申请的法定安全评价业务范围",
dataIndex: "safetyIndustryCategoryCode",
ellipsis: true,
render: (codes) =>
(Array.isArray(codes) ? codes : [])
.map((code) => QUALIFICATION_INDUSTRY_OPTIONS_MAP[code] || code)
.join("、") || "-",
},
{ title: "办公地址", dataIndex: "businessAddress", ellipsis: true },
{
title: "状态",
width: 80,
@ -382,7 +414,7 @@ function OrgAccountPage(props) {
open={viewOpen}
destroyOnHidden
title="机构信息查看"
width={760}
width={900}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
@ -393,38 +425,74 @@ function OrgAccountPage(props) {
>
{detail && (
<Descriptions bordered column={2} size="small">
<Descriptions.Item label="机构名称">
<Descriptions.Item label="单位名称">
{detail.unitName}
</Descriptions.Item>
<Descriptions.Item label="统一社会信用代码">
{detail.creditCode}
</Descriptions.Item>
<Descriptions.Item label="属地">
{detail.districtName}
<Descriptions.Item label="营业执照" span={2}>
{(() => {
let files = [];
if (Array.isArray(detail.attachmentUrls)) {
files = detail.attachmentUrls;
} else if (typeof detail.attachmentUrls === "string" && detail.attachmentUrls) {
try { files = JSON.parse(detail.attachmentUrls); } catch {}
}
if (!files.length) return "-";
return files.map((f, i) => (
<a key={i} href={f.url} target="_blank" rel="noreferrer" style={{ marginRight: 8 }}>
{f.name || f.url}
</a>
));
})()}
</Descriptions.Item>
<Descriptions.Item label="所属行业">
<Descriptions.Item label="注册地址" span={2}>
{detail.registerAddress || "-"}
</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>
{detail.businessAddress || "-"}
</Descriptions.Item>
<Descriptions.Item label="信息公开网址">
{detail.infoDisclosureUrl || "-"}
</Descriptions.Item>
<Descriptions.Item label="资质证书编号">
{detail.qualificationCertNo || "-"}
</Descriptions.Item>
<Descriptions.Item label="法定代表人">
{detail.legalRepresentative || "-"}
</Descriptions.Item>
<Descriptions.Item label="法定代表人电话">
{detail.legalRepresentativePhone || "-"}
</Descriptions.Item>
<Descriptions.Item label="传真">
{detail.fax || "-"}
</Descriptions.Item>
<Descriptions.Item label="联系人及电话">
{mergeContactNamePhone(detail.contactName, detail.contactPhone) || "-"}
</Descriptions.Item>
<Descriptions.Item label="固定资产总值(万元)">
{detail.fixedAssetsTotal ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="工作场所建筑面积(㎡)">
{detail.workplaceArea ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="档案室面积(㎡)">
{detail.archiveRoomArea ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="专职安全评价师数量">
{detail.fulltimeEvaluatorCount ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="注册安全工程师数量">
{detail.registeredEngineerCount ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="拟申请的法定安全评价业务范围" span={2}>
{(detail.safetyIndustryCategoryCode || [])
.map((code) => QUALIFICATION_INDUSTRY_OPTIONS_MAP[code] || code)
.join("、") || "-"}
</Descriptions.Item>
<Descriptions.Item label="经营地址" span={2}>
{detail.businessAddress}
</Descriptions.Item>
<Descriptions.Item label="企业状态">
{ENTERPRISE_STATUS_OPTIONS_MAP[detail.enterpriseStatusCode]}
</Descriptions.Item>
<Descriptions.Item label="联系人及电话" span={2}>
{mergeContactNamePhone(detail.contactName, detail.contactPhone) || "-"}
</Descriptions.Item>
<Descriptions.Item label="法定代表人">
{detail.legalRepresentative}
</Descriptions.Item>
<Descriptions.Item label="法人电话">
{detail.legalRepresentativePhone}
</Descriptions.Item>
<Descriptions.Item label="企业规模" span={2}>
{detail.enterpriseScaleName}
<Descriptions.Item label="单位基本情况介绍" span={2}>
{detail.orgIntro || "-"}
</Descriptions.Item>
</Descriptions>
)}
@ -434,7 +502,7 @@ function OrgAccountPage(props) {
open={editOpen}
destroyOnHidden
title="编辑机构信息"
width={760}
width={900}
confirmLoading={editLoading}
okText="保存"
cancelText="取消"
@ -450,58 +518,94 @@ function OrgAccountPage(props) {
<Col span={12}>
<Form.Item
name="unitName"
label="机构名称"
rules={[{ required: true, message: "请输入机构名称" }]}
label="单位名称"
rules={[{ required: true, message: "请输入单位名称" }]}
>
<Input />
<Input placeholder="请输入单位名称" allowClear maxLength={200} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="creditCode"
label="统一社会信用代码"
rules={[{ required: true, message: "请输入信用代码" }]}
rules={[creditCodeRule(true)]}
>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="districtName" label="属地">
<Select
options={CHONGQING_DISTRICTS}
placeholder="请选择属地"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="safetyIndustryCategoryCode" label="所属行业">
<Select
mode="multiple"
placeholder="请选择"
allowClear
>
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.value}>
{opt.label}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="businessAddress" label="经营地址">
<Input placeholder="请输入" allowClear />
<Input placeholder="请输入统一社会信用代码" allowClear maxLength={18} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="enterpriseStatusCode" label="企业状态">
<Select
options={ENTERPRISE_STATUS_OPTIONS}
placeholder="请选择"
allowClear
<AttachmentUpload
name="attachmentUrls"
label="营业执照"
accept=".pdf,.jpg,.jpeg,.png"
maxCount={5}
extra="支持上传营业执照扫描件限定pdf和图片最多5个。"
rules={[{ required: true, message: "请上传营业执照" }]}
/>
</Col>
<Col span={24}>
<Form.Item
name="registerAddress"
label="注册地址"
rules={[{ required: true, message: "请输入注册地址" }]}
>
<Input placeholder="请输入注册地址" maxLength={500} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="businessAddress"
label="办公地址"
rules={[{ required: true, message: "请输入办公地址" }]}
>
<Input.TextArea rows={3} placeholder="请输入办公地址" maxLength={500} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="infoDisclosureUrl"
label="信息公开网址"
rules={[urlRule("信息公开网址", true)]}
normalize={(value) =>
typeof value === "string" ? value.trim() : value
}
>
<Input placeholder="请输入信息公开网址" allowClear maxLength={500} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="qualificationCertNo"
label="资质证书编号"
extra="非必填,初次申请无需填写"
>
<Input placeholder="初次申请无需填写" allowClear maxLength={100} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="legalRepresentative"
label="法定代表人"
rules={[{ required: true, message: "请输入法定代表人" }]}
>
<Input placeholder="请输入法定代表人" allowClear maxLength={50} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="legalRepresentativePhone"
label="法定代表人电话"
rules={[phoneRule("法定代表人电话", true)]}
normalize={(value) =>
typeof value === "string" ? value.trim() : value
}
>
<Input placeholder="请输入法定代表人电话" allowClear maxLength={20} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="fax" label="传真" rules={[{ required: true, message: "请输入传真" }]}>
<Input placeholder="请输入传真" allowClear maxLength={20} />
</Form.Item>
</Col>
<Col span={12}>
@ -552,33 +656,99 @@ function OrgAccountPage(props) {
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="legalRepresentative" label="法定代表人">
<Input placeholder="请选择" allowClear />
<Form.Item
name="fixedAssetsTotal"
label="固定资产总值(万元)"
rules={[
{ required: true, message: "请输入固定资产总值" },
{
validator: (_, value) => {
if (value === undefined || value === null || value === "") {
return Promise.resolve();
}
if (Number(value) < 0) {
return Promise.reject(new Error("固定资产总值应为非负数字"));
}
return Promise.resolve();
},
},
]}
>
<InputNumber style={{ width: "100%" }} min={0} precision={4} max={99999999999} allowClear placeholder="请输入固定资产总值" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="legalRepresentativePhone"
label="法人电话"
rules={[
{
pattern: /(^1[3-9]\d{9}$)|(^0\d{2,3}-?\d{7,8}$)/,
message: "请输入正确的手机号或座机号",
},
]}
name="workplaceArea"
label="工作场所建筑面积(㎡)"
rules={[positiveNumberRule("工作场所建筑面积", true)]}
>
<Input placeholder="请输入" allowClear />
<InputNumber style={{ width: "100%" }} min={0} max={99999} precision={2} placeholder="请输入工作场所建筑面积" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="enterpriseScaleName" label="企业规模">
<Select
placeholder="请选择"
allowClear
options={ENTERPRISE_SCALE_OPTIONS.map((o) => ({
label: o.label,
value: o.label,
}))}
<Form.Item
name="archiveRoomArea"
label="档案室面积(㎡)"
rules={[positiveNumberRule("档案室面积", true)]}
>
<InputNumber style={{ width: "100%" }} min={0} max={99999} precision={2} placeholder="请输入档案室面积" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="fulltimeEvaluatorCount"
label="专职安全评价师数量"
rules={[nonNegativeIntegerRule("专职安全评价师数量", true)]}
>
<InputNumber style={{ width: "100%" }} min={0} max={99999} precision={0} placeholder="请输入专职安全评价师数量" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="registeredEngineerCount"
label="注册安全工程师数量"
rules={[nonNegativeIntegerRule("注册安全工程师数量", true)]}
>
<InputNumber style={{ width: "100%" }} min={0} max={99999} precision={0} placeholder="请输入注册安全工程师数量" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="safetyIndustryCategoryCode"
label="拟申请的法定安全评价业务范围"
rules={[{ required: true, message: "请选择拟申请的法定安全评价业务范围" }]}
>
<Checkbox.Group
style={{
display: "grid",
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
gap: "0.65rem 1rem",
padding: "1rem",
border: "1px solid #e2e8f0",
borderRadius: 8,
background: "#f8fafc",
}}
>
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
<Checkbox key={opt.value} value={opt.value}>
{opt.label}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item
name="orgIntro"
label="单位基本情况介绍(可附页)"
rules={[{ required: true, message: "请输入单位基本情况介绍" }]}
>
<Input.TextArea
rows={7}
placeholder="请简要介绍单位基本情况,如成立时间、人员构成、技术力量、业绩等"
maxLength={2000}
showCount
/>
</Form.Item>
</Col>