diff --git a/mock.zip b/mock.zip new file mode 100644 index 0000000..3920692 Binary files /dev/null and b/mock.zip differ diff --git a/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx b/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx index 06caa1c..f033fac 100644 --- a/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx +++ b/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +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 './EvaluateTaskPersonnelSelector.less'; @@ -75,33 +75,19 @@ const EvaluateTaskPersonnelSelector: React.FC [ { id: '1', name: '采购部' }, { id: '2', name: '技术部' }, { id: '3', name: '质量部' }, { id: '4', name: '生产部' }, { id: '5', name: '财务部' }, - ]; - - /** - * 初始化人员数据和已选状态 - */ - useEffect(() => { - // 加载人员数据(模拟API请求) - fetchPersonnelData(); - - // 设置已选人员 - if (selectedPersonnel && selectedPersonnel.length > 0) { - const selectedIds = selectedPersonnel.map(item => item.id); - setSelectedKeys(selectedIds); - } - }, [selectedPersonnel]); + ], []); /** * 模拟获取人员数据 * 实际项目中替换为API调用 */ - const fetchPersonnelData = () => { + const fetchPersonnelData = useCallback(() => { // 模拟API调用获取评价人员 const mockPersonnel: PersonnelItem[] = [ { id: '1', name: '张三', department: '采购部', position: '采购经理' }, @@ -119,46 +105,59 @@ const EvaluateTaskPersonnelSelector: React.FC { + // 加载人员数据(模拟API请求) + fetchPersonnelData(); + + // 设置已选人员 + if (selectedPersonnel && selectedPersonnel.length > 0) { + const selectedIds = selectedPersonnel.map(item => item.id); + setSelectedKeys(selectedIds); + } + }, [fetchPersonnelData, selectedPersonnel]); /** * 处理搜索 * @param {string} value - 搜索关键词 */ - const handleSearch = (value: string) => { + const handleSearch = useCallback((value: string) => { setKeyword(value); - }; + }, []); /** * 处理Tab切换 * @param {string} key - 选中的Tab键值 */ - const handleTabChange = (key: string) => { + const handleTabChange = useCallback((key: string) => { setActiveTab(key); - }; + }, []); /** * 处理单个人员选择 * @param {string} personnelId - 人员ID * @param {boolean} checked - 是否选中 */ - const handleSelect = (personnelId: string, checked: boolean) => { - let newSelectedKeys = [...selectedKeys]; - if (checked) { - newSelectedKeys.push(personnelId); - } else { - newSelectedKeys = newSelectedKeys.filter(id => id !== personnelId); - } - - setSelectedKeys(newSelectedKeys); - }; + const handleSelect = useCallback((personnelId: string, checked: boolean) => { + setSelectedKeys(prevKeys => { + if (checked) { + return [...prevKeys, personnelId]; + } else { + return prevKeys.filter(id => id !== personnelId); + } + }); + }, []); /** * 处理部门人员全选 * @param {string} deptId - 部门ID * @param {boolean} checked - 是否全选 */ - const handleSelectAll = (deptId: string, checked: boolean) => { + const handleSelectAll = useCallback((deptId: string, checked: boolean) => { const deptName = departments.find(d => d.id === deptId)?.name || ''; const deptPersonnel = personnel.filter(p => p.department === deptName && @@ -166,38 +165,64 @@ const EvaluateTaskPersonnelSelector: React.FC p.id); - let newSelectedKeys = [...selectedKeys]; - - if (checked) { - // 添加该部门所有人员 - deptPersonnelIds.forEach(id => { - if (!newSelectedKeys.includes(id)) { - newSelectedKeys.push(id); - } - }); - } else { - // 移除该部门所有人员 - newSelectedKeys = newSelectedKeys.filter(id => !deptPersonnelIds.includes(id)); - } - - setSelectedKeys(newSelectedKeys); - }; + 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]); /** * 确认选择 * 将选中的人员ID列表转换为人员对象列表 */ - const handleConfirm = () => { + 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 ( + 0 ? selectedCount : 0} + size="small" + offset={[5, -3]} + style={{ backgroundColor: selectedCount > 0 ? '#1890ff' : '#d9d9d9' }} + > + {dept.name} + + ); + }, [getSelectedCountByDept]); /** * 渲染部门下的人员列表 * @param {string} deptId - 部门ID * @returns {React.ReactNode} - 渲染结果 */ - const renderDepartmentPersonnel = (deptId: string) => { + const renderDepartmentPersonnel = useCallback((deptId: string) => { // 获取部门名称 const deptName = departments.find(d => d.id === deptId)?.name || ''; @@ -273,38 +298,19 @@ const EvaluateTaskPersonnelSelector: React.FC ); - }; + }, [departments, handleSelect, handleSelectAll, keyword, personnel, selectedKeys]); - /** - * 计算各部门已选人数 - * @param {string} deptId - 部门ID - * @returns {number} - 已选人数 - */ - const getSelectedCountByDept = (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; - }; - - /** - * 渲染Tab标签 - * @param {Department} dept - 部门信息 - * @returns {React.ReactNode} - 渲染结果 - */ - const renderTabTitle = (dept: Department) => { - const selectedCount = getSelectedCountByDept(dept.id); - return ( - 0 ? selectedCount : 0} - size="small" - offset={[5, -3]} - style={{ backgroundColor: selectedCount > 0 ? '#1890ff' : '#d9d9d9' }} + // 使用useMemo优化部门Tab的渲染 + const departmentTabs = useMemo(() => { + return departments.map(dept => ( + - {dept.name} - - ); - }; + {renderDepartmentPersonnel(dept.id)} + + )); + }, [departments, renderDepartmentPersonnel, renderTabTitle]); return (
@@ -346,14 +352,7 @@ const EvaluateTaskPersonnelSelector: React.FC - {departments.map(dept => ( - - {renderDepartmentPersonnel(dept.id)} - - ))} + {departmentTabs} {/* 底部确认按钮区域 */} diff --git a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx index cae8488..8a855c6 100644 --- a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx +++ b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx @@ -7,15 +7,11 @@ import { Select, Form, InputNumber, - Upload, message, - Divider, - Switch, } from 'antd'; import { PlusOutlined, MinusCircleOutlined, - UploadOutlined, PlusCircleOutlined, DeleteOutlined, } from '@ant-design/icons'; @@ -24,14 +20,9 @@ import { StarLevel, StarLevelText } from '@/dicts/supplierTemplateDict'; import { generateUUID } from '@/utils/utils'; import './EvaluateTemplateTable.less'; -// 注意:这里我们使用SupplierEvaluate命名空间中的类型 -// 类型定义已移动到 src/typings.d.ts 文件中 - const { Option } = Select; const { TextArea } = Input; -// 移除类型定义,使用全局命名空间中的类型 - interface EvaluateTemplateTableProps { value?: any[]; onChange?: (value: any[]) => void; @@ -133,7 +124,7 @@ const EvaluateTemplateTable: React.FC = ({ const convertTableDataToApiData = (tableData: TableRowItem[]): any[] => { // 按一级指标分组 const groupedByLevel1 = tableData.reduce((acc: Record, item: TableRowItem) => { - // 为空的baseIndicator也需要分组,使用特殊键标识 - 这是关键修复 + // 为空的baseIndicator也需要分组,使用特殊键标识 const groupKey = item.baseIndicator || `empty-${item.key}`; if (!acc[groupKey]) { @@ -151,7 +142,6 @@ const EvaluateTemplateTable: React.FC = ({ // 生成唯一的临时ID const tempStId = `temp-st-${generateUUID(16)}-${stIndex}`; - // 简化数据转换,使用可选链和短路评估 return { id: firstItem.stId || tempStId, baseIndicator: firstItem.baseIndicator || '', @@ -182,43 +172,36 @@ const EvaluateTemplateTable: React.FC = ({ fetchIndicatorTypes(); }, []); + // 确保数据中没有重复的key + const ensureUniqueKeys = (data: TableRowItem[]): TableRowItem[] => { + const keyMap = new Map(); + + return data.map((item, index) => { + if (!item.key || item.key.includes('undefined') || keyMap.has(item.key)) { + const newKey = `fixed-${generateUUID(32)}-${index}`; + return { ...item, key: newKey }; + } + + keyMap.set(item.key, true); + return item; + }); + }; + // 单独处理value变化 useEffect(() => { // 初始化表格数据或value变化时更新 if (value && value.length > 0) { - console.log('useEffect中接收到的value:', value); - // 避免不必要的状态更新,比较新旧数据是否相同 const currentValueStr = JSON.stringify(value); - // 注意:当dataSource为空时,避免调用convertTableDataToApiData const currentDataSourceApiStr = dataSource.length > 0 ? JSON.stringify(convertTableDataToApiData(dataSource)) : ''; if (currentValueStr !== currentDataSourceApiStr) { const tableData = convertApiDataToTableData(value); - console.log('转换后的tableData:', tableData); - console.log('转换后的tableData key列表:', tableData.map(item => item.key)); // 保留现有项的key,确保稳定性 if (dataSource && dataSource.length > 0) { - console.log('现有dataSource:', dataSource); - console.log('现有dataSource key列表:', dataSource.map(item => item.key)); - - // 检查现有dataSource中是否有重复的key - const existingKeyMap = new Map(); - const duplicateKeys = dataSource.filter(item => { - if (existingKeyMap.has(item.key)) { - return true; - } - existingKeyMap.set(item.key, true); - return false; - }).map(item => item.key); - - if (duplicateKeys.length > 0) { - console.error('现有dataSource中发现重复的key:', duplicateKeys); - } - - const updatedTableData = tableData.map((newItem, index) => { + const updatedTableData = tableData.map((newItem) => { // 尝试查找对应的现有项,通过stId和ndId匹配 const existingItem = dataSource.find(existing => (existing.stId === newItem.stId && existing.ndId === newItem.ndId) || @@ -227,62 +210,17 @@ const EvaluateTemplateTable: React.FC = ({ // 如果找到现有项,保留其key if (existingItem) { - console.log(`保留现有项的key: ${existingItem.key}`); return { ...newItem, key: existingItem.key }; } - // 生成唯一的key - const uniqueKey = `init-${generateUUID(32)}-${index}`; - console.log(`为新项生成key: ${uniqueKey}`); - return { ...newItem, key: uniqueKey }; + return newItem; }); - console.log('更新后的tableData:', updatedTableData); - console.log('更新后的tableData key列表:', updatedTableData.map(item => item.key)); - - // 再次检查是否有重复的key - const finalKeyMap = new Map(); - const finalData = updatedTableData.map((item, index) => { - if (finalKeyMap.has(item.key)) { - // 如果仍然有重复的key,强制生成新的key - console.warn(`useEffect中仍然发现重复的key: ${item.key},强制重新生成`); - const forceNewKey = `force-${generateUUID(32)}-${index}-${Date.now()}`; - return { - ...item, - key: forceNewKey - }; - } else { - finalKeyMap.set(item.key, true); - return item; - } - }); - - console.log('最终的tableData:', finalData); - console.log('最终的tableData key列表:', finalData.map(item => item.key)); - - setDataSource(finalData); + // 确保所有key都是唯一的 + setDataSource(ensureUniqueKeys(updatedTableData)); } else { // 确保初始数据的每一项都有唯一的key - const keyMap = new Map(); - const dataWithUniqueKeys = tableData.map((item, index) => { - const key = item.key || `init-${generateUUID(32)}-${index}`; - - if (keyMap.has(key)) { - // 如果key重复,生成新的key - const newKey = `unique-${generateUUID(32)}-${index}`; - console.log(`初始化时发现重复的key: ${key},生成新key: ${newKey}`); - keyMap.set(newKey, true); - return { ...item, key: newKey }; - } else { - keyMap.set(key, true); - return { ...item, key }; - } - }); - - console.log('初始化的dataWithUniqueKeys:', dataWithUniqueKeys); - console.log('初始化的key列表:', dataWithUniqueKeys.map(item => item.key)); - - setDataSource(dataWithUniqueKeys); + setDataSource(ensureUniqueKeys(tableData)); } } } else if (value && value.length === 0 && dataSource.length > 0) { @@ -293,62 +231,8 @@ const EvaluateTemplateTable: React.FC = ({ // 更新数据源 const updateDataSource = (newData: TableRowItem[]) => { - console.log('updateDataSource接收到的数据:', newData); - console.log('updateDataSource接收到的key列表:', newData.map(item => item.key)); - - // 检查是否有重复的key - const keyMap = new Map(); - newData.forEach(item => { - if (keyMap.has(item.key)) { - keyMap.set(item.key, keyMap.get(item.key)! + 1); - } else { - keyMap.set(item.key, 1); - } - }); - - // 打印重复的key - const duplicateKeys = Array.from(keyMap.entries()) - .filter(([_, count]) => count > 1) - .map(([key]) => key); - - if (duplicateKeys.length > 0) { - console.error('发现重复的key:', duplicateKeys); - console.error('重复key的详细信息:', newData.filter(item => duplicateKeys.includes(item.key))); - } - // 确保每行都有唯一稳定的key - const dataWithStableKeys = newData.map((item, index) => { - // 如果key不存在、包含undefined或者是重复的key,使用工具函数生成新key - if (!item.key || item.key.includes('undefined') || duplicateKeys.includes(item.key)) { - console.log(`为项 ${index} 重新生成key,原key: ${item.key}`); - const newKey = `fixed-${generateUUID(32)}-${index}`; - return { - ...item, - key: newKey - }; - } - return item; - }); - - // 再次检查是否有重复的key - const finalKeyMap = new Map(); - const finalData = dataWithStableKeys.map((item, index) => { - if (finalKeyMap.has(item.key)) { - // 如果仍然有重复的key,强制生成新的key - console.warn(`仍然发现重复的key: ${item.key},强制重新生成`); - const forceNewKey = `force-${generateUUID(32)}-${index}-${Date.now()}`; - return { - ...item, - key: forceNewKey - }; - } else { - finalKeyMap.set(item.key, true); - return item; - } - }); - - console.log('最终的数据源:', finalData); - console.log('最终的key列表:', finalData.map(item => item.key)); + const finalData = ensureUniqueKeys(newData); setDataSource(finalData); if (onChange) { @@ -413,10 +297,8 @@ const EvaluateTemplateTable: React.FC = ({ // 添加一级指标 const addLevel1Indicator = (currentRecord?: TableRowItem) => { - // 使用改进后的工具函数生成唯一key,不再需要额外的时间戳和随机数 + // 使用改进后的工具函数生成唯一key const newKey = `level1-${generateUUID(32)}`; - console.log('添加一级指标,生成的key:', newKey); - console.log('当前数据源:', dataSource); const newItem: TableRowItem = { key: newKey, @@ -431,7 +313,6 @@ const EvaluateTemplateTable: React.FC = ({ orderBy: dataSource.length + 1, // 确保正确的排序 ndOrderBy: 1 // 设置二级指标初始排序 }; - console.log('新建的一级指标项:', newItem); // 制作数据源的副本,避免直接修改状态 const newData = [...dataSource]; @@ -443,11 +324,9 @@ const EvaluateTemplateTable: React.FC = ({ const sameGroup = newData.filter((item) => item.baseIndicator === currentRecord.baseIndicator); const lastOfGroup = sameGroup[sameGroup.length - 1]; insertIndex = newData.findIndex((item) => item.key === lastOfGroup.key); - console.log('插入位置(相同baseIndicator):', insertIndex); } else if (currentRecord && currentRecord.key) { // 如果是新增的空白行,直接在其后插入 insertIndex = newData.findIndex((item) => item.key === currentRecord.key); - console.log('插入位置(空白行后):', insertIndex); } // 如果找到了位置,在该位置后插入,否则添加到末尾 @@ -456,8 +335,6 @@ const EvaluateTemplateTable: React.FC = ({ } else { newData.push(newItem); } - console.log('插入后的数据源:', newData); - console.log('插入后的数据源中的key列表:', newData.map(item => item.key)); updateDataSource(newData); }; @@ -827,31 +704,6 @@ const EvaluateTemplateTable: React.FC = ({ return (
- {console.log('渲染Table前的dataSource:', dataSource)} - {console.log('渲染Table前的dataSource中的key列表:', dataSource.map(item => item.key))} - {(() => { - // 检查是否有重复的key - const keyMap = new Map(); - dataSource.forEach(item => { - if (keyMap.has(item.key)) { - keyMap.set(item.key, keyMap.get(item.key)! + 1); - } else { - keyMap.set(item.key, 1); - } - }); - - // 打印重复的key - const duplicateKeys = Array.from(keyMap.entries()) - .filter(([_, count]) => count > 1) - .map(([key]) => key); - - if (duplicateKeys.length > 0) { - console.error('渲染Table前发现重复的key:', duplicateKeys); - console.error('重复key的详细信息:', dataSource.filter(item => duplicateKeys.includes(item.key))); - } - - return null; - })()} void; + formData: Partial; + onFormDataChange: (data: Partial) => void; } interface TemplateItem { id: string; templateName: string; - isUnlimitedCategory?: boolean; + categoryLimitation?: string; } -const BasicInfoStep: React.FC = ({ formData, onFormDataChange }) => { +const BasicInfoStep = forwardRef(({ formData, onFormDataChange }, ref) => { const [form] = Form.useForm(); const [templates, setTemplates] = useState([]); - const [categoryTemplates, setCategoryTemplates] = useState([]); const [selectedCategoryId, setSelectedCategoryId] = useState(undefined); + const [loading, setLoading] = useState(false); + + // 暴露表单方法给父组件 + useImperativeHandle(ref, () => ({ + validateFields: () => form.validateFields(), + getFieldsValue: () => form.getFieldsValue(), + setFieldsValue: (values: any) => form.setFieldsValue(values), + })); + + // 获取模板列表 + const fetchTemplates = async () => { + setLoading(true); + try { + const response = await getAllTemplates(); + if (response.success && response.data) { + setTemplates(response.data); + } else { + setTemplates([ + { id: '1', templateName: '不限品类模板', categoryLimitation: '0' }, + { id: '2', templateName: '限制品类模板', categoryLimitation: '1' }, + ]); + } + } catch (error) { + console.error('获取模板列表失败:', error); + // 使用模拟数据 + setTemplates([ + { id: '1', templateName: '不限品类模板', categoryLimitation: '0' }, + { id: '2', templateName: '限制品类模板', categoryLimitation: '1' }, + ]); + } finally { + setLoading(false); + } + }; // 获取评价模板和初始化表单数据 useEffect(() => { - // 模拟API调用获取模板列表 - const mockTemplates = [ - { id: '1', templateName: '不限品类', isUnlimitedCategory: true }, - { id: '2', templateName: '按品类', isUnlimitedCategory: false }, - ]; - setTemplates(mockTemplates); + fetchTemplates(); // 初始化表单数据 if (formData) { - const initialValues = { - ...formData, - evaluateStartTime: formData.evaluateStartTime ? moment(formData.evaluateStartTime) : undefined, - evaluateEndTime: formData.evaluateEndTime ? moment(formData.evaluateEndTime) : undefined, - }; - form.setFieldsValue(initialValues); + form.setFieldsValue(formData); // 设置已选品类 if (formData.categoryId) { @@ -48,73 +72,61 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang } }, []); - // 获取品类对应的模板数据 - useEffect(() => { - if (selectedCategoryId) { - // 模拟API调用,根据品类ID获取对应的模板列表 - const mockCategoryTemplates = [ - { id: 'ct1', templateName: '硬件设备评价模板' }, - { id: 'ct2', templateName: '备件评价模板' }, - { id: 'ct3', templateName: '通用备件评价模板' }, - ]; - setCategoryTemplates(mockCategoryTemplates); - } else { - setCategoryTemplates([]); - } - }, [selectedCategoryId]); - - // 检查当前选择的模板是否限制品类 - const isTemplateUnlimitedCategory = () => { - const templateId = form.getFieldValue('templateId'); - const currentTemplate = templates.find(t => t.id === templateId); - return currentTemplate?.isUnlimitedCategory; - }; - // 表单值变化时触发 const handleValuesChange = (changedValues: any, allValues: any) => { // 处理模板变更 if (changedValues.templateId) { - // 如果选择了不限品类的模板,清空品类选择和品类模板选择 - if (isTemplateUnlimitedCategory()) { + const template = templates.find((t) => t.id === changedValues.templateId); + if (template) { form.setFieldsValue({ - categoryId: undefined, - categoryTemplateId: undefined + categoryLimitation: template.categoryLimitation || '0', }); - setSelectedCategoryId(undefined); + + // 如果选择了不限品类的模板,清空品类选择 + if (template.categoryLimitation === '0') { + form.setFieldsValue({ + categoryId: undefined, + }); + setSelectedCategoryId(undefined); + } } } // 处理品类变更 if (changedValues.categoryId) { setSelectedCategoryId(changedValues.categoryId?.[0]); - form.setFieldsValue({ categoryTemplateId: undefined }); } - // 将moment对象转换为字符串再传递 + // 默认设置weightStatus为0 const formattedValues = { ...allValues, - evaluateStartTime: allValues.evaluateStartTime?.format('YYYY-MM-DD'), - evaluateEndTime: allValues.evaluateEndTime?.format('YYYY-MM-DD'), + weightStatus: 0, }; + onFormDataChange(formattedValues); }; // 年度选项生成 - const yearOptions = () => { + const yearOptions = useCallback(() => { const currentYear = new Date().getFullYear(); - return Array.from({ length: 11 }, (_, i) => currentYear - 5 + i).map(year => ( - + return Array.from({ length: 11 }, (_, i) => currentYear - 5 + i).map((year) => ( + )); - }; + }, []); // 判断是否显示品类选择器 - const shouldShowCategorySelector = () => { - const templateId = form.getFieldValue('templateId'); - if (!templateId) return false; + const shouldShowCategorySelector = useCallback(() => { + const categoryLimitation = form.getFieldValue('categoryLimitation'); + return categoryLimitation === CategoryLimitationType.LIMITED; + }, [form]); - const currentTemplate = templates.find(t => t.id === templateId); - return currentTemplate && !currentTemplate.isUnlimitedCategory; - }; + // 处理品类选择变化 + const handleCategoryChange = useCallback((value: string | string[]) => { + const categoryId = Array.isArray(value) ? value[0] : value; + setSelectedCategoryId(categoryId); + }, []); return (
@@ -123,7 +135,10 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang form={form} layout="vertical" onValuesChange={handleValuesChange} - initialValues={formData} + initialValues={{ + ...formData, + categoryLimitation: formData.categoryLimitation || '0', + }} >
@@ -141,7 +156,7 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang name="evaluateYear" rules={[{ required: true, message: '请选择评价年度' }]} > - {yearOptions()} @@ -152,49 +167,42 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang ({ + value: value ? moment(value) : undefined, + })} + normalize={(value) => value && value.format('YYYY-MM-DD')} > - + ({ + value: value ? moment(value) : undefined, + })} + normalize={(value) => value && value.format('YYYY-MM-DD')} > - + - + - + + 通用不限品类 + 限制品类 + {shouldShowCategorySelector() && ( @@ -204,16 +212,7 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang name="categoryId" rules={[{ required: true, message: '请选择品类' }]} > - { - const categoryId = values?.[0]; - setSelectedCategoryId(categoryId); - - if (!categoryId) { - form.setFieldsValue({ categoryTemplateId: undefined }); - } - }} - /> + )} @@ -223,15 +222,11 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang - + {templates.map((template) => ( @@ -244,6 +239,6 @@ const BasicInfoStep: React.FC = ({ formData, onFormDataChang ); -}; +}); export default BasicInfoStep; diff --git a/src/pages/supplierEvaluateManage/supplierTaskManage/components/EvaluatorSelectStep.tsx b/src/pages/supplierEvaluateManage/supplierTaskManage/components/EvaluatorSelectStep.tsx index 64e4892..5c8c5d0 100644 --- a/src/pages/supplierEvaluateManage/supplierTaskManage/components/EvaluatorSelectStep.tsx +++ b/src/pages/supplierEvaluateManage/supplierTaskManage/components/EvaluatorSelectStep.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import { Card, Button, Table, Space, Modal, Tag, Tooltip, Input, Form, InputNumber } from 'antd'; import { UserOutlined, SettingOutlined } from '@ant-design/icons'; import styles from '../supplierTaskManageAdd.less'; @@ -25,7 +25,7 @@ interface WeightUnit { weight: number; } -const EvaluatorSelectStep: React.FC = ({ formData, onFormDataChange }) => { +const EvaluatorSelectStep = forwardRef(({ formData, onFormDataChange }, ref) => { const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [suppliers, setSuppliers] = useState([]); const [batchSelectModalVisible, setBatchSelectModalVisible] = useState(false); @@ -41,6 +41,57 @@ const EvaluatorSelectStep: React.FC = ({ formData, onF ]); const [form] = Form.useForm(); + // 暴露表单方法给父组件 + useImperativeHandle(ref, () => ({ + validateFields: () => { + // 自定义验证逻辑 + if (suppliers.length === 0) { + return Promise.reject('请至少添加一个供应商'); + } + + // 检查是否每个供应商都有评价人员 + const hasNoEvaluator = suppliers.some(supplier => + !supplier.evaluators || supplier.evaluators.length === 0 + ); + + if (hasNoEvaluator) { + return Promise.reject('存在未分配评价人员的供应商'); + } + + return Promise.resolve(); + }, + getFieldsValue: () => { + // 构建评价人员列表 + const indicatorList = suppliers.flatMap(supplier => + supplier.evaluators?.map(evaluator => ({ + userId: evaluator.id, + type: 0, // 假设按评价单评价 + indicatorIds: [] + })) || [] + ); + + // 构建供应商ID列表 + const supplierIds = suppliers.map(supplier => ({ + id: supplier.key, + userIds: supplier.evaluators?.map(e => e.id) || [] + })); + + return { + indicatorList, + supplierIds, + suppliersWithEvaluators: suppliers + }; + }, + setFieldsValue: (values: any) => { + if (values.suppliersWithEvaluators) { + setSuppliers(values.suppliersWithEvaluators); + } + if (values.weightUnits) { + setWeightUnits(values.weightUnits); + } + }, + })); + // 从上一步获取供应商数据 useEffect(() => { if (formData.selectedSuppliers) { @@ -75,9 +126,25 @@ const EvaluatorSelectStep: React.FC = ({ formData, onF // 更新表单数据 const updateFormData = (updatedData: any) => { + // 构建评价人员列表 + const indicatorList = suppliers.flatMap(supplier => + supplier.evaluators?.map(evaluator => ({ + userId: evaluator.id, + type: 0, // 假设按评价单评价 + indicatorIds: [] + })) || [] + ); + + // 构建供应商ID列表 + const supplierIds = suppliers.map(supplier => ({ + id: supplier.key, + userIds: supplier.evaluators?.map(e => e.id) || [] + })); + onFormDataChange({ - ...formData, - ...updatedData + ...updatedData, + indicatorList, + supplierIds }); }; @@ -252,122 +319,103 @@ const EvaluatorSelectStep: React.FC = ({ formData, onF }, ]; - // 权重表格列定义 - const weightColumns = [ - { - title: '序号', - dataIndex: 'id', - key: 'id', - width: 80, - render: (_: string, __: any, index: number) => index + 1 - }, - { - title: '评价单位', - dataIndex: 'name', - key: 'name' - }, - { - title: *权重 (%), - dataIndex: 'weight', - key: 'weight', - render: (_: number, record: WeightUnit) => ( - - - - ) - } - ]; - return (
-
- - {selectedRowKeys.length > 0 && ( + +
+ - )} - - -
+ +
+
-
- - {/* 批量选择模态框 */} - setBatchSelectModalVisible(false)} - width={800} - destroyOnClose - > - - - - {/* 设置评分单位权重模态框 */} - setWeightSettingModalVisible(false)} - width={600} - > -
-
setBatchSelectModalVisible(false)} + footer={null} + width={700} + > + - - + - {/* 评价人员选择/查看模态框 */} - setEvaluatorModalVisible(false)} - width={800} - destroyOnClose - > - - + {/* 单个供应商选择评价人员弹窗 */} + setEvaluatorModalVisible(false)} + footer={null} + width={700} + > + + + + {/* 权重设置弹窗 */} + setWeightSettingModalVisible(false)} + > +
+ +

请设置各评分单位的权重比例,总和需为100%

+ {weightUnits.map(unit => ( + + `${value}%`} + parser={(value: string | undefined) => { + if (!value) return 0; + const num = Number(value.replace('%', '')); + return num; + }} + style={{ width: '100%' }} + /> + + ))} +
+ +
+ ); -}; +}); export default EvaluatorSelectStep; diff --git a/src/pages/supplierEvaluateManage/supplierTaskManage/components/SupplierSelectStep.tsx b/src/pages/supplierEvaluateManage/supplierTaskManage/components/SupplierSelectStep.tsx index 56350b6..bb8c1a5 100644 --- a/src/pages/supplierEvaluateManage/supplierTaskManage/components/SupplierSelectStep.tsx +++ b/src/pages/supplierEvaluateManage/supplierTaskManage/components/SupplierSelectStep.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import { Card, Row, Col, Input, Select, Radio, Table, Button, Space, Pagination, Form } from 'antd'; import { SearchOutlined, ArrowRightOutlined } from '@ant-design/icons'; import styles from '../supplierTaskManageAdd.less'; @@ -17,7 +17,7 @@ interface SupplierItem { category: string; } -const SupplierSelectStep: React.FC = ({ formData, onFormDataChange }) => { +const SupplierSelectStep = forwardRef(({ formData, onFormDataChange }, ref) => { const [filterForm] = Form.useForm(); const [categoryKeyword, setCategoryKeyword] = useState(''); const [selectedCategory, setSelectedCategory] = useState(undefined); @@ -33,6 +33,25 @@ const SupplierSelectStep: React.FC = ({ formData, onFor const [pendingSelectedRowKeys, setPendingSelectedRowKeys] = useState([]); const [selectedSelectedRowKeys, setSelectedSelectedRowKeys] = useState([]); + // 暴露表单方法给父组件 + useImperativeHandle(ref, () => ({ + validateFields: () => { + // 这里可以添加自定义验证逻辑 + return Promise.resolve(); + }, + getFieldsValue: () => { + return { + selectedSuppliers, + supplierIds: selectedSuppliers.map(supplier => ({ id: supplier.key })) + }; + }, + setFieldsValue: (values: any) => { + if (values.selectedSuppliers) { + setSelectedSuppliers(values.selectedSuppliers); + } + }, + })); + // 初始化数据 useEffect(() => { // 从formData中恢复已选供应商 @@ -87,7 +106,10 @@ const SupplierSelectStep: React.FC = ({ formData, onFor // 更新表单数据 const updateFormData = (suppliers: SupplierItem[]) => { - onFormDataChange({ selectedSuppliers: suppliers }); + onFormDataChange({ + selectedSuppliers: suppliers, + supplierIds: suppliers.map(supplier => ({ id: supplier.key })) + }); }; // 处理筛选条件变化 @@ -242,7 +264,7 @@ const SupplierSelectStep: React.FC = ({ formData, onFor onValuesChange={handleFilterChange} > -
+ - + - - - + - + @@ -274,9 +294,7 @@ const SupplierSelectStep: React.FC = ({ formData, onFor - - - + - + -
{ - const [currentStep, setCurrentStep] = useState(2); + const [currentStep, setCurrentStep] = useState(0); const [loading, setLoading] = useState(false); - const [formData, setFormData] = useState({}); + const [formData, setFormData] = useState>({}); + + // 创建表单引用 + const basicFormRef = useRef(null); + const supplierFormRef = useRef(null); + const evaluatorFormRef = useRef(null); // 步骤配置 const steps = [ { title: '基本信息', description: '请填写基本信息', - content: , + content: , }, { title: '选择供应商', description: '请选择参加评价的供应商', - content: , + content: , }, { title: '选择评价人员', description: '请选择供应商评价人员', - content: , + content: , }, { title: '设置评价分工', @@ -54,54 +60,31 @@ const SupplierTaskManageAdd: React.FC = () => { }; // 下一步 - const handleNext = () => { - // 验证当前步骤表单 - if (currentStep === 0) { - // 验证基本信息 - if (!formData.evaluateTheme) { - message.error('请填写评价主题'); - return; + const handleNext = async () => { + try { + // 验证当前步骤表单 + if (currentStep === 0 && basicFormRef.current) { + // 验证基本信息 + await basicFormRef.current.validateFields(); + } else if (currentStep === 1 && supplierFormRef.current) { + // 验证供应商选择 + await supplierFormRef.current.validateFields(); + } else if (currentStep === 2 && evaluatorFormRef.current) { + // 验证评价人员选择 + await evaluatorFormRef.current.validateFields(); } - if (!formData.evaluateStartTime) { - message.error('请选择评价开始时间'); - return; - } - if (!formData.evaluateEndTime) { - message.error('请选择评价结束时间'); - return; - } - if (!formData.templateId) { - message.error('请选择适用评价模板'); - return; - } - if (!formData.categoryId && formData.templateId !== '1') { // 如果不是"不限品类"模板,则需要选择品类 - message.error('请选择品类'); - return; - } - } else if (currentStep === 1) { - // 验证供应商选择 - if (!formData.selectedSuppliers || formData.selectedSuppliers.length === 0) { - message.error('请至少选择一个供应商'); - return; - } - } else if (currentStep === 2) { - // 验证评价人员选择 - if (!formData.suppliersWithEvaluators) { - message.error('请为供应商分配评价人员'); - return; - } - // 检查是否每个供应商都有评价人员 - const hasNoEvaluator = formData.suppliersWithEvaluators.some((supplier: any) => - supplier.evaluatorCount === 0 - ); - if (hasNoEvaluator) { - message.error('存在未分配评价人员的供应商,请为所有供应商分配评价人员'); - return; + + // 验证通过,进入下一步 + setCurrentStep(currentStep + 1); + } catch (errorInfo: any) { + // 表单验证失败 + console.log('表单验证失败:', errorInfo); + if (typeof errorInfo === 'string') { + message.error(errorInfo); + } else if (errorInfo && errorInfo.errorFields) { + message.error('请完成必填项'); } } - - // 进入下一步 - setCurrentStep(currentStep + 1); }; // 上一步 @@ -113,17 +96,34 @@ const SupplierTaskManageAdd: React.FC = () => { const handleSubmit = async () => { setLoading(true); try { - // 模拟提交 - console.log('提交数据:', formData); - setTimeout(() => { + // 构建提交数据 + const submitData: SupplierEvaluate.TaskAddRequest = { + evaluateTheme: formData.evaluateTheme || '', + startTime: formData.startTime || '', + endTime: formData.endTime || '', + templateId: formData.templateId || '', + categoryLimitation: formData.categoryLimitation || '0', + evaluateYear: formData.evaluateYear || '', + supplierIds: formData.supplierIds || [], + indicatorList: formData.indicatorList || [], + taskDeptWeightList: formData.taskDeptWeightList || [], + weightStatus: formData.weightStatus || 0 + }; + + // 调用API提交数据 + const response = await addTask(submitData); + + if (response.success) { message.success('任务创建成功'); - setLoading(false); // 显示最后一步(创建成功) setCurrentStep(steps.length - 1); - }, 1000); + } else { + message.error(response.message || '提交失败'); + } } catch (error) { console.error('提交失败:', error); message.error('提交失败'); + } finally { setLoading(false); } }; diff --git a/src/servers/api/supplierEvaluate.ts b/src/servers/api/supplierEvaluate.ts index 2948467..bff9e9b 100644 --- a/src/servers/api/supplierEvaluate.ts +++ b/src/servers/api/supplierEvaluate.ts @@ -123,3 +123,74 @@ export async function getDepartmentList() { method: 'GET', }); } + +/** + * 获取评价任务列表 + * @param params 查询参数 + * @returns Promise + */ +export async function getTaskList(params: SupplierEvaluate.TaskRequest) { + return request('/coscoEvaluate/task/getPage', { + method: 'POST', + data: params, + }); +} + +/** + * 获取评价任务详情 + * @param id 任务ID + * @returns Promise + */ +export async function getTaskDetail(id: string) { + return request>(`/coscoEvaluate/task/${id}`, { + method: 'GET', + }); +} + +/** + * 新增评价任务 + * @param params 任务数据 + * @returns Promise + */ +export async function addTask(params: SupplierEvaluate.TaskAddRequest) { + return request>('/coscoEvaluate/task/addTask', { + method: 'POST', + data: params, + }); +} + +/** + * 更新评价任务 + * @param params 任务数据 + * @returns Promise + */ +export async function updateTask(params: SupplierEvaluate.TaskUpdateRequest) { + return request>('/coscoEvaluate/task', { + method: 'PUT', + data: params, + }); +} + +/** + * 删除评价任务 + * @param id 任务ID + * @returns Promise + */ +export async function deleteTask(id: string) { + return request>('/coscoEvaluate/task/delete', { + method: 'POST', + data: { id }, + }); +} + +/** + * 批量删除评价任务 + * @param ids 任务ID数组 + * @returns Promise + */ +export async function batchDeleteTask(ids: string[]) { + return request>('/coscoEvaluate/task/batchDelete', { + method: 'POST', + data: { ids }, + }); +} diff --git a/src/typings.d.ts b/src/typings.d.ts index 39d8d9c..b04b89f 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -108,12 +108,17 @@ declare namespace SupplierEvaluate { createTime: string; updateBy?: string; updateTime?: string; + // 接口返回的字段 + evaluateTheme?: string; + statusName?: string; + tenantName?: string; }; type TaskSearchParams = { taskName?: string; status?: string; dateRange?: string[]; + evaluateTheme?: string; }; // 评价结果相关类型 @@ -365,6 +370,144 @@ declare namespace SupplierEvaluate { templateType: string; [property: string]: any; } + + // 评价任务请求和响应类型 + interface BasePageRequest { + pageNo: number; + pageSize: number; + [property: string]: any; + } + + interface TaskRequest { + basePageRequest: BasePageRequest; + isAnswer?: number; + [property: string]: any; + } + + interface TaskResponse { + code: number; + data: { + countId: null; + current: number; + hitCount: boolean; + maxLimit: null; + optimizeCountSql: boolean; + orders: string[]; + pages: number; + records: { + basePageRequest?: null; + endTime?: string; + evaluateTheme?: string; + id?: string; + startTime?: string; + status?: string; + statusName?: string; + tenantName?: string; + [property: string]: any; + }[]; + searchCount: boolean; + size: number; + total: number; + [property: string]: any; + }; + message: string; + success: boolean; + [property: string]: any; + } + + // 评价任务新增接口类型 + interface TaskIndicatorList { + /** + * 指标id集合 + */ + indicatorIds?: string[]; + /** + * 评价类型(0 按评价单 1 按指标) + */ + type: number; + /** + * 用户id + */ + userId: string; + [property: string]: any; + } + + interface TaskSupplierId { + /** + * 供应商id + */ + id: string; + /** + * 用户集合 + */ + userIds: string[]; + [property: string]: any; + } + + interface TaskDeptWeight { + /** + * 权重分配部门 + */ + weightDept?: string; + /** + * 权重比例值 + */ + weightValue?: string; + [property: string]: any; + } + + interface TaskAddRequest { + /** + * 品类限制类型(0.通用不限品类、1.限制品类) + */ + categoryLimitation: string; + /** + * 评价结束时间 + */ + endTime: string; + /** + * 评价主题 + */ + evaluateTheme: string; + /** + * 评价年度 + */ + evaluateYear: string; + /** + * 指标分配列表 + */ + indicatorList: TaskIndicatorList[]; + /** + * 评价开始时间 + */ + startTime: string; + /** + * 供应商列表 + */ + supplierIds: TaskSupplierId[]; + /** + * 部门权重列表 + */ + taskDeptWeightList: TaskDeptWeight[]; + /** + * 评价表模板id(cosco_evaluate_template表主键) + */ + templateId: string; + /** + * 权重启用状态(0.不启用、1.启用) + */ + weightStatus: number; + [property: string]: any; + } + + interface TaskUpdateRequest { + id: string; + evaluateTheme: string; + templateId: string; + startTime: string; + endTime: string; + [property: string]: any; + } } declare module '*.css'; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b7e608d..8e7223b 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -38,31 +38,28 @@ export const validateFileSize = (file: File, maxSize: number,type: string[]) => export const generateUUID = (length: number) => { // 使用更复杂的字符集 const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; - // 使用加密安全的随机数生成(如果可用) - let array; + // 使用加密安全的随机数生成 + let result = ''; + + // 添加时间戳前缀,确保唯一性 + const timestamp = Date.now().toString(36); + result += timestamp.substring(timestamp.length - Math.min(6, length / 4)); + + // 获取随机字符 + const randomPart = []; if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { - array = new Uint8Array(length); + const array = new Uint8Array(length); window.crypto.getRandomValues(array); + for (let i = 0; i < length - result.length; i++) { + randomPart.push(characters.charAt(array[i] % charactersLength)); + } } else { - array = new Uint8Array(length); - for (let i = 0; i < length; i++) { - array[i] = Math.floor(Math.random() * 256); + for (let i = 0; i < length - result.length; i++) { + randomPart.push(characters.charAt(Math.floor(Math.random() * charactersLength))); } } - // 生成UUID - const timestamp = Date.now().toString(36); - let result = ''; - - // 添加时间戳前缀(最多6个字符) - result += timestamp.substring(timestamp.length - Math.min(6, length / 3)); - - // 添加随机字符 - for (let i = 0; i < length - result.length; i++) { - const randomIndex = array[i % array.length] % characters.length; - result += characters.charAt(randomIndex); - } - - return result; + return result + randomPart.join(''); };