dev_1.0.1
tangjie 2026-07-01 17:57:42 +08:00
parent 70c645f465
commit 9102384a71
10 changed files with 225 additions and 128 deletions

View File

@ -10,7 +10,7 @@ module.exports = {
javaGitBranch: "<branch-name>",
// 本地联调 safety-eval-servicecontext-path: /safety-eval默认端口 8095
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
API_HOST: "http://192.168.0.204:8095",
API_HOST: "http://192.168.0.149",
},
production: {
// 应用后端分支名称,部署上线需要

View File

@ -0,0 +1,68 @@
import { Form, Image, Upload } from "antd";
import { useState } from "react";
const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
export default function AttachmentUpload({ name, label, disabled }) {
const [previewImage, setPreviewImage] = useState("");
return (
<>
<Form.Item
name={name}
label={label}
valuePropName="fileList"
getValueProps={(value) => {
if (Array.isArray(value)) {
return { fileList: value };
}
return {
fileList: value
? value.split(",").map((item) => ({
url: item,
uid: item,
status: "done",
name: "附件",
}))
: [],
};
}}
getValueFromEvent={({ fileList }) => {
return (
fileList?.map((file) => ({
url: file.response?.data?.url || file.url,
uid: file.uid,
status: "done",
name: file.name,
})) || []
);
}}
>
<Upload
disabled={disabled}
listType="picture-card"
action={`${window.process.env.app.API_HOST}/safety-eval/file/upload`}
onPreview={(file) => {
if (isImage(file.url || file.thumbUrl)) {
setPreviewImage(file.url || file.thumbUrl);
} else {
window.open(file.url || file.thumbUrl);
}
}}
>
上传附件
</Upload>
</Form.Item>
<Image
style={{ display: "none" }}
preview={{
visible: !!previewImage,
src: previewImage,
onVisibleChange: (visible) => {
if (!visible) setPreviewImage("");
},
}}
/>
</>
);
}

View File

@ -1,21 +1,39 @@
import { Form, Input, InputNumber, Row, Col, Select } from "antd";
import { CHONGQING_DISTRICTS, QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import {
CHONGQING_DISTRICTS,
QUALIFICATION_INDUSTRY_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { FILING_UNIT_TYPE_OPTIONS } from "~/enumerate/qualFilingOptions";
import FilingUpload, { resolveFilingUploadUrl } from "./FilingUpload";
import AttachmentUpload from "~/components/AttachmentUpload";
export default function
BasicInfoStep({ form, disabled, onAttachmentChange }) {
export default function BasicInfoStep({ form, disabled }) {
return (
<Form form={form} layout="vertical" disabled={disabled}>
<Row gutter={16}>
<Col span={24}>
<Form.Item name="businessScope" label="申请的业务范围" rules={[{ required: !disabled, message: "请选择业务范围" }]}>
<Select options={QUALIFICATION_INDUSTRY_OPTIONS} placeholder="请选择证照类型" />
<Form.Item
name="businessScope"
label="申请的业务范围"
rules={[{ required: !disabled, message: "请选择业务范围" }]}
>
<Select
options={QUALIFICATION_INDUSTRY_OPTIONS}
placeholder="请选择证照类型"
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="filingTerritoryName" label="备案属地" rules={[{ required: !disabled, message: "请选择备案属地" }]}>
<Select options={CHONGQING_DISTRICTS} placeholder="请选择" showSearch optionFilterProp="label" />
<Form.Item
name="filingTerritoryName"
label="备案属地"
rules={[{ required: !disabled, message: "请选择备案属地" }]}
>
<Select
options={CHONGQING_DISTRICTS}
placeholder="请选择"
showSearch
optionFilterProp="label"
/>
</Form.Item>
</Col>
<Col span={12}>
@ -79,12 +97,18 @@ BasicInfoStep({ form, disabled, onAttachmentChange }) {
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="fulltimeEvaluatorCount" label="专职安全评价师数量(人)">
<Form.Item
name="fulltimeEvaluatorCount"
label="专职安全评价师数量(人)"
>
<InputNumber style={{ width: "100%" }} min={0} placeholder="人" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="registeredEngineerCount" label="注册安全工程师数量(人)">
<Form.Item
name="registeredEngineerCount"
label="注册安全工程师数量(人)"
>
<InputNumber style={{ width: "100%" }} min={0} placeholder="人" />
</Form.Item>
</Col>
@ -94,18 +118,7 @@ BasicInfoStep({ form, disabled, onAttachmentChange }) {
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="attachments" label="上传附件">
<FilingUpload
disabled={disabled}
onChange={async (files) => {
if (disabled) {
return;
}
const url = resolveFilingUploadUrl(files);
onAttachmentChange?.(url, files);
}}
/>
</Form.Item>
<AttachmentUpload name="attachmentUrl" label="上传附件" disabled={disabled} />
</Col>
</Row>
</Form>

View File

@ -1,6 +1,7 @@
import { DatePicker, Form, Input, Row, Col, Select } from "antd";
import { COMMITMENT_PARAGRAPHS } from "../../filingCommitment";
import FilingUpload, { resolveFilingUploadUrl } from "./FilingUpload";
import AttachmentUpload from "~/components/AttachmentUpload";
const BLANK_STYLE = {
display: "inline-block",
@ -13,9 +14,7 @@ const BLANK_STYLE = {
function renderBlank(value) {
const text = value?.trim() || "";
return (
<span style={BLANK_STYLE}>{text || "\u00A0".repeat(8)}</span>
);
return <span style={BLANK_STYLE}>{text || "\u00A0".repeat(8)}</span>;
}
function CommitmentBody({ legalRepName, filingUnitName }) {
@ -23,10 +22,20 @@ function CommitmentBody({ legalRepName, filingUnitName }) {
return (
<div style={{ fontSize: 14, lineHeight: 2, color: "rgba(0,0,0,0.85)" }}>
{COMMITMENT_PARAGRAPHS.map((paragraph) => (
<p key={paragraph.key} style={{ margin: paragraph.key === "intro" ? "0 0 12px" : "0 0 10px", textIndent: paragraph.key === "intro" ? 0 : "2em" }}>
<p
key={paragraph.key}
style={{
margin: paragraph.key === "intro" ? "0 0 12px" : "0 0 10px",
textIndent: paragraph.key === "intro" ? 0 : "2em",
}}
>
{paragraph.parts.map((part, index) => {
if (part.type === "blank") {
return <span key={`${paragraph.key}-${index}`}>{renderBlank(values[part.field])}</span>;
return (
<span key={`${paragraph.key}-${index}`}>
{renderBlank(values[part.field])}
</span>
);
}
return <span key={`${paragraph.key}-${index}`}>{part.value}</span>;
})}
@ -50,7 +59,9 @@ export default function CommitmentStep({
form.setFieldsValue({ legalRepPersonnelId: undefined, legalRepName: "" });
return;
}
const selected = personnelOptions.find((item) => item.value === personnelId);
const selected = personnelOptions.find(
(item) => item.value === personnelId,
);
form.setFieldsValue({
legalRepPersonnelId: personnelId,
legalRepName: selected?.staffName || selected?.label || "",
@ -70,15 +81,19 @@ export default function CommitmentStep({
<Input />
</Form.Item>
<div style={{
border: "1px solid #e8e8e8",
borderRadius: 4,
padding: "20px 24px",
background: "#fafafa",
marginBottom: 24,
}}
<div
style={{
border: "1px solid #e8e8e8",
borderRadius: 4,
padding: "20px 24px",
background: "#fafafa",
marginBottom: 24,
}}
>
<CommitmentBody legalRepName={legalRepName} filingUnitName={filingUnitName} />
<CommitmentBody
legalRepName={legalRepName}
filingUnitName={filingUnitName}
/>
</div>
<Row gutter={24}>
@ -104,20 +119,11 @@ export default function CommitmentStep({
</Form.Item>
</Col>
</Row>
<Form.Item name="signatureFiles" label="电子签名">
<FilingUpload
disabled={disabled}
accept="image/*"
onChange={(files) => {
if (disabled) {
return;
}
const url = resolveFilingUploadUrl(files);
onSignatureChange?.(url, files);
}}
/>
</Form.Item>
<AttachmentUpload
name="legalRepSignatureUrl"
label="电子签名"
disabled={disabled}
/>
</Form>
</div>
);

View File

@ -15,9 +15,10 @@ export default function FilingUpload({
<Upload
maxCount={maxCount}
accept={accept}
action={`${window.process.env.app.API_HOST}/safety-eval/file/upload`}
disabled={disabled}
fileList={fileList}
beforeUpload={() => false}
onChange={(info) => {
if (disabled) return;
onChange?.(info.fileList);

View File

@ -1,7 +1,5 @@
import { Button, Space, Table, Tag } from "antd";
import { Button, Image, Space, Table, Tag, Upload } from "antd";
import { useState } from "react";
import { resolveUploadFileId } from "~/utils/mockUpload";
import FilePreviewModal from "~/components/FilePreviewModal";
export default function MaterialStep({
materials = [],
@ -9,8 +7,10 @@ export default function MaterialStep({
onUpload,
loading,
}) {
const [preview, setPreview] = useState(null);
console.log(disabled)
const [previewImage, setPreviewImage] = useState("");
const isImage = (url) => /\.(png|jpe?g|gif|bmp|webp|svg)(\?.*)?$/i.test(url || "");
return (
<>
<Table
@ -44,16 +44,35 @@ console.log(disabled)
render: (_, record) => (
<Space size="small">
{record.attachmentUrl ? (
<Button type="link" size="small" onClick={() => setPreview(record)}>预览</Button>
<Button
type="link"
size="small"
onClick={() => {
if (isImage(record.attachmentUrl)) {
setPreviewImage(record.attachmentUrl);
} else {
window.open(record.attachmentUrl);
}
}}
>
预览
</Button>
) : (
!disabled && (
<Button
type="link"
size="small"
onClick={() => onUpload?.(record, resolveUploadFileId([]))}
<Upload
showUploadList={false}
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);
}
}
}}
>
上传
</Button>
<Button type="link" size="small">上传</Button>
</Upload>
)
)}
</Space>
@ -62,12 +81,15 @@ console.log(disabled)
{ title: "注释", dataIndex: "materialRemark", ellipsis: true },
]}
/>
<FilePreviewModal
open={!!preview}
title="材料预览"
fileName={preview?.materialContent}
url={preview?.attachmentUrl}
onCancel={() => setPreview(null)}
<Image
style={{ display: "none" }}
preview={{
visible: !!previewImage,
src: previewImage,
onVisibleChange: (visible) => {
if (!visible) setPreviewImage("");
},
}}
/>
</>
);

View File

@ -93,12 +93,14 @@ function FilingFormPage(props) {
useEffect(() => {
loadPersonnelOptions();
if(query.id){
fetchQualFilingDetail({ id: query.id }).then((res) => {
updateDetail((prev) => ({ ...prev, ...res?.data || {} }));
fetchQualFilingDetail(query.id).then((res) => {
setDetail((prev) => ({ ...prev, ...res?.data || {} }));
console.log(res?.data, 'res.data');
basicForm.setFieldsValue(res?.data || {});
});
}
else{
updateDetail((prev) => ({ ...prev, materials: FILING_MATERIAL_TEMPLATE }));
setDetail((prev) => ({ ...prev, materials: FILING_MATERIAL_TEMPLATE }));
}
}, []);
@ -131,21 +133,6 @@ function FilingFormPage(props) {
}, [commitmentForm, personnelOptions]);
const updateDetail = useCallback(
(updater) => {
setDetail((prev) => {
const next =
typeof updater === "function"
? updater(prev)
: { ...prev, ...updater };
detailRef.current = next;
return next;
});
},
[ mode, readOnly],
);
const collectCurrentDetail = useCallback(() => {
const current = detailRef.current || detail;
return mergeDetailFromForms(current, {
@ -189,8 +176,9 @@ function FilingFormPage(props) {
setSubmitting(true);
try {
const currentDetail = collectCurrentDetail();
debugger
const saved = await persistFilingToBackend(props, currentDetail, mode);
clearLocalDraft(mode);
if (mode === FILING_FORM_MODE.CHANGE) {
const res = await props.qualFilingChangeSubmit({
@ -219,32 +207,31 @@ function FilingFormPage(props) {
const handleMaterialUpload = (record, url) => {
const attachmentUrl = url || resolveUploadFileId([]);
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
materials: (prev.materials || []).map((item) =>
item.id === record.id || item.sortOrder === record.sortOrder
{
return item.id === record.id
? {
...item,
attachmentUrl,
uploadStatusCode: 2,
uploadStatusName: "已上传",
}
: item,
: item
}
),
}));
message.success(`材料已选择,${saveActionHint}`);
};
const handleAttachmentChange = (url) => {
updateDetail((prev) => ({ ...prev, attachmentUrl: url }));
};
const handleSignatureChange = (url, files) => {
commitmentForm.setFieldsValue({
signatureFiles: files,
legalRepSignatureUrl: url,
});
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
commitment: {
...prev.commitment,
@ -273,7 +260,7 @@ function FilingFormPage(props) {
? mapStaffRowToFilingPersonnel(row)
: mapStaffRowToFilingPersonnel({ id });
});
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
personnelList: [...(prev.personnelList || []), ...newRows],
}));
@ -285,7 +272,7 @@ function FilingFormPage(props) {
title: "提示",
content: `确认删除人员「${record.personName}」?`,
onOk: () => {
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
personnelList: (prev.personnelList || []).filter(
(item) => item.id !== record.id,
@ -314,7 +301,7 @@ function FilingFormPage(props) {
? mapEquipRowToFilingEquipment(row)
: mapEquipRowToFilingEquipment({ id });
});
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
equipmentList: [...(prev.equipmentList || []), ...newRows],
}));
@ -326,7 +313,7 @@ function FilingFormPage(props) {
title: "提示",
content: `确认移除装备「${record.deviceName}」?`,
onOk: () => {
updateDetail((prev) => ({
setDetail((prev) => ({
...prev,
equipmentList: (prev.equipmentList || []).filter(
(item) => item.id !== record.id,
@ -390,7 +377,6 @@ function FilingFormPage(props) {
<BasicInfoStep
form={basicForm}
disabled={readOnly}
onAttachmentChange={handleAttachmentChange}
/>
)}
{activeStep === "materials" && (
@ -461,7 +447,10 @@ function FilingFormPage(props) {
{!isLastStep && (
<Button
type="primary"
onClick={() => setActiveStep(STEP_ITEMS[stepIndex + 1].key)}
onClick={() => {
console.log(basicForm.getFieldsValue());
setActiveStep(STEP_ITEMS[stepIndex + 1].key)
}}
>
下一步
</Button>

View File

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

View File

@ -267,20 +267,18 @@ export async function persistFilingToBackend(props, detail, mode) {
}
export function mergeDetailFromForms(detail, { basicValues, commitmentValues }) {
const attachmentUrl = resolveUploadFileId(basicValues?.attachments) || detail.attachmentUrl;
return {
...detail,
...basicValues,
attachmentUrl,
attachmentUrl: basicValues?.attachmentUrl?.map((file) => file.url).join(",") || undefined,
materials: detail.materials,
personnelList: detail.personnelList,
equipmentList: detail.equipmentList,
commitment: {
...detail.commitment,
...commitmentValues,
legalRepSignatureUrl: resolveUploadFileId(commitmentValues?.signatureFiles)
|| commitmentValues?.legalRepSignatureUrl
|| detail.commitment?.legalRepSignatureUrl,
legalRepSignatureUrl: basicValues?.legalRepSignatureUrl?.map((file) => file.url).join(",") || undefined,
},
};
}

View File

@ -85,15 +85,15 @@ export function verifyFilingPrerequisites(detail = {}) {
passed: SKIP_PERSONNEL_VERIFY || keyOk,
});
const mgmtMaterial = (detail.materials || []).find((m) => Number(m.sortOrder) === 10);
const mgmtUploaded = !!mgmtMaterial?.attachmentUrl;
items.push({
key: "mgmtSystem",
label: "管理体系",
desc: mgmtUploaded ? "管理体系文件已上传" : "尚未上传管理体系文件",
passed: mgmtUploaded,
});
// const mgmtUploaded=detail.materials.every((m) => m.attachmentUrl);
// items.push({
// key: "mgmtSystem",
// label: "管理体系",
// desc: mgmtUploaded? "管理体系文件已上传" : "尚未上传管理体系文件",
// passed: mgmtUploaded,
// });
return {
passed: items.every((item) => item.passed),