// 供应商评价 模板管理新增中的table import React, { useState, useEffect } from 'react'; import { Table, Input, Button, Select, Form, InputNumber, Upload, message, Divider, Switch, } from 'antd'; import { PlusOutlined, MinusCircleOutlined, UploadOutlined, PlusCircleOutlined, DeleteOutlined, } from '@ant-design/icons'; import { getDictList, DictItem } from '@/servers/api/dicts'; import { StarLevel, StarLevelText } from '@/dicts/supplierTemplateDict'; import './EvaluateTemplateTable.less'; // 注意:这里我们使用any类型是为了灵活处理多种数据格式 // 实际上,API的类型定义在 src/servers/api/typings.d.ts 中的 SupplierEvaluate 命名空间下 // 包含了 IndicatorStItem 和 IndicatorNdItem 类型 const { Option } = Select; const { TextArea } = Input; interface EvaluateTemplateTableProps { value?: any[]; onChange?: (value: any[]) => void; isDetail?: boolean; // 是否详情展示用,如果为true则将input都改为text展示并且将操作列隐藏 } // 内部使用的数据结构,扁平化后的行数据 interface TableRowItem { key: string; stId?: string; // 一级指标ID ndId?: string; // 二级指标ID baseIndicator: string; descIndicator?: string; stScore: string; indicatorType?: string; subIndicator: string; ndScore: string; isStar: string; desc?: string; orderBy?: number; ndOrderBy?: number; } const EvaluateTemplateTable: React.FC = ({ value = [], onChange, isDetail = false, }) => { const [dataSource, setDataSource] = useState([]); const [form] = Form.useForm(); const [indicatorTypes, setIndicatorTypes] = useState([]); const [loadingTypes, setLoadingTypes] = useState(false); // 获取指标类型字典 const fetchIndicatorTypes = async () => { setLoadingTypes(true); try { const res = await getDictList('Indicator_type'); if (res.success && res.data) { setIndicatorTypes(res.data); } else { message.error('获取指标类型失败'); } } catch (error) { console.error('获取指标类型失败:', error); message.error('获取指标类型失败'); } finally { setLoadingTypes(false); } }; // 将API数据转换为表格数据 const convertApiDataToTableData = (apiData: any[]): TableRowItem[] => { // 检查数据是否已经是扁平化的表格数据格式 if (apiData.length > 0 && 'subIndicator' in apiData[0]) { return apiData.map((item, index) => ({ key: item.id || `row-${index}`, stId: item.stId, 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 })); } // 如果是嵌套结构,需要扁平化处理 if (apiData.length > 0 && 'indicatorNdList' in apiData[0]) { const flattenedData: TableRowItem[] = []; apiData.forEach((stItem: any) => { stItem.indicatorNdList.forEach((ndItem: any, ndIndex: number) => { flattenedData.push({ key: ndItem.id || `${stItem.id}-${ndIndex}`, stId: stItem.id, ndId: ndItem.id, baseIndicator: stItem.baseIndicator, descIndicator: stItem.descIndicator, stScore: stItem.score, indicatorType: stItem.indicatorType, subIndicator: ndItem.subIndicator, ndScore: ndItem.score, isStar: ndItem.isStar, desc: ndItem.desc, orderBy: typeof stItem.orderBy === 'string' ? parseInt(stItem.orderBy) : stItem.orderBy, ndOrderBy: typeof ndItem.orderBy === 'string' ? parseInt(ndItem.orderBy) : ndItem.orderBy }); }); }); 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 []; }; // 将表格数据转换回API格式 const convertTableDataToApiData = (tableData: TableRowItem[]): any[] => { // 按一级指标分组 const groupedByLevel1 = tableData.reduce((acc: Record, item: TableRowItem) => { const baseIndicator = item.baseIndicator; if (!baseIndicator) return acc; if (!acc[baseIndicator]) { acc[baseIndicator] = []; } acc[baseIndicator].push(item); return acc; }, {}); // 转换为API需要的格式 return Object.keys(groupedByLevel1).map((baseIndicator, stIndex) => { const level1Items = groupedByLevel1[baseIndicator]; 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 })) }; return stItem; }); }; // 初始化数据和获取字典 useEffect(() => { // 获取指标类型字典 fetchIndicatorTypes(); // 初始化表格数据 if (value && value.length > 0) { const tableData = convertApiDataToTableData(value); setDataSource(tableData); } }, [value]); // 更新数据源 const updateDataSource = (newData: TableRowItem[]) => { setDataSource(newData); if (onChange) { // 转换回API格式再传递给父组件 const apiData = convertTableDataToApiData(newData); onChange(apiData); } }; // 处理输入变化 const handleInputChange = (val: any, record: TableRowItem, field: string) => { const newData = [...dataSource]; const index = newData.findIndex((item) => item.key === record.key); if (index > -1) { const item = newData[index]; // 特殊处理baseIndicator字段,确保唯一性 if (field === 'baseIndicator') { // 如果之前为空,现在有值,则视为新的一级指标 if (!item.baseIndicator && val) { // 检查是否有重复的baseIndicator const existingNames = newData.map((d) => d.baseIndicator).filter(Boolean); if (existingNames.includes(val)) { message.warning('指标名称已存在,请使用不同的名称'); return; } } // 更新同组内所有行的一级指标信息 if (item.baseIndicator) { const oldName = item.baseIndicator; newData.forEach((row, i) => { if (row.baseIndicator === oldName) { newData[i] = { ...row, baseIndicator: val }; } }); } else { newData[index] = { ...item, [field]: val }; } } // 处理其他一级指标字段,需要同步到同组的所有行 else if (['descIndicator', 'stScore', 'indicatorType'].includes(field)) { const baseIndicator = item.baseIndicator; if (baseIndicator) { newData.forEach((row, i) => { if (row.baseIndicator === baseIndicator) { newData[i] = { ...row, [field]: val }; } }); } else { newData[index] = { ...item, [field]: val }; } } // 处理二级指标字段,只更新当前行 else { newData[index] = { ...item, [field]: val }; } updateDataSource(newData); } }; // 添加一级指标 const addLevel1Indicator = (currentRecord?: TableRowItem) => { const newKey = `level1-${Date.now()}`; const newItem: TableRowItem = { key: newKey, baseIndicator: '', // 初始为空 descIndicator: '', stScore: '0', indicatorType: '', subIndicator: '', ndScore: '0', isStar: StarLevel.NO, desc: '', }; // 找到当前记录所在的位置 const newData = [...dataSource]; // 找到当前记录所在的一级指标组的最后一行 let insertIndex = -1; if (currentRecord && currentRecord.baseIndicator) { // 找到相同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) { // 如果是新增的空白行,直接在其后插入 insertIndex = newData.findIndex((item) => item.key === currentRecord.key); } // 如果找到了位置,在该位置后插入,否则添加到末尾 if (insertIndex !== -1) { newData.splice(insertIndex + 1, 0, newItem); } else { newData.push(newItem); } updateDataSource(newData); }; // 删除一级指标 const removeLevel1Indicator = (record: TableRowItem) => { // 如果baseIndicator为空,只删除当前行 if (!record.baseIndicator) { const newData = dataSource.filter((item) => item.key !== record.key); updateDataSource(newData); return; } // 删除所有具有相同baseIndicator的行 const newData = dataSource.filter((item) => item.baseIndicator !== record.baseIndicator); updateDataSource(newData); }; // 添加二级指标 const addSubIndicator = (parentKey: string) => { const newKey = `level2-${Date.now()}`; const parent = dataSource.find((item) => item.key === parentKey); if (!parent || !parent.baseIndicator) { message.warning('请先填写一级指标名称'); return; } const newItem: TableRowItem = { key: newKey, baseIndicator: parent.baseIndicator, descIndicator: parent.descIndicator, stScore: parent.stScore, indicatorType: parent.indicatorType, subIndicator: '', ndScore: '0', isStar: StarLevel.NO, desc: '', }; // 找到当前记录所在的位置 const newData = [...dataSource]; const parentIndex = newData.findIndex((item) => item.key === parentKey); // 插入到父级之后 if (parentIndex !== -1) { newData.splice(parentIndex + 1, 0, newItem); } else { newData.push(newItem); } updateDataSource(newData); }; // 删除二级指标 const removeSubIndicator = (key: string) => { const newData = dataSource.filter((item) => item.key !== key); updateDataSource(newData); }; // 获取一级指标的行数 const getLevel1RowSpan = (baseIndicator: string) => { // 如果baseIndicator为空,返回1(不合并) if (!baseIndicator) { return 1; } return dataSource.filter((item) => item.baseIndicator === baseIndicator).length; }; // 处理合并单元格 const renderWithRowSpan = ( content: any, record: TableRowItem, render: (cellContent: any, record: TableRowItem) => React.ReactNode, ) => { // 如果baseIndicator为空,不进行合并 if (!record.baseIndicator) { return { children: render(content, record), props: { rowSpan: 1, }, }; } // 查找相同baseIndicator的所有项 const level1Items = dataSource.filter((item) => item.baseIndicator === record.baseIndicator); const index = level1Items.findIndex((item) => item.key === record.key); if (index === 0) { return { children: render(content, record), props: { rowSpan: level1Items.length, }, }; } return { props: { rowSpan: 0, }, }; }; const columns = [ { title: '一级指标', children: [ { title: '序号', dataIndex: 'index', align: 'center', key: 'index', width: 50, render: (_: any, record: TableRowItem, index: number) => { return renderWithRowSpan(index + 1, record, (cellContent) => cellContent); }, }, { title: '类型', dataIndex: 'indicatorType', align: 'center', key: 'indicatorType', width: 120, render: (text: string, record: TableRowItem) => { return renderWithRowSpan(text, record, (cellContent) => { if (isDetail) { // 在详情模式下,查找并显示字典中对应的名称 const typeItem = indicatorTypes.find((item) => item.code === cellContent); return typeItem ? typeItem.dicName : cellContent || '-'; } return ( ); }); }, }, { title: '基本指标', align: 'center', dataIndex: 'baseIndicator', key: 'baseIndicator', width: 150, render: (text: string, record: TableRowItem) => { return renderWithRowSpan(text, record, (cellContent) => { if (isDetail) { return cellContent || '-'; } return ( handleInputChange(e.target.value, record, 'baseIndicator')} placeholder="请输入基本指标" /> ); }); }, }, { title: '指标说明', align: 'center', dataIndex: 'descIndicator', key: 'descIndicator', width: 200, render: (text: string, record: TableRowItem) => { return renderWithRowSpan(text, record, (cellContent) => { if (isDetail) { return cellContent || '-'; } return ( handleInputChange(e.target.value, record, 'descIndicator')} placeholder="请输入指标说明" /> ); }); }, }, { title: '分值', align: 'center', dataIndex: 'stScore', key: 'stScore', width: 80, render: (text: string, record: TableRowItem) => { return renderWithRowSpan(text, record, (cellContent) => { if (isDetail) { return cellContent || '-'; } return ( handleInputChange(val?.toString() || '0', record, 'stScore')} style={{ width: '100%' }} /> ); }); }, }, { title: '操作', key: 'level1Action', align: 'center', width: 100, render: (_: any, record: TableRowItem) => { if (isDetail) return null; return renderWithRowSpan(null, record, () => (
)); }, }, ].filter((col) => !(isDetail && col.key === 'level1Action')), }, { title: '二级指标', children: [ { title: '细分指标', dataIndex: 'subIndicator', key: 'subIndicator', align: 'center', width: 200, render: (text: string, record: TableRowItem) => { if (isDetail) { return text || '-'; } return ( handleInputChange(e.target.value, record, 'subIndicator')} placeholder="请输入细分指标" /> ); }, }, { title: '分值', dataIndex: 'ndScore', key: 'ndScore', width: 80, align: 'center', render: (text: string, record: TableRowItem) => { if (isDetail) { return text || '-'; } return ( handleInputChange(val?.toString() || '0', record, 'ndScore')} style={{ width: '100%' }} /> ); }, }, { title: '星号项', dataIndex: 'isStar', key: 'isStar', align: 'center', width: 80, render: (text: string, record: TableRowItem) => { if (isDetail) { return text === StarLevel.YES ? '*' : ''; } return ( ); }, }, { title: '评分说明', dataIndex: 'desc', align: 'center', key: 'desc', render: (text: string, record: TableRowItem) => { if (isDetail) { return text || '-'; } return (