diff --git a/config/router.config.ts b/config/router.config.ts index c9f095c..4ecb400 100644 --- a/config/router.config.ts +++ b/config/router.config.ts @@ -75,6 +75,15 @@ export default [ }, component: '@/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManageDetail', }, + { + name: 'supplierEvaluateRuleManage', + path: 'supplierEvaluateRuleManage', + meta: { + title: '评价规则管理', + icon: 'icon-liebiaomoshi', + }, + component: '@/pages/supplierEvaluateManage/supplierEvaluateRuleManage/supplierEvaluateRuleManage', + }, { name: 'supplierTaskManage', path: 'supplierTaskManage', @@ -225,7 +234,7 @@ export default [ path: '/backend', name: 'backend', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/backend/workbenches', @@ -257,7 +266,7 @@ export default [ path: '/informationRetrieval', name: 'informationRetrieval', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/informationRetrieval/registrationQuery', @@ -283,7 +292,7 @@ export default [ path: '/informationManagement', name: 'informationManagement', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/informationManagement/SupplierRegisterAgent', @@ -309,7 +318,7 @@ export default [ path: '/admission', name: 'admission', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/admission/admissionManagement', @@ -347,7 +356,7 @@ export default [ path: '/category', name: 'category', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/category/category-manage', @@ -385,7 +394,7 @@ export default [ path: '/supplierBlacklist', name: 'supplierBlacklist', icon: 'smile', - component: '@/layouts/MainLayout', + component: '@/layouts/MainLayout', routes: [ { path: '/supplierBlacklist/manage', diff --git a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx index 8a855c6..239712e 100644 --- a/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx +++ b/src/components/EvaluateTemplateTable/EvaluateTemplateTable.tsx @@ -454,7 +454,7 @@ const EvaluateTemplateTable: React.FC = ({ key: 'index', width: 50, render: (_: any, record: TableRowItem, index: number) => { - return renderWithRowSpan(index + 1, record, (cellContent) => cellContent); + return <>{index + 1}; }, }, { diff --git a/src/components/ScoreEvaluationTable/README.md b/src/components/ScoreEvaluationTable/README.md new file mode 100644 index 0000000..5fcad6f --- /dev/null +++ b/src/components/ScoreEvaluationTable/README.md @@ -0,0 +1,90 @@ +# ScoreEvaluationTable 评分表格组件 + +## 组件介绍 + +ScoreEvaluationTable 是一个用于展示和填写供应商评价得分的表格组件。该组件基于 EvaluateTemplateTable 组件扩展,在二级指标中添加了评分列和说明列,可用于评价结果的展示和评分录入。 + +## 功能特点 + +- 支持一级指标和二级指标的层级展示 +- 支持评分和评分说明的录入和展示 +- 支持详情模式(只读)和编辑模式 +- 支持数据格式的自动转换 +- 自动合并一级指标单元格 + +## 使用方法 + +```tsx +import ScoreEvaluationTable from '@/components/ScoreEvaluationTable'; + +// 示例数据 +const evaluationData = [ + { + id: '1', + baseIndicator: '质量管理', + descIndicator: '质量管理体系完善程度', + score: '30', + indicatorNdList: [ + { + id: '1-1', + subIndicator: '质量管理体系认证', + score: '10', + isStar: '0', + actualScore: '8', + remark: '已获得ISO9001认证' + }, + { + id: '1-2', + subIndicator: '质量控制流程', + score: '10', + isStar: '1', + actualScore: '9', + remark: '质量控制流程完善' + } + ] + } +]; + +// 详情模式(只读) + + +// 编辑模式 + console.log(newData)} + isDetail={false} +/> +``` + +## 属性说明 + +| 属性名 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| value | array | [] | 表格数据,应符合API返回的评价指标数据结构 | +| onChange | function | - | 数据变更回调函数 | +| isDetail | boolean | false | 是否为详情模式(只读) | +| loading | boolean | false | 表格加载状态 | + +## 数据格式 + +组件接受的数据格式应为一个数组,每个元素代表一个一级指标,包含以下字段: + +```typescript +interface IndicatorItem { + id?: string; // 一级指标ID + baseIndicator: string; // 一级指标名称 + descIndicator?: string; // 一级指标说明 + score: string; // 一级指标分值 + indicatorNdList: { // 二级指标列表 + id?: string; // 二级指标ID + subIndicator: string; // 二级指标名称 + score: string; // 二级指标分值 + isStar?: string; // 是否为星号项 + actualScore?: string; // 实际评分 + remark?: string; // 评分说明 + }[]; +} +``` diff --git a/src/components/ScoreEvaluationTable/ScoreEvaluationTable.less b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.less new file mode 100644 index 0000000..b037586 --- /dev/null +++ b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.less @@ -0,0 +1,35 @@ +// 评分表格样式 +.score-evaluation-table { + margin-bottom: 16px; + + .ant-table-thead > tr > th { + background-color: #f5f5f5; + font-weight: 500; + } + + .ant-table-tbody > tr > td { + padding: 8px; + } + + .ellipsis-text { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + // 评分列样式 + .low-score { + color: #ff4d4f; + } + + .high-score { + color: #52c41a; + } + + // 星号项样式 + .star-item { + color: #faad14; + margin-left: 4px; + } +} diff --git a/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx new file mode 100644 index 0000000..d3c1d75 --- /dev/null +++ b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx @@ -0,0 +1,370 @@ +// 供应商评价得分表格组件 +// 用于展示和填写供应商评价得分,基于EvaluateTemplateTable组件扩展 +// 在二级指标中添加了评分列和说明列 +import React, { useState, useEffect } from 'react'; +import { + Table, + Input, + InputNumber, + Typography, + Tooltip, +} from 'antd'; +import './ScoreEvaluationTable.less'; + +const { Text } = Typography; +const { TextArea } = Input; + +/** + * 评分表格组件属性 + */ +interface ScoreEvaluationTableProps { + /** + * 表格数据,应符合API返回的评价指标数据结构 + */ + value?: any[]; + /** + * 数据变更回调函数 + */ + onChange?: (value: any[]) => void; + /** + * 是否为详情模式(只读) + */ + isDetail?: boolean; + /** + * 表格加载状态 + */ + loading?: boolean; +} + +/** + * 内部使用的表格行数据结构 + */ +interface TableRowItem { + key: string; + stId?: string; // 一级指标ID + ndId?: string; // 二级指标ID + baseIndicator: string; // 一级指标名称 + descIndicator?: string; // 一级指标说明 + stScore: string; // 一级指标分值 + subIndicator: string; // 二级指标名称 + ndScore: string; // 二级指标分值 + isStar?: string; // 是否为星号项 + score?: string | number; // 实际评分 + remark?: string; // 评分说明 +} + +/** + * 供应商评价得分表格组件 + * 用于展示和填写供应商评价得分,支持详情模式和编辑模式 + */ +const ScoreEvaluationTable: React.FC = ({ + value = [], + onChange, + isDetail = false, + loading = false, +}) => { + // 表格数据源 + const [dataSource, setDataSource] = useState([]); + + // 将API数据转换为表格数据 + const convertApiDataToTableData = (apiData: any[]): TableRowItem[] => { + // 检查数据是否已经是扁平化的表格数据格式 + if (apiData.length > 0 && 'subIndicator' in apiData[0]) { + return apiData as TableRowItem[]; + } + + // 如果是嵌套结构,需要扁平化处理 + if (apiData.length > 0 && 'indicatorNdList' in apiData[0]) { + const flattenedData: TableRowItem[] = []; + + apiData.forEach((stItem: any, stIndex: number) => { + if (!stItem.indicatorNdList || stItem.indicatorNdList.length === 0) { + // 如果没有二级指标,添加一个空的二级指标行 + flattenedData.push({ + key: `${stItem.id || stIndex}-0`, + stId: stItem.id, + baseIndicator: stItem.baseIndicator || '', + descIndicator: stItem.descIndicator || '', + stScore: stItem.score || '0', + subIndicator: '', + ndScore: '0', + score: stItem.actualScore || '', + remark: stItem.remark || '', + }); + } else { + // 处理二级指标 + stItem.indicatorNdList.forEach((ndItem: any, ndIndex: number) => { + flattenedData.push({ + key: `${stItem.id || stIndex}-${ndItem.id || ndIndex}`, + stId: stItem.id, + ndId: ndItem.id, + baseIndicator: stItem.baseIndicator || '', + descIndicator: stItem.descIndicator || '', + stScore: stItem.score || '0', + subIndicator: ndItem.subIndicator || '', + ndScore: ndItem.score || '0', + isStar: ndItem.isStar || '', + score: ndItem.actualScore || '', + remark: ndItem.remark || '', + }); + }); + } + }); + + return flattenedData; + } + + return []; + }; + + // 将表格数据转换回API格式 + const convertTableDataToApiData = (tableData: TableRowItem[]): any[] => { + // 按一级指标分组 + const groupedByLevel1 = tableData.reduce((acc: Record, item: TableRowItem) => { + const groupKey = item.baseIndicator || `empty-${item.key}`; + + if (!acc[groupKey]) { + acc[groupKey] = []; + } + acc[groupKey].push(item); + return acc; + }, {}); + + // 转换为API需要的格式 + return Object.keys(groupedByLevel1).map((groupKey, stIndex) => { + const level1Items = groupedByLevel1[groupKey]; + const firstItem = level1Items[0]; + + return { + id: firstItem.stId || `temp-st-${stIndex}`, + baseIndicator: firstItem.baseIndicator || '', + descIndicator: firstItem.descIndicator || '', + score: firstItem.stScore || '0', + indicatorNdList: level1Items.map((item, ndIndex) => { + return { + id: item.ndId || `temp-nd-${stIndex}-${ndIndex}`, + subIndicator: item.subIndicator || '', + score: item.ndScore || '0', + isStar: item.isStar || '', + actualScore: item.score || '', + remark: item.remark || '' + }; + }) + }; + }); + }; + + // 单独处理value变化 + useEffect(() => { + if (value && value.length > 0) { + const tableData = convertApiDataToTableData(value); + setDataSource(tableData); + } else { + setDataSource([]); + } + }, [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) { + newData[index] = { ...newData[index], [field]: val }; + updateDataSource(newData); + } + }; + + // 获取一级指标的行数 + const getLevel1RowSpan = (baseIndicator: string) => { + if (!baseIndicator) return 1; + return dataSource.filter((item) => item.baseIndicator === baseIndicator).length; + }; + + const columns = [ + { + title: '一级指标', + children: [ + { + title: '序号', + dataIndex: 'index', + align: 'center' as const, + key: 'index', + width: 50, + render: (_: any, record: TableRowItem, index: number) => { + return <>{index + 1}; + }, + }, + { + title: '基本指标', + align: 'center' as const, + dataIndex: 'baseIndicator', + key: 'baseIndicator', + width: 150, + render: (text: string, record: TableRowItem) => { + if (!record.baseIndicator) return text || '-'; + + // 查找相同baseIndicator的所有项 + const level1Items = dataSource.filter((item) => item.baseIndicator === record.baseIndicator); + const index = level1Items.findIndex((item) => item.key === record.key); + + if (index === 0) { + return { + children: text || '-', + props: { rowSpan: level1Items.length }, + }; + } + + return { + props: { rowSpan: 0 }, + }; + }, + }, + { + title: '指标说明', + align: 'center' as const, + dataIndex: 'descIndicator', + key: 'descIndicator', + width: 200, + render: (text: string, record: TableRowItem) => { + if (!record.baseIndicator) return text || '-'; + + // 查找相同baseIndicator的所有项 + const level1Items = dataSource.filter((item) => item.baseIndicator === record.baseIndicator); + const index = level1Items.findIndex((item) => item.key === record.key); + + if (index === 0) { + return { + children: text || '-', + props: { rowSpan: level1Items.length }, + }; + } + + return { + props: { rowSpan: 0 }, + }; + }, + }, + { + title: '分值', + align: 'center' as const, + dataIndex: 'stScore', + key: 'stScore', + width: 80, + render: (text: string, record: TableRowItem) => { + if (!record.baseIndicator) return text || '0'; + + // 查找相同baseIndicator的所有项 + const level1Items = dataSource.filter((item) => item.baseIndicator === record.baseIndicator); + const index = level1Items.findIndex((item) => item.key === record.key); + + if (index === 0) { + return { + children: text || '0', + props: { rowSpan: level1Items.length }, + }; + } + + return { + props: { rowSpan: 0 }, + }; + }, + }, + ], + }, + { + title: '二级指标', + children: [ + { + title: '细分指标', + dataIndex: 'subIndicator', + key: 'subIndicator', + align: 'center' as const, + width: 200, + render: (text: string) => text || '-', + }, + { + title: '分值', + dataIndex: 'ndScore', + key: 'ndScore', + width: 80, + align: 'center' as const, + render: (text: string) => text || '0', + }, + { + title: '评分', + dataIndex: 'score', + key: 'score', + width: 100, + align: 'center' as const, + render: (text: string | number, record: TableRowItem) => { + if (isDetail) { + return text || '-'; + } + return ( + handleInputChange(val, record, 'score')} + style={{ width: '100%' }} + placeholder="请输入评分" + /> + ); + }, + }, + { + title: '评分说明', + dataIndex: 'remark', + key: 'remark', + width: 200, + render: (text: string, record: TableRowItem) => { + if (isDetail) { + return ( + +
{text || '-'}
+
+ ); + } + return ( +