From 9eb1bed092da8d9660969c9c46e285896eb3ede9 Mon Sep 17 00:00:00 2001 From: linxd <544554903@qq.com> Date: Mon, 23 Jun 2025 20:29:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E8=AF=84=E4=BB=B7=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=96=B0=E5=A2=9E=E5=92=8C=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EvaluateTemplateTable.tsx | 334 +++++++++++++----- .../EvaluateTemplateTable/README.md | 145 ++++++++ .../supplierTemplateManageAdd.tsx | 54 ++- src/servers/api/supplierEvaluate.ts | 60 +++- src/servers/api/typings.d.ts | 23 +- src/typings.d.ts | 138 ++++++++ src/utils/utils.ts | 34 ++ 7 files changed, 664 insertions(+), 124 deletions(-) create mode 100644 src/components/EvaluateTemplateTable/README.md diff --git a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx index 16f9af7..cae8488 100644 --- a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx +++ b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx @@ -21,15 +21,17 @@ import { } from '@ant-design/icons'; import { getDictList, DictItem } from '@/servers/api/dicts'; import { StarLevel, StarLevelText } from '@/dicts/supplierTemplateDict'; +import { generateUUID } from '@/utils/utils'; import './EvaluateTemplateTable.less'; -// 注意:这里我们使用any类型是为了灵活处理多种数据格式 -// 实际上,API的类型定义在 src/servers/api/typings.d.ts 中的 SupplierEvaluate 命名空间下 -// 包含了 IndicatorStItem 和 IndicatorNdItem 类型 +// 注意:这里我们使用SupplierEvaluate命名空间中的类型 +// 类型定义已移动到 src/typings.d.ts 文件中 const { Option } = Select; const { TextArea } = Input; +// 移除类型定义,使用全局命名空间中的类型 + interface EvaluateTemplateTableProps { value?: any[]; onChange?: (value: any[]) => void; @@ -86,17 +88,12 @@ const EvaluateTemplateTable: React.FC = ({ // 检查数据是否已经是扁平化的表格数据格式 if (apiData.length > 0 && 'subIndicator' in apiData[0]) { return apiData.map((item, index) => ({ - key: item.id || `row-${index}`, - stId: item.stId, + ...item, + key: item.id || `row-${generateUUID(16)}-${index}`, ndId: item.id, - baseIndicator: item.baseIndicator || '', - descIndicator: item.descIndicator || '', stScore: item.stScore || '0', - indicatorType: item.indicatorType || '', - subIndicator: item.subIndicator || '', ndScore: item.score || '0', isStar: item.isStar || StarLevel.NO, - desc: item.desc || '', orderBy: typeof item.stOrderBy === 'string' ? parseInt(item.stOrderBy) : item.stOrderBy, ndOrderBy: typeof item.orderBy === 'string' ? parseInt(item.orderBy) : item.orderBy })); @@ -106,7 +103,7 @@ const EvaluateTemplateTable: React.FC = ({ if (apiData.length > 0 && 'indicatorNdList' in apiData[0]) { const flattenedData: TableRowItem[] = []; - apiData.forEach((stItem: any) => { + apiData.forEach((stItem: any, stIndex: number) => { stItem.indicatorNdList.forEach((ndItem: any, ndIndex: number) => { flattenedData.push({ key: ndItem.id || `${stItem.id}-${ndIndex}`, @@ -118,7 +115,7 @@ const EvaluateTemplateTable: React.FC = ({ indicatorType: stItem.indicatorType, subIndicator: ndItem.subIndicator, ndScore: ndItem.score, - isStar: ndItem.isStar, + isStar: ndItem.isStar || StarLevel.NO, desc: ndItem.desc, orderBy: typeof stItem.orderBy === 'string' ? parseInt(stItem.orderBy) : stItem.orderBy, ndOrderBy: typeof ndItem.orderBy === 'string' ? parseInt(ndItem.orderBy) : ndItem.orderBy @@ -129,25 +126,6 @@ const EvaluateTemplateTable: React.FC = ({ return flattenedData; } - // 如果是旧格式,尝试兼容处理 - if (apiData.length > 0 && 'level1Name' in apiData[0]) { - return apiData.map((item, index) => ({ - key: item.key || `legacy-${index}`, - stId: item._originalData?.stItem?.id, - ndId: item._originalData?.ndItem?.id, - baseIndicator: item.level1Name || '', - descIndicator: item.level1Description || '', - stScore: item.level1Score?.toString() || '0', - indicatorType: item.indicator_type || '', - subIndicator: item.level2Name || '', - ndScore: item.level2Score?.toString() || '0', - isStar: item.isStar || StarLevel.NO, - desc: item.desc_score || '', - orderBy: typeof item.orderBy === 'string' ? parseInt(item.orderBy) : (item.orderBy || 0), - ndOrderBy: typeof item.ndOrderBy === 'string' ? parseInt(item.ndOrderBy) : (item.ndOrderBy || 0) - })); - } - return []; }; @@ -155,40 +133,46 @@ const EvaluateTemplateTable: React.FC = ({ const convertTableDataToApiData = (tableData: TableRowItem[]): any[] => { // 按一级指标分组 const groupedByLevel1 = tableData.reduce((acc: Record, item: TableRowItem) => { - const baseIndicator = item.baseIndicator; - if (!baseIndicator) return acc; + // 为空的baseIndicator也需要分组,使用特殊键标识 - 这是关键修复 + const groupKey = item.baseIndicator || `empty-${item.key}`; - if (!acc[baseIndicator]) { - acc[baseIndicator] = []; + if (!acc[groupKey]) { + acc[groupKey] = []; } - acc[baseIndicator].push(item); + acc[groupKey].push(item); return acc; }, {}); // 转换为API需要的格式 - return Object.keys(groupedByLevel1).map((baseIndicator, stIndex) => { - const level1Items = groupedByLevel1[baseIndicator]; + return Object.keys(groupedByLevel1).map((groupKey, stIndex) => { + const level1Items = groupedByLevel1[groupKey]; const firstItem = level1Items[0]; - // 使用any类型避免类型错误 - const stItem: any = { - id: firstItem.stId, - baseIndicator, - descIndicator: firstItem.descIndicator, - score: firstItem.stScore, - orderBy: firstItem.orderBy || stIndex + 1, - indicatorType: firstItem.indicatorType, - indicatorNdList: level1Items.map((item, ndIndex) => ({ - id: item.ndId, - subIndicator: item.subIndicator, - score: item.ndScore, - isStar: item.isStar, - orderBy: item.ndOrderBy || ndIndex + 1, - desc: item.desc - })) - }; + // 生成唯一的临时ID + const tempStId = `temp-st-${generateUUID(16)}-${stIndex}`; - return stItem; + // 简化数据转换,使用可选链和短路评估 + return { + id: firstItem.stId || tempStId, + baseIndicator: firstItem.baseIndicator || '', + descIndicator: firstItem.descIndicator || '', + score: firstItem.stScore || '0', + orderBy: firstItem.orderBy || stIndex + 1, + indicatorType: firstItem.indicatorType || '', + indicatorNdList: level1Items.map((item, ndIndex) => { + // 生成唯一的临时ID + const tempNdId = `temp-nd-${generateUUID(16)}-${stIndex}-${ndIndex}`; + + return { + id: item.ndId || tempNdId, + subIndicator: item.subIndicator || '', + score: item.ndScore || '0', + isStar: item.isStar || StarLevel.NO, + orderBy: item.ndOrderBy || ndIndex + 1, + desc: item.desc || '' + }; + }) + }; }); }; @@ -196,20 +180,180 @@ const EvaluateTemplateTable: React.FC = ({ useEffect(() => { // 获取指标类型字典 fetchIndicatorTypes(); + }, []); - // 初始化表格数据 + // 单独处理value变化 + useEffect(() => { + // 初始化表格数据或value变化时更新 if (value && value.length > 0) { - const tableData = convertApiDataToTableData(value); - setDataSource(tableData); + 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) => { + // 尝试查找对应的现有项,通过stId和ndId匹配 + const existingItem = dataSource.find(existing => + (existing.stId === newItem.stId && existing.ndId === newItem.ndId) || + (existing.baseIndicator === newItem.baseIndicator && existing.subIndicator === newItem.subIndicator) + ); + + // 如果找到现有项,保留其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 }; + }); + + 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); + } 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); + } + } + } else if (value && value.length === 0 && dataSource.length > 0) { + // 如果value被清空,也清空dataSource + setDataSource([]); } }, [value]); // 更新数据源 const updateDataSource = (newData: TableRowItem[]) => { - setDataSource(newData); + 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)); + + setDataSource(finalData); if (onChange) { // 转换回API格式再传递给父组件 - const apiData = convertTableDataToApiData(newData); + const apiData = convertTableDataToApiData(finalData); onChange(apiData); } }; @@ -269,7 +413,10 @@ const EvaluateTemplateTable: React.FC = ({ // 添加一级指标 const addLevel1Indicator = (currentRecord?: TableRowItem) => { - const newKey = `level1-${Date.now()}`; + // 使用改进后的工具函数生成唯一key,不再需要额外的时间戳和随机数 + const newKey = `level1-${generateUUID(32)}`; + console.log('添加一级指标,生成的key:', newKey); + console.log('当前数据源:', dataSource); const newItem: TableRowItem = { key: newKey, @@ -277,13 +424,16 @@ const EvaluateTemplateTable: React.FC = ({ descIndicator: '', stScore: '0', indicatorType: '', - subIndicator: '', + subIndicator: '', // 注意:二级指标需要为空字符串,不能为undefined ndScore: '0', isStar: StarLevel.NO, desc: '', + orderBy: dataSource.length + 1, // 确保正确的排序 + ndOrderBy: 1 // 设置二级指标初始排序 }; + console.log('新建的一级指标项:', newItem); - // 找到当前记录所在的位置 + // 制作数据源的副本,避免直接修改状态 const newData = [...dataSource]; // 找到当前记录所在的一级指标组的最后一行 @@ -293,9 +443,11 @@ 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); } // 如果找到了位置,在该位置后插入,否则添加到末尾 @@ -304,6 +456,8 @@ const EvaluateTemplateTable: React.FC = ({ } else { newData.push(newItem); } + console.log('插入后的数据源:', newData); + console.log('插入后的数据源中的key列表:', newData.map(item => item.key)); updateDataSource(newData); }; @@ -324,7 +478,9 @@ const EvaluateTemplateTable: React.FC = ({ // 添加二级指标 const addSubIndicator = (parentKey: string) => { - const newKey = `level2-${Date.now()}`; + // 使用工具函数生成唯一key + const newKey = `level2-${generateUUID(16)}`; + const parent = dataSource.find((item) => item.key === parentKey); if (!parent || !parent.baseIndicator) { @@ -342,6 +498,7 @@ const EvaluateTemplateTable: React.FC = ({ ndScore: '0', isStar: StarLevel.NO, desc: '', + ndOrderBy: dataSource.filter(item => item.baseIndicator === parent.baseIndicator).length + 1 }; // 找到当前记录所在的位置 @@ -532,7 +689,9 @@ const EvaluateTemplateTable: React.FC = ({