2025-06-24 14:49:45 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Input, Select, Row, Col, Table, Button, Form, Tooltip, message } from 'antd';
|
|
|
|
|
import { RightOutlined, LeftOutlined } from '@ant-design/icons';
|
|
|
|
|
import { getSupplierPage } from '@/servers/api/supplier';
|
2025-06-24 18:58:43 +08:00
|
|
|
|
import CategorySelector from '@/components/CategorySelector/CategorySelector';
|
2025-06-24 14:49:45 +08:00
|
|
|
|
import './SupplierSelector.less';
|
2025-07-03 10:21:55 +08:00
|
|
|
|
import { useIntl } from 'umi';
|
2025-06-24 14:49:45 +08:00
|
|
|
|
|
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SupplierSelector 组件的属性接口
|
|
|
|
|
* @interface SupplierSelectorProps
|
|
|
|
|
* @property {Function} [onSelect] - 当选择供应商发生变化时的回调函数
|
|
|
|
|
* @property {any[]} [selectedSuppliers] - 初始已选择的供应商列表
|
|
|
|
|
*/
|
|
|
|
|
interface SupplierSelectorProps {
|
|
|
|
|
onSelect?: (selected: any[]) => void;
|
|
|
|
|
selectedSuppliers?: any[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 供应商数据项接口,对应后端SupplierPageVo
|
|
|
|
|
* @interface SupplierItem
|
|
|
|
|
* @property {string} id - 供应商ID
|
|
|
|
|
* @property {string} name - 供应商名称
|
|
|
|
|
* @property {string} [deptId] - 部门ID
|
|
|
|
|
* @property {string} [deptName] - 部门名称
|
|
|
|
|
* @property {string} [companyName] - 公司名称
|
|
|
|
|
* @property {string} [categoryName] - 所属品类
|
|
|
|
|
* @property {string} [levelName] - 最新评价等级
|
|
|
|
|
* @property {Date} [admissionTime] - 准入时间
|
|
|
|
|
* @property {Date} [evaluationTime] - 最新评价时间
|
|
|
|
|
* @property {any} [key: string] - 其他属性
|
|
|
|
|
*/
|
|
|
|
|
interface SupplierItem {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
deptId?: string;
|
|
|
|
|
deptName?: string;
|
|
|
|
|
companyName?: string;
|
|
|
|
|
categoryName?: string;
|
|
|
|
|
levelName?: string;
|
|
|
|
|
admissionTime?: Date;
|
|
|
|
|
evaluationTime?: Date;
|
|
|
|
|
[key: string]: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 供应商选择器组件
|
|
|
|
|
* 用于从系统中选择供应商数据,支持搜索过滤、分页加载、双列表展示
|
|
|
|
|
*
|
|
|
|
|
* @param {SupplierSelectorProps} props - 组件属性
|
|
|
|
|
* @returns {React.ReactElement} 供应商选择器组件
|
|
|
|
|
*/
|
|
|
|
|
const SupplierSelector: React.FC<SupplierSelectorProps> = ({
|
|
|
|
|
onSelect,
|
|
|
|
|
selectedSuppliers: initialSelectedSuppliers = [],
|
|
|
|
|
}) => {
|
2025-07-03 10:21:55 +08:00
|
|
|
|
const intl = useIntl();
|
2025-06-24 14:49:45 +08:00
|
|
|
|
// 表单实例,用于管理查询条件
|
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
|
|
|
|
|
|
// 待选供应商数据列表
|
|
|
|
|
const [tableListData, setTableListData] = useState<SupplierItem[]>([]);
|
|
|
|
|
|
|
|
|
|
// 分页信息
|
|
|
|
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 });
|
|
|
|
|
|
|
|
|
|
// 加载状态
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
// 左侧待选列表中已选中的项的ID
|
|
|
|
|
const [leftSelected, setLeftSelected] = useState<React.Key[]>([]);
|
|
|
|
|
|
|
|
|
|
// 右侧已选列表中选中的项的ID
|
|
|
|
|
const [rightSelected, setRightSelected] = useState<React.Key[]>([]);
|
|
|
|
|
|
|
|
|
|
// 最终已选择的供应商列表
|
|
|
|
|
const [chosenSuppliers, setChosenSuppliers] = useState<SupplierItem[]>(initialSelectedSuppliers);
|
|
|
|
|
|
2025-06-24 18:58:43 +08:00
|
|
|
|
// 查询参数
|
|
|
|
|
const [queryParams, setQueryParams] = useState({
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 10
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-24 14:49:45 +08:00
|
|
|
|
/**
|
|
|
|
|
* 监听初始已选供应商变化,更新内部状态
|
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setChosenSuppliers(initialSelectedSuppliers);
|
|
|
|
|
}, [initialSelectedSuppliers]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 合并供应商列表并去重
|
|
|
|
|
* @param {SupplierItem[]} suppliers - 原有的供应商列表
|
|
|
|
|
* @param {SupplierItem[]} selected - 新选择的供应商列表
|
|
|
|
|
* @returns {SupplierItem[]} 合并后的供应商列表,去除重复项
|
|
|
|
|
*/
|
|
|
|
|
const filteredData = (suppliers: SupplierItem[], selected: SupplierItem[]) => {
|
|
|
|
|
// 创建已有供应商ID的集合,用于快速查找
|
|
|
|
|
const ids = new Set(suppliers.map((item: SupplierItem) => item.id));
|
|
|
|
|
// 过滤出未在原列表中出现的供应商
|
|
|
|
|
const newSelected = selected.filter((item: SupplierItem) => !ids.has(item.id));
|
|
|
|
|
// 合并列表
|
|
|
|
|
return [...suppliers, ...newSelected];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将左侧选中的供应商移动到右侧已选列表
|
|
|
|
|
*/
|
|
|
|
|
const moveToRight = () => {
|
|
|
|
|
// 获取左侧选中的供应商数据
|
|
|
|
|
const selected = tableListData.filter((item: SupplierItem) => leftSelected.includes(item.id));
|
|
|
|
|
// 合并到已选列表并去重
|
|
|
|
|
const chosenSuppliersNew = filteredData(chosenSuppliers, selected);
|
|
|
|
|
// 更新已选列表
|
|
|
|
|
setChosenSuppliers(chosenSuppliersNew);
|
|
|
|
|
// 清空左侧选择状态
|
|
|
|
|
setLeftSelected([]);
|
|
|
|
|
// 触发外部回调
|
|
|
|
|
onSelect?.(chosenSuppliersNew);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将右侧选中的供应商从已选列表中移除
|
|
|
|
|
*/
|
|
|
|
|
const moveToLeft = () => {
|
|
|
|
|
// 过滤掉右侧选中的供应商
|
|
|
|
|
const remaining = chosenSuppliers.filter((item: SupplierItem) => !rightSelected.includes(item.id));
|
|
|
|
|
// 更新已选列表
|
|
|
|
|
setChosenSuppliers(remaining);
|
|
|
|
|
// 清空右侧选择状态
|
|
|
|
|
setRightSelected([]);
|
|
|
|
|
// 触发外部回调
|
|
|
|
|
onSelect?.(remaining);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取供应商列表数据
|
|
|
|
|
*/
|
2025-06-24 18:58:43 +08:00
|
|
|
|
const fetchTableData = async () => {
|
|
|
|
|
if (loading) return;
|
|
|
|
|
|
2025-06-24 14:49:45 +08:00
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
// 调用API获取供应商列表
|
2025-06-24 18:58:43 +08:00
|
|
|
|
const response = await getSupplierPage(queryParams);
|
2025-06-24 14:49:45 +08:00
|
|
|
|
|
|
|
|
|
if (response && response.code === 200) {
|
|
|
|
|
// 请求成功,更新数据和分页信息
|
2025-06-26 18:14:41 +08:00
|
|
|
|
setTableListData(response.data.records.map((item: any) => ({
|
|
|
|
|
...item,
|
|
|
|
|
supplierName: item.name,
|
|
|
|
|
})));
|
2025-06-24 14:49:45 +08:00
|
|
|
|
setPagination({
|
2025-06-24 18:58:43 +08:00
|
|
|
|
current: queryParams.pageNo,
|
|
|
|
|
pageSize: queryParams.pageSize,
|
2025-06-24 14:49:45 +08:00
|
|
|
|
total: response.data.total || 0
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 请求失败,显示错误信息
|
2025-07-03 10:21:55 +08:00
|
|
|
|
message.error(response?.message || intl.formatMessage({ id: 'supplierTaskManage.message.fetchSupplierListFailed' }));
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setTableListData([]);
|
|
|
|
|
setPagination({ current: 1, pageSize: 10, total: 0 });
|
2025-06-24 14:49:45 +08:00
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// 处理异常情况
|
|
|
|
|
console.error('获取供应商列表出错:', error);
|
2025-07-03 10:21:55 +08:00
|
|
|
|
message.error(intl.formatMessage({ id: 'supplierTaskManage.message.fetchSupplierFailed' }));
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setTableListData([]);
|
|
|
|
|
setPagination({ current: 1, pageSize: 10, total: 0 });
|
2025-06-24 14:49:45 +08:00
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2025-06-24 18:58:43 +08:00
|
|
|
|
* 当查询参数变化时,加载数据
|
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchTableData();
|
|
|
|
|
}, [queryParams]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 组件初始挂载时,加载一次数据
|
2025-06-24 14:49:45 +08:00
|
|
|
|
*/
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const values = form.getFieldsValue();
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setQueryParams(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
...values,
|
|
|
|
|
pageNo: 1
|
|
|
|
|
}));
|
|
|
|
|
}, []); // 空依赖数组,只在组件挂载时执行一次
|
2025-06-24 14:49:45 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理查询表单提交
|
|
|
|
|
* @param {any} values - 表单值
|
|
|
|
|
*/
|
|
|
|
|
const handleSearch = (values: any) => {
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setQueryParams(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
...values,
|
|
|
|
|
pageNo: 1
|
|
|
|
|
}));
|
2025-06-24 14:49:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重置查询表单并重新加载数据
|
|
|
|
|
*/
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
form.resetFields();
|
|
|
|
|
const values = form.getFieldsValue();
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setQueryParams(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
...values,
|
|
|
|
|
pageNo: 1
|
|
|
|
|
}));
|
2025-06-24 14:49:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理表格分页变化
|
|
|
|
|
* @param {any} paginationInfo - 分页信息
|
|
|
|
|
*/
|
|
|
|
|
const handleTableChange = (paginationInfo: any) => {
|
2025-06-24 18:58:43 +08:00
|
|
|
|
setQueryParams(prev => ({
|
|
|
|
|
...prev,
|
|
|
|
|
pageNo: paginationInfo.current,
|
|
|
|
|
pageSize: paginationInfo.pageSize
|
|
|
|
|
}));
|
2025-06-24 14:49:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 表格列定义
|
|
|
|
|
const columns = [
|
|
|
|
|
{
|
2025-07-03 10:21:55 +08:00
|
|
|
|
title: intl.formatMessage({ id: 'supplierTaskManage.column.supplierName' }),
|
2025-06-26 18:14:41 +08:00
|
|
|
|
dataIndex: 'supplierName',
|
2025-06-24 14:49:45 +08:00
|
|
|
|
ellipsis: true,
|
2025-06-26 18:14:41 +08:00
|
|
|
|
render: (supplierName: string) => (
|
|
|
|
|
<Tooltip placement="topLeft" title={supplierName}>
|
|
|
|
|
{supplierName}
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</Tooltip>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
2025-07-03 10:21:55 +08:00
|
|
|
|
title: intl.formatMessage({ id: 'supplierTaskManage.column.socialCreditCode' }),
|
2025-06-26 18:14:41 +08:00
|
|
|
|
dataIndex: 'socialCreditCode',
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
render: (socialCreditCode: string) => (
|
|
|
|
|
<Tooltip placement="topLeft" title={socialCreditCode}>
|
|
|
|
|
{socialCreditCode || '-'}
|
|
|
|
|
</Tooltip>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
2025-07-03 10:21:55 +08:00
|
|
|
|
title: intl.formatMessage({ id: 'supplierTaskManage.column.categoryName' }),
|
2025-06-24 14:49:45 +08:00
|
|
|
|
dataIndex: 'categoryName',
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
render: (categoryName: string) => (
|
|
|
|
|
<Tooltip placement="topLeft" title={categoryName}>
|
|
|
|
|
{categoryName || '-'}
|
|
|
|
|
</Tooltip>
|
|
|
|
|
),
|
2025-06-24 18:58:43 +08:00
|
|
|
|
},
|
2025-06-26 18:14:41 +08:00
|
|
|
|
|
2025-06-24 18:58:43 +08:00
|
|
|
|
];
|
2025-06-24 14:49:45 +08:00
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="supplier-selector">
|
|
|
|
|
{/* 查询表单 */}
|
2025-06-24 18:58:43 +08:00
|
|
|
|
<Form layout="inline" form={form} onFinish={handleSearch} style={{ marginBottom: 30 }}>
|
2025-07-03 10:21:55 +08:00
|
|
|
|
<Form.Item name="name" label={intl.formatMessage({ id: 'supplierTaskManage.form.supplierName' })}>
|
|
|
|
|
<Input placeholder={intl.formatMessage({ id: 'supplierTaskManage.placeholder.supplierName' })} allowClear />
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
2025-07-03 10:21:55 +08:00
|
|
|
|
<Form.Item name="deptId" label={intl.formatMessage({ id: 'supplierTaskManage.form.department' })}>
|
|
|
|
|
<Select placeholder={intl.formatMessage({ id: 'supplierTaskManage.placeholder.selectDepartment' })} allowClear style={{ width: 150 }}>
|
|
|
|
|
<Option value="1">{intl.formatMessage({ id: 'supplierTaskManage.department.purchase' })}</Option>
|
|
|
|
|
<Option value="2">{intl.formatMessage({ id: 'supplierTaskManage.department.technology' })}</Option>
|
|
|
|
|
<Option value="3">{intl.formatMessage({ id: 'supplierTaskManage.department.quality' })}</Option>
|
2025-06-24 18:58:43 +08:00
|
|
|
|
</Select>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
2025-07-03 10:21:55 +08:00
|
|
|
|
<Form.Item name="categoryId" label={intl.formatMessage({ id: 'supplierTaskManage.form.category' })}>
|
2025-06-24 18:58:43 +08:00
|
|
|
|
<CategorySelector multiple={false} style={{ width: 150 }} />
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item>
|
2025-07-03 10:21:55 +08:00
|
|
|
|
<Button type="primary" htmlType="submit">{intl.formatMessage({ id: 'supplierTaskManage.button.search' })}</Button>
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
<Form.Item>
|
2025-07-03 10:21:55 +08:00
|
|
|
|
<Button onClick={handleReset}>{intl.formatMessage({ id: 'supplierTaskManage.button.reset' })}</Button>
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
{/* 供应商选择区域 */}
|
|
|
|
|
<Row gutter={16} className="supplier-lists">
|
|
|
|
|
{/* 左侧待选列表 */}
|
|
|
|
|
<Col span={10}>
|
|
|
|
|
<div className="list-title">
|
2025-07-03 10:21:55 +08:00
|
|
|
|
{intl.formatMessage({ id: 'supplierTaskManage.text.availableSuppliers' })}
|
|
|
|
|
<span className="search-count">{intl.formatMessage({ id: 'supplierTaskManage.text.itemCount' }, { count: pagination.total })}</span>
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
<Table
|
|
|
|
|
rowSelection={{
|
|
|
|
|
type: 'checkbox',
|
|
|
|
|
onChange: setLeftSelected,
|
|
|
|
|
selectedRowKeys: leftSelected
|
|
|
|
|
}}
|
|
|
|
|
rowKey="id"
|
|
|
|
|
dataSource={tableListData}
|
|
|
|
|
columns={columns}
|
|
|
|
|
loading={loading}
|
|
|
|
|
pagination={pagination}
|
|
|
|
|
onChange={handleTableChange}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
{/* 中间操作按钮 */}
|
|
|
|
|
<Col span={4} className="transfer-buttons">
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<RightOutlined />}
|
|
|
|
|
onClick={moveToRight}
|
|
|
|
|
disabled={leftSelected.length === 0}
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
icon={<LeftOutlined />}
|
|
|
|
|
onClick={moveToLeft}
|
|
|
|
|
disabled={rightSelected.length === 0}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
|
|
|
|
|
{/* 右侧已选列表 */}
|
|
|
|
|
<Col span={10}>
|
|
|
|
|
<div className="list-title">
|
2025-07-03 10:21:55 +08:00
|
|
|
|
{intl.formatMessage({ id: 'supplierTaskManage.text.selectedSuppliers' })}
|
|
|
|
|
<span className="search-count">{intl.formatMessage({ id: 'supplierTaskManage.text.itemCount' }, { count: chosenSuppliers.length })}</span>
|
2025-06-24 14:49:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
<Table
|
|
|
|
|
rowSelection={{
|
|
|
|
|
type: 'checkbox',
|
|
|
|
|
onChange: setRightSelected,
|
|
|
|
|
selectedRowKeys: rightSelected
|
|
|
|
|
}}
|
|
|
|
|
columns={columns}
|
|
|
|
|
dataSource={chosenSuppliers}
|
|
|
|
|
rowKey="id"
|
|
|
|
|
pagination={false}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default SupplierSelector;
|