评价打分

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

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