dev_1.0.1
tangjie 2026-07-02 15:01:19 +08:00
parent 160be8ae9c
commit 2d3f1895f3
11 changed files with 286 additions and 143 deletions

View File

@ -5,3 +5,9 @@ export const queryReviewList = declareRequest(
"Get > /safety-eval/qual-filing/page",
'qualReviewList: [] | res.data || [] & qualReviewTotal: 0 | res.total || 0'
);
export const queryComplianceCheck = declareRequest(
"qualReviewLoading",
"Get > /safety-eval/qual-filing/compliance-check > id",
'complianceCheckItems: [] | res.data || []',
);

View File

@ -29,12 +29,14 @@ export default function AttachmentUpload({ name, label, disabled = false }) {
};
}}
getValueFromEvent={({ fileList }) => {
console.log(fileList, 'fileList')
return (
fileList?.map((file) => ({
url: file.response?.data?.url || file.url,
uid: file.uid,
status: "done",
name: file.name,
percent: file.percent,
})) || []
);
}}

View File

@ -51,6 +51,7 @@ export default function CommitmentStep({
personnelOptions = [],
onSignatureChange,
}) {
const legalRepName = Form.useWatch("legalRepName", form);
const filingUnitName = Form.useWatch("filingUnitName", form);

View File

@ -1,6 +1,6 @@
import { Button, Space, Table } from "antd";
import { Button, Image, Space, Table, Upload } from "antd";
import { useState } from "react";
import { resolveUploadFileId } from "~/utils/mockUpload";
import OrgEquipmentSelectModal from "./OrgEquipmentSelectModal";
export default function EquipmentStep({
@ -11,8 +11,11 @@ export default function EquipmentStep({
onUploadCalibration,
}) {
const [selectOpen, setSelectOpen] = useState(false);
const [previewImage, setPreviewImage] = useState("");
const existingIds = equipmentList.map((item) => String(item.sourceEquipmentId || ""));
const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
return (
<>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 12 }}>
@ -33,17 +36,42 @@ export default function EquipmentStep({
{ title: "生产厂家", dataIndex: "manufacturer" },
{
title: "计量检定情况",
width: 120,
width: 180,
render: (_, record) => (
!disabled ? (
<Button
type="link"
size="small"
onClick={() => onUploadCalibration?.(record, resolveUploadFileId([]))}
>
上传报告
</Button>
) : (record.calibrationReportUrl ? "已上传" : "-")
<Space size="small">
{record.calibrationReportUrl ? (
<Button
type="link"
size="small"
onClick={() => {
if (isImage(record.calibrationReportUrl)) {
setPreviewImage(record.calibrationReportUrl);
} else {
window.open(record.calibrationReportUrl);
}
}}
>
预览
</Button>
) : (
!disabled && (
<Upload
showUploadList={false}
action={`${window.process.env.app.API_HOST}/safety-eval/file/upload`}
onChange={(info) => {
if (info.file.status === "done") {
const data = info.file.response?.data;
if (data) {
onUploadCalibration?.(record, data);
}
}
}}
>
<Button type="link" size="small">上传报告</Button>
</Upload>
)
)}
</Space>
),
},
{
@ -66,6 +94,16 @@ export default function EquipmentStep({
setSelectOpen(false);
}}
/>
<Image
style={{ display: "none" }}
preview={{
visible: !!previewImage,
src: previewImage,
onVisibleChange: (visible) => {
if (!visible) setPreviewImage("");
},
}}
/>
</>
);
}

View File

@ -64,9 +64,9 @@ export default function MaterialStep({
action={`${window.process.env.app.API_HOST}/safety-eval/file/upload`}
onChange={(info) => {
if (info.file.status === "done") {
const url = info.file.response?.data?.url;
if (url) {
onUpload?.(record, url);
const data = info.file.response?.data;
if (data) {
onUpload?.(record, data);
}
}
}}

View File

@ -55,7 +55,7 @@ function FilingFormPage(props) {
: mode === FILING_FORM_MODE.CHANGE
? "change"
: "application";
const goBackList = useCallback(() => goFilingList(listMode), [listMode]);
const saveActionHint =
mode === FILING_FORM_MODE.FILED
? "请点击提交后保存"
@ -66,6 +66,7 @@ function FilingFormPage(props) {
return personnelOptions;
}
const options = await fetchOrgPersonnelOptions().catch(() => []);
setPersonnelOptions(options);
return options;
}, [personnelOptions.length]);
@ -167,23 +168,24 @@ function FilingFormPage(props) {
if (result?.success) {
setVerifyOpen(false);
message.success(isSaveDraft ? "暂存成功" : "提交成功");
message.success(config.isSaveDraft ? "暂存成功" : "提交成功");
props.history.push("filingApplication/list");
} else {
message.error(result?.message || (isSaveDraft ? "暂存失败" : "提交失败"));
message.error(result?.message || (config.isSaveDraft ? "暂存失败" : "提交失败"));
}
setSubmitting(false);
};
const handleMaterialUpload = (record, url) => {
const attachmentUrl = url || resolveUploadFileId([]);
const handleMaterialUpload = (record, {url, ext}) => {
const attachmentUrl = url ;
setDetail((prev) => ({
...prev,
materials: (prev.materials || []).map((item) => {
return item.materialType === record.materialType
return item.id === record.id
? {
...item,
attachmentUrl,
materialFormat: ext,
uploadStatusCode: 2,
uploadStatusName: "已上传",
}
@ -193,20 +195,8 @@ function FilingFormPage(props) {
message.success(`材料已选择,${saveActionHint}`);
};
const handleSignatureChange = (url, files) => {
commitmentForm.setFieldsValue({
signatureFiles: files,
legalRepSignatureUrl: url,
});
setDetail((prev) => ({
...prev,
commitment: {
...prev.commitment,
legalRepSignatureUrl: url,
signatureFiles: files,
},
}));
};
const handlePersonnelAdd = (sourcePersonnelIds, rows = []) => {
const existing = new Set(
@ -290,8 +280,8 @@ function FilingFormPage(props) {
});
};
const handleEquipmentCalibration = (record, url) => {
updateDetail((prev) => ({
const handleEquipmentCalibration = (record, { url }) => {
setDetail((prev) => ({
...prev,
equipmentList: (prev.equipmentList || []).map((item) =>
item.id === record.id ? { ...item, calibrationReportUrl: url } : item,
@ -320,7 +310,7 @@ function FilingFormPage(props) {
{
key: "commitment",
label: "3. 法定代表人承诺书",
children: <CommitmentStep form={commitmentForm} disabled={readOnly} />,
children: <CommitmentStep form={commitmentForm} disabled={readOnly} personnelOptions={personnelOptions} />,
forceRender: true,
},
{
@ -344,7 +334,7 @@ function FilingFormPage(props) {
disabled={readOnly}
onAdd={handleEquipmentAdd}
onRemove={handleEquipmentRemove}
onCalibration={handleEquipmentCalibration}
onUploadCalibration={handleEquipmentCalibration}
/>
),
},
@ -399,21 +389,7 @@ function FilingFormPage(props) {
borderTop: "1px solid #f0f0f0",
}}
>
<Space>
<Button
onClick={() =>
goFilingList(
mode === FILING_FORM_MODE.FILED
? "filed"
: mode === FILING_FORM_MODE.CHANGE
? "change"
: "application",
)
}
>
取消
</Button>
</Space>
<Space>
{stepIndex > 0 && (
<Button

View File

@ -44,7 +44,7 @@ export default function FilingListTable({
const columns = [
{ title: "备案属地", dataIndex: "filingTerritoryName", width: 120, ellipsis: true },
{ title: "备案单位", dataIndex: "filingUnitName", ellipsis: true },
{ title: "备案编号", dataIndex: "filingNo", width: 180, render: (val, record) => val || record.id || "-" },
{ title: "备案编号", dataIndex: "filingNo", width: 200, render: (val, record) => val || record.id || "-" },
{ title: "安全评价业务范围", dataIndex: "businessScope", ellipsis: true },
];

View File

@ -1,16 +1,16 @@
/** 备案材料通用模板(对齐后端 QualFilingMaterialTemplateProvider / 原型 10 项) */
export const FILING_MATERIAL_TEMPLATE = [
{ materialType: 1, materialContent: "申请材料目录(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
{ materialType: 2, materialContent: "申请书(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名并加盖单位公章", requiredFlag: 1 },
{ materialType: 3, materialContent: "法人证明(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "营业执照或事业单位法人证书等", requiredFlag: 1 },
{ materialType: 4, materialContent: "三年内无重大违法失信记录查询证明(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名并加盖单位公章", requiredFlag: 1 },
{ materialType: 5, materialContent: "法定代表人承诺书(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名", requiredFlag: 1 },
{ materialType: 6, materialContent: "固定资产法定证明材料或书面承诺", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
{ materialType: 7, materialContent: "工作场所及档案室面积证明资料(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "房产证、租赁协议等", requiredFlag: 1 },
{ materialType: 8, materialContent: "安全评价师专业能力证明(彩色复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "学历证书、职称证及其他相关证明材料", requiredFlag: 1 },
{ materialType: 9, materialContent: "相关负责人证明材料(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "任命文件、简历、职称证等", requiredFlag: 1 },
{ materialType: 10, materialContent: "机构内部管理制度(非受控版)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
{id: 1, materialType: 1, materialContent: "申请材料目录(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
{id: 2, materialType: 2, materialContent: "申请书(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名并加盖单位公章", requiredFlag: 1 },
{id: 3, materialType: 3, materialContent: "法人证明(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "营业执照或事业单位法人证书等", requiredFlag: 1 },
{id: 4, materialType: 4, materialContent: "三年内无重大违法失信记录查询证明(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名并加盖单位公章", requiredFlag: 1 },
{id: 5, materialType: 5, materialContent: "法定代表人承诺书(原件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "法定代表人亲笔签名", requiredFlag: 1 },
{id: 6, materialType: 6, materialContent: "固定资产法定证明材料或书面承诺", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
{id: 7, materialType: 7, materialContent: "工作场所及档案室面积证明资料(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "房产证、租赁协议等", requiredFlag: 1 },
{id: 8, materialType: 8, materialContent: "安全评价师专业能力证明(彩色复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "学历证书、职称证及其他相关证明材料", requiredFlag: 1 },
{id: 9, materialType: 9, materialContent: "相关负责人证明材料(复印件)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: "任命文件、简历、职称证等", requiredFlag: 1 },
{id: 10, materialType: 10, materialContent: "机构内部管理制度(非受控版)", materialFormat: "pdf", attachmentDesc: "纸质版扫描件", materialRemark: null, requiredFlag: 1 },
];
export function createLocalMaterials() {

View File

@ -271,14 +271,14 @@ export function mergeDetailFromForms(detail, { basicValues, commitmentValues })
return {
...detail,
...basicValues,
attachmentUrl: basicValues?.attachmentUrl?.map((file) => file.url).join(",") || undefined,
attachmentUrl: Array.isArray(basicValues?.attachmentUrl) ? basicValues?.attachmentUrl?.map((file) => file.url).join(",") : basicValues?.attachmentUrl,
materials: detail.materials,
personnelList: detail.personnelList,
equipmentList: detail.equipmentList,
commitment: {
...detail.commitment,
...commitmentValues,
legalRepSignatureUrl: commitmentValues?.legalRepSignatureUrl?.map((file) => file.url).join(",") || undefined,
legalRepSignatureUrl: Array.isArray(commitmentValues?.legalRepSignatureUrl) ? commitmentValues?.legalRepSignatureUrl?.map((file) => file.url).join(",") : commitmentValues?.legalRepSignatureUrl,
},
};
}

View File

@ -1,22 +1,37 @@
import React, { useState } from "react";
import { Button, Tag, Space, Modal, Select, Input, Form } from "antd";
import { Button, Tag, Space, Modal, Select, Input, Form, Spin } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import { REVIEW_STATUS_MAP } from "~/enumerate/constant";
import { MOCK_DETAIL, MOCK_MATERIALS, LEGAL_CHECK_ITEMS, LEGAL_STATUS_STYLE } from "../mockData";
import { tools } from "@cqsjjb/jjb-common-lib";
import {
MOCK_DETAIL,
MOCK_MATERIALS,
LEGAL_CHECK_ITEMS,
LEGAL_STATUS_STYLE,
} from "../mockData";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_QUAL_REVIEW } from "~/enumerate/namespace";
import FilingTabs from "../FilingTabs";
import "./indes.less";
const { TextArea } = Input;
const {router}=tools;
const QualReviewForm = (props) => {
const { queryComplianceCheck, qualReview } = props;
const { qualReviewLoading, complianceCheckItems } = qualReview;
const [form] = Form.useForm();
const [compliance, setCompliance] = useState({});
const [reviewVisible, setReviewVisible] = useState(false);
const detail = MOCK_DETAIL;
const statusConfig = REVIEW_STATUS_MAP[detail.reviewStatus];
const legalPassCount = LEGAL_CHECK_ITEMS.filter((i) => i.status === "pass").length;
const legalFailCount = LEGAL_CHECK_ITEMS.filter((i) => i.status === "fail").length;
const legalPassCount = LEGAL_CHECK_ITEMS.filter(
(i) => i.status === "pass",
).length;
const legalFailCount = LEGAL_CHECK_ITEMS.filter(
(i) => i.status === "fail",
).length;
const handleSubmit = (action) => {
form.validateFields().then((values) => {
@ -24,94 +39,199 @@ const QualReviewForm = (props) => {
setReviewVisible(false);
});
};
useEffect(() => {
queryComplianceCheck({id: router.query.id});
}, []);
return (
<PageLayout
title={
<Space>
<span>资质备案审核</span>
{statusConfig && <Tag color={statusConfig.color}>{statusConfig.label}</Tag>}
{statusConfig && (
<Tag color={statusConfig.color}>{statusConfig.label}</Tag>
)}
</Space>
}
history={props.history}
previous
footer={<Button type="primary" onClick={() => setReviewVisible(true)}>审核操作</Button>}
footer={
<Button type="primary" onClick={() => setReviewVisible(true)}>
审核操作
</Button>
}
>
<div className="qual-review-form">
<div className="legal-check-bar">
<div className="legal-check-title">法律规定内容</div>
<div className="legal-check-grid">
{LEGAL_CHECK_ITEMS.map((item) => {
const style = LEGAL_STATUS_STYLE[item.status];
return (
<div key={item.key} className="legal-check-item" style={{ borderLeft: `3px solid ${style.borderColor}`, background: style.bg }}>
<span>{style.icon}</span>
<div>
<div style={{ fontWeight: 500 }}>{item.title}</div>
<div className="legal-check-item-desc">{item.desc}</div>
<Spin spinning={qualReviewLoading}>
<div className="qual-review-form">
<div className="legal-check-bar">
<div className="legal-check-title">法律规定内容</div>
<div className="legal-check-grid">
{complianceCheckItems.map((item) => {
const style = LEGAL_STATUS_STYLE[item.status];
return (
<div
key={item.key}
className="legal-check-item"
style={{
borderLeft: `3px solid ${style.borderColor}`,
background: style.bg,
}}
>
<span>{style.icon}</span>
<div>
<div style={{ fontWeight: 500 }}>{item.title}</div>
<div className="legal-check-item-desc">{item.description}</div>
</div>
</div>
);
})}
</div>
</div>
<FilingTabs
detail={detail}
isReview
compliance={compliance}
onComplianceChange={(id, v) =>
setCompliance((prev) => ({ ...prev, [id]: v }))
}
/>
<Modal
title="审核操作"
open={reviewVisible}
onCancel={() => setReviewVisible(false)}
width={580}
footer={
<div
style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}
>
<Button onClick={() => setReviewVisible(false)}>取消</Button>
<Button type="primary" onClick={() => handleSubmit("submit")}>
提交
</Button>
<Button
type="primary"
style={{ background: "#52c41a", borderColor: "#52c41a" }}
onClick={() => handleSubmit("pass")}
>
通过
</Button>
</div>
}
>
<div
style={{
background: "#f8fafc",
border: "1px solid #d9d9d9",
borderRadius: 6,
padding: "0.75rem",
marginBottom: "1rem",
}}
>
<div
style={{
fontSize: "0.88rem",
fontWeight: 600,
marginBottom: "0.5rem",
}}
>
评价分析
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: "0.4rem",
}}
>
<div style={statStyle}>
<div style={statLabelStyle}>已核验材料</div>
<div
style={{
fontSize: "1.1rem",
fontWeight: 700,
color: "#1677ff",
}}
>
{legalPassCount + legalFailCount}
</div>
</div>
);
})}
</div>
<div style={statStyle}>
<div style={statLabelStyle}>符合</div>
<div
style={{
fontSize: "1.1rem",
fontWeight: 700,
color: "#059669",
}}
>
{legalPassCount}
</div>
</div>
<div style={statStyle}>
<div style={statLabelStyle}>不符合</div>
<div
style={{
fontSize: "1.1rem",
fontWeight: 700,
color: "#dc2626",
}}
>
{legalFailCount}
</div>
</div>
</div>
</div>
<Form form={form} layout="vertical" style={{ marginTop: "0.5rem" }}>
<Form.Item
label="审核结果"
name="reviewResult"
rules={[{ required: true, message: "请选择审核结果" }]}
>
<Select placeholder="请选择">
<Select.Option value="pass">通过</Select.Option>
<Select.Option value="pending">待定</Select.Option>
<Select.Option value="reject">不通过</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="指派专家"
name="expert"
rules={[{ required: true, message: "请选择专家" }]}
>
<Select placeholder="— 请选择 —">
<Select.Option value="chen">陈教授化工安全</Select.Option>
<Select.Option value="li">李教授矿山安全</Select.Option>
<Select.Option value="wang">王高工建筑施工</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="综合审核意见"
name="remark"
rules={[{ required: true, message: "请输入综合审核意见" }]}
>
<TextArea rows={3} placeholder="请输入综合审核意见..." />
</Form.Item>
</Form>
</Modal>
</div>
<FilingTabs
detail={detail}
isReview
compliance={compliance}
onComplianceChange={(id, v) => setCompliance((prev) => ({ ...prev, [id]: v }))}
/>
<Modal
title="审核操作"
open={reviewVisible}
onCancel={() => setReviewVisible(false)}
width={580}
footer={
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
<Button onClick={() => setReviewVisible(false)}>取消</Button>
<Button type="primary" onClick={() => handleSubmit("submit")}>提交</Button>
<Button type="primary" style={{ background: "#52c41a", borderColor: "#52c41a" }} onClick={() => handleSubmit("pass")}>通过</Button>
</div>
}
>
<div style={{ background: "#f8fafc", border: "1px solid #d9d9d9", borderRadius: 6, padding: "0.75rem", marginBottom: "1rem" }}>
<div style={{ fontSize: "0.88rem", fontWeight: 600, marginBottom: "0.5rem" }}>评价分析</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: "0.4rem" }}>
<div style={statStyle}><div style={statLabelStyle}>已核验材料</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#1677ff" }}>{legalPassCount+legalFailCount}</div></div>
<div style={statStyle}><div style={statLabelStyle}>符合</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#059669" }}>{legalPassCount}</div></div>
<div style={statStyle}><div style={statLabelStyle}>不符合</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#dc2626" }}>{legalFailCount}</div></div>
</div>
</div>
<Form form={form} layout="vertical" style={{ marginTop: "0.5rem" }}>
<Form.Item label="审核结果" name="reviewResult" rules={[{ required: true, message: "请选择审核结果" }]}>
<Select placeholder="请选择">
<Select.Option value="pass">通过</Select.Option>
<Select.Option value="pending">待定</Select.Option>
<Select.Option value="reject">不通过</Select.Option>
</Select>
</Form.Item>
<Form.Item label="指派专家" name="expert" rules={[{ required: true, message: "请选择专家" }]}>
<Select placeholder="— 请选择 —">
<Select.Option value="chen">陈教授化工安全</Select.Option>
<Select.Option value="li">李教授矿山安全</Select.Option>
<Select.Option value="wang">王高工建筑施工</Select.Option>
</Select>
</Form.Item>
<Form.Item label="综合审核意见" name="remark" rules={[{ required: true, message: "请输入综合审核意见" }]}>
<TextArea rows={3} placeholder="请输入综合审核意见..." />
</Form.Item>
</Form>
</Modal>
</div>
</Spin>
</PageLayout>
);
};
const statStyle = { background: "#fff", border: "1px solid #d9d9d9", borderRadius: 6, padding: "0.4rem", textAlign: "center" };
const statStyle = {
background: "#fff",
border: "1px solid #d9d9d9",
borderRadius: 6,
padding: "0.4rem",
textAlign: "center",
};
const statLabelStyle = { fontSize: "0.7rem", color: "rgba(0,0,0,0.45)" };
export default QualReviewForm;
export default Connect(
[NS_QUAL_REVIEW],
true,
)(QualReviewForm);

View File

@ -114,5 +114,5 @@ export const LEGAL_CHECK_ITEMS = [
export const LEGAL_STATUS_STYLE = {
pass: { icon: "✅", borderColor: "#059669", bg: "#f0fdf4" },
warn: { icon: "⚠️", borderColor: "#d97706", bg: "#fffbeb" },
fail: { icon: "❌", borderColor: "#dc2626", bg: "#fef2f2" },
NOT_PASS: { icon: "❌", borderColor: "#dc2626", bg: "#fef2f2" },
};