safety-eval-service-frontend/src/pages/Container/SafetyEvalBusiness/ProjectDocLibrary/index.js

344 lines
10 KiB
JavaScript
Raw Normal View History

2026-07-22 09:49:21 +08:00
import React, { useState, useEffect } from "react";
import {
Form,
Table,
Button,
Input,
Select,
Space,
Modal,
Upload,
message,
Flex,
} from "antd";
import { PlusOutlined, UploadOutlined } from "@ant-design/icons";
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 { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { MATERIAL_TYPE_OPTIONS, MATERIAL_TYPE_MAP } from "~/enumerate/constant";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { tools } from "@cqsjjb/jjb-common-lib";
import AttachmentUpload from "~/components/AttachmentUpload";
2026-07-28 15:10:56 +08:00
import PreviewUrlButton from "~/components/PreviewUrlButton";
2026-07-22 09:49:21 +08:00
const { router } = tools;
const ProjectDocLibrary = (props) => {
const [searchForm] = Form.useForm();
const [uploadForm] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
const [uploadModalOpen, setUploadModalOpen] = useState(false);
const [projectList, setProjectList] = useState([]);
const [docViewerVisible, setDocViewerVisible] = useState(false);
const [currentDocs, setCurrentDocs] = useState([]);
const { evalProjectDocLoading, evalProjectDocSaveLoading } = props.safetyEvalBusiness;
const getData = async (pagination) => {
const params = {
...router.query,
current: pagination?.current || router.query.current || 1,
size: pagination?.size || router.query.size || 10,
};
const res = await props.evalProjectDocPage(params);
if (res?.success !== false) {
setDataSource(res?.data || []);
setTotal(res?.total || 0);
}
};
const getProjectList = async () => {
const res = await props.evalProjectPage({ current: 1, size: 999 });
if (res?.success !== false) {
setProjectList(res?.data || []);
}
};
useEffect(() => {
searchForm.setFieldsValue(router.query);
getData();
}, []);
const handleSearch = (values) => {
router.query = { ...router.query, ...values, current: 1, size: 10 };
getData();
};
const handleReset = (values) => {
searchForm.resetFields();
router.query = { ...values, current: 1, size: 10 };
getData();
};
const handlePageChange = (pagination) => {
router.query = {
...router.query,
current: pagination.current,
size: pagination.pageSize,
};
getData(pagination);
};
const handleOpenUpload = () => {
uploadForm.resetFields();
getProjectList();
setUploadModalOpen(true);
};
const handleUploadSubmit = async () => {
const values = await uploadForm.validateFields();
2026-07-22 11:40:47 +08:00
const res = await props.evalProjectDocSave({
2026-07-22 09:49:21 +08:00
projectId: values.projectId,
projectDocUrl: values.file.map((item) => ({
project_doc_url: item.url,
materialName: item.name,
materialType: values.materialType,
})),
});
2026-07-22 11:40:47 +08:00
if (res?.success !== false) {
message.success("上传成功");
setUploadModalOpen(false);
getData();
}
2026-07-22 09:49:21 +08:00
};
const columns = [
{ title: "项目编号", dataIndex: "projectNo", width: 130 },
{
title: "项目名称",
dataIndex: "projectName",
ellipsis: true,
width: 200,
},
{ title: "企业名称", dataIndex: "customerName", ellipsis: true, width: 180 },
{ title: "评价类型", dataIndex: "evalTypeName", width: 120 },
{
title: "上传文档数量",
width: 110,
render: (_, record) => {
const map = record.projectDocUrlMap || {};
const count = Object.values(map).reduce((sum, arr) => sum + (arr?.length || 0), 0);
return <span>{count}</span>;
},
},
{
title: "操作",
width: 100,
fixed: "right",
render: (_, record) => (
<Button
type="link"
size="small"
onClick={() => {
const map = record.projectDocUrlMap || {};
setCurrentDocs(Object.entries(map));
setDocViewerVisible(true);
}}
>
查看文档
</Button>
),
},
];
return (
<PageLayout
title="项目资料管理"
extra={
<Button type="primary" icon={<PlusOutlined />} onClick={handleOpenUpload}>
上传资料
</Button>
}
>
<div style={{ fontSize: 13, color: "#666", marginBottom: 16 }}>
按项目归集企业和机构上传的资料先选择项目再查看项目文档
</div>
<SearchForm
form={searchForm}
loading={false}
formLine={[
<Form.Item key="projectName" name="projectName">
<ControlWrapper.Input
label="项目名称"
placeholder="输入项目名称"
allowClear
/>
</Form.Item>,
<Form.Item key="customerName" name="customerName">
<ControlWrapper.Input
label="企业名称"
placeholder="输入企业名称"
allowClear
/>
</Form.Item>,
]}
onFinish={handleSearch}
onReset={handleReset}
style={{ marginBottom: 16 }}
/>
<Table
rowKey="id"
columns={columns}
dataSource={dataSource}
loading={evalProjectDocLoading}
scroll={{ y: props.scrollY, x: 1000 }}
pagination={{
total,
current: router.query.current || 1,
pageSize: router.query.size || 10,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
}}
onChange={handlePageChange}
/>
<Modal
title="上传资料"
open={uploadModalOpen}
onCancel={() => setUploadModalOpen(false)}
onOk={handleUploadSubmit}
confirmLoading={evalProjectDocSaveLoading}
width={700}
destroyOnHide={true}
>
<Form form={uploadForm} layout="vertical" preserve={false}>
<Form.Item
label="项目"
name="projectId"
rules={[{ required: true, message: "请选择项目" }]}
>
<Select
placeholder="请选择项目"
showSearch
optionFilterProp="label"
options={projectList.map((p) => ({
label: `${p.projectNo} - ${p.projectName}`,
value: p.id,
}))}
/>
</Form.Item>
<Form.Item label='资料类别' name='materialType' rules={[{ required: true, message: "请选择资料类别" }]}>
<Select
placeholder="请选择资料类别"
showSearch
optionFilterProp="label"
options={MATERIAL_TYPE_OPTIONS}
/>
</Form.Item>
<AttachmentUpload
label="选择文件"
name="file"
maxCount={10}
accept=".pdf,.doc,.docx,.png,.jpg,.jpeg,.bmp,.webp"
extra="最多上传10个文件"
rules={[{ required: true, message: "请上传文件" }]}
/>
</Form>
</Modal>
<Modal
title="项目文档"
open={docViewerVisible}
onCancel={() => setDocViewerVisible(false)}
footer={null}
width={700}
destroyOnHide={true}
>
{currentDocs.map(([type, docs]) => (
<div key={type} style={{ marginBottom: 24 }}>
<h4 style={{ marginBottom: 8, color: "#333" }}>{MATERIAL_TYPE_MAP[type] || type}</h4>
<Table
rowKey="project_doc_url"
dataSource={docs}
pagination={false}
size="small"
columns={[
{ title: "文件名称", dataIndex: "materialName", ellipsis: true },
{
title: "操作",
width: 100,
render: (_, record) => (
2026-07-28 15:10:56 +08:00
<PreviewUrlButton url={record.project_doc_url} />
2026-07-22 09:49:21 +08:00
),
},
]}
/>
</div>
))}
{currentDocs.length === 0 && (
<div style={{ textAlign: "center", color: "#999", padding: 40 }}>暂无文档</div>
)}
</Modal>
</PageLayout>
);
};
/** 上传按钮字段组件 - 上传后回填 url */
const UploadButtonField = ({ value, onChange }) => {
const [uploading, setUploading] = useState(false);
return (
<UploadButtonInner
value={value}
onChange={onChange}
uploading={uploading}
setUploading={setUploading}
/>
);
};
const UploadButtonInner = ({ value, onChange, uploading, setUploading }) => {
return (
<Flex gap={8} align="center">
{value && (
<span style={{ fontSize: 12, color: "#999", maxWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{value.split("/").pop()}
</span>
)}
<Upload
showUploadList={false}
action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`}
headers={{ token: sessionStorage.getItem("token") }}
beforeUpload={(file) => {
const ext = "." + file.name.split(".").pop().toLowerCase();
if (ext === ".gif") {
message.error("不支持 GIF 格式图片上传");
return Upload.LIST_IGNORE;
}
return true;
}}
onChange={(info) => {
if (info.file.status === "uploading") {
setUploading(true);
} else if (info.file.status === "done") {
setUploading(false);
const url = info.file.response?.data;
if (url) {
onChange?.(url);
}
} else if (info.file.status === "error") {
setUploading(false);
message.error("上传失败");
}
}}
>
<Button icon={<UploadOutlined />} loading={uploading} size="small">
选择文件
</Button>
</Upload>
</Flex>
);
};
export default Connect(
[NS_SAFETY_EVAL_BUSINESS],
true,
)(AntdTableFuncControl(ProjectDocLibrary));