dev-tmp1
tangjie 2026-08-12 17:12:19 +08:00
parent 81209bb537
commit c05a311bd6
6 changed files with 1317 additions and 1052 deletions

View File

@ -28,6 +28,8 @@
"@eigenpal/docx-editor-react": "^1.9.0", "@eigenpal/docx-editor-react": "^1.9.0",
"@rc-component/motion": "^1.0.0", "@rc-component/motion": "^1.0.0",
"@rc-component/util": "^1.11.1", "@rc-component/util": "^1.11.1",
"pizzip": "latest",
"docxtemplater": "latest",
"antd": "^5.29.2", "antd": "^5.29.2",
"china-division": "^2.7.0", "china-division": "^2.7.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",

File diff suppressed because it is too large Load Diff

View File

@ -456,7 +456,7 @@ function OrgAccountPage(props) {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={24}> <Col span={24}>
<Form.Item name="businessAddress" label="企事业单位经营地址"> <Form.Item name="businessAddress" label="经营地址">
<Input /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>

View File

@ -13,12 +13,8 @@ import {
DatePicker, DatePicker,
Space, Space,
ConfigProvider, ConfigProvider,
Card,
} from "antd"; } from "antd";
import { import { DownloadOutlined, ExportOutlined } from "@ant-design/icons";
DownloadOutlined,
ExportOutlined,
} from "@ant-design/icons";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction"; import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm"; import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
@ -27,6 +23,7 @@ import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { NS_REGULATOR_EVAL_REPORT, NS_ORG_INFO } from "~/enumerate/namespace"; import { NS_REGULATOR_EVAL_REPORT, NS_ORG_INFO } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { tools } from "@cqsjjb/jjb-common-lib"; import { tools } from "@cqsjjb/jjb-common-lib";
import { renderDocxBlob, fillDocxTemplate } from "~/utils/fillDocxTemplate";
import dayjs from "dayjs"; import dayjs from "dayjs";
import "./index.less"; import "./index.less";
@ -36,6 +33,10 @@ const { RangePicker } = DatePicker;
const API_HOST = window.process?.env?.app?.API_HOST || ""; const API_HOST = window.process?.env?.app?.API_HOST || "";
/** 监督检查单 docx 模板(线上) */
const CHECKLIST_TEMPLATE_URL =
"https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a7c36c2e4b0435c62b76a34.docx";
/** 原型 CSS--radius 8 / --radius-lg 12 */ /** 原型 CSS--radius 8 / --radius-lg 12 */
const PROTO_THEME = { const PROTO_THEME = {
token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6 }, token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6 },
@ -264,6 +265,7 @@ const EvalReportDatabase = (props) => {
const [spotCheckData, setSpotCheckData] = useState([]); const [spotCheckData, setSpotCheckData] = useState([]);
const [orgOptions, setOrgOptions] = useState([]); const [orgOptions, setOrgOptions] = useState([]);
const [spotCheckForm] = Form.useForm(); const [spotCheckForm] = Form.useForm();
const [viewChecklistLoading, setViewChecklistLoading] = useState(false);
const { const {
regulatorEvalReportPageLoading, regulatorEvalReportPageLoading,
@ -274,6 +276,9 @@ const EvalReportDatabase = (props) => {
regulatorSpotCheckReportLoading, regulatorSpotCheckReportLoading,
} = props.regulatorEvalReport || {}; } = props.regulatorEvalReport || {};
const { registeredOrgListData } = props.orgInfo || {};
const fetchSummary = async () => { const fetchSummary = async () => {
const res = await props.regulatorEvalReportSummary(); const res = await props.regulatorEvalReportSummary();
if (res?.success !== false) { if (res?.success !== false) {
@ -289,8 +294,12 @@ const EvalReportDatabase = (props) => {
}; };
// RangePicker 拆成接口字段 // RangePicker 拆成接口字段
if (params.archiveTimeRange?.length === 2) { if (params.archiveTimeRange?.length === 2) {
params.archiveTimeStart = dayjs(params.archiveTimeRange[0]).format("YYYY-MM-DD"); params.archiveTimeStart = dayjs(params.archiveTimeRange[0]).format(
params.archiveTimeEnd = dayjs(params.archiveTimeRange[1]).format("YYYY-MM-DD"); "YYYY-MM-DD",
);
params.archiveTimeEnd = dayjs(params.archiveTimeRange[1]).format(
"YYYY-MM-DD",
);
} }
delete params.archiveTimeRange; delete params.archiveTimeRange;
@ -361,11 +370,96 @@ const EvalReportDatabase = (props) => {
} }
}; };
const getChecklistData = () => {
const data = registeredOrgListData.find(
(o) => o.id === spotCheckForm.getFieldValue("orgId"),
);
const unitName = data.unitName;
return {
unitName,
fileName: `安全评价报告监督检查单_${unitName}.docx`,
data: {
unitName,
creditCode: data.creditCode || "",
qualificationCertNo: data.qualificationCertNo || "",
legalRepresentative: data.legalRepresentative || "",
businessAddress: data.businessAddress || "",
principalName: data.principalName || "",
principalPhone: data.principalPhone || "",
checkTypeName: getOptionLabel(
CHECK_TYPE_OPTIONS,
spotCheckForm.getFieldValue("checkType"),
),
planCheckDate: spotCheckForm.getFieldValue("planCheckDate")
? dayjs(spotCheckForm.getFieldValue("planCheckDate")).format(
"YYYY年M月D日",
)
: "",
checkLeader: spotCheckForm.getFieldValue("checkLeader") || "",
checkLocation: spotCheckForm.getFieldValue("checkLocation") || "",
checkMembers: spotCheckForm.getFieldValue("checkMembers") || "",
reports: spotCheckData.map((item,index) => ({
index: index + 1,
reportNo: item.reportNo || "",
reportName: item.reportName || "",
evalTypeName: item.evalTypeName || "",
industryName: item.industryName || "",
issueDate: item.issueDate || "",
})),
},
};
};
const uploadDocx = async (blob, fileName) => {
const formData = new FormData();
formData.append("file", new File([blob], fileName), fileName);
const res = await fetch(
`${window.process.env.app.API_HOST}/safetyEval/file/upload`,
{
method: "POST",
headers: { token: sessionStorage.getItem("token") },
body: formData,
},
);
const json = await res.json();
const url = json?.data?.url;
if (!url) throw new Error("上传失败");
return url;
};
const handleViewChecklist = async () => {
setViewChecklistLoading(true);
try {
const { fileName, data } = getChecklistData();
const blob = await renderDocxBlob(CHECKLIST_TEMPLATE_URL, data);
const url = await uploadDocx(blob, fileName);
window.open(url, "_blank");
message.success("检查单已生成");
} finally {
setViewChecklistLoading(false);
}
};
const handleDownloadChecklist = async () => {
const { fileName, data } = getChecklistData();
await fillDocxTemplate(
CHECKLIST_TEMPLATE_URL,
data,
fileName,
{ download: true },
);
message.success("检查单已下载");
};
const handleSearch = (values) => { const handleSearch = (values) => {
const next = { ...values, current: 1, size: router.query.size || 20 }; const next = { ...values, current: 1, size: router.query.size || 20 };
if (next.archiveTimeRange?.length === 2) { if (next.archiveTimeRange?.length === 2) {
next.archiveTimeStart = dayjs(next.archiveTimeRange[0]).format("YYYY-MM-DD"); next.archiveTimeStart = dayjs(next.archiveTimeRange[0]).format(
next.archiveTimeEnd = dayjs(next.archiveTimeRange[1]).format("YYYY-MM-DD"); "YYYY-MM-DD",
);
next.archiveTimeEnd = dayjs(next.archiveTimeRange[1]).format(
"YYYY-MM-DD",
);
} else { } else {
next.archiveTimeStart = undefined; next.archiveTimeStart = undefined;
next.archiveTimeEnd = undefined; next.archiveTimeEnd = undefined;
@ -515,15 +609,6 @@ const EvalReportDatabase = (props) => {
} }
}; };
const handleOpenSpotcheckList = async (record) => {
setCurrentReport(record);
setSpotcheckVisible(true);
setSpotcheckList([]);
const res = await props.regulatorSpotcheckList({ reportId: record.id });
if (res?.success !== false) {
setSpotcheckList(res?.data || []);
}
};
const handleOpenSpotcheckStart = (record) => { const handleOpenSpotcheckStart = (record) => {
setCurrentReport(record); setCurrentReport(record);
@ -592,7 +677,6 @@ const EvalReportDatabase = (props) => {
title: "报告编号", title: "报告编号",
dataIndex: "reportNo", dataIndex: "reportNo",
width: 150, width: 150,
}, },
{ {
title: "报告名称", title: "报告名称",
@ -640,12 +724,8 @@ const EvalReportDatabase = (props) => {
fixed: "right", fixed: "right",
render: (_, record) => ( render: (_, record) => (
<TableAction size={4} wrap> <TableAction size={4} wrap>
<a onClick={() => handleViewDetail(record)}> <a onClick={() => handleViewDetail(record)}>查看报告</a>
查看报告 <a onClick={() => handleOpenAi(record)}>AI分析</a>
</a>
<a onClick={() => handleOpenAi(record)}>
AI分析
</a>
</TableAction> </TableAction>
), ),
}, },
@ -669,8 +749,10 @@ const EvalReportDatabase = (props) => {
dataIndex: "checkResultName", dataIndex: "checkResultName",
width: 90, width: 90,
render: (val, record) => { render: (val, record) => {
if (record.checkResultCode === 1) return <span className="redb-tag redb-tag-success">合格</span>; if (record.checkResultCode === 1)
if (record.checkResultCode === 2) return <span className="redb-tag redb-tag-danger">不合格</span>; return <span className="redb-tag redb-tag-success">合格</span>;
if (record.checkResultCode === 2)
return <span className="redb-tag redb-tag-danger">不合格</span>;
return val || "-"; return val || "-";
}, },
}, },
@ -697,8 +779,10 @@ const EvalReportDatabase = (props) => {
dataIndex: "reviewResultName", dataIndex: "reviewResultName",
width: 100, width: 100,
render: (val, record) => { render: (val, record) => {
if (record.reviewResultCode === 1) return <span className="redb-tag redb-tag-success">通过</span>; if (record.reviewResultCode === 1)
if (record.reviewResultCode === 2) return <span className="redb-tag redb-tag-danger">不通过</span>; return <span className="redb-tag redb-tag-success">通过</span>;
if (record.reviewResultCode === 2)
return <span className="redb-tag redb-tag-danger">不通过</span>;
return val || "-"; return val || "-";
}, },
}, },
@ -709,11 +793,7 @@ const EvalReportDatabase = (props) => {
render: (_, record) => { render: (_, record) => {
// 有整改反馈且尚未复核时可复核 // 有整改反馈且尚未复核时可复核
if (record.rectifyFeedback && !record.reviewResultCode) { if (record.rectifyFeedback && !record.reviewResultCode) {
return ( return <a onClick={() => handleOpenReview(record)}>复核</a>;
<a onClick={() => handleOpenReview(record)}>
复核
</a>
);
} }
return "-"; return "-";
}, },
@ -796,7 +876,10 @@ const EvalReportDatabase = (props) => {
/> />
</Form.Item>, </Form.Item>,
<Form.Item key="archiveTimeRange" name="archiveTimeRange"> <Form.Item key="archiveTimeRange" name="archiveTimeRange">
<RangePicker style={{ width: "100%" }} placeholder={["报送时间起", "报送时间止"]} /> <RangePicker
style={{ width: "100%" }}
placeholder={["报送时间起", "报送时间止"]}
/>
</Form.Item>, </Form.Item>,
]} ]}
onFinish={handleSearch} onFinish={handleSearch}
@ -811,10 +894,18 @@ const EvalReportDatabase = (props) => {
<Button type="primary" size="small" onClick={handleOpenSpotCheck}> <Button type="primary" size="small" onClick={handleOpenSpotCheck}>
抽检 抽检
</Button> </Button>
<Button size="small" icon={<DownloadOutlined />} onClick={handleBatchDownload}> <Button
size="small"
icon={<DownloadOutlined />}
onClick={handleBatchDownload}
>
批量下载 批量下载
</Button> </Button>
<Button size="small" icon={<ExportOutlined />} onClick={handleOpenExport}> <Button
size="small"
icon={<ExportOutlined />}
onClick={handleOpenExport}
>
导出目录 导出目录
</Button> </Button>
</div> </div>
@ -888,12 +979,18 @@ const EvalReportDatabase = (props) => {
[ [
"评价类型", "评价类型",
detailData.evalTypeName || detailData.evalTypeName ||
getOptionLabel(EVAL_TYPE_OPTIONS, detailData.evalTypeCode), getOptionLabel(
EVAL_TYPE_OPTIONS,
detailData.evalTypeCode,
),
], ],
[ [
"所属行业", "所属行业",
detailData.industryName || detailData.industryName ||
getOptionLabel(INDUSTRY_OPTIONS, detailData.industryCode), getOptionLabel(
INDUSTRY_OPTIONS,
detailData.industryCode,
),
], ],
[ [
"签发日期", "签发日期",
@ -904,7 +1001,9 @@ const EvalReportDatabase = (props) => {
[ [
"报送时间", "报送时间",
detailData.archiveTime detailData.archiveTime
? dayjs(detailData.archiveTime).format("YYYY-MM-DD HH:mm") ? dayjs(detailData.archiveTime).format(
"YYYY-MM-DD HH:mm",
)
: "-", : "-",
], ],
]} ]}
@ -923,11 +1022,15 @@ const EvalReportDatabase = (props) => {
onClick={() => onClick={() =>
handleFileDownload( handleFileDownload(
detailData.fileUrl, detailData.fileUrl,
detailData.fileName || detailData.reportName || "正式安全评价报告.pdf" detailData.fileName ||
detailData.reportName ||
"正式安全评价报告.pdf",
) )
} }
> >
{detailData.fileName || detailData.reportName || "正式安全评价报告.pdf"} {detailData.fileName ||
detailData.reportName ||
"正式安全评价报告.pdf"}
</span>, </span>,
getFileExtLabel(detailData.fileName), getFileExtLabel(detailData.fileName),
"V1.0", "V1.0",
@ -935,17 +1038,16 @@ const EvalReportDatabase = (props) => {
]} ]}
/> />
</ModalSection> </ModalSection>
<ModalSection <ModalSection title="合同文件" desc="查看上传的合同文件。">
title="合同文件"
desc="查看上传的合同文件。"
>
<ProtoTable <ProtoTable
headers={["文件名称", "文件类型"]} headers={["文件名称", "文件类型"]}
rows={parseFileList(detailData.contractFileUrls).map((f) => [ rows={parseFileList(detailData.contractFileUrls).map((f) => [
<span <span
key={f.url} key={f.url}
style={{ color: "#1677ff", cursor: "pointer" }} style={{ color: "#1677ff", cursor: "pointer" }}
onClick={() => handleFileDownload(f.url, f.name || "未命名文件")} onClick={() =>
handleFileDownload(f.url, f.name || "未命名文件")
}
> >
{f.name || "未命名文件"} {f.name || "未命名文件"}
</span>, </span>,
@ -953,17 +1055,16 @@ const EvalReportDatabase = (props) => {
])} ])}
/> />
</ModalSection> </ModalSection>
<ModalSection <ModalSection title="其他过程文档" desc="查看上传的过程文档。">
title="其他过程文档"
desc="查看上传的过程文档。"
>
<ProtoTable <ProtoTable
headers={["文件名称", "文件类型"]} headers={["文件名称", "文件类型"]}
rows={parseFileList(detailData.processDocUrls).map((f) => [ rows={parseFileList(detailData.processDocUrls).map((f) => [
<span <span
key={f.url} key={f.url}
style={{ color: "#1677ff", cursor: "pointer" }} style={{ color: "#1677ff", cursor: "pointer" }}
onClick={() => handleFileDownload(f.url, f.name || "未命名文件")} onClick={() =>
handleFileDownload(f.url, f.name || "未命名文件")
}
> >
{f.name || "未命名文件"} {f.name || "未命名文件"}
</span>, </span>,
@ -1009,7 +1110,10 @@ const EvalReportDatabase = (props) => {
{aiData && ( {aiData && (
<> <>
<ModalSection title="分析概览" desc={aiData.analysis.overview}> <ModalSection title="分析概览" desc={aiData.analysis.overview}>
<div className="redb-summary-strip" style={{ marginTop: "0.8rem", marginBottom: 0 }}> <div
className="redb-summary-strip"
style={{ marginTop: "0.8rem", marginBottom: 0 }}
>
{aiData.analysis.metrics.map((m) => ( {aiData.analysis.metrics.map((m) => (
<span key={m.label}> <span key={m.label}>
{m.label}<strong>{m.value}</strong> {m.label}<strong>{m.value}</strong>
@ -1033,7 +1137,9 @@ const EvalReportDatabase = (props) => {
<td>{row.dim}</td> <td>{row.dim}</td>
<td>{row.result}</td> <td>{row.result}</td>
<td> <td>
<span className={`redb-tag ${levelClass[row.level] || ""}`}> <span
className={`redb-tag ${levelClass[row.level] || ""}`}
>
{row.level} {row.level}
</span> </span>
</td> </td>
@ -1067,7 +1173,13 @@ const EvalReportDatabase = (props) => {
title="导出设置" title="导出设置"
desc="导出当前查询范围内的数据,系统将记录导出人和导出时间。" desc="导出当前查询范围内的数据,系统将记录导出人和导出时间。"
> >
<Form form={exportForm} layout="vertical" preserve={false} className="redb-form-grid" scrollToFirstError> <Form
form={exportForm}
layout="vertical"
preserve={false}
className="redb-form-grid"
scrollToFirstError
>
<Form.Item <Form.Item
name="scope" name="scope"
label="导出范围" label="导出范围"
@ -1082,7 +1194,12 @@ const EvalReportDatabase = (props) => {
]} ]}
/> />
</Form.Item> </Form.Item>
<Form.Item name="fileFormat" label="文件格式" initialValue="CSV" className="redb-field"> <Form.Item
name="fileFormat"
label="文件格式"
initialValue="CSV"
className="redb-field"
>
<Select <Select
options={[ options={[
{ label: "CSV", value: "CSV" }, { label: "CSV", value: "CSV" },
@ -1091,7 +1208,11 @@ const EvalReportDatabase = (props) => {
]} ]}
/> />
</Form.Item> </Form.Item>
<Form.Item name="exportRemark" label="导出说明" className="redb-field redb-field-full"> <Form.Item
name="exportRemark"
label="导出说明"
className="redb-field redb-field-full"
>
<TextArea rows={3} placeholder="选填" /> <TextArea rows={3} placeholder="选填" />
</Form.Item> </Form.Item>
</Form> </Form>
@ -1114,14 +1235,37 @@ const EvalReportDatabase = (props) => {
title="批量操作确认" title="批量操作确认"
desc="批量分类将按年度、行业和评价类型重新校验目录;批量下载将生成所选报告的压缩文件。" desc="批量分类将按年度、行业和评价类型重新校验目录;批量下载将生成所选报告的压缩文件。"
> >
<Form form={batchForm} layout="vertical" preserve={false} className="redb-form-grid" scrollToFirstError> <Form
<Form.Item name="scopeLabel" label="操作范围" className="redb-field"> form={batchForm}
layout="vertical"
preserve={false}
className="redb-form-grid"
scrollToFirstError
>
<Form.Item
name="scopeLabel"
label="操作范围"
className="redb-field"
>
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<Form.Item name="keepRecord" label="保留原分类记录" className="redb-field"> <Form.Item
<Select options={[{ label: "是", value: "是" }, { label: "否", value: "否" }]} /> name="keepRecord"
label="保留原分类记录"
className="redb-field"
>
<Select
options={[
{ label: "是", value: "是" },
{ label: "否", value: "否" },
]}
/>
</Form.Item> </Form.Item>
<Form.Item name="remark" label="操作说明" className="redb-field redb-field-full"> <Form.Item
name="remark"
label="操作说明"
className="redb-field redb-field-full"
>
<TextArea rows={3} placeholder="选填" /> <TextArea rows={3} placeholder="选填" />
</Form.Item> </Form.Item>
</Form> </Form>
@ -1182,7 +1326,12 @@ const EvalReportDatabase = (props) => {
destroyOnClose destroyOnClose
className="redb-modal" className="redb-modal"
> >
<Form form={spotcheckForm} layout="vertical" preserve={false} scrollToFirstError> <Form
form={spotcheckForm}
layout="vertical"
preserve={false}
scrollToFirstError
>
<Form.Item <Form.Item
name="checkResultCode" name="checkResultCode"
label="抽查结果" label="抽查结果"
@ -1196,20 +1345,34 @@ const EvalReportDatabase = (props) => {
/> />
</Form.Item> </Form.Item>
<Form.Item name="checkOpinion" label="抽查意见"> <Form.Item name="checkOpinion" label="抽查意见">
<TextArea rows={3} placeholder="填写抽查意见" maxLength={500} showCount /> <TextArea
rows={3}
placeholder="填写抽查意见"
maxLength={500}
showCount
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
noStyle noStyle
shouldUpdate={(prev, cur) => prev.checkResultCode !== cur.checkResultCode} shouldUpdate={(prev, cur) =>
prev.checkResultCode !== cur.checkResultCode
}
> >
{({ getFieldValue }) => {({ getFieldValue }) =>
getFieldValue("checkResultCode") === 2 ? ( getFieldValue("checkResultCode") === 2 ? (
<Form.Item <Form.Item
name="rectifyRequire" name="rectifyRequire"
label="整改要求" label="整改要求"
rules={[{ required: true, message: "不合格须填写整改要求" }]} rules={[
{ required: true, message: "不合格须填写整改要求" },
]}
> >
<TextArea rows={3} placeholder="请填写整改要求" maxLength={500} showCount /> <TextArea
rows={3}
placeholder="请填写整改要求"
maxLength={500}
showCount
/>
</Form.Item> </Form.Item>
) : null ) : null
} }
@ -1234,7 +1397,12 @@ const EvalReportDatabase = (props) => {
<div>整改反馈{reviewRecord.rectifyFeedback || "-"}</div> <div>整改反馈{reviewRecord.rectifyFeedback || "-"}</div>
</div> </div>
)} )}
<Form form={reviewForm} layout="vertical" preserve={false} scrollToFirstError> <Form
form={reviewForm}
layout="vertical"
preserve={false}
scrollToFirstError
>
<Form.Item <Form.Item
name="reviewResultCode" name="reviewResultCode"
label="复核结果" label="复核结果"
@ -1273,7 +1441,9 @@ const EvalReportDatabase = (props) => {
> >
<div className="redb-modal-section"> <div className="redb-modal-section">
<h4>抽检设置</h4> <h4>抽检设置</h4>
<p>从选定评价机构已报送的安全评价报告中随机抽取生成监督检查单</p> <p>
从选定评价机构已报送的安全评价报告中随机抽取生成监督检查单
</p>
</div> </div>
<Form form={spotCheckForm} layout="vertical" preserve={false}> <Form form={spotCheckForm} layout="vertical" preserve={false}>
<Row gutter={16}> <Row gutter={16}>
@ -1307,7 +1477,10 @@ const EvalReportDatabase = (props) => {
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item name="planCheckDate" label="计划检查日期"> <Form.Item name="planCheckDate" label="计划检查日期">
<DatePicker style={{ width: "100%" }} placeholder="选择日期" /> <DatePicker
style={{ width: "100%" }}
placeholder="选择日期"
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -1327,7 +1500,10 @@ const EvalReportDatabase = (props) => {
</Col> </Col>
<Col span={24}> <Col span={24}>
<Form.Item name="checkBasis" label="补充检查依据"> <Form.Item name="checkBasis" label="补充检查依据">
<TextArea rows={2} placeholder="可填写本次专项检查、年度计划或属地监管文件依据" /> <TextArea
rows={2}
placeholder="可填写本次专项检查、年度计划或属地监管文件依据"
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@ -1352,6 +1528,20 @@ const EvalReportDatabase = (props) => {
<h4 style={{ margin: 0 }}>随机抽取报告</h4> <h4 style={{ margin: 0 }}>随机抽取报告</h4>
</div> </div>
<Space> <Space>
<Button
size="small"
type="primary"
loading={viewChecklistLoading}
onClick={handleViewChecklist}
>
查看检查单
</Button>
<Button
size="small"
onClick={handleDownloadChecklist}
>
下载检查单
</Button>
<Button <Button
size="small" size="small"
loading={regulatorSpotCheckReportLoading} loading={regulatorSpotCheckReportLoading}
@ -1362,16 +1552,29 @@ const EvalReportDatabase = (props) => {
</Space> </Space>
</div> </div>
<Table <Table
rowKey="id" rowKey="archiveTime"
size="small" size="small"
columns={[ columns={[
{ title: "报告名称", dataIndex: "reportName", ellipsis: true }, {
{ title: "被检查机构", dataIndex: "orgName", width: 200, ellipsis: true }, title: "报告名称",
dataIndex: "reportName",
ellipsis: true,
},
{
title: "被检查机构",
dataIndex: "orgName",
width: 200,
ellipsis: true,
},
{ {
title: "操作", title: "操作",
width: 90, width: 90,
render: (_, record) => ( render: (_, record) => (
<Button type="primary" size="small" onClick={() => handleViewDetail(record)}> <Button
type="primary"
size="small"
onClick={() => handleViewDetail(record)}
>
查看 查看
</Button> </Button>
), ),

View File

@ -0,0 +1,76 @@
import PizZip from "pizzip";
import Docxtemplater from "docxtemplater";
/** 模板文件缓存,避免重复下载 */
const templateCache = new Map();
/**
* 加载 docx 模板文件内容带缓存相同 URL 只下载一次
* @param {string} templateUrl - 模板文件 URL
* @returns {Promise<ArrayBuffer>}
*/
async function loadTemplate(templateUrl) {
if (templateCache.has(templateUrl)) {
return templateCache.get(templateUrl);
}
const response = await fetch(templateUrl);
const arrayBuffer = await response.arrayBuffer();
templateCache.set(templateUrl, arrayBuffer);
return arrayBuffer;
}
/**
* 加载 docx 模板并填充数据返回生成的 Blob
* @param {string} templateUrl - 模板文件 URL /xxx.docx
* @param {object} data - 占位符数据 { orgName: "xxx" }
* @returns {Promise<Blob>} 填充后的 docx Blob
*/
export async function renderDocxBlob(templateUrl, data) {
const arrayBuffer = await loadTemplate(templateUrl);
const zip = new PizZip(arrayBuffer);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.render(data);
return doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
}
/**
* 加载 docx 模板填充数据后打开预览或触发下载
* @param {string} templateUrl - 模板文件 URL /xxx.docx
* @param {object} data - 占位符数据 { orgName: "xxx" }
* @param {string} fileName - 文件名称
* @param {object} [options] - 可选配置
* @param {boolean} [options.download=false] - true 时触发下载默认在新窗口预览
*/
export async function fillDocxTemplate(
templateUrl,
data,
fileName,
{ download = false } = {},
) {
const blob = await renderDocxBlob(templateUrl, data);
const url = URL.createObjectURL(blob);
if (download) {
const link = document.createElement("a");
link.href = url;
link.download = fileName || "document.docx";
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
return;
}
// 默认:新窗口打开预览
window.open(url, "_blank");
// 延迟释放,避免新窗口加载前 URL 失效
setTimeout(() => URL.revokeObjectURL(url), 60000);
}

View File

@ -38,7 +38,7 @@ export const orgAccountResetPassword = declareRequest(
export const registeredOrgList = declareRequest( export const registeredOrgList = declareRequest(
"orgInfoLoading", "orgInfoLoading",
"Get > /safetyEval/org-info/page", "Get > /safetyEval/org-info/page",
'data: [] | res.data || [] & totalCount: 0 | res.totalCount || 0' 'registeredOrgListData: [] | res.data || [] & totalCount: 0 | res.totalCount || 0'
); );
export const registeredOrgGet = declareRequest( export const registeredOrgGet = declareRequest(