开发供应商评价模块

This commit is contained in:
linxd
2025-06-23 10:54:39 +08:00
parent 8a121eff8a
commit 402d3dd575
19 changed files with 3089 additions and 80 deletions

View File

@ -22,26 +22,41 @@ export default [
routes: [
{
path: '/',
meta: {
title: '首页',
},
redirect: '/index',
},
{
name: 'index',
path: '/index',
meta: {
title: '首页',
},
component: '@/pages/index',
},
{
name: 'userManage',
path: '/userManage',
meta: {
title: '用户管理',
},
component: '@/pages/userManage/userManage',
},
{
name: 'downloadManage',
path: '/downloadManage',
meta: {
title: '下载管理',
},
component: '@/pages/downloadManage/downloadManage',
},
{
name: 'noticeManage',
path: '/noticeManage',
meta: {
title: '公告管理',
},
component: '@/pages/noticeManage/noticeManage',
},
{
@ -52,76 +67,153 @@ export default [
{
name: 'aboutManage',
path: '/aboutManage',
meta: {
title: '关于我们',
},
component: '@/pages/aboutManage/aboutManage',
},
{
name: 'helpManage',
path: '/helpManage',
meta: {
title: '帮助中心',
},
component: '@/pages/helpManage/helpManage',
},
{
name: 'readQuestionManage',
path: '/readQuestionManage',
meta: {
title: '已读问题',
},
component: '@/pages/userQuestionManage/readQuestionManage',
},
{
name: 'unreadQuestionManage',
path: '/unreadQuestionManage',
meta: {
title: '未读问题',
},
component: '@/pages/userQuestionManage/unreadQuestionManage',
},
{
name: 'friendLinkCategory',
path: '/friendLinkCategory',
meta: {
title: '友情链接分类',
},
component: '@/pages/friendLinkManage/friendLinkCategory',
},
{
name: 'friendLinkManage',
path: '/friendLinkManage',
meta: {
title: '友情链接管理',
},
component: '@/pages/friendLinkManage/friendLinkManage',
},
{
name: 'supplierTemplateManage',
path: '/supplierTemplateManage',
meta: {
title: '供应商模板管理',
},
component: '@/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManage',
},
{
name: 'supplierTaskManage',
path: '/supplierTaskManage',
meta: {
title: '供应商任务管理',
},
component: '@/pages/supplierEvaluateManage/supplierTaskManage/supplierTaskManage',
},
{
name: 'supplierEvaluateScore',
path: '/supplierEvaluateScore',
meta: {
title: '供应商评分管理',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateScore/supplierEvaluateScore',
},
{
name: 'supplierEvaluateResult',
path: '/supplierEvaluateResult',
meta: {
title: '供应商评价结果',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResult',
},
{
name: 'supplierEvaluateResultInfo',
path: '/supplierEvaluateResult/supplierEvaluateResultInfo',
meta: {
title: '供应商评价结果详情',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultInfo',
},
{
name: 'supplierEvaluateResultScoreDetail',
path: '/supplierEvaluateResult/supplierEvaluateResultScoreDetail',
meta: {
title: '供应商评价结果得分明细',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultScoreDetail',
},
{
name: 'supplierEvaluateResultScoreByList',
path: '/supplierEvaluateResult/supplierEvaluateResultScoreByList',
meta: {
title: '供应商评价结果打分情况',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultScoreByList',
},
{
name: 'supplierEvaluateResultByZb',
path: '/supplierEvaluateResult/supplierEvaluateResultByZb',
meta: {
title: '评价结果详情',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResult/supplierEvaluateResultByZb',
},
{
name: 'supplierEvaluateResultApproval',
path: '/supplierEvaluateResultApproval',
meta: {
title: '供应商评价结果审批',
},
component: '@/pages/supplierEvaluateManage/supplierEvaluateResultApproval/supplierEvaluateResultApproval',
},
{
name: 'supplierAnnualTemplateManage',
path: '/supplierAnnualTemplateManage',
meta: {
title: '供应商年度模板管理',
},
component: '@/pages/supplierAnnualManage/supplierAnnualTemplateManage/supplierAnnualTemplateManage',
},
{
name: 'supplierAnnualTaskManage',
path: '/supplierAnnualTaskManage',
meta: {
title: '供应商年度任务管理',
},
component: '@/pages/supplierAnnualManage/supplierAnnualTaskManage/supplierAnnualTaskManage',
},
{
name: 'supplierAnnualQuery',
path: '/supplierAnnualQuery',
meta: {
title: '供应商年度查询',
},
component: '@/pages/supplierAnnualManage/supplierAnnualQuery/supplierAnnualQuery',
},
{
name: 'supplierAnnualResult',
path: '/supplierAnnualResult',
meta: {
title: '供应商年度结果',
},
component: '@/pages/supplierAnnualManage/supplierAnnualResult/supplierAnnualResult',
},
]

View File

@ -0,0 +1,43 @@
// 模板类型 - 从任务字典导入
import { TaskType, TaskTypeText } from './supplierTaskDict';
// 模板状态
export const TemplateStatus = {
ENABLED: 'enabled', // 启用
DISABLED: 'disabled', // 禁用
};
export const TemplateStatusText = {
[TemplateStatus.ENABLED]: '已启用',
[TemplateStatus.DISABLED]: '已禁用',
};
export const TemplateStatusColor = {
[TemplateStatus.ENABLED]: 'green',
[TemplateStatus.DISABLED]: 'red',
};
// 评价等级
export const EvaluateLevel = {
EXCELLENT: 'excellent', // 优秀
GOOD: 'good', // 良好
AVERAGE: 'average', // 一般
POOR: 'poor', // 较差
};
export const EvaluateLevelText = {
[EvaluateLevel.EXCELLENT]: 'A',
[EvaluateLevel.GOOD]: 'B',
[EvaluateLevel.AVERAGE]: 'C',
[EvaluateLevel.POOR]: 'D',
};
export const EvaluateLevelColor = {
[EvaluateLevel.EXCELLENT]: 'green',
[EvaluateLevel.GOOD]: 'blue',
[EvaluateLevel.AVERAGE]: 'orange',
[EvaluateLevel.POOR]: 'red',
};
// 重新导出任务类型
export { TaskType, TaskTypeText };

View File

@ -174,13 +174,11 @@ const DomesticForm: React.FC<DomesticFormProps> = ({
/>
</Form.Item>
</Col>
<Col span={24}>
<Col span={8}>
<Form.Item
name="businessScope"
label="经营范围"
rules={[{ required: true, message: '请输入经营范围' }]}
labelCol={{ span: 2 }}
wrapperCol={{ span: 22 }}
>
<TextArea
placeholder="金融专用产品、广告传媒"

View File

@ -182,7 +182,6 @@ const ForeignForm: React.FC<ForeignFormProps> = ({ form, countdown, handleGetCap
<Form.Item
label="注册资本"
required
style={{ marginBottom: 0 }}
>
<Input.Group compact style={{ display: 'flex' }}>
<Form.Item name="capitalCurrency" noStyle initialValue="USD">
@ -208,17 +207,16 @@ const ForeignForm: React.FC<ForeignFormProps> = ({ form, countdown, handleGetCap
</Input.Group>
</Form.Item>
</Col>
<Col span={24}>
<Col span={8}>
<Form.Item
name="businessScope"
label="经营范围"
labelCol={{ span: 2 }}
wrapperCol={{ span: 22 }}
rules={[{ required: true, message: '请输入经营范围' }]}
>
<TextArea placeholder="请输入经营范围" rows={2} maxLength={200} showCount />
</Form.Item>
</Col>
<Col span={8}></Col>
<Col span={8}>
<Form.Item
name="contactName"

View File

@ -0,0 +1,242 @@
import React from 'react';
import { Table, Progress, Typography, Card, Tag } from 'antd';
import styles from './components.less';
const { Title, Text } = Typography;
interface GeneralEvaluationProps {
supplierName?: string;
}
const GeneralEvaluation: React.FC<GeneralEvaluationProps> = ({ supplierName }) => {
// 评价数据
const evaluationData = [
{
key: '1',
firstIndex: '基本情况(base case)',
firstDescription: '企业基本资质与存续状态证明',
firstScore: 4,
secondIndex: '资质文件',
secondScore: 1,
averageScore: 3,
rowSpan: 2,
},
{
key: '2',
firstIndex: '基本情况(base case)',
firstDescription: '企业基本资质与存续状态证明',
firstScore: 4,
secondIndex: 'XXX',
secondScore: 3,
averageScore: 1,
rowSpan: 0, // 设为0表示不显示
},
{
key: '3',
firstIndex: '成本(cost)',
firstDescription: '商品与服务的总拥有成本',
firstScore: 7,
secondIndex: 'XXX',
secondScore: 7,
averageScore: 6.5,
rowSpan: 1,
},
{
key: '4',
firstIndex: '创新(innovate)',
firstDescription: '为公司提供竞争优势,提高商业价值而持续提供创新性建议及解决方案',
firstScore: 20,
secondIndex: 'XXX',
secondScore: 20,
averageScore: 18.5,
rowSpan: 1,
},
{
key: '5',
firstIndex: '服务 (service)',
firstDescription: '为满足公司商业需求而提供的服务与支持',
firstScore: 10,
secondIndex: 'XXX',
secondScore: 10,
averageScore: 10,
rowSpan: 1,
},
{
key: '6',
firstIndex: '质量(quality)',
firstDescription: '提供满足需求(规格)的产品与服务',
firstScore: 25,
secondIndex: 'XXX',
secondScore: 25,
averageScore: 24,
rowSpan: 1,
},
{
key: '7',
firstIndex: '绿色(environment protection)',
firstDescription: '优秀的交付能力及与之相关的灵活性',
firstScore: 25,
secondIndex: 'XXX',
secondScore: 25,
averageScore: 24,
rowSpan: 1,
},
{
key: '8',
firstIndex: '安全(safety)',
firstDescription: '产品质量安全、环境保护、风险防控及商业道德约束',
firstScore: 6,
secondIndex: 'XXX',
secondScore: 6,
averageScore: 5,
rowSpan: 1,
},
{
key: '9',
firstIndex: '廉洁(integrity)',
firstDescription: '商业道德约束',
firstScore: 3,
secondIndex: '商业贿赂与道德风险',
secondScore: 3,
averageScore: 3,
rowSpan: 1,
},
{
key: '10',
firstIndex: '合计',
firstDescription: '',
firstScore: 100,
secondIndex: '',
secondScore: 100,
averageScore: 95,
rowSpan: 1,
},
];
const columns = [
{
title: '',
dataIndex: 'index',
key: 'index',
width: 50,
render: (text: string, record: any, index: number) => index + 1,
},
{
title: '一级指标',
children: [
{
title: '*基本指标',
dataIndex: 'firstIndex',
key: 'firstIndex',
width: 150,
render: (text: string, record: any, index: number) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
{
title: '*指标说明',
dataIndex: 'firstDescription',
key: 'firstDescription',
width: 220,
render: (text: string, record: any) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
{
title: '分值',
dataIndex: 'firstScore',
key: 'firstScore',
width: 80,
render: (text: number, record: any) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
],
},
{
title: '二级指标',
children: [
{
title: '*细分指标',
dataIndex: 'secondIndex',
key: 'secondIndex',
width: 180,
},
{
title: '分值',
dataIndex: 'secondScore',
key: 'secondScore',
width: 80,
},
],
},
{
title: '平均分',
dataIndex: 'averageScore',
key: 'averageScore',
width: 80,
render: (score: number) => (
<span className={score < 60 ? styles.lowScore : ''}>
{score}
</span>
),
},
];
// 计算总分
const totalScore = evaluationData[evaluationData.length - 1].averageScore;
const totalPossibleScore = evaluationData[evaluationData.length - 1].firstScore;
const scorePercentage = (totalScore / totalPossibleScore) * 100;
return (
<div className={styles.evaluationContainer}>
<Card className={styles.summaryCard}>
<div className={styles.scoreSummary}>
<div className={styles.scoreCircle}>
<Progress
type="circle"
percent={scorePercentage}
format={() => `${totalScore}`}
width={80}
strokeColor={{
'0%': '#108ee9',
'100%': '#87d068',
}}
/>
</div>
<div className={styles.scoreInfo}>
<Title level={4}></Title>
<Text type="secondary">: {totalPossibleScore}</Text>
{supplierName && <Text type="secondary">: {supplierName}</Text>}
</div>
</div>
</Card>
<Table
columns={columns}
dataSource={evaluationData}
pagination={false}
className={styles.scoreTable}
bordered
size="middle"
/>
</div>
);
};
export default GeneralEvaluation;

View File

@ -0,0 +1,265 @@
import React from 'react';
import { Table, Progress, Typography, Card, Tag } from 'antd';
import styles from './components.less';
const { Title, Text } = Typography;
interface TechnicalEvaluationProps {
supplierName?: string;
}
const TechnicalEvaluation: React.FC<TechnicalEvaluationProps> = ({ supplierName }) => {
// 评价数据
const evaluationData = [
{
key: '1',
firstIndex: '技术实力',
firstDescription: '技术团队规模与研发能力',
firstScore: 20,
secondIndex: '研发团队规模',
secondScore: 10,
averageScore: 9,
rowSpan: 2,
},
{
key: '2',
firstIndex: '技术实力',
firstDescription: '技术团队规模与研发能力',
firstScore: 20,
secondIndex: '技术创新能力',
secondScore: 10,
averageScore: 8,
rowSpan: 0, // 设为0表示不显示
},
{
key: '3',
firstIndex: '产品性能',
firstDescription: '产品的技术指标与性能表现',
firstScore: 25,
secondIndex: '核心性能指标',
secondScore: 15,
averageScore: 14,
rowSpan: 2,
},
{
key: '4',
firstIndex: '产品性能',
firstDescription: '产品的技术指标与性能表现',
firstScore: 25,
secondIndex: '稳定性与可靠性',
secondScore: 10,
averageScore: 9.5,
rowSpan: 0,
},
{
key: '5',
firstIndex: '技术支持',
firstDescription: '提供的技术服务与支持能力',
firstScore: 15,
secondIndex: '响应速度',
secondScore: 8,
averageScore: 7.5,
rowSpan: 2,
},
{
key: '6',
firstIndex: '技术支持',
firstDescription: '提供的技术服务与支持能力',
firstScore: 15,
secondIndex: '问题解决能力',
secondScore: 7,
averageScore: 6.5,
rowSpan: 0,
},
{
key: '7',
firstIndex: '技术文档',
firstDescription: '技术文档的完整性与准确性',
firstScore: 10,
secondIndex: '文档完整性',
secondScore: 5,
averageScore: 4.5,
rowSpan: 2,
},
{
key: '8',
firstIndex: '技术文档',
firstDescription: '技术文档的完整性与准确性',
firstScore: 10,
secondIndex: '文档准确性',
secondScore: 5,
averageScore: 4,
rowSpan: 0,
},
{
key: '9',
firstIndex: '技术培训',
firstDescription: '提供的技术培训与知识转移',
firstScore: 15,
secondIndex: '培训质量',
secondScore: 8,
averageScore: 7,
rowSpan: 2,
},
{
key: '10',
firstIndex: '技术培训',
firstDescription: '提供的技术培训与知识转移',
firstScore: 15,
secondIndex: '培训频率',
secondScore: 7,
averageScore: 6,
rowSpan: 0,
},
{
key: '11',
firstIndex: '兼容性',
firstDescription: '与现有系统的兼容性',
firstScore: 15,
secondIndex: '系统兼容性评估',
secondScore: 15,
averageScore: 13.5,
rowSpan: 1,
},
{
key: '12',
firstIndex: '合计',
firstDescription: '',
firstScore: 100,
secondIndex: '',
secondScore: 100,
averageScore: 89.5,
rowSpan: 1,
},
];
const columns = [
{
title: '',
dataIndex: 'index',
key: 'index',
width: 50,
render: (text: string, record: any, index: number) => index + 1,
},
{
title: '一级指标',
children: [
{
title: '*基本指标',
dataIndex: 'firstIndex',
key: 'firstIndex',
width: 150,
className: styles.requiredColumn,
render: (text: string, record: any, index: number) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
{
title: '*指标说明',
dataIndex: 'firstDescription',
key: 'firstDescription',
width: 220,
className: styles.requiredColumn,
render: (text: string, record: any) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
{
title: '分值',
dataIndex: 'firstScore',
key: 'firstScore',
width: 80,
render: (text: number, record: any) => {
return {
children: text,
props: {
rowSpan: record.rowSpan,
},
};
},
},
],
},
{
title: '二级指标',
children: [
{
title: '*细分指标',
dataIndex: 'secondIndex',
key: 'secondIndex',
width: 180,
className: styles.requiredColumn,
},
{
title: '分值',
dataIndex: 'secondScore',
key: 'secondScore',
width: 80,
},
],
},
{
title: '平均分',
dataIndex: 'averageScore',
key: 'averageScore',
width: 80,
render: (score: number) => (
<span className={score < 60 ? styles.lowScore : ''}>
{score}
</span>
),
},
];
// 计算总分
const totalScore = evaluationData[evaluationData.length - 1].averageScore;
const totalPossibleScore = evaluationData[evaluationData.length - 1].firstScore;
const scorePercentage = (totalScore / totalPossibleScore) * 100;
return (
<div className={styles.evaluationContainer}>
<Card className={styles.summaryCard}>
<div className={styles.scoreSummary}>
<div className={styles.scoreCircle}>
<Progress
type="circle"
percent={scorePercentage}
format={() => `${totalScore}`}
width={80}
strokeColor={{
'0%': '#108ee9',
'100%': '#87d068',
}}
/>
</div>
<div className={styles.scoreInfo}>
<Title level={4}></Title>
<Text type="secondary">: {totalPossibleScore}</Text>
{supplierName && <Text type="secondary">: {supplierName}</Text>}
</div>
</div>
</Card>
<Table
columns={columns}
dataSource={evaluationData}
pagination={false}
className={styles.scoreTable}
bordered
size="middle"
/>
</div>
);
};
export default TechnicalEvaluation;

View File

@ -0,0 +1,80 @@
.evaluationContainer {
margin-top: 16px;
}
.summaryCard {
margin-bottom: 16px;
}
.scoreSummary {
display: flex;
align-items: center;
}
.scoreCircle {
margin-right: 24px;
}
.scoreInfo {
display: flex;
flex-direction: column;
justify-content: center;
h4 {
margin-bottom: 4px;
}
}
.scoreCell {
display: flex;
flex-direction: column;
span {
margin-bottom: 4px;
}
}
.scoreTable {
margin-top: 16px;
:global {
.ant-table-thead > tr > th {
background-color: #f5f5f5;
text-align: center;
font-weight: 500;
}
.ant-table-tbody > tr > td {
text-align: center;
vertical-align: middle;
}
}
}
/* 必填列样式 */
.requiredColumn {
position: relative;
&::before {
content: '*';
color: #ff4d4f;
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
}
}
/* 低分警示 */
.lowScore {
color: #ff4d4f;
font-weight: 500;
}
/* 表格嵌套表头样式 */
:global {
.ant-table-thead > tr > th.ant-table-cell-fix-left,
.ant-table-thead > tr > th.ant-table-cell-fix-right {
background-color: #f5f5f5;
}
}

View File

@ -0,0 +1,113 @@
/* 供应商评价结果模块公共样式 */
/* 详情项样式 */
.detail-item {
display: flex;
margin-bottom: 12px;
.label {
width: 120px;
text-align: right;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
margin-right: 8px;
}
.content {
flex: 1;
word-break: break-all;
}
}
/* 页面标题行样式 */
.headerRow {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
/* 标题区域样式 */
.titleSection {
display: flex;
align-items: center;
}
/* 操作区域样式 */
.actionSection {
display: flex;
align-items: center;
button {
margin-left: 8px;
}
}
/* 页面标题样式 */
.pageTitle {
margin-bottom: 0 !important;
}
/* 页面头部样式 - 从supplierEvaluateResultInfo.less */
.page-header {
margin-bottom: 24px;
h2 {
margin-top: 16px;
margin-bottom: 16px;
font-size: 20px;
font-weight: 500;
}
}
/* 详情项样式 - 从supplierEvaluateResultInfo.less */
.detailItem {
margin-bottom: 8px;
line-height: 22px;
.label {
display: inline-block;
width: 100px;
color: rgba(0, 0, 0, 0.65);
font-weight: 500;
}
.content {
display: inline-block;
color: rgba(0, 0, 0, 0.85);
}
}
/* 得分详情模态框样式 - 从supplierEvaluateResultInfo.less */
.scoreDetailModal {
.scoreItem {
display: flex;
margin-bottom: 12px;
.scoreLabel {
width: 120px;
font-weight: 500;
}
.scoreValue {
flex: 1;
}
}
}
/* 打分情况模态框样式 - 从supplierEvaluateResultInfo.less */
.scoringModal {
.scoringItem {
display: flex;
margin-bottom: 12px;
.scoringLabel {
width: 120px;
font-weight: 500;
}
.scoringValue {
flex: 1;
}
}
}

View File

@ -1,11 +1,321 @@
import React from 'react';
import { Card } from 'antd';
import React, { useState, useEffect } from 'react';
import {
Card,
Form,
Input,
Select,
Button,
Table,
Space,
Tag,
DatePicker,
TablePaginationConfig,
Modal,
Row,
Col,
Tooltip,
message,
} from 'antd';
import {
SearchOutlined,
DeleteOutlined,
EyeOutlined,
PlusOutlined,
} from '@ant-design/icons';
import { TaskStatus, TaskStatusText, TaskStatusColor, TaskType, TaskTypeText } from '@/dicts/supplierTaskDict';
import moment from 'moment';
import styles from './supplierEvaluateResult.less';
import { history } from 'umi';
const { Option } = Select;
const { RangePicker } = DatePicker;
// 删除本地接口定义,使用全局定义
const SupplierEvaluateResult: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [resultData, setResultData] = useState<SupplierEvaluate.EvaluateResultRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SupplierEvaluate.EvaluateResultSearchParams>({});
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 创建单位数据
const unitOptions = [
{ label: '中山市合创展包装材料有限公司', value: '中山市合创展包装材料有限公司' },
{ label: '广州市科技发展有限公司', value: '广州市科技发展有限公司' },
{ label: '深圳市创新科技有限公司', value: '深圳市创新科技有限公司' },
{ label: '东莞市制造业有限公司', value: '东莞市制造业有限公司' },
];
// 模拟获取评价结果列表
const fetchResultList = async (
current = 1,
pageSize = 10,
params: SupplierEvaluate.EvaluateResultSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
setLoading(true);
try {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: SupplierEvaluate.EvaluateResultRecord[] = Array.from({ length: 35 }).map((_, index) => {
const id = `${index + 1}`;
const status = Object.values(TaskStatus)[Math.floor(Math.random() * 3)];
const taskType = Math.random() > 0.5 ? TaskType.REGULAR : TaskType.SPECIAL;
// 随机选择单位和品类
const unitIndex = Math.floor(Math.random() * unitOptions.length);
const categoryIndex = Math.floor(Math.random() * categoryOptions.length);
// 随机生成日期
const startDate = moment().subtract(Math.floor(Math.random() * 60), 'days');
const endDate = moment(startDate).add(Math.floor(Math.random() * 30) + 15, 'days');
return {
id,
key: id,
evaluateTitle: `${TaskTypeText[taskType]}任务${index + 1}`,
evaluateCategory: categoryOptions[categoryIndex].value,
initiatingUnit: unitOptions[unitIndex].value,
startTime: startDate.format('YYYY-MM-DD'),
endTime: endDate.format('YYYY-MM-DD'),
status,
createTime: moment().subtract(Math.floor(Math.random() * 90), 'days').format('YYYY-MM-DD HH:mm:ss'),
};
});
// 根据搜索条件过滤
let filteredData = [...mockData];
if (params.evaluateTitle) {
filteredData = filteredData.filter(item =>
item.evaluateTitle.includes(params.evaluateTitle || '')
);
}
if (params.status) {
filteredData = filteredData.filter(item =>
item.status === params.status
);
}
if (params.timeRange && params.timeRange.length === 2) {
const start = moment(params.timeRange[0]);
const end = moment(params.timeRange[1]);
filteredData = filteredData.filter(item => {
const itemStart = moment(item.startTime);
return itemStart.isBetween(start, end, null, '[]');
});
}
// 分页
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
setResultData(paginatedData);
setPagination({
...pagination,
current,
pageSize,
total: filteredData.length,
});
setLoading(false);
}, 500);
} catch (error) {
console.error('获取评价结果列表失败:', error);
message.error('获取评价结果列表失败');
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchResultList(pagination.current, pagination.pageSize, {});
}, []);
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchResultList(newPagination.current, newPagination.pageSize, searchParams);
};
// 处理搜索
const handleSearch = (values: any) => {
const { timeRange, ...rest } = values;
const params: SupplierEvaluate.EvaluateResultSearchParams = { ...rest };
if (timeRange && timeRange.length === 2) {
params.timeRange = [timeRange[0].format('YYYY-MM-DD'), timeRange[1].format('YYYY-MM-DD')];
}
fetchResultList(1, pagination.pageSize, params);
};
// 处理重置
const handleReset = () => {
form.resetFields();
fetchResultList(1, pagination.pageSize, {});
};
// 查看详情 - 修改为跳转到详情页
const handleViewDetail = (record: SupplierEvaluate.EvaluateResultRecord) => {
history.push({
pathname: '/supplierEvaluateResult/supplierEvaluateResultInfo',
state: { record }
});
};
// 获取状态标签
const getStatusTag = (status: string) => {
const color = TaskStatusColor[status as keyof typeof TaskStatusColor] || 'default';
const text = TaskStatusText[status as keyof typeof TaskStatusText] || '未知状态';
return <Tag color={color}>{text}</Tag>;
};
const columns = [
{
title: '序号',
render: (_: any, __: SupplierEvaluate.EvaluateResultRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
{
title: '评价主题',
dataIndex: 'evaluateTitle',
key: 'evaluateTitle',
width: 200,
ellipsis: {
showTitle: false,
},
render: (evaluateTitle: string) => (
<Tooltip placement="topLeft" title={evaluateTitle}>
{evaluateTitle}
</Tooltip>
),
},
{
title: '评价品类',
dataIndex: 'evaluateCategory',
key: 'evaluateCategory',
width: 120,
},
{
title: '发起单位',
dataIndex: 'initiatingUnit',
key: 'initiatingUnit',
width: 200,
ellipsis: {
showTitle: false,
},
render: (text: string) => (
<Tooltip placement="topLeft" title={text}>
{text}
</Tooltip>
),
},
{
title: '评价开始时间',
dataIndex: 'startTime',
key: 'startTime',
width: 120,
},
{
title: '评价结束时间',
dataIndex: 'endTime',
key: 'endTime',
width: 120,
},
{
title: '评价状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => getStatusTag(status),
},
{
title: '操作',
key: 'action',
width: 100,
align: 'center' as const,
render: (_: unknown, record: SupplierEvaluate.EvaluateResultRecord) => (
<Button type="link" onClick={() => handleViewDetail(record)}>
</Button>
),
},
];
return (
<Card title="供应商评价结果">
<div></div>
</Card>
<div className="common-container">
<div className="filter-action-row">
<Form
form={form}
layout="inline"
onFinish={handleSearch}
className="filter-form"
>
<Form.Item name="evaluateTitle" label="评价主题">
<Input placeholder="请输入评价主题" allowClear />
</Form.Item>
<Form.Item name="timeRange" label="评价时间">
<RangePicker
placeholder={['开始日期', '结束日期']}
format="YYYY-MM-DD"
/>
</Form.Item>
<Form.Item name="status" label="评价状态">
<Select placeholder="请选择状态" allowClear style={{ width: 150 }}>
<Option value={TaskStatus.DRAFT}>{TaskStatusText[TaskStatus.DRAFT]}</Option>
<Option value={TaskStatus.PROCESSING}>{TaskStatusText[TaskStatus.PROCESSING]}</Option>
<Option value={TaskStatus.COMPLETED}>{TaskStatusText[TaskStatus.COMPLETED]}</Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={handleReset}
>
</Button>
</Form.Item>
</Form>
</div>
<div className="content-area">
<Table
columns={columns}
dataSource={resultData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1100 }}
/>
</div>
</div>
);
};

View File

@ -0,0 +1 @@
// 评价结果详情

View File

@ -0,0 +1,307 @@
// 供应商评价结果详情
import React, { useState, useEffect } from 'react';
import {
Card,
Form,
Input,
Select,
Button,
Table,
Space,
Tag,
TablePaginationConfig,
Modal,
Row,
Col,
Tooltip,
message,
} from 'antd';
import {
SearchOutlined,
DeleteOutlined,
ArrowLeftOutlined,
FileTextOutlined,
BarChartOutlined,
} from '@ant-design/icons';
import { history, useLocation } from 'umi';
import moment from 'moment';
import { EvaluateLevel, EvaluateLevelText, EvaluateLevelColor } from '@/dicts/supplierTemplateDict';
import styles from './supplierEvaluateResult.less';
const { Option } = Select;
const SupplierEvaluateResultInfo: React.FC = () => {
const location = useLocation<{ record: SupplierEvaluate.EvaluateResultRecord }>();
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [resultData, setResultData] = useState<SupplierEvaluate.EvaluateResultDetailRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] =
useState<SupplierEvaluate.EvaluateResultDetailSearchParams>({});
const [parentRecord, setParentRecord] = useState<SupplierEvaluate.EvaluateResultRecord | null>(
null,
);
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 评价等级选项
const levelOptions = Object.entries(EvaluateLevelText).map(([value, label]) => ({
label,
value,
}));
// 获取上级页面传递的数据
useEffect(() => {
if (location.state?.record) {
setParentRecord(location.state.record);
}
}, [location]);
// 模拟获取评价结果详情列表
const fetchResultDetailList = async (
current = 1,
pageSize = 10,
params: SupplierEvaluate.EvaluateResultDetailSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
setLoading(true);
try {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: SupplierEvaluate.EvaluateResultDetailRecord[] = Array.from({
length: 35,
}).map((_, index) => {
const id = `${index + 1}`;
// 随机选择品类
const categoryIndex = Math.floor(Math.random() * categoryOptions.length);
// 随机生成得分和等级
const score = Math.floor(Math.random() * 40) + 60; // 60-100分
let level;
if (score >= 90) {
level = EvaluateLevel.EXCELLENT;
} else if (score >= 80) {
level = EvaluateLevel.GOOD;
} else if (score >= 70) {
level = EvaluateLevel.AVERAGE;
} else {
level = EvaluateLevel.POOR;
}
return {
id,
key: id,
supplierName: `供应商${index + 1}`,
category: categoryOptions[categoryIndex].value,
score,
level,
};
});
// 根据搜索条件过滤
let filteredData = [...mockData];
if (params.supplierName) {
filteredData = filteredData.filter((item) =>
item.supplierName.includes(params.supplierName || ''),
);
}
if (params.level) {
filteredData = filteredData.filter((item) => item.level === params.level);
}
// 分页
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
setResultData(paginatedData);
setPagination({
...pagination,
current,
pageSize,
total: filteredData.length,
});
setLoading(false);
}, 500);
} catch (error) {
console.error('获取评价结果详情列表失败:', error);
message.error('获取评价结果详情列表失败');
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchResultDetailList(pagination.current, pagination.pageSize, {});
}, []);
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchResultDetailList(newPagination.current, newPagination.pageSize, searchParams);
};
// 处理搜索
const handleSearch = (values: any) => {
fetchResultDetailList(1, pagination.pageSize, values);
};
// 处理重置
const handleReset = () => {
form.resetFields();
fetchResultDetailList(1, pagination.pageSize, {});
};
// 返回上一页
const handleBack = () => {
history.goBack();
};
// 查看得分明细
const handleViewScoreDetail = (record: SupplierEvaluate.EvaluateResultDetailRecord) => {
history.push({
pathname: '/supplierEvaluateResult/supplierEvaluateResultScoreDetail',
state: { record }
});
};
// 查看打分情况
const handleViewScoring = (record: SupplierEvaluate.EvaluateResultDetailRecord) => {
message.info(`查看供应商 ${record.supplierName} 的打分情况`);
};
// 获取等级标签
const getLevelTag = (level: string) => {
const text = EvaluateLevelText[level as keyof typeof EvaluateLevelText] || '未知等级';
return text;
};
const columns = [
{
title: '序号',
render: (_: any, __: SupplierEvaluate.EvaluateResultDetailRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
{
title: '供应商名称',
dataIndex: 'supplierName',
key: 'supplierName',
width: 200,
ellipsis: {
showTitle: false,
},
render: (supplierName: string) => (
<Tooltip placement="topLeft" title={supplierName}>
{supplierName}
</Tooltip>
),
},
{
title: '品类',
dataIndex: 'category',
key: 'category',
width: 120,
},
{
title: '评价得分',
dataIndex: 'score',
key: 'score',
width: 100
},
{
title: '评价等级',
dataIndex: 'level',
key: 'level',
width: 100,
align: 'center' as const,
render: (level: string) => getLevelTag(level),
},
{
title: '操作',
key: 'action',
width: 180,
align: 'center' as const,
render: (_: unknown, record: SupplierEvaluate.EvaluateResultDetailRecord) => (
<Space size="middle">
<Button
type="link"
onClick={() => handleViewScoreDetail(record)}
>
</Button>
<Button type="link" onClick={() => handleViewScoring(record)}>
</Button>
</Space>
),
},
];
return (
<div className="common-container">
<div className="filter-action-row">
<Form form={form} layout="inline" onFinish={handleSearch} className="filter-form">
<Form.Item name="supplierName" label="供应商名称">
<Input placeholder="请输入供应商名称" allowClear />
</Form.Item>
<Form.Item name="level" label="评价等级">
<Select placeholder="请选择评价等级" allowClear style={{ width: 150 }}>
{levelOptions.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button type="primary" danger icon={<DeleteOutlined />} onClick={handleReset}>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
</Button>
</div>
</div>
<div className="content-area">
<Table
columns={columns}
dataSource={resultData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1100 }}
/>
</div>
</div>
);
};
export default SupplierEvaluateResultInfo;

View File

@ -0,0 +1 @@
// 供应商评价结果打分情况

View File

@ -0,0 +1,64 @@
// 供应商评价结果得分明细
import React, { useState, useEffect } from 'react';
import { Tabs, Button, Typography, Row, Col } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { history, useLocation } from 'umi';
import GeneralEvaluation from './components/GeneralEvaluation';
import TechnicalEvaluation from './components/TechnicalEvaluation';
import styles from './supplierEvaluateResult.less';
const { Title } = Typography;
const { TabPane } = Tabs;
const SupplierEvaluateResultScoreDetail: React.FC = () => {
const location = useLocation<{ record: SupplierEvaluate.EvaluateResultDetailRecord }>();
const [supplierRecord, setSupplierRecord] = useState<SupplierEvaluate.EvaluateResultDetailRecord | null>(null);
const [activeTab, setActiveTab] = useState<string>('1');
// 获取上级页面传递的数据
useEffect(() => {
if (location.state?.record) {
setSupplierRecord(location.state.record);
}
}, [location]);
// 处理Tab切换
const handleTabChange = (key: string) => {
setActiveTab(key);
};
// 返回上一页
const handleBack = () => {
history.goBack();
};
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}>
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
</Button>
</div>
</div>
<div className="content-area">
<Tabs activeKey={activeTab} onChange={handleTabChange} type="card">
<TabPane tab="通用评价" key="1">
<GeneralEvaluation supplierName={supplierRecord?.supplierName} />
</TabPane>
<TabPane tab="技术评价" key="2">
<TechnicalEvaluation supplierName={supplierRecord?.supplierName} />
</TabPane>
</Tabs>
</div>
</div>
);
};
export default SupplierEvaluateResultScoreDetail;

View File

@ -1,11 +1,385 @@
import React from 'react';
import { Card } from 'antd';
import React, { useState, useEffect } from 'react';
import {
Card,
Form,
Input,
Select,
Button,
Table,
Space,
Tag,
DatePicker,
TablePaginationConfig,
Tooltip,
message,
} from 'antd';
import {
SearchOutlined,
DeleteOutlined,
PlusOutlined,
CheckOutlined,
CloseOutlined,
EyeOutlined,
} from '@ant-design/icons';
import { TaskStatus, TaskStatusText, TaskStatusColor } from '@/dicts/supplierTaskDict';
import moment from 'moment';
const { Option } = Select;
const { RangePicker } = DatePicker;
// 审批状态
const ApprovalStatus = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
};
const ApprovalStatusText = {
[ApprovalStatus.PENDING]: '待审批',
[ApprovalStatus.APPROVED]: '已通过',
[ApprovalStatus.REJECTED]: '已拒绝',
};
const ApprovalStatusColor = {
[ApprovalStatus.PENDING]: 'orange',
[ApprovalStatus.APPROVED]: 'green',
[ApprovalStatus.REJECTED]: 'red',
};
// 定义搜索参数接口
interface ApprovalSearchParams {
evaluateTitle?: string;
status?: string;
timeRange?: [string, string];
}
// 定义审批记录接口
interface ApprovalRecord {
id: string;
evaluateTitle: string;
evaluateCategory: string;
initiatingUnit: string;
startTime: string;
endTime: string;
status: string;
createTime: string;
key: string;
}
const SupplierEvaluateResultApproval: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [approvalData, setApprovalData] = useState<ApprovalRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<ApprovalSearchParams>({});
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 创建单位数据
const unitOptions = [
{ label: '中山市合创展包装材料有限公司', value: '中山市合创展包装材料有限公司' },
{ label: '广州市科技发展有限公司', value: '广州市科技发展有限公司' },
{ label: '深圳市创新科技有限公司', value: '深圳市创新科技有限公司' },
{ label: '东莞市制造业有限公司', value: '东莞市制造业有限公司' },
];
// 模拟获取审批列表
const fetchApprovalList = async (
current = 1,
pageSize = 10,
params: ApprovalSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
setLoading(true);
try {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: ApprovalRecord[] = Array.from({ length: 35 }).map((_, index) => {
const id = `${index + 1}`;
const statusOptions = [ApprovalStatus.PENDING, ApprovalStatus.APPROVED, ApprovalStatus.REJECTED];
const status = statusOptions[Math.floor(Math.random() * statusOptions.length)];
// 随机选择单位和品类
const unitIndex = Math.floor(Math.random() * unitOptions.length);
const categoryIndex = Math.floor(Math.random() * categoryOptions.length);
// 随机生成日期
const startDate = moment().subtract(Math.floor(Math.random() * 60), 'days');
const endDate = moment(startDate).add(Math.floor(Math.random() * 30) + 15, 'days');
return {
id,
key: id,
evaluateTitle: `供应商评价任务${index + 1}`,
evaluateCategory: categoryOptions[categoryIndex].value,
initiatingUnit: unitOptions[unitIndex].value,
startTime: startDate.format('YYYY-MM-DD'),
endTime: endDate.format('YYYY-MM-DD'),
status,
createTime: moment().subtract(Math.floor(Math.random() * 90), 'days').format('YYYY-MM-DD HH:mm:ss'),
};
});
// 根据搜索条件过滤
let filteredData = [...mockData];
if (params.evaluateTitle) {
filteredData = filteredData.filter(item =>
item.evaluateTitle.includes(params.evaluateTitle || '')
);
}
if (params.status) {
filteredData = filteredData.filter(item =>
item.status === params.status
);
}
if (params.timeRange && params.timeRange.length === 2) {
const start = moment(params.timeRange[0]);
const end = moment(params.timeRange[1]);
filteredData = filteredData.filter(item => {
const itemStart = moment(item.startTime);
return itemStart.isBetween(start, end, null, '[]');
});
}
// 分页
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
setApprovalData(paginatedData);
setPagination({
...pagination,
current,
pageSize,
total: filteredData.length,
});
setLoading(false);
}, 500);
} catch (error) {
console.error('获取审批列表失败:', error);
message.error('获取审批列表失败');
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchApprovalList(pagination.current, pagination.pageSize, {});
}, []);
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchApprovalList(newPagination.current, newPagination.pageSize, searchParams);
};
// 处理搜索
const handleSearch = (values: any) => {
const { timeRange, ...rest } = values;
const params: ApprovalSearchParams = { ...rest };
if (timeRange && timeRange.length === 2) {
params.timeRange = [timeRange[0].format('YYYY-MM-DD'), timeRange[1].format('YYYY-MM-DD')];
}
fetchApprovalList(1, pagination.pageSize, params);
};
// 处理重置
const handleReset = () => {
form.resetFields();
fetchApprovalList(1, pagination.pageSize, {});
};
// 处理审批通过
const handleApprove = (record: ApprovalRecord) => {
message.success(`已通过评价: ${record.evaluateTitle}`);
fetchApprovalList(pagination.current, pagination.pageSize, searchParams);
};
// 处理审批拒绝
const handleReject = (record: ApprovalRecord) => {
message.error(`已拒绝评价: ${record.evaluateTitle}`);
fetchApprovalList(pagination.current, pagination.pageSize, searchParams);
};
// 处理查看详情
const handleViewDetail = (record: ApprovalRecord) => {
message.info(`查看评价详情: ${record.evaluateTitle}`);
};
// 获取状态标签
const getStatusTag = (status: string) => {
const color = ApprovalStatusColor[status as keyof typeof ApprovalStatusColor] || 'default';
const text = ApprovalStatusText[status as keyof typeof ApprovalStatusText] || '未知状态';
return <Tag color={color}>{text}</Tag>;
};
const columns = [
{
title: '序号',
render: (_: any, __: ApprovalRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
{
title: '评价主题',
dataIndex: 'evaluateTitle',
key: 'evaluateTitle',
width: 200,
ellipsis: {
showTitle: false,
},
render: (evaluateTitle: string) => (
<Tooltip placement="topLeft" title={evaluateTitle}>
{evaluateTitle}
</Tooltip>
),
},
{
title: '评价品类',
dataIndex: 'evaluateCategory',
key: 'evaluateCategory',
width: 120,
},
{
title: '发起单位',
dataIndex: 'initiatingUnit',
key: 'initiatingUnit',
width: 200,
ellipsis: {
showTitle: false,
},
render: (text: string) => (
<Tooltip placement="topLeft" title={text}>
{text}
</Tooltip>
),
},
{
title: '评价开始时间',
dataIndex: 'startTime',
key: 'startTime',
width: 120,
},
{
title: '评价结束时间',
dataIndex: 'endTime',
key: 'endTime',
width: 120,
},
{
title: '审批状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => getStatusTag(status),
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center' as const,
render: (_: unknown, record: ApprovalRecord) => (
<Space size="middle">
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => handleViewDetail(record)}
>
</Button>
{record.status === ApprovalStatus.PENDING && (
<>
<Button
type="link"
icon={<CheckOutlined />}
onClick={() => handleApprove(record)}
style={{ color: 'green' }}
>
</Button>
<Button
type="link"
icon={<CloseOutlined />}
onClick={() => handleReject(record)}
danger
>
</Button>
</>
)}
</Space>
),
},
];
return (
<Card title="供应商评价结果审批">
<div></div>
</Card>
<div className="common-container">
<div className="filter-action-row">
<Form form={form} layout="inline" onFinish={handleSearch} className="filter-form">
<Form.Item name="evaluateTitle" label="评价主题">
<Input placeholder="请输入评价主题" allowClear />
</Form.Item>
<Form.Item name="timeRange" label="评价时间">
<RangePicker
placeholder={['开始日期', '结束日期']}
format="YYYY-MM-DD"
/>
</Form.Item>
<Form.Item name="status" label="审批状态">
<Select placeholder="请选择状态" allowClear style={{ width: 150 }}>
<Option value={ApprovalStatus.PENDING}>{ApprovalStatusText[ApprovalStatus.PENDING]}</Option>
<Option value={ApprovalStatus.APPROVED}>{ApprovalStatusText[ApprovalStatus.APPROVED]}</Option>
<Option value={ApprovalStatus.REJECTED}>{ApprovalStatusText[ApprovalStatus.REJECTED]}</Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button type="primary" danger icon={<DeleteOutlined />} onClick={handleReset}>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button type="primary" ghost icon={<PlusOutlined />} onClick={() => message.info('新增审批')}>
</Button>
</div>
</div>
<div className="content-area">
<Table
columns={columns}
dataSource={approvalData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1200 }}
/>
</div>
</div>
);
};

View File

@ -1,11 +1,348 @@
import React from 'react';
import { Card } from 'antd';
import React, { useState, useEffect } from 'react';
import {
Card,
Form,
Input,
Select,
Button,
Table,
Space,
Tag,
DatePicker,
TablePaginationConfig,
Tooltip,
message,
} from 'antd';
import {
SearchOutlined,
DeleteOutlined,
PlusOutlined,
EditOutlined,
EyeOutlined,
FormOutlined,
} from '@ant-design/icons';
import { TaskStatus, TaskStatusText, TaskStatusColor } from '@/dicts/supplierTaskDict';
import moment from 'moment';
const { Option } = Select;
const { RangePicker } = DatePicker;
// 定义搜索参数接口
interface ScoreSearchParams {
evaluateTitle?: string;
supplierName?: string;
status?: string;
}
// 定义打分记录接口
interface ScoreRecord {
id: string;
evaluateTitle: string;
supplierName: string;
category: string;
status: string;
scoreStatus: string; // 打分状态:未打分、已打分、已提交
createTime: string;
key: string;
}
// 打分状态
const ScoreStatus = {
NOT_SCORED: 'not_scored',
SCORED: 'scored',
SUBMITTED: 'submitted',
};
const ScoreStatusText = {
[ScoreStatus.NOT_SCORED]: '未打分',
[ScoreStatus.SCORED]: '已打分',
[ScoreStatus.SUBMITTED]: '已提交',
};
const ScoreStatusColor = {
[ScoreStatus.NOT_SCORED]: 'orange',
[ScoreStatus.SCORED]: 'blue',
[ScoreStatus.SUBMITTED]: 'green',
};
const SupplierEvaluateScore: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [scoreData, setScoreData] = useState<ScoreRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<ScoreSearchParams>({});
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 模拟获取打分列表
const fetchScoreList = async (
current = 1,
pageSize = 10,
params: ScoreSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
setLoading(true);
try {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: ScoreRecord[] = Array.from({ length: 35 }).map((_, index) => {
const id = `${index + 1}`;
const status = Object.values(TaskStatus)[Math.floor(Math.random() * 3)];
const scoreStatus = Object.values(ScoreStatus)[Math.floor(Math.random() * 3)];
// 随机选择品类
const categoryIndex = Math.floor(Math.random() * categoryOptions.length);
return {
id,
key: id,
evaluateTitle: `供应商评价任务${index + 1}`,
supplierName: `供应商${index + 1}`,
category: categoryOptions[categoryIndex].value,
status,
scoreStatus,
createTime: moment().subtract(Math.floor(Math.random() * 90), 'days').format('YYYY-MM-DD HH:mm:ss'),
};
});
// 根据搜索条件过滤
let filteredData = [...mockData];
if (params.evaluateTitle) {
filteredData = filteredData.filter(item =>
item.evaluateTitle.includes(params.evaluateTitle || '')
);
}
if (params.supplierName) {
filteredData = filteredData.filter(item =>
item.supplierName.includes(params.supplierName || '')
);
}
if (params.status) {
filteredData = filteredData.filter(item =>
item.scoreStatus === params.status
);
}
// 分页
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
setScoreData(paginatedData);
setPagination({
...pagination,
current,
pageSize,
total: filteredData.length,
});
setLoading(false);
}, 500);
} catch (error) {
console.error('获取打分列表失败:', error);
message.error('获取打分列表失败');
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchScoreList(pagination.current, pagination.pageSize, {});
}, []);
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchScoreList(newPagination.current, newPagination.pageSize, searchParams);
};
// 处理搜索
const handleSearch = (values: any) => {
fetchScoreList(1, pagination.pageSize, values);
};
// 处理重置
const handleReset = () => {
form.resetFields();
fetchScoreList(1, pagination.pageSize, {});
};
// 处理打分
const handleScore = (record: ScoreRecord) => {
message.info(`开始为 ${record.supplierName} 打分`);
};
// 处理查看
const handleView = (record: ScoreRecord) => {
message.info(`查看 ${record.supplierName} 的打分情况`);
};
// 处理提交
const handleSubmit = (record: ScoreRecord) => {
message.success(`已提交 ${record.supplierName} 的评分`);
fetchScoreList(pagination.current, pagination.pageSize, searchParams);
};
// 获取状态标签
const getScoreStatusTag = (status: string) => {
const color = ScoreStatusColor[status as keyof typeof ScoreStatusColor] || 'default';
const text = ScoreStatusText[status as keyof typeof ScoreStatusText] || '未知状态';
return <Tag color={color}>{text}</Tag>;
};
const columns = [
{
title: '序号',
render: (_: any, __: ScoreRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
{
title: '评价主题',
dataIndex: 'evaluateTitle',
key: 'evaluateTitle',
width: 200,
ellipsis: {
showTitle: false,
},
render: (evaluateTitle: string) => (
<Tooltip placement="topLeft" title={evaluateTitle}>
{evaluateTitle}
</Tooltip>
),
},
{
title: '供应商名称',
dataIndex: 'supplierName',
key: 'supplierName',
width: 150,
ellipsis: {
showTitle: false,
},
render: (supplierName: string) => (
<Tooltip placement="topLeft" title={supplierName}>
{supplierName}
</Tooltip>
),
},
{
title: '品类',
dataIndex: 'category',
key: 'category',
width: 120,
},
{
title: '打分状态',
dataIndex: 'scoreStatus',
key: 'scoreStatus',
width: 100,
render: (status: string) => getScoreStatusTag(status),
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center' as const,
render: (_: unknown, record: ScoreRecord) => (
<Space size="middle">
{record.scoreStatus === ScoreStatus.NOT_SCORED && (
<Button
type="link"
icon={<FormOutlined />}
onClick={() => handleScore(record)}
>
</Button>
)}
{record.scoreStatus === ScoreStatus.SCORED && (
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleSubmit(record)}
>
</Button>
)}
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => handleView(record)}
>
</Button>
</Space>
),
},
];
return (
<Card title="供应商评价打分">
<div></div>
</Card>
<div className="common-container">
<div className="filter-action-row">
<Form form={form} layout="inline" onFinish={handleSearch} className="filter-form">
<Form.Item name="evaluateTitle" label="评价主题">
<Input placeholder="请输入评价主题" allowClear />
</Form.Item>
<Form.Item name="supplierName" label="供应商名称">
<Input placeholder="请输入供应商名称" allowClear />
</Form.Item>
<Form.Item name="status" label="打分状态">
<Select placeholder="请选择状态" allowClear style={{ width: 150 }}>
<Option value={ScoreStatus.NOT_SCORED}>{ScoreStatusText[ScoreStatus.NOT_SCORED]}</Option>
<Option value={ScoreStatus.SCORED}>{ScoreStatusText[ScoreStatus.SCORED]}</Option>
<Option value={ScoreStatus.SUBMITTED}>{ScoreStatusText[ScoreStatus.SUBMITTED]}</Option>
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button type="primary" danger icon={<DeleteOutlined />} onClick={handleReset}>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button type="primary" ghost icon={<PlusOutlined />} onClick={() => message.info('新增打分任务')}>
</Button>
</div>
</div>
<div className="content-area">
<Table
columns={columns}
dataSource={scoreData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1100 }}
/>
</div>
</div>
);
};

View File

@ -29,28 +29,7 @@ import styles from './supplierTaskManage.less';
const { Option } = Select;
const { RangePicker } = DatePicker;
// 任务记录类型
interface TaskRecord {
id: string;
taskName: string;
taskCode: string;
taskType: string;
templateName: string;
status: string;
startTime: string;
endTime: string;
createBy: string;
createTime: string;
updateBy?: string;
updateTime?: string;
key: string;
}
interface SearchParams {
taskName?: string;
status?: string;
dateRange?: [string, string];
}
// 删除本地接口定义,使用全局类型定义
const SupplierTaskManage: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@ -62,8 +41,8 @@ const SupplierTaskManage: React.FC = () => {
const [currentId, setCurrentId] = useState<string>('');
// 查看详情数据
const [viewData, setViewData] = useState<TaskRecord | null>(null);
const [taskData, setTaskData] = useState<TaskRecord[]>([]);
const [viewData, setViewData] = useState<SupplierEvaluate.TaskRecord | null>(null);
const [taskData, setTaskData] = useState<SupplierEvaluate.TaskRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
@ -72,13 +51,13 @@ const SupplierTaskManage: React.FC = () => {
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SearchParams>({});
const [searchParams, setSearchParams] = useState<SupplierEvaluate.TaskSearchParams>({});
// 模拟获取任务列表
const fetchTaskList = async (
current = 1,
pageSize = 10,
params: SearchParams = searchParams,
params: SupplierEvaluate.TaskSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
@ -90,7 +69,7 @@ const SupplierTaskManage: React.FC = () => {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: TaskRecord[] = Array.from({ length: 35 }).map((_, index) => {
const mockData: SupplierEvaluate.TaskRecord[] = Array.from({ length: 35 }).map((_, index) => {
const id = `${index + 1}`;
const status = Object.values(TaskStatus)[Math.floor(Math.random() * 3)];
const taskType = Math.random() > 0.5 ? TaskType.REGULAR : TaskType.SPECIAL;
@ -155,7 +134,7 @@ const SupplierTaskManage: React.FC = () => {
}, []);
// 处理查看
const handleView = (record: TaskRecord) => {
const handleView = (record: SupplierEvaluate.TaskRecord) => {
setCurrentId(record.id);
setIsViewMode(true);
setViewData(record);
@ -163,7 +142,7 @@ const SupplierTaskManage: React.FC = () => {
};
// 处理编辑
const handleEdit = (record: TaskRecord) => {
const handleEdit = (record: SupplierEvaluate.TaskRecord) => {
setIsEdit(true);
setIsViewMode(false);
setCurrentId(record.id);
@ -172,7 +151,7 @@ const SupplierTaskManage: React.FC = () => {
};
// 处理删除
const showDeleteConfirm = (record: TaskRecord) => {
const showDeleteConfirm = (record: SupplierEvaluate.TaskRecord) => {
Modal.confirm({
title: '确认删除',
icon: <ExclamationCircleOutlined />,
@ -197,7 +176,7 @@ const SupplierTaskManage: React.FC = () => {
};
// 处理发布/取消
const handlePublishStatus = (record: TaskRecord) => {
const handlePublishStatus = (record: SupplierEvaluate.TaskRecord) => {
const isInProgress = record.status === TaskStatus.PROCESSING;
const actionText = isInProgress ? '取消' : '开始';
@ -238,7 +217,7 @@ const SupplierTaskManage: React.FC = () => {
const columns = [
{
title: '序号',
render: (_: any, __: TaskRecord, index: number) =>
render: (_: any, __: SupplierEvaluate.TaskRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
@ -286,7 +265,7 @@ const SupplierTaskManage: React.FC = () => {
key: 'action',
width: 180,
align: 'center' as const,
render: (_: unknown, record: TaskRecord) => (
render: (_: unknown, record: SupplierEvaluate.TaskRecord) => (
<Space size="middle">
<Button type="link" onClick={() => handleView(record)}>
@ -354,7 +333,7 @@ const SupplierTaskManage: React.FC = () => {
// 处理搜索
const handleSearch = (values: any) => {
const { dateRange, ...rest } = values;
const params: SearchParams = { ...rest };
const params: SupplierEvaluate.TaskSearchParams = { ...rest };
if (dateRange && dateRange.length === 2) {
params.dateRange = [dateRange[0].format('YYYY-MM-DD'), dateRange[1].format('YYYY-MM-DD')];
@ -544,26 +523,23 @@ const SupplierTaskManage: React.FC = () => {
<Form.Item name="dateRange" label="评价时间">
<RangePicker />
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
fetchTaskList(1, pagination.pageSize, {});
}}
>
</Button>
</Form.Item>
</Form>
</div>
<div className="button-row">
<div className="left-buttons">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
fetchTaskList(1, pagination.pageSize, {});
}}
>
</Button>
</div>
<div className="right-buttons">
<Button type="primary" ghost icon={<PlusOutlined />} onClick={handleAdd}>

View File

@ -0,0 +1,3 @@
.supplierTemplateManageContainer {
}

View File

@ -1,11 +1,666 @@
import React from 'react';
import { Card } from 'antd';
import React, { useState, useEffect } from 'react';
import {
Button,
Table,
Space,
Modal,
message,
Input,
Select,
Form,
Tooltip,
Tag,
TablePaginationConfig,
DatePicker,
Row,
Col,
} from 'antd';
import {
PlusOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
SearchOutlined,
EditOutlined,
EyeOutlined,
} from '@ant-design/icons';
import { TaskType, TaskTypeText, TemplateStatus, TemplateStatusText, TemplateStatusColor } from '@/dicts/supplierTemplateDict';
const { Option } = Select;
const { RangePicker } = DatePicker;
const SupplierTemplateManage: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [isEdit, setIsEdit] = useState<boolean>(false);
const [isViewMode, setIsViewMode] = useState<boolean>(false);
const [currentId, setCurrentId] = useState<string>('');
// 查看详情数据
const [viewData, setViewData] = useState<SupplierEvaluate.TemplateRecord | null>(null);
const [templateData, setTemplateData] = useState<SupplierEvaluate.TemplateRecord[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total} 条记录`,
});
const [searchParams, setSearchParams] = useState<SupplierEvaluate.TemplateSearchParams>({});
// 创建单位数据
const companyOptions = [
{ label: '中山市合创展包装材料有限公司', value: '中山市合创展包装材料有限公司' },
{ label: '广州市科技发展有限公司', value: '广州市科技发展有限公司' },
{ label: '深圳市创新科技有限公司', value: '深圳市创新科技有限公司' },
{ label: '东莞市制造业有限公司', value: '东莞市制造业有限公司' },
];
// 品类数据
const categoryOptions = [
{ label: '食品', value: '食品' },
{ label: '电子', value: '电子' },
{ label: '机械', value: '机械' },
{ label: '化工', value: '化工' },
{ label: '医药', value: '医药' },
];
// 部门数据
const departmentOptions = [
{ label: '采购部', value: '采购部' },
{ label: '技术部', value: '技术部' },
{ label: '质量部', value: '质量部' },
{ label: '生产部', value: '生产部' },
];
// 模拟获取模板列表
const fetchTemplateList = async (
current = 1,
pageSize = 10,
params: SupplierEvaluate.TemplateSearchParams = searchParams,
) => {
// 更新搜索参数状态
if (params !== searchParams) {
setSearchParams(params);
}
setLoading(true);
try {
// 模拟API请求
setTimeout(() => {
// 模拟数据
const mockData: SupplierEvaluate.TemplateRecord[] = Array.from({ length: 35 }).map((_, index) => {
const id = `${index + 1}`;
const status = Math.random() > 0.5 ? TemplateStatus.ENABLED : TemplateStatus.DISABLED;
const taskType = Math.random() > 0.5 ? TaskType.REGULAR : TaskType.SPECIAL;
// 随机选择单位和品类
const companyIndex = Math.floor(Math.random() * companyOptions.length);
const categoryIndex = Math.floor(Math.random() * categoryOptions.length);
const departmentIndex = Math.floor(Math.random() * departmentOptions.length);
return {
id,
key: id,
templateName: `${TaskTypeText[taskType]}评价模板${index + 1}`,
templateCode: `TEMP${String(index + 1).padStart(4, '0')}`,
templateType: taskType,
category: categoryOptions[categoryIndex].value,
status,
createBy: companyOptions[companyIndex].value,
department: departmentOptions[departmentIndex].value,
createTime: '2023-01-01 12:00:00',
};
});
// 根据搜索条件过滤
let filteredData = [...mockData];
if (params.templateName) {
filteredData = filteredData.filter(item =>
item.templateName.includes(params.templateName || '')
);
}
if (params.status) {
filteredData = filteredData.filter(item =>
item.status === params.status
);
}
if (params.createBy) {
filteredData = filteredData.filter(item =>
item.createBy === params.createBy
);
}
if (params.category) {
filteredData = filteredData.filter(item =>
item.category === params.category
);
}
// 分页
const startIndex = (current - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedData = filteredData.slice(startIndex, endIndex);
setTemplateData(paginatedData);
setPagination({
...pagination,
current,
pageSize,
total: filteredData.length,
});
setLoading(false);
}, 500);
} catch (error) {
console.error('获取模板列表失败:', error);
message.error('获取模板列表失败');
setLoading(false);
}
};
// 首次加载获取数据
useEffect(() => {
fetchTemplateList(pagination.current, pagination.pageSize, {});
}, []);
// 处理查看
const handleView = (record: SupplierEvaluate.TemplateRecord) => {
setCurrentId(record.id);
setIsViewMode(true);
setViewData(record);
setModalVisible(true);
};
// 处理编辑
const handleEdit = (record: SupplierEvaluate.TemplateRecord) => {
setIsEdit(true);
setIsViewMode(false);
setCurrentId(record.id);
setViewData(record);
setModalVisible(true);
};
// 处理删除
const showDeleteConfirm = (record: SupplierEvaluate.TemplateRecord) => {
Modal.confirm({
title: '确认删除',
icon: <ExclamationCircleOutlined />,
content: `确定要删除模板: ${record.templateName}?`,
okText: '确定',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
try {
// 模拟删除请求
setTimeout(() => {
message.success('删除成功');
fetchTemplateList(pagination.current, pagination.pageSize, searchParams);
}, 500);
} catch (error) {
console.error('删除模板失败:', error);
message.error('删除模板失败');
}
},
});
};
// 处理启用/禁用
const handleStatusToggle = (record: SupplierEvaluate.TemplateRecord) => {
const isEnabled = record.status === TemplateStatus.ENABLED;
const actionText = isEnabled ? '禁用' : '启用';
Modal.confirm({
title: `确认${actionText}`,
icon: <ExclamationCircleOutlined />,
content: `确定要${actionText}模板: ${record.templateName}?`,
okText: '确定',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
try {
// 模拟请求
setTimeout(() => {
message.success(`${actionText}成功`);
fetchTemplateList(pagination.current, pagination.pageSize, searchParams);
}, 500);
} catch (error) {
console.error(`${actionText}失败:`, error);
message.error(`${actionText}失败`);
}
},
});
};
// 获取状态标签
const getStatusTag = (status: string) => {
const color = TemplateStatusColor[status as keyof typeof TemplateStatusColor] || 'default';
const text = TemplateStatusText[status as keyof typeof TemplateStatusText] || '未知状态';
return <Tag color={color}>{text}</Tag>;
};
// 处理表格分页变化
const handleTableChange = (newPagination: TablePaginationConfig) => {
fetchTemplateList(newPagination.current, newPagination.pageSize, searchParams);
};
const columns = [
{
title: '序号',
render: (_: any, __: SupplierEvaluate.TemplateRecord, index: number) =>
(pagination.current! - 1) * pagination.pageSize! + index + 1,
width: 80,
},
{
title: '模板名称',
dataIndex: 'templateName',
key: 'templateName',
width: 200,
ellipsis: {
showTitle: false,
},
render: (templateName: string) => (
<Tooltip placement="topLeft" title={templateName}>
{templateName}
</Tooltip>
),
},
{
title: '品类',
dataIndex: 'category',
key: 'category',
width: 120,
},
{
title: '创建单位',
dataIndex: 'createBy',
key: 'createBy',
width: 180,
ellipsis: {
showTitle: false,
},
render: (text: string) => (
<Tooltip placement="topLeft" title={text}>
{text}
</Tooltip>
),
},
{
title: '创建部门',
dataIndex: 'department',
key: 'department',
width: 120,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => getStatusTag(status),
},
{
title: '操作',
key: 'action',
width: 150,
align: 'center' as const,
render: (_: unknown, record: SupplierEvaluate.TemplateRecord) => (
<Space size="middle">
<Button type="link" onClick={() => handleEdit(record)}>
</Button>
<Button type="link" onClick={() => handleView(record)}>
</Button>
</Space>
),
},
];
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
};
const hasSelected = selectedRowKeys.length > 0;
// 处理添加
const handleAdd = () => {
setIsEdit(false);
setIsViewMode(false);
setCurrentId('');
setViewData(null);
setModalVisible(true);
};
// 处理批量删除
const handleBatchDelete = () => {
Modal.confirm({
title: '确认批量删除',
icon: <ExclamationCircleOutlined />,
content: '确定要删除选中的模板吗?此操作不可恢复。',
okText: '删除',
okType: 'danger',
cancelText: '取消',
maskClosable: false,
onOk: async () => {
setLoading(true);
try {
// 模拟批量删除
setTimeout(() => {
setSelectedRowKeys([]);
message.success('批量删除成功');
fetchTemplateList(pagination.current, pagination.pageSize, searchParams);
setLoading(false);
}, 1000);
} catch (error) {
console.error('批量删除失败:', error);
message.error('批量删除失败');
setLoading(false);
}
},
});
};
// 处理搜索
const handleSearch = (values: any) => {
const { dateRange, ...rest } = values;
const params: SupplierEvaluate.TemplateSearchParams = { ...rest };
if (dateRange && dateRange.length === 2) {
params.dateRange = [dateRange[0].format('YYYY-MM-DD'), dateRange[1].format('YYYY-MM-DD')];
}
fetchTemplateList(1, pagination.pageSize, params);
};
// 处理模态框取消
const handleModalCancel = () => {
setModalVisible(false);
setIsViewMode(false);
setViewData(null);
};
// 渲染模板详情
const renderTemplateDetail = () => {
if (!viewData) return null;
return (
<div >
<Row gutter={[16, 16]}>
<Col span={12}>
<div >
<span ></span>
<span>{viewData.templateName}</span>
</div>
</Col>
<Col span={12}>
<div >
<span ></span>
<span >{viewData.templateCode}</span>
</div>
</Col>
<Col span={12}>
<div>
<span ></span>
<span >
{TaskTypeText[viewData.templateType as keyof typeof TaskTypeText] || '未知类型'}
</span>
</div>
</Col>
<Col span={12}>
<div >
<span ></span>
<span >{viewData.category}</span>
</div>
</Col>
<Col span={12}>
<div >
<span></span>
<span >{getStatusTag(viewData.status)}</span>
</div>
</Col>
<Col span={12}>
<div >
<span ></span>
<span >{viewData.createBy}</span>
</div>
</Col>
<Col span={12}>
<div >
<span ></span>
<span >{viewData.department}</span>
</div>
</Col>
<Col span={12}>
<div >
<span ></span>
<span >{viewData.createTime}</span>
</div>
</Col>
{viewData.updateBy && (
<Col span={12}>
<div >
<span ></span>
<span >{viewData.updateBy}</span>
</div>
</Col>
)}
{viewData.updateTime && (
<Col span={12}>
<div >
<span ></span>
<span >{viewData.updateTime}</span>
</div>
</Col>
)}
</Row>
</div>
);
};
// 渲染模板表单
const renderTemplateForm = () => {
return (
<Form
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
initialValues={viewData || {}}
>
<Form.Item
label="模板名称"
name="templateName"
rules={[{ required: true, message: '请输入模板名称' }]}
>
<Input placeholder="请输入模板名称" />
</Form.Item>
<Form.Item
label="模板编号"
name="templateCode"
rules={[{ required: true, message: '请输入模板编号' }]}
>
<Input placeholder="请输入模板编号" disabled={isEdit} />
</Form.Item>
<Form.Item
label="模板类型"
name="templateType"
rules={[{ required: true, message: '请选择模板类型' }]}
>
<Select placeholder="请选择模板类型">
<Option value={TaskType.REGULAR}>{TaskTypeText[TaskType.REGULAR]}</Option>
<Option value={TaskType.SPECIAL}>{TaskTypeText[TaskType.SPECIAL]}</Option>
</Select>
</Form.Item>
<Form.Item
label="品类"
name="category"
rules={[{ required: true, message: '请选择品类' }]}
>
<Select placeholder="请选择品类">
{categoryOptions.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="创建单位"
name="createBy"
rules={[{ required: true, message: '请选择创建单位' }]}
>
<Select placeholder="请选择创建单位">
{companyOptions.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="创建部门"
name="department"
rules={[{ required: true, message: '请选择创建部门' }]}
>
<Select placeholder="请选择创建部门">
{departmentOptions.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="模板状态"
name="status"
rules={[{ required: true, message: '请选择模板状态' }]}
>
<Select placeholder="请选择模板状态">
<Option value={TemplateStatus.ENABLED}>{TemplateStatusText[TemplateStatus.ENABLED]}</Option>
<Option value={TemplateStatus.DISABLED}>{TemplateStatusText[TemplateStatus.DISABLED]}</Option>
</Select>
</Form.Item>
<Form.Item wrapperCol={{ offset: 6, span: 16 }}>
<Button type="primary" htmlType="submit">
</Button>
<Button style={{ marginLeft: 8 }} onClick={handleModalCancel}>
</Button>
</Form.Item>
</Form>
);
};
return (
<Card title="供应商模板管理">
<div></div>
</Card>
<div className={`common-container`}>
<div className="filter-action-row">
<Form
form={form}
name="search"
onFinish={handleSearch}
layout="inline"
className="filter-form"
>
<Form.Item name="templateName" label="模板名称">
<Input placeholder="请输入模板名称" allowClear />
</Form.Item>
<Form.Item name="createBy" label="创建单位">
<Select placeholder="请选择创建单位" allowClear style={{ width: 200 }}>
{companyOptions.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item name="category" label="品类">
<Select placeholder="请选择品类" allowClear style={{ width: 150 }}>
{categoryOptions.map(option => (
<Option key={option.value} value={option.value}>{option.label}</Option>
))}
</Select>
</Form.Item>
<Form.Item className="filter-btns">
<Button type="primary" htmlType="submit" icon={<SearchOutlined />} onClick={() => form.submit()}>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => {
form.resetFields();
fetchTemplateList(1, pagination.pageSize, {});
}}
>
</Button>
</Form.Item>
</Form>
<div className="right-buttons">
<Button type="primary" ghost icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
<Button
danger
icon={<DeleteOutlined />}
onClick={handleBatchDelete}
disabled={!hasSelected}
loading={loading}
>
</Button>
</div>
</div>
<div className="content-area">
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={templateData}
pagination={pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1200 }}
/>
</div>
{/* 新增/编辑/查看模态框 */}
<Modal
title={isViewMode
? "查看模板详情"
: isEdit
? "编辑模板"
: "新增模板"}
visible={modalVisible}
onCancel={handleModalCancel}
width={800}
maskClosable={false}
destroyOnClose
footer={
isViewMode
? [
<Button key="close" onClick={handleModalCancel}>
</Button>,
]
: null
}
>
{isViewMode ? renderTemplateDetail() : renderTemplateForm()}
</Modal>
</div>
);
};

150
src/typings.d.ts vendored
View File

@ -43,3 +43,153 @@ declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
declare const REQUEST_BASE: string;
declare const UPLOAD_URL: string;
declare namespace API {
// ... 其他类型定义
// 通用响应类型
type Result<T = any> = {
code: number;
msg: string;
data: T;
};
// 分页查询参数
type PageParams = {
current?: number;
pageSize?: number;
};
// 分页响应数据
type PageResult<T = any> = {
list: T[];
pagination: {
total: number;
pageSize: number;
current: number;
};
};
}
declare namespace SupplierEvaluate {
// 模板管理相关类型
type TemplateRecord = {
id: string;
key: string;
templateName: string;
templateCode: string;
templateType: string;
category: string;
status: string;
createBy: string;
department?: string;
createTime: string;
updateBy?: string;
updateTime?: string;
};
type TemplateSearchParams = {
templateName?: string;
status?: string;
createBy?: string;
category?: string;
dateRange?: string[];
};
// 任务管理相关类型
type TaskRecord = {
id: string;
key: string;
taskName: string;
taskCode: string;
taskType: string;
templateName: string;
status: string;
startTime: string;
endTime: string;
createBy: string;
createTime: string;
updateBy?: string;
updateTime?: string;
};
type TaskSearchParams = {
taskName?: string;
status?: string;
dateRange?: string[];
};
// 评价结果相关类型
type EvaluateResultRecord = {
id: string;
key: string;
taskName: string;
taskCode: string;
taskType: string;
status: string;
startTime: string;
endTime: string;
createBy: string;
createTime: string;
updateBy?: string;
updateTime?: string;
supplierCount?: number;
approvalStatus?: string;
};
type EvaluateResultSearchParams = {
taskName?: string;
status?: string;
approvalStatus?: string;
dateRange?: string[];
};
// 评价结果详情相关类型
type EvaluateResultDetailRecord = {
id: string;
key: string;
supplierName: string;
category: string;
score: number;
level: string;
};
type EvaluateResultDetailSearchParams = {
supplierName?: string;
level?: string;
};
// 评价得分明细相关类型
type ScoreDetailItem = {
key: string;
index: number;
itemName: string;
weight: number;
score: number;
weightedScore: number;
remark?: string;
};
}
declare module '*.css';
declare module '*.less';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module '*.svg' {
export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement;
const url: string;
export default url;
}
declare module 'slash2';
declare module '*.json';
declare module 'react-copy-to-clipboard';
declare module 'react-fittext';
declare module '@antv/data-set';
declare module 'nzh/cn';
declare module 'webpack-theme-color-replacer';
declare module 'webpack-theme-color-replacer/client';