|
|
@@ -1,15 +1,17 @@
|
|
|
import React, { useState } from 'react';
|
|
|
-import { Table, Button, Space, Input, Modal, Form, message, Upload, Select } from 'antd';
|
|
|
+import { Table, Button, Space, Input, Modal, Form, message, Upload, Select, DatePicker } from 'antd';
|
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined } from '@ant-design/icons';
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
import { fileClient, clientClient } from '@/client/api';
|
|
|
-import type { InferResponseType } from 'hono/client';
|
|
|
+import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
// 定义类型
|
|
|
type FileItem = InferResponseType<typeof fileClient.$get, 200>['data'][0];
|
|
|
type FileListResponse = InferResponseType<typeof fileClient.$get, 200>;
|
|
|
type ClientItem = InferResponseType<typeof clientClient.$get, 200>['data'][0];
|
|
|
+type CreateFileRequest = InferRequestType<typeof fileClient.$post>['json'];
|
|
|
+type UpdateFileRequest = InferRequestType<typeof fileClient[':id']['$put']>['json'];
|
|
|
|
|
|
const Files: React.FC = () => {
|
|
|
const [form] = Form.useForm();
|
|
|
@@ -22,48 +24,50 @@ const Files: React.FC = () => {
|
|
|
// 获取客户列表
|
|
|
const { data: clientsData } = useQuery({
|
|
|
queryKey: ['clients'],
|
|
|
- queryFn: () => clientClient.$get({ query: { page: 1, pageSize: 1000 } }) as Promise<InferResponseType<typeof clientClient.$get, 200>>,
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await clientClient.$get({ query: { page: 1, pageSize: 1000 } });
|
|
|
+ if (!response.ok) throw new Error('Failed to fetch clients');
|
|
|
+ return await response.json() as InferResponseType<typeof clientClient.$get, 200>;
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000, // 5分钟缓存
|
|
|
onSuccess: (result) => {
|
|
|
setClients(result.data);
|
|
|
},
|
|
|
});
|
|
|
- );
|
|
|
|
|
|
// 获取文件列表数据
|
|
|
- const fetchFiles = ({ page, pageSize }: { page: number; pageSize: number }): Promise<FileListResponse> =>
|
|
|
- fileClient.$get({ query: { page, pageSize, keyword: searchText } }) as Promise<FileListResponse>;
|
|
|
-
|
|
|
- const { data, isLoading: loading, refetch } = useQuery(
|
|
|
- ['files', pagination.current, pagination.pageSize, searchText],
|
|
|
- () => fetchFiles({ page: pagination.current, pageSize: pagination.pageSize }),
|
|
|
- {
|
|
|
- onSuccess: (result) => {
|
|
|
- setDataSource(result.data);
|
|
|
- setPagination({
|
|
|
- ...pagination,
|
|
|
- total: result.pagination.total,
|
|
|
- });
|
|
|
- },
|
|
|
- }
|
|
|
- );
|
|
|
+ const fetchFiles = async ({ page, pageSize }: { page: number; pageSize: number }): Promise<FileListResponse> => {
|
|
|
+ const response = await fileClient.$get({ query: { page, pageSize, keyword: searchText } });
|
|
|
+ if (!response.ok) throw new Error('Failed to fetch files');
|
|
|
+ return await response.json() as FileListResponse;
|
|
|
+ };
|
|
|
|
|
|
- const [dataSource, setDataSource] = useState<FileItem[]>([]);
|
|
|
const [pagination, setPagination] = useState({
|
|
|
current: 1,
|
|
|
pageSize: 10,
|
|
|
total: 0,
|
|
|
});
|
|
|
|
|
|
+ const { data, isLoading: loading } = useQuery({
|
|
|
+ queryKey: ['files', pagination.current, pagination.pageSize, searchText],
|
|
|
+ queryFn: () => fetchFiles({ page: pagination.current, pageSize: pagination.pageSize }),
|
|
|
+ keepPreviousData: true, // 保留前一页数据直到新数据加载完成
|
|
|
+ onSuccess: (result) => {
|
|
|
+ setPagination({
|
|
|
+ ...pagination,
|
|
|
+ total: result.pagination.total,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
// 搜索
|
|
|
const handleSearch = () => {
|
|
|
setPagination({ ...pagination, current: 1 });
|
|
|
- refetch();
|
|
|
};
|
|
|
|
|
|
// 分页变化
|
|
|
- const handleTableChange = (pagination: any) => {
|
|
|
- setPagination(pagination);
|
|
|
- refetch();
|
|
|
+ const handleTableChange = (newPagination: any) => {
|
|
|
+ setPagination(newPagination);
|
|
|
};
|
|
|
|
|
|
// 显示添加/编辑弹窗
|
|
|
@@ -96,43 +100,42 @@ const Files: React.FC = () => {
|
|
|
|
|
|
// 创建文件记录
|
|
|
const createFile = useMutation({
|
|
|
- mutationFn: (data: any) => fileClient.$post({ json: data }),
|
|
|
+ mutationFn: (data: CreateFileRequest) => fileClient.$post({ json: data }),
|
|
|
onSuccess: () => {
|
|
|
message.success('文件记录创建成功');
|
|
|
queryClient.invalidateQueries({ queryKey: ['files'] });
|
|
|
setModalVisible(false);
|
|
|
},
|
|
|
- onError: () => {
|
|
|
- message.error('操作失败,请重试');
|
|
|
+ onError: (error: Error) => {
|
|
|
+ message.error(`操作失败: ${error.message}`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 更新文件记录
|
|
|
const updateFile = useMutation({
|
|
|
- mutationFn: ({ id, data }: { id: string; data: any }) => fileClient[':id'].$put({ param: { id }, json: data }),
|
|
|
+ mutationFn: ({ id, data }: { id: string; data: UpdateFileRequest }) =>
|
|
|
+ fileClient[':id'].$put({ param: { id }, json: data }),
|
|
|
onSuccess: () => {
|
|
|
message.success('文件记录更新成功');
|
|
|
queryClient.invalidateQueries({ queryKey: ['files'] });
|
|
|
setModalVisible(false);
|
|
|
},
|
|
|
- onError: () => {
|
|
|
- message.error('操作失败,请重试');
|
|
|
+ onError: (error: Error) => {
|
|
|
+ message.error(`操作失败: ${error.message}`);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 删除文件记录
|
|
|
- const deleteFile = useMutation(
|
|
|
- (id: string) => fileClient[':id'].$delete({ param: { id } }),
|
|
|
- {
|
|
|
- onSuccess: () => {
|
|
|
- message.success('文件记录删除成功');
|
|
|
- queryClient.invalidateQueries(['files']);
|
|
|
- },
|
|
|
- onError: () => {
|
|
|
- message.error('删除失败,请重试');
|
|
|
- }
|
|
|
+ const deleteFile = useMutation({
|
|
|
+ mutationFn: (id: string) => fileClient[':id'].$delete({ param: { id } }),
|
|
|
+ onSuccess: () => {
|
|
|
+ message.success('文件记录删除成功');
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['files'] });
|
|
|
+ },
|
|
|
+ onError: (error: Error) => {
|
|
|
+ message.error(`删除失败: ${error.message}`);
|
|
|
}
|
|
|
- );
|
|
|
+ });
|
|
|
|
|
|
// 提交表单
|
|
|
const handleSubmit = async () => {
|
|
|
@@ -140,18 +143,21 @@ const Files: React.FC = () => {
|
|
|
const values = await form.validateFields();
|
|
|
|
|
|
// 处理日期字段
|
|
|
- if (values.uploadTime) values.uploadTime = values.uploadTime.format('YYYY-MM-DD HH:mm:ss');
|
|
|
- if (values.lastUpdated) values.lastUpdated = values.lastUpdated.format('YYYY-MM-DD HH:mm:ss');
|
|
|
+ const payload = {
|
|
|
+ ...values,
|
|
|
+ uploadTime: values.uploadTime?.format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ lastUpdated: values.lastUpdated?.format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ };
|
|
|
|
|
|
if (editingKey) {
|
|
|
// 更新操作
|
|
|
- await updateFile.mutateAsync({ id: editingKey, data: values });
|
|
|
+ await updateFile.mutateAsync({ id: editingKey, data: payload });
|
|
|
} else {
|
|
|
// 创建操作
|
|
|
- await createFile.mutateAsync(values);
|
|
|
+ await createFile.mutateAsync(payload);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- message.error('操作失败,请重试');
|
|
|
+ message.error('表单验证失败,请检查输入');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -189,7 +195,7 @@ const Files: React.FC = () => {
|
|
|
title: '上传时间',
|
|
|
dataIndex: 'uploadTime',
|
|
|
key: 'uploadTime',
|
|
|
- render: (time: string) => time ? new Date(time).toLocaleString() : '-',
|
|
|
+ render: (time: string) => time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-',
|
|
|
},
|
|
|
{
|
|
|
title: '上传用户',
|
|
|
@@ -250,7 +256,7 @@ const Files: React.FC = () => {
|
|
|
|
|
|
<Table
|
|
|
columns={columns}
|
|
|
- dataSource={dataSource}
|
|
|
+ dataSource={data?.data || []}
|
|
|
rowKey="id"
|
|
|
loading={loading}
|
|
|
pagination={pagination}
|
|
|
@@ -266,7 +272,12 @@ const Files: React.FC = () => {
|
|
|
<Button key="cancel" onClick={handleCancel}>
|
|
|
取消
|
|
|
</Button>,
|
|
|
- <Button key="submit" type="primary" onClick={handleSubmit}>
|
|
|
+ <Button
|
|
|
+ key="submit"
|
|
|
+ type="primary"
|
|
|
+ onClick={handleSubmit}
|
|
|
+ loading={createFile.isPending || updateFile.isPending}
|
|
|
+ >
|
|
|
确定
|
|
|
</Button>,
|
|
|
]}
|