供应商

This commit is contained in:
孙景学
2025-06-24 10:52:30 +08:00
parent f24a87a15c
commit c1267c8228
116 changed files with 16058 additions and 1 deletions

View File

@ -0,0 +1,343 @@
import { Request, Response } from 'express';
// const data = [
// {
// title: 'Name',
// dataIndex: '评审项',
// key: '评审项',
// },
// {
// title: 'Other',
// children: [
// {
// title: 'Age',
// key: '人员A',
// },
// {
// title: 'Address',
// key: '人员B',
// },
// ]
// },
// ]
const dataInvoiceInfo = [
{
id: '1',
taxpayerType: '一般纳税人',
taxpayerCode: '91345678901234567X',
head: '北京某科技有限公司',
address: '北京市朝阳区XX路99号',
phone: '010-12345678',
bank: '中国银行北京分行',
account: '6228888888888888',
updateTime: '2025-06-17 10:20:00',
voided: false,
qualificationCertificate: 'https://example.com/cert1.pdf',
},
{
id: '2',
taxpayerType: '小规模纳税人',
taxpayerCode: '91345678901234566Y',
head: '上海某信息技术有限公司',
address: '上海市浦东新区XX大厦8楼',
phone: '021-87654321',
bank: '工商银行上海分行',
account: '6229999999999999',
updateTime: '2025-06-16 15:30:00',
voided: true,
qualificationCertificate: '',
},
]
const mockQualificationData = [
{
id: '1',
certificateType: '建筑业企业资质证书',
name: '建筑工程施工总承包一级',
code: 'ZJ-A123456',
typeLevel: '一级',
authority: '住房和城乡建设部',
dateTime: '2023-03-01',
termOfValidity: '2028-03-01',
updateTime: '2025-06-17 10:30:00',
},
{
id: '2',
certificateType: '安全生产许可证',
name: '施工企业安全生产许可证',
code: 'AQ-789012',
typeLevel: 'A级',
authority: '应急管理部',
dateTime: '2022-06-15',
termOfValidity: '2025-06-15',
updateTime: '2025-06-17 11:45:00',
},
]
export const mockData = {
base: {
"id": "123456",
"supplierType": "dvs",
"licenceAccessory": "https://example.com/license.pdf",
"licenceDate": "2025-12-31",
"enterpriseType": "company",
"name": "深圳供应商有限公司",
"nameEn": "Shenzhen Supplier Co., Ltd.",
"socialCreditCode": "91440300MA5F3XXXXQ",
"range": "电子元器件、金属材料销售",
"regAddress": "广东省深圳市南山区科技园",
"workAddress": "广东省深圳市南山区软件产业基地",
"parentCompanyInvestor": "深圳控股集团有限公司",
"legalPerson": "李四",
"idCard": "440301199001015678",
"capital": 5000,
"contactsName": "王五",
"contactsPhone": "13800138000",
"contactsType": "法人代表",
"contactsEmail": "contact@supplier.com",
"telephone": "0755-12345678",
"nation": "新加坡",
"vat": "SG12345678VAT",
"taxpayerId": "SG-TAX-998877",
"currency": "SGD",
"personName": "张三",
"personPhone": "13812345678",
"personBank": "中国银行深圳分行",
"personAccount": "6222020200123456789",
"remark": "该供应商已完成初步审核",
"accessStatus": 1,
"blacklistStatus": 0,
"greylistStatus": 1,
"fillinStatus": 0,
"fillinBy": "",
"sapCode": "SAP998877",
"delFlag": "normal",
"createBy": "admin",
"createTime": "2024-06-01 10:00:00",
"updateBy": "admin",
"updateTime": "2025-06-01 11:00:00",
"lastUpdateTime": "2025-06-17 09:30:00"
},
qualifications: [{
"id": "cert-001",
"supplierId": "supplier-123456",
"certificateType": "安全生产许可证",
"name": "建筑施工总承包一级资质",
"code": "ZJ20230605001",
"typeLevel": "一级",
"authority": "住房和城乡建设部",
"dateTime": "2023-06-05",
"termOfValidity": "2026-06-05",
"accessory": "https://example.com/certificate.pdf",
"delFlag": "normal",
"createBy": "admin",
"createTime": "2023-06-01 10:00:00",
"updateBy": "admin",
"updateTime": "2024-06-01 11:00:00",
"lastUpdateTime": "2025-06-17 09:30:00"
}],
invoice: {
"id": "invoice-001",
"supplierId": "supplier-123456",
"taxpayerType": "general",
"taxpayerCode": "91440300MA5F3XXXXQ",
"phone": "0755-12345678",
"account": "6222020200123456789",
"head": "深圳供应商有限公司",
"address": "深圳市南山区科技园开票楼101号",
"bank": "中国银行深圳分行",
"qualificationCertificate": "https://example.com/tax-cert.pdf",
"delFlag": "normal",
"createBy": "admin",
"createTime": "2024-06-01 09:00:00",
"updateBy": "admin",
"updateTime": "2025-06-01 10:00:00",
"lastUpdateTime": "2025-06-17 08:30:00"
},
bank: [{
"id": "bank-001",
"supplierId": "supplier-123456",
"interbankNumber": "123456789012",
"bank": "中国银行深圳分行",
"swiftCode": "BKCHCNBJ45A",
"accountName": "Shenzhen Supplier Co., Ltd.",
"account": "6222020200123456789",
"currency": "CNY",
"nation": "中国",
"province": "广东省",
"city": "深圳市",
"delFlag": "normal",
"createBy": "admin",
"createTime": "2024-06-01 09:00:00",
"updateBy": "admin",
"updateTime": "2025-06-01 10:00:00",
"lastUpdateTime": "2025-06-17 08:30:00"
}],
survey: {
"supplierName": "深圳供应商有限公司",
"name": "李四",
"position": "采购经理",
"phone": "13800138000",
"email": "lisi@supplier.com",
"dateTime": "2025-06-17",
},
questionReply: [
{
"surveyQuestion": "法律法规:\n我们确保经营和提供的产品服务遵守国家及 各业务所在地的所有使用法律、法规",
"replyValue": "是",
},{
"surveyQuestion": "健康和安全:\n我们为员工提供符合法律法规的安全且健康 的工作场所。我们建立安全管理体系,并向 员工传达工作场所或生活设施的健康和安全 标准,致力于减少工作对员工造成的伤害和 疾病。",
"replyValue": "符合",
},{
"surveyQuestion": "环境:\n我们能够以环境友好的方式经营。我们遵守 适用的环境法律、法规和标准;并建立有效 的环境管理体系。\n我们遵守贵集团对相关产品或服务的部分附 加环境要求,这些要求和规定体现在设计与 产品规范的合同文档中。",
"replyValue": "符合",
},{
"surveyQuestion": "监督和记录:\n我们保留记录遵守相关法律和此行为准则的必要文件并根据要求为贵集团提供对文件的查看权。我们会允许贵集团在适当的时候以验证行为准则执行为目的的现场勘查",
"replyValue": "符合",
}
],
attachments: {
"attachmentsType": "commitment",
"fileName": "anti-bribery-commitment.pdf",
"fileType": "pdf",
"fileSize": "204800",
"filePath": "/data/files/anti-bribery-commitment.pdf",
"fileUrl": "http://example.com/files/anti-bribery-commitment.pdf",
}
};
// 代码中会兼容本地 service mock 以及部署站点的静态数据
export default {
// 供应商信息
'GET /api/system/coscoSupplier': (req: Request, res: Response) => {
res.json({
code: 200,
data: mockData,
msg: '操作成功'
});
},
//
'GET /api/system/qualifications': (req: Request, res: Response) => {
res.json({ code: 200,
data: mockQualificationData,
total: 2,
msg: '操作成功'
});
},
//
'GET /api/system/invoice': (req: Request, res: Response) => {
res.json({ code: 200,
data: dataInvoiceInfo,
total: 2,
msg: '操作成功'
});
},
//
'GET /api/system/bank': (req: Request, res: Response) => {
res.json({ code: 200,
data: [
{
id: '1',
interbankNumber: '123456789',
bank: '中国银行',
accountName: '张三',
account: '6228480000000000000',
currency: '人民币',
nation: '中国',
province: '广东省',
city: '广州市',
updateTime: '2024-06-18',
},
{
id: '2',
interbankNumber: '987654321',
bank: '工商银行',
accountName: '李四',
account: '6228480000000000001',
currency: '美元',
nation: '中国',
province: '江苏省',
city: '南京市',
updateTime: '2024-06-17',
},
],
total: 2,
msg: '操作成功'
});
},
//
'GET /api/system/tianyancha': (req: Request, res: Response) => {
res.json({ code: 200,
data: [
{
key: '1',
base: '京',
name: '北京科技有限公司',
legalPersonName: '张三',
legalPersonType: '1',
regNumber: '110108123456789',
industry: '信息技术',
companyOrgType: '有限责任公司',
regLocation: '北京市海淀区中关村',
estiblishTime: '2010-06-15',
fromTime: '2010-06-16',
toTime: '2025-06-15',
businessScope: '软件开发、技术咨询',
approvedTime: '2010-06-10',
regStatus: '存续',
regCapital: '5000万元',
regInstitute: '北京市工商局',
orgNumber: '1234567890',
creditCode: '91110108MA01A12345',
property3: 'Beijing Tech Co., Ltd.',
updatetime: '2025-06-15',
companyId: '1001',
taxNumber: '110108123456789',
email: 'contact@bjtech.com',
website: 'http://www.bjtech.com',
phoneNumber: '010-12345678',
lastUpdateTime: '2025-06-15 10:00:00',
},
{
key: '2',
base: '沪',
name: '上海电子商务有限公司',
legalPersonName: '李四',
legalPersonType: '1',
regNumber: '310101987654321',
industry: '电子商务',
companyOrgType: '股份有限公司',
regLocation: '上海市浦东新区',
estiblishTime: '2015-03-20',
fromTime: '2015-03-21',
toTime: '2030-03-20',
businessScope: '电子商务平台运营、广告设计',
approvedTime: '2015-03-15',
regStatus: '存续',
regCapital: '1亿元',
regInstitute: '上海市工商局',
orgNumber: '0987654321',
creditCode: '91310101MA1AB23456',
property3: 'Shanghai E-commerce Co., Ltd.',
updatetime: '2025-06-15',
companyId: '1002',
taxNumber: '310101987654321',
email: 'info@shcommerce.com',
website: 'http://www.shcommerce.com',
phoneNumber: '021-87654321',
lastUpdateTime: '2025-06-15 09:30:00',
},
],
total: 2,
msg: '操作成功' });
},
'GET /api/500': (req: Request, res: Response) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: 'error',
message: 'error',
path: '/base/category/list',
});
},
};

View File

@ -0,0 +1,257 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Button, Radio, Input, Upload, message, Spin } from 'antd';
import { reviewInfo, uploadFile } from '../services'; // 你的接口
interface ResultModalProps {
visible: boolean;
record?: { id?: string; [key: string]: any } | null;
onCancel: () => void;
onSubmit: (payload: any) => void;
}
// 单元格内容
type CellValue = {
reviewResult?: 0 | 1; // 0合格 1不合格
remark?: string;
file?: any;
};
const ResultModal: React.FC<ResultModalProps> = ({
visible,
record,
onCancel,
onSubmit
}) => {
const [suppliers, setSuppliers] = useState<any[]>([]);
const [items, setItems] = useState<any[]>([]);
const [cellData, setCellData] = useState<{
[itemId: string]: { [supplierId: string]: CellValue }
}>({});
const [loading, setLoading] = useState(false);
// 备注弹窗
const [remarksModalVisible, setRemarksModalVisible] = useState(false);
const [remarks, setRemarks] = useState('');
const [fileList, setFileList] = useState<any[]>([]);
const [currentCell, setCurrentCell] = useState<{ itemId: string, supplierId: string } | null>(null);
// 拉取评审数据
useEffect(() => {
if (visible && record?.id) {
setLoading(true);
reviewInfo({ id: record.id, userId: 'E0001' })
.then((res: any) => {
const data = res?.data || [];
setSuppliers(data);
// 用第一家供应商的coscoAccessItemList生成所有评审项
const firstSupplier = data[0] || {};
const itemList = (firstSupplier.coscoAccessItemList || []).filter((i: { itemType:string } ) => i.itemType !== 'summary');
setItems(itemList);
// 初始化 cellData
const newCellData: any = {};
itemList.forEach((item: any) => {
newCellData[item.id] = {};
data.forEach((sup: any) => {
newCellData[item.id][sup.supplierId] = {};
});
});
setCellData(newCellData);
})
.finally(() => setLoading(false));
}
}, [visible, record]);
// 单选
const handleRadioChange = (itemId: string, supplierId: string, value: string) => {
setCellData(prev => ({
...prev,
[itemId]: {
...prev[itemId],
[supplierId]: {
...prev[itemId]?.[supplierId],
reviewResult: value === '合格' ? 0 : 1
}
}
}));
};
// 打开备注弹窗
const openRemarksModal = (itemId: string, supplierId: string) => {
setCurrentCell({ itemId, supplierId });
const cell = cellData[itemId]?.[supplierId] || {};
setRemarks(cell.remark || '');
setFileList(cell.file
? [{
uid: '-1',
name: cell.file.fileName,
status: 'done',
url: cell.file.fileUrl,
response: cell.file
}]
: []);
setRemarksModalVisible(true);
};
// 上传
const uploadProps = {
fileList,
maxCount: 1,
onRemove: () => setFileList([]),
customRequest: async (options: any) => {
const res = await uploadFile(options.file);
const fileObj = res;
setFileList([{
uid: options.file.uid,
name: fileObj.fileName,
status: 'done',
url: fileObj.url,
response: fileObj
}]);
message.success('文件上传成功');
options.onSuccess && options.onSuccess(res, options.file);
}
};
// 备注提交
const handleSubmitRemarks = () => {
if (!currentCell) return;
setCellData(prev => ({
...prev,
[currentCell.itemId]: {
...prev[currentCell.itemId],
[currentCell.supplierId]: {
...prev[currentCell.itemId]?.[currentCell.supplierId],
remark: remarks,
file: fileList[0]?.response || undefined
}
}
}));
setRemarksModalVisible(false);
};
// 提交
const handleSubmit = () => {
// 组装参数
let result: any[] = [];
items.forEach(item => {
suppliers.forEach(sup => {
const cell = cellData?.[item.id]?.[sup.supplierId];
if (cell && cell.reviewResult !== undefined) {
result.push({
id: item.id,
reviewResult: cell.reviewResult,
remark: cell.remark || '',
coscoAccessTtemAttachments: cell.file || undefined
});
}
});
});
console.log(items,'items');
console.log(result,'result');
onSubmit && onSubmit({ coscoAccessUserItemList: result });
};
// 组装表头
const columns = [
{
title: '评审项',
dataIndex: 'itemName',
key: 'itemName',
width: 200,
fixed: 'left'
},
...suppliers.map(sup => ({
title: sup.supplierName,
dataIndex: sup.supplierId,
key: sup.supplierId,
width: 300,
render: (_: any, row: any) => {
const v = cellData?.[row.key]?.[sup.supplierId] || {};
console.log(sup,'sup');
return (
<div>
<Radio.Group
value={
v.reviewResult === 0
? '合格'
: v.reviewResult === 1
? '不合格'
: undefined
}
onChange={e => handleRadioChange(row.key, sup.supplierId, e.target.value)}
>
<Radio value="合格"></Radio>
<Radio value="不合格"></Radio>
</Radio.Group>
<Button type="link" onClick={() => openRemarksModal(row.key, sup.supplierId)}></Button>
{v.remark && <span style={{ color: '#aaa', fontSize: 12 }}></span>}
{v.file && <span style={{ color: '#52c41a', fontSize: 12, marginLeft: 8 }}></span>}
</div>
);
}
}))
];
// 行数据
const tableData = items.map(item => ({
key: item.id,
itemName: item.itemName
}));
return (
<Modal
title="评审结果"
visible={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}></Button>,
<Button key="submit" type="primary" onClick={handleSubmit}></Button>
]}
width={1000}
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }}
centered
destroyOnClose
>
<Spin spinning={loading}>
<Table
dataSource={tableData}
columns={columns}
pagination={false}
bordered
scroll={{ x: 300 * suppliers.length + 200 }}
/>
</Spin>
<Modal
title="备注/上传附件"
visible={remarksModalVisible}
onCancel={() => setRemarksModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setRemarksModalVisible(false)}></Button>,
<Button key="submit" type="primary" onClick={handleSubmitRemarks}></Button>
]}
destroyOnClose
>
<Input.TextArea
rows={4}
placeholder="请输入备注"
value={remarks}
onChange={e => setRemarks(e.target.value)}
/>
<Upload {...uploadProps}>
<Button style={{ marginTop: 12 }}></Button>
</Upload>
</Modal>
</Modal>
);
};
export default ResultModal;

View File

@ -0,0 +1,166 @@
import React, { useEffect, useState } from "react";
import { connect, useIntl } from 'umi';
import { Form, Button, Table, Select, Space, Input } from 'antd';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { SearchOutlined } from '@ant-design/icons';
import { getPage } from './services';
//查看评审结果 弹窗
import ResultModal from './components/ResultModal';
interface Data {
deptName: string;
categoryName: string;
createTime: string;
exitTime: string;
exitReason: string;
}
interface ModalInfo {
type: 'view' | 'result' | null;
visible: boolean;
record: Data | null;
}
const CooperateEnterprise: React.FC = () => {
const [searchForm] = Form.useForm();
const intl = useIntl();
const [data, setData] = useState<Data[]>([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState<TablePaginationConfig>({ current: 1, pageSize: 10, total: 0 });
const [modalInfo, setModalInfo] = useState<ModalInfo>({ type: null, visible: false, record: null });
const openModal = (type: 'view' | 'result', record: Data) => {
setModalInfo({ type, visible: true, record });
};
const closeModal = () => {
setModalInfo({ type: null, visible: false, record: null });
};
// 列表数据
const getList = async (params: { pageNo: number; pageSize: number; parentCode: string; }) => {
setLoading(true);
try {
const { code, data } = await getPage(params);
if (code === 200) {
setData(data.records);
setPagination({ current: params.pageNo, pageSize: params.pageSize, total: data.total });
}
} catch (error) {
console.error('Failed to fetch data:', error);
} finally {
setLoading(false);
}
};
const handleReset = () => {
searchForm.resetFields();
getList({ pageNo: 1, pageSize: pagination.pageSize ?? 10, parentCode: '' });
};
const handleSearch = (values: any) => {
const { parentCode } = values;
getList({
pageNo: 1,
pageSize: pagination.pageSize ?? 10,
parentCode: parentCode || ''
});
};
useEffect(() => {
getList({ pageNo: 1, pageSize: 10, parentCode: '' });
}, []);
const columns: ColumnsType<Data> = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center',
render: (_: any, __: any, index: number) => index + 1,
},
{
title: '准入工作',
dataIndex: 'accessWorkName',
key: 'accessWorkName',
},
{
title: '发起单位',
dataIndex: 'deptId',
key: 'deptId',
},
{
title: '准入部门',
dataIndex: 'deptId',
key: 'deptId',
},
{
title: '品类',
dataIndex: 'exitTime',
key: 'exitTime',
},
{
title: '准入方式',
dataIndex: 'accessTypeText',
key: 'accessTypeText',
},
{
title: '评审时间',
dataIndex: 'createTime',
key: 'createTime',
},
{
title: '评审状态',
dataIndex: 'reviewStatusText',
key: 'reviewStatusText',
},
{
title: '操作',
render: (_: any, record: any) => (
<Space>
<a onClick={() => openModal('result', record)}></a>
</Space>
),
},
];
return (
<>
<Form
form={searchForm}
layout="inline"
onFinish={handleSearch}
style={{ marginBottom: 16 }}
>
<Form.Item name="parentCode" label="准入工作">
<Input placeholder="请输入准入工作" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
</Button>
</Form.Item>
<Form.Item>
<Button onClick={handleReset}></Button>
</Form.Item>
</Form>
<Table
rowKey="id"
className="custom-table"
columns={columns}
dataSource={data}
pagination={pagination}
loading={loading}
onChange={(pagination) => getList({ pageNo: pagination.current!, pageSize: pagination.pageSize!, parentCode: '', })}
/>
<ResultModal
visible={modalInfo.type === 'result' && modalInfo.visible}
record={modalInfo.record}
onCancel={closeModal}
/>
</>
);
};
export default connect()(CooperateEnterprise);

View File

@ -0,0 +1,78 @@
import request from '@/utils/request';
/**
* 准入列表
*/
interface getPageData {
pageNo: number;
pageSize: number;
parentCode?: string;
}
export const getPage = (data: getPageData) => request.post('/coscoAccessWork/getPage', { data });
/**
* 评审修改时用的详情页
*/
interface reviewInfoData {
id: string;
userId?: string;
}
export const reviewInfo = (params: reviewInfoData) => request.get(`/coscoAccessWork/reviewInfo`, { params });
/**
* 上传文件
* @param file 上传的文件对象
* @returns 上传结果
*/
export const uploadFile = async (file: File) => {
const formData = new FormData();
formData.append('file', file);
return request('/fileConfig/files/upload', {
method: 'POST',
data: formData,
});
};
export async function coscoSupplier(params:any) {
console.log(params,'params');
return request('/api/system/coscoSupplier', {
method: 'GET',
params
});
}
export async function library(params:any) {
return request('/api/system/library', {
method: 'GET',
params
});
}
export async function qualifications(params:any) {
return request('/api/system/qualifications', {
method: 'GET',
params
});
}
export async function invoice(params:any) {
return request('/api/system/invoice', {
method: 'GET',
params
});
}
export async function bank(params:any) {
return request('/api/system/bank', {
method: 'GET',
params
});
}