菜单,合同前端提交

This commit is contained in:
32503
2025-06-11 09:48:28 +08:00
parent f9d6d38866
commit 300a9945cf
8 changed files with 2602 additions and 0 deletions

View File

@ -0,0 +1,359 @@
import React, {useState, useRef, useCallback} from 'react';
import { history } from 'umi';
import {Input, Button, Modal, Form, Select, message, DatePicker} from 'antd';
import { SearchOutlined , UndoOutlined} from '@ant-design/icons';
import ProTable, {ActionType, ProColumns} from '@ant-design/pro-table';
import {saveDateTimeFormatter} from "@/utils/DateUtils";
import { fetchContracts, deleteContract,updateContract, getContract } from "@/pages/Contract/ContractService";
import moment from "moment";
const { Option } = Select;
interface Contract {
contractType: string;
contractCode: string;
contractName: string;
supplierName: string;
contractAmount: number;
supplierCreditCode: string;
status: string;
createTime: string;
}
const contractTypeList = [
{
label: '我已执行合同,录入阳光购系统',
value: '0'
},
{
label: '我要与供应商进行在线合同磋商',
value: '1'
}
];
const ContractList: React.FC = () => {
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [searchParams, setSearchParams] = useState({
contractCode: '',
contractName: '',
supplierName: '',
projectName: '',
biddingName: '',
createDate: undefined
});
const actionRef = useRef<ActionType>();
const handleEdit = useCallback((selectedRecord: any, opt: string) => {
getContract(selectedRecord.id).then((res) => {
history.push({
pathname: '/stepOne',
state: {
contractInfo: res.data,
opt: opt
}
});
});
},[]);
// 选择合同类型
const handleChoose = () => {
// 重置表单到初始值
form.resetFields();
setIsModalVisible(true);
};
const createContract = async () => {
// 验证表单并获取值
const values = await form.validateFields();
//路由跳转
history.push({
pathname: '/stepOne',
state: {
contractType: values.contractType
}
});
// 关闭模态框
setIsModalVisible(false);
};
// 处理搜索
const handleSearch = useCallback(() => {
if (searchParams.createDate) {
searchParams.createDate = saveDateTimeFormatter(moment(searchParams.createDate))
setSearchParams({
...searchParams
})
}
actionRef.current?.reload()
}, []);
const goPerformance = useCallback((selectedRecord: any) => {
getContract(selectedRecord.id).then((res) => {
if(res?.code === 200){
history.push({
pathname: '/performance',
state: {
contractInfo: res.data,
opt: 'edit'
}
});
}
});
},[])
const handleUpdate = useCallback((optRecord: any) => {
updateContract(optRecord).then((r: any) => {
if (r?.code == 200) {
message.success('操作成功');
} else {
message.error('操作失败');
}
}).finally(() => actionRef.current?.reload());
},[])
const handleOperation = useCallback((optRecord: any, status: string) => {
optRecord.status = status;
const operation = status == '2' ? '中止' : status == '3' ? '终止' : '';
Modal.confirm({
title: '确认' + operation + '该合同?',
onOk: async () => {
await handleUpdate(optRecord);
}
})
},[])
const handleDelete = useCallback((id: string) => {
Modal.confirm({
title: '确认删除该合同?',
onOk: async () => {
await deleteContract(id).then((r: any) => {
if (r?.code == 200) {
message.success('删除成功');
} else {
message.error('删除失败');
}
})
.finally(() => actionRef.current?.reload());
},
});
},[])
// 处理重置
const handleReset = () => {
setSearchParams({
contractCode: '',
contractName: '',
supplierName: '',
projectName: '',
biddingName: '',
createDate: undefined
});
actionRef.current?.reload();
};
// 定义ProTable的列配置
const columns: ProColumns<Contract>[] = [
{
title: '合同/协议编号',
dataIndex: 'contractCode',
key: 'contractCode',
width: 150,
},
{
title: '合同/协议名称',
dataIndex: 'contractName',
key: 'contractName',
width: 200,
},
{
title: '供应商名称',
dataIndex: 'supplierName',
key: 'supplierName',
width: 180,
},
{
title: '合同金额',
dataIndex: 'amount',
key: 'amount',
width: 120,
render: (text: any) => `${text}`,
},
{
title: '创建时间',
dataIndex: 'createDate',
key: 'createDate',
width: 160,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
render: (_: any, rec: Contract) => {
const status = rec.status;//合同状态
if (status === '0') {
return (<>稿</>)
}else if (status === '1') {
return (<></>)
}else if (status === '2') {
return (<></>)
}else if (status === '3') {
return (<></>)
}else if (status === '4') {
return (<></>)
} else {
return (<></>)
}
}
},
{
title: '操作',
key: 'operation',
width: 250,
valueType: 'option',
render: (text: any, optRecord: any) => (
<>
{optRecord.status === '0' && (<Button type="link" danger onClick={() => handleDelete(optRecord.id)}></Button>)}
{(optRecord.status === '0'|| optRecord.status === '2' )&& (<Button type="link" danger onClick={() => handleEdit(optRecord, 'edit')}></Button>)}
{optRecord.status === '1' && (<Button type="link" danger onClick={() => goPerformance(optRecord)}></Button>)}
{optRecord.status === '1' && (<Button type="link" danger onClick={() => handleOperation(optRecord, '2')}></Button>)}
{optRecord.status === '1' && (<Button type="link" danger onClick={() => handleOperation(optRecord, '3')}></Button>)}
{(<Button type="link" onClick={() => handleEdit(optRecord, 'detail')}></Button>)}
</>
)
},
];
// 定义ProTable的请求函数
const request = async (params: any, sorter: any) => {
try {
// 构建请求参数,合并搜索参数
const requestParams = {
...params,
...searchParams,
basePageRequest: {
pageNo: params.current || 1,
pageSize: params.pageSize || 10
},
};
const result = await fetchContracts(requestParams);
// 转换数据格式以适应ProTable的要求
return {
data: result.data.records,
total: result.data.total,
success: true,
};
} catch (error) {
message.error('获取合同列表失败');
return {
data: [],
total: 0,
success: false,
};
}
};
return (
<div style={{ padding: '3px' }}>
<Modal
centered
title={'请选择类型'}
visible={isModalVisible}
onCancel={() => setIsModalVisible(false)}
onOk={createContract}
okText="确定"
>
<Form
form={form}
initialValues={{
contractType: '0'
}}
>
<Form.Item
name="contractType"
label="合同类型"
rules={[{ required: true, message: '请选择合同类型' }]}
>
<Select placeholder="请选择">
{contractTypeList.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</Form>
</Modal>
{/* 标题部分 */}
<div style={{ borderLeft: '4px solid #0066cc', paddingLeft: 8, marginBottom: 16 }}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
{/* 第一行搜索框 */}
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center' }}>
<Input
placeholder="搜索合同编号"
value={searchParams.contractCode}
onChange={(e) => setSearchParams({...searchParams, contractCode: e.target.value})}
style={{ width: 200, marginRight: 16 }}
/>
<Input
placeholder="搜索合同名称关键词"
value={searchParams.contractName}
onChange={(e) => setSearchParams({...searchParams, contractName: e.target.value})}
style={{ width: 200, marginRight: 16 }}
/>
<Input
placeholder="搜索供应商关键词"
value={searchParams.supplierName}
onChange={(e) => setSearchParams({...searchParams, supplierName: e.target.value})}
style={{ width: 200, marginRight: 16 }}
/>
<div style={{ marginLeft: 'auto' }}>
<Button type="primary" onClick={handleSearch} icon={<SearchOutlined />}></Button>
<Button type="text" onClick={handleReset} icon={<UndoOutlined/> } style={{ marginLeft: 8, border: '1px solid #d9d9d9', borderRadius: 2 }}></Button>
</div>
</div>
{/* 第二行搜索框 */}
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center' }}>
<Input
placeholder="搜索项目名称关键词"
value={searchParams.projectName}
onChange={(e) => setSearchParams({...searchParams, projectName: e.target.value})}
style={{ width: 200, marginRight: 16 }}
/>
<Input
placeholder="搜索标段名称关键词"
value={searchParams.biddingName}
onChange={(e) => setSearchParams({...searchParams, biddingName: e.target.value})}
style={{ width: 200, marginRight: 16 }}
/>
<DatePicker placeholder="请选择创建时间" inputReadOnly={true} onChange={ (date, dateString) => {
// @ts-ignore
setSearchParams({...searchParams, createDate: date})}
} style={{ width: 200, marginRight: 16 }}/>
<Button type="primary" style={{ marginLeft: 'auto', width: 170 }} onClick={handleChoose}></Button>
</div>
{/* 使用ProTable替代Table */}
<ProTable<Contract>
actionRef={actionRef}//action触发后更新表格
columns={columns}
request={request}
rowKey="contractCode"
pagination={{
pageSize: 10,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}}
// 移除操作按钮
options={false}
search={false}
/>
</div>
);
};
export default ContractList;

View File

@ -0,0 +1,146 @@
import request from "@/utils/request";
// 字典项类型定义
export interface CoscoContract {
// 基本信息
id: string;
contractType: number;
projectName: string;
biddingName: string;
biddingCode: string;
// 采购单位信息
purchaserName: string;
purchaserCode: string;
industryCode: string;
// 供应商信息
supplierId: string;
supplierName: string;
supplierCode: string;
signerName: string;
signerContact: string;
// 合同基本信息
frameworkAgreementId: string;
contractCode: string;
contractName: string;
categoryCode: string;
signedDatetime: string; // 日期时间通常在前端使用字符串表示
valuationType: number;
amount: number; // BigDecimal 转为 number
payType: number;
startDatetime: string;
endDatetime: string;
// 履约信息
performancePlace: string;
guaranteeType: number;
note: string;
contactAccessory: string;
}
// 获取字典列表
// export const fetchDictionaries = async (): Promise<DictionaryItem[]> => {
// const response = await axios.get('/api/sys-manager-ebtp-project/v1/dictProject/getDictList?parentCode=procurement_type&toParentCode=entrust');
// return response.data.data;
// };
const prefix = '/api/biz-service-ebtp-project/';
export async function fetchContracts(params: any) {
return request(prefix + 'v1/contract/getPage', {
data: params,
method: 'POST'
});
}
export async function updateContract(params: any) {
return request(prefix + 'v1/contract/update', {
data: params,
method: 'POST'
});
}
export async function createContract(params: any) {
return request(prefix + 'v1/contract', {
data: params,
method: 'POST'
});
}
export async function getContract(param: any) {
return request(prefix + 'v1/contract/' + param, {
method: 'get'
});
}
export async function listNegotiate(params: any) {
return request(prefix + 'v1/negotiate/list', {
data: params,
method: 'POST'
});
}
export async function deleteContract(param: any) {
return request(prefix + 'v1/contract/delete/' + param, {
method: 'get',
});
}
export async function createPerformance(params: any) {
return request(prefix + 'v1/performance', {
data: params,
method: 'POST'
});
}
export async function updatePerformance(params: any) {
return request(prefix + 'v1/performance/update', {
data: params,
method: 'POST'
});
}
export async function createAmountItem(params: any) {
return request(prefix + 'v1/amount/item', {
data: params,
method: 'POST'
});
}
export async function updateAmountItem(params: any) {
return request(prefix + 'v1/amount/item/update', {
data: params,
method: 'POST'
});
}
export async function createNegotiate(params: any) {
return request(prefix + 'v1/negotiate', {
data: params,
method: 'POST'
});
}
export async function updateNegotiate(params: any) {
return request(prefix + 'v1/negotiate/update', {
data: params,
method: 'POST'
});
}
export async function deleteAmountItem(param: any) {
return request(prefix + 'v1/amount/item/delete/' + param, {
method: 'get',
});
}
export async function getPerformanceById(params: any) {
return request(prefix + 'v1/performance', {
params: params,
method: 'POST'
});
}

View File

@ -0,0 +1,250 @@
import React, {useEffect, useState} from 'react';
import {Table, Button, Upload, message, Form} from 'antd';
import {PlusOutlined, DeleteOutlined, SendOutlined, CheckOutlined} from '@ant-design/icons';
import moment, {Moment} from "moment";
import {createNegotiate, listNegotiate} from "@/pages/Contract/ContractService";
import {useLocation} from "umi";
import {saveDateTimeFormatter} from "@/utils/DateUtils";
import TextArea from "antd/es/input/TextArea";
import {history} from "@@/core/history";
const createDate = moment()
const tableData: any[] | (() => any[]) = [
// {
// key: '1',
// id: '',
// contractFileId: '',
// contractFileName: '2024年度汽车保养服务合同.doc',
// createBy: '集团',
// createDate: createDate,
// note: '我方已上传,请确认',
// },
// {
// key: '2',
// id: '',
// contractFileId: '',
// contractFileName: '2024年度汽车保养服务合同-20241123.doc',
// createBy: '北京风向文化有限公司',
// createDate: createDate,
// note: '已对合同内容进行修改,请确认。',
// },
];
const Negotiate: React.FC = () => {
const location = useLocation();
const contractInfo = location.state?.contractInfo;
const opt = location.state?.opt;
let readOnly = false;
if (opt === 'detail') {
readOnly = true;
}
// 模拟表格数据
const [tableDataState, setTableDataState] = useState(tableData);
// 处理TextArea内容变化
const handleNoteChange = (e: React.ChangeEvent<HTMLTextAreaElement>, key: string) => {
const newTableData = tableDataState.map(item => {
if (item.key === key) {
return {...item, note: e.target.value};
}
return item;
});
setTableDataState(newTableData);
};
const handleDelete = (key: string) => {
const newTableData = tableDataState.filter((row) => row.key !== key);
newTableData.forEach((item, index) => {
item.key = index.toString();
})
setTableDataState(newTableData);
};
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
}
};
const fetchData = async () => {
try {
await listNegotiate({
contractId: contractInfo.id
}).then((res) => {
if (res?.code === 200) {
setTableDataState(res.data);
if (res.data && res.data.length != 0) {
for (const i in res.data) {
const record = res.data[i];
record.readOnly = true;
}
}
}
}); // 传递搜索参数
} catch (error) {
message.error('获取磋商信息列表失败');
} finally {
}
};
useEffect(() => {
fetchData().then(res => {} );
}, []);
const saveNegotiateItem = (record: any) => {
delete record.key;
record.contractId = contractInfo.id;
record.createDate = saveDateTimeFormatter(moment(record.createDate))
createNegotiate(record).then((res) => {
if (res?.code === 200) {
message.success("发送成功")
} else {
message.error("发送失败")
}
})
}
// @ts-ignore
const columns: Table.ColumnType<any>[] = [
{
title: '序号',
dataIndex: 'key',
key: 'key',
render: (_: any, record: any, index: number) => index + 1,
},
{
title: '合同文件名称',
dataIndex: 'contractFileName',
key: 'contractFileName',
render: (text: string) => (
<span style={{color: 'red'}}>
{text ? <a href="#">{text}</a> : (
<Upload
name="file"
// listType="simple"
onChange={(info) => {
console.log('Uploaded file info:', info);
message.success('文件上传成功');
}}
>
<Button type="primary"></Button>
</Upload>
)}
</span>
),
},
{
title: '上传方',
dataIndex: 'createBy',
key: 'createBy',
},
{
title: '创建时间',
dataIndex: 'createDate',
key: 'createDate',
render: (value: Moment, record: any) => value ? moment(value).format('yyyy-MM-DD') : null
},
{
title: '备注',
dataIndex: 'note',
key: 'note',
render: (text: string, record: any) =>
(
<TextArea
placeholder="请输入备注"
value={text}
readOnly={readOnly}
onChange={(e) => handleNoteChange(e, record.key)}
/>
)
},
{
title: '操作',
key: 'operation',
render: (_: any, record: any) => (
<div>
<Button
type="link"
danger
disabled={readOnly}
icon={<DeleteOutlined/>}
onClick={() => handleDelete(record.key)}
>
</Button>
<Button type="link" icon={<CheckOutlined/>} onClick={() => saveNegotiateItem(record)}
hidden={readOnly || record.id}>
</Button>
<Button type="link" icon={<SendOutlined/>} disabled={readOnly}>
</Button>
</div>
),
},
];
const addRow = () => {
const newRow = {
id: '',
key: (tableDataState.length + 1).toString(),
contractFileName: '',
contractFileId: '',
createBy: '', // 设置默认上传方
createDate: createDate,
note: ''
};
const newTableData = [...tableDataState, newRow];
setTableDataState(newTableData);
};
const handleNegotiationEnd = () => {
console.log('磋商结束操作触发');
message.success('磋商结束');
};
return (
<div style={{marginLeft: 20}}>
{/* 补充标题部分 */}
<div style={{borderLeft: '4px solid #0066cc', paddingLeft: 8, marginBottom: 16}}>
<span style={{fontSize: 16, fontWeight: 'bold'}}></span>
</div>
<Table
rowSelection={{
type: 'checkbox',
...rowSelection,
}}
columns={columns}
dataSource={tableDataState}/>
{/* 增加行按钮 */}
<Button type="primary" onClick={addRow} icon={<PlusOutlined/>} hidden={readOnly}>
</Button>
{/* 居中的磋商结束按钮 */}
<div style={{textAlign: 'center', marginTop: 24}}>
<Button
type="primary"
onClick={handleNegotiationEnd}
style={{width: 160}}
hidden={readOnly}
>
</Button>
<Form.Item label="">
<Button type="primary" hidden={!readOnly} onClick={() => {
history.push({
pathname: '/contract'
})
}}>
</Button>
</Form.Item>
</div>
</div>
);
};
export default Negotiate;

View File

@ -0,0 +1,592 @@
import React, {useEffect, useState} from 'react';
import {Form, Select, Input, Table, Button, message, DatePicker} from 'antd';
import {useLocation} from "umi";
const {Option} = Select;
import {
createPerformance,
updateContract,
createAmountItem,
updatePerformance, updateAmountItem
} from "./ContractService";
import {history} from "@@/core/history";
import {saveDateTimeFormatter} from "@/utils/DateUtils";
import moment, {Moment} from "moment";
const createDate = moment()
// 模拟合同履约状态选项
const performanceStatusOptions = [
{label: '正常履约', value: '0'},
{label: '中止履约', value: '1'},
{label: '终止履约', value: '2'}
];
const yesOrNo = [
{label: '是', value: '0'},
{label: '否', value: '1'}
]
// 模拟合同履约阶段选项
const performanceStageOptions = [
{label: '交付前', value: '0'},
{label: '交付中', value: '1'},
{label: '交付完毕', value: '2'},
{label: '质量保证期间', value: '3'},
{label: '履约结束', value: '4'},
{label: '其他', value: '5'}
];
//合同履约阶段说明
const performanceStageDescOptions = [
{label: '因供应商交付质量(态度、时间逾期、拒签合约、项目造假、资质不达标、存在造假或其他负面原因)导致的合同提前终止;', value: '0'},
{label: '因触发合同条款,双方友好协商提前终止合同;', value: '1'},
{label: '采购方主动取消,提前终止合同;', value: '2'},
{label: '供应商主动取消或提前(因价格、工作量)终止合同;', value: '3'},
{label: '因录入问题或系统操作问题导致的合同作废(取消);', value: '4'},
{label: '因不可抗力因素(战争、疫情、灾害、新增监管要求)导致的合同提前终止;', value: '5'},
{label: '其他(需列名具体原因)', value: '6'}
];
// 模拟金额单位选项
const amountUnitOptions = [
{label: '元', value: '0'},
{label: '万元', value: '1'},
];
const initTableData = [
{
key: '1',
id: '',
contractPerformanceId: '',
invoiceAmountItem: 0,
invoiceUnit: '0',
payAmountItem: 0,
payUnit: '0',
createDate: createDate
}
];
// @ts-ignore
const Performance: React.FC = () => {
const [form] = Form.useForm();
const location = useLocation();
const contractInfo = location.state?.contractInfo;
const opt = location.state?.opt;
const totalReadOnly = true;
let readOnly = false;
if(opt ==='detail'){
readOnly = true;
}else if(opt === 'edit') {
readOnly = false;
}
const [tableData, setTableData] = useState(initTableData);
// @ts-ignore
const [invoiceAmount, setInvoiceAmount] = useState(0);
// @ts-ignore
const [payAmount, setPayAmount] = useState(0);
let status0 = true;
let status1 = true;
let changeType0 = true;
let phaseDescriptionText0 = true;
// const [status0, setStatus0] = useState(true);
// const [status1, setStatus1] = useState(true);
// const [changeType0,setChangeType0] = useState(true);
// 计算总发票金额
const calculateTotalInvoiceAmount = (data: any) => {
return data.reduce((sum: number, row: { invoiceAmountItem: any; }) => sum + Number(row.invoiceAmountItem || 0), 0);
};
// 计算总支付金额
const calculateTotalPaymentAmount = (data: any) => {
return data.reduce((sum: number, row: { payAmountItem: any; }) => sum + Number(row.payAmountItem || 0), 0);
};
useEffect(() => {
const newInvoiceAmount = calculateTotalInvoiceAmount(tableData);
const newPayAmount = calculateTotalPaymentAmount(tableData);
// 更新状态
setInvoiceAmount(newInvoiceAmount);
setPayAmount(newPayAmount);
// 同步到表单
form.setFieldsValue({
invoiceAmount: newInvoiceAmount,
payAmount: newPayAmount
});
}, [tableData, form]);
// 初始化数据
useEffect(() => {
if(opt && contractInfo.performance){
// 设置表单初始值
form.setFieldsValue({
...contractInfo.performance,
changeDate: moment(contractInfo.performance.changeDate)
});
// 处理表格数据
if(contractInfo.performance.list){
const newTableData = contractInfo.performance.list.map((amountItem: any, index: number) => ({
...amountItem,
key: index + '',
invoiceUnit: '0',
payUnit: '0'
}));
setTableData(newTableData);
}
}
}, [opt, contractInfo, form]);
// 1111
const handleInvoiceAmountItemChange = (key: string, value: any) => {
if(isNaN(value)){
message.error("请输入正确的数字").then()
}else{
// 更新表格中对应行的发票金额
const newTableData = tableData.map((row: any) =>
row.key === key ? {...row, invoiceAmountItem: value} : row
);
setTableData(newTableData);
}
};
// 222
const handlePayAmountItemChange = (key: string, value: any) => {
if(isNaN(value)){
message.error("请输入正确的数字").then()
}else{
// 更新表格中对应行的支付金额
const newTableData = tableData.map((row: any) =>
row.key === key ? {...row, payAmountItem: value} : row
);
setTableData(newTableData);
}
};
const handleDeleteRow = (key: string) => {
const newTableData = tableData.filter((row: any) => row.key !== key);
newTableData.forEach((item,index) => {
item.key = index.toString();
})
setTableData(newTableData);
};
// @ts-ignore
const columns: Table.ColumnType<any>[] = [
{
title: '序号',
dataIndex: 'key',
key: 'key',
render: (_: any, record: any, index: number) => index + 1,
},
{
title: '发票金额(CNY)',
dataIndex: 'invoiceAmountItem',
key: 'invoiceAmountItem',
render: (text: any, record: any) => (
<Input
readOnly={readOnly}
placeholder="请输入金额"
value={text}
onChange={(e) => handleInvoiceAmountItemChange(record.key, e.target.value)}
/>
),
},
{
title: '',
dataIndex: 'invoiceUnit',
key: 'invoiceUnit',
render: (text: any, record: any) => (
<Select
defaultValue={'0'}
disabled
value={text}
>
{amountUnitOptions.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
),
},
{
title: '支付金额(CNY)',
dataIndex: 'payAmountItem',
key: 'payAmountItem',
render: (text: any, record: any) => (
<Input
readOnly={readOnly}
placeholder="请输入金额"
value={text}
onChange={(e) => handlePayAmountItemChange(record.key, e.target.value)}
/>
),
},
{
title: '',
dataIndex: 'payUnit',
key: 'payUnit',
render: (text: any, record: any) => (
<Select
defaultValue={'0'}
disabled
value={text}
>
{amountUnitOptions.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
),
},
{
title: '录入时间',
dataIndex: 'createDate',
key: 'createDate',
render: (value: Moment, record: any) => value?moment(value).format('yyyy-MM-DD'):null
},
{
title: '操作',
key: 'operation',
render: (_: any, record: any) => (
<Button type="link" danger onClick={() => handleDeleteRow(record.key)} disabled={readOnly}>
</Button>
),
},
];
const addRow = () => {
const newRow = {
key: (tableData.length + 1).toString(),
id: '',
contractPerformanceId: '',
invoiceAmountItem: 0,
invoiceUnit: '0',
payAmountItem: 0,
payUnit: '0',
createDate: createDate
};
const newTableData = [...tableData, newRow];
setTableData(newTableData);
};
const buildAmountItem: any = (id: string) => {
const amountItemList: any[] = [];
tableData.forEach((data) => {
delete data.key;
if (data.invoiceAmountItem != 0 || data.payAmountItem != 0) {
data.contractPerformanceId = id;
data.createDate = saveDateTimeFormatter(moment(data.createDate));
amountItemList.push(data);
}
})
return amountItemList;
}
const statusChange = (value: any) => {
status0 = (value==='2'?false:true)
status1 = (value==='1'?false:true)
phaseDescriptionText0 = (true);
}
const onFinish = (values: any) => {
if(opt === 'edit'){
values.contractId = contractInfo.id;
values.contractCode = contractInfo.contractCod;
values.contractName = contractInfo.contractName;
const newValues = {
...values,
actualAmount: values.actualAmount?Number(values.actualAmount):0,
changeDate: saveDateTimeFormatter(values.changeDate)
}
updatePerformance(newValues).then((res) => {
const performanceId = res.data.id;
if(performanceId){
updateAmountItem(buildAmountItem(performanceId)).then((amountRes: any) => {
if (amountRes.code === 200) {
message.success('保存成功').then(r =>
history.push({
pathname: '/contract',
state: {
contractInfo: res.data
}
})
);
} else {
message.error('保存失败').then();
}
})
}
})
}else {
//履约信息与合同信息 关联
values.contractId = contractInfo.id;
values.contractCode = contractInfo.contractCod;
values.contractName = contractInfo.contractName;
values.actualAmount = values.actualAmount?Number(values.actualAmount):0
values.changeDate = saveDateTimeFormatter(values.changeDate)
try {
//修改合同信息状态为 进行中
contractInfo.status = 1;
updateContract(contractInfo);//更新合同状态
createPerformance(values).then((res: any) => {
const performanceId = res.data.id;
if (performanceId) {
createAmountItem(buildAmountItem(performanceId)).then((amountRes) => {
if (amountRes.code === 200) {
message.success('保存成功').then(r =>
history.push({
pathname: '/contract',
state: {
contractInfo: res.data
}
})
);
} else {
message.error('保存失败').then();
}
})
}
});
} catch (error) {
message.error(error.message || '保存失败').then();
}
}
};
const onFinishFailed = (errorInfo: any) => {
message.error('表单提交失败,请检查输入');
};
return (
<div>
{/* 标题部分 */}
<div style={{
borderLeft: '4px solid #0066cc',
paddingLeft: 16,
marginBottom: 16,
position: 'relative',
left: 10
}}>
<span style={{fontSize: 16, fontWeight: 'bold'}}></span>
</div>
<Form
form={form}
labelCol={{span:6}}
wrapperCol={{span:18}}
initialValues={{}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
style={{marginLeft: 20}}
>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
<Form.Item name="id" hidden={true}></Form.Item>
{/* 合同履约状态 */}
<Form.Item
name="status"
label="合同履约状态"
rules={[{required: true, message: '请选择合同履约状态'}]}
style={{width: '50%', marginRight: 16}}
>
<Select placeholder="请选择" disabled={readOnly} onChange={statusChange}>
{performanceStatusOptions.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{/* 合同履约阶段 */}
<Form.Item
name="phase"
label="合同履约阶段"
hidden={!status0 || !status1}
rules={[{required: status0, message: '请选择合同履约阶段'}]}
style={{width: '50%'}}
>
<Select placeholder="请选择" disabled={readOnly} onChange={ (value) => {
phaseDescriptionText0 = (value==='5'? false: true)
}}>
{performanceStageOptions.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="phaseDescription"
label="合同履约阶段说明"
hidden={status0}
rules={[{required: !status0, message: '请选择合同履约阶段'}]}
style={{width: '50%'}}
>
<Select placeholder="请选择" disabled={readOnly} onChange={(value) => {
phaseDescriptionText0 = (value==='6'? false: true)
}}>
{performanceStageDescOptions.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
hidden={phaseDescriptionText0}
name="phaseDescriptionText"
label="合同履约阶段说明"
style={{width: '45%'}}
rules={[{ message: '请填写合同履约阶段说明'}]}
>
<Input
placeholder="请输入合同履约阶段说明"
readOnly={readOnly}
/>
</Form.Item>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{/* 合同金额变更 */}
<Form.Item
name="changeType"
label="合同金额变更"
rules={[{required: true, message: '请选择合同金额变更情况'}]}
style={{width: '50%', marginRight: 16}}
>
<Select placeholder="请选择" disabled={readOnly} onChange={ (value) => {
changeType0 = (value === '0'?false: true);
}}>
{yesOrNo.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{/* 合同实际金额 */}
<Form.Item
name="amount"
label="合同金额(CNY) / 元"
hidden={changeType0}
style={{width: '50%'}}
rules={[{ pattern: /^[0-9]+(\.[0-9]{1,2})?$/, message: '请输入正确的金额格式,最多保留两位小数' },
{ validator: (_, value) => value > 0 ? Promise.resolve() : Promise.reject('金额必须大于0') }]}
>
<Input
placeholder="请输入合同金额"
// value={actualAmount}
// onChange={handleActualAmountChange}
readOnly={readOnly}
/>
</Form.Item>
{/* 合同是否有扣款 */}
<Form.Item
name="changeDate"
hidden={changeType0}
label="合同变更日期"
style={{width: '45%'}}
>
<DatePicker placeholder="请选择时间" inputReadOnly={true} format={"yyyy-MM-DD"} disabled={readOnly} style={{width:'100%'}} />
</Form.Item>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{/* 合同实际金额 */}
<Form.Item
name="actualAmount"
label="合同实际金额 / 元"
style={{width: '50%'}}
rules={[{ required: true ,pattern: /^[0-9]+(\.[0-9]{1,2})?$/, message: '请输入正确的金额格式,最多保留两位小数' },
{ validator: (_, value) => value > 0 ? Promise.resolve() : Promise.reject('金额必须大于0') }]}
>
<Input
placeholder="请输入合同实际金额"
// value={actualAmount}
// onChange={handleActualAmountChange}
readOnly={readOnly}
/>
</Form.Item>
{/* 合同是否有扣款 */}
<Form.Item
name="deductionsStatus"
label="合同是否有扣款"
rules={[{required: true, message: '请选择合同是否有扣款'}]}
style={{width: '45%'}}
>
<Select placeholder="请选择" disabled={readOnly}>
{yesOrNo.map(({label, value}) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
<label style={{marginLeft: '55%',marginBottom:'10px',marginTop:'5px'}}></label>
</div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
{/* 累计发票金额(CNY) */}
<Form.Item
name="invoiceAmount"
label="累计发票金额(CNY) / 元"
rules={[{required: true, message: '请输入累计发票金额'}]}
style={{width: '50%'}}
>
<Input
value={invoiceAmount}
readOnly={totalReadOnly}
placeholder="请输入金额"
/>
</Form.Item>
{/* 累计支付金额(CNY) */}
<Form.Item
name="payAmount"
label="累计支付金额(CNY) / 元"
rules={[{required: true, message: '请输入累计支付金额'}]}
style={{width: '45%'}}
>
<Input
value={payAmount}
readOnly={totalReadOnly}
placeholder="请输入金额"
/>
</Form.Item>
</div>
{/* 表格部分 */}
<div style={{marginLeft: '50%', transform: 'translateX(-50%)', width: '100%'}}>
<Table columns={columns} dataSource={tableData}/>
<Button type="primary" onClick={addRow} hidden={readOnly || !status0}>
+
</Button>
</div>
{/* 按钮部分 */}
<div style={{display: 'flex', justifyContent: 'center', marginTop: 24}}>
<Form.Item label="">
<Button type="primary" htmlType="submit" hidden={readOnly}>
</Button>
</Form.Item>
<Form.Item label="">
<Button type="primary" hidden={!readOnly} onClick={ ()=> {
history.push({
pathname: '/contract'
})
}}>
</Button>
</Form.Item>
</div>
</Form>
</div>
);
};
export default Performance;

View File

@ -0,0 +1,340 @@
import React, {useState} from 'react';
import { Form, Input, Select, Button, Spin, message } from 'antd';
const { Option } = Select;
import { useLocation } from 'umi';
import {createContract, updateContract} from "./ContractService";
import {history} from "@@/core/history";
// 模拟行业选项
const industryOptions = [
{ label: '制造业', value:'0' },
{ label: '服务业', value:'service' },
{ label: '零售业', value:'retail' },
];
const StepOne: React.FC = () => {
const [form] = Form.useForm();
let readOnly = false;
const location = useLocation();
const contractType = location.state?.contractType;
const contractInfo = location.state?.contractInfo;
const opt = location.state?.opt;
const [loading, setLoading] = useState<boolean>(false);
if(opt){
form.setFieldsValue({
...contractInfo
});
if(opt === 'detail'){
readOnly = true;
}else if (opt ==='edit'){
readOnly = false;
}
}
// 表单提交处理函数
const onFinish = (values: any) => {
values.contractType = contractType;
if(opt === 'detail'){
history.push({
pathname: '/stepTwo',
state: {
contractInfo: contractInfo,
opt: opt
}
});
}else{
try {
if (values.id) {
setLoading(true)
updateContract(values).then((res: any) => {
if(res.code === 200){
// message.success("当前数据更新成功").then(() => {
history.push({
pathname: '/stepTwo',
state: {
contractInfo: res.data,
opt: opt
}
});
// }).then( () => {
setLoading(false)
// })
}else {
message.error('更新失败');
}
})
} else {
createContract(values).then( (res: any) => {
if(res.code === 200){
history.push({
pathname: '/stepTwo',
state: {
contractInfo: res.data
}
});
}else {
message.error('更新失败');
}
});
}
} catch (error) {
message.error(error.message || '操作失败');
}
}
};
// @ts-ignore
const validateCreditCode = (value: string) => {
// 基本格式校验
if (!value || value.length !== 18) {
return '统一社会信用代码必须为18位';
}
// 正则校验允许数字和大写字母不包括I、O、Z、S、V
const pattern = /^[0-9A-HJ-NPQRTUWXY]{18}$/;
if (!pattern.test(value)) {
return '统一社会信用代码格式不正确';
}
// 校验码计算
const weightFactor = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
const codePoint = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'J': 18,
'K': 19, 'L': 20, 'M': 21, 'N': 22, 'P': 23, 'Q': 24, 'R': 25, 'T': 26, 'U': 27,
'W': 28, 'X': 29, 'Y': 30
};
const checkCodeDict = {
0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9',
10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F', 16: 'G', 17: 'H', 18: 'J',
19: 'K', 20: 'L', 21: 'M', 22: 'N', 23: 'P', 24: 'Q', 25: 'R', 26: 'T', 27: 'U',
28: 'W', 29: 'X', 30: 'Y'
};
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += codePoint[value[i]] * weightFactor[i];
}
const mod = sum % 31;
const checkCode = checkCodeDict[mod];
if (checkCode !== value[17]) {
return '统一社会信用代码校验位不正确';
}
return null; // 校验通过
};
// 表单提交失败处理函数
const onFinishFailed = (errorInfo: any) => {
// console.log('Form submission failed:', errorInfo);
message.error('表单提交失败,请检查输入');
};
return (
<div style={{ padding: '10px' }}>
{/* 添加全局遮罩 */}
{loading && (
<Spin
tip="加载中..."
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 9999,
background: 'rgba(255, 255, 255, 0.8)',
}}
/>
)}
<Form
form={form}
labelCol={{span:4}}
wrapperCol={{span:20}}
initialValues={{
status: 0
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
{/* 项目基本信息部分 */}
<div style={{ borderLeft: '4px solid #0066cc', paddingLeft: 8, marginBottom: 16 }}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item hidden={true} name="id"></Form.Item>
<Form.Item
name="status"
label="合同状态"
hidden={true}
>
<Input value={0}/>
</Form.Item>
<Form.Item
name="projectName"
label="项目名称"
rules={[{ required: true, message: '请输入项目名称' }]}
style={{ width: '50%', marginRight: 16 }}
>
<Input placeholder="请输入项目名称" readOnly={readOnly}/>
</Form.Item>
<Form.Item name="projectSelect" label="" style={{ width: '80%' }}>
<Button type="primary" hidden={readOnly}></Button>
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item
name="biddingName"
label="标段名称"
rules={[{ required: true, message: '请输入标段名称' }]}
style={{ width: '50%', marginRight: 16 }}
>
<Input placeholder="请输入标段名称" readOnly={readOnly}/>
</Form.Item>
<Form.Item
name="biddingCode"
label="标段编号"
rules={[{ required: true, message: '请输入标段编号' }]}
style={{ width: '45%' }}
>
<Input placeholder="请输入标段编号" readOnly={readOnly} />
</Form.Item>
</div>
<Form.Item name="sectionSelect" label="" style={{ width: '20%' }}>
<Button type="primary" hidden={readOnly}></Button>
</Form.Item>
{/* 我方签约主体信息部分 */}
<div style={{ borderLeft: '4px solid #0066cc', paddingLeft: 8, marginBottom: 16, marginTop: 16 }}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item
name="purchaserName"
label="采购单位"
rules={[{ required: true, message: '请输入采购单位' }]}
style={{ width: '50%', marginRight: 16 }}
>
<Input placeholder="请输入采购单位" readOnly={readOnly} />
</Form.Item>
<Form.Item
name="purchaserCode"
label="统一社会信用代码"
rules={[{ required: true, message: '请输入统一社会信用代码' },
// ({ getFieldValue }) => ({
// validator(rule, value) {
// const errorMessage = validateCreditCode(value);
// if (errorMessage) {
// return Promise.reject(errorMessage);
// }
// return Promise.resolve();
// },
// })
]}
style={{ width: '50%', marginRight: 16 }}
>
<Input placeholder="请输入统一社会信用代码" readOnly={readOnly} />
</Form.Item>
<Form.Item
name="industryCode"
label="所属主要行业"
rules={[{ required: true, message: '请选择所属主要行业' }]}
style={{ width: '45%' }}
>
<Select placeholder="请选择" disabled={readOnly}>
{industryOptions.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</div>
{/* 对方签约主体信息部分 */}
<div style={{ borderLeft: '4px solid #0066cc', paddingLeft: 8, marginBottom: 16, marginTop: 16 }}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item
name="supplierName"
label="供应商公司名称"
rules={[{ required: true, message: '请输入供应商公司名称' }]}
style={{ width: '50%', marginRight: 16 }}
>
<Input placeholder="请输入供应商公司名称" readOnly={readOnly} />
</Form.Item>
<Form.Item
name="supplierCode"
label="统一社会信用代码"
rules={[
{ required: true, message: '请输入统一社会信用代码' },
// ({ getFieldValue }) => ({
// validator(rule, value) {
// const errorMessage = validateCreditCode(value);
// if (errorMessage) {
// return Promise.reject(errorMessage);
// }
// return Promise.resolve();
// },
// })
]}
style={{ width: '45%' }}
>
<Input placeholder="请输入统一社会信用代码" readOnly={readOnly} />
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item name="supplierSelect" label="" style={{ width: '20%' }}>
<Button type="primary" hidden={readOnly}></Button>
</Form.Item>
{/*<Form.Item*/}
{/* name="supplierName"*/}
{/* label="供应商公司名称"*/}
{/* rules={[{ required: true, message: '请输入供应商公司名称' }]}*/}
{/* style={{ width: '50%', marginRight: 16 }}*/}
{/*>*/}
{/* <Input placeholder="请输入供应商公司名称" readOnly={readOnly} />*/}
{/*</Form.Item>*/}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<Form.Item
name="signerName"
label="供应商签约人"
style={{ width: '50%', marginRight: 16 }}
rules={[{ min: 2, message: '姓名长度不能少于2个字符' },
{ max: 20, message: '姓名长度不能超过20个字符' },
{ pattern: /^[\u4e00-\u9fa5a-zA-Z\s·]{2,20}$/, message: '请输入正确的姓名' }]}
>
<Input placeholder="" readOnly={readOnly}/>
</Form.Item>
<Form.Item
name="signerContact"
label="供应商联系方式"
style={{ width: '50%' }}
rules={ [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }]}
>
<Input placeholder="" readOnly={readOnly} />
</Form.Item>
</div>
{/* 居中显示的下一步按钮 */}
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 24 }}>
<Form.Item>
<Button type="primary" htmlType="submit" style={{ width: 200 }} onClick={ () => {}}>
</Button>
</Form.Item>
</div>
</Form>
</div>
);
};
export default StepOne;

View File

@ -0,0 +1,458 @@
import React, {useState} from 'react';
import {Form, Input, Select, DatePicker, Button, message, Spin, Upload} from 'antd';
import {useLocation} from "umi";
import {updateContract} from "@/pages/Contract/ContractService";
import {history} from "@@/core/history";
import { saveDateTimeFormatter } from '@/utils/DateUtils';
import moment from "moment";
const { Option } = Select;
// 合同金额单位 元/万元
// const amountUnits = [
// { label: '元', value: '0' },
// { label: '万元', value:'1' }
// ];
// 合同主要标的类型选项
const categoryCodeOptions = [
{ label: '土地、建筑物及构筑物', value: '0' },
{ label: '通用设备', value:'1' },
{ label: '专用设备', value: '2' },
{ label: '文物和陈列品', value: '3' },
];
// 合同计价方式选项
const pricingMethodOptions = [
{ label: '总价合同', value: '0' },
{ label: '单价合同', value: '1' },
{ label: '成本加酬金合同', value: '2' },
{ label: '其他形式合同', value: '3' }
];
// 付款方式选项
const paymentMethodOptions = [
{ label: '一次性付款', value: '0' },
{ label: '分期付款', value: '1' },
{ label: '其他', value: '2' },
];
// 模拟履约担保选项
const guaranteeOptions = [
{ label: '无', value: '0'},
{ label: '履约担保金', value: '1' },
{ label: '银行履约保函', value: '2' },
{ label: '履约担保书', value: '3' }
];
const StepTwo: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
// const [payType0,setPayType0] = useState(true);
let payType0 = true;
let valuationType0 = true;
// const [valuationType0,setValuationType0] = useState(true);
const [form] = Form.useForm();
const location = useLocation();
const contractInfo = location.state?.contractInfo;
const opt = location.state?.opt;
const isShow = contractInfo? contractInfo.contractType=='0'? false: true: true;
let readOnly = false;
if(opt){
form.setFieldsValue({
...contractInfo,
endDatetime: contractInfo.endDatetime ? moment(contractInfo.endDatetime) : null,
startDatetime: contractInfo.startDatetime ? moment(contractInfo.startDatetime) : null,
signedDatetime: contractInfo.signedDatetime ? moment(contractInfo.signedDatetime) : null,
performancePlace: contractInfo.performancePlace ? contractInfo.performancePlace.split(",") : [],
mainType: contractInfo.mainType ? contractInfo.mainType.split(","): []
})
if(opt === 'detail'){
readOnly = true
}else if(opt ==='edit'){
readOnly = false
}
}
payType0 = (form.getFieldValue("payType") === '2' ? false: true);
valuationType0 = (form.getFieldValue("valuationType") === '3' ? false: true);
function assignMatchingProperties<T>(original: T, updates: Partial<T>): T {
const result: T = { ...original };
for (const key in updates) {
if (Object.prototype.hasOwnProperty.call(updates, key)) {
const typedKey = key as keyof T;
if (typedKey in original && updates[typedKey] !== undefined) {
result[typedKey] = updates[typedKey] as T[keyof T];
}
}
}
return result;
}
// 表单提交处理函数
const onFinish = (values: any) => {
let newContract = null;
if(opt){
newContract = values;
}else{
const updatedContract = assignMatchingProperties(contractInfo, values);
newContract = updatedContract;
}
newContract.performancePlace = newContract.performancePlace.join(',');
newContract.mainType = newContract.mainType.join(',');
newContract.signedDatetime = saveDateTimeFormatter(newContract.signedDatetime);
newContract.startDatetime = saveDateTimeFormatter(newContract.startDatetime);
newContract.endDatetime = saveDateTimeFormatter(newContract.endDatetime);
setLoading(true)
updateContract(newContract).then( (res: any) => {
if(res?.code === 200){
setLoading(false)
message.success('保存成功');
}else {
message.error('保存失败');
}
});
};
// 表单提交失败处理函数
const onFinishFailed = (errorInfo: any) => {
message.error('表单提交失败,请检查输入');
};
const goNegotiatePage = async () => {
if(opt === 'detail'){
history.push({
pathname: '/negotiate',
state: {
contractInfo: contractInfo,
opt: opt
}
});
}else{
const values = await form.validateFields();
const updatedContract = assignMatchingProperties(contractInfo, values);
updatedContract.performancePlace = updatedContract.performancePlace.join(',')
updatedContract.mainType = updatedContract.mainType.join(',');
updatedContract.signedDatetime = saveDateTimeFormatter(updatedContract.signedDatetime);
updatedContract.startDatetime = saveDateTimeFormatter(updatedContract.startDatetime);
updatedContract.endDatetime = saveDateTimeFormatter(updatedContract.endDatetime);
setLoading(true);
updateContract(updatedContract).then( (res: any) => {
if(res.code === 200){
// message.success("当前数据更新成功").then(() => {
history.push({
pathname: '/negotiate',
state: {
contractInfo: updatedContract,
opt: opt
}
});
// }).then( () => {
setLoading(false);
// })
}else {
message.error('保存失败');
}
});
}
}
const goPerformancePage = async () => {
if(opt ==='detail'){
history.push({
pathname: '/performance',
state: {
contractInfo: contractInfo,
opt: "detail"
}
});
}else{
const values = await form.validateFields();
const updatedContract = assignMatchingProperties(contractInfo, values);
updatedContract.performancePlace = updatedContract.performancePlace.join(',')
updatedContract.mainType = updatedContract.mainType.join(',');
updatedContract.signedDatetime = saveDateTimeFormatter(updatedContract.signedDatetime);
updatedContract.startDatetime = saveDateTimeFormatter(updatedContract.startDatetime);
updatedContract.endDatetime = saveDateTimeFormatter(updatedContract.endDatetime);
setLoading(true);
updateContract(updatedContract).then( (res: any) => {
if(res.code === 200){
message.success("当前数据更新成功").then(() => {
history.push({
pathname: '/performance',
state: {
contractInfo: updatedContract,
opt: opt
}
});
}).then( () => {
setLoading(false);
})
}else {
message.error('保存失败');
}
});
}
}
const payTypeOnChange = (value: any) => {
payType0 = (value === '2' ? false: true);
};
const onValuationTypeChange = (value: any) => {
valuationType0 = (value === '3' ? false: true);
};
return (
<div>
{/* 标题部分 */}
<div style={{
borderLeft: '4px solid #0066cc',
paddingLeft: 16,
marginBottom: 16,
position: 'relative',
left: 10
}}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
{/* 添加全局遮罩 */}
{loading && (
<Spin
tip="加载中..."
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 9999,
background: 'rgba(255, 255, 255, 0.8)',
}}
/>
)}
<Form
form={form}
labelCol={{span:5}}
wrapperCol={{span:19}}
initialValues={{}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
style={{ marginLeft: 20 }}
>
<Form.Item name="id" hidden={true}></Form.Item>
{/* 框架协议编号,独占一行 */}
<Form.Item
name="frameworkAgreementId"
label="框架协议编号"
style={{ width: '45%', marginRight: 16 }}
>
<Input placeholder="当合同为执行框架协议或执行统谈分签协议时,须填写对应的框架协议或统谈分签协议的编号。" readOnly={readOnly}/>
</Form.Item>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 合同编号和合同名称 */}
<Form.Item
name="contractCode"
label="合同编号"
rules={[{ required: true, message: '请输入合同编号' }]}
style={{ width: '45%', marginRight: 16 }}
>
<Input readOnly={readOnly}/>
</Form.Item>
<Form.Item
name="contractName"
label="合同名称"
rules={[{ required: true, message: '请输入合同名称' }]}
style={{ width: '45%' }}
>
<Input readOnly={readOnly}/>
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 合同主要标的类型和公司签订日期 */}
<Form.Item
name="mainType"
label="合同主要标的类型"
rules={[{ required: true, message: '请选择合同主要标的类型' }]}
style={{ width: '45%', marginRight: 16 }}
>
<Select placeholder="请选择" disabled={readOnly} mode="multiple">
{categoryCodeOptions.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="signedDatetime"
label="公司签订日期"
rules={[{ required: true, message: '请选择公司签订日期' }]}
style={{ width: '45%' }}
>
<DatePicker placeholder="请选择时间" inputReadOnly={true} format={"yyyy-MM-DD"} disabled={readOnly} style={{width:'100%'}} />
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 合同计价方式,独占一行 */}
<Form.Item
name="valuationType"
label="合同计价方式"
rules={[{ required: true, message: '请选择合同计价方式' }]}
style={{ width: '45%', marginRight: 16 }}
>
<Select placeholder="请选择" disabled={readOnly} onChange={onValuationTypeChange}>
{pricingMethodOptions.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="vnote"
label="备注"
style={{ width: '45%' }}
hidden={valuationType0}
rules={[{ required: !valuationType0, message: '请填写备注' }]}
>
<Input placeholder="请输入备注" readOnly={readOnly}/>
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 合同金额(CNY)和下拉选项 */}
<Form.Item
name="amount"
label="合同金额(CNY) / 元"
// labelCol={{span:6}}
// wrapperCol={{span:18}}
style={{ width: '45%'}}
rules={[{ pattern: /^[0-9]+(\.[0-9]{1,2})?$/, message: '请输入正确的金额格式,最多保留两位小数' },
{ validator: (_, value) => value > 0 ? Promise.resolve() : Promise.reject('金额必须大于0') }]}
>
<Input placeholder="" readOnly={readOnly}/>
</Form.Item>
{/*<Form.Item name="amountUnit" rules={[{ required: true, message: '请选合同金额单位' }]} initialValue={'0'} style={{ width: '5%',marginLeft:'20px'}}>*/}
{/* <Select placeholder="请选择" disabled={readOnly}>*/}
{/* {amountUnits.map(({ label, value }) => (*/}
{/* <Option key={value} value={value}>*/}
{/* {label}*/}
{/* </Option>*/}
{/* ))}*/}
{/* </Select>*/}
{/*</Form.Item>*/}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 付款方式和备注输入框 */}
<Form.Item
name="payType"
label="付款方式"
rules={[{ required: true, message: '请选择付款方式' }]}
style={{ width: '45%', marginRight: 16 }}
>
<Select placeholder="请选择" onChange={ (value) => payTypeOnChange(value)} disabled={readOnly}>
{paymentMethodOptions.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
name="note"
label="备注"
style={{ width: '45%' }}
hidden={payType0}
rules={[{ required: !payType0, message: '请填写备注' }]}
>
<Input placeholder="请输入备注" readOnly={readOnly}/>
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 合同约定开始日期和合同约定结束日期 */}
<Form.Item
name="startDatetime"
label="合同约定开始日期"
rules={[{ required: true, message: '请选择合同约定开始日期' }]}
style={{ width: '45%', marginRight: 16 }}
>
<DatePicker placeholder="请选择时间" format={"yyyy-MM-DD"} inputReadOnly={true} style={{width:'100%'}} onChange={ (date, dateString) =>{
}} disabled={readOnly}/>
</Form.Item>
<Form.Item
name="endDatetime"
label="合同约定结束日期"
rules={[{ required: true, message: '请选择合同约定结束日期' }]}
style={{ width: '45%' }}
>
<DatePicker placeholder="请选择时间" inputReadOnly={true} format={"yyyy-MM-DD"} disabled={readOnly} style={{width:'100%'}}/>
</Form.Item>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{/* 履约地点和履约担保 */}
<Form.Item
name="performancePlace"
label="履约地点"
rules={[{ required: true, message: '请选择履约地点' }]}
style={{ width: '45%', marginRight: 16 }}
>
<Select mode="multiple" placeholder="请选择" disabled={readOnly}>
<Option value="beijing"></Option>
<Option value="shanghai"></Option>
<Option value="shenzhen"></Option>
</Select>
</Form.Item>
<Form.Item
name="guaranteeType"
label="履约担保"
rules={[{ required: true, message: '请选择履约担保' }]}
style={{ width: '45%' }}
>
<Select placeholder="请选择" disabled={readOnly}>
{guaranteeOptions.map(({ label, value }) => (
<Option key={value} value={value}>
{label}
</Option>
))}
</Select>
</Form.Item>
</div>
<div hidden={isShow} style={{
borderLeft: '4px solid #0066cc',
paddingLeft: 16,
marginBottom: 16,
position: 'relative'
}}>
<span style={{ fontSize: 16, fontWeight: 'bold' }}></span>
</div>
<div hidden={isShow} style={{ display: 'flex',marginLeft:30}}>
<Upload
name="file"
// listType="simple"
onChange={(info) => {
console.log('Uploaded file info:', info);
message.success('文件上传成功');
}}
>
<label style={{left: 40, marginRight: 20}}>: </label><Button type="primary"></Button>
<p></p>
<label style={{color: 'red'}}>.rar .zip .doc .docx .pdf 100MB</label>
</Upload>
</div>
{/* 按钮部分 */}
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 24 }}>
<Button type="primary" htmlType="submit" hidden={readOnly}>
</Button>
<Button type="primary" style={{ marginLeft: 24 }} onClick={goNegotiatePage} hidden={!isShow}>
</Button>
<Button type="primary" style={{ marginLeft: 24 }} onClick={goPerformancePage} hidden={isShow}>
</Button>
</div>
</Form>
</div>
);
};
export default StepTwo;

View File

@ -0,0 +1,426 @@
import React, {useState, useEffect} from 'react';
import {Button, Form, Input, Select, Table, Modal, message, Popconfirm} from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import {
fetchMenuList,
updateMenu,
deleteMenu,
createMenu
} from "@/pages/Menu/MenuService";
const {Option} = Select;
// 模拟菜单数据结构
type MenuItem = {
id: number;
menuId: number;
key: number;
menuName: string;
parentId: number;
orderNum: number;
path: string;
component: string;
isFrame: number;
isCache: number;
menuType: 'M' | 'C' | 'F';
visible: string;
status: string;
perms: string;
icon: string;
createBy: string;
createTime: string;
updateBy: string;
updateTime: string;
remark: string;
children?: MenuItem[]; // 用于处理树结构
};
// 模拟后端返回数据
// @ts-ignore
const mockMenuData: MenuItem[] = [];
const MenuManagement: React.FC = () => {
const [form] = Form.useForm();
const [folder, setFolder] = useState(false);
const [btn, setBtn] = useState(false);
const [loading, setLoading] = useState(false);
const [menuList, setMenuList] = useState<MenuItem[]>([]);
const [visible, setVisible] = useState(false);
const [editingItem, setEditingItem] = useState<MenuItem | null>(null);
// const [status, setStatus] = useState('');
const [searchMenuName, setSearchMenuName] = useState('');
const [searchStatus, setSearchStatus] = useState('');
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
const onExpand = (expanded: any, record: any) => {
if (expanded) {
setExpandedRowKeys([record.id]);
} else {
setExpandedRowKeys([]);
}
};
const fetchMenu = async () => {
setLoading(true);
try {
const result = await fetchMenuList({}); // 传递搜索参数
setMenuList(result.data);
} catch (error) {
message.error('获取字典列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchMenu();
}, [searchMenuName, searchStatus]);
// 处理编辑
const handleEdit = (item: MenuItem) => {
debugger;
setEditingItem(item);
form.setFieldsValue({
id: item.menuId,
parentId: item.parentId,
// parentName: item.parentName,
menuName: item.menuName,
orderNum: item.orderNum,
path: item.path,
component: item.component,
isFrame: item.isFrame,
isCache: item.isCache,
menuType: item.menuType,
visible: item.visible,
status: item.status,
perms: item.perms,
icon: item.icon,
remark: item.remark,
});
setVisible(true);
};
const handleDelete = async (menuItem: MenuItem) => {
const id = menuItem.menuId;
try {
await deleteMenu(id); // 使用 `await` 等待删除接口完成
message.success('删除成功');
await fetchMenu(); // 调用已有的 `fetchMenu` 刷新数据
} catch (error) {
message.error('删除失败');
}
};
// 处理新增子菜单
const handleAddSub = (parentItem: MenuItem) => {
setEditingItem({
...parentItem,
menuId: -1, // 临时标识新增项
});
form.setFieldsValue({
id: null,
menuName: '',
orderNum: 0,
path: '',
component: '',
isFrame: 1,
isCache: 0,
menuType: 'C',
visible: '0',
status: '0',
perms: '',
icon: '',
remark: '',
parentId: parentItem.menuId
});
setVisible(true);
};
// 表格列配置
// @ts-ignore
const columns: Table.ColumnType<MenuItem>[] = [
{
title: '菜单名称',
dataIndex: 'menuName',
key: 'menuName',
},
{
title: '排序',
dataIndex: 'orderNum',
key: 'orderNum',
},
{
title: '权限标识',
dataIndex: 'perms',
key: 'perms',
},
{
title: '组件路径',
dataIndex: 'component',
key: 'component',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (text: string) => (text === '0' ? '正常' : '停用'),
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
},
{
title: '操作',
key: 'action',
render: (_: any, record: any) => (
<div>
<Button type="link" onClick={() => handleEdit(record)}></Button>
<Button type="link" onClick={() => handleAddSub(record)}></Button>
<Popconfirm
title="确定要删除吗?"
onConfirm={() => handleDelete(record)}
okText="确定"
cancelText="取消"
>
<Button icon={<DeleteOutlined />} danger>
</Button>
</Popconfirm>
</div>
),
},
];
// 显示模态框
const handleAdd = () => {
setEditingItem(null);
form.resetFields();
setVisible(true);
};
// 模态框确定按钮处理
const handleOk = async () => {
const values = await form.validateFields();
debugger;
if (editingItem) {
if (editingItem.menuId === -1) {
// 新增操作
const newItem: MenuItem = {
...values
};
await createMenu(newItem);
message.success('创建成功');
fetchMenu();
} else {
// 编辑操作
const newItem: MenuItem = {
...values
};
await updateMenu(newItem);
message.success('修改成功');
fetchMenu();
}
} else {
await createMenu(values);
message.success('创建成功');
fetchMenu();
}
setVisible(false);
};
// 模态框取消按钮处理
const handleCancel = () => {
setVisible(false);
form.resetFields();
};
// 搜索功能处理
const handleSearch = () => {
const filteredData = menuList.filter(item => {
const nameMatch = item.menuName.includes(searchMenuName);
const statusMatch = searchStatus === '' || item.status === searchStatus;
return nameMatch && statusMatch;
});
setMenuList(filteredData);
};
// 重置搜索功能处理
const handleReset = () => {
setSearchMenuName('');
setSearchStatus('');
setMenuList(mockMenuData);
};
const handleMenuTypeChange = (value: string) => {
if (value == 'M') {
setFolder(true)
setBtn(false)
} else if (value == 'F') {
setBtn(true)
setFolder(false)
} else {
setFolder(false)
setBtn(false)
}
};
// @ts-ignore
return (
<div style={{padding: '16px'}}>
<Form form={form} layout="inline" style={{marginBottom: '16px'}}>
<Form.Item
name="menuName"
label="菜单名称"
>
<Input
placeholder="请输入菜单名称"
onChange={(e) => setSearchMenuName(e.target.value)}
/>
</Form.Item>
<Form.Item
name="status"
label="状态"
>
<Select
placeholder="菜单状态"
onChange={(value: any) => setSearchStatus(value)}
>
<Option value=""></Option>
<Option value="0"></Option>
<Option value="1"></Option>
</Select>
</Form.Item>
<Button type="primary" onClick={handleSearch}></Button>
<Button type="link" onClick={handleReset}></Button>
</Form>
<Button
type="primary"
style={{marginBottom: '8px'}}
onClick={handleAdd}
></Button>
<Table
loading={loading}
rowKey={record => record.id}
dataSource={menuList}
columns={columns}
expandable={{
expandedRowKeys,
onExpand,
}}>
</Table>
<Modal
title={editingItem && editingItem.id === -1 ? '新增菜单' : '修改菜单'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
>
<Form form={form}>
<Form.Item
name="id"
hidden={true}
>
</Form.Item>
<Form.Item name="parentId" hidden={true}></Form.Item>
<Form.Item
name="menuType"
label="菜单类型"
rules={[{required: true, message: '请选择菜单类型'}]}
>
<Select placeholder="请选择" onChange={handleMenuTypeChange}>
<Option value="M"></Option>
<Option value="C"></Option>
<Option value="F"></Option>
</Select>
</Form.Item>
<Form.Item
name="menuName"
label="菜单名称"
rules={[{required: true, message: '请输入菜单名称'}]}
>
<Input placeholder="请输入菜单名称"/>
</Form.Item>
<Form.Item
name="orderNum"
label="排序"
rules={[{required: true, message: '请输入排序'}]}
>
<Input placeholder="请输入排序"/>
</Form.Item>
<Form.Item
name="isFrame"
label="是否为外链"
hidden={btn}
rules={[{type: 'number', message: '请输入正确的数值'}]}
>
<Select placeholder="请选择" defaultValue={1}>
<Option value={0}></Option>
<Option value={1}></Option>
</Select>
</Form.Item>
<Form.Item
name="path"
label="路由地址"
hidden={btn}
>
<Input placeholder="请输入路由地址"/>
</Form.Item>
<Form.Item
name="query"
label="路由参数"
hidden={folder || btn}
>
<Input placeholder="请输入路由参数"/>
</Form.Item>
<Form.Item
name="component"
label="组件路径"
hidden={folder || btn}
>
<Input placeholder="请输入组件路径"/>
</Form.Item>
<Form.Item
name="perms"
label="权限标识"
hidden={folder && !btn}
>
<Input placeholder="请输入权限标识"/>
</Form.Item>
<Form.Item
name="isCache"
label="是否缓存"
hidden={folder || btn}
rules={[{type: 'number', message: '请输入正确的数值'}]}
>
<Select placeholder="请选择" defaultValue={0}>
<Option value={0}></Option>
<Option value={1}></Option>
</Select>
</Form.Item>
<Form.Item
name="visible"
label="显示状态(显示/隐藏)"
hidden={btn}
rules={[{required: true, message: '请选择显示状态'}]}
>
<Select placeholder="请选择" defaultValue="0">
<Option value="0"></Option>
<Option value="1"></Option>
</Select>
</Form.Item>
<Form.Item
name="status"
label="菜单状态(正常/停用)"
>
<Select placeholder="请选择" defaultValue="0">
<Option value="0"></Option>
<Option value="1"></Option>
</Select>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default MenuManagement;

View File

@ -0,0 +1,31 @@
import request from "@/utils/request";
const prefix = '/api/sys-manager-ebtp-project/';
export async function fetchMenuList(params: any) {
return request(prefix + 'v1/menu/treeselect', {
params: params,
method: 'GET'
});
}
export async function createMenu(params: any) {
return request(prefix + 'v1/menu', {
data: params,
method: 'POST'
});
}
export async function updateMenu(params: any) {
return request(prefix + 'v1/menu', {
data: params,
method: 'PUT'
});
}
export async function deleteMenu(param: any) {
return request(prefix + 'v1/menu/delete/' + param, {
method: 'DELETE',
});
}