diff --git a/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx b/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx index 44389f9..f7eee81 100644 --- a/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx +++ b/src/components/EvaluateTaskPersonnelSelector/EvaluateTaskPersonnelSelector.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Table, Input, Button, Space, message } from 'antd'; import type { TablePaginationConfig } from 'antd/es/table'; import { SearchOutlined } from '@ant-design/icons'; @@ -131,7 +131,7 @@ const EvaluateTaskPersonnelSelector: React.FC { + const handleSelectChange = (selectedRowKeys: React.Key[]) => { setSelectedKeys(selectedRowKeys as string[]); }; diff --git a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx index 4fe53b5..a780901 100644 --- a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx +++ b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx @@ -1,23 +1,15 @@ // 供应商评价 模板管理新增中的table import React, { useState, useEffect } from 'react'; import { useIntl } from 'umi'; -import { - Table, - Input, - Button, - Select, - Form, - InputNumber, - message, - Popconfirm, -} from 'antd'; +import { Table, Input, Button, Select, InputNumber, message, Popconfirm } from 'antd'; import { PlusOutlined, MinusCircleOutlined, PlusCircleOutlined, DeleteOutlined, } from '@ant-design/icons'; -import { getDictList, DictItem } from '@/servers/api/dicts'; +import { getDictList } from '@/servers/api/dicts'; +import type { DictItem } from '@/servers/api/dicts'; import { StarLevel, StarLevelText } from '@/dicts/supplierTemplateDict'; import { generateUUID } from '@/utils/utils'; import './EvaluateTemplateTable.less'; @@ -35,6 +27,12 @@ interface EvaluateTemplateTableProps { } // 内部使用的数据结构,扁平化后的行数据 +/** + * 表格行数据结构 + * 重要:区分了一级指标和二级指标的禁用状态 + * - disabledSt:一级指标禁用状态,同一组内应该保持一致 + * - disabledNd:二级指标禁用状态,独立控制每个二级指标 + */ interface TableRowItem { key: string; stId?: string; // 一级指标ID @@ -50,6 +48,8 @@ interface TableRowItem { orderBy?: number; ndOrderBy?: number; selected?: boolean; // 是否选中 + disabledSt?: boolean; // 是否禁用一级 + disabledNd?: boolean; // 是否禁用二级 } const EvaluateTemplateTable: React.FC = ({ @@ -62,7 +62,6 @@ const EvaluateTemplateTable: React.FC = ({ }) => { const intl = useIntl(); const [dataSource, setDataSource] = useState([]); - const [form] = Form.useForm(); const [indicatorTypes, setIndicatorTypes] = useState([]); const [loadingTypes, setLoadingTypes] = useState(false); @@ -74,17 +73,26 @@ const EvaluateTemplateTable: React.FC = ({ if (res.success && res.data) { setIndicatorTypes(res.data); } else { - message.error(intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.message.getTypeFailed' })); + message.error( + intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.message.getTypeFailed' }), + ); } } catch (error) { console.error('获取指标类型失败:', error); - message.error(intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.message.getTypeFailed' })); + message.error( + intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.message.getTypeFailed' }), + ); } finally { setLoadingTypes(false); } }; - // 将API数据转换为表格数据 + /** + * 将API数据转换为表格数据 + * + * 重要说明:这里处理了禁用状态的转换,将API数据中的disabled属性映射到表格数据的disabledSt和disabledNd属性 + * 确保一级指标和二级指标的禁用状态能正确保留 + */ const convertApiDataToTableData = (apiData: any[]): TableRowItem[] => { // 检查数据是否已经是扁平化的表格数据格式 if (apiData.length > 0 && 'subIndicator' in apiData[0]) { @@ -97,15 +105,16 @@ const EvaluateTemplateTable: React.FC = ({ isStar: item.isStar || StarLevel.NO, orderBy: typeof item.stOrderBy === 'string' ? parseInt(item.stOrderBy) : item.stOrderBy, ndOrderBy: typeof item.orderBy === 'string' ? parseInt(item.orderBy) : item.orderBy, - selected: defaultSelectedIds.includes(item.id) // 根据defaultSelectedIds设置选中状态 + selected: defaultSelectedIds.includes(item.id), // 根据defaultSelectedIds设置选中状态 + disabledSt: item.disabledSt ?? false, + disabledNd: item.disabledNd, })); } // 如果是嵌套结构,需要扁平化处理 if (apiData.length > 0 && 'indicatorNdList' in apiData[0]) { const flattenedData: TableRowItem[] = []; - - apiData.forEach((stItem: any, stIndex: number) => { + apiData.forEach((stItem: any) => { stItem.indicatorNdList.forEach((ndItem: any, ndIndex: number) => { flattenedData.push({ key: ndItem.id || `${stItem.id}-${ndIndex}`, @@ -120,34 +129,41 @@ const EvaluateTemplateTable: React.FC = ({ isStar: ndItem.isStar || StarLevel.NO, descScore: ndItem.descScore, orderBy: typeof stItem.orderBy === 'string' ? parseInt(stItem.orderBy) : stItem.orderBy, - ndOrderBy: typeof ndItem.orderBy === 'string' ? parseInt(ndItem.orderBy) : ndItem.orderBy, - selected: defaultSelectedIds.includes(ndItem.id) // 根据defaultSelectedIds设置选中状态 + ndOrderBy: + typeof ndItem.orderBy === 'string' ? parseInt(ndItem.orderBy) : ndItem.orderBy, + selected: defaultSelectedIds.includes(ndItem.id), // 根据defaultSelectedIds设置选中状态 + // 关键点1:将API数据中的disabled属性映射到表格数据的disabledSt和disabledNd + // 这确保了一级指标的禁用状态正确传递到所有相关行,二级指标保持自己的禁用状态 + disabledSt: stItem.disabled, // 根据disabled设置一级禁用状态 + disabledNd: ndItem.disabled, // 根据disabled设置二级禁用状态 }); }); }); return flattenedData; } - return []; }; // 将表格数据转换回API格式 const convertTableDataToApiData = (tableData: TableRowItem[]): any[] => { // 按一级指标分组 - const groupedByLevel1 = tableData.reduce((acc: Record, item: TableRowItem) => { - // 为空的baseIndicator也需要分组,使用特殊键标识 - const groupKey = item.baseIndicator || `empty-${item.key}`; + const groupedByLevel1 = tableData.reduce( + (acc: Record, item: TableRowItem) => { + // 为空的baseIndicator也需要分组,使用特殊键标识 + const groupKey = item.baseIndicator || `empty-${item.key}`; - if (!acc[groupKey]) { - acc[groupKey] = []; - } - acc[groupKey].push(item); - return acc; - }, {}); + if (!acc[groupKey]) { + acc[groupKey] = []; + } + acc[groupKey].push(item); + return acc; + }, + {}, + ); // 转换为API需要的格式 - return Object.keys(groupedByLevel1).map((groupKey, stIndex) => { + const result = Object.keys(groupedByLevel1).map((groupKey, stIndex) => { const level1Items = groupedByLevel1[groupKey]; const firstItem = level1Items[0]; @@ -161,6 +177,7 @@ const EvaluateTemplateTable: React.FC = ({ score: firstItem.stScore || '0', orderBy: firstItem.orderBy || stIndex + 1, indicatorType: firstItem.indicatorType || '', + disabled: firstItem.disabledSt, // 关键点2: 保留一级指标的禁用状态,确保在回传API格式时不丢失 indicatorNdList: level1Items.map((item, ndIndex) => { // 生成唯一的临时ID const tempNdId = `temp-nd-${generateUUID(16)}-${stIndex}-${ndIndex}`; @@ -171,17 +188,21 @@ const EvaluateTemplateTable: React.FC = ({ score: item.ndScore || '0', isStar: item.isStar || StarLevel.NO, orderBy: item.ndOrderBy || ndIndex + 1, - descScore: item.descScore || '' + descScore: item.descScore || '', + disabled: item.disabledNd, // 关键点3: 保留二级指标的禁用状态,确保在回传API格式时不丢失 }; - }) + }), }; }); + + return result; }; // 初始化数据和获取字典 useEffect(() => { // 获取指标类型字典 fetchIndicatorTypes(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 确保数据中没有重复的key @@ -202,22 +223,24 @@ const EvaluateTemplateTable: React.FC = ({ // 单独处理value变化 useEffect(() => { // 初始化表格数据或value变化时更新 + if (value && value.length > 0) { // 避免不必要的状态更新,比较新旧数据是否相同 const currentValueStr = JSON.stringify(value); - const currentDataSourceApiStr = dataSource.length > 0 ? - JSON.stringify(convertTableDataToApiData(dataSource)) : ''; + const currentDataSourceApiStr = + dataSource.length > 0 ? JSON.stringify(convertTableDataToApiData(dataSource)) : ''; if (currentValueStr !== currentDataSourceApiStr) { const tableData = convertApiDataToTableData(value); - // 保留现有项的key,确保稳定性 if (dataSource && dataSource.length > 0) { const updatedTableData = tableData.map((newItem) => { // 尝试查找对应的现有项,通过stId和ndId匹配 - const existingItem = dataSource.find(existing => - (existing.stId === newItem.stId && existing.ndId === newItem.ndId) || - (existing.baseIndicator === newItem.baseIndicator && existing.subIndicator === newItem.subIndicator) + const existingItem = dataSource.find( + (existing) => + (existing.stId === newItem.stId && existing.ndId === newItem.ndId) || + (existing.baseIndicator === newItem.baseIndicator && + existing.subIndicator === newItem.subIndicator), ); // 如果找到现有项,保留其key和selected状态 @@ -225,7 +248,9 @@ const EvaluateTemplateTable: React.FC = ({ return { ...newItem, key: existingItem.key, - selected: defaultSelectedIds.includes(newItem.ndId || '') ? true : existingItem.selected + selected: defaultSelectedIds.includes(newItem.ndId || '') + ? true + : existingItem.selected, }; } @@ -248,17 +273,17 @@ const EvaluateTemplateTable: React.FC = ({ // 处理defaultSelectedIds变化 useEffect(() => { if (defaultSelectedIds.length > 0 && dataSource.length > 0) { - const newData = dataSource.map(item => ({ + const newData = dataSource.map((item) => ({ ...item, - selected: defaultSelectedIds.includes(item.ndId || '') + selected: defaultSelectedIds.includes(item.ndId || ''), })); - console.log("newData",newData) + setDataSource(newData); // 如果有onSelect回调,传递所有选中的项 if (onSelect) { - const selectedItems = newData.filter(item => item.selected); + const selectedItems = newData.filter((item) => item.selected); // 转换为API格式再传递给父组件 const selectedApiData = convertTableDataToApiData(selectedItems); onSelect(selectedApiData); @@ -268,6 +293,7 @@ const EvaluateTemplateTable: React.FC = ({ // 更新数据源 const updateDataSource = (newData: TableRowItem[]) => { + // 确保每行都有唯一稳定的key const finalData = ensureUniqueKeys(newData); @@ -281,7 +307,6 @@ const EvaluateTemplateTable: React.FC = ({ // 处理勾选状态变化 const handleCheckChange = (record: TableRowItem, checked: boolean) => { - console.log("handleCheckChange") const newData = [...dataSource]; const index = newData.findIndex((item) => item.key === record.key); if (index > -1) { @@ -290,7 +315,7 @@ const EvaluateTemplateTable: React.FC = ({ // 如果有onSelect回调,传递所有选中的项 if (onSelect) { - const selectedItems = newData.filter(item => item.selected); + const selectedItems = newData.filter((item) => item.selected); // 转换为API格式再传递给父组件 const selectedApiData = convertTableDataToApiData(selectedItems); onSelect(selectedApiData); @@ -312,7 +337,11 @@ const EvaluateTemplateTable: React.FC = ({ // 检查是否有重复的baseIndicator const existingNames = newData.map((d) => d.baseIndicator).filter(Boolean); if (existingNames.includes(val)) { - message.warning(intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.message.duplicateName' })); + message.warning( + intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.message.duplicateName', + }), + ); return; } } @@ -351,7 +380,10 @@ const EvaluateTemplateTable: React.FC = ({ } }; - // 添加一级指标 + /** + * 添加一级指标 + * 重要:添加新行时需要正确设置disabledSt和disabledNd属性,否则会导致禁用状态丢失 + */ const addLevel1Indicator = (currentRecord?: TableRowItem) => { // 使用改进后的工具函数生成唯一key const newKey = `level1-${generateUUID(32)}`; @@ -369,16 +401,20 @@ const EvaluateTemplateTable: React.FC = ({ orderBy: dataSource.length + 1, // 确保正确的排序 ndOrderBy: 1, // 设置二级指标初始排序 selected: false, // 默认未选中 + // 关键点4:确保新添加的行也有正确的禁用属性,否则会导致现有禁用状态丢失 + disabledSt: false, // 默认不禁用一级指标 + disabledNd: false, // 默认不禁用二级指标 }; - // 制作数据源的副本,避免直接修改状态 - const newData = [...dataSource]; + const newData = dataSource.map(item => ({ ...item })); // 找到当前记录所在的一级指标组的最后一行 let insertIndex = -1; if (currentRecord && currentRecord.baseIndicator) { // 找到相同baseIndicator的最后一个元素 - const sameGroup = newData.filter((item) => item.baseIndicator === currentRecord.baseIndicator); + const sameGroup = newData.filter( + (item) => item.baseIndicator === currentRecord.baseIndicator, + ); const lastOfGroup = sameGroup[sameGroup.length - 1]; insertIndex = newData.findIndex((item) => item.key === lastOfGroup.key); } else if (currentRecord && currentRecord.key) { @@ -393,6 +429,7 @@ const EvaluateTemplateTable: React.FC = ({ newData.push(newItem); } + updateDataSource(newData); }; @@ -410,7 +447,10 @@ const EvaluateTemplateTable: React.FC = ({ updateDataSource(newData); }; - // 添加二级指标 + /** + * 添加二级指标 + * 重要:添加二级指标时需要从父级继承一级指标的禁用状态,确保同组内禁用状态一致 + */ const addSubIndicator = (parentKey: string) => { // 使用工具函数生成唯一key const newKey = `level2-${generateUUID(16)}`; @@ -418,7 +458,11 @@ const EvaluateTemplateTable: React.FC = ({ const parent = dataSource.find((item) => item.key === parentKey); if (!parent || !parent.baseIndicator) { - message.warning(intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.placeholder.baseIndicator' })); + message.warning( + intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.placeholder.baseIndicator', + }), + ); return; } @@ -432,8 +476,12 @@ const EvaluateTemplateTable: React.FC = ({ ndScore: '0', isStar: StarLevel.NO, descScore: '', - ndOrderBy: dataSource.filter(item => item.baseIndicator === parent.baseIndicator).length + 1, + ndOrderBy: + dataSource.filter((item) => item.baseIndicator === parent.baseIndicator).length + 1, selected: false, // 默认未选中 + // 关键点7:继承父级的一级指标禁用状态,确保同组内禁用状态一致 + disabledSt: parent.disabledSt, // 继承父级的一级指标禁用状态 + disabledNd: false, // 默认不禁用二级指标 }; // 找到当前记录所在的位置 @@ -447,6 +495,7 @@ const EvaluateTemplateTable: React.FC = ({ newData.push(newItem); } + updateDataSource(newData); }; @@ -465,7 +514,12 @@ const EvaluateTemplateTable: React.FC = ({ return dataSource.filter((item) => item.baseIndicator === baseIndicator).length; }; - // 处理合并单元格 + /** + * 处理合并单元格 + * + * 重要:这是解决问题的关键方法,通过检查同组内是否有任何行的disabledSt为true + * 来决定是否禁用整个一级指标组的控件 + */ const renderWithRowSpan = ( content: any, record: TableRowItem, @@ -486,8 +540,19 @@ const EvaluateTemplateTable: React.FC = ({ const index = level1Items.findIndex((item) => item.key === record.key); if (index === 0) { + // 关键点5:检查同组内是否有任何行的disabledSt为true + // 这是解决问题的核心,通过这个判断来决定是否禁用整个一级指标组的控件 + const anyDisabled = level1Items.some(item => !!item.disabledSt); + + // 关键点6:创建一个临时记录,使用组内任何行的禁用状态 + // 这样确保只要组内有一行禁用,整个组的一级指标控件都会显示为禁用状态 + const tempRecord = { + ...record, + disabledSt: anyDisabled + }; + return { - children: render(content, record), + children: render(content, tempRecord), props: { rowSpan: level1Items.length, }, @@ -528,13 +593,17 @@ const EvaluateTemplateTable: React.FC = ({ const typeItem = indicatorTypes.find((item) => item.code === cellContent); return typeItem ? typeItem.dicName : cellContent || '-'; } + return ( handleInputChange(e.target.value, record, 'baseIndicator')} - placeholder={intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.placeholder.baseIndicator' })} + placeholder={intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.placeholder.baseIndicator', + })} /> ); }); }, }, { - title: intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.indicatorDescription' }), + title: intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.indicatorDescription', + }), align: 'center', dataIndex: 'descIndicator', key: 'descIndicator', @@ -581,8 +655,11 @@ const EvaluateTemplateTable: React.FC = ({ return ( handleInputChange(e.target.value, record, 'descIndicator')} - placeholder={intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.placeholder.indicatorDescription' })} + placeholder={intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.placeholder.indicatorDescription', + })} /> ); }); @@ -603,8 +680,9 @@ const EvaluateTemplateTable: React.FC = ({ handleInputChange(val?.toString() || '0', record, 'stScore')} + onChange={(val) => handleInputChange(val?.toString() || '0', record, 'stScore')} style={{ width: '100%' }} /> ); @@ -627,20 +705,31 @@ const EvaluateTemplateTable: React.FC = ({ onClick={() => { addLevel1Indicator(record); }} - title={intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.button.addLevelOne' })} + title={intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.button.addLevelOne', + })} /> removeLevel1Indicator(record)} - okText={intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.confirm.ok' })} - cancelText={intl.formatMessage({ id: 'supplierTemplateManage.evaluateTable.confirm.cancel' })} + okText={intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.confirm.ok', + })} + cancelText={intl.formatMessage({ + id: 'supplierTemplateManage.evaluateTable.confirm.cancel', + })} > -