|
@@ -1,234 +1,314 @@
|
|
|
-import React, { useState } from 'react';
|
|
|
|
|
-import { Table, Button, Space, Input, Modal, Form, message, Select, DatePicker } from 'antd';
|
|
|
|
|
-import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
|
|
|
|
|
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
-import { hetongRenewClient, hetongClient } from '@/client/api';
|
|
|
|
|
-import type { InferResponseType } from 'hono/client';
|
|
|
|
|
-import dayjs from 'dayjs';
|
|
|
|
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
|
|
+import { Table, Button, Space, Tag, Input, DatePicker, Select, Form, message, Modal, Typography, Divider, Card } from 'antd';
|
|
|
|
|
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ReloadOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
|
|
|
|
|
+import type { TableProps, FormProps } from 'antd';
|
|
|
|
|
+import { useRequest } from 'ahooks';
|
|
|
|
|
+import dayjs, { Dayjs } from 'dayjs';
|
|
|
|
|
+import { App } from 'antd';
|
|
|
|
|
+import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
|
|
|
|
|
-// 定义类型
|
|
|
|
|
-type HetongRenewItem = InferResponseType<typeof hetongRenewClient.$get, 200>['data'][0];
|
|
|
|
|
-type HetongRenewListResponse = InferResponseType<typeof hetongRenewClient.$get, 200>;
|
|
|
|
|
-type HetongItem = InferResponseType<typeof hetongClient.$get, 200>['data'][0];
|
|
|
|
|
|
|
+// API客户端导入
|
|
|
|
|
+import { contractRenewClient } from '@/client/api';
|
|
|
|
|
+import { contractClient } from '@/client/api';
|
|
|
|
|
+
|
|
|
|
|
+// 类型定义
|
|
|
|
|
+type ContractRenewItem = InferResponseType<typeof contractRenewClient[':id']['$get'], 200>;
|
|
|
|
|
+type ContractRenewListResponse = InferResponseType<typeof contractRenewClient.$get, 200>;
|
|
|
|
|
+type CreateContractRenewRequest = InferRequestType<typeof contractRenewClient.$post>['json'];
|
|
|
|
|
+type UpdateContractRenewRequest = InferRequestType<typeof contractRenewClient[':id']['$put']>['json'];
|
|
|
|
|
+type ContractItem = InferResponseType<typeof contractClient[':id']['$get'], 200>;
|
|
|
|
|
+type ContractListResponse = InferResponseType<typeof contractClient.$get, 200>;
|
|
|
|
|
+
|
|
|
|
|
+// 续签状态枚举
|
|
|
|
|
+const RenewStatusEnum = {
|
|
|
|
|
+ 0: { label: '草稿', color: 'gold' },
|
|
|
|
|
+ 1: { label: '待审核', color: 'blue' },
|
|
|
|
|
+ 2: { label: '已审核', color: 'green' },
|
|
|
|
|
+ 3: { label: '已拒绝', color: 'red' },
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const { Title } = Typography;
|
|
|
|
|
+const { Option } = Select;
|
|
|
|
|
+const { confirm } = Modal;
|
|
|
|
|
+const { RangePicker } = DatePicker;
|
|
|
|
|
|
|
|
const ContractRenews: React.FC = () => {
|
|
const ContractRenews: React.FC = () => {
|
|
|
|
|
+ const { message: antMessage } = App.useApp();
|
|
|
const [form] = Form.useForm();
|
|
const [form] = Form.useForm();
|
|
|
- const [modalVisible, setModalVisible] = useState(false);
|
|
|
|
|
- const [editingKey, setEditingKey] = useState<string | null>(null);
|
|
|
|
|
- const [searchText, setSearchText] = useState('');
|
|
|
|
|
- const [contracts, setContracts] = useState<HetongItem[]>([]);
|
|
|
|
|
- const queryClient = useQueryClient();
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const [searchForm] = Form.useForm();
|
|
|
|
|
+ const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
|
|
|
|
+ const [currentRecord, setCurrentRecord] = useState<ContractRenewItem | null>(null);
|
|
|
|
|
+ const [contracts, setContracts] = useState<ContractItem[]>([]);
|
|
|
|
|
+ const [pageSize, setPageSize] = useState<number>(10);
|
|
|
|
|
+ const [currentPage, setCurrentPage] = useState<number>(1);
|
|
|
|
|
+ const [total, setTotal] = useState<number>(0);
|
|
|
|
|
+
|
|
|
// 获取合同列表
|
|
// 获取合同列表
|
|
|
- const { data: contractsData } = useQuery(
|
|
|
|
|
- ['contractsForRenew'],
|
|
|
|
|
- async () => {
|
|
|
|
|
- const response = await hetongClient.$get({ query: { page: 1, pageSize: 1000 } });
|
|
|
|
|
- if (response.status !== 200) throw new Error('Failed to fetch contracts');
|
|
|
|
|
- return response.json() as Promise<InferResponseType<typeof hetongClient.$get, 200>>;
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- onSuccess: (result) => {
|
|
|
|
|
- setContracts(result.data);
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ const { run: fetchContracts } = useRequest(async () => {
|
|
|
|
|
+ const response = await contractClient.$get({
|
|
|
|
|
+ query: { page: 1, pageSize: 1000 }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('获取合同列表失败');
|
|
|
}
|
|
}
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 获取合同续签列表数据
|
|
|
|
|
- const fetchContractRenews = async ({ page, pageSize }: { page: number; pageSize: number }): Promise<HetongRenewListResponse> => {
|
|
|
|
|
- const response = await hetongRenewClient.$get({ query: { page, pageSize, keyword: searchText } });
|
|
|
|
|
- if (response.status !== 200) throw new Error('Failed to fetch contract renews');
|
|
|
|
|
- return response.json() as Promise<HetongRenewListResponse>;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const { data, isLoading: loading, refetch } = useQuery(
|
|
|
|
|
- ['contractRenews', pagination.current, pagination.pageSize, searchText],
|
|
|
|
|
- () => fetchContractRenews({ page: pagination.current, pageSize: pagination.pageSize }),
|
|
|
|
|
- {
|
|
|
|
|
- onSuccess: (result) => {
|
|
|
|
|
- setDataSource(result.data);
|
|
|
|
|
- setPagination({
|
|
|
|
|
- ...pagination,
|
|
|
|
|
- total: result.pagination.total,
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ return data.data;
|
|
|
|
|
+ }, {
|
|
|
|
|
+ onSuccess: (data) => {
|
|
|
|
|
+ setContracts(data);
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ antMessage.error(`获取合同列表失败: ${error.message}`);
|
|
|
|
|
+ },
|
|
|
|
|
+ manual: false
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 获取续签列表
|
|
|
|
|
+ const { loading, run: fetchRenews } = useRequest(async (params: any) => {
|
|
|
|
|
+ const response = await contractRenewClient.$get({
|
|
|
|
|
+ query: params
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('获取续签记录失败');
|
|
|
}
|
|
}
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const [dataSource, setDataSource] = useState<HetongRenewItem[]>([]);
|
|
|
|
|
- const [pagination, setPagination] = useState({
|
|
|
|
|
- current: 1,
|
|
|
|
|
- pageSize: 10,
|
|
|
|
|
- total: 0,
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return response.json();
|
|
|
|
|
+ }, {
|
|
|
|
|
+ onSuccess: (data: ContractRenewListResponse) => {
|
|
|
|
|
+ setTotal(data.pagination.total);
|
|
|
|
|
+ return data;
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error) => {
|
|
|
|
|
+ antMessage.error(`获取续签记录失败: ${error.message}`);
|
|
|
|
|
+ },
|
|
|
|
|
+ manual: true
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- // 搜索
|
|
|
|
|
- const handleSearch = () => {
|
|
|
|
|
- setPagination({ ...pagination, current: 1 });
|
|
|
|
|
- refetch();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 搜索功能
|
|
|
|
|
+ const handleSearch = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const values = await searchForm.validateFields();
|
|
|
|
|
+ const params: any = {
|
|
|
|
|
+ page: currentPage,
|
|
|
|
|
+ pageSize,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理搜索参数
|
|
|
|
|
+ if (values.contractId) {
|
|
|
|
|
+ params.contractId = values.contractId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (values.status !== undefined && values.status !== null) {
|
|
|
|
|
+ params.status = values.status;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (values.dateRange && values.dateRange.length > 0) {
|
|
|
|
|
+ params.startDate = values.dateRange[0].format('YYYY-MM-DD');
|
|
|
|
|
+ params.endDate = values.dateRange[1].format('YYYY-MM-DD');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fetchRenews(params);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ antMessage.error('搜索参数验证失败');
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
- // 分页变化
|
|
|
|
|
- const handleTableChange = (pagination: any) => {
|
|
|
|
|
- setPagination(pagination);
|
|
|
|
|
- refetch();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 重置搜索
|
|
|
|
|
+ const handleReset = () => {
|
|
|
|
|
+ searchForm.resetFields();
|
|
|
|
|
+ fetchRenews({ page: 1, pageSize });
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
- // 显示添加/编辑弹窗
|
|
|
|
|
- const showModal = (record?: HetongRenewItem) => {
|
|
|
|
|
- setModalVisible(true);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 分页变化处理
|
|
|
|
|
+ const handleTableChange: TableProps<ContractRenewItem>['onChange'] = (pagination) => {
|
|
|
|
|
+ setCurrentPage(pagination.current || 1);
|
|
|
|
|
+ setPageSize(pagination.pageSize || 10);
|
|
|
|
|
+
|
|
|
|
|
+ fetchRenews({
|
|
|
|
|
+ page: pagination.current || 1,
|
|
|
|
|
+ pageSize: pagination.pageSize || 10,
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 打开新增/编辑模态框
|
|
|
|
|
+ const showModal = (record?: ContractRenewItem) => {
|
|
|
|
|
+ setCurrentRecord(record || null);
|
|
|
|
|
+
|
|
|
if (record) {
|
|
if (record) {
|
|
|
- setEditingKey(record.id);
|
|
|
|
|
|
|
+ // 编辑模式 - 填充表单
|
|
|
form.setFieldsValue({
|
|
form.setFieldsValue({
|
|
|
- id: record.id,
|
|
|
|
|
contractId: record.contractId,
|
|
contractId: record.contractId,
|
|
|
- amount: record.amount,
|
|
|
|
|
- revenue: record.revenue,
|
|
|
|
|
- endDate: record.endDate ? dayjs(record.endDate) : null,
|
|
|
|
|
- state: record.state,
|
|
|
|
|
- content: record.content,
|
|
|
|
|
- auditStatus: record.auditStatus,
|
|
|
|
|
- auditTime: record.auditTime ? dayjs(record.auditTime) : null,
|
|
|
|
|
- auditReasons: record.auditReasons,
|
|
|
|
|
- userId: record.userId,
|
|
|
|
|
- createdTime: record.createdTime ? dayjs(record.createdTime) : null,
|
|
|
|
|
|
|
+ renewNo: record.renewNo,
|
|
|
|
|
+ renewStartDate: record.renewStartDate ? dayjs(record.renewStartDate) : null,
|
|
|
|
|
+ renewEndDate: record.renewEndDate ? dayjs(record.renewEndDate) : null,
|
|
|
|
|
+ renewAmount: record.renewAmount,
|
|
|
|
|
+ status: record.status,
|
|
|
|
|
+ remark: record.remark,
|
|
|
});
|
|
});
|
|
|
} else {
|
|
} else {
|
|
|
- setEditingKey(null);
|
|
|
|
|
|
|
+ // 新增模式 - 重置表单
|
|
|
form.resetFields();
|
|
form.resetFields();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ setIsModalVisible(true);
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
- // 关闭弹窗
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭模态框
|
|
|
const handleCancel = () => {
|
|
const handleCancel = () => {
|
|
|
- setModalVisible(false);
|
|
|
|
|
|
|
+ setIsModalVisible(false);
|
|
|
form.resetFields();
|
|
form.resetFields();
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
- // 创建合同续签记录
|
|
|
|
|
- const createContractRenew = useMutation(
|
|
|
|
|
- async (data: any) => {
|
|
|
|
|
- const response = await hetongRenewClient.$post({ json: data });
|
|
|
|
|
- if (!response.ok) throw new Error('Failed to create contract renew');
|
|
|
|
|
- return response.json();
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- onSuccess: () => {
|
|
|
|
|
- message.success('合同续签记录创建成功');
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ['contractRenews'] });
|
|
|
|
|
- setModalVisible(false);
|
|
|
|
|
- },
|
|
|
|
|
- onError: () => {
|
|
|
|
|
- message.error('操作失败,请重试');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 更新合同续签记录
|
|
|
|
|
- const updateContractRenew = useMutation(
|
|
|
|
|
- async ({ id, data }: { id: string; data: any }) => {
|
|
|
|
|
- const response = await hetongRenewClient[':id'].$put({ param: { id: parseInt(id, 10) }, json: data });
|
|
|
|
|
- if (!response.ok) throw new Error('Failed to update contract renew');
|
|
|
|
|
- return response.json();
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- onSuccess: () => {
|
|
|
|
|
- message.success('合同续签记录更新成功');
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ['contractRenews'] });
|
|
|
|
|
- setModalVisible(false);
|
|
|
|
|
- },
|
|
|
|
|
- onError: () => {
|
|
|
|
|
- message.error('操作失败,请重试');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 删除合同续签记录
|
|
|
|
|
- const deleteContractRenew = useMutation(
|
|
|
|
|
- async (id: string) => {
|
|
|
|
|
- const response = await hetongRenewClient[':id'].$delete({ param: { id: parseInt(id, 10) } });
|
|
|
|
|
- if (!response.ok) throw new Error('Failed to delete contract renew');
|
|
|
|
|
- return response.json();
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- onSuccess: () => {
|
|
|
|
|
- message.success('合同续签记录删除成功');
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ['contractRenews'] });
|
|
|
|
|
- },
|
|
|
|
|
- onError: () => {
|
|
|
|
|
- message.error('删除失败,请重试');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 提交表单
|
|
|
|
|
- const handleSubmit = async () => {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 保存续签记录
|
|
|
|
|
+ const handleSubmit: FormProps['onFinish'] = async (values) => {
|
|
|
try {
|
|
try {
|
|
|
- const values = await form.validateFields();
|
|
|
|
|
-
|
|
|
|
|
- // 处理日期字段
|
|
|
|
|
- if (values.endDate) values.endDate = values.endDate.format('YYYY-MM-DD');
|
|
|
|
|
- if (values.auditTime) values.auditTime = values.auditTime.format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
|
- if (values.createdTime) values.createdTime = values.createdTime.format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
|
-
|
|
|
|
|
- if (editingKey) {
|
|
|
|
|
|
|
+ // 格式化日期
|
|
|
|
|
+ const formattedValues: CreateContractRenewRequest | UpdateContractRenewRequest = {
|
|
|
|
|
+ ...values,
|
|
|
|
|
+ renewStartDate: values.renewStartDate ? values.renewStartDate.format('YYYY-MM-DD') : undefined,
|
|
|
|
|
+ renewEndDate: values.renewEndDate ? values.renewEndDate.format('YYYY-MM-DD') : undefined,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (currentRecord) {
|
|
|
// 更新操作
|
|
// 更新操作
|
|
|
- await updateContractRenew.mutateAsync({ id: editingKey, data: values });
|
|
|
|
|
|
|
+ const response = await contractRenewClient[':id']['$put']({
|
|
|
|
|
+ param: { id: currentRecord.id },
|
|
|
|
|
+ json: formattedValues as UpdateContractRenewRequest
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('更新续签记录失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ antMessage.success('续签记录更新成功');
|
|
|
} else {
|
|
} else {
|
|
|
// 创建操作
|
|
// 创建操作
|
|
|
- await createContractRenew.mutateAsync(values);
|
|
|
|
|
|
|
+ const response = await contractRenewClient.$post({
|
|
|
|
|
+ json: formattedValues as CreateContractRenewRequest
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('创建续签记录失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ antMessage.success('续签记录创建成功');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ setIsModalVisible(false);
|
|
|
|
|
+ fetchRenews({ page: currentPage, pageSize }); // 重新加载数据
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- message.error('操作失败,请重试');
|
|
|
|
|
|
|
+ antMessage.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 删除续签记录
|
|
|
|
|
+ const handleDelete = (id: number) => {
|
|
|
|
|
+ confirm({
|
|
|
|
|
+ title: '确认删除',
|
|
|
|
|
+ content: '您确定要删除这条续签记录吗?此操作不可撤销。',
|
|
|
|
|
+ okText: '确认',
|
|
|
|
|
+ cancelText: '取消',
|
|
|
|
|
+ onOk: async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await contractRenewClient[':id']['$delete']({
|
|
|
|
|
+ param: { id }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('删除失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ antMessage.success('续签记录删除成功');
|
|
|
|
|
+ fetchRenews({ page: currentPage, pageSize }); // 重新加载数据
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ antMessage.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化加载数据
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ fetchContracts();
|
|
|
|
|
+ fetchRenews({ page: 1, pageSize });
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
// 表格列定义
|
|
// 表格列定义
|
|
|
const columns = [
|
|
const columns = [
|
|
|
{
|
|
{
|
|
|
- title: '续签ID',
|
|
|
|
|
- dataIndex: 'id',
|
|
|
|
|
- key: 'id',
|
|
|
|
|
|
|
+ title: '续签编号',
|
|
|
|
|
+ dataIndex: 'renewNo',
|
|
|
|
|
+ key: 'renewNo',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ sorter: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- title: '合同ID',
|
|
|
|
|
- dataIndex: 'contractId',
|
|
|
|
|
- key: 'contractId',
|
|
|
|
|
- render: (contractId: string) => {
|
|
|
|
|
- const contract = contracts.find(c => c.id === contractId);
|
|
|
|
|
- return contract ? `${contract.id} (${contract.contractNumber})` : contractId;
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ title: '原合同编号',
|
|
|
|
|
+ dataIndex: ['contract', 'contractNo'],
|
|
|
|
|
+ key: 'contractNo',
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ sorter: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- title: '续签金额',
|
|
|
|
|
- dataIndex: 'amount',
|
|
|
|
|
- key: 'amount',
|
|
|
|
|
|
|
+ title: '客户名称',
|
|
|
|
|
+ dataIndex: ['contract', 'customer', 'name'],
|
|
|
|
|
+ key: 'customerName',
|
|
|
|
|
+ width: 180,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '续签期限',
|
|
|
|
|
+ key: 'period',
|
|
|
|
|
+ width: 200,
|
|
|
|
|
+ render: (_, record: ContractRenewItem) => (
|
|
|
|
|
+ <>
|
|
|
|
|
+ {record.renewStartDate ? dayjs(record.renewStartDate).format('YYYY-MM-DD') : '-'}
|
|
|
|
|
+ {' 至 '}
|
|
|
|
|
+ {record.renewEndDate ? dayjs(record.renewEndDate).format('YYYY-MM-DD') : '-'}
|
|
|
|
|
+ </>
|
|
|
|
|
+ ),
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- title: '续签状态',
|
|
|
|
|
- dataIndex: 'state',
|
|
|
|
|
- key: 'state',
|
|
|
|
|
|
|
+ title: '续签金额',
|
|
|
|
|
+ dataIndex: 'renewAmount',
|
|
|
|
|
+ key: 'renewAmount',
|
|
|
|
|
+ width: 120,
|
|
|
|
|
+ render: (amount: number) => `¥ ${amount.toFixed(2)}`,
|
|
|
|
|
+ sorter: (a: ContractRenewItem, b: ContractRenewItem) => a.renewAmount - b.renewAmount,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- title: '结束日期',
|
|
|
|
|
- dataIndex: 'endDate',
|
|
|
|
|
- key: 'endDate',
|
|
|
|
|
- render: (date: string) => date ? new Date(date).toLocaleDateString() : '-',
|
|
|
|
|
|
|
+ title: '状态',
|
|
|
|
|
+ dataIndex: 'status',
|
|
|
|
|
+ key: 'status',
|
|
|
|
|
+ width: 100,
|
|
|
|
|
+ filters: Object.entries(RenewStatusEnum).map(([value, { label }]) => ({
|
|
|
|
|
+ text: label,
|
|
|
|
|
+ value,
|
|
|
|
|
+ })),
|
|
|
|
|
+ render: (status: number) => {
|
|
|
|
|
+ const statusInfo = RenewStatusEnum[status] || { label: '未知', color: 'default' };
|
|
|
|
|
+ return <Tag color={statusInfo.color}>{statusInfo.label}</Tag>;
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- title: '审批状态',
|
|
|
|
|
- dataIndex: 'auditStatus',
|
|
|
|
|
- key: 'auditStatus',
|
|
|
|
|
|
|
+ title: '创建时间',
|
|
|
|
|
+ dataIndex: 'createdAt',
|
|
|
|
|
+ key: 'createdAt',
|
|
|
|
|
+ width: 160,
|
|
|
|
|
+ render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm'),
|
|
|
|
|
+ sorter: true,
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
title: '操作',
|
|
title: '操作',
|
|
|
key: 'action',
|
|
key: 'action',
|
|
|
- render: (_: any, record: HetongRenewItem) => (
|
|
|
|
|
- <Space size="middle">
|
|
|
|
|
|
|
+ width: 150,
|
|
|
|
|
+ render: (_: any, record: ContractRenewItem) => (
|
|
|
|
|
+ <Space size="small">
|
|
|
<Button
|
|
<Button
|
|
|
type="text"
|
|
type="text"
|
|
|
icon={<EditOutlined />}
|
|
icon={<EditOutlined />}
|
|
|
onClick={() => showModal(record)}
|
|
onClick={() => showModal(record)}
|
|
|
|
|
+ size="small"
|
|
|
>
|
|
>
|
|
|
编辑
|
|
编辑
|
|
|
</Button>
|
|
</Button>
|
|
@@ -236,7 +316,8 @@ const ContractRenews: React.FC = () => {
|
|
|
type="text"
|
|
type="text"
|
|
|
danger
|
|
danger
|
|
|
icon={<DeleteOutlined />}
|
|
icon={<DeleteOutlined />}
|
|
|
- onClick={() => deleteContractRenew.mutate(record.id)}
|
|
|
|
|
|
|
+ onClick={() => handleDelete(record.id)}
|
|
|
|
|
+ size="small"
|
|
|
>
|
|
>
|
|
|
删除
|
|
删除
|
|
|
</Button>
|
|
</Button>
|
|
@@ -244,183 +325,199 @@ const ContractRenews: React.FC = () => {
|
|
|
),
|
|
),
|
|
|
},
|
|
},
|
|
|
];
|
|
];
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
- <div className="p-4">
|
|
|
|
|
- <div className="flex justify-between items-center mb-4">
|
|
|
|
|
- <h2 className="text-xl font-bold">合同续签管理</h2>
|
|
|
|
|
|
|
+ <div className="p-6">
|
|
|
|
|
+ <div className="flex justify-between items-center mb-6">
|
|
|
|
|
+ <Title level={2}>合同续签管理</Title>
|
|
|
<Button
|
|
<Button
|
|
|
type="primary"
|
|
type="primary"
|
|
|
icon={<PlusOutlined />}
|
|
icon={<PlusOutlined />}
|
|
|
onClick={() => showModal()}
|
|
onClick={() => showModal()}
|
|
|
>
|
|
>
|
|
|
- 添加续签记录
|
|
|
|
|
|
|
+ 新增续签记录
|
|
|
</Button>
|
|
</Button>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <div className="mb-4">
|
|
|
|
|
- <Input
|
|
|
|
|
- placeholder="搜索合同ID或续签状态"
|
|
|
|
|
- prefix={<SearchOutlined />}
|
|
|
|
|
- value={searchText}
|
|
|
|
|
- onChange={(e) => setSearchText(e.target.value)}
|
|
|
|
|
- onPressEnter={handleSearch}
|
|
|
|
|
- style={{ width: 300 }}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <Card className="mb-6">
|
|
|
|
|
+ <Form
|
|
|
|
|
+ form={searchForm}
|
|
|
|
|
+ layout="inline"
|
|
|
|
|
+ onFinish={handleSearch}
|
|
|
|
|
+ className="mb-4"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Form.Item name="contractId" label="合同">
|
|
|
|
|
+ <Select placeholder="选择合同" style={{ width: 200 }}>
|
|
|
|
|
+ {contracts.map(contract => (
|
|
|
|
|
+ <Option key={contract.id} value={contract.id}>
|
|
|
|
|
+ {contract.contractNo} - {contract.customer?.name}
|
|
|
|
|
+ </Option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+
|
|
|
|
|
+ <Form.Item name="status" label="状态">
|
|
|
|
|
+ <Select placeholder="选择状态" style={{ width: 150 }}>
|
|
|
|
|
+ {Object.entries(RenewStatusEnum).map(([value, { label, color }]) => (
|
|
|
|
|
+ <Option key={value} value={Number(value)}>
|
|
|
|
|
+ <Tag color={color} style={{ marginRight: 4 }} />
|
|
|
|
|
+ {label}
|
|
|
|
|
+ </Option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+
|
|
|
|
|
+ <Form.Item name="dateRange" label="续签日期">
|
|
|
|
|
+ <RangePicker
|
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
|
+ style={{ width: 300 }}
|
|
|
|
|
+ placeholder={['开始日期', '结束日期']}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+
|
|
|
|
|
+ <Form.Item>
|
|
|
|
|
+ <Space>
|
|
|
|
|
+ <Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
|
|
|
|
|
+ 搜索
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button onClick={handleReset} icon={<ReloadOutlined />}>
|
|
|
|
|
+ 重置
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </Space>
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
+ </Form>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <Table<ContractRenewItem>
|
|
|
|
|
+ columns={columns}
|
|
|
|
|
+ rowKey="id"
|
|
|
|
|
+ loading={loading}
|
|
|
|
|
+ dataSource={fetchRenews.data?.data || []}
|
|
|
|
|
+ pagination={{
|
|
|
|
|
+ current: currentPage,
|
|
|
|
|
+ pageSize: pageSize,
|
|
|
|
|
+ total: total,
|
|
|
|
|
+ showSizeChanger: true,
|
|
|
|
|
+ showQuickJumper: true,
|
|
|
|
|
+ showTotal: (total) => `共 ${total} 条记录`,
|
|
|
|
|
+ pageSizeOptions: ['10', '20', '50', '100'],
|
|
|
|
|
+ }}
|
|
|
|
|
+ onChange={handleTableChange}
|
|
|
|
|
+ scroll={{ x: 'max-content' }}
|
|
|
/>
|
|
/>
|
|
|
- <Button type="default" onClick={handleSearch} style={{ marginLeft: 8 }}>
|
|
|
|
|
- 搜索
|
|
|
|
|
- </Button>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <Table
|
|
|
|
|
- columns={columns}
|
|
|
|
|
- dataSource={dataSource}
|
|
|
|
|
- rowKey="id"
|
|
|
|
|
- loading={loading}
|
|
|
|
|
- pagination={pagination}
|
|
|
|
|
- onChange={handleTableChange}
|
|
|
|
|
- bordered
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 新增/编辑模态框 */}
|
|
|
<Modal
|
|
<Modal
|
|
|
- title={editingKey ? "编辑合同续签记录" : "添加合同续签记录"}
|
|
|
|
|
- open={modalVisible}
|
|
|
|
|
|
|
+ title={currentRecord ? '编辑续签记录' : '新增续签记录'}
|
|
|
|
|
+ open={isModalVisible}
|
|
|
onCancel={handleCancel}
|
|
onCancel={handleCancel}
|
|
|
footer={[
|
|
footer={[
|
|
|
- <Button key="cancel" onClick={handleCancel}>
|
|
|
|
|
|
|
+ <Button key="cancel" onClick={handleCancel} icon={<CloseOutlined />}>
|
|
|
取消
|
|
取消
|
|
|
</Button>,
|
|
</Button>,
|
|
|
- <Button key="submit" type="primary" onClick={handleSubmit}>
|
|
|
|
|
- 确定
|
|
|
|
|
|
|
+ <Button key="submit" type="primary" htmlType="submit" form="renewForm" icon={<SaveOutlined />}>
|
|
|
|
|
+ 保存
|
|
|
</Button>,
|
|
</Button>,
|
|
|
]}
|
|
]}
|
|
|
|
|
+ destroyOnClose
|
|
|
|
|
+ centered
|
|
|
width={700}
|
|
width={700}
|
|
|
>
|
|
>
|
|
|
- <Form form={form} layout="vertical">
|
|
|
|
|
- {!editingKey && (
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="id"
|
|
|
|
|
- label="续签记录ID"
|
|
|
|
|
- rules={[{ required: true, message: '请输入续签记录ID' }]}
|
|
|
|
|
- >
|
|
|
|
|
- <Input placeholder="请输入续签记录ID" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- )}
|
|
|
|
|
-
|
|
|
|
|
|
|
+ <Divider orientation="left">续签信息</Divider>
|
|
|
|
|
+ <Form
|
|
|
|
|
+ id="renewForm"
|
|
|
|
|
+ form={form}
|
|
|
|
|
+ layout="vertical"
|
|
|
|
|
+ onFinish={handleSubmit}
|
|
|
|
|
+ initialValues={{ status: 0 }}
|
|
|
|
|
+ >
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
name="contractId"
|
|
name="contractId"
|
|
|
- label="原合同"
|
|
|
|
|
- rules={[{ required: true, message: '请选择原合同' }]}
|
|
|
|
|
|
|
+ label="关联合同"
|
|
|
|
|
+ rules={[{ required: true, message: '请选择关联合同' }]}
|
|
|
>
|
|
>
|
|
|
- <Select placeholder="请选择原合同">
|
|
|
|
|
|
|
+ <Select placeholder="选择合同" style={{ width: '100%' }}>
|
|
|
{contracts.map(contract => (
|
|
{contracts.map(contract => (
|
|
|
- <Select.Option key={contract.id} value={contract.id}>
|
|
|
|
|
- {`${contract.id} - ${contract.contractNumber} (${contract.clientId})`}
|
|
|
|
|
- </Select.Option>
|
|
|
|
|
|
|
+ <Option key={contract.id} value={contract.id}>
|
|
|
|
|
+ {contract.contractNo} - {contract.customer?.name}
|
|
|
|
|
+ </Option>
|
|
|
))}
|
|
))}
|
|
|
</Select>
|
|
</Select>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="amount"
|
|
|
|
|
- label="续签金额"
|
|
|
|
|
- >
|
|
|
|
|
- <Input placeholder="请输入续签金额" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
-
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="revenue"
|
|
|
|
|
- label="续签收入"
|
|
|
|
|
- >
|
|
|
|
|
- <Input placeholder="请输入续签收入" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="endDate"
|
|
|
|
|
- label="续签结束日期"
|
|
|
|
|
- >
|
|
|
|
|
- <DatePicker format="YYYY-MM-DD" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
-
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="state"
|
|
|
|
|
- label="续签状态"
|
|
|
|
|
- >
|
|
|
|
|
- <Input placeholder="请输入续签状态:如待审批、已生效等" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <Form.Item
|
|
|
|
|
+ name="renewNo"
|
|
|
|
|
+ label="续签编号"
|
|
|
|
|
+ rules={[{ required: true, message: '请输入续签编号' }]}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Input placeholder="请输入续签编号" disabled={!!currentRecord} />
|
|
|
|
|
+ </Form.Item>
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name="auditStatus"
|
|
|
|
|
- label="审批情况"
|
|
|
|
|
|
|
+ name="renewStartDate"
|
|
|
|
|
+ label="续签开始日期"
|
|
|
|
|
+ rules={[{ required: true, message: '请选择续签开始日期' }]}
|
|
|
>
|
|
>
|
|
|
- <Input placeholder="请输入审批情况" />
|
|
|
|
|
|
|
+ <DatePicker
|
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
|
+ placeholder="请选择开始日期"
|
|
|
|
|
+ />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name="userId"
|
|
|
|
|
- label="处理用户ID"
|
|
|
|
|
|
|
+ name="renewEndDate"
|
|
|
|
|
+ label="续签结束日期"
|
|
|
|
|
+ rules={[{ required: true, message: '请选择续签结束日期' }]}
|
|
|
>
|
|
>
|
|
|
- <Input placeholder="请输入处理用户ID" />
|
|
|
|
|
|
|
+ <DatePicker
|
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
|
+ placeholder="请选择结束日期"
|
|
|
|
|
+ />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name="content"
|
|
|
|
|
- label="续签内容描述"
|
|
|
|
|
- >
|
|
|
|
|
- <Input.TextArea rows={4} placeholder="请输入续签内容描述" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
-
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="auditReasons"
|
|
|
|
|
- label="审批原因"
|
|
|
|
|
|
|
+ name="renewAmount"
|
|
|
|
|
+ label="续签金额"
|
|
|
|
|
+ rules={[
|
|
|
|
|
+ { required: true, message: '请输入续签金额' },
|
|
|
|
|
+ { type: 'number', min: 0, message: '金额必须大于等于0' }
|
|
|
|
|
+ ]}
|
|
|
>
|
|
>
|
|
|
- <Input.TextArea rows={2} placeholder="请输入审批原因" />
|
|
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ placeholder="请输入续签金额"
|
|
|
|
|
+ addonBefore="¥"
|
|
|
|
|
+ precision={2}
|
|
|
|
|
+ />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
|
|
- {!editingKey && (
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="createdTime"
|
|
|
|
|
- label="记录创建时间"
|
|
|
|
|
- rules={[{ required: true, message: '请选择记录创建时间' }]}
|
|
|
|
|
- >
|
|
|
|
|
- <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- )}
|
|
|
|
|
- </Form>
|
|
|
|
|
- </Modal>
|
|
|
|
|
- </div>
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name="content"
|
|
|
|
|
- label="续签内容描述"
|
|
|
|
|
|
|
+ name="status"
|
|
|
|
|
+ label="状态"
|
|
|
|
|
+ rules={[{ required: true, message: '请选择状态' }]}
|
|
|
>
|
|
>
|
|
|
- <Input.TextArea rows={4} placeholder="请输入续签内容描述" />
|
|
|
|
|
|
|
+ <Select placeholder="选择状态" style={{ width: '100%' }}>
|
|
|
|
|
+ {Object.entries(RenewStatusEnum).map(([value, { label, color }]) => (
|
|
|
|
|
+ <Option key={value} value={Number(value)}>
|
|
|
|
|
+ <Tag color={color} style={{ marginRight: 4 }} />
|
|
|
|
|
+ {label}
|
|
|
|
|
+ </Option>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </Select>
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
|
|
<Form.Item
|
|
<Form.Item
|
|
|
- name="auditReasons"
|
|
|
|
|
- label="审批原因"
|
|
|
|
|
|
|
+ name="remark"
|
|
|
|
|
+ label="备注"
|
|
|
>
|
|
>
|
|
|
- <Input.TextArea rows={2} placeholder="请输入审批原因" />
|
|
|
|
|
|
|
+ <Input.TextArea rows={4} placeholder="请输入备注信息" />
|
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
-
|
|
|
|
|
- {!editingKey && (
|
|
|
|
|
- <Form.Item
|
|
|
|
|
- name="createdTime"
|
|
|
|
|
- label="记录创建时间"
|
|
|
|
|
- rules={[{ required: true, message: '请选择记录创建时间' }]}
|
|
|
|
|
- >
|
|
|
|
|
- <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" />
|
|
|
|
|
- </Form.Item>
|
|
|
|
|
- )}
|
|
|
|
|
</Form>
|
|
</Form>
|
|
|
</Modal>
|
|
</Modal>
|
|
|
</div>
|
|
</div>
|