暂存评价任务新增

This commit is contained in:
linxd
2025-06-24 18:58:43 +08:00
parent 9a45e65db1
commit d7df286214
22 changed files with 1502 additions and 908 deletions

View File

@ -1,53 +1,25 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { Input, Button, Badge, Avatar, Space, Tabs, Checkbox, Row, Col, Card, Empty, Divider, Alert } from 'antd';
import { SearchOutlined, UserOutlined, TeamOutlined, CheckCircleOutlined } from '@ant-design/icons';
import { Table, Input, Button, Space, message } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { getUserList } from '@/servers/api/user';
import './EvaluateTaskPersonnelSelector.less';
const { TabPane } = Tabs;
const { Search } = Input;
/**
* 评价任务人员选择组件属性接口
* @interface EvaluateTaskPersonnelSelectorProps
* @property {Function} onSelect - 选择确认后的回调函数,返回所选人员数组
* @property {PersonnelItem[]} selectedPersonnel - 已选择的人员列表
* @property {API.PersonnelItem[]} selectedPersonnel - 已选择的人员列表
*/
interface EvaluateTaskPersonnelSelectorProps {
onSelect: (personnel: PersonnelItem[]) => void;
selectedPersonnel?: PersonnelItem[];
}
/**
* 人员信息接口
* @interface PersonnelItem
* @property {string} id - 人员唯一标识
* @property {string} name - 人员姓名
* @property {string} department - 所属部门
* @property {string} position - 职位信息
* @property {boolean} selected - 是否被选中
*/
export interface PersonnelItem {
id: string;
name: string;
department: string;
position?: string;
selected?: boolean;
}
/**
* 部门信息接口
* @interface Department
* @property {string} id - 部门ID
* @property {string} name - 部门名称
*/
interface Department {
id: string;
name: string;
onSelect: (personnel: API.PersonnelItem[]) => void; // 选择确认后的回调函数
selectedPersonnel?: API.PersonnelItem[]; // 已选择的人员列表(用于回显)
}
/**
* 评价任务人员选择组件
* 用于在评价任务管理中选择评价人员,按部门分类展示
* 用于在评价任务管理中选择评价人员,显示为简单表格
*
* @component
* @example
@ -64,302 +36,147 @@ const EvaluateTaskPersonnelSelector: React.FC<EvaluateTaskPersonnelSelectorProps
}) => {
// 搜索关键词
const [keyword, setKeyword] = useState<string>('');
// 所有可选人员列表
const [personnel, setPersonnel] = useState<PersonnelItem[]>([]);
const [personnel, setPersonnel] = useState<API.PersonnelItem[]>([]);
// 已选择人员ID列表
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
// 当前选中的部门Tab
const [activeTab, setActiveTab] = useState<string>('1');
// 加载状态
const [loading, setLoading] = useState<boolean>(false);
// 初始化时根据传入的selectedPersonnel设置选中状态
useEffect(() => {
// 直接使用传入的selectedPersonnel更新选中状态
// 如果为空数组,则清空选择
const newSelectedIds = selectedPersonnel?.map(item => item.id) || [];
setSelectedKeys(newSelectedIds);
}, [selectedPersonnel]); // 依赖selectedPersonnel而不是selectedPersonnelIds
/**
* 部门数据列表
* 实际项目中可通过API获取
* 从API获取用户列表数据
*/
const departments: Department[] = useMemo(() => [
{ id: '1', name: '采购部' },
{ id: '2', name: '技术部' },
{ id: '3', name: '质量部' },
{ id: '4', name: '生产部' },
{ id: '5', name: '财务部' },
], []);
const fetchPersonnelData = useCallback(async () => {
setLoading(true);
try {
const params: API.UserListRequest = {
basePageRequest: {
pageNo: 1,
pageSize: 100, // 获取足够多的数据
},
keyword: keyword || undefined,
};
const response = await getUserList(params);
if (response && response.code === 200 && response.data) {
const users = response.data as API.UserItem[];
// 转换API返回的用户数据为组件所需格式
const personnelData: API.PersonnelItem[] = users.map((user) => ({
id: user.userId, // 用户ID
name: user.userName, // 用户名称
department: user.userDept, // 用户部门
position: '', // API中没有提供职位信息
}));
setPersonnel(personnelData);
} else {
message.error(response?.message || '获取用户列表失败');
}
} catch (error) {
console.error('获取人员数据失败:', error);
message.error('获取人员数据失败,请稍后重试');
} finally {
setLoading(false);
}
}, [keyword]); // 移除selectedPersonnelIds依赖
/**
* 模拟获取人员数据
* 实际项目中替换为API调用
*/
const fetchPersonnelData = useCallback(() => {
// 模拟API调用获取评价人员
const mockPersonnel: PersonnelItem[] = [
{ id: '1', name: '张三', department: '采购部', position: '采购经理' },
{ id: '2', name: '李四', department: '技术部', position: '技术专家' },
{ id: '3', name: '王五', department: '质量部', position: '质量工程师' },
{ id: '4', name: '赵六', department: '采购部', position: '高级采购' },
{ id: '5', name: '钱七', department: '技术部', position: '部门经理' },
{ id: '6', name: '孙八', department: '质量部', position: '质量总监' },
{ id: '7', name: '周九', department: '生产部', position: '生产主管' },
{ id: '8', name: '吴十', department: '财务部', position: '财务经理' },
{ id: '9', name: '郑十一', department: '采购部', position: '采购专员' },
{ id: '10', name: '王十二', department: '技术部', position: '工程师' },
{ id: '11', name: '刘十三', department: '财务部', position: '财务专员' },
{ id: '12', name: '陈十四', department: '生产部', position: '技术员' },
];
setPersonnel(mockPersonnel);
}, []);
/**
* 初始化人员数据和已选状态
* 初始化人员数据
*/
useEffect(() => {
// 加载人员数据(模拟API请求
// 加载人员数据API请求
fetchPersonnelData();
// 设置已选人员
if (selectedPersonnel && selectedPersonnel.length > 0) {
const selectedIds = selectedPersonnel.map(item => item.id);
setSelectedKeys(selectedIds);
}
}, [fetchPersonnelData, selectedPersonnel]);
}, [fetchPersonnelData]);
/**
* 处理搜索
* @param {string} value - 搜索关键词
*/
const handleSearch = useCallback((value: string) => {
const handleSearch = (value: string) => {
setKeyword(value);
}, []);
};
/**
* 处理Tab切换
* @param {string} key - 选中的Tab键值
* 处理行选择变化
* @param {string[]} selectedRowKeys - 选中的行keys
* @param {API.PersonnelItem[]} selectedRows - 选中的行数据
*/
const handleTabChange = useCallback((key: string) => {
setActiveTab(key);
}, []);
const handleSelectChange = (selectedRowKeys: React.Key[], selectedRows: API.PersonnelItem[]) => {
setSelectedKeys(selectedRowKeys as string[]);
};
/**
* 处理单个人员选择
* @param {string} personnelId - 人员ID
* @param {boolean} checked - 是否选中
* 处理确认选择
*/
const handleSelect = useCallback((personnelId: string, checked: boolean) => {
setSelectedKeys(prevKeys => {
if (checked) {
return [...prevKeys, personnelId];
} else {
return prevKeys.filter(id => id !== personnelId);
}
});
}, []);
const handleConfirm = () => {
// 根据选中的ID筛选出完整的人员数据
const selectedData = personnel.filter(item => selectedKeys.includes(item.id));
/**
* 处理部门人员全选
* @param {string} deptId - 部门ID
* @param {boolean} checked - 是否全选
*/
const handleSelectAll = useCallback((deptId: string, checked: boolean) => {
const deptName = departments.find(d => d.id === deptId)?.name || '';
const deptPersonnel = personnel.filter(p =>
p.department === deptName &&
(keyword ? p.name.includes(keyword) : true)
);
const deptPersonnelIds = deptPersonnel.map(p => p.id);
// 回调传递给父组件
onSelect(selectedData);
};
setSelectedKeys(prevKeys => {
if (checked) {
// 添加该部门所有人员
const newKeys = new Set([...prevKeys, ...deptPersonnelIds]);
return Array.from(newKeys);
} else {
// 移除该部门所有人员
return prevKeys.filter(id => !deptPersonnelIds.includes(id));
}
});
}, [departments, keyword, personnel]);
// 表格列定义
const columns = [
{
title: '姓名', // 列标题
dataIndex: 'name', // 数据字段名
key: 'name', // 列唯一标识
},
{
title: '部门',
dataIndex: 'department',
key: 'department',
},
];
/**
* 确认选择
* 将选中的人员ID列表转换为人员对象列表
*/
const handleConfirm = useCallback(() => {
const selectedItems = personnel.filter(item => selectedKeys.includes(item.id));
onSelect(selectedItems);
}, [onSelect, personnel, selectedKeys]);
/**
* 计算各部门已选人数
* @param {string} deptId - 部门ID
* @returns {number} - 已选人数
*/
const getSelectedCountByDept = useCallback((deptId: string) => {
const deptName = departments.find(d => d.id === deptId)?.name || '';
const deptPersonnel = personnel.filter(p => p.department === deptName);
const selectedCount = deptPersonnel.filter(p => selectedKeys.includes(p.id)).length;
return selectedCount;
}, [departments, personnel, selectedKeys]);
/**
* 渲染Tab标签
* @param {Department} dept - 部门信息
* @returns {React.ReactNode} - 渲染结果
*/
const renderTabTitle = useCallback((dept: Department) => {
const selectedCount = getSelectedCountByDept(dept.id);
return (
<Badge
count={selectedCount > 0 ? selectedCount : 0}
size="small"
offset={[5, -3]}
style={{ backgroundColor: selectedCount > 0 ? '#1890ff' : '#d9d9d9' }}
>
<span>{dept.name}</span>
</Badge>
);
}, [getSelectedCountByDept]);
/**
* 渲染部门下的人员列表
* @param {string} deptId - 部门ID
* @returns {React.ReactNode} - 渲染结果
*/
const renderDepartmentPersonnel = useCallback((deptId: string) => {
// 获取部门名称
const deptName = departments.find(d => d.id === deptId)?.name || '';
// 过滤出当前部门且符合搜索条件的人员
const deptPersonnel = personnel.filter(p =>
p.department === deptName &&
(keyword ? p.name.includes(keyword) : true)
);
// 检查该部门是否全部选中
const deptPersonnelIds = deptPersonnel.map(p => p.id);
const isAllSelected = deptPersonnelIds.length > 0 && deptPersonnelIds.every(id => selectedKeys.includes(id));
// 如果没有符合条件的人员,显示空状态
if (deptPersonnel.length === 0) {
return <Empty description="暂无符合条件的人员" />;
}
return (
<div className="department-personnel">
<Card className="personnel-card">
<div className="department-header">
<Space>
<TeamOutlined />
<span className="department-title">{deptName}</span>
<Divider type="vertical" />
<Checkbox
checked={isAllSelected}
onChange={(e) => handleSelectAll(deptId, e.target.checked)}
>
</Checkbox>
<span className="department-count">
({deptPersonnel.length})
</span>
</Space>
</div>
<Divider style={{ margin: '12px 0' }} />
<Row gutter={[16, 16]} className="personnel-list">
{deptPersonnel.map(person => (
<Col span={8} key={person.id}>
<Card
size="small"
className={`personnel-item ${selectedKeys.includes(person.id) ? 'selected' : ''}`}
>
<Checkbox
checked={selectedKeys.includes(person.id)}
onChange={(e) => handleSelect(person.id, e.target.checked)}
>
<Space>
<Avatar
icon={<UserOutlined />}
size="small"
className={selectedKeys.includes(person.id) ? 'selected-avatar' : ''}
/>
<div className="personnel-info">
<div className="personnel-name">
{person.name}
{selectedKeys.includes(person.id) && (
<CheckCircleOutlined className="selected-icon" />
)}
</div>
<div className="personnel-position">{person.position}</div>
</div>
</Space>
</Checkbox>
</Card>
</Col>
))}
</Row>
</Card>
</div>
);
}, [departments, handleSelect, handleSelectAll, keyword, personnel, selectedKeys]);
// 使用useMemo优化部门Tab的渲染
const departmentTabs = useMemo(() => {
return departments.map(dept => (
<TabPane
tab={renderTabTitle(dept)}
key={dept.id}
>
{renderDepartmentPersonnel(dept.id)}
</TabPane>
));
}, [departments, renderDepartmentPersonnel, renderTabTitle]);
return (
<div className="evaluate-task-personnel-selector">
{/* 顶部搜索和统计区域 */}
<div className="selector-header">
<Search
placeholder="请输入人员姓名搜索"
allowClear
enterButton={<><SearchOutlined /> </>}
onSearch={handleSearch}
onChange={(e) => setKeyword(e.target.value)}
className="search-input"
/>
<div className="search-bar">
<Search
placeholder="请输入姓名搜索"
onSearch={handleSearch}
enterButton={<Button icon={<SearchOutlined />}></Button>}
/>
</div>
<div className="selected-count">
<Badge
count={selectedKeys.length}
showZero
overflowCount={99}
style={{ backgroundColor: '#1890ff' }}
>
<span className="count-label"></span>
</Badge>
: <span className="count">{selectedKeys.length}</span>
</div>
</div>
{/* 显示已选人员数量的提示 */}
{selectedKeys.length > 0 && (
<Alert
message={`已选择 ${selectedKeys.length} 名评价人员`}
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{/* 部门分类标签页 */}
<Tabs
activeKey={activeTab}
onChange={handleTabChange}
className="department-tabs"
>
{departmentTabs}
</Tabs>
{/* 底部确认按钮区域 */}
<Table
rowSelection={{
selectedRowKeys: selectedKeys,
onChange: handleSelectChange,
}}
columns={columns}
dataSource={personnel}
rowKey="id"
size="small"
loading={loading}
pagination={{ pageSize: 10 }}
/>
<div className="selector-footer">
<Button type="primary" onClick={handleConfirm} size="large">
({selectedKeys.length})
</Button>
<Space>
<Button onClick={() => setSelectedKeys([])}></Button>
<Button type="primary" onClick={handleConfirm}>
</Button>
</Space>
</div>
</div>
);