safety-eval-service-frontend/src/pages/Container/EnterpriseInfo/QualificationCert/index.js

500 lines
15 KiB
JavaScript
Raw Normal View History

2026-06-23 18:07:30 +08:00
import { Connect } from "@cqsjjb/jjb-dva-runtime";
2026-07-06 16:49:57 +08:00
import {
Button,
2026-07-07 13:54:55 +08:00
DatePicker,
2026-07-06 16:49:57 +08:00
Descriptions,
Form,
2026-07-07 13:54:55 +08:00
Image,
Input,
2026-07-06 16:49:57 +08:00
message,
Modal,
2026-07-07 13:54:55 +08:00
Select,
2026-07-06 16:49:57 +08:00
Table,
2026-07-07 13:54:55 +08:00
2026-07-06 16:49:57 +08:00
} from "antd";
2026-07-07 13:54:55 +08:00
import AttachmentUpload from "~/components/AttachmentUpload";
import { PlusOutlined } from "@ant-design/icons";
2026-06-23 18:07:30 +08:00
import { useEffect, useState } from "react";
2026-06-29 16:45:58 +08:00
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
2026-06-30 18:30:30 +08:00
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
2026-06-23 18:07:30 +08:00
import { NS_ORG_QUALIFICATION_CERT } from "~/enumerate/namespace";
2026-06-26 14:27:49 +08:00
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
2026-06-23 18:07:30 +08:00
import { toDayjs } from "~/utils/dateFormat";
import { dateRangeRule } from "~/utils/validators";
2026-06-30 18:30:30 +08:00
const { router } = tools;
2026-07-07 13:54:55 +08:00
const { TextArea } = Input;
const { RangePicker } = DatePicker;
/** certImageUrl逗号分隔URL↔ 文件列表({ url }[] */
function parseCertImageUrl(str) {
if (!str) return [];
return String(str).split(",").filter(Boolean).map((url, i) => ({
url: url.trim(),
name: `证书图片${i + 1}.jpg`,
}));
}
function stringifyCertImageUrl(files) {
if (!files?.length) return undefined;
return files.map((f) => f.url || f.response?.data?.url).filter(Boolean).join(",");
}
2026-06-30 18:30:30 +08:00
2026-06-23 18:07:30 +08:00
function QualificationCertPage(props) {
const [addModalOpen, setAddModalOpen] = useState(false);
const [viewModalOpen, setViewModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const [form] = Form.useForm();
2026-06-30 18:30:30 +08:00
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
const getData = async () => {
setLoading(true);
try {
const params = {
...router.query,
current: router.query.current || 1,
pageSize: router.query.pageSize || 10,
};
const res = await props.orgQualificationCertList(params);
if (res?.success !== false) {
2026-07-07 13:54:55 +08:00
setDataSource(res?.data || []);
setTotal(res?.total || 0);
2026-06-30 18:30:30 +08:00
}
} catch (err) {
console.warn("[QualificationCert] list failed:", err);
} finally {
setLoading(false);
}
};
const handleReset = (values) => {
2026-07-07 13:54:55 +08:00
router.query = { ...values, validStartDate: undefined, validEndDate: undefined, current: 1, pageSize: 10 };
2026-06-30 18:30:30 +08:00
getData();
};
useEffect(() => {
2026-07-07 13:54:55 +08:00
const params = {
...router.query
};
if (params.validStartDate && params.validEndDate) {
params.validDate = [toDayjs(params.validStartDate), toDayjs(params.validEndDate)];
}
form.setFieldsValue(params);
2026-06-30 18:30:30 +08:00
getData();
}, []);
2026-06-23 18:07:30 +08:00
const onDelete = (id) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
2026-07-07 13:59:46 +08:00
const res = await props.orgQualificationCertRemove({ data: id });
2026-06-23 18:07:30 +08:00
if (res?.success !== false) {
message.success("删除成功");
getData();
}
},
});
};
2026-07-07 13:54:55 +08:00
const onToggleStatus = (record) => {
const enabled = record.enableFlag === 1;
2026-06-23 18:07:30 +08:00
Modal.confirm({
title: "提示",
2026-07-07 13:54:55 +08:00
content: enabled ? "是否停用该证书?" : "是否启用该证书?",
2026-06-23 18:07:30 +08:00
okText: "是",
cancelText: "否",
onOk: async () => {
2026-07-07 13:54:55 +08:00
const action = enabled ? props.orgQualificationCertDisable : props.orgQualificationCertEnable;
2026-07-07 14:32:37 +08:00
const res = await action({ data: record.id });
2026-06-23 18:07:30 +08:00
if (res?.success !== false) {
message.success("操作成功");
2026-06-26 14:27:49 +08:00
await getData();
2026-06-30 18:30:30 +08:00
} else {
2026-06-26 14:27:49 +08:00
message.error(res?.message || "操作失败");
2026-06-23 18:07:30 +08:00
}
},
});
};
return (
2026-06-30 18:30:30 +08:00
<PageLayout
title={
<div>
<span>安全评价资质证书管理</span>
<div className="pageLayout-extra">
机构需将自身拥有的安全评价资质证书信息录入系统包括资质等级发证机关发证日期有效期等
</div>
</div>
}
2026-07-06 16:49:57 +08:00
extra={
<Button
type="primary"
2026-07-07 13:54:55 +08:00
icon={<PlusOutlined />}
2026-07-06 16:49:57 +08:00
onClick={() => {
setCurrentId("");
setAddModalOpen(true);
}}
>
新增证书
</Button>
}
2026-06-30 18:30:30 +08:00
>
<SearchForm
style={{ marginBottom: 24 }}
2026-06-23 18:07:30 +08:00
form={form}
2026-06-30 18:30:30 +08:00
loading={loading}
formLine={[
<Form.Item key="likeCertName" name="likeCertName">
2026-07-06 16:49:57 +08:00
<ControlWrapper.Input
label="证书名称"
placeholder="请输入证书名称"
allowClear
/>
2026-06-30 18:30:30 +08:00
</Form.Item>,
<Form.Item key="validDate" name="validDate">
<ControlWrapper.DatePicker.RangePicker label="证书有效期" />
</Form.Item>,
2026-06-23 18:07:30 +08:00
]}
2026-06-30 18:30:30 +08:00
onReset={handleReset}
onFinish={(values) => {
2026-07-07 13:54:55 +08:00
if (values.validDate) {
values.validStartDate = values.validDate?.[0]?.format("YYYY-MM-DD");
values.validEndDate = values.validDate?.[1]?.format("YYYY-MM-DD");
delete values.validDate;
}
2026-06-30 18:30:30 +08:00
router.query = {
...values,
current: 1,
pageSize: 10,
};
getData();
}}
2026-06-23 18:07:30 +08:00
/>
2026-06-30 18:30:30 +08:00
2026-06-23 18:07:30 +08:00
<Table
2026-06-30 18:30:30 +08:00
rowKey="id"
2026-06-23 18:07:30 +08:00
columns={[
2026-07-07 13:54:55 +08:00
{ title: "证照类别", dataIndex: "licenseTypeName" },
2026-06-23 18:07:30 +08:00
{ title: "证书名称", dataIndex: "certName" },
{
title: "证书有效期",
width: 220,
2026-07-06 16:49:57 +08:00
render: (_, record) =>
`${record.validStartDate ?? ""} ~ ${record.validEndDate ?? ""}`,
2026-06-23 18:07:30 +08:00
},
{ title: "证书编号", dataIndex: "certNo" },
{
title: "状态",
width: 80,
2026-07-06 16:49:57 +08:00
render: (_, record) =>
2026-07-07 13:54:55 +08:00
record.enableFlag === 1 ? "启用" : "禁用",
2026-06-23 18:07:30 +08:00
},
{
title: "照片",
width: 100,
2026-06-30 18:30:30 +08:00
render: (_, record) =>
2026-07-07 13:54:55 +08:00
record.certImageUrl ? (
<Image width={60} height={60} style={{ objectFit: "cover" }} src={record.certImageUrl.split(",")[0]?.trim()} preview={{ mask: "查看" }} />
2026-07-06 16:49:57 +08:00
) : (
<span></span>
),
2026-06-23 18:07:30 +08:00
},
{
title: "操作",
2026-06-30 18:30:30 +08:00
width: 200,
fixed: "right",
2026-06-23 18:07:30 +08:00
render: (_, record) => (
2026-06-30 18:30:30 +08:00
<TableAction>
2026-06-23 18:07:30 +08:00
<Button
type="link"
onClick={() => {
setCurrentId(record.id);
setViewModalOpen(true);
}}
>
查看
</Button>
<Button
type="link"
onClick={() => {
setCurrentId(record.id);
setAddModalOpen(true);
}}
>
编辑
</Button>
<Button danger type="link" onClick={() => onDelete(record.id)}>
删除
</Button>
2026-07-07 14:32:37 +08:00
2026-06-30 18:30:30 +08:00
</TableAction>
2026-06-23 18:07:30 +08:00
),
},
]}
2026-06-30 18:30:30 +08:00
dataSource={dataSource}
2026-07-07 14:32:37 +08:00
scroll={{ y: props.scrollY , x: 1400}}
2026-06-30 18:30:30 +08:00
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: router.query.current || 1,
pageSize: router.query.pageSize || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, pageSize };
getData();
},
}}
2026-06-23 18:07:30 +08:00
/>
{addModalOpen && (
<CertFormModal
open={addModalOpen}
currentId={currentId}
requestAdd={props.orgQualificationCertAdd}
requestEdit={props.orgQualificationCertEdit}
requestDetails={props.orgQualificationCertInfo}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={getData}
/>
)}
{viewModalOpen && (
<CertViewModal
open={viewModalOpen}
currentId={currentId}
requestDetails={props.orgQualificationCertInfo}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
)}
2026-06-29 16:45:58 +08:00
</PageLayout>
2026-06-23 18:07:30 +08:00
);
}
function CertFormModal({
open,
currentId,
requestAdd,
requestEdit,
requestDetails,
onCancel,
onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
2026-07-07 13:54:55 +08:00
if (!open) return;
2026-06-23 18:07:30 +08:00
if (!currentId) {
form.resetFields();
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
2026-07-07 13:54:55 +08:00
const res = await requestDetails({ id: currentId });
if (cancelled || !res?.data) return;
2026-06-23 18:07:30 +08:00
const data = { ...res.data };
data.issueDate = toDayjs(data.issueDate);
2026-07-07 13:54:55 +08:00
data.validDate = [toDayjs(data.validStartDate), toDayjs(data.validEndDate)];
2026-07-07 13:59:46 +08:00
data.certImageUrl = data.certImageUrl;
2026-06-23 18:07:30 +08:00
form.setFieldsValue(data);
2026-06-30 18:30:30 +08:00
} catch (err) {
2026-06-23 18:07:30 +08:00
console.warn("[QualificationCert] load detail failed:", err);
2026-06-30 18:30:30 +08:00
} finally {
2026-07-07 13:54:55 +08:00
if (!cancelled) setDetailLoading(false);
2026-06-23 18:07:30 +08:00
}
})();
2026-07-07 13:54:55 +08:00
return () => { cancelled = true; };
2026-06-23 18:07:30 +08:00
}, [open, currentId]);
const handleCancel = () => {
form.resetFields();
onCancel();
};
const handleSubmit = async (values) => {
try {
setSubmitting(true);
values.validStartDate = values.validDate?.[0];
values.validEndDate = values.validDate?.[1];
2026-07-07 13:59:46 +08:00
values.certImageUrl = values.certImageUrl?.map((item) => item.url).join(",");
2026-07-07 13:54:55 +08:00
if (currentId) values.id = currentId;
2026-06-23 18:07:30 +08:00
const request = currentId ? requestEdit : requestAdd;
const res = await request(values);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
handleCancel();
onSuccess();
}
2026-06-30 18:30:30 +08:00
} finally {
2026-06-23 18:07:30 +08:00
setSubmitting(false);
}
};
return (
<Modal
open={open}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-23 18:07:30 +08:00
maskClosable={false}
title={currentId ? "编辑证书" : "添加证书"}
width={800}
loading={detailLoading}
confirmLoading={submitting}
onOk={form.submit}
onCancel={handleCancel}
>
<div style={{ color: "#999", marginBottom: 16, fontSize: 12 }}>
严谨在本互联网非涉密平台处理传输国家秘密和工作秘密请确认扫描传输的文件资料不涉及国家秘密和工作秘密
</div>
2026-07-07 13:54:55 +08:00
<Form form={form} layout="horizontal" onFinish={handleSubmit} labelCol={{ span: 4 }}>
<Form.Item
name="licenseTypeName"
label="证照类型"
rules={[{ required: true, message: "请选择证照类型" }]}
>
<Select placeholder="请选择证照类型">
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.label}>
{opt.label}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="certName"
label="证书名称"
rules={[{ required: true, message: "请输入证书名称" }]}
>
<Input placeholder="请输入证书名称" />
</Form.Item>
<Form.Item
name="certNo"
label="证明编号"
rules={[{ required: true, message: "请输入证明编号" }]}
>
<Input placeholder="请输入证明编号" />
</Form.Item>
<Form.Item
name="issueOrg"
label="发证机关"
rules={[{ required: true, message: "请输入发证机关" }]}
>
<Input placeholder="请输入发证机关" />
</Form.Item>
<Form.Item
name="issueDate"
label="发证日期"
rules={[{ required: true, message: "请选择发证日期" }]}
>
<DatePicker style={{ width: "100%" }} placeholder="请选择发证日期" />
</Form.Item>
<Form.Item
name="validDate"
label="证书有效期"
rules={[
{ required: true, message: "请选择证书有效期" },
dateRangeRule(),
]}
>
<RangePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="remark" label="备注">
<TextArea rows={3} placeholder="请输入备注" />
</Form.Item>
2026-07-07 13:59:46 +08:00
<AttachmentUpload name="certImageUrl" accept="image/*" label="证书图片" maxCount={3} />
2026-07-07 13:54:55 +08:00
</Form>
2026-06-23 18:07:30 +08:00
</Modal>
);
}
function CertViewModal({ open, currentId, requestDetails, onCancel }) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
if (!open || !currentId) {
setInfo({});
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
2026-07-07 13:54:55 +08:00
const res = await requestDetails({ id: currentId });
if (cancelled || !res?.data) return;
setInfo({ ...res.data, certImageUrlList: parseCertImageUrl(res.data.certImageUrl) });
2026-06-30 18:30:30 +08:00
} catch (err) {
2026-06-23 18:07:30 +08:00
console.warn("[QualificationCert] load view failed:", err);
2026-06-30 18:30:30 +08:00
} finally {
2026-07-07 13:54:55 +08:00
if (!cancelled) setDetailLoading(false);
2026-06-23 18:07:30 +08:00
}
})();
2026-07-07 13:54:55 +08:00
return () => { cancelled = true; };
2026-06-23 18:07:30 +08:00
}, [open, currentId]);
return (
<Modal
open={open}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-23 18:07:30 +08:00
maskClosable={false}
title="证书详情"
width={800}
loading={detailLoading}
cancelText="返回"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={1} labelStyle={{ width: 160 }}>
2026-07-07 13:54:55 +08:00
<Descriptions.Item label="证照类型">{info.licenseTypeName}</Descriptions.Item>
2026-06-23 18:07:30 +08:00
<Descriptions.Item label="证书名称">{info.certName}</Descriptions.Item>
<Descriptions.Item label="证明编号">{info.certNo}</Descriptions.Item>
<Descriptions.Item label="发证机关">{info.issueOrg}</Descriptions.Item>
<Descriptions.Item label="发证日期">{info.issueDate}</Descriptions.Item>
<Descriptions.Item label="证书有效期">
{`${info.validStartDate ?? ""} ~ ${info.validEndDate ?? ""}`}
</Descriptions.Item>
<Descriptions.Item label="备注">{info.remark || "-"}</Descriptions.Item>
<Descriptions.Item label="证书图片">
2026-07-07 13:54:55 +08:00
{info.certImageUrlList?.length ? (
<Image.PreviewGroup>
{info.certImageUrlList.map((file, idx) => (
<Image
key={idx}
src={file.url}
alt="证书图片"
width={100}
height={100}
style={{ objectFit: "cover", marginRight: 8 }}
/>
))}
</Image.PreviewGroup>
) : (
<span>暂无图片</span>
)}
2026-06-23 18:07:30 +08:00
</Descriptions.Item>
</Descriptions>
</Modal>
);
}
2026-07-06 16:49:57 +08:00
export default Connect(
[NS_ORG_QUALIFICATION_CERT],
true,
2026-07-07 13:54:55 +08:00
)(AntdTableFuncControl(QualificationCertPage));