对接评价结果接口

This commit is contained in:
linxd
2025-06-24 14:00:51 +08:00
parent 0375e369de
commit 6f4efad67b
32 changed files with 2069 additions and 1936 deletions

View File

@ -454,7 +454,7 @@ const EvaluateTemplateTable: React.FC<EvaluateTemplateTableProps> = ({
key: 'index',
width: 50,
render: (_: any, record: TableRowItem, index: number) => {
return renderWithRowSpan(index + 1, record, (cellContent) => cellContent);
return <>{index + 1}</>;
},
},
{

View File

@ -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: '质量控制流程完善'
}
]
}
];
// 详情模式(只读)
<ScoreEvaluationTable
value={evaluationData}
isDetail={true}
/>
// 编辑模式
<ScoreEvaluationTable
value={evaluationData}
onChange={(newData) => 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; // 评分说明
}[];
}
```

View File

@ -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;
}
}

View File

@ -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<ScoreEvaluationTableProps> = ({
value = [],
onChange,
isDetail = false,
loading = false,
}) => {
// 表格数据源
const [dataSource, setDataSource] = useState<TableRowItem[]>([]);
// 将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<string, TableRowItem[]>, 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 (
<InputNumber
min={0}
max={parseFloat(record.ndScore) || 100}
value={text ? parseFloat(String(text)) : undefined}
onChange={(val) => handleInputChange(val, record, 'score')}
style={{ width: '100%' }}
placeholder="请输入评分"
/>
);
},
},
{
title: '评分说明',
dataIndex: 'remark',
key: 'remark',
width: 200,
render: (text: string, record: TableRowItem) => {
if (isDetail) {
return (
<Tooltip title={text}>
<div className="ellipsis-text">{text || '-'}</div>
</Tooltip>
);
}
return (
<TextArea
value={text}
onChange={(e) => handleInputChange(e.target.value, record, 'remark')}
placeholder="请输入评分说明"
autoSize={{ minRows: 1, maxRows: 3 }}
/>
);
},
},
],
},
];
return (
<div className="score-evaluation-table">
<Table
columns={columns}
dataSource={dataSource}
pagination={false}
bordered
rowKey="key"
size="middle"
loading={loading}
scroll={{ x: 'max-content' }}
locale={{ emptyText: '无数据' }}
/>
</div>
);
};
export default ScoreEvaluationTable;

View File

@ -0,0 +1,3 @@
import ScoreEvaluationTable from './ScoreEvaluationTable';
export default ScoreEvaluationTable;