供应商退出、准入、 工作台

This commit is contained in:
孙景学
2025-06-27 10:41:33 +08:00
parent 527637cce3
commit 4d54b36a16
40 changed files with 3746 additions and 1631 deletions

View File

@ -3,7 +3,6 @@ import { Modal, Form, Select, Button, Tree, message, DatePicker, Radio, Upload,
//
import { UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload/interface';
import moment from 'moment';
//组件
import SupplierSelector from './SupplierSelector';
import ReviewerSelector from './ReviewerSelector';
@ -14,10 +13,10 @@ const { Option } = Select;
const { RangePicker } = DatePicker;
//selected 类型
interface Reviewer {
key: string;
userId: string;
name: string;
id: string;
dept: string;
deptId: string;
}
// 传入的人接口
interface ReviewerSelectorData {
@ -85,7 +84,7 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
// 只取叶子节点 key
const leafKeys = findLeafKeys(convertTreeData(categoriesTreeData));
const onlyLeafChecked = keys.filter(key => leafKeys.includes(key));
const onlyLeafChecked = keys.filter(key => leafKeys.includes(String(key)));
setCheckedKeys(keys); // UI 显示用,还是全量
form.setFieldsValue({ categoryIds: onlyLeafChecked }); // 只存叶子到表单
@ -138,7 +137,20 @@ const CreateModal: React.FC<{ visible: boolean; onCancel: () => void; }> = ({ vi
};
// 提交
const onFinish = async (values: any) => {
const finalPayload = {
const finalPayload: {
coscoAccessWork: {
startTime: string;
endTime: string;
accessType: string;
accessWorkName: string;
deptId: string;
};
categoryIds: string[];
supplierIds: string[];
coscoAccessUserls: { userId: string; deptId: string; isLeader: number }[];
coscoAccessItems: { itemName: string; reviewBy: string[] }[];
coscoAccessWorkAttachments: any;
} = {
coscoAccessWork: {
startTime: '',
endTime: '',

View File

@ -1,201 +1,223 @@
import React from 'react';
import { Modal, Table, Tag, Typography, Button } from 'antd';
import React, { useEffect, useState } from 'react';
import { Modal, Table, Button, Select, Spin, message } from 'antd';
import { reviewInfoData } from '../services';
const { Link } = Typography;
const dataSource = [
{
key: '1',
item: '供应商信息登记表',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '2',
item: '供应商反商业贿赂承诺书',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '3',
item: '供应商社会准则符合性自审问卷',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '4',
item: '是否为禁止使用供应商',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '5',
item: '3年内经营活动中没有重大违法记录的书面声明',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '6',
item: '企业存续状态审核(营业执照等)',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '不合格',
companyB_lisiRemark: '查看备注',
},
{
key: '7',
item: '行业资质、认证情况(按需)',
companyA_zhangsan: '合格',
companyA_lisi: '合格',
companyB_zhangsan: '合格',
companyB_lisi: '合格',
},
{
key: '8',
item: '现场考察、产品试用情况(按需)',
companyA_zhangsan: '合格',
companyA_zhangsanRemark: '查看备注',
companyA_lisi: '合格',
companyA_lisiRemark: '查看备注',
companyB_zhangsan: '合格',
companyB_zhangsanRemark: '查看备注',
companyB_lisi: '合格',
companyB_lisiRemark: '查看备注',
},
];
const columns = [
{
title: '序号',
dataIndex: 'key',
width: 60,
},
{
title: '评审项',
dataIndex: 'item',
width: 260,
},
{
title: (
<div>
<div></div>
</div>
),
children: [
{
title: '张三',
dataIndex: 'companyA_zhangsan',
width: 100,
render: (text: string, record: any) => renderStatus(text, record.companyA_zhangsanRemark),
},
{
title: '李四',
dataIndex: 'companyA_lisi',
width: 100,
render: (text: string, record: any) => renderStatus(text, record.companyA_lisiRemark),
},
],
},
{
title: (
<div>
<div>广</div>
</div>
),
children: [
{
title: '张三',
dataIndex: 'companyB_zhangsan',
width: 100,
render: (text: string, record: any) => renderStatus(text, record.companyB_zhangsanRemark),
},
{
title: '李四',
dataIndex: 'companyB_lisi',
width: 100,
render: (text: string, record: any) => renderStatus(text, record.companyB_lisiRemark),
},
],
},
];
function renderStatus(text: string, remark?: string) {
const color = text === '合格' ? 'green' : text === '不合格' ? 'red' : undefined;
return (
<div>
<Tag color={color} style={{ marginRight: 6 }}>
{text}
</Tag>
{remark && (
<Link style={{ fontSize: 12 }} onClick={() => alert('备注内容')}>
{remark}
</Link>
)}
</div>
);
interface ResultModalProps {
visible: boolean;
record?: { id?: string; [key: string]: any } | null;
onCancel: () => void;
}
const ResultModal: React.FC<{
// 只读备注弹窗
const RemarkViewModal: React.FC<{
visible: boolean;
record?: any;
onCancel: () => void;
}> = ({ visible, onCancel }) => {
remark: string;
file?: any;
}> = ({ visible, onCancel, remark, file }) => (
<Modal
visible={visible}
title="备注信息"
footer={null}
onCancel={onCancel}
destroyOnClose
>
<div>
<div style={{ marginBottom: 12 }}>
<b></b>{remark || '无'}
</div>
{file && file.fileUrl && (
<div>
<b></b>
<a href={file.fileUrl} target="_blank" rel="noopener noreferrer">
{file.fileName}
</a>
</div>
)}
</div>
</Modal>
);
const ResultModal: React.FC<ResultModalProps> = ({
visible,
record,
onCancel,
}) => {
const [loading, setLoading] = useState(false);
const [suppliers, setSuppliers] = useState<any[]>([]);
const [items, setItems] = useState<any[]>([]);
const [groupSummaryResult, setGroupSummaryResult] = useState<{ [k: string]: '0' | '1' | undefined }>({});
// 查看备注弹窗
const [remarkModal, setRemarkModal] = useState({ open: false, remark: '', file: undefined as any });
// 拉取数据
useEffect(() => {
if (visible && record?.id) {
setLoading(true);
reviewInfoData({ id: record.id })
.then((res: any) => {
const supplierList = res?.data || [];
setSuppliers(supplierList);
// 所有项,取第一家公司
const allItems = (supplierList[0]?.coscoAccessItemList || []);
// 非summary项
setItems(allItems.filter((item: any) => item.itemType !== 'summary'));
// summary 行初始化
const summaryMap: { [k: string]: '0' | '1' | undefined } = {};
supplierList.forEach((sup: any) => {
// summary 行
const summaryItem = (sup.coscoAccessItemList || []).find((i: any) => i.itemType === 'summary');
console.log(summaryItem,'summaryItem');
summaryMap[sup.supplierId] = summaryItem.coscoAccessUserItemList[0]?.reviewResult;
});
setGroupSummaryResult(summaryMap);
})
.finally(() => setLoading(false));
} else if (!visible) {
setSuppliers([]);
setItems([]);
setGroupSummaryResult({});
}
}, [visible, record]);
// 构造二级表头
const columns: any[] = [
{
title: '评审项',
dataIndex: 'itemName',
key: 'itemName',
width: 180,
fixed: 'left',
},
...suppliers.map(sup => {
// 当前公司所有人员所有非summary项取 coscoAccessUserItemList 多个)
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((item: any) => {
if (item.itemType !== 'summary' && Array.isArray(item.coscoAccessUserItemList)) {
item.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const reviewers = Array.from(reviewerSet);
return {
title: (
<div>
<div style={{ fontWeight: 600 }}>{sup.supplierName}</div>
</div>
),
children: reviewers.map((reviewBy: string) => ({
title: reviewBy,
dataIndex: `${sup.supplierId}_${reviewBy}`,
key: `${sup.supplierId}_${reviewBy}`,
width: 100,
align: 'center',
render: (_: any, row: any) => {
// 在sup.coscoAccessItemList里找该itemName下该人员
const item = (sup.coscoAccessItemList || []).find((it: any) => it.itemName === row.itemName && it.itemType !== 'summary');
if (!item) return null;
const userItem = (item.coscoAccessUserItemList || []).find((u: any) => u.reviewBy === reviewBy);
if (!userItem) return null;
return (
<div>
{userItem.reviewResult === '0'
? <span style={{ color: '#52c41a' }}></span>
: userItem.reviewResult === '1'
? <span style={{ color: '#f5222d' }}></span>
: ''}
{userItem.remark && (
<Button type="link" size="small" onClick={() =>
setRemarkModal({ open: true, remark: userItem.remark, file: userItem.coscoAccessTtemAttachments })
}></Button>
)}
</div>
);
}
}))
}
})
];
// 组装表格行数据
const tableData = items.map(item => {
const row: any = {
key: item.id,
itemName: item.itemName
};
suppliers.forEach(sup => {
// 每个人员一格
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((it: any) => {
if (it.itemType !== 'summary' && Array.isArray(it.coscoAccessUserItemList)) {
it.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const reviewers = Array.from(reviewerSet);
reviewers.forEach((reviewBy: string) => {
row[`${sup.supplierId}_${reviewBy}`] = null; // 具体显示用render
});
});
return row;
});
// summary 行
const summaryRow = (
<Table.Summary.Row>
<Table.Summary.Cell index={-1}></Table.Summary.Cell>
{suppliers.map((sup, index) => {
// 统计reviewer个数
const reviewerSet = new Set<string>();
(sup.coscoAccessItemList || []).forEach((item: any) => {
if (item.itemType !== 'summary' && Array.isArray(item.coscoAccessUserItemList)) {
item.coscoAccessUserItemList.forEach((u: any) => {
reviewerSet.add(u.reviewBy);
});
}
});
const colSpan = reviewerSet.size || 1;
return (
<Table.Summary.Cell index={index} key={sup.supplierId} colSpan={colSpan} align="center">
<span style={{color: groupSummaryResult[sup.supplierId] === '0'? '#52c41a': '#f5222d' }}> {groupSummaryResult[sup.supplierId] === '0'? '合格': '不合格'}</span>
</Table.Summary.Cell>
)
})}
</Table.Summary.Row>
);
return (
<Modal
title="评审结果"
title="查看评审结果"
visible={visible}
onCancel={onCancel}
footer={null}
footer={[
<Button key="cancel" onClick={onCancel}> </Button>,
]}
width={1000}
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }}
centered
destroyOnClose
>
<Table
dataSource={dataSource}
columns={columns}
pagination={false}
bordered
scroll={{ x: 900 }}
summary={() => {
// 汇总结果示例,模拟图片底部汇总
return (
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={2} align="right">
</Table.Summary.Cell>
<Table.Summary.Cell index={2} align="center" colSpan={2}>
<Tag color="green"></Tag>
</Table.Summary.Cell>
<Table.Summary.Cell index={4} align="center" colSpan={2}>
<Tag color="red"></Tag>
</Table.Summary.Cell>
</Table.Summary.Row>
);
}}
<Spin spinning={loading}>
<Table
columns={columns}
dataSource={tableData}
pagination={false}
bordered
summary={() => summaryRow}
scroll={{ x: 300 * suppliers.length + 200 }}
/>
</Spin>
<RemarkViewModal
visible={remarkModal.open}
onCancel={() => setRemarkModal({ open: false, remark: '', file: undefined })}
remark={remarkModal.remark}
file={remarkModal.file}
/>
<div style={{ textAlign: 'right' }}>
<Button type="primary" >
</Button>
<Button type="primary" >
</Button>
<Button onClick={onCancel} style={{ marginRight: 8 }}>
</Button>
</div>
</Modal>
);
};