评价打分

This commit is contained in:
linxd
2025-06-27 15:02:47 +08:00
parent ebacfc4135
commit ad241f7adb
14 changed files with 1110 additions and 200 deletions

View File

@ -5,5 +5,7 @@ export default defineConfig({
define: {
UPLOAD_URL: '/upload',
REQUEST_BASE: '/api',
// 下载文件时用到,直接window.open 拼上接口地址+参数
SERVER_BASE: 'http://10.0.0.10:18012',
},
});

View File

@ -5,5 +5,7 @@ export default defineConfig({
define:{
UPLOAD_URL: '/upload',
REQUEST_BASE: '/api',
// 下载文件时用到,直接window.open 拼上接口地址+参数
SERVER_BASE: 'http://10.0.0.10:18012',
},
});

View File

@ -122,6 +122,16 @@ export default [
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore',
},
{
name: 'supplierEvaluateScoreDetail',
path: 'supplierEvaluateScoreDetail',
meta: {
title: '评价打分详情',
hide: true,
icon: 'icon-liebiaomoshi',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScoreDetail',
},
{
name: 'supplierEvaluateResult',
path: 'supplierEvaluateResult',

View File

@ -73,6 +73,7 @@
"dva": "2.4.1",
"echarts": "^5.2.2",
"echarts-for-react": "^3.0.2",
"file-saver": "^2.0.5",
"lodash": "4.17.21",
"moment": "^2.29.4",
"omit.js": "2.0.2",

7
pnpm-lock.yaml generated
View File

@ -59,6 +59,9 @@ dependencies:
echarts-for-react:
specifier: ^3.0.2
version: 3.0.2(echarts@5.6.0)(react@16.14.0)
file-saver:
specifier: ^2.0.5
version: 2.0.5
lodash:
specifier: 4.17.21
version: 4.17.21
@ -7936,6 +7939,10 @@ packages:
flat-cache: 3.2.0
dev: true
/file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
requiresBuild: true

View File

@ -69,7 +69,7 @@ const ScoreEvaluationTable: React.FC<ScoreEvaluationTableProps> = ({
// 将API数据转换为表格数据
const convertApiDataToTableData = (apiData: any[]): TableRowItem[] => {
// 检查数据是否已经是扁平化的表格数据格式
if (apiData.length > 0 && 'subIndicator' in apiData[0]) {
if (apiData.length > 0 && 'subIndicator' in apiData[0] && !('indicatorNdList' in apiData[0])) {
return apiData as TableRowItem[];
}
@ -84,7 +84,7 @@ const ScoreEvaluationTable: React.FC<ScoreEvaluationTableProps> = ({
key: `${stItem.id || stIndex}-0`,
stId: stItem.id,
baseIndicator: stItem.baseIndicator || '',
descIndicator: stItem.descIndicator || '',
descIndicator: stItem.indicatorDesc || '',
stScore: stItem.score || '0',
subIndicator: '',
ndScore: '0',
@ -94,19 +94,22 @@ const ScoreEvaluationTable: React.FC<ScoreEvaluationTableProps> = ({
} 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 || '',
});
// 确保ndItem是一个对象而不是直接渲染
if (typeof ndItem === 'object' && ndItem !== null) {
flattenedData.push({
key: `${stItem.id || stIndex}-${ndItem.id || ndIndex}`,
stId: stItem.id,
ndId: ndItem.id,
baseIndicator: stItem.baseIndicator || '',
descIndicator: stItem.indicatorDesc || '',
stScore: stItem.score || '0',
subIndicator: ndItem.subIndicator || '',
ndScore: ndItem.subScore || '0',
isStar: ndItem.starIndicator || '',
score: ndItem.scoreNum || '',
remark: ndItem.remark || '',
});
}
});
}
});
@ -138,15 +141,15 @@ const ScoreEvaluationTable: React.FC<ScoreEvaluationTableProps> = ({
return {
id: firstItem.stId || `temp-st-${stIndex}`,
baseIndicator: firstItem.baseIndicator || '',
descIndicator: firstItem.descIndicator || '',
indicatorDesc: 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 || '',
subScore: item.ndScore || '0',
starIndicator: item.isStar || '0',
scoreNum: item.score || '',
remark: item.remark || ''
};
})

View File

@ -44,14 +44,7 @@ const SupplierEvaluateResultInfo: React.FC = () => {
);
const [evaluateRules, setEvaluateRules] = useState<API.EvaluateRuleItem[]>([]);
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 获取上级页面传递的数据
useEffect(() => {

View File

@ -6,7 +6,8 @@
margin-bottom: 16px;
.titleSection {
flex: 1;
display: flex;
align-items: center;
.pageTitle {
margin-bottom: 0;
@ -15,7 +16,41 @@
.actionSection {
display: flex;
justify-content: flex-end;
align-items: center;
}
}
.submitButtonContainer {
display: flex;
justify-content: center;
margin-top: 24px;
}
:global {
.filter-action-row {
margin-bottom: 16px;
.filter-form {
.filter-btns {
margin-left: auto;
}
}
}
.content-area {
background-color: #fff;
padding: 24px;
margin-bottom: 24px;
border-radius: 2px;
}
}
/* Add styles for the tables */
:global(.ant-table) {
margin-top: 16px;
}
/* Add styles for the tabs */
:global(.ant-tabs-nav) {
margin-bottom: 16px;
}

View File

@ -1,203 +1,453 @@
// 供应商评价打分
import React, { useState, useEffect } from 'react';
import { Button, Card, Form, message, Typography, Spin, Space } from 'antd';
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
import { history, useLocation } from 'umi';
import ScoreEvaluationTable from '@/components/ScoreEvaluationTable';
import {
Button,
Form,
message,
Typography,
Space,
Tabs,
Input,
DatePicker,
Select,
Table,
} from 'antd';
import { SearchOutlined, DeleteOutlined } from '@ant-design/icons';
import { history } from 'umi';
import styles from './supplierEvaluateScore.less';
import { getSupplierScoreDetail, submitEvaluateScore } from '@/servers/api/supplierEvaluate';
import {
getSupplierDimension,
getTaskPage,
} from '@/servers/api/supplierEvaluate';
const { Title } = Typography;
const { TabPane } = Tabs;
const { RangePicker } = DatePicker;
const { Option } = Select;
const SupplierEvaluateScore: React.FC = () => {
const [form] = Form.useForm();
const location = useLocation<{
record: API.EvaluateSupplierRecord;
parentRecord: API.EvaluateTaskRecord;
}>();
const [loading, setLoading] = useState<boolean>(false);
const [submitting, setSubmitting] = useState<boolean>(false);
const [supplierRecord, setSupplierRecord] = useState<API.EvaluateSupplierRecord | null>(null);
const [parentRecord, setParentRecord] = useState<API.EvaluateTaskRecord | null>(null);
const [scoreDetail, setScoreDetail] = useState<API.EvaluateScoreDetailData | null>(null);
const [scoreData, setScoreData] = useState<any[]>([]);
const [filterForm] = Form.useForm();
// 新增状态
const [activeTab, setActiveTab] = useState<string>('supplier');
const [supplierTableData, setSupplierTableData] = useState<
supplierEvaluateScore.SupplierDimensionRecord[]
>([]);
const [taskTableData, setTaskTableData] = useState<supplierEvaluateScore.TaskPageRecord[]>([]);
const [supplierTableLoading, setSupplierTableLoading] = useState<boolean>(false);
const [taskTableLoading, setTaskTableLoading] = useState<boolean>(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
// 获取上级页面传递的数据
useEffect(() => {
if (location.state?.record) {
setSupplierRecord(location.state.record);
}
if (location.state?.parentRecord) {
setParentRecord(location.state.parentRecord);
}
}, [location]);
// 构建查询参数
const buildQueryParams = () => {
const values = filterForm.getFieldsValue();
// 将API数据转换为ScoreEvaluationTable组件所需的格式
const formatDataForScoreTable = (data: API.EvaluateScoreDetailData | null) => {
if (!data?.taskIndicatorVo) return [];
return data.taskIndicatorVo.map(indicator => {
return {
baseIndicator: indicator.baseIndicator,
descIndicator: indicator.indicatorDesc,
score: indicator.score,
indicatorNdList: indicator.subIndicator?.map(subItem => {
return {
subIndicator: subItem.subIndicator,
score: subItem.subScore,
isStar: subItem.starIndicator,
id: subItem.id,
actualScore: subItem.scoreNum || '',
remark: subItem.remark || ''
};
}) || []
if (activeTab === 'supplier') {
const params: supplierEvaluateScore.SupplierDimensionRequest = {
basePageRequest: {
pageNo: pagination.current,
pageSize: pagination.pageSize,
},
keyword: values.keyword || undefined,
status: values.status || undefined,
};
// 处理时间范围
if (values.evaluationTime && values.evaluationTime.length === 2) {
params.startTime = values.evaluationTime[0].format('YYYY-MM-DD');
params.endTime = values.evaluationTime[1].format('YYYY-MM-DD');
}
return params;
} else {
const params: supplierEvaluateScore.TaskPageRequest = {
basePageRequest: {
pageNo: pagination.current,
pageSize: pagination.pageSize,
},
keyword: values.keyword || undefined,
status: values.status || undefined,
};
// 处理时间范围
if (values.evaluationTime && values.evaluationTime.length === 2) {
params.startTime = values.evaluationTime[0].format('YYYY-MM-DD');
params.endTime = values.evaluationTime[1].format('YYYY-MM-DD');
}
return params;
}
};
// 获取供应商维度列表
const fetchSupplierDimensionList = async (params: any) => {
setSupplierTableLoading(true);
try {
const response = await getSupplierDimension(params);
if (response.success && response.data) {
setSupplierTableData(response.data.records || []);
setPagination({
...pagination,
current: response.data.current || 1,
total: response.data.total || 0,
});
} else {
message.error(response.message || '获取供应商列表失败');
setSupplierTableData([]);
}
} catch (error) {
console.error('获取供应商列表失败:', error);
message.error('获取供应商列表失败');
setSupplierTableData([]);
} finally {
setSupplierTableLoading(false);
}
};
// 获取任务列表
const fetchTaskList = async (params: any) => {
setTaskTableLoading(true);
try {
const response = await getTaskPage(params);
if (response.success && response.data) {
setTaskTableData(response.data.records || []);
setPagination({
...pagination,
current: response.data.current || 1,
total: response.data.total || 0,
});
} else {
message.error(response.message || '获取任务列表失败');
setTaskTableData([]);
}
} catch (error) {
console.error('获取任务列表失败:', error);
message.error('获取任务列表失败');
setTaskTableData([]);
} finally {
setTaskTableLoading(false);
}
};
// 处理搜索按钮点击
const handleSearch = async () => {
const params = buildQueryParams();
if (activeTab === 'supplier') {
fetchSupplierDimensionList(params);
} else {
fetchTaskList(params);
}
};
// 初始化加载数据
useEffect(() => {
handleSearch();
}, [activeTab]);
// 处理标签页切换
const handleTabChange = (key: string) => {
setActiveTab(key);
setPagination({
current: 1,
pageSize: 10,
total: 0,
});
filterForm.resetFields();
};
// 处理重置按钮点击
const handleReset = () => {
filterForm.resetFields();
setPagination({
current: 1,
pageSize: 10,
total: 0,
});
handleSearch();
};
// 处理分页变化
const handleTableChange = (page: any) => {
setPagination({
...pagination,
current: page.current,
pageSize: page.pageSize,
});
const params = buildQueryParams();
params.basePageRequest.pageNo = page.current;
params.basePageRequest.pageSize = page.pageSize;
if (activeTab === 'supplier') {
fetchSupplierDimensionList(params);
} else {
fetchTaskList(params);
}
};
// 跳转到评分页面
const goToScoring = (record: any, recordType: 'supplier' | 'task', mode: 'view' | 'score') => {
// 构建路由状态参数
const state: {
record: any;
mode: 'view' | 'score';
recordType: 'supplier' | 'task';
} = {
record: record,
mode,
recordType: recordType,
};
if (recordType === 'task') {
state.record.evaluateTaskId = record.id;
history.push({
pathname: '/supplierEvaluate/supplierEvaluateResultInfo',
state: {record},
});
return;
}
history.push({
pathname: '/supplierEvaluate/supplierEvaluateScoreDetail',
state: state,
});
};
// 获取得分明细数据
const fetchScoreDetail = async () => {
if (!supplierRecord?.id) {
message.error('缺少必要参数,无法获取数据');
// 导出功能
const handleExport = async (record: any) => {
if (!record?.id) {
message.error('缺少必要参数,无法导出');
return;
}
setLoading(true);
try {
const response = await getSupplierScoreDetail(supplierRecord.id);
if (response.success && response.data) {
setScoreDetail(response.data);
// 转换数据格式
const formattedData = formatDataForScoreTable(response.data);
setScoreData(formattedData);
} else {
message.error(response.message || '获取评价得分明细失败');
}
window.open(
`${SERVER_BASE}/coscoEvaluate/supplier/export?evaluateTaskId=${record.id}`,
'_blank',
'noopener,noreferrer',
);
} catch (error) {
console.error('获取评价得分明细失败:', error);
message.error('获取评价得分明细失败');
} finally {
setLoading(false);
console.error('导出失败:', error);
message.error('导出失败,请稍后再试');
}
};
// 供应商记录加载完成后,获取得分明细
useEffect(() => {
if (supplierRecord?.id) {
fetchScoreDetail();
}
}, [supplierRecord]);
// 供应商Tab的表格列
const supplierColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render: (_: any, __: any, index: number) =>
(pagination.current - 1) * pagination.pageSize + index + 1,
},
{
title: '供应商名称',
dataIndex: 'name',
key: 'name',
},
{
title: '评价主题',
dataIndex: 'evaluateTheme',
key: 'evaluateTheme',
},
{
title: '发起单位',
dataIndex: 'tenantName',
key: 'tenantName',
},
{
title: '评价开始时间',
dataIndex: 'startTime',
key: 'startTime',
},
{
title: '评价结束时间',
dataIndex: 'endTime',
key: 'endTime',
},
{
title: '评价状态',
dataIndex: 'statusName',
key: 'statusName',
},
{
title: '提交状态',
dataIndex: 'submissionStatus',
key: 'submissionStatus',
},
{
title: '提交时间',
dataIndex: 'submissionTime',
key: 'submissionTime',
},
{
title: '操作',
key: 'action',
render: (text: string, record: any) => (
<Space>
<Button type="link" onClick={() => goToScoring(record, 'supplier', 'view')}>
</Button>
{record.status === '1' && ( // 只有待评分状态才显示打分按钮
<Button type="link" onClick={() => goToScoring(record, 'supplier', 'score')}>
</Button>
)}
</Space>
),
},
];
// 返回上一页
const handleBack = () => {
history.goBack();
};
// 评价任务Tab的表格列
const taskColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render: (_: any, __: any, index: number) =>
(pagination.current - 1) * pagination.pageSize + index + 1,
},
{
title: '评价主题',
dataIndex: 'evaluateTheme',
key: 'evaluateTheme',
},
{
title: '发起单位',
dataIndex: 'deptName',
key: 'deptName',
},
{
title: '评价开始时间',
dataIndex: 'startTime',
key: 'startTime',
},
{
title: '评价结束时间',
dataIndex: 'endTime',
key: 'endTime',
},
{
title: '评价状态',
dataIndex: 'statusName',
key: 'statusName',
},
{
title: '操作',
key: 'action',
render: (text: string, record: any) => (
<Space>
<Button type="link" onClick={() => goToScoring(record, 'task', 'view')}>
</Button>
<Button type="link" onClick={() => handleExport(record)}>
</Button>
</Space>
),
},
];
// 处理评分数据变更
const handleScoreDataChange = (newData: any[]) => {
setScoreData(newData);
};
// 筛选表单
const renderFilterForm = () => (
<div className="filter-action-row">
<div className="filter-form">
<Form form={filterForm} layout="inline">
<Form.Item name="keyword" label="关键字">
<Input
placeholder={
activeTab === 'supplier' ? '请输入供应商名称或评价主题' : '请输入评价主题'
}
allowClear
/>
</Form.Item>
// 提交评分
const handleSubmit = async () => {
if (!supplierRecord?.id) {
message.error('缺少必要参数,无法提交');
return;
}
<Form.Item name="evaluationTime" label="评价时间">
<RangePicker />
</Form.Item>
// 验证所有二级指标是否都已评分
const hasEmptyScore = scoreData.some(item =>
item.indicatorNdList.some((subItem: any) =>
!subItem.actualScore && subItem.actualScore !== 0
)
);
<Form.Item name="status" label="评价状态">
<Select placeholder="请选择" style={{ width: 150 }} allowClear>
<Option value="1"></Option>
<Option value="2"></Option>
<Option value="3"></Option>
<Option value="4"></Option>
</Select>
</Form.Item>
if (hasEmptyScore) {
message.warning('请为所有指标填写评分');
return;
}
setSubmitting(true);
try {
// 构建提交数据
const submitData = {
id: supplierRecord.id,
taskIndicatorVo: scoreData.map(item => ({
baseIndicator: item.baseIndicator,
indicatorDesc: item.descIndicator,
score: item.score,
subIndicator: item.indicatorNdList.map((subItem: any) => ({
id: subItem.id,
subIndicator: subItem.subIndicator,
subScore: subItem.score,
starIndicator: subItem.isStar,
scoreNum: subItem.actualScore,
remark: subItem.remark
}))
}))
};
const response = await submitEvaluateScore(submitData);
if (response.success) {
message.success('评分提交成功');
// 提交成功后返回列表页
history.goBack();
} else {
message.error(response.message || '评分提交失败');
}
} catch (error) {
console.error('评分提交失败:', error);
message.error('评分提交失败');
} finally {
setSubmitting(false);
}
};
<Form.Item className="filter-btns">
<Space>
<Button type="primary" onClick={handleSearch}>
<SearchOutlined />
</Button>
<Button onClick={handleReset} type="primary" danger>
<DeleteOutlined />
</Button>
</Space>
</Form.Item>
</Form>
</div>
</div>
);
return (
<div className="common-container">
<div className={styles.headerRow}>
<div className={styles.titleSection}>
<Title level={4} className={styles.pageTitle}>
{supplierRecord?.supplierName || '供应商'} -
</Title>
</div>
<div className={styles.actionSection}>
<Space>
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
</Button>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSubmit}
loading={submitting}
>
</Button>
</Space>
</div>
</div>
{/* 筛选条件区域 */}
{renderFilterForm()}
<div className="content-area">
<Card bordered={false}>
{loading ? (
<div className="loading-container" style={{ textAlign: 'center', padding: '50px' }}>
<Spin tip="加载中..." />
</div>
) : (
<Form form={form} layout="vertical">
<ScoreEvaluationTable
value={scoreData}
onChange={handleScoreDataChange}
isDetail={false}
loading={loading}
/>
</Form>
)}
</Card>
<Tabs activeKey={activeTab} onChange={handleTabChange}>
<TabPane tab="按供应商" key="supplier">
<Table
columns={supplierColumns}
dataSource={supplierTableData}
rowKey="id"
loading={supplierTableLoading}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
}}
onChange={handleTableChange}
/>
</TabPane>
<TabPane tab="按评价任务" key="task">
<Table
columns={taskColumns}
dataSource={taskTableData}
rowKey="id"
loading={taskTableLoading}
pagination={{
current: pagination.current,
pageSize: pagination.pageSize,
total: pagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
}}
onChange={handleTableChange}
/>
</TabPane>
</Tabs>
</div>
</div>
);

View File

@ -0,0 +1,302 @@
import React, { useState, useEffect } from 'react';
import { history, useLocation } from 'umi';
import {
Button,
Card,
Descriptions,
Divider,
Spin,
message,
Typography,
Empty,
Space,
Form,
Modal,
} from 'antd';
import { ArrowLeftOutlined, SaveOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { getSupplierScoreDetail, saveEvaluateScore } from '@/servers/api/supplierEvaluate';
import ScoreEvaluationTable from '@/components/ScoreEvaluationTable';
import styles from './supplierEvaluateScore.less';
const { Title } = Typography;
const { confirm } = Modal;
const SupplierEvaluateScoreDetail: React.FC = () => {
const [form] = Form.useForm();
const location = useLocation<{
record: supplierEvaluateScore.SupplierDimensionRecord | supplierEvaluateScore.TaskPageRecord;
mode?: 'view' | 'score'; // 查看模式或打分模式
recordType: 'supplier' | 'task'; // 记录类型:供应商或任务
}>();
const [loading, setLoading] = useState<boolean>(false);
const [submitting, setSubmitting] = useState<boolean>(false);
const [saving, setSaving] = useState<boolean>(false);
const [scoreDetail, setScoreDetail] = useState<supplierEvaluateScore.ScoreDetailData | null>(
null,
);
const [scoreData, setScoreData] = useState<any[]>([]);
const [record, setRecord] = useState<any>(null);
const [mode, setMode] = useState<'view' | 'score'>('view'); // 默认为查看模式
const [recordType, setRecordType] = useState<'supplier' | 'task'>('supplier'); // 默认为供应商类型
// 从路由获取传递的记录
useEffect(() => {
if (location.state?.record) {
setRecord(location.state.record);
// 设置记录类型
if (location.state.recordType) {
setRecordType(location.state.recordType);
}
// 设置模式
if (location.state.mode) {
setMode(location.state.mode);
} else {
// 如果没有传递mode则根据状态判断
setMode(location.state.record.status === '1' ? 'score' : 'view');
}
} else {
message.error('缺少必要参数,无法获取详情');
history.goBack();
}
}, [location]);
// 将API数据转换为ScoreEvaluationTable组件所需的格式
const formatDataForScoreTable = (data: supplierEvaluateScore.ScoreDetailData) => {
if (!data?.taskIndicatorVo) return [];
return data.taskIndicatorVo.map((indicator) => {
return {
baseIndicator: indicator.baseIndicator,
indicatorDesc: indicator.indicatorDesc,
score: indicator.score,
// 为ScoreEvaluationTable组件添加额外字段
indicatorNdList:
indicator.subIndicator
?.map((subItem) => {
// 确保subItem是一个对象而不是直接渲染
if (typeof subItem !== 'object' || subItem === null) {
return null;
}
return {
id: subItem.id,
subIndicator: subItem.subIndicator,
score: subItem.subScore,
isStar: subItem.starIndicator,
actualScore: subItem.scoreNum || '',
remark: subItem.remark || '',
};
})
.filter(Boolean) || [],
};
});
};
// 获取评价打分详情
const fetchScoreDetail = async () => {
if (!record?.id) {
message.error('缺少必要参数,无法获取数据');
return;
}
setLoading(true);
try {
const response = await getSupplierScoreDetail(record.id);
if (response.success && response.data) {
setScoreDetail(response.data);
// 转换数据格式
const formattedData = formatDataForScoreTable(response.data);
setScoreData(formattedData);
} else {
message.error(response.message || '获取评价得分明细失败');
}
} catch (error) {
console.error('获取评价得分明细失败:', error);
message.error('获取评价得分明细失败');
} finally {
setLoading(false);
}
};
// 当记录加载完成后,获取得分明细
useEffect(() => {
if (record?.id) {
fetchScoreDetail();
}
}, [record]);
// 返回上一页
const handleBack = () => {
history.goBack();
};
// 处理评分数据变更
const handleScoreDataChange = (newData: any[]) => {
setScoreData(newData);
};
// 提交评分
const handleSubmit = async () => {
if (!record?.id) {
message.error('缺少必要参数,无法提交');
return;
}
// 验证所有二级指标是否都已评分
const hasEmptyScore = scoreData.some((item) =>
item.indicatorNdList.some((subItem: any) => !subItem.scoreNum && subItem.scoreNum !== 0),
);
if (hasEmptyScore) {
message.warning('请为所有指标填写评分');
return;
}
// 显示确认对话框
confirm({
title: '提交确认',
icon: <ExclamationCircleOutlined />,
content: '评分提交后将不可修改,确定要提交吗?',
okText: '确定',
cancelText: '取消',
onOk: async () => {
setSubmitting(true);
try {
// 构建提交数据
const submitData: supplierEvaluateScore.ScoreSaveRequest = {
id: record.id,
scoreVoList: scoreData.flatMap((item) => {
return item.indicatorNdList.map((subItem: any) => ({
id: item.id,
remark: subItem.remark || item.remark || '',
score: String(subItem.scoreNum || item.score || ''),
}));
}),
};
const response = await saveEvaluateScore(submitData);
if (response.success) {
message.success('评分保存成功');
history.goBack();
} else {
message.error(response.message || '评分保存失败');
}
} catch (error) {
console.error('评分提交失败:', error);
message.error('评分提交失败');
} finally {
setSubmitting(false);
}
},
});
};
// 获取评价状态文本
const getStatusText = (status: string) => {
const statusMap: { [key: string]: string } = {
'1': '待评分',
'2': '已评分',
'3': '进行中',
'4': '已完成',
};
return statusMap[status] || '未知状态';
};
// 获取供应商名称
const getSupplierName = () => {
if (recordType === 'supplier') {
return record?.supplierName || record?.name || scoreDetail?.name || '-';
} else {
return scoreDetail?.name || '-';
}
};
// 获取评价主题
const getEvaluateTheme = () => {
return record?.evaluateTheme || '-';
};
// 获取发起单位
const getUnitName = () => {
return record?.tenantName || record?.deptName || '-';
};
// 判断是否可以编辑(只有打分模式才可编辑)
const canEdit = mode === 'score';
return (
<div className="common-container">
<div className={styles.headerRow}>
<div className={styles.titleSection}>
<Title level={4} className={styles.pageTitle}>
{getSupplierName()} -
</Title>
</div>
<div className={styles.actionSection}>
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
</Button>
</div>
</div>
<Spin spinning={loading}>
{scoreDetail ? (
<div className="content-area">
<Card title="基本信息" bordered={false}>
<Descriptions column={2} bordered>
<Descriptions.Item label="供应商名称">{getSupplierName()}</Descriptions.Item>
<Descriptions.Item label="品类">{scoreDetail.category || '-'}</Descriptions.Item>
<Descriptions.Item label="评价主题">{getEvaluateTheme()}</Descriptions.Item>
<Descriptions.Item label="评价状态">
{getStatusText(record?.status || '') || '-'}
</Descriptions.Item>
<Descriptions.Item label="评价开始时间">
{record?.startTime || '-'}
</Descriptions.Item>
<Descriptions.Item label="评价结束时间">{record?.endTime || '-'}</Descriptions.Item>
<Descriptions.Item label="发起单位">{getUnitName()}</Descriptions.Item>
</Descriptions>
</Card>
<Divider />
<Card title="评价打分" bordered={false}>
{scoreData.length > 0 ? (
<ScoreEvaluationTable
value={scoreData}
onChange={handleScoreDataChange}
isDetail={!canEdit} // 如果不可编辑,则以只读方式显示
/>
) : (
<Empty description="暂无评分数据" />
)}
{/* 只在打分模式下显示提交按钮,并移至底部 */}
{canEdit && (
<div className={styles.submitButtonContainer}>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSubmit}
loading={submitting}
style={{ marginTop: 24 }}
>
</Button>
</div>
)}
</Card>
</div>
) : (
!loading && <Empty description="暂无评分数据" />
)}
</Spin>
</div>
);
};
export default SupplierEvaluateScoreDetail;

View File

@ -21,7 +21,7 @@ import type {
EvaluateRuleUpdateRequest,
TaskAddRequest,
} from '@/servers/dao/supplierEvaluateTask';
import '@/servers/dao/supplierEvaluateScore.d.ts';
/**
* 获取所有模板列表
* @returns 所有模板列表
@ -291,19 +291,19 @@ export async function deleteEvaluateRule(id: string) {
* @returns Promise
*/
export async function getSupplierScoreDetail(id: string) {
return request<API.APIResponse<EvaluateScoreDetailData>>(`/coscoEvaluate/supplier/getScoreResult`, {
return request<supplierEvaluateScore.ScoreDetailResponse>(`/coscoEvaluate/user/getIndicator`, {
method: 'GET',
params: { id }
});
}
/**
* 提交供应商评价得分
* @param params 评价得分数据
* @returns
* 保存评分数据
* @param params 保存评分参数
* @returns Promise
*/
export async function submitEvaluateScore(params: any) {
return request('/api/supplier/evaluate/score/submit', {
export async function saveEvaluateScore(params: supplierEvaluateScore.ScoreSaveRequest) {
return request<supplierEvaluateScore.ScoreSaveResponse>(`/coscoEvaluate/user/getScore`, {
method: 'POST',
data: params,
});
@ -320,3 +320,42 @@ export async function getIndicator(id: string) {
params: { id },
});
}
/**
* 获取供应商维度列表
* @param params 查询参数
* @returns Promise
*/
export async function getSupplierDimension(params: supplierEvaluateScore.SupplierDimensionRequest) {
return request<supplierEvaluateScore.SupplierDimensionResponse>(`/coscoEvaluate/supplier/getSupplierDimension`, {
method: 'POST',
data: params,
});
}
/**
* 获取任务列表
* @param params 查询参数
* @returns Promise
*/
export async function getTaskPage(params: supplierEvaluateScore.TaskPageRequest) {
return request<supplierEvaluateScore.TaskPageResponse>(`/coscoEvaluate/task/getTaskPage`, {
method: 'POST',
data: params,
});
}
/**
* 导出供应商评价数据
* @param evaluateTaskId 评价任务ID
* @returns Promise
*/
export async function exportSupplierEvaluate(evaluateTaskId: string) {
const params: supplierEvaluateScore.ExportRequest = { evaluateTaskId };
return request(`/coscoEvaluate/supplier/export`, {
method: 'GET',
params,
responseType: 'arrayBuffer',
getResponse: false,
});
}

View File

@ -0,0 +1,205 @@
// 评价打分模块类型声明
declare namespace supplierEvaluateScore {
// 基础分页请求参数
interface BasePageRequest {
pageNo: number;
pageSize: number;
[property: string]: any;
}
// 评价打分请求参数
interface ScorePageRequest {
basePageRequest: BasePageRequest;
keyword?: string;
startTime?: string;
endTime?: string;
status?: string;
[property: string]: any;
}
// 按供应商维度查询参数
interface SupplierDimensionRequest {
basePageRequest: BasePageRequest;
keyword?: string; // 供应商名称或评价主题关键字
startTime?: string; // 评价开始时间
endTime?: string; // 评价结束时间
status?: string; // 评价状态1-待评分2-已评分3-进行中4-已完成
[property: string]: any;
}
// 按任务查询参数
interface TaskPageRequest {
basePageRequest: BasePageRequest;
keyword?: string; // 评价主题关键字
startTime?: string; // 评价开始时间
endTime?: string; // 评价结束时间
status?: string; // 评价状态1-待评分2-已评分3-进行中4-已完成
[property: string]: any;
}
// 供应商维度列表返回数据类型
interface SupplierDimensionResponse {
code: number;
data: SupplierDimensionData;
message: string;
success: boolean;
[property: string]: any;
}
interface SupplierDimensionData {
countId: null;
current: number;
hitCount: boolean;
maxLimit: null;
optimizeCountSql: boolean;
orders: any[];
pages: number;
records: SupplierDimensionRecord[];
searchCount: boolean;
size: number;
total: number;
[property: string]: any;
}
interface SupplierDimensionRecord {
basePageRequest?: null;
endTime?: string;
evaluateTheme?: string;
id?: string;
startTime?: string;
status?: string; // 1-待评分2-已评分3-进行中4-已完成
statusName?: string;
tenantName?: string; // 发起单位
userId?: null;
supplierName?: string;
submissionStatus?: string; // 提交状态
submissionTime?: string; // 提交时间
name?: string; // 供应商名称,与 supplierName 同义
[property: string]: any;
}
// 评价任务列表返回数据类型
interface TaskPageResponse {
code: number;
data: TaskPageData;
message: string;
success: boolean;
[property: string]: any;
}
interface TaskPageData {
countId: null;
current: number;
hitCount: boolean;
maxLimit: null;
optimizeCountSql: boolean;
orders: any[];
pages: number;
records: TaskPageRecord[];
searchCount: boolean;
size: number;
total: number;
[property: string]: any;
}
interface TaskPageRecord {
basePageRequest?: null;
endTime?: string;
evaluateTheme?: string;
id?: string;
startTime?: string;
status?: string; // 1-待评分2-已评分3-进行中4-已完成
statusName?: string;
deptName?: string; // 发起单位/部门
tenantName?: string; // 发起单位,与 deptName 同义
userId?: null;
[property: string]: any;
}
// 评价打分明细请求参数
interface ScoreDetailRequest {
id: string;
[property: string]: any;
}
// 评价打分明细返回数据类型
interface ScoreDetailResponse {
code: number;
data: ScoreDetailData;
message: string;
success: boolean;
[property: string]: any;
}
// 评价明细数据
interface ScoreDetailData {
category: string;
name: string;
taskIndicatorVo: TaskIndicatorVo[];
[property: string]: any;
}
// 评价指标项
interface TaskIndicatorVo {
baseIndicator: string;
indicatorDesc: string;
score: string;
subIndicator: SubIndicator[];
}
// 评价子指标项
interface SubIndicator {
id: string;
remark: string | null;
scoreNum: string | number | null;
starIndicator: string;
stId: string;
subIndicator: string;
subScore: string;
[property: string]: any;
}
// 打分保存请求参数
interface ScoreSaveRequest {
id: string;
scoreVoList: ScoreVoList[];
}
// 打分项
interface ScoreVoList {
id?: string;
remark?: string;
score?: string;
}
// 打分保存响应
interface ScoreSaveResponse {
code: number;
success: boolean;
message: string;
data: any;
[property: string]: any;
}
// 提交评分请求参数
interface ScoreSubmitRequest {
id: string;
taskIndicatorVo: TaskIndicatorVo[];
}
// 提交评分响应
interface ScoreSubmitResponse {
code: number;
success: boolean;
message: string;
data: any;
[property: string]: any;
}
// 导出评价数据请求参数
interface ExportRequest {
evaluateTaskId: string;
[property: string]: any;
}
}

5
src/typings.d.ts vendored
View File

@ -11,7 +11,9 @@ declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module 'omit.js';
declare module 'file-saver' {
export function saveAs(data: Blob | File | string, filename?: string, options?: object): void;
}
// google analytics interface
interface GAFieldsObject {
eventCategory: string;
@ -43,6 +45,7 @@ declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
declare const REQUEST_BASE: string;
declare const UPLOAD_URL: string;
declare const SERVER_BASE: string;
declare namespace API {
// ... 其他类型定义

58
src/utils/download.ts Normal file
View File

@ -0,0 +1,58 @@
/**
* 通用文件下载工具(基于 umi-request + file-saver
*/
import { extend } from 'umi-request';
import { saveAs } from 'file-saver';
// 单独配置一个不拦截响应的 request 实例
const downloadRequest = extend({
timeout: 10000,
responseType: 'blob', // 关键
credentials: 'include', // 根据实际需要携带 cookie
prefix: REQUEST_BASE,
});
/**
* 下载文件通用函数
* @param url 请求地址
* @param params 请求参数(可选)
* @param filename 下载后的文件名(可选)
* @param method 请求方法(默认 POST
*/
export async function downloadFile({
url,
params,
filename,
method = 'POST',
}: {
url: string;
params?: Record<string, any>;
filename?: string;
method?: 'GET' | 'POST';
}) {
try {
const blob: Blob = await downloadRequest(url, {
method,
data: method === 'POST' ? params : undefined,
params: method === 'GET' ? params : undefined,
});
// 尝试从响应头中获取文件名
const contentDisposition = (blob as any).response?.headers?.get?.('content-disposition');
let finalFilename = filename;
if (!finalFilename && contentDisposition) {
const match = contentDisposition.match(/filename="?([^"]+)"?/);
if (match && match[1]) {
finalFilename = decodeURIComponent(match[1]);
}
}
finalFilename = finalFilename || '下载文件';
saveAs(blob, finalFilename);
} catch (error) {
console.error('文件下载失败:', error);
}
}