2
0
yourname 6 місяців тому
батько
коміт
df95aca57c

+ 7 - 4
src/client/index.tsx

@@ -1,6 +1,9 @@
 // 如果当前是在 /big 下
 if (window.location.pathname.startsWith('/admin')) {
-    import('./admin/index')
-  }else{
-    import('./home/index')
-  }
+  import('./admin/index')
+}
+else if (window.location.pathname.startsWith('/member')) {
+  import('./member/index')
+} else {
+  import('./home/index')
+}

+ 84 - 0
src/client/member/components/TemplateCard.tsx

@@ -0,0 +1,84 @@
+import { Card, Dropdown, message } from 'antd';
+import { EllipsisOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
+import { useNavigate } from 'react-router-dom';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import type { Template } from '@/share/exceltypes';
+
+interface TemplateCardProps {
+  template: Template;
+}
+
+export function TemplateCard({ template }: TemplateCardProps) {
+  const navigate = useNavigate();
+  const queryClient = useQueryClient();
+
+  const { mutate: deleteTemplate } = useMutation({
+    mutationFn: async () => {
+      const response = await fetch(`/v1/member/templates/${template.id}`, {
+        method: 'DELETE'
+      });
+      return response.json();
+    },
+    onSuccess: () => {
+      message.success('删除成功');
+      queryClient.invalidateQueries({ queryKey: ['templates'] });
+    },
+    onError: () => {
+      message.error('删除失败');
+    }
+  });
+
+  const menuItems = [
+    {
+      key: 'edit',
+      label: '编辑',
+      icon: <EditOutlined />,
+      onClick: () => navigate(`/member/template/edit/${template.id}`)
+    },
+    {
+      key: 'delete',
+      label: '删除',
+      icon: <DeleteOutlined />,
+      onClick: () => {
+        if (confirm('确定要删除此模板吗?')) {
+          deleteTemplate();
+        }
+      }
+    }
+  ];
+
+  return (
+    <Card
+      hoverable
+      cover={
+        template.thumbnail ? (
+          <img 
+            alt={template.name} 
+            src={template.thumbnail} 
+            className="h-40 object-cover"
+          />
+        ) : (
+          <div className="h-40 bg-gray-100 flex items-center justify-center text-gray-400">
+            无预览图
+          </div>
+        )
+      }
+      actions={[
+        <Dropdown key="more" menu={{ items: menuItems }}>
+          <EllipsisOutlined />
+        </Dropdown>
+      ]}
+      onClick={() => navigate(`/member/template/edit/${template.id}`)}
+    >
+      <Card.Meta
+        title={template.name}
+        description={
+          <div className="text-gray-500 text-sm">
+            <div>创建时间:{new Date(template.created_at).toLocaleDateString()}</div>
+            <div>最后更新:{new Date(template.updated_at).toLocaleDateString()}</div>
+          </div>
+        }
+      />
+    </Card>
+  );
+} 

+ 77 - 0
src/client/member/components/TemplateSelector.tsx

@@ -0,0 +1,77 @@
+import React from 'react';
+import { Select, Spin, message } from 'antd';
+import type { Template } from '@/share/exceltypes';
+import { useQuery } from '@tanstack/react-query';
+
+interface TemplateSelectorProps {
+  onTemplateSelected: (templateId: string) => void;
+  value?: string;
+  disabled?: boolean;
+  required?: boolean;
+}
+
+/**
+ * 模板选择器组件
+ * 用于通过模板ID选择Excel解析模板
+ * 注意: 使用时需确保同时提供input和templateId,两项均为必填
+ */
+const TemplateSelector: React.FC<TemplateSelectorProps> = ({ 
+  onTemplateSelected, 
+  value, 
+  disabled = false,
+  required = true
+}) => {
+  const { requestWithToken } = useApiRequest();
+  
+  // 使用 React Query 获取模板数据
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ['templates'],
+    queryFn: async () => {
+      try {
+        const result = await requestWithToken({
+          url: '/v1/member/templates',
+          method: 'GET'
+        });
+        
+        if (result.success && Array.isArray(result.data)) {
+          return result.data as Template[];
+        } else {
+          message.error('获取模板列表失败');
+          return [];
+        }
+      } catch (error) {
+        console.error('获取模板列表错误:', error);
+        message.error('获取模板列表失败');
+        throw error;
+      }
+    }
+  });
+
+  const handleChange = (value: string) => {
+    onTemplateSelected(value);
+  };
+
+  return (
+    <Spin spinning={isLoading}>
+      <Select
+        placeholder={required ? "请选择模板(必填)" : "请选择模板"}
+        style={{ width: '100%' }}
+        onChange={handleChange}
+        loading={isLoading}
+        allowClear={!required}
+        value={value}
+        disabled={disabled}
+        status={required && !value ? "error" : undefined}
+      >
+        {data?.map(template => (
+          <Select.Option key={template.id} value={String(template.id)}>
+            {template.template_name}
+          </Select.Option>
+        ))}
+      </Select>
+      {isError && <div style={{ color: 'red', marginTop: '4px' }}>获取模板列表失败</div>}
+    </Spin>
+  );
+};
+
+export default TemplateSelector; 

+ 15 - 1
src/client/member/menu.tsx

@@ -6,6 +6,8 @@ import {
   DashboardOutlined,
   TeamOutlined,
   InfoCircleOutlined,
+  FileTextOutlined,
+  ApiOutlined,
 } from '@ant-design/icons';
 
 export interface MenuItem {
@@ -74,7 +76,19 @@ export const useMenu = () => {
       key: 'dashboard',
       label: '控制台',
       icon: <DashboardOutlined />,
-      path: '/member/dashboard'
+      path: '/member/home'
+    },
+    {
+      key: 'templates',
+      label: '模板管理',
+      icon: <FileTextOutlined />,
+      path: '/member/templates'
+    },
+    {
+      key: 'api-sandbox',
+      label: 'API调试',
+      icon: <ApiOutlined />,
+      path: '/member/api-sandbox'
     },
   ];
 

+ 61 - 0
src/client/member/pages/Home.tsx

@@ -0,0 +1,61 @@
+import { Card, Row, Col, Button } from "antd";
+import { PlusOutlined, ApiOutlined } from "@ant-design/icons";
+import { useQuery } from "@tanstack/react-query";
+import { TemplateCard } from "../components/TemplateCard";
+import type { Template } from "@/share/exceltypes"
+import { Link } from "react-router";
+
+function MemberHome() {
+
+  const { data: recentTemplates } = useQuery<Template[]>({
+    queryKey: ["templates", "recent"],
+    queryFn: async () => {
+      // const result = await requestWithToken({
+      //   url: '/v1/member/templates/recent',
+      //   method: 'GET',
+      // });
+
+      // return result.data;
+      return [];
+    },
+  });
+
+  return (
+    <div className="p-6">
+      <Row gutter={[16, 16]}>
+        <Col span={24}>
+          <Card title="快速操作" className="shadow-sm">
+            <Row gutter={16}>
+              <Col>
+                <Link to="/member/template/add">
+                  <Button type="primary" icon={<PlusOutlined />}>
+                    创建新模板
+                  </Button>
+                </Link>
+              </Col>
+              <Col>
+                <Link to="/member/api-sandbox">
+                  <Button icon={<ApiOutlined />}>API调试</Button>
+                </Link>
+              </Col>
+            </Row>
+          </Card>
+        </Col>
+
+        <Col span={24}>
+          <Card title="最近使用的模板" className="shadow-sm">
+            <Row gutter={[16, 16]}>
+              {recentTemplates?.map((template) => (
+                <Col key={template.id} xs={24} sm={12} md={8} lg={6}>
+                  <TemplateCard template={template} />
+                </Col>
+              ))}
+            </Row>
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+}
+
+export default MemberHome;

+ 6 - 0
src/client/member/routes.tsx

@@ -6,6 +6,7 @@ import { ErrorPage } from './components/ErrorPage';
 import { NotFoundPage } from './components/NotFoundPage';
 import { DashboardPage } from './pages/Dashboard';
 import { LoginPage } from './pages/Login';
+import MemberHome from './pages/Home';
 
 export const router = createBrowserRouter([
   {
@@ -33,6 +34,11 @@ export const router = createBrowserRouter([
         element: <DashboardPage />,
         errorElement: <ErrorPage />
       },
+      {
+        path: 'home',
+        element: <MemberHome />,
+        errorElement: <ErrorPage />
+      },
       {
         path: '*',
         element: <NotFoundPage />,