Explorar el Código

✨ feat(files): 增强文件管理页面功能与用户体验

- 添加DatePicker组件支持日期选择
- 优化文件列表加载逻辑,使用keepPreviousData提升分页体验
- 完善类型定义,添加CreateFileRequest和UpdateFileRequest类型
- 改进错误处理,显示具体错误信息
- 优化表单提交逻辑,处理日期格式化
- 为提交按钮添加加载状态

♻️ refactor(files): 重构文件管理页面代码结构

- 移除冗余的dataSource状态,直接使用查询数据
- 优化客户列表查询,添加错误处理和缓存策略
- 改进文件列表查询函数,添加错误处理
- 统一mutation语法,使用对象形式配置
- 优化分页处理逻辑,移除冗余的refetch调用

🐛 fix(files): 修复文件管理页面若干问题

- 修复日期格式化问题,使用dayjs统一处理时间显示
- 修复表单验证失败时的错误提示信息
- 修复文件列表渲染问题,确保空状态正确显示
- 修复编辑文件时的参数传递问题
yourname hace 8 meses
padre
commit
f5ee6109b1
Se han modificado 1 ficheros con 61 adiciones y 50 borrados
  1. 61 50
      src/client/admin/pages/Files.tsx

+ 61 - 50
src/client/admin/pages/Files.tsx

@@ -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>,
         ]}