Ver código fonte

✨ feat(admin): 创建训练代码管理页面

- 实现训练代码数据的列表展示、分页和搜索功能
- 添加创建、编辑、删除训练代码的交互功能
- 设计训练代码表单,包含股票代码、名称、类型等字段
- 集成Ant Design组件库,实现表格、模态框等UI元素
- 添加数据加载状态和操作结果提示反馈
yourname 5 meses atrás
pai
commit
b543379edd
1 arquivos alterados com 349 adições e 0 exclusões
  1. 349 0
      src/client/admin/pages/StockXunlianCodesPage.tsx

+ 349 - 0
src/client/admin/pages/StockXunlianCodesPage.tsx

@@ -0,0 +1,349 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Modal, Form, Input, DatePicker, Space, Typography, message, Tag } from 'antd';
+import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
+import { stockXunlianCodesClient } from '@/client/api';
+import type { InferResponseType, InferRequestType } from 'hono/client';
+import { App } from 'antd';
+
+const { Title } = Typography;
+
+// 定义类型
+type StockXunlianCodesListResponse = InferResponseType<typeof stockXunlianCodesClient.$get, 200>;
+type StockXunlianCodesItem = StockXunlianCodesListResponse['data'][0];
+type CreateStockXunlianCodesRequest = InferRequestType<typeof stockXunlianCodesClient.$post>['json'];
+type UpdateStockXunlianCodesRequest = InferRequestType<typeof stockXunlianCodesClient[':id']['$put']>['json'];
+
+export const StockXunlianCodesPage: React.FC = () => {
+  const [data, setData] = useState<StockXunlianCodesItem[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  });
+  const [searchText, setSearchText] = useState('');
+  const [isModalVisible, setIsModalVisible] = useState(false);
+  const [isEditing, setIsEditing] = useState(false);
+  const [currentItem, setCurrentItem] = useState<StockXunlianCodesItem | null>(null);
+  const [form] = Form.useForm();
+  const { message: antMessage } = App.useApp();
+
+  // 获取数据列表
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      const res = await stockXunlianCodesClient.$get({
+        query: {
+          page: pagination.current,
+          pageSize: pagination.pageSize,
+          keyword: searchText,
+        },
+      });
+      
+      if (!res.ok) {
+        throw new Error('获取数据失败');
+      }
+      
+      const result = await res.json() as StockXunlianCodesListResponse;
+      setData(result.data);
+      setPagination(prev => ({
+        ...prev,
+        total: result.pagination.total,
+      }));
+    } catch (error) {
+      console.error('获取训练代码数据失败:', error);
+      antMessage.error('获取数据失败,请重试');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始加载和分页、搜索变化时重新获取数据
+  useEffect(() => {
+    fetchData();
+  }, [pagination.current, pagination.pageSize]);
+
+  // 搜索功能
+  const handleSearch = () => {
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchData();
+  };
+
+  // 显示创建模态框
+  const showCreateModal = () => {
+    setIsEditing(false);
+    setCurrentItem(null);
+    form.resetFields();
+    setIsModalVisible(true);
+  };
+
+  // 显示编辑模态框
+  const showEditModal = (record: StockXunlianCodesItem) => {
+    setIsEditing(true);
+    setCurrentItem(record);
+    form.setFieldsValue({
+      code: record.code,
+      stockName: record.stockName,
+      name: record.name,
+      type: record.type || undefined,
+      description: record.description || undefined,
+      tradeDate: record.tradeDate ? new Date(record.tradeDate) : null,
+    });
+    setIsModalVisible(true);
+  };
+
+  // 处理表单提交
+  const handleSubmit = async () => {
+    try {
+      const values = await form.validateFields();
+      
+      if (isEditing && currentItem) {
+        // 更新数据
+        const res = await stockXunlianCodesClient[':id'].$put({
+          param: { id: currentItem.id },
+          json: values as UpdateStockXunlianCodesRequest,
+        });
+        
+        if (!res.ok) {
+          throw new Error('更新失败');
+        }
+        antMessage.success('更新成功');
+      } else {
+        // 创建新数据
+        const res = await stockXunlianCodesClient.$post({
+          json: values as CreateStockXunlianCodesRequest,
+        });
+        
+        if (!res.ok) {
+          throw new Error('创建失败');
+        }
+        antMessage.success('创建成功');
+      }
+      
+      setIsModalVisible(false);
+      fetchData();
+    } catch (error) {
+      console.error('提交表单失败:', error);
+      antMessage.error(isEditing ? '更新失败,请重试' : '创建失败,请重试');
+    }
+  };
+
+  // 删除数据
+  const handleDelete = async (id: number) => {
+    try {
+      const res = await stockXunlianCodesClient[':id'].$delete({
+        param: { id },
+      });
+      
+      if (!res.ok) {
+        throw new Error('删除失败');
+      }
+      
+      antMessage.success('删除成功');
+      fetchData();
+    } catch (error) {
+      console.error('删除数据失败:', error);
+      antMessage.error('删除失败,请重试');
+    }
+  };
+
+  // 表格列定义
+  const columns = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id',
+      width: 80,
+    },
+    {
+      title: '股票代码',
+      dataIndex: 'code',
+      key: 'code',
+      filters: [
+        ...Array.from(new Set(data.map(item => item.code))).map(code => ({
+          text: code,
+          value: code,
+        }))
+      ],
+      onFilter: (value: string, record: StockXunlianCodesItem) => record.code === value,
+    },
+    {
+      title: '股票名称',
+      dataIndex: 'stockName',
+      key: 'stockName',
+    },
+    {
+      title: '案例名称',
+      dataIndex: 'name',
+      key: 'name',
+    },
+    {
+      title: '案例类型',
+      dataIndex: 'type',
+      key: 'type',
+      render: (type: string | null) => type ? (
+        <Tag color="blue">{type}</Tag>
+      ) : (
+        <Tag color="default">未分类</Tag>
+      ),
+      filters: [
+        { text: '技术分析', value: '技术分析' },
+        { text: '基本面分析', value: '基本面分析' },
+        { text: '未分类', value: '未分类' },
+      ],
+      onFilter: (value: string, record: StockXunlianCodesItem) => {
+        if (value === '未分类') {
+          return !record.type;
+        }
+        return record.type === value;
+      },
+    },
+    {
+      title: '交易日期',
+      dataIndex: 'tradeDate',
+      key: 'tradeDate',
+      render: (date: string) => date ? new Date(date).toLocaleString() : '-',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createdAt',
+      key: 'createdAt',
+      render: (date: string) => new Date(date).toLocaleString(),
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: StockXunlianCodesItem) => (
+        <Space size="small">
+          <Button 
+            type="text" 
+            icon={<EditOutlined />} 
+            onClick={() => showEditModal(record)}
+          >
+            编辑
+          </Button>
+          <Button 
+            type="text" 
+            danger 
+            icon={<DeleteOutlined />} 
+            onClick={() => handleDelete(record.id)}
+          >
+            删除
+          </Button>
+        </Space>
+      ),
+    },
+  ];
+
+  return (
+    <div className="page-container">
+      <div className="page-header" style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+        <Title level={2}>训练代码管理</Title>
+        <Button type="primary" icon={<PlusOutlined />} onClick={showCreateModal}>
+          添加训练代码
+        </Button>
+      </div>
+      
+      <div className="search-container" style={{ marginBottom: 16 }}>
+        <Input
+          placeholder="搜索股票代码或案例名称"
+          value={searchText}
+          onChange={(e) => setSearchText(e.target.value)}
+          onPressEnter={handleSearch}
+          style={{ width: 300 }}
+          suffix={<SearchOutlined onClick={handleSearch} />}
+        />
+      </div>
+      
+      <Table
+        columns={columns}
+        dataSource={data.map(item => ({ ...item, key: item.id }))}
+        loading={loading}
+        pagination={{
+          current: pagination.current,
+          pageSize: pagination.pageSize,
+          total: pagination.total,
+          showSizeChanger: true,
+          showTotal: (total) => `共 ${total} 条记录`,
+        }}
+        onChange={(p) => setPagination({ ...pagination, current: p.current || 1, pageSize: p.pageSize || 10 })}
+        rowKey="id"
+      />
+      
+      <Modal
+        title={isEditing ? "编辑训练代码" : "添加训练代码"}
+        open={isModalVisible}
+        onOk={handleSubmit}
+        onCancel={() => setIsModalVisible(false)}
+        destroyOnClose
+        maskClosable={false}
+        width={700}
+      >
+        <Form
+          form={form}
+          layout="vertical"
+          name="stock_xunlian_codes_form"
+        >
+          <Form.Item
+            name="code"
+            label="股票代码"
+            rules={[
+              { required: true, message: '请输入股票代码' },
+              { max: 255, message: '股票代码不能超过255个字符' }
+            ]}
+          >
+            <Input placeholder="请输入股票代码" />
+          </Form.Item>
+          
+          <Form.Item
+            name="stockName"
+            label="股票名称"
+            rules={[
+              { required: true, message: '请输入股票名称' },
+              { max: 255, message: '股票名称不能超过255个字符' }
+            ]}
+          >
+            <Input placeholder="请输入股票名称" />
+          </Form.Item>
+          
+          <Form.Item
+            name="name"
+            label="案例名称"
+            rules={[
+              { required: true, message: '请输入案例名称' },
+              { max: 255, message: '案例名称不能超过255个字符' }
+            ]}
+          >
+            <Input placeholder="请输入案例名称" />
+          </Form.Item>
+          
+          <Form.Item
+            name="type"
+            label="案例类型"
+            rules={[{ max: 255, message: '案例类型不能超过255个字符' }]}
+          >
+            <Input placeholder="请输入案例类型(如:技术分析、基本面分析)" />
+          </Form.Item>
+          
+          <Form.Item
+            name="description"
+            label="案例描述"
+            rules={[{ max: 255, message: '案例描述不能超过255个字符' }]}
+          >
+            <Input.TextArea rows={4} placeholder="请输入案例描述" />
+          </Form.Item>
+          
+          <Form.Item
+            name="tradeDate"
+            label="交易日期"
+            rules={[{ required: true, message: '请选择交易日期' }]}
+          >
+            <DatePicker showTime placeholder="请选择交易日期" style={{ width: '100%' }} />
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+};
+
+export default StockXunlianCodesPage;