瀏覽代碼

✨ feat(dependencies): add echarts and socket.io-client packages

- 添加echarts@5.6.0用于股票图表展示功能
- 添加socket.io-client@4.8.1用于实时数据通信

✨ feat(stock): add stock chart component and related hooks

- 实现股票图表核心组件StockChart
- 添加交易记录管理、利润计算等相关hooks
- 实现绘图工具栏和利润展示组件

✨ feat(routes): add stock and exam routes

- 添加股票交易页面路由/mobile/stock
- 添加考试相关页面路由/exam/*
- 调整教室页面路由参数结构

♻️ refactor(imports): standardize import statements and paths

- 移除所有文件中的.ts扩展名导入
- 统一使用@/别名导入共享类型和hooks
- 优化组件间引用路径

🔥 feat(types): add stock shared types

- 创建共享类型文件types_stock.ts
- 定义教室状态、提交记录等核心业务类型
- 提供状态枚举和中文映射关系

♻️ refactor(exam): optimize socket client usage

- 重构考试组件中的socket客户端逻辑
- 统一auth hook导入路径
- 优化类型引用方式

🔥 refactor: remove unused useTradeSimulator hook

- 删除未使用的交易模拟器钩子文件
- 清理相关引用和依赖
yourname 5 月之前
父節點
當前提交
9ebf05d40e

+ 2 - 0
package.json

@@ -27,6 +27,7 @@
     "dayjs": "^1.11.13",
     "debug": "^4.4.1",
     "dotenv": "^16.5.0",
+    "echarts": "^5.6.0",
     "formdata-node": "^6.0.3",
     "hono": "^4.7.11",
     "ioredis": "^5.6.1",
@@ -43,6 +44,7 @@
     "react-router-dom": "^7.6.1",
     "react-toastify": "^11.0.5",
     "reflect-metadata": "^0.2.2",
+    "socket.io-client": "^4.8.1",
     "typeorm": "^0.3.24",
     "vod-js-sdk-v6": "1.7.1-beta.1"
   },

+ 111 - 0
pnpm-lock.yaml

@@ -65,6 +65,9 @@ importers:
       dotenv:
         specifier: ^16.5.0
         version: 16.5.0
+      echarts:
+        specifier: ^5.6.0
+        version: 5.6.0
       formdata-node:
         specifier: ^6.0.3
         version: 6.0.3
@@ -113,6 +116,9 @@ importers:
       reflect-metadata:
         specifier: ^0.2.2
         version: 0.2.2
+      socket.io-client:
+        specifier: ^4.8.1
+        version: 4.8.1
       typeorm:
         specifier: ^0.3.24
         version: 0.3.24(babel-plugin-macros@3.1.0)(ioredis@5.6.1)(mysql2@3.14.1)(reflect-metadata@0.2.2)
@@ -1178,6 +1184,9 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@socket.io/component-emitter@3.1.2':
+    resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
   '@sqltools/formatter@1.2.5':
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
 
@@ -1676,6 +1685,15 @@ packages:
   dayjs@1.11.13:
     resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
 
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   debug@4.4.1:
     resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
     engines: {node: '>=6.0'}
@@ -1730,6 +1748,9 @@ packages:
   ecdsa-sig-formatter@1.0.11:
     resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
 
+  echarts@5.6.0:
+    resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
+
   electron-to-chromium@1.5.167:
     resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==}
 
@@ -1739,6 +1760,13 @@ packages:
   emoji-regex@9.2.2:
     resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
 
+  engine.io-client@6.6.3:
+    resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
+
+  engine.io-parser@5.2.3:
+    resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+    engines: {node: '>=10.0.0'}
+
   enhanced-resolve@5.18.1:
     resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
     engines: {node: '>=10.13.0'}
@@ -2818,6 +2846,14 @@ packages:
   simple-swizzle@0.2.2:
     resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
 
+  socket.io-client@4.8.1:
+    resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+    engines: {node: '>=10.0.0'}
+
+  socket.io-parser@4.2.4:
+    resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+    engines: {node: '>=10.0.0'}
+
   source-map-js@1.2.1:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
@@ -2936,6 +2972,9 @@ packages:
   tr46@0.0.3:
     resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
 
+  tslib@2.3.0:
+    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
@@ -3189,6 +3228,18 @@ packages:
     resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
     engines: {node: '>=12'}
 
+  ws@8.17.1:
+    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
   ws@8.18.0:
     resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
     engines: {node: '>=10.0.0'}
@@ -3209,6 +3260,10 @@ packages:
     resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
     engines: {node: '>=4.0'}
 
+  xmlhttprequest-ssl@2.1.2:
+    resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+    engines: {node: '>=0.4.0'}
+
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -3254,6 +3309,9 @@ packages:
   zod@3.25.64:
     resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==}
 
+  zrender@5.6.1:
+    resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
+
 snapshots:
 
   '@aliyun-sls/web-base@0.2.9':
@@ -4100,6 +4158,8 @@ snapshots:
   '@rollup/rollup-win32-x64-msvc@4.43.0':
     optional: true
 
+  '@socket.io/component-emitter@3.1.2': {}
+
   '@sqltools/formatter@1.2.5': {}
 
   '@tailwindcss/node@4.1.10':
@@ -4688,6 +4748,10 @@ snapshots:
 
   dayjs@1.11.13: {}
 
+  debug@4.3.7:
+    dependencies:
+      ms: 2.1.3
+
   debug@4.4.1:
     dependencies:
       ms: 2.1.3
@@ -4726,12 +4790,31 @@ snapshots:
     dependencies:
       safe-buffer: 5.2.1
 
+  echarts@5.6.0:
+    dependencies:
+      tslib: 2.3.0
+      zrender: 5.6.1
+
   electron-to-chromium@1.5.167: {}
 
   emoji-regex@8.0.0: {}
 
   emoji-regex@9.2.2: {}
 
+  engine.io-client@6.6.3:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-parser: 5.2.3
+      ws: 8.17.1
+      xmlhttprequest-ssl: 2.1.2
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  engine.io-parser@5.2.3: {}
+
   enhanced-resolve@5.18.1:
     dependencies:
       graceful-fs: 4.2.11
@@ -5954,6 +6037,24 @@ snapshots:
     dependencies:
       is-arrayish: 0.3.2
 
+  socket.io-client@4.8.1:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-client: 6.6.3
+      socket.io-parser: 4.2.4
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  socket.io-parser@4.2.4:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+    transitivePeerDependencies:
+      - supports-color
+
   source-map-js@1.2.1: {}
 
   source-map@0.5.7: {}
@@ -6053,6 +6154,8 @@ snapshots:
 
   tr46@0.0.3: {}
 
+  tslib@2.3.0: {}
+
   tslib@2.8.1: {}
 
   typeorm@0.3.24(babel-plugin-macros@3.1.0)(ioredis@5.6.1)(mysql2@3.14.1)(reflect-metadata@0.2.2):
@@ -6266,6 +6369,8 @@ snapshots:
       string-width: 5.1.2
       strip-ansi: 7.1.0
 
+  ws@8.17.1: {}
+
   ws@8.18.0: {}
 
   xml2js@0.6.2:
@@ -6275,6 +6380,8 @@ snapshots:
 
   xmlbuilder@11.0.1: {}
 
+  xmlhttprequest-ssl@2.1.2: {}
+
   y18n@5.0.8: {}
 
   yallist@3.1.1: {}
@@ -6318,3 +6425,7 @@ snapshots:
   zod@3.22.3: {}
 
   zod@3.25.64: {}
+
+  zrender@5.6.1:
+    dependencies:
+      tslib: 2.3.0

+ 1 - 1
src/client/mobile/components/Exam/ExamAdmin.tsx

@@ -4,7 +4,7 @@ import { Table, Button, message, Input, QRCode, Modal, Tabs } from 'antd';
 // import type { ColumnType } from 'antd/es/table';
 import type { GetProp , TableProps} from 'antd';
 import dayjs from 'dayjs';
-import { useSocketClient } from './hooks/useSocketClient.ts';
+import { useSocketClient } from './hooks/useSocketClient';
 import type {
   QuizState,
   ExamSocketRoomMessage

+ 3 - 3
src/client/mobile/components/Exam/ExamCard.tsx

@@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query';
 import { useSearchParams, useNavigate } from "react-router";
 import dayjs from 'dayjs';
 import { message } from 'antd';
-import { useSocketClient } from './hooks/useSocketClient.ts';
+import { useSocketClient } from './hooks/useSocketClient';
 import { ClassroomDataAPI } from '../../api/classroom_data.ts';
-import { ClassroomStatus } from '../../../share/types_stock.ts';
+import { ClassroomStatus } from '@/share/types_stock';
 import type { QuizState } from './types.ts';
 import type { AnswerRecord, Answer } from './types.ts';
-import { useAuth } from "../../hooks.tsx";
+import { useAuth } from '@/client/mobile/hooks/AuthProvider';
 
 
 

+ 1 - 2
src/client/mobile/components/Exam/ExamIndex.tsx

@@ -1,10 +1,9 @@
 import React, { useState, useCallback } from 'react';
 import { useNavigate } from "react-router";
-import type { ClassroomData } from '../../../share/types_stock.ts';
 import dayjs from 'dayjs';
 import { message } from 'antd';
 import { ClassroomDataAPI } from '../../api/classroom_data.ts';
-import { ClassroomStatus } from '../../../share/types_stock.ts';
+import { ClassroomStatus } from '@/share/types_stock';
 
 // 教室号输入页面
 function ExamIndex() {

+ 2 - 1
src/client/mobile/components/Exam/hooks/useSocketClient.ts

@@ -9,13 +9,14 @@ import type {
   Answer,
   CumulativeResult
 } from '../types.ts';
+import { useAuth } from '@/client/mobile/hooks/AuthProvider.js';
 
 interface FullExamSocketMessage extends Omit<ExamSocketMessage, 'timestamp'> {
   id: string;
   from: string;
   timestamp: string;
 }
-import { useAuth } from "../../../hooks.tsx";
+
 
 // 工具函数:统一错误处理
 const handleAsyncOperation = async <T>(

+ 11 - 11
src/client/mobile/components/stock/components/stock-chart/mod.ts

@@ -1,14 +1,14 @@
-import StockChart from "./src/components/StockChart.tsx";
-import MemoToggle from "./src/components/MemoToggle.tsx";
-import type { StockChartRef, StockChartProps } from "./src/components/StockChart.tsx";
-import type { TradeRecord } from "./src/types/index.ts";
-import { TradePanel } from './src/components/TradePanel.tsx';
-import { useTradeRecords } from './src/hooks/useTradeRecords.ts';
-import { useStockQueries } from './src/hooks/useStockQueries.ts';
-import { useProfitCalculator } from './src/hooks/useProfitCalculator.ts';
-import { ProfitDisplay } from './src/components/ProfitDisplay.tsx';
-import { useStockDataFilter } from './src/hooks/useStockDataFilter.ts';
-import { DrawingToolbar } from './src/components/DrawingToolbar.tsx';
+import StockChart from "./src/components/StockChart";
+import MemoToggle from "./src/components/MemoToggle";
+import type { StockChartRef, StockChartProps } from "./src/components/StockChart";
+import type { TradeRecord } from "./src/types/index";
+import { TradePanel } from './src/components/TradePanel';
+import { useTradeRecords } from './src/hooks/useTradeRecords';
+import { useStockQueries } from './src/hooks/useStockQueries';
+import { useProfitCalculator } from './src/hooks/useProfitCalculator';
+import { ProfitDisplay } from './src/components/ProfitDisplay';
+import { useStockDataFilter } from './src/hooks/useStockDataFilter';
+import { DrawingToolbar } from './src/components/DrawingToolbar';
 
 export { StockChart, MemoToggle, TradePanel, useTradeRecords, useStockQueries, useProfitCalculator, ProfitDisplay, useStockDataFilter, DrawingToolbar };
 export type { StockChartRef, StockChartProps, TradeRecord };

+ 1 - 1
src/client/mobile/components/stock/components/stock-chart/src/components/DrawingToolbar.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { ActiveType } from '../types/index.ts'
+import { ActiveType } from '../types/index'
 
 interface DrawingToolbarProps {
   onStartDrawing: (type: ActiveType) => void;

+ 1 - 1
src/client/mobile/components/stock/components/stock-chart/src/components/ProfitDisplay.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import type { ProfitSummary } from '../types/index.ts';
+import type { ProfitSummary } from '../types/index';
 
 interface ProfitDisplayProps {
   profitSummary: ProfitSummary;

+ 3 - 3
src/client/mobile/components/stock/components/stock-chart/src/components/StockChart.tsx

@@ -1,9 +1,9 @@
 import React, { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
 import * as echarts from 'echarts';
 import type { EChartsType, EChartsOption } from 'echarts';
-import { StockChart as StockChartLib } from '../lib/index.ts';
-import type { StockData, DateMemo, TradeRecord, ActiveType } from '../types/index.ts';
-import { ChartDrawingTools } from '../lib/drawing/ChartDrawingTools.ts';
+import { StockChart as StockChartLib } from '../lib/index';
+import type { StockData, DateMemo, TradeRecord, ActiveType } from '../types/index';
+import { ChartDrawingTools } from '../lib/drawing/ChartDrawingTools';
 
 // 将 StockChartRef 接口移到 Props 定义之前
 interface StockChartRef {

+ 1 - 1
src/client/mobile/components/stock/components/stock-chart/src/hooks/useProfitCalculator.ts

@@ -1,5 +1,5 @@
 import { useState, useCallback, useMemo } from 'react';
-import type { TradeRecord, DailyProfit, ProfitSummary, StockData } from '../types/index.ts';
+import type { TradeRecord, DailyProfit, ProfitSummary, StockData } from '../types/index';
 
 export function useProfitCalculator(stockData: StockData[], trades: TradeRecord[]) {
   const [currentDate, setCurrentDate] = useState<string>('');

+ 1 - 1
src/client/mobile/components/stock/components/stock-chart/src/hooks/useStockDataFilter.ts

@@ -1,5 +1,5 @@
 import { useState, useCallback } from 'react';
-import type { StockData } from '../types/index.ts';
+import type { StockData } from '../types/index';
 
 export function useStockDataFilter(fullData: StockData[]) {
   const [dayNum, setDayNum] = useState(120); // 默认120天

+ 2 - 2
src/client/mobile/components/stock/components/stock-chart/src/hooks/useStockQueries.ts

@@ -1,6 +1,6 @@
 import { useQuery } from '@tanstack/react-query';
-import { stockApi } from '../services/api.ts';
-import type { StockData, DateMemo } from '../types/index.ts';
+import { stockApi } from '../services/api';
+import type { StockData, DateMemo } from '../types/index';
 import { toast } from 'react-toastify';
 import { useEffect } from 'react';
 

+ 1 - 1
src/client/mobile/components/stock/components/stock-chart/src/hooks/useTradeRecords.ts

@@ -1,5 +1,5 @@
 import { useState, useCallback } from 'react';
-import type { TradeRecord, TradeRecordGroup, StockData } from '../types/index.ts';
+import type { TradeRecord, TradeRecordGroup, StockData } from '../types/index';
 
 export function useTradeRecords(stockData: StockData[]) {
   const [trades, setTrades] = useState<TradeRecord[]>([]);

+ 0 - 2
src/client/mobile/components/stock/components/stock-chart/src/hooks/useTradeSimulator.ts

@@ -1,2 +0,0 @@
-// ... 删除整个文件
- 

+ 1 - 1
src/client/mobile/components/stock/hooks/useStockSocketClient.ts

@@ -1,6 +1,6 @@
+import { useAuth } from '@/client/mobile/hooks/AuthProvider';
 import { useEffect, useState, useCallback } from 'react';
 import { io, Socket } from 'socket.io-client';
-import { useAuth } from '../../../hooks.tsx';
 
 interface ExamData {
   roomId: string;

+ 3 - 3
src/client/mobile/components/stock/stock_main.tsx

@@ -1,10 +1,10 @@
 import React, { useRef, useState, useCallback, useEffect } from 'react';
-import { useStockSocket } from './hooks/useStockSocketClient.ts';
+import { useStockSocket } from './hooks/useStockSocketClient';
 import { useSearchParams } from 'react-router';
 import { toast} from 'react-toastify';
-import { StockChart, MemoToggle, TradePanel, useTradeRecords, useStockQueries, useProfitCalculator, ProfitDisplay, useStockDataFilter, DrawingToolbar } from './components/stock-chart/mod.ts';
+import { StockChart, MemoToggle, TradePanel, useTradeRecords, useStockQueries, useProfitCalculator, ProfitDisplay, useStockDataFilter, DrawingToolbar } from './components/stock-chart/mod';
 import type { StockChartRef } from './components/stock-chart/mod.ts';
-import { ActiveType } from "./components/stock-chart/src/types/index.ts";
+import { ActiveType } from "./components/stock-chart/src/types/index";
 
 export function StockMain() {
   const chartRef = useRef<StockChartRef>(null);

+ 37 - 8
src/client/mobile/routes.tsx

@@ -12,6 +12,10 @@ import { ClassroomPage } from './pages/ClassroomPage';
 import { LoginPage } from './pages/Login';
 import StockHomePage from './pages/StockHomePage';
 import { XunlianPage } from './pages/XunlianPage';
+import { StockMain } from './components/stock/stock_main';
+import ExamIndex from './components/Exam/ExamIndex';
+import ExamAdmin from './components/Exam/ExamAdmin';
+import ExamCard from './components/Exam/ExamCard';
 
 export const router = createBrowserRouter([
   // {
@@ -37,14 +41,14 @@ export const router = createBrowserRouter([
       },
     ],
   },
-  {
-    path: '/mobile/classroom',
-    element: (
-      <ProtectedRoute>
-        <ClassroomPage />
-      </ProtectedRoute>
-    ),
-  },
+  // {
+  //   path: '/mobile/classroom',
+  //   element: (
+  //     <ProtectedRoute>
+  //       <ClassroomPage />
+  //     </ProtectedRoute>
+  //   ),
+  // },
   {
     path: '/mobile/xunlian',
     element: (
@@ -53,6 +57,31 @@ export const router = createBrowserRouter([
       </ProtectedRoute>
     ),
   },
+  {
+    path: '/mobile/classroom/:id?/:role?',
+    element: <ProtectedRoute><ClassroomPage /></ProtectedRoute>,
+    errorElement: <ErrorPage />
+  },
+  {
+    path: '/mobile/stock',
+    element: <ProtectedRoute><StockMain /></ProtectedRoute>,
+    errorElement: <ErrorPage />
+  },
+  {
+    path: '/mobile/exam',
+    element: <ProtectedRoute><ExamIndex /></ProtectedRoute>,
+    errorElement: <ErrorPage />
+  },
+  {
+    path: '/mobile/exam/:id',
+    element: <ProtectedRoute><ExamAdmin /></ProtectedRoute>,
+    errorElement: <ErrorPage />
+  },
+  {
+    path: '/mobile/exam/card',
+    element: <ProtectedRoute><ExamCard /></ProtectedRoute>,
+    errorElement: <ErrorPage />
+  },
   {
     path: '*',
     element: <NotFoundPage />,

+ 182 - 0
src/share/types_stock.ts

@@ -0,0 +1,182 @@
+// 教室状态枚举
+export enum ClassroomStatus {
+    CLOSED = 0,  // 关闭
+    OPEN = 1     // 开放
+  }
+  
+  // 教室状态中文映射
+  export const ClassroomStatusNameMap: Record<ClassroomStatus, string> = {
+    [ClassroomStatus.CLOSED]: '关闭',
+    [ClassroomStatus.OPEN]: '开放'
+  };
+  
+  // 教室数据接口
+  export interface ClassroomData {
+    /** 主键ID */
+    id: number;
+    
+    /** 教室号 */
+    classroom_no: string;
+    
+    /** 训练日期 */
+    training_date: string;
+    
+    /** 持股 */
+    holding_stock?: string;
+    
+    /** 持币 */
+    holding_cash?: string;
+    
+    /** 价格 */
+    price?: string;
+    
+    /** 代码 */
+    code?: string;
+    
+    /** 状态 */
+    status: ClassroomStatus;
+    
+    /** 备用字段 */
+    spare?: string;
+    
+    /** 提交用户ID */
+    submit_user?: number;
+    
+    /** 创建时间 */
+    created_at: string;
+    
+    /** 更新时间 */
+    updated_at: string;
+  }
+  
+  // 教室数据列表响应
+  export interface ClassroomDataListResponse {
+    data: ClassroomData[];
+    pagination: {
+      current: number;
+      pageSize: number;
+      total: number;
+      totalPages: number;
+    };
+  }
+  
+  // 日期备注接口
+  export interface DateNote {
+    /** 主键ID */
+    id: number;
+    
+    /** 股票代码 */
+    code: string;
+    
+    /** 备注日期 */
+    note_date: string;
+    
+    /** 备注内容 */
+    note: string;
+    
+    /** 创建时间 */
+    created_at: string;
+    
+    /** 更新时间 */
+    updated_at: string;
+  }
+  
+  // 日期备注列表响应
+  export interface DateNoteListResponse {
+    data: DateNote[];
+    pagination: {
+      current: number;
+      pageSize: number;
+      total: number;
+      totalPages: number;
+    };
+  }
+  
+  // 提交记录状态枚举
+  export enum SubmissionRecordStatus {
+    PENDING = 0,   // 待处理
+    APPROVED = 1,  // 已通过
+    REJECTED = 2   // 已拒绝
+  }
+  
+  // 提交记录状态中文映射
+  export const SubmissionRecordStatusNameMap: Record<SubmissionRecordStatus, string> = {
+    [SubmissionRecordStatus.PENDING]: '待处理',
+    [SubmissionRecordStatus.APPROVED]: '已通过',
+    [SubmissionRecordStatus.REJECTED]: '已拒绝'
+  };
+  
+  // 提交记录实体
+  export interface SubmissionRecord {
+    /** 主键ID */
+    id: number;
+    
+    /** 用户ID */
+    user_id: number;
+    
+    /** 用户昵称 */
+    nickname: string;
+    
+    /** 成绩 */
+    score: number;
+    
+    /** 代码 */
+    code: string;
+    
+    /** 训练日期 */
+    training_date: string;
+    
+    /** 标记 */
+    mark?: string;
+    
+    /** 状态 */
+    status: SubmissionRecordStatus;
+    
+    /** 创建时间 */
+    created_at: string;
+    
+    /** 更新时间 */
+    updated_at: string;
+  }
+  
+  // 提交记录列表响应
+  export interface SubmissionRecordListResponse {
+    data: SubmissionRecord[];
+    pagination: {
+      current: number;
+      pageSize: number;
+      total: number;
+      totalPages: number;
+    };
+  }
+  
+  // 训练代码实体
+  export interface XunlianCode {
+    /** 主键ID */
+    id: number;
+    
+    /** 股票代码 */
+    code: string;
+    
+    /** 股票名称 */
+    stock_name: string;
+    
+    /** 案例名称 */
+    name: string;
+    
+    /** 案例类型 */
+    type?: string;
+    
+    /** 案例描述 */
+    description?: string;
+    
+    /** 交易日期 */
+    trade_date: string;
+    
+    /** 创建时间 */
+    created_at: string;
+    
+    /** 更新时间 */
+    updated_at: string;
+  }
+