Files
fe_supplier_frontend/src/components/SupplierSelector/SupplierSelector.tsx
2025-07-15 09:07:43 +08:00

363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
import CategorySelector from '@/components/CategorySelector/CategorySelector';
import './SupplierSelector.less';
import { useIntl } from 'umi';
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 = [],
}) => {
const intl = useIntl();
// 表单实例,用于管理查询条件
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);
// 查询参数
const [queryParams, setQueryParams] = useState({
pageNo: 1,
pageSize: 10
});
/**
* 监听初始已选供应商变化,更新内部状态
*/
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);
};
/**
* 获取供应商列表数据
*/
const fetchTableData = async () => {
if (loading) return;
setLoading(true);
try {
// 调用API获取供应商列表
const response = await getSupplierPage(queryParams);
if (response && response.code === 200) {
// 请求成功,更新数据和分页信息
setTableListData(response.data.records.map((item: any) => ({
...item,
supplierName: item.name,
})));
setPagination({
current: queryParams.pageNo,
pageSize: queryParams.pageSize,
total: response.data.total || 0
});
} else {
// 请求失败,显示错误信息
message.error(response?.message || intl.formatMessage({ id: 'supplierTaskManage.message.fetchSupplierListFailed' }));
setTableListData([]);
setPagination({ current: 1, pageSize: 10, total: 0 });
}
} catch (error) {
// 处理异常情况
console.error('获取供应商列表出错:', error);
message.error(intl.formatMessage({ id: 'supplierTaskManage.message.fetchSupplierFailed' }));
setTableListData([]);
setPagination({ current: 1, pageSize: 10, total: 0 });
} finally {
setLoading(false);
}
};
/**
* 当查询参数变化时,加载数据
*/
useEffect(() => {
fetchTableData();
}, [queryParams]);
/**
* 组件初始挂载时,加载一次数据
*/
useEffect(() => {
const values = form.getFieldsValue();
setQueryParams(prev => ({
...prev,
...values,
pageNo: 1
}));
}, []); // 空依赖数组,只在组件挂载时执行一次
/**
* 处理查询表单提交
* @param {any} values - 表单值
*/
const handleSearch = (values: any) => {
setQueryParams(prev => ({
...prev,
...values,
pageNo: 1
}));
};
/**
* 重置查询表单并重新加载数据
*/
const handleReset = () => {
form.resetFields();
const values = form.getFieldsValue();
setQueryParams(prev => ({
...prev,
...values,
pageNo: 1
}));
};
/**
* 处理表格分页变化
* @param {any} paginationInfo - 分页信息
*/
const handleTableChange = (paginationInfo: any) => {
setQueryParams(prev => ({
...prev,
pageNo: paginationInfo.current,
pageSize: paginationInfo.pageSize
}));
};
// 表格列定义
const columns = [
{
title: intl.formatMessage({ id: 'supplierTaskManage.column.supplierName' }),
dataIndex: 'supplierName',
width: 120,
ellipsis: true,
render: (supplierName: string) => (
<Tooltip placement="topLeft" title={supplierName}>
{supplierName}
</Tooltip>
),
},
{
title: intl.formatMessage({ id: 'supplierTaskManage.column.socialCreditCode' }),
dataIndex: 'socialCreditCode',
width: 120,
ellipsis: true,
render: (socialCreditCode: string) => (
<Tooltip placement="topLeft" title={socialCreditCode}>
{socialCreditCode || '-'}
</Tooltip>
),
},
{
title: intl.formatMessage({ id: 'supplierTaskManage.column.categoryName' }),
dataIndex: 'categoryName',
width: 120,
ellipsis: true,
render: (categoryName: string) => (
<Tooltip placement="topLeft" title={categoryName}>
{categoryName || '-'}
</Tooltip>
),
},
];
return (
<div className="supplier-selector">
{/* 查询表单 */}
<Form layout="inline" form={form} onFinish={handleSearch} style={{ marginBottom: 30 }}>
<Form.Item name="name" label={intl.formatMessage({ id: 'supplierTaskManage.form.supplierName' })}>
<Input placeholder={intl.formatMessage({ id: 'supplierTaskManage.placeholder.supplierName' })} allowClear />
</Form.Item>
<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>
</Select>
</Form.Item>
<Form.Item name="categoryId" label={intl.formatMessage({ id: 'supplierTaskManage.form.category' })}>
<CategorySelector multiple={false} style={{ width: 150 }} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">{intl.formatMessage({ id: 'supplierTaskManage.button.search' })}</Button>
</Form.Item>
<Form.Item>
<Button onClick={handleReset}>{intl.formatMessage({ id: 'supplierTaskManage.button.reset' })}</Button>
</Form.Item>
</Form>
{/* 供应商选择区域 */}
<Row gutter={16} className="supplier-lists">
{/* 左侧待选列表 */}
<Col span={10}>
<div className="list-title">
{intl.formatMessage({ id: 'supplierTaskManage.text.availableSuppliers' })}
<span className="search-count">{intl.formatMessage({ id: 'supplierTaskManage.text.itemCount' }, { count: pagination.total })}</span>
</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">
{intl.formatMessage({ id: 'supplierTaskManage.text.selectedSuppliers' })}
<span className="search-count">{intl.formatMessage({ id: 'supplierTaskManage.text.itemCount' }, { count: chosenSuppliers.length })}</span>
</div>
<Table
rowSelection={{
type: 'checkbox',
onChange: setRightSelected,
selectedRowKeys: rightSelected
}}
columns={columns}
dataSource={chosenSuppliers}
rowKey="id"
pagination={false}
/>
</Col>
</Row>
</div>
);
};
export default SupplierSelector;