供应商

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,187 @@
import React, { useEffect, useState } from "react";
import { Form, Button, Table, Select, Input, Space, DatePicker, message } from "antd";
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons';
// 类型定义
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
// Mock数据
const MOCK_DATA = Array.from({ length: 188 }).map((_, idx) => ({
id: idx + 1,
subject: `${idx % 2 ? '中山市合创展包装材料有限公司' : '上海硕建建筑技术工程有限公司'}等黑灰名单申请`,
unit: idx % 3 === 0 ? '中远海运特运' : idx % 3 === 1 ? '中远海运散运' : '中远海运广州公司',
dept: '采购部',
applyTime: `2024-10-${String(26 - (idx % 5)).padStart(2, "0")} ${idx % 2 ? '9:00' : '10:00'}`,
status: idx < 3 ? '未开始' : idx < 7 ? '进行中' : '已结束',
result: idx < 8 ? '' : (idx % 2 ? '通过' : '驳回'),
}));
// 状态、结果选项
const statusOptions = [
{ label: "请选择", value: "" },
{ label: "未开始", value: "未开始" },
{ label: "进行中", value: "进行中" },
{ label: "已结束", value: "已结束" },
];
const resultOptions = [
{ label: "请选择", value: "" },
{ label: "通过", value: "通过" },
{ label: "驳回", value: "驳回" },
];
// 列表类型
interface DataType {
id: number;
subject: string;
unit: string;
dept: string;
applyTime: string;
status: string;
result: string;
}
const BlacklistAudit: React.FC = () => {
// 搜索表单
const [form] = Form.useForm();
// 列表数据
const [data, setData] = useState<DataType[]>([]);
// 加载
const [loading, setLoading] = useState(false);
// 分页
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: MOCK_DATA.length,
});
// 查询
const handleSearch = () => {
setPagination({ ...pagination, current: 1 });
getList(1, pagination.pageSize);
};
// 重置
const handleReset = () => {
form.resetFields();
setPagination({ ...pagination, current: 1 });
getList(1, pagination.pageSize);
};
// 获取列表(模拟接口,支持分页和部分筛选)
const getList = async (page: number = 1, pageSize: number = 10) => {
setLoading(true);
setTimeout(() => {
let filtered = MOCK_DATA;
const values = form.getFieldsValue();
if (values.subject) {
filtered = filtered.filter(item => item.subject.includes(values.subject));
}
if (values.unit) {
filtered = filtered.filter(item => item.unit === values.unit);
}
if (values.status) {
filtered = filtered.filter(item => item.status === values.status);
}
if (values.result) {
filtered = filtered.filter(item => item.result === values.result);
}
// 日期筛选略
setData(filtered.slice((page - 1) * pageSize, page * pageSize));
setPagination({ current: page, pageSize, total: filtered.length });
setLoading(false);
}, 300);
};
// 初始化
useEffect(() => {
getList();
}, []);
// 列
const columns: ColumnsType<DataType> = [
{
title: "序号",
dataIndex: "index",
key: "index",
align: "center",
width: 60,
render: (_: any, __: any, idx: number) =>
(pagination.current! - 1) * pagination.pageSize! + idx + 1,
},
{ title: "申请主题", dataIndex: "subject", key: "subject", align: "center", ellipsis: true },
{ title: "发起单位", dataIndex: "unit", key: "unit", align: "center" },
{ title: "发起部门", dataIndex: "dept", key: "dept", align: "center" },
{ title: "申请时间", dataIndex: "applyTime", key: "applyTime", align: "center" },
{ title: "流程状态", dataIndex: "status", key: "status", align: "center" },
{ title: "审批结果", dataIndex: "result", key: "result", align: "center" },
{
title: "操作",
key: "option",
align: "center",
render: (record: DataType) => (
<Space>
<a style={{ color: "#1677ff" }}></a>
<a style={{ color: "#1677ff" }}></a>
</Space>
),
},
];
// 单元下拉mock示例
const unitOptions = [
{ label: "请选择", value: "" },
{ label: "中远海运特运", value: "中远海运特运" },
{ label: "中远海运散运", value: "中远海运散运" },
{ label: "中远海运广州公司", value: "中远海运广州公司" },
];
return (
<div>
{/* 搜索区 */}
<Form form={form} layout="inline" style={{ marginBottom: 12 }}>
<Form.Item name="subject" label="申请主题">
<Input placeholder="请输入供应商名称关键字" style={{ width: 220 }} allowClear />
</Form.Item>
<Form.Item name="unit" label="发起单位">
<Select style={{ width: 160 }}>
{unitOptions.map(opt => <Select.Option value={opt.value} key={opt.value}>{opt.label}</Select.Option>)}
</Select>
</Form.Item>
<Form.Item name="applyTime" label="申请时间">
<DatePicker.RangePicker style={{ width: 220 }} />
</Form.Item>
<Form.Item name="status" label="流程状态">
<Select style={{ width: 120 }}>
{statusOptions.map(opt => <Select.Option value={opt.value} key={opt.value}>{opt.label}</Select.Option>)}
</Select>
</Form.Item>
<Form.Item name="result" label="审批结果">
<Select style={{ width: 120 }}>
{resultOptions.map(opt => <Select.Option value={opt.value} key={opt.value}>{opt.label}</Select.Option>)}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch} style={{ marginRight: 8 }}></Button>
<Button icon={<ReloadOutlined />} onClick={handleReset}></Button>
</Form.Item>
</Form>
{/* 表格 */}
<Table
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={{
...pagination,
showQuickJumper: true,
showSizeChanger: true,
showTotal: t => `${t}`,
onChange: (current, pageSize) => getList(current, pageSize),
}}
bordered
style={{ background: '#fff' }}
/>
</div>
);
};
export default BlacklistAudit;

View File

@ -0,0 +1,140 @@
import React, { useState } from "react";
import { Modal, Button, Select, Input, Table, message } from "antd";
import SupplierSelectModal from "./SupplierSelectModal"; // 上面生成的组件
const { Option } = Select;
interface Supplier {
id: number;
name: string;
unit: string;
dept: string;
category: string;
}
interface CreateBlacklistModalProps {
visible: boolean;
onOk: () => void;
onCancel: () => void;
}
const initialSuppliers: Supplier[] = [
{
id: 1,
name: "中山市合创展包装材料有限公司",
unit: "采购部",
dept: "采购部",
category: "船用燃油",
lastEval: "D",
lastEvalDate: "2025-02-20",
},
{
id: 2,
name: "广东振兴塑胶机械有限公司",
unit: "采购部",
dept: "采购部",
category: "船用燃油",
lastEval: "D",
lastEvalDate: "2025-02-20",
}
];
const CreateBlacklistModal: React.FC<CreateBlacklistModalProps> = ({
visible, onOk, onCancel
}) => {
const [selectVisible, setSelectVisible] = useState(false);
const [suppliers, setSuppliers] = useState<Supplier[]>(initialSuppliers);
const [type, setType] = useState("黑名单");
const [reason, setReason] = useState("");
// 删除已选供应商
const removeSupplier = (id: number) => {
setSuppliers(suppliers.filter(s => s.id !== id));
};
// 选择供应商
const handleSelectOk = (selected: Supplier[]) => {
setSuppliers(selected);
setSelectVisible(false);
};
// 提交
const handleSubmit = () => {
if (!reason.trim()) {
message.error("请输入黑灰名单原因!");
return;
}
message.success("提交成功(此处为前端示例)");
onOk?.();
};
const columns = [
{ title: "供应商名称", dataIndex: "name", align: "center" },
{ title: "准入部门", dataIndex: "unit", align: "center" },
{ title: "准入时间", dataIndex: "accessTime", align: "center", render: () => "2023-04-20 13:00" },
{ title: "准入品类", dataIndex: "category", align: "center" },
{ title: "最近一次评价", dataIndex: "lastEval", align: "center" },
{ title: "评价时间", dataIndex: "lastEvalDate", align: "center" },
{
title: "选择",
dataIndex: "option",
align: "center",
render: (_: any, record: Supplier) => (
<Button type="link" onClick={() => removeSupplier(record.id)}></Button>
)
}
];
return (
<>
<Modal
title={<div style={{ textAlign: "center", fontWeight: 700, fontSize: 28 }}></div>}
visible={visible}
onCancel={onCancel}
width={900}
footer={null}
destroyOnClose
bodyStyle={{ padding: "36px 36px 16px" }}
>
<Button type="primary" style={{ marginBottom: 18 }} onClick={() => setSelectVisible(true)}>
</Button>
<Table
columns={columns}
dataSource={suppliers}
rowKey="id"
bordered
pagination={false}
/>
<div style={{ margin: "32px 0 8px 0" }}>
<Select style={{ width: 200, marginRight: 16 }} value={type} onChange={setType}>
<Option value="黑名单"></Option>
<Option value="灰名单"></Option>
</Select>
</div>
<div>
<Input.TextArea
placeholder="进入黑灰名单原因(必须填写)"
rows={4}
value={reason}
onChange={e => setReason(e.target.value)}
style={{ width: "100%", marginTop: 8 }}
/>
</div>
<div style={{ marginTop: 28, textAlign: "center" }}>
<Button type="primary" style={{ marginRight: 24 }} onClick={handleSubmit}></Button>
<Button onClick={onCancel}></Button>
</div>
</Modal>
<SupplierSelectModal
visible={selectVisible}
selectedSuppliers={suppliers}
onOk={handleSelectOk}
onCancel={() => setSelectVisible(false)}
/>
</>
);
};
export default CreateBlacklistModal;

View File

@ -0,0 +1,229 @@
import React, { useEffect, useState } from "react";
import { Modal, Table, Button, Checkbox, Row, Col, Input, Select, Pagination, Form, Space } from "antd";
const { Option } = Select;
// mock 数据
const mockSuppliers = Array.from({ length: 25 }).map((_, i) => ({
id: i + 1,
name: `XXXX${i + 1}有限公司`,
unit: "散运三级单位",
dept: "采购部",
category: ["燃油", "润滑油", "备件"][i % 3],
evalLevel: ["A", "B", "C", "D"][i % 4],
}));
const evalOptions = [
{ value: "", label: "请选择" },
{ value: "A", label: "A" },
{ value: "B", label: "B" },
{ value: "C", label: "C" },
{ value: "D", label: "D" },
];
const categoryOptions = [
{ value: "", label: "请选择" },
{ value: "燃油", label: "燃油" },
{ value: "润滑油", label: "润滑油" },
{ value: "备件", label: "备件" },
];
const PAGE_SIZE = 5;
const SupplierSelectModal = ({
visible,
onOk,
onCancel,
selectedSuppliers,
}) => {
// 查询表单
const [form] = Form.useForm();
// 选中
const [leftSelected, setLeftSelected] = useState([]);
const [rightSelected, setRightSelected] = useState([]);
const [rightData, setRightData] = useState(selectedSuppliers || []);
const [leftPage, setLeftPage] = useState(1);
const [query, setQuery] = useState({});
// 数据过滤
const filteredSuppliers = mockSuppliers.filter(item => {
const { name = "", evalLevel = "", category = "" } = query;
return (
(!name || item.name.includes(name)) &&
(!evalLevel || item.evalLevel === evalLevel) &&
(!category || item.category === category)
);
});
const pagedSuppliers = filteredSuppliers.slice((leftPage - 1) * PAGE_SIZE, leftPage * PAGE_SIZE);
useEffect(() => {
setRightData(selectedSuppliers || []);
}, [selectedSuppliers, visible]);
// 查询
const handleSearch = () => {
setQuery(form.getFieldsValue());
setLeftPage(1);
setLeftSelected([]);
};
// 重置
const handleReset = () => {
form.resetFields();
setQuery({});
setLeftPage(1);
setLeftSelected([]);
};
const leftColumns = [
{
title: <Checkbox
checked={leftSelected.length === pagedSuppliers.length && pagedSuppliers.length > 0}
indeterminate={leftSelected.length > 0 && leftSelected.length < pagedSuppliers.length}
onChange={e => {
setLeftSelected(e.target.checked ? pagedSuppliers.map(i => i.id) : []);
}}
></Checkbox>,
dataIndex: "select",
width: 60,
render: (_: any, record) => (
<Checkbox
checked={leftSelected.includes(record.id)}
onChange={e => {
setLeftSelected(e.target.checked
? [...leftSelected, record.id]
: leftSelected.filter(id => id !== record.id));
}}
></Checkbox>
)
},
{ title: "供应商名称", dataIndex: "name" },
{ title: "准入单位", dataIndex: "unit" },
{ title: "准入部门", dataIndex: "dept" },
{ title: "准入品类", dataIndex: "category" },
{ title: "最近一次评价等级", dataIndex: "evalLevel" }
];
// 已选表格
const rightColumns = [
{
title: <Checkbox
checked={rightSelected.length === rightData.length && rightData.length > 0}
indeterminate={rightSelected.length > 0 && rightSelected.length < rightData.length}
onChange={e => {
setRightSelected(e.target.checked ? rightData.map(i => i.id) : []);
}}
></Checkbox>,
dataIndex: "select",
width: 60,
render: (_: any, record) => (
<Checkbox
checked={rightSelected.includes(record.id)}
onChange={e => {
setRightSelected(e.target.checked
? [...rightSelected, record.id]
: rightSelected.filter(id => id !== record.id));
}}
></Checkbox>
)
},
{ title: "供应商名称", dataIndex: "name" },
{ title: "准入单位", dataIndex: "unit" },
{ title: "准入部门", dataIndex: "dept" },
{ title: "准入品类", dataIndex: "category" }
];
// 添加
const handleAdd = () => {
const added = pagedSuppliers.filter(s => leftSelected.includes(s.id));
const combined = [...rightData, ...added.filter(i => !rightData.some(r => r.id === i.id))];
setRightData(combined);
setLeftSelected([]);
};
// 移除
const handleRemove = () => {
setRightData(rightData.filter(i => !rightSelected.includes(i.id)));
setRightSelected([]);
};
// 确认
const handleOk = () => {
onOk(rightData);
};
return (
<Modal
title="选择供应商"
visible={visible}
onCancel={onCancel}
onOk={handleOk}
width={1000}
destroyOnClose
okText="确认"
cancelText="取消"
bodyStyle={{ padding: 24 }}
>
{/* 查询区 */}
<Form layout="inline" form={form} style={{ marginBottom: 16 }}>
<Form.Item name="name" label="供应商名称">
<Input style={{ width: 180 }} allowClear />
</Form.Item>
<Form.Item name="evalLevel" label="最近一次评价等级">
<Select style={{ width: 140 }}>
{evalOptions.map(opt => <Option value={opt.value} key={opt.value}>{opt.label}</Option>)}
</Select>
</Form.Item>
<Form.Item name="category" label="准入品类">
<Select style={{ width: 140 }}>
{categoryOptions.map(opt => <Option value={opt.value} key={opt.value}>{opt.label}</Option>)}
</Select>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" onClick={handleSearch}></Button>
<Button onClick={handleReset}></Button>
</Space>
</Form.Item>
</Form>
<Row gutter={32}>
<Col span={11}>
<div style={{ marginBottom: 8 }}> <span style={{ color: "#1890ff" }}>{filteredSuppliers.length}</span></div>
<Table
columns={leftColumns}
dataSource={pagedSuppliers}
rowKey="id"
pagination={false}
size="small"
bordered
/>
<div style={{ textAlign: "right", marginTop: 8 }}>
<Pagination
simple
current={leftPage}
pageSize={PAGE_SIZE}
total={filteredSuppliers.length}
onChange={setLeftPage}
/>
</div>
</Col>
<Col span={2} style={{ textAlign: "center", display: "flex", flexDirection: "column", justifyContent: "center" }}>
<Button type="primary" disabled={!leftSelected.length} onClick={handleAdd} style={{ marginBottom: 16 }}>{">"}</Button>
<Button disabled={!rightSelected.length} onClick={handleRemove}>{"<"}</Button>
</Col>
<Col span={11}>
<div style={{ marginBottom: 8 }}></div>
<Table
columns={rightColumns}
dataSource={rightData}
rowKey="id"
pagination={false}
size="small"
bordered
/>
</Col>
</Row>
</Modal>
);
};
export default SupplierSelectModal;

View File

@ -0,0 +1,171 @@
import React, { useEffect, useState } from "react";
import { Form, Button, Table, Select, Input, DatePicker, Space, message } from "antd";
import { SearchOutlined, ReloadOutlined, PlusOutlined } from "@ant-design/icons";
import CreateBlacklistModal from './components/CreateBlacklistModal'
// mock 数据和选项省略(和上面一致)
// mock 数据与服务(实际请换为接口)
const orgOptions = [
{ label: "请选择", value: "" },
{ label: "中远海运特运", value: "中远海运特运" },
{ label: "中远海运散运", value: "中远海运散运" },
{ label: "中远海运物流", value: "中远海运物流" },
{ label: "中远海运广州公司", value: "中远海运广州公司" },
];
const statusOptions = [
{ label: "请选择", value: "" },
{ label: "未开始", value: "未开始" },
{ label: "进行中", value: "进行中" },
{ label: "已结束", value: "已结束" },
];
// mock接口
const mockGetList = async ({ page = 1, pageSize = 10 }) => {
// 生成数据
const list = Array.from({ length: pageSize }).map((_, i) => {
const idx = (page - 1) * pageSize + i + 1;
return {
id: idx,
topic: ["中山市合创展包装材料有限公司等黑灰名单申请", "深圳市欧阳华斯电源有限公司等黑灰名单申请", "广东振兴塑胶机械有限公司等黑灰名单申请", "上海硕建建筑技术工程有限公司等黑灰名单申请"][i % 4],
org: ["中远海运特运", "中远海运散运", "中远海运广州公司"][i % 3],
dept: "采购部",
time: `2024-10-2${6 - (i % 3)} 9:00`,
status: ["未开始", "进行中", "已结束"][i % 3],
}
});
return new Promise(resolve => {
setTimeout(() => {
resolve({
code: 200,
data: list,
total: 188,
});
}, 300);
});
};
// 查询记录状态
const recordStatusOptions = [
{ label: "请选择", value: "" },
{ label: "未开始", value: "未开始" },
{ label: "进行中", value: "进行中" },
{ label: "已结束", value: "已结束" },
];
const blacklistManage: React.FC = () => {
const [form] = Form.useForm();
const [data, setData] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
const [createVisible, setCreateVisible] = useState(false);
// 查询接口
const getList = async (page = 1, pageSize = 10) => {
setLoading(true);
// 可传查询条件 form.getFieldsValue()
const { code, data: list, total } = await mockGetList({ page, pageSize });
if (code === 200) {
setData(list);
setPagination(prev => ({ ...prev, current: page, pageSize, total }));
}
setLoading(false);
};
// 查询
const handleSearch = () => {
setPagination({ ...pagination, current: 1 });
getList(1, pagination.pageSize);
};
// 重置
const handleReset = () => {
form.resetFields();
setPagination({ ...pagination, current: 1 });
getList(1, pagination.pageSize);
};
// 分页
const handleTableChange = (pageConf: any) => {
getList(pageConf.current, pageConf.pageSize);
};
// 发起申请
const handleApply = () => {
setCreateVisible(true)
};
useEffect(() => {
getList();
}, []);
const columns = [
{
title: "序号",
dataIndex: "index",
key: "index",
align: "center",
width: 60,
render: (_: any, __: any, idx: number) => idx + 1,
},
{ title: "申请主题", dataIndex: "topic", key: "topic", align: "center" },
{ title: "发起单位", dataIndex: "org", key: "org", align: "center" },
{ title: "发起部门", dataIndex: "dept", key: "dept", align: "center" },
{ title: "发起时间", dataIndex: "time", key: "time", align: "center" },
{ title: "审批记录状态", dataIndex: "status", key: "status", align: "center" },
{
title: "操作",
key: "option",
align: "center",
render: (record: any) => (
<a style={{ color: "#1677ff" }} onClick={() => message.error('123')}></a>
),
},
];
return (
<>
{/* 查询表单 */}
<Form form={form} layout="inline" style={{ marginBottom: 12 }}>
<Form.Item name="topic" label="申请主题">
<Input placeholder="请输入供应商名称关键字" style={{ width: 180 }} />
</Form.Item>
<Form.Item name="org" label="发起单位">
<Select style={{ width: 150 }} allowClear options={orgOptions} />
</Form.Item>
<Form.Item name="status" label="审批记录状态">
<Select style={{ width: 130 }} allowClear options={recordStatusOptions} />
</Form.Item>
<Form.Item name="time" label="发起时间">
<DatePicker.RangePicker style={{ width: 220 }} />
</Form.Item>
<Form.Item>
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}></Button>
<Button style={{ marginLeft: 8 }} icon={<ReloadOutlined />} onClick={handleReset}></Button>
</Form.Item>
</Form>
{/* 顶部按钮区 */}
<div style={{ marginBottom: 16 }}>
<Button type="primary" icon={<PlusOutlined />} onClick={handleApply}>
</Button>
</div>
{/* 表格 */}
<Table
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={{
...pagination,
showQuickJumper: true,
showSizeChanger: true,
showTotal: total => `${total}`,
}}
onChange={handleTableChange}
bordered
/>
<CreateBlacklistModal visible={createVisible} onCancel={() => setCreateVisible(false)} onOk={() => setCreateVisible(false)} />
</>
);
};
export default blacklistManage;