487 lines
15 KiB
JavaScript
487 lines
15 KiB
JavaScript
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||
import {
|
||
Button,
|
||
DatePicker,
|
||
Descriptions,
|
||
Form,
|
||
Image,
|
||
Input,
|
||
message,
|
||
Modal,
|
||
Select,
|
||
Table,
|
||
|
||
} from "antd";
|
||
import AttachmentUpload from "~/components/AttachmentUpload";
|
||
import { PlusOutlined } from "@ant-design/icons";
|
||
import { useEffect, useState } from "react";
|
||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||
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";
|
||
import { NS_ORG_QUALIFICATION_CERT } from "~/enumerate/namespace";
|
||
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
|
||
import { toDayjs } from "~/utils/dateFormat";
|
||
import { dateRangeRule } from "~/utils/validators";
|
||
|
||
const { router } = tools;
|
||
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 QualificationCertPage(props) {
|
||
const [addModalOpen, setAddModalOpen] = useState(false);
|
||
const [viewModalOpen, setViewModalOpen] = useState(false);
|
||
const [currentId, setCurrentId] = useState("");
|
||
const [form] = Form.useForm();
|
||
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,
|
||
// 映射后端期望的字段名:前端 likeCertName → 后端 certName
|
||
certName: router.query.likeCertName || undefined,
|
||
// 映射日期筛选:前端 validStartDate/validEndDate → 后端 startTime/endTime (LocalDateTime 格式)
|
||
startTime: router.query.validStartDate ? `${router.query.validStartDate} 00:00:00` : undefined,
|
||
endTime: router.query.validEndDate ? `${router.query.validEndDate} 23:59:59` : undefined,
|
||
// 后端分页字段名为 size
|
||
size: router.query.pageSize || 10,
|
||
};
|
||
const res = await props.orgQualificationCertList(params);
|
||
if (res?.success !== false) {
|
||
setDataSource(res?.data || []);
|
||
setTotal(res?.total || 0);
|
||
}
|
||
} catch (err) {
|
||
console.warn("[QualificationCert] list failed:", err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleReset = (values) => {
|
||
router.query = { ...values, validStartDate: undefined, validEndDate: undefined, current: 1, pageSize: 10 };
|
||
getData();
|
||
};
|
||
|
||
useEffect(() => {
|
||
const params = {
|
||
...router.query
|
||
};
|
||
if (params.validStartDate && params.validEndDate) {
|
||
params.validDate = [toDayjs(params.validStartDate), toDayjs(params.validEndDate)];
|
||
}
|
||
form.setFieldsValue(params);
|
||
getData();
|
||
}, []);
|
||
|
||
const onDelete = (id) => {
|
||
Modal.confirm({
|
||
title: "提示",
|
||
content: "数据将会删除,您是否确认删除?",
|
||
okText: "是",
|
||
cancelText: "否",
|
||
onOk: async () => {
|
||
const res = await props.orgQualificationCertRemove({ data: id });
|
||
if (res?.success !== false) {
|
||
message.success("删除成功");
|
||
getData();
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
|
||
|
||
return (
|
||
<PageLayout
|
||
title={
|
||
<div>
|
||
<span>安全评价资质证书管理</span>
|
||
<div className="pageLayout-extra">
|
||
机构需将自身拥有的安全评价资质证书信息录入系统,包括资质等级、发证机关、发证日期、有效期等。
|
||
</div>
|
||
</div>
|
||
}
|
||
extra={
|
||
<Button
|
||
type="primary"
|
||
icon={<PlusOutlined />}
|
||
onClick={() => {
|
||
setCurrentId("");
|
||
setAddModalOpen(true);
|
||
}}
|
||
>
|
||
新增证书
|
||
</Button>
|
||
}
|
||
>
|
||
<SearchForm
|
||
style={{ marginBottom: 24 }}
|
||
form={form}
|
||
loading={loading}
|
||
formLine={[
|
||
<Form.Item key="likeCertName" name="likeCertName">
|
||
<ControlWrapper.Input
|
||
label="证书名称"
|
||
placeholder="请输入证书名称"
|
||
allowClear
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="validDate" name="validDate">
|
||
<ControlWrapper.DatePicker.RangePicker label="证书有效期" />
|
||
</Form.Item>,
|
||
]}
|
||
onReset={handleReset}
|
||
onFinish={(values) => {
|
||
if (values.validDate) {
|
||
values.validStartDate = values.validDate?.[0]?.format("YYYY-MM-DD");
|
||
values.validEndDate = values.validDate?.[1]?.format("YYYY-MM-DD");
|
||
delete values.validDate;
|
||
}
|
||
router.query = {
|
||
...values,
|
||
current: 1,
|
||
pageSize: 10,
|
||
};
|
||
getData();
|
||
}}
|
||
/>
|
||
|
||
<Table
|
||
rowKey="id"
|
||
columns={[
|
||
{ title: "证照类别", dataIndex: "licenseTypeName" },
|
||
{ title: "证书名称", dataIndex: "certName" },
|
||
{
|
||
title: "证书有效期",
|
||
width: 220,
|
||
render: (_, record) =>
|
||
`${record.validStartDate ?? ""} ~ ${record.validEndDate ?? ""}`,
|
||
},
|
||
{ title: "证书编号", dataIndex: "certNo" },
|
||
{
|
||
title: "状态",
|
||
width: 80,
|
||
render: (_, record) =>
|
||
record.enableFlag === 1 ? "启用" : "禁用",
|
||
},
|
||
{
|
||
title: "照片",
|
||
width: 100,
|
||
render: (_, record) =>
|
||
record.certImageUrl ? (
|
||
<Image width={60} height={60} style={{ objectFit: "cover" }} src={record.certImageUrl.split(",")[0]?.trim()} preview={{ mask: "查看" }} />
|
||
) : (
|
||
<span>无</span>
|
||
),
|
||
},
|
||
{
|
||
title: "操作",
|
||
width: 180,
|
||
fixed: "right",
|
||
render: (_, record) => (
|
||
<TableAction>
|
||
<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>
|
||
|
||
</TableAction>
|
||
),
|
||
},
|
||
]}
|
||
dataSource={dataSource}
|
||
scroll={{ y: props.scrollY , x: 1400}}
|
||
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();
|
||
},
|
||
}}
|
||
/>
|
||
|
||
{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("");
|
||
}}
|
||
/>
|
||
)}
|
||
</PageLayout>
|
||
);
|
||
}
|
||
|
||
function CertFormModal({
|
||
open,
|
||
currentId,
|
||
requestAdd,
|
||
requestEdit,
|
||
requestDetails,
|
||
onCancel,
|
||
onSuccess,
|
||
}) {
|
||
const [form] = Form.useForm();
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [detailLoading, setDetailLoading] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
if (!currentId) {
|
||
form.resetFields();
|
||
return;
|
||
}
|
||
let cancelled = false;
|
||
(async () => {
|
||
setDetailLoading(true);
|
||
try {
|
||
const res = await requestDetails({ id: currentId });
|
||
if (cancelled || !res?.data) return;
|
||
const data = { ...res.data };
|
||
data.issueDate = toDayjs(data.issueDate);
|
||
data.validDate = [toDayjs(data.validStartDate), toDayjs(data.validEndDate)];
|
||
data.certImageUrl = data.certImageUrl;
|
||
form.setFieldsValue(data);
|
||
} catch (err) {
|
||
console.warn("[QualificationCert] load detail failed:", err);
|
||
} finally {
|
||
if (!cancelled) setDetailLoading(false);
|
||
}
|
||
})();
|
||
return () => { cancelled = true; };
|
||
}, [open, currentId]);
|
||
|
||
const handleCancel = () => {
|
||
form.resetFields();
|
||
onCancel();
|
||
};
|
||
|
||
const handleSubmit = async (values) => {
|
||
try {
|
||
setSubmitting(true);
|
||
values.validStartDate = values.validDate?.[0];
|
||
values.validEndDate = values.validDate?.[1];
|
||
|
||
values.certImageUrl = Array.isArray(values.certImageUrl) ? values.certImageUrl.map((item) => item.url).join(",") : values.certImageUrl;
|
||
if (currentId) values.id = currentId;
|
||
const request = currentId ? requestEdit : requestAdd;
|
||
const res = await request(values);
|
||
if (res?.success !== false) {
|
||
message.success(currentId ? "编辑成功" : "添加成功");
|
||
handleCancel();
|
||
onSuccess();
|
||
}
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
open={open}
|
||
destroyOnHidden
|
||
maskClosable={false}
|
||
title={currentId ? "编辑证书" : "添加证书"}
|
||
width={800}
|
||
loading={detailLoading}
|
||
confirmLoading={submitting}
|
||
onOk={form.submit}
|
||
onCancel={handleCancel}
|
||
>
|
||
<div style={{ color: "#999", marginBottom: 16, fontSize: 12 }}>
|
||
严谨在本互联网非涉密平台处理、传输国家秘密和工作秘密,请确认扫描、传输的文件资料不涉及国家秘密和工作秘密
|
||
</div>
|
||
<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>
|
||
<AttachmentUpload name="certImageUrl" accept="image/*" label="证书图片" maxCount={3} />
|
||
</Form>
|
||
</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 {
|
||
const res = await requestDetails({ id: currentId });
|
||
if (cancelled || !res?.data) return;
|
||
setInfo({ ...res.data, certImageUrlList: parseCertImageUrl(res.data.certImageUrl) });
|
||
} catch (err) {
|
||
console.warn("[QualificationCert] load view failed:", err);
|
||
} finally {
|
||
if (!cancelled) setDetailLoading(false);
|
||
}
|
||
})();
|
||
return () => { cancelled = true; };
|
||
}, [open, currentId]);
|
||
|
||
return (
|
||
<Modal
|
||
open={open}
|
||
destroyOnHidden
|
||
maskClosable={false}
|
||
title="证书详情"
|
||
width={800}
|
||
loading={detailLoading}
|
||
cancelText="返回"
|
||
okButtonProps={{ style: { display: "none" } }}
|
||
onCancel={onCancel}
|
||
>
|
||
<Descriptions bordered column={1} labelStyle={{ width: 160 }}>
|
||
<Descriptions.Item label="证照类型">{info.licenseTypeName}</Descriptions.Item>
|
||
<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="证书图片">
|
||
{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>
|
||
)}
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
export default Connect(
|
||
[NS_ORG_QUALIFICATION_CERT],
|
||
true,
|
||
)(AntdTableFuncControl(QualificationCertPage)); |