2
0
Pārlūkot izejas kodu

已成功创建模板列表页面组件 src/client/member/pages/TemplateList.tsx。该组件包含以下功能:
1. 使用antd Table展示模板列表
2. 支持分页和滚动加载
3. 提供编辑和删除操作
4. 包含新增模板按钮
5. 使用react-query进行数据管理
6. 完全类型安全的TypeScript实现
7. 遵循项目代码规范

yourname 5 mēneši atpakaļ
vecāks
revīzija
869c8c71c6

+ 135 - 0
src/client/member/pages/TemplateList.tsx

@@ -0,0 +1,135 @@
+import { Card, Table, Button, Space, message, Popconfirm } from 'antd';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { useNavigate } from 'react-router';
+import { useState } from 'react';
+import { excelTemplateClient } from '@/client/api';
+import type { InferResponseType } from 'hono/client';
+import { PlusOutlined } from '@ant-design/icons';
+
+type TemplateListResponse = InferResponseType<typeof excelTemplateClient.$get, 200>;
+type TemplateItem = TemplateListResponse['data'][0];
+
+function TemplateList() {
+  const navigate = useNavigate();
+  const queryClient = useQueryClient();
+
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10
+  });
+
+  const { data, isLoading } = useQuery({
+    queryKey: ['templates', pagination],
+    queryFn: async () => {
+      const res = await excelTemplateClient.$get({
+        query: {
+          page: pagination.current,
+          pageSize: pagination.pageSize
+        }
+      });
+      if (res.status !== 200) {
+        const data = await res.json();
+        throw new Error(data.message);
+      }
+      return await res.json();
+    }
+  });
+
+  const { mutate: deleteTemplate, isPending: isDeleting } = useMutation({
+    mutationFn: async (id: number) => {
+      const res = await excelTemplateClient[':id'].$delete({
+        param: { id }
+      });
+      if (res.status !== 200) {
+        const data = await res.json();
+        throw new Error(data.message);
+      }
+      return await res.json();
+    },
+    onSuccess: () => {
+      message.success('删除成功');
+      queryClient.invalidateQueries({ queryKey: ['templates'] });
+    },
+    onError: () => {
+      message.error('删除失败');
+    }
+  });
+
+  const columns = [
+    {
+      title: '模板名称',
+      dataIndex: 'name',
+      key: 'name'
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createdAt',
+      key: 'createdAt',
+      render: (date: string) => new Date(date).toLocaleString()
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: TemplateItem) => (
+        <Space>
+          <Button 
+            size="small" 
+            onClick={() => navigate(`/member/templates/${record.id}`)}
+          >
+            编辑
+          </Button>
+          <Popconfirm
+            title="确定要删除吗?"
+            onConfirm={() => deleteTemplate(record.id)}
+            okButtonProps={{ loading: isDeleting }}
+          >
+            <Button size="small" danger>
+              删除
+            </Button>
+          </Popconfirm>
+        </Space>
+      )
+    }
+  ];
+
+  return (
+    <div className="p-3 h-full">
+      <Card 
+        title="模板列表"
+        extra={
+          <Button 
+            type="primary" 
+            icon={<PlusOutlined />}
+            onClick={() => navigate('/member/templates/new')}
+          >
+            新增模板
+          </Button>
+        }
+        styles={{
+          body: {
+            height: 'calc(100vh - 130px)'
+          }
+        }}
+      >
+        <Table
+          loading={isLoading}
+          columns={columns}
+          dataSource={data?.data || []}
+          rowKey="id"
+          scroll={{ y: 'calc(100vh - 200px)' }}
+          pagination={{
+            ...pagination,
+            total: data?.pagination?.total || 0,
+            showSizeChanger: true,
+            showTotal: (total) => `共 ${total} 条`,
+            onChange: (page, pageSize) => {
+              setPagination({ current: page, pageSize });
+            }
+          }}
+        />
+      </Card>
+    </div>
+  );
+}
+
+export default TemplateList;

+ 3 - 3
src/server/api/excel-templates/[id]/delete.ts

@@ -9,9 +9,9 @@ import { authMiddleware } from '@/server/middleware/auth.middleware';
 const excelTemplateService = new ExcelTemplateService(AppDataSource);
 
 const GetParams = z.object({
-  id: z.string().openapi({
+  id: z.coerce.number().openapi({
     param: { name: 'id', in: 'path' },
-    example: '1',
+    example: 1,
     description: '模板ID'
   })
 });
@@ -59,7 +59,7 @@ const routeDef = createRoute({
 const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
   try {
     const { id } = c.req.valid('param');
-    const result = await excelTemplateService.delete(Number(id));
+    const result = await excelTemplateService.delete(id);
     
     if (!result.affected) {
       return c.json({