diff --git a/config/config.dev.ts b/config/config.dev.ts index 93fd3d8..b3c99d7 100644 --- a/config/config.dev.ts +++ b/config/config.dev.ts @@ -5,5 +5,7 @@ export default defineConfig({ define: { UPLOAD_URL: '/upload', REQUEST_BASE: '/api', + // 下载文件时用到,直接window.open 拼上接口地址+参数 + SERVER_BASE: 'http://10.0.0.10:18012', }, }); diff --git a/config/config.prod.ts b/config/config.prod.ts index 2b907e5..0c52f71 100644 --- a/config/config.prod.ts +++ b/config/config.prod.ts @@ -5,5 +5,7 @@ export default defineConfig({ define:{ UPLOAD_URL: '/upload', REQUEST_BASE: '/api', + // 下载文件时用到,直接window.open 拼上接口地址+参数 + SERVER_BASE: 'http://10.0.0.10:18012', }, }); diff --git a/config/router.config.ts b/config/router.config.ts index dc081f4..62d8d2e 100644 --- a/config/router.config.ts +++ b/config/router.config.ts @@ -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', diff --git a/package.json b/package.json index 0c3845e..702f1a8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8db067..5202035 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx index d3c1d75..48c262c 100644 --- a/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx +++ b/src/components/ScoreEvaluationTable/ScoreEvaluationTable.tsx @@ -69,7 +69,7 @@ const ScoreEvaluationTable: React.FC = ({ // 将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 = ({ 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 = ({ } 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 = ({ 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 || '' }; }) diff --git a/src/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultInfo.tsx b/src/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultInfo.tsx index 1fc1665..0cd9907 100644 --- a/src/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultInfo.tsx +++ b/src/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultInfo.tsx @@ -44,14 +44,7 @@ const SupplierEvaluateResultInfo: React.FC = () => { ); const [evaluateRules, setEvaluateRules] = useState([]); - // 品类数据 - const categoryOptions = [ - { label: '食品', value: '食品' }, - { label: '电子', value: '电子' }, - { label: '机械', value: '机械' }, - { label: '化工', value: '化工' }, - { label: '医药', value: '医药' }, - ]; + // 获取上级页面传递的数据 useEffect(() => { diff --git a/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.less b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.less index 60eda9e..9b76c38 100644 --- a/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.less +++ b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.less @@ -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; +} diff --git a/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.tsx b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.tsx index 352b9dc..a7bcd89 100644 --- a/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.tsx +++ b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore.tsx @@ -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(false); - const [submitting, setSubmitting] = useState(false); - const [supplierRecord, setSupplierRecord] = useState(null); - const [parentRecord, setParentRecord] = useState(null); - const [scoreDetail, setScoreDetail] = useState(null); - const [scoreData, setScoreData] = useState([]); + const [filterForm] = Form.useForm(); + // 新增状态 + const [activeTab, setActiveTab] = useState('supplier'); + const [supplierTableData, setSupplierTableData] = useState< + supplierEvaluateScore.SupplierDimensionRecord[] + >([]); + const [taskTableData, setTaskTableData] = useState([]); + const [supplierTableLoading, setSupplierTableLoading] = useState(false); + const [taskTableLoading, setTaskTableLoading] = useState(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) => ( + + + {record.status === '1' && ( // 只有待评分状态才显示打分按钮 + + )} + + ), + }, + ]; - // 返回上一页 - 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) => ( + + + + + ), + }, + ]; - // 处理评分数据变更 - const handleScoreDataChange = (newData: any[]) => { - setScoreData(newData); - }; + // 筛选表单 + const renderFilterForm = () => ( +
+
+
+ + + - // 提交评分 - const handleSubmit = async () => { - if (!supplierRecord?.id) { - message.error('缺少必要参数,无法提交'); - return; - } + + + - // 验证所有二级指标是否都已评分 - const hasEmptyScore = scoreData.some(item => - item.indicatorNdList.some((subItem: any) => - !subItem.actualScore && subItem.actualScore !== 0 - ) - ); + + + - 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); - } - }; + + + + + + +
+
+
+ ); return (
- {supplierRecord?.supplierName || '供应商'} - 评价打分 + 供应商评价打分
-
- - - - -
+ {/* 筛选条件区域 */} + {renderFilterForm()} +
- - {loading ? ( -
- -
- ) : ( -
- - - )} -
+ + + `共 ${total} 条`, + }} + onChange={handleTableChange} + /> + + +
`共 ${total} 条`, + }} + onChange={handleTableChange} + /> + + ); diff --git a/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScoreDetail.tsx b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScoreDetail.tsx new file mode 100644 index 0000000..0249d1e --- /dev/null +++ b/src/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScoreDetail.tsx @@ -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(false); + const [submitting, setSubmitting] = useState(false); + const [saving, setSaving] = useState(false); + const [scoreDetail, setScoreDetail] = useState( + null, + ); + const [scoreData, setScoreData] = useState([]); + const [record, setRecord] = useState(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: , + 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 ( +
+
+
+ + {getSupplierName()} - 评价打分 + +
+
+ +
+
+ + + {scoreDetail ? ( +
+ + + {getSupplierName()} + {scoreDetail.category || '-'} + {getEvaluateTheme()} + + {getStatusText(record?.status || '') || '-'} + + + {record?.startTime || '-'} + + {record?.endTime || '-'} + {getUnitName()} + + + + + + + {scoreData.length > 0 ? ( + + ) : ( + + )} + + {/* 只在打分模式下显示提交按钮,并移至底部 */} + {canEdit && ( +
+ +
+ )} +
+
+ ) : ( + !loading && + )} +
+
+ ); +}; + +export default SupplierEvaluateScoreDetail; diff --git a/src/servers/api/supplierEvaluate.ts b/src/servers/api/supplierEvaluate.ts index 0ebc5f8..c735ab9 100644 --- a/src/servers/api/supplierEvaluate.ts +++ b/src/servers/api/supplierEvaluate.ts @@ -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>(`/coscoEvaluate/supplier/getScoreResult`, { + return request(`/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(`/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(`/coscoEvaluate/supplier/getSupplierDimension`, { + method: 'POST', + data: params, + }); +} + +/** + * 获取任务列表 + * @param params 查询参数 + * @returns Promise + */ +export async function getTaskPage(params: supplierEvaluateScore.TaskPageRequest) { + return request(`/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, + }); +} diff --git a/src/servers/dao/supplierEvaluateScore.d.ts b/src/servers/dao/supplierEvaluateScore.d.ts new file mode 100644 index 0000000..fa4ccee --- /dev/null +++ b/src/servers/dao/supplierEvaluateScore.d.ts @@ -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; + } +} diff --git a/src/typings.d.ts b/src/typings.d.ts index f47257e..9ffc33f 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -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 { // ... 其他类型定义 diff --git a/src/utils/download.ts b/src/utils/download.ts new file mode 100644 index 0000000..911d020 --- /dev/null +++ b/src/utils/download.ts @@ -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; + 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); + } +}