对接评价结果接口
This commit is contained in:
@ -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}</>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
90
src/components/ScoreEvaluationTable/README.md
Normal file
90
src/components/ScoreEvaluationTable/README.md
Normal 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; // 评分说明
|
||||
}[];
|
||||
}
|
||||
```
|
@ -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;
|
||||
}
|
||||
}
|
370
src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx
Normal file
370
src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx
Normal 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;
|
3
src/components/ScoreEvaluationTable/index.ts
Normal file
3
src/components/ScoreEvaluationTable/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import ScoreEvaluationTable from './ScoreEvaluationTable';
|
||||
|
||||
export default ScoreEvaluationTable;
|
Reference in New Issue
Block a user