Преглед изворни кода

🗑️ chore(live): 移除直播相关组件和功能

- 删除直播课堂相关组件:AuthLayout、ClassroomLayout、ClassroomProvider
- 移除阿里云IM SDK类型定义文件
- 删除useClassroom钩子和相关业务逻辑
- 清理错误页面和受保护路由组件
- 移除考试系统相关组件:ExamAdmin、ExamCard、ExamIndex
- 删除股票图表组件及相关工具、类型定义
- 清理认证提供器和页面组件
- 移除路由配置和相关页面
yourname пре 6 месеци
родитељ
комит
4ba9db033c
47 измењених фајлова са 0 додато и 8127 уклоњено
  1. 0 17
      src/client/live/components/Classroom/AuthLayout.tsx
  2. 0 200
      src/client/live/components/Classroom/ClassroomLayout.tsx
  3. 0 38
      src/client/live/components/Classroom/ClassroomProvider.tsx
  4. 0 1515
      src/client/live/components/Classroom/alivc-im.iife.d.ts
  5. 0 942
      src/client/live/components/Classroom/useClassroom.ts
  6. 0 43
      src/client/live/components/ErrorPage.tsx
  7. 0 438
      src/client/live/components/Exam/ExamAdmin.tsx
  8. 0 360
      src/client/live/components/Exam/ExamCard.tsx
  9. 0 103
      src/client/live/components/Exam/ExamIndex.tsx
  10. 0 387
      src/client/live/components/Exam/hooks/useSocketClient.ts
  11. 0 66
      src/client/live/components/Exam/types.ts
  12. 0 26
      src/client/live/components/NotFoundPage.tsx
  13. 0 37
      src/client/live/components/ProtectedRoute.tsx
  14. 0 14
      src/client/live/components/stock/components/stock-chart/mod.ts
  15. 0 75
      src/client/live/components/stock/components/stock-chart/src/components/DrawingToolbar.tsx
  16. 0 26
      src/client/live/components/stock/components/stock-chart/src/components/MemoToggle.tsx
  17. 0 65
      src/client/live/components/stock/components/stock-chart/src/components/ProfitDisplay.tsx
  18. 0 246
      src/client/live/components/stock/components/stock-chart/src/components/StockChart.tsx
  19. 0 33
      src/client/live/components/stock/components/stock-chart/src/components/TradePanel.tsx
  20. 0 85
      src/client/live/components/stock/components/stock-chart/src/hooks/useProfitCalculator.ts
  21. 0 62
      src/client/live/components/stock/components/stock-chart/src/hooks/useStockDataFilter.ts
  22. 0 94
      src/client/live/components/stock/components/stock-chart/src/hooks/useStockQueries.ts
  23. 0 63
      src/client/live/components/stock/components/stock-chart/src/hooks/useTradeRecords.ts
  24. 0 77
      src/client/live/components/stock/components/stock-chart/src/lib/DateMemoHandler.ts
  25. 0 114
      src/client/live/components/stock/components/stock-chart/src/lib/StockChart.ts
  26. 0 163
      src/client/live/components/stock/components/stock-chart/src/lib/config/ChartBaseConfig.ts
  27. 0 9
      src/client/live/components/stock/components/stock-chart/src/lib/constants/colors.ts
  28. 0 67
      src/client/live/components/stock/components/stock-chart/src/lib/data/DataProcessor.ts
  29. 0 552
      src/client/live/components/stock/components/stock-chart/src/lib/drawing/ChartDrawingTools.ts
  30. 0 222
      src/client/live/components/stock/components/stock-chart/src/lib/drawing/DrawingTools.ts
  31. 0 3
      src/client/live/components/stock/components/stock-chart/src/lib/index.ts
  32. 0 131
      src/client/live/components/stock/components/stock-chart/src/lib/markers/MarkerProcessor.ts
  33. 0 36
      src/client/live/components/stock/components/stock-chart/src/services/api.ts
  34. 0 210
      src/client/live/components/stock/components/stock-chart/src/types/index.ts
  35. 0 96
      src/client/live/components/stock/hooks/useStockSocketClient.ts
  36. 0 250
      src/client/live/components/stock/stock_main.tsx
  37. 0 65
      src/client/live/components/stock/types/exam.ts
  38. 0 140
      src/client/live/hooks/AuthProvider.tsx
  39. 0 41
      src/client/live/index.tsx
  40. 0 14
      src/client/live/layouts/MainLayout.tsx
  41. 0 212
      src/client/live/pages/ClassroomPage.tsx
  42. 0 133
      src/client/live/pages/LoginPage.tsx
  43. 0 149
      src/client/live/pages/MemberPage.tsx
  44. 0 190
      src/client/live/pages/RegisterPage.tsx
  45. 0 92
      src/client/live/pages/StockHomePage.tsx
  46. 0 121
      src/client/live/pages/XunlianPage.tsx
  47. 0 105
      src/client/live/routes.tsx

+ 0 - 17
src/client/live/components/Classroom/AuthLayout.tsx

@@ -1,17 +0,0 @@
-import React, { ReactNode } from 'react';
-
-interface AuthLayoutProps {
-  children: ReactNode;
-}
-
-export const AuthLayout = ({ children }: AuthLayoutProps) => {
-  return (
-    <div className="flex flex-col h-screen bg-gray-100">
-      <div className="flex-1 flex items-center justify-center p-4">
-        <div className="w-full max-w-md bg-white rounded-lg shadow p-6">
-          {children}
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 200
src/client/live/components/Classroom/ClassroomLayout.tsx

@@ -1,200 +0,0 @@
-import React, { ReactNode } from 'react';
-import { Role } from './useClassroom';
-import { useClassroomContext } from './ClassroomProvider';
-import {
-  VideoCameraIcon,
-  CameraIcon,
-  MicrophoneIcon,
-  ShareIcon,
-  ClipboardDocumentIcon,
-  PaperAirplaneIcon
-} from '@heroicons/react/24/outline';
-
-interface ClassroomLayoutProps {
-  children: ReactNode;
-  role: Role;
-}
-
-export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
-  const [showVideo, setShowVideo] = React.useState(role !== Role.Teacher);
-  const [showShareLink, setShowShareLink] = React.useState(false);
-  const {
-    remoteScreenContainer,
-    remoteCameraContainer,
-    isCameraOn,
-    isAudioOn,
-    isScreenSharing,
-    toggleCamera,
-    toggleAudio,
-    toggleScreenShare,
-    messageList,
-    msgText,
-    setMsgText,
-    sendMessage,
-    handUpList,
-    questions,
-    classStatus,
-    shareLink,
-    showCameraOverlay,
-    setShowCameraOverlay
-  } = useClassroomContext();
-
-  return (
-    <div className="flex flex-col md:flex-row h-screen bg-gray-100">
-      {/* 视频区域 */}
-      {showVideo && (
-        <div className="relative h-[300px] md:flex-1 md:h-auto bg-black">
-          {/* 主屏幕共享容器 */}
-          <div
-            id="remoteScreenContainer"
-            ref={remoteScreenContainer}
-            className="w-full h-full"
-          >
-            {/* 屏幕共享视频将在这里动态添加 */}
-          </div>
-          
-          {/* 摄像头小窗容器 - 固定在右上角 */}
-          <div
-            id="remoteCameraContainer"
-            ref={remoteCameraContainer}
-            className={`absolute top-4 right-4 z-10 w-1/4 aspect-video ${
-              showCameraOverlay ? 'block' : 'hidden'
-            }`}
-          >
-            {/* 摄像头视频将在这里动态添加 */}
-          </div>
-          
-          {/* 摄像头小窗开关按钮 */}
-          <button
-            type="button"
-            onClick={() => setShowCameraOverlay(!showCameraOverlay)}
-            className={`absolute top-4 right-4 z-20 p-2 rounded-full ${
-              showCameraOverlay ? 'bg-green-500' : 'bg-gray-500'
-            } text-white`}
-            title={showCameraOverlay ? '隐藏摄像头小窗' : '显示摄像头小窗'}
-          >
-            <CameraIcon className="w-4 h-4" />
-          </button>
-        </div>
-      )}
-
-      {/* 消息和控制面板列 */}
-      <div className={`${showVideo ? 'w-full md:w-96 flex-1' : 'flex-1'} flex flex-col`}>
-        {/* 消息区域 */}
-        <div className="flex flex-col h-full">
-          {/* 消息列表 - 填充剩余空间 */}
-          <div className="flex-1 overflow-y-auto bg-white shadow-lg p-4">
-            {messageList.map((msg, i) => (
-              <div key={i} className="text-sm mb-1">{msg}</div>
-            ))}
-            </div>
-          </div>
-
-          {/* 底部固定区域 */}
-          <div className="bg-white shadow-lg p-4">
-            {/* 控制面板 */}
-            <div className="p-2 flex flex-col gap-3 mb-1 border-b border-gray-200">
-              <div className="flex flex-wrap gap-2">
-                {role === Role.Teacher && (
-                  <button
-                    type="button"
-                    onClick={() => setShowVideo(!showVideo)}
-                    className={`p-2 rounded-full ${showVideo ? 'bg-gray-500' : 'bg-gray-300'} text-white`}
-                    title={showVideo ? '隐藏视频' : '显示视频'}
-                  >
-                    <VideoCameraIcon className="w-4 h-4" />
-                  </button>
-                )}
-                <button
-                  type="button"
-                  onClick={toggleCamera}
-                  className={`p-2 rounded-full ${isCameraOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
-                  title={isCameraOn ? '关闭摄像头' : '开启摄像头'}
-                >
-                  <CameraIcon className="w-4 h-4" />
-                </button>
-                <button
-                  type="button"
-                  onClick={toggleAudio}
-                  className={`p-2 rounded-full ${isAudioOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
-                  title={isAudioOn ? '关闭麦克风' : '开启麦克风'}
-                >
-                  <MicrophoneIcon className="w-4 h-4" />
-                </button>
-                {role === Role.Teacher && (
-                  <button
-                    type="button"
-                    onClick={toggleScreenShare}
-                    className={`p-2 rounded-full ${isScreenSharing ? 'bg-green-500' : 'bg-blue-500'} text-white`}
-                    title={isScreenSharing ? '停止共享' : '共享屏幕'}
-                  >
-                    <ShareIcon className="w-4 h-4" />
-                  </button>
-                )}
-                {role === Role.Teacher && shareLink && (
-                  <button
-                    type="button"
-                    onClick={() => setShowShareLink(!showShareLink)}
-                    className="p-2 rounded-full bg-blue-500 text-white"
-                    title="分享链接"
-                  >
-                    <ClipboardDocumentIcon className="w-4 h-4" />
-                  </button>
-                )}
-              </div>
-
-              {showShareLink && shareLink && (
-                <div className="bg-blue-50 p-2 rounded">
-                  <div className="flex items-center gap-1">
-                    <input
-                      type="text"
-                      value={shareLink}
-                      readOnly
-                      className="flex-1 text-xs border rounded px-2 py-1 truncate"
-                    />
-                    <button
-                      type="button"
-                      onClick={() => navigator.clipboard.writeText(shareLink)}
-                      className="p-2 bg-blue-500 text-white rounded"
-                      title="复制链接"
-                    >
-                      <ClipboardDocumentIcon className="w-4 h-4" />
-                    </button>
-                  </div>
-                </div>
-              )}
-              
-              {/* 角色特定内容 */}
-              <div className="flex-1 overflow-y-auto">
-                {children}
-              </div>
-            </div>
-
-            {/* 消息输入框 */}
-            <div className="relative mt-2">
-              <textarea
-                value={msgText}
-                onChange={(e) => setMsgText(e.target.value)}
-                onKeyDown={(e) => {
-                  if (e.key === 'Enter' && !e.shiftKey) {
-                    e.preventDefault();
-                    sendMessage();
-                  }
-                }}
-                className="w-full border rounded px-2 py-1 pr-10"
-                placeholder="输入消息..."
-                rows={3}
-              />
-              <button
-                type="button"
-                onClick={sendMessage}
-                className="absolute right-2 bottom-2 p-1 bg-blue-500 text-white rounded-full"
-              >
-                <PaperAirplaneIcon className="w-4 h-4" />
-              </button>
-            </div>
-          </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 38
src/client/live/components/Classroom/ClassroomProvider.tsx

@@ -1,38 +0,0 @@
-import React, { useState, useEffect, useRef, createContext, useContext, useMemo } from 'react';
-import { useParams } from 'react-router';
-import { useClassroom , Role } from './useClassroom';
-import { User } from '../../hooks/AuthProvider';
-
-type ClassroomContextType = ReturnType<typeof useClassroom>;
-
-const ClassroomContext = createContext<ClassroomContextType | null>(null);
-
-export const ClassroomProvider: React.FC<{children: React.ReactNode, user: User}> = ({ children, user }) => {
-  const classroom = useClassroom({ user });
-
-  const { id: classId, role: pathRole } = useParams();
-
-  useEffect(() => {
-    if (classId) {
-      classroom.setClassId(classId);
-    }
-    
-    if (pathRole && (pathRole === Role.Teacher || pathRole === Role.Student)) {
-      classroom.setRole(pathRole === Role.Teacher ? Role.Teacher : Role.Student);
-    }
-  }, [classId, pathRole]);
-
-  return (
-    <ClassroomContext.Provider value={classroom}>
-      {children}
-    </ClassroomContext.Provider>
-  );
-};
-
-export const useClassroomContext = () => {
-  const context = useContext(ClassroomContext);
-  if (!context) {
-    throw new Error('useClassroomContext must be used within a ClassroomProvider');
-  }
-  return context;
-};

+ 0 - 1515
src/client/live/components/Classroom/alivc-im.iife.d.ts

@@ -1,1515 +0,0 @@
-declare namespace AliVCInteraction {
-  class AliVCIMAttachmentManager {
-    private static instance;
-    private wasmIns;
-    private attachmentManager;
-    private uploader;
-    constructor(wasmIns: any, wasmInterface: any);
-    static getInstance(wasmIns: any, wasmInterface: any): AliVCIMAttachmentManager;
-    getAttachmentReq(attachmentReq: ImAttachmentReq): Promise<any>;
-    /**
-     * 上传附件
-     * @param {string} reqId
-     * @param {ImAttachmentReq} attachmentReq
-     * @returns {ImAttachmentRes}
-     */
-    uploadAttachment(reqId: string, attachmentReq: ImAttachmentReq): Promise<ImAttachmentRes>;
-    /**
-     * 取消上传附件
-     * @param {string} reqId
-     * @returns
-     */
-    cancelAttachmentUpload(reqId: string): Promise<void>;
-    /**
-     * 删除已上传附件
-     * @param {ImAttachmentRes} attachment
-     * @returns
-     */
-    deleteAttachment(attachment: ImAttachmentRes): Promise<void>;
-    destroy(): void;
-}
-
-class AliVCIMGroupManager extends EventEmitter<ImGroupListener> {
-    private wasmIns;
-    private wasmGroupManager;
-    private groupListener;
-    constructor(wasmIns: any, wasmInterface: any);
-    addGroupListener(): void;
-    removeGroupListener(): void;
-    destroy(): void;
-    /**
-     * 创建群组,限管理员才能操作
-     * @param {ImCreateGroupReq} req
-     * @returns {Promise<ImCreateGroupRsp>}
-     */
-    createGroup(req: ImCreateGroupReq): Promise<ImCreateGroupRsp>;
-    /**
-     * 查询群组信息
-     * @param {string | ImQueryGroupReq} groupIdOrReq
-     * @returns {Promise<ImGroupInfo>}
-     */
-    queryGroup(groupIdOrReq: string | ImQueryGroupReq): Promise<ImGroupInfo>;
-    /**
-     * 关闭群组,限管理员才能操作
-     * @param {string | ImCloseGroupReq} groupIdOrReq
-     * @returns {Promise<void>}
-     */
-    closeGroup(groupIdOrReq: string | ImCloseGroupReq): Promise<void>;
-    /**
-     * 加入群组
-     * @param {string | ImJoinGroupReq} groupIdOrReq
-     * @returns {Promise<ImGroupInfo>}
-     */
-    joinGroup(groupIdOrReq: string | ImJoinGroupReq): Promise<ImGroupInfo>;
-    /**
-     * 离开群组
-     * @param {string | ImLeaveGroupReq} groupIdOrReq
-     * @returns {Promise<void>}
-     */
-    leaveGroup(groupIdOrReq: string | ImLeaveGroupReq): Promise<void>;
-    /**
-     * 修改群组信息
-     * @param {ImModifyGroupReq} req
-     * @returns {Promise<void>}
-     */
-    modifyGroup(req: ImModifyGroupReq): Promise<void>;
-    /**
-     * 查询最近组成员
-     * @param {string | ImListRecentGroupUserReq} groupIdOrReq
-     * @returns {Promise<ImListRecentGroupUserRsp>}
-     */
-    listRecentGroupUser(groupIdOrReq: string | ImListRecentGroupUserReq): Promise<ImListRecentGroupUserRsp>;
-    /**
-     * 查询群组成员,限管理员才能操作
-     * @param {string | ImListGroupUserReq} groupIdOrReq
-     * @returns {Promise<ImListGroupUserRsp>}
-     */
-    listGroupUser(groupIdOrReq: string | ImListGroupUserReq): Promise<ImListGroupUserRsp>;
-    /**
-     * 全体禁言,限管理员才能操作
-     * @param {string | ImMuteAllReq} groupIdOrReq
-     * @returns {Promise<void>}
-     */
-    muteAll(groupIdOrReq: string | ImMuteAllReq): Promise<void>;
-    /**
-     * 取消全体禁言,限管理员才能操作
-     * @param {string | ImCancelMuteAllReq} groupIdOrReq
-     * @returns {Promise<void>}
-     */
-    cancelMuteAll(groupIdOrReq: string | ImCancelMuteAllReq): Promise<void>;
-    /**
-     * 禁言指定用户,限管理员才能操作
-     * @param {ImMuteUserReq} req
-     * @returns {Promise<void>}
-     */
-    muteUser(req: ImMuteUserReq): Promise<void>;
-    /**
-     * 取消禁言指定用户,限管理员才能操作
-     * @param {ImCancelMuteUserReq} req
-     * @returns {Promise<void>}
-     */
-    cancelMuteUser(req: ImCancelMuteUserReq): Promise<void>;
-    /**
-     * 查询禁言用户列表,限管理员才能操作
-     * @param {string | ImListMuteUsersReq} groupIdOrReq
-     * @returns {Promise<ImListMuteUsersRsp>}
-     */
-    listMuteUsers(groupIdOrReq: string | ImListMuteUsersReq): Promise<ImListMuteUsersRsp>;
-}
-
-class AliVCIMMessageManager extends EventEmitter<ImMessageListener> {
-    private wasmIns;
-    private wasmMessageManager;
-    private messageListener;
-    private streamMessageManager;
-    constructor(wasmIns: any, wasmInterface: any);
-    addMessageListener(): void;
-    removeMessageListener(): void;
-    destroy(): void;
-    /**
-     * 发送单聊普通消息
-     * @param {ImSendMessageToUserReq} req
-     * @returns {string} messageId
-     */
-    sendC2cMessage(req: ImSendMessageToUserReq): Promise<string>;
-    /**
-     * 发送群聊普通消息
-     * @param {ImSendMessageToGroupReq} req
-     * @returns {string} messageId
-     */
-    sendGroupMessage(req: ImSendMessageToGroupReq): Promise<string>;
-    /**
-     * 查询消息列表
-     * @param {ImListMessageReq} req
-     * @returns {ImListMessageRsp}
-     */
-    listMessage(req: ImListMessageReq): Promise<ImListMessageRsp>;
-    /**
-     * 查询最近消息
-     * @param {string |ImListRecentMessageReq} groupIdOrReq
-     * @returns {ImListRecentMessageRsp}
-     */
-    listRecentMessage(groupIdOrReq: string | ImListRecentMessageReq): Promise<ImListRecentMessageRsp>;
-    /**
-     * 查询历史消息,该接口主要用户直播结束后的历史消息回放,用户无需进入群组可查询,比较耗时,在直播过程中不建议使用,另外该接口后续可能会收费。
-     * @param {ImListHistoryMessageReq} req
-     * @returns {ImListHistoryMessageRsp}
-     */
-    listHistoryMessage(req: ImListHistoryMessageReq): Promise<ImListHistoryMessageRsp>;
-    /**
-     * 删除/撤回群消息
-     */
-    deleteMessage(req: ImDeleteMessageReq): Promise<void>;
-    /**
-     * 创建流式消息
-     */
-    createStreamMessage(req: ImCreateStreamMessageReq): Promise<ImStreamMessageSender>;
-    /**
-     * 拒收流式消息
-     */
-    rejectStreamMessage(req: ImRejectStreamMessageReq): Promise<void>;
-    /**
-     * 自定义流式消息转发
-     */
-    forwardCustomMessage(req: ImForwardCustomMessageReq): Promise<ImForwardCustomMessageRsp>;
-}
-
-/**
- * Minimal `EventEmitter` interface that is molded against the Node.js
- * `EventEmitter` interface.
- */
-class EventEmitter<
-EventTypes extends EventEmitter.ValidEventTypes = string | symbol,
-Context extends any = any
-> {
-    static prefixed: string | boolean;
-
-    /**
-     * Return an array listing the events for which the emitter has registered
-     * listeners.
-     */
-    eventNames(): Array<EventEmitter.EventNames<EventTypes>>;
-
-    /**
-     * Return the listeners registered for a given event.
-     */
-    listeners<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T
-    ): Array<EventEmitter.EventListener<EventTypes, T>>;
-
-    /**
-     * Return the number of listeners listening to a given event.
-     */
-    listenerCount(event: EventEmitter.EventNames<EventTypes>): number;
-
-    /**
-     * Calls each of the listeners registered for a given event.
-     */
-    emit<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    ...args: EventEmitter.EventArgs<EventTypes, T>
-    ): boolean;
-
-    /**
-     * Add a listener for a given event.
-     */
-    on<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    fn: EventEmitter.EventListener<EventTypes, T>,
-    context?: Context
-    ): this;
-    addListener<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    fn: EventEmitter.EventListener<EventTypes, T>,
-    context?: Context
-    ): this;
-
-    /**
-     * Add a one-time listener for a given event.
-     */
-    once<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    fn: EventEmitter.EventListener<EventTypes, T>,
-    context?: Context
-    ): this;
-
-    /**
-     * Remove the listeners of a given event.
-     */
-    removeListener<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    fn?: EventEmitter.EventListener<EventTypes, T>,
-    context?: Context,
-    once?: boolean
-    ): this;
-    off<T extends EventEmitter.EventNames<EventTypes>>(
-    event: T,
-    fn?: EventEmitter.EventListener<EventTypes, T>,
-    context?: Context,
-    once?: boolean
-    ): this;
-
-    /**
-     * Remove all listeners, or those of the specified event.
-     */
-    removeAllListeners(event?: EventEmitter.EventNames<EventTypes>): this;
-}
-
-namespace EventEmitter {
-    interface ListenerFn<Args extends any[] = any[]> {
-        (...args: Args): void;
-    }
-
-    interface EventEmitterStatic {
-        new <
-        EventTypes extends ValidEventTypes = string | symbol,
-        Context = any
-        >(): EventEmitter<EventTypes, Context>;
-    }
-
-    /**
-     * `object` should be in either of the following forms:
-     * ```
-     * interface EventTypes {
-     *   'event-with-parameters': any[]
-     *   'event-with-example-handler': (...args: any[]) => void
-     * }
-     * ```
-     */
-    type ValidEventTypes = string | symbol | object;
-
-    type EventNames<T extends ValidEventTypes> = T extends string | symbol
-    ? T
-    : keyof T;
-
-    type ArgumentMap<T extends object> = {
-        [K in keyof T]: T[K] extends (...args: any[]) => void
-        ? Parameters<T[K]>
-        : T[K] extends any[]
-        ? T[K]
-        : any[];
-    };
-
-    type EventListener<
-    T extends ValidEventTypes,
-    K extends EventNames<T>
-    > = T extends string | symbol
-    ? (...args: any[]) => void
-    : (
-    ...args: ArgumentMap<Exclude<T, string | symbol>>[Extract<K, keyof T>]
-    ) => void;
-
-    type EventArgs<
-    T extends ValidEventTypes,
-    K extends EventNames<T>
-    > = Parameters<EventListener<T, K>>;
-
-    const EventEmitter: EventEmitterStatic;
-}
-
-interface ImAttachmentProgress {
-    progress: number;
-    totalSize: number;
-    currentSize: number;
-}
-
-interface ImAttachmentReq {
-    id: string;
-    fileType: ImAttachmentType;
-    fileName: string;
-    file?: File | Blob;
-    filePath?: string;
-    extra?: string;
-    onProgress?: (res: ImAttachmentProgress) => void;
-}
-
-interface ImAttachmentRes {
-    id: string;
-    fileType: ImAttachmentType;
-    fileSize: number;
-    fileName: string;
-    accessKey: string;
-    fileDuration: number;
-    extra: string;
-}
-
-enum ImAttachmentType {
-    IMAGE = 1,
-    AUDIO = 2,
-    VIDEO = 3,
-    OTHER = 4
-}
-
-interface ImAuth {
-    /**
-     * 随机数,格式:"AK-随机串", 最长64字节, 仅限A-Z,a-z,0-9及"_",可为空
-     */
-    nonce: string;
-    /**
-     * 过期时间:从1970到过期时间的秒数
-     */
-    timestamp: number;
-    /**
-     * 角色,为admin时,表示该用户可以调用管控接口,可为空,如果要给当前用户admin权限,应该传admin
-     */
-    role?: string;
-    /**
-     * token
-     */
-    token: string;
-}
-
-interface ImCancelMuteAllReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImCancelMuteUserReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param userList 被取消禁言的用户列表
-     */
-    userList: string[];
-}
-
-interface ImCloseGroupReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImCreateGroupReq {
-    /**
-     * @param groupId 群组id,【可选】id为空的话,会由sdk内部生成
-     */
-    groupId?: string;
-    /**
-     * @param groupName 群组名称
-     */
-    groupName: string;
-    /**
-     * @param extension 业务扩展字段
-     */
-    groupMeta?: string;
-}
-
-interface ImCreateGroupRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param alreadyExist 是否已经创建过
-     */
-    alreadyExist: boolean;
-}
-
-interface ImCreateStreamMessageReq {
-    /**
-     * 数据类型
-     */
-    dataType: ImStreamMessageDataType;
-    /**
-     * 数据接收类型
-     */
-    receiverType: ImStreamMessageReceiverType;
-    /**
-     * 数据接收者ID
-     */
-    receiverId: string;
-}
-
-interface ImDeleteMessageReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param messageId 消息id
-     */
-    messageId: string;
-}
-
-let ImEngine: typeof ImEngine_2;
-
-class ImEngine_2 extends EventEmitter<ImSdkListener> {
-    private wasmIns;
-    private wasmEngine;
-    private wasmInterface;
-    private transport?;
-    private appEventManager?;
-    private eventListener;
-    private messageManager?;
-    private groupManager?;
-    private attachmentManager?;
-    private pluginProvider?;
-    private uploader;
-    private supportsWebRtc;
-    private supportWASM;
-    private initFlag;
-    constructor();
-    static engine: ImEngine_2;
-    /**
-     * @brief 获取 SDK 引擎实例(单例)
-     * @returns ImEngine
-     */
-    static createEngine(): ImEngine_2;
-    /**
-     * 当前 SDK 是否支持,支持 WASM 或者 ASM
-     * @returns
-     */
-    static isSupport(): boolean;
-    static getSdkVersion(): string;
-    private initTransport;
-    private initAppEvent;
-    private loadWasm;
-    private preloadUploader;
-    private initNativePlugin;
-    /**
-     * @brief 初始化
-     * @param config SDK配置信息
-     */
-    init(config: ImSdkConfig): Promise<0 | ImErrors.ERROR_CLIENT_REPEATED_INIT>;
-    /**
-     * 添加 Engine 事件监听
-     */
-    private addEventListener;
-    private removeEventListener;
-    private destroy;
-    /**
-     * @brief 销毁
-     */
-    unInit(): boolean;
-    /**
-     * @brief 登录
-     * @param req 登录请求数据
-     */
-    login(loginReq: ImLoginReq): Promise<void>;
-    /**
-     * @brief 登出
-     */
-    logout(): Promise<void>;
-    /**
-     * 强制重连
-     */
-    reconnect(): void;
-    /**
-     * @brief 获取当前登录用户 ID
-     */
-    getCurrentUserId(): string;
-    /**
-     * @brief 是否登录
-     */
-    isLogin(): boolean;
-    /**
-     * @brief 是否已退出登录
-     */
-    isLogout(): boolean;
-    /**
-     * @brief 获取消息管理器 {AliVCIMMessageInterface}
-     * @return 返回消息管理器实例
-     */
-    getMessageManager(): AliVCIMMessageManager | undefined;
-    /**
-     * @brief 获取群组管理器 {AliVCIMGroupInterface}
-     * @return 返回群组管理器实例
-     */
-    getGroupManager(): AliVCIMGroupManager | undefined;
-    /**
-     * @brief 获取附件管理器 {AliVCIMAttachmentInterface}
-     * @return 返回附件管理器实例
-     */
-    getAttachmentManager(): AliVCIMAttachmentManager | undefined;
-}
-
-enum ImErrors {
-    /**
-     * 已经登录
-     */
-    ERROR_HAS_LOGIN = 304,
-    /**
-     * 参数错误;参数无法解析
-     */
-    ERROR_INVALID_PARAM = 400,
-    /**
-     * 错误码(subcode)	说明
-     * 403	操作无权限; 或登录时鉴权失败
-     */
-    ERROR_NO_PERMISSION = 403,
-    /**
-     * no session,可能因为客户网络变化等原因导致的连接变化,服务器在新连接上收到消息无法正常处理,需要reconnect 信令。
-     */
-    ERROR_NO_SESSION = 404,
-    /**
-     * 审核不通过
-     */
-    ERROR_AUDIT_FAIL = 406,
-    /**
-     * 繁忙,发送太快,稍候重试
-     * 服务端同学确认不需要区分这两个错误
-     */
-    ERROR_INTERNAL_BUSY = 412,
-    ERROR_INTERNAL_BUSY2 = 413,
-    /**
-     * 发送 c2c 消息对方用户不在线
-     */
-    ERROR_USER_OFFLINE = 424,
-    /**
-     * 未加入群组
-     */
-    ERROR_GROUP_NOT_JOINED = 425,
-    /**
-     * 操作过快,短时间内,发起过多请求。如同一个用户,1秒内发起2次登录。
-     */
-    ERROR_INTERNAL_BUSY3 = 429,
-    /**
-     * 群组不存在
-     */
-    ERROR_GROUP_NOT_EXIST = 440,
-    /**
-     * 群组已删除
-     */
-    ERROR_GROUP_DELETED = 441,
-    /**
-     * 无法在该群组中发送消息,被禁言
-     */
-    ERROR_SEND_GROUP_MSG_FAIL = 442,
-    /**
-     * 进了太多的群组, 列表人数超大等
-     */
-    ERROR_REACH_MAX = 443,
-    /**
-     * 无法加入该群,被禁止加入(暂无需求未实现)预留
-     */
-    ERROR_JOIN_GROUP_FAIL = 450,
-    /**
-     * ots 查询错误
-     */
-    ERROR_OTS_FAIL = 480,
-    /**
-     * 系统临时错误,稍候重试
-     */
-    ERROR_INTERNALE_RROR = 500,
-    /**
-     * 底层重复初始化
-     */
-    ERROR_CLIENT_REPEATED_INIT = -1,
-    /**
-     * 初始化配置信息有误
-     */
-    ERROR_CLIENT_INIT_INVALID_PARAM = -2,
-    /**
-     * 未初始化
-     */
-    ERROR_CLIENT_NOT_INIT = 1,
-    /**
-     * 参数异常
-     */
-    ERROR_CLIENT_INVALID_PARAM = 2,
-    /**
-     * 状态有误
-     */
-    ERROR_CLIENT_INVALID_STATE = 3,
-    /**
-     * 建连失败
-     */
-    ERROR_CLIENT_CONNECT_ERROR = 4,
-    /**
-     * 建连超时
-     */
-    ERROR_CLIENT_CONNECT_TIMEOUT = 5,
-    /**
-     * 发送失败
-     */
-    ERROR_CLIENT_SEND_FAILED = 6,
-    /**
-     * 发送取消
-     */
-    ERROR_CLIENT_SEND_CANCEL = 7,
-    /**
-     * 发送超时
-     */
-    ERROR_CLIENT_SEND_TIMEOUT = 8,
-    /**
-     * 订阅失败
-     */
-    ERROR_CLIENT_SUB_ERROR = 9,
-    /**
-     * 订阅通道断连
-     */
-    ERROR_CLIENT_SUB_DISCONNECT = 10,
-    /**
-     * 订阅超时
-     */
-    ERROR_CLIENT_SUB_TIMEOUT = 11,
-    /**
-     * 压缩失败
-     */
-    ERROR_CLIENT_COMPRESS_ERROR = 12,
-    /**
-     * 解压失败
-     */
-    ERROR_CLIENT_DECOMPRESS_ERROR = 13,
-    /**
-     * 加密失败
-     */
-    ERROR_CLIENT_ENCRYPT_ERROR = 14,
-    /**
-     * 解密失败
-     */
-    ERROR_CLIENT_DECRYPT_ERROR = 15,
-    /**
-     * 消息体封装失败
-     */
-    ERROR_CLIENT_CONVERTER_ERROR = 16,
-    /**
-     * 消息体解析失败
-     */
-    ERROR_CLIENT_PARSE_ERROR = 17,
-    /**
-     * 数据为空
-     */
-    ERROR_CLIENT_DATA_EMPTY = 18,
-    /**
-     * 数据错误
-     */
-    ERROR_CLIENT_DATA_ERROR = 19,
-    /**
-     * 地址出错(可能是AppSign有误,如头部带了空格、内容被截断等)
-     */
-    ERROR_CLIENT_URL_ERROR = 20,
-    /**
-     * 建连取消
-     */
-    CONNECT_CANCEL = 21,
-    /**
-     * 重试超过次数限制
-     */
-    RETRY_OVER_TIME = 22,
-    /**
-     * 状态错误
-     */
-    ERROR_INVALID_STATE = 601,
-    /**
-     * 未登录
-     */
-    ERROR_NOT_LOGIN = 602,
-    /**
-     * 收到上次session的消息
-     */
-    ERROR_RECEIVE_LAST_SESSION = 603,
-    /**
-     * Parse Data Error
-     */
-    ERROR_PARSE_DATA_ERROR = 604
-}
-
-interface ImForwardCustomMessageReq {
-    /**
-     * 数据接收者ID
-     */
-    receiverId: string;
-    /**
-     * 数据,若需要传对象,需要序列化
-     */
-    data: string;
-}
-
-interface ImForwardCustomMessageRsp {
-    /**
-     * 流式消息ID
-     */
-    messageId: string;
-    /**
-     * 数据,若返回的是对象,需要反序列化
-     */
-    data: string;
-}
-
-interface ImGroupInfo {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param groupName 群组名称
-     */
-    groupName: string;
-    /**
-     * @param groupMeta 群组透传信息
-     */
-    groupMeta: string;
-    /**
-     * @param createTime 创建时间
-     */
-    createTime: number;
-    /**
-     * @param creator 创建者id
-     */
-    creator: string;
-    /**
-     * @param admins 管理员列表
-     */
-    admins: string[];
-    /**
-     * @param statistics 群组统计
-     */
-    statistics: ImGroupStatistics;
-    /**
-     * @param muteStatus 群禁言信息
-     */
-    muteStatus: ImGroupMuteStatus;
-}
-
-interface ImGroupInfoStatus {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param groupMeta 群组扩展信息
-     */
-    groupMeta: string;
-    /**
-     * @param adminList 管理员列表
-     */
-    adminList: string[];
-}
-
-interface ImGroupListener {
-    /**
-     * @deprecated 1.4.1 后请使用 memberdatachange 事件
-     *
-     * 群组成员变化
-     * @param groupId  群组ID
-     * @param memberCount 当前群组人数
-     * @param joinUsers 加入的用户
-     * @param leaveUsers 离开的用户
-     */
-    memberchange: (groupId: string, memberCount: number, joinUsers: ImUser[], leaveUsers: ImUser[]) => void;
-    /**
-     * 1.4.1 版本新增新的群组成员变化,返回的是一个对象
-     * @param data 群组成员变化数据对象
-     * @param data.groupId  群组ID
-     * @param data.onlineCount 当前群组在线人数
-     * @param data.pv  加入群组累积pv数
-     * @param data.isBigGroup 是否是大群组
-     * @param data.joinUsers 加入的用户
-     * @param data.leaveUsers 离开的用户
-     */
-    memberdatachange: (data: ImMemberChangeData) => void;
-    /**
-     * 退出群组
-     * @param groupId  群组ID
-     * @param reason 退出原因 1: 群被解散, 2:被踢出来了
-     */
-    exit: (groupId: string, reason: number) => void;
-    /**
-     * 群组静音状态变化
-     * @param groupId  群组ID
-     * @param status 静音状态
-     */
-    mutechange: (groupId: string, status: ImGroupMuteStatus) => void;
-    /**
-     * 群组信息变化
-     * @param groupId  群组ID
-     * @param info 群组信息
-     */
-    infochange: (groupId: string, info: ImGroupInfoStatus) => void;
-}
-
-interface ImGroupMuteStatus {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param muteAll 是否全员禁言
-     */
-    muteAll: boolean;
-    /**
-     * @param muteUserList 禁言用户ID列表
-     */
-    muteUserList: string[];
-    /**
-     * @param whiteUserList 白名单用户ID列表
-     */
-    whiteUserList: string[];
-}
-
-interface ImGroupStatistics {
-    /**
-     * @param pv PV
-     */
-    pv: number;
-    /**
-     * @param onlineCount 在线人数
-     */
-    onlineCount: number;
-    /**
-     * @param msgAmount 消息数量
-     */
-    msgAmount: {
-        [key: string]: number;
-    };
-}
-
-interface ImJoinGroupReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImLeaveGroupReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImListGroupUserReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param sortType 排序方式,ASC-先加入优先,DESC-后加入优先
-     */
-    sortType?: ImSortType;
-    /**
-     * @param nextPageToken 默认表示第一页,遍历时服务端会返回,客户端获取下一页时,应带上
-     */
-    nextPageToken?: number;
-    /**
-     * @deprecated 请使用 nextPageToken
-     */
-    nextpagetoken?: number;
-    /**
-     * @param pageSize 最大不超过50
-     */
-    pageSize?: number;
-}
-
-interface ImListGroupUserRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param nextPageToken 下一页的token
-     */
-    nextPageToken: number;
-    /**
-     * @deprecated 请使用 nextPageToken
-     */
-    nextpagetoken?: number;
-    /**
-     * @param hasMore 是否还有下一页
-     */
-    hasMore: boolean;
-    /**
-     * @param userList 返回的群组的在线成员列表
-     */
-    userList: ImUser[];
-}
-
-interface ImListHistoryMessageReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param type 消息类型
-     */
-    type: number;
-    /**
-     * @param nextPageToken 不传时表示第一页,遍历时服务端会返回,客户端获取下一页时,应带上
-     */
-    nextPageToken?: number;
-    /**
-     * @param sortType 排序类型,默认为时间递增
-     */
-    sortType?: ImSortType;
-    /**
-     * @param pageSize 取值范围 10~30
-     */
-    pageSize?: number;
-    /**
-     * @param begintime 按时间范围遍历,开始时间,不传时表示最早时间,单位:秒
-     */
-    beginTime?: number;
-    /**
-     * @param endtime 按时间范围遍历,结束时间,不传时表示最晚时间,单位:秒
-     */
-    endTime?: number;
-}
-
-interface ImListHistoryMessageRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param nextPageToken 不传时表示第一页,遍历时服务端会返回,客户端获取下一页时,应带上
-     */
-    nextPageToken?: number;
-    /**
-     *@param hasMore 是否有更多数据
-     */
-    hasMore: boolean;
-    /**
-     *@param messageList 返回消息列表
-     **/
-    messageList: ImMessage[];
-}
-
-interface ImListMessageReq {
-    /**
-     * @param groupId 话题id,聊天插件实例id
-     */
-    groupId: string;
-    /**
-     * @param type 消息类型
-     */
-    type: number;
-    /**
-     * @param nextPageToken 不传时表示第一页,遍历时服务端会返回,客户端获取下一页时应带上
-     */
-    nextPageToken?: number;
-    /**
-     * @deprecated 请使用nextPageToken
-     */
-    nextpagetoken?: number;
-    /**
-     * @param sortType 排序类型,默认为时间递增
-     */
-    sortType?: ImSortType;
-    /**
-     * @param pageSize 分页拉取的大小,默认10条,最大30条
-     */
-    pageSize?: number;
-    /**
-     * @param begintime 按时间范围遍历,开始时间,不传时表示最早时间,单位:秒
-     */
-    beginTime?: number;
-    /**
-     * @param endtime 按时间范围遍历,结束时间,不传时表示最晚时间,单位:秒
-     */
-    endTime?: number;
-}
-
-interface ImListMessageRsp {
-    /**
-     ** @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     *@param nextpagetoken 客户端获取下一页时,应带上
-     */
-    nextPageToken: number;
-    /**
-     * @deprecated 请使用 nextPageToken
-     */
-    nextpagetoken?: number;
-    /**
-     *@param hasmore 是否有更多数据
-     */
-    hasMore: boolean;
-    /**
-     *@param messageList 返回消息列表
-     **/
-    messageList: ImMessage[];
-}
-
-interface ImListMuteUsersReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImListMuteUsersRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param muteAll 是否全员禁言
-     */
-    muteAll: boolean;
-    /**
-     * @param muteUserList 禁言用户ID列表
-     */
-    muteUserList: string[];
-    /**
-     * @param whiteUserList 白名单用户ID列表
-     */
-    whiteUserList: string[];
-}
-
-interface ImListRecentGroupUserReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImListRecentGroupUserRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param total 群组成员总数
-     */
-    total: number;
-    /**
-     * @param userList 返回的群组的在线成员列表
-     */
-    userList: ImUser[];
-}
-
-interface ImListRecentMessageReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param seqnum 消息序列号
-     */
-    seqnum?: number;
-    /**
-     * @param pageSize 分页拉取的大小,默认50条
-     */
-    pageSize?: number;
-}
-
-interface ImListRecentMessageRsp {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param messageList 返回消息列表
-     */
-    messageList: ImMessage[];
-}
-
-interface ImLoginReq {
-    user: ImUser;
-    /**
-     * 用户鉴权信息
-     */
-    userAuth: ImAuth;
-}
-
-enum ImLogLevel {
-    NONE = 0,
-    DBUG = 1,
-    INFO = 2,
-    WARN = 3,
-    ERROR = 4
-}
-
-interface ImMemberChangeData {
-    groupId: string;
-    onlineCount: number;
-    pv: number;
-    isBigGroup: boolean;
-    joinUsers: ImUser[];
-    leaveUsers: ImUser[];
-}
-
-interface ImMessage {
-    /**
-     * @param groupId 话题id,聊天插件实例id
-     */
-    groupId?: string;
-    /**
-     * @param messageId 消息id
-     */
-    messageId: string;
-    /**
-     *@param type 消息类型。系统消息小于10000
-     */
-    type: number;
-    /**
-     *@param sender 发送者
-     */
-    sender?: ImUser;
-    /**
-     **@param data 消息内容
-     */
-    data: string;
-    /**
-     *@param seqnum 消息顺序号
-     */
-    seqnum: number;
-    /**
-     *@param timestamp 消息发送时间
-     */
-    timestamp: number;
-    /**
-     *@param level 消息分级
-     **/
-    level: ImMessageLevel;
-    /**
-     * @param repeatCount 消息统计数量增长值,默认1,主要用于聚合同类型消息。
-     */
-    repeatCount: number;
-    /**
-     * @param totalMsgs 同类型的消息数量
-     */
-    totalMsgs: number;
-}
-
-enum ImMessageLevel {
-    NORMAL = 0,
-    HIGH = 1
-}
-
-interface ImMessageListener {
-    /**
-     * 接收到c2c消息
-     * @param msg 消息
-     */
-    recvc2cmessage: (msg: ImMessage) => void;
-    /**
-     * 接收到群消息
-     * @param msg 消息
-     * @param groupId 群id
-     */
-    recvgroupmessage: (msg: ImMessage, groupId: string) => void;
-    /**
-     * 删除群消息
-     * @param msgId 消息id
-     * @param groupId 群id
-     */
-    deletegroupmessage: (msgId: string, groupId: string) => void;
-    /**
-     * 流消息结束通知
-     * @param {string} messageId 消息ID
-     * @param {number} endCode 结束原因:0正常处理结束,1主动取消,2与智能体服务连接异常断开,3连接超时断开,4收到新的开始切片,5包请求异常
-     * @param {number} [subCode] 详情码
-     * @param {string} [subMsg]  详情信息
-     */
-    streammessageend: (messageId: string, endCode: number, subCode?: number, subMsg?: string) => void;
-    /**
-     * 接收到流消息
-     * @param message 流消息
-     */
-    recvstreammessage: (message: ImStreamMessage) => void;
-}
-
-interface ImModifyGroupReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param forceUpdateGroupMeta 为true表示强制刷新groupMeta信息,若groupMeta为空则表示清空;
-     *                             为false,则只有groupMeta不空才更新groupMeta信息
-     */
-    forceUpdateGroupMeta?: boolean;
-    /**
-     * @param groupMeta 群信息扩展字段
-     */
-    groupMeta?: string;
-    /**
-     * @param forceUpdateAdmins 为true表示强制刷新admins信息,若admins为空则表示清空;
-     *                          为false,则只有admins不空才更新admins信息
-     */
-    forceUpdateAdmins?: boolean;
-    /**
-     * @param admins 群管理员ID列表,最多设置3个管理员
-     */
-    admins?: string[];
-}
-
-interface ImMuteAllReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImMuteUserReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-    /**
-     * @param userList 需要禁言的用户列表
-     */
-    userList: string[];
-}
-
-interface ImQueryGroupReq {
-    /**
-     * @param groupId 群组id
-     */
-    groupId: string;
-}
-
-interface ImRejectStreamMessageReq {
-    /**
-     * 流式消息ID
-     */
-    messageId: string;
-    /**
-     * 数据接收类型
-     */
-    receiverType: ImStreamMessageReceiverType;
-    /**
-     * 错误码
-     */
-    code: number;
-    /**
-     * 错误信息
-     */
-    msg: string;
-}
-
-interface ImSdkConfig {
-    /**
-     * 设备唯一标识
-     */
-    deviceId?: string;
-    /**
-     * 应用ID
-     */
-    appId: string;
-    /**
-     * 应用签名
-     */
-    appSign: string;
-    /**
-     * 日志级别
-     */
-    logLevel?: ImLogLevel;
-    /**
-     * 来源
-     */
-    source?: string;
-    /**
-     * 心跳超时时间,单位是秒,默认 99s,允许 [15-120]s
-     */
-    heartbeatTimeout?: number;
-    /**
-     * @param extra 用户自定义参数
-     */
-    extra?: {
-        [key: string]: string;
-    };
-    /**
-     * @param uploader 附件上传器参数
-     */
-    uploader?: {
-        /**
-         * 是否提前加载,默认 false
-         */
-        preload?: boolean;
-        /**
-         * 指定sdk文件地址
-         */
-        sdkUrl?: string;
-    };
-}
-
-interface ImSdkListener {
-    /**
-     * 连接中
-     */
-    connecting: () => void;
-    /**
-     * 连接成功
-     */
-    connectsuccess: () => void;
-    /**
-     * 连接失败
-     */
-    connectfailed: (error: Error) => void;
-    /**
-     * 连接断开
-     * @param code 断开原因 1:主动退出, 2:被踢出 3:超时等其他原因 4:在其他端上登录
-     */
-    disconnect: (code: number) => void;
-    /**
-     * 连接状态变化
-     * state 状态 0:未连接 1:连接中 2:已连接 3:已断联
-     */
-    linkstate: (data: {
-        previousState: number;
-        currentState: number;
-    }) => void;
-    /**
-     * token过期
-     * @param callback 更新 Token 的回调
-     */
-    tokenexpired: (callback: TokenCallback) => void;
-    /**
-     * 重连成功
-     */
-    reconnectsuccess: (groupInfos: ImGroupInfo[]) => void;
-}
-
-interface ImSendMessageToGroupReq {
-    /**
-     * @param groupId 话题id,聊天插件实例id
-     */
-    groupId: string;
-    /**
-     * @param type 消息类型,小于等于10000位系统消息,大于10000位自定义消息
-     */
-    type: number;
-    /**
-     * @param data 消息体
-     */
-    data: string;
-    /**
-     * @param skipMuteCheck 跳过禁言检测,true:忽略被禁言用户,还可发消息;false:当被禁言时,消息无法发送,默认为false,即为不跳过禁言检测。
-     */
-    skipMuteCheck?: boolean;
-    /**
-     * @param skipAudit 跳过安全审核,true:发送的消息不经过阿里云安全审核服务审核;false:发送的消息经过阿里云安全审核服务审核,审核失败则不发送;
-     */
-    skipAudit?: boolean;
-    /**
-     * @param level 消息分级
-     */
-    level?: ImMessageLevel;
-    /**
-     * @param noStorage 为true时,表示该消息不需要存储,也无法拉取查询
-     */
-    noStorage?: boolean;
-    /**
-     * @param repeatCount 消息统计数量增长值,默认1,主要用于聚合同类型消息,只发送一次请求,例如点赞场景
-     */
-    repeatCount?: number;
-}
-
-interface ImSendMessageToUserReq {
-    /**
-     * 消息类型。系统消息小于10000
-     */
-    type: number;
-    /**
-     * 消息体
-     */
-    data: string;
-    /**
-     * 接收者用户
-     */
-    receiverId: string;
-    /**
-     * 跳过安全审核,true:发送的消息不经过阿里云安全审核服务审核;false:发送的消息经过阿里云安全审核服务审核,审核失败则不发送;
-     */
-    skipAudit?: boolean;
-    /**
-     * 消息分级
-     */
-    level?: ImMessageLevel;
-}
-
-enum ImSortType {
-    ASC = 0,
-    DESC = 1
-}
-
-interface ImStreamData {
-    seqNum: number;
-    byteData: ArrayBuffer;
-}
-
-interface ImStreamMessage {
-    /**
-     * 流式消息ID
-     */
-    messageId: string;
-    /**
-     * 发送用户
-     */
-    sender: ImUser;
-    /**
-     * 流式消息帧数据
-     */
-    data: ImStreamData;
-}
-
-enum ImStreamMessageDataType {
-    TEXT = 1,// 文本
-    BINARY_FILE = 2
-}
-
-enum ImStreamMessageReceiverType {
-    SERVER = 0
-}
-
-class ImStreamMessageSender extends EventEmitter<ImStreamMessageSenderListener> {
-    private wasmIns;
-    private sender;
-    constructor(wasmIns: any);
-    setSender(sender: any): void;
-    getMessageId(): string;
-    /**
-     * 发送字节数据
-     * @param {Uint8Array} byteData 字节数据
-     * @param {boolean} isLast 是否结束流
-     * @param {ImAttachmentRes[]} [attachments] 附件列表
-     */
-    sendByteData(byteData: Uint8Array, isLast: boolean, attachments?: ImAttachmentRes[]): void;
-    /**
-     * 取消发送
-     * @param {number} [code] 取消码
-     * @param {string} [msg] 取消原因
-     */
-    cancel(code?: number, msg?: string): void;
-    destroy(): void;
-}
-
-interface ImStreamMessageSenderListener {
-    /**
-     * 流消息结束通知
-     * @param {string} messageId 消息ID
-     * @param {number} endCode 结束原因:0正常处理结束,1主动取消,2与智能体服务连接异常断开,3连接超时断开,4收到新的开始切片,5包请求异常
-     * @param {number} [subCode] 详情码
-     * @param {string} [subMsg]  详情信息
-     */
-    streammessageend: (messageId: string, endCode: number, subCode?: number, subMsg?: string) => void;
-}
-
-enum ImStreamMessageStatus {
-    CONTINUE = 0,// 中间帧
-    START = 1,// 开始帧
-    END = 2,// 结束帧
-    ALL = 3,// 一次性传输
-    CANCEL = 4
-}
-
-enum ImStreamMessageType {
-    NORMAL = 0
-}
-
-interface ImUser {
-    /**
-     * @param user_id 用户id
-     */
-    userId: string;
-    /**
-     * @param user_extension 用户扩展信息
-     */
-    userExtension?: string;
-}
-
-type TokenCallback = (error: {
-    code?: number;
-    msg: string;
-} | null, auth?: ImAuth) => void;
-}

+ 0 - 942
src/client/live/components/Classroom/useClassroom.ts

@@ -1,942 +0,0 @@
-import { useState, useEffect, useRef } from 'react';
-import { useParams } from 'react-router';
-// import { ClassroomAPI } from '../../api/index.ts';
-// @ts-types="../../../share/aliyun-rtc-sdk.d.ts"
-// @ts-types="./alivc-im.iife.d.ts"
-import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
-import { toast } from 'react-toastify';
-import { User } from '../../hooks/AuthProvider';
-import { aliyunClient } from '@/client/api';
-import { UserType } from '@/server/modules/users/user.enum';
-export enum Role {
-  Teacher = UserType.TEACHER,
-  Student = UserType.STUDENT
-}
-
-// 从SDK中提取需要的类型和枚举
-type ImEngine = InstanceType<typeof AliVCInteraction.ImEngine>;
-type ImGroupManager = AliVCInteraction.AliVCIMGroupManager;
-type ImMessageManager = AliVCInteraction.AliVCIMMessageManager;
-type ImLogLevel = AliVCInteraction.ImLogLevel;
-type ImMessageLevel = AliVCInteraction.ImMessageLevel;
-const { ERROR } = AliVCInteraction.ImLogLevel;
-const { NORMAL, HIGH } = AliVCInteraction.ImMessageLevel;
-
-interface ImUser {
-  userId: string;
-  userExtension?: string;
-}
-
-interface ImGroupMessage {
-  groupId: string;
-  type: number;
-  data: string;
-  sender?: ImUser;
-  timestamp?: number;
-}
-
-// 互动消息类型
-enum InteractionAction {
-  HandUp = 'hand_up',
-  CancelHandUp = 'cancel_hand_up',
-  AnswerHandUp = 'answer_hand_up'
-}
-
-interface InteractionMessage {
-  action: InteractionAction;
-  studentId: string;
-  studentName?: string;
-  timestamp?: number;
-  question?: string;
-}
-
-interface HandUpRequest {
-  studentId: string;
-  studentName?: string;
-  timestamp: number;
-  question?: string;
-}
-
-interface Question {
-  studentId: string;
-  studentName?: string;
-  question: string;
-  timestamp: number;
-}
-
-export enum ClassStatus {
-  NOT_STARTED = 'not_started',
-  IN_PROGRESS = 'in_progress',
-  ENDED = 'ended'
-}
-
-
-export const useClassroom = ({ user }:{ user : User }) => {
-  // 状态管理
-  // const [userId, setUserId] = useState<string>(''); // 保持string类型
-  const userId = user.id.toString();
-  const [isCameraOn, setIsCameraOn] = useState<boolean>(false);
-  const [isAudioOn, setIsAudioOn] = useState<boolean>(false);
-  const [isScreenSharing, setIsScreenSharing] = useState<boolean>(false);
-  const [className, setClassName] = useState<string>('');
-  const [role, setRole] = useState<Role | undefined>();
-  const [classId, setClassId] = useState<string>('');
-  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
-  const [isJoinedClass, setIsJoinedClass] = useState<boolean>(false);
-  const [msgText, setMsgText] = useState<string>('');
-  const [messageList, setMessageList] = useState<string[]>([]);
-  const [errorMessage, setErrorMessage] = useState<string>('');
-  const [classStatus, setClassStatus] = useState<ClassStatus>(ClassStatus.NOT_STARTED);
-  const [handUpList, setHandUpList] = useState<HandUpRequest[]>([]);
-  const [questions, setQuestions] = useState<Question[]>([]);
-  const [students, setStudents] = useState<Array<{id: string, name: string}>>([]);
-  const [shareLink, setShareLink] = useState<string>('');
-  const [showCameraOverlay, setShowCameraOverlay] = useState<boolean>(true);
-
-  // SDK实例
-  const imEngine = useRef<ImEngine | null>(null);
-  const imGroupManager = useRef<ImGroupManager | null>(null);
-  const imMessageManager = useRef<ImMessageManager | null>(null);
-  const aliRtcEngine = useRef<AliRtcEngine | null>(null);
-  const remoteVideoElMap = useRef<Record<string, HTMLVideoElement>>({});
-  const remoteScreenContainer = useRef<HTMLDivElement>(null); // 主屏幕共享容器(重命名)
-  const remoteCameraContainer = useRef<HTMLDivElement>(null); // 摄像头小窗容器
-
-  // 辅助函数
-  const showMessage = (text: string): void => {
-    setMessageList((prevMessageList) => [...prevMessageList, text])
-  };
-
-  const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
-    toast[type](message);
-  };
-
-
-
-
-  // 事件监听函数
-  const listenImEvents = (): void => {
-    if (!imEngine.current) return;    
-    if (!role) return;    
-
-    imEngine.current.on('connectsuccess', () => {
-      showMessage('IM连接成功');
-    });
-
-    imEngine.current.on('disconnect', async (code: number) => {
-      showMessage(`IM断开连接: ${code}`);
-      // 自动重连
-      try {
-        const res = await aliyunClient.im_token.$post({
-          json: { role }
-        });
-        if(!res.ok) { 
-          const { message } = await res.json()
-          throw new Error(message)
-        }
-        const { token, nonce, timestamp } = await res.json()
-        await imEngine.current!.login({
-          user: {
-            userId,
-            userExtension: JSON.stringify(user)
-          },
-          userAuth: {
-            nonce,
-            timestamp,
-            token,
-            role
-          }
-        });
-        showMessage('IM自动重连成功');
-      } catch (err: unknown) {
-        const error = err as Error;
-        showMessage(`IM自动重连失败: ${error.message}`);
-      }
-    });
-  };
-
-  const listenGroupEvents = (): void => {
-    if (!imGroupManager.current) return;
-
-    imGroupManager.current.on('memberchange', (groupId: string, memberCount: number, joinUsers: ImUser[], leaveUsers: ImUser[]) => {
-      showMessage(`成员变更: 加入${joinUsers.length}人, 离开${leaveUsers.length}人`);
-    });
-  };
-
-  const listenMessageEvents = (): void => {
-    if (!imMessageManager.current) return;
-
-    imMessageManager.current.on('recvgroupmessage', (msg: AliVCInteraction.ImMessage, groupId: string) => {
-      if (msg.type === 88889) { // 课堂状态消息
-        try {
-          const data = JSON.parse(msg.data);
-          if (data.action === 'start_class') {
-            setClassStatus(ClassStatus.IN_PROGRESS);
-            showMessage('老师已开始上课');
-          } else if (data.action === 'end_class') {
-            setClassStatus(ClassStatus.ENDED);
-            showMessage('老师已结束上课');
-          }
-        } catch (err) {
-          console.error('解析课堂状态消息失败', err);
-        }
-      } else if (msg.type === 88890) { // 静音指令
-        try {
-          const data = JSON.parse(msg.data);
-          if (data.action === 'toggle_mute' && data.userId === userId) {
-            showMessage(data.mute ? '你已被老师静音' : '老师已取消你的静音');
-          }
-        } catch (err) {
-          console.error('解析静音指令失败', err);
-        }
-      } else if (msg.type === 88891) { // 举手消息
-        try {
-          const data = JSON.parse(msg.data) as InteractionMessage;
-          if (data.action === InteractionAction.HandUp) {
-            const handUpData: HandUpRequest = {
-              ...data,
-              timestamp: data.timestamp || Date.now()
-            };
-            setHandUpList([...handUpList, handUpData]);
-            showMessage(`${data.studentName || data.studentId} 举手了`);
-          } else if (data.action === InteractionAction.CancelHandUp) {
-            setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
-          }
-        } catch (err) {
-          console.error('解析举手消息失败', err);
-        }
-      } else if (msg.type === 88892) { // 问题消息
-        try {
-          const data = JSON.parse(msg.data) as {question: string};
-          if (typeof data.question === 'string') {
-            const question: Question = {
-              studentId: msg.sender?.userId || 'unknown',
-              studentName: (() => {
-                try {
-                  return msg.sender?.userExtension ? JSON.parse(msg.sender.userExtension)?.nickname : null;
-                } catch {
-                  return null;
-                }
-              })() || msg.sender?.userId || '未知用户',
-              question: data.question,
-              timestamp: msg.timestamp || Date.now()
-            };
-            setQuestions([...questions, question]);
-          }
-          showMessage(`收到问题: ${data.question}`);
-        } catch (err) {
-          console.error('解析问题消息失败', err);
-        }
-      } else if (msg.type === 88893) { // 应答消息
-        try {
-          const data = JSON.parse(msg.data) as InteractionMessage;
-          if (data.action === InteractionAction.AnswerHandUp && data.studentId === userId) {
-            showMessage('老师已应答你的举手');
-            setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
-          }
-        } catch (err) {
-          console.error('解析应答消息失败', err);
-        }
-      } else if (msg.type === 88888) { // 普通文本消息
-        const sender = msg.sender;
-        const userExtension = JSON.parse(sender?.userExtension || '{}') as User;
-        const senderName = userExtension.nickname || userExtension.username;
-        showMessage(`${ senderName || '未知用户' }: ${msg.data}`);
-      }
-    });
-  };
-
-  // RTC相关函数
-  const removeRemoteVideo = (userId: string, type: 'camera' | 'screen' = 'camera') => {
-    const vid = `${type}_${userId}`;
-    const el = remoteVideoElMap.current[vid];
-    if (el) {
-      aliRtcEngine.current!.setRemoteViewConfig(null, userId, type === 'camera' ? AliRtcVideoTrack.AliRtcVideoTrackCamera : AliRtcVideoTrack.AliRtcVideoTrackScreen);
-      el.pause();
-      
-      // 根据流类型从不同容器移除
-      if (type === 'camera') {
-        remoteCameraContainer.current?.removeChild(el);
-      } else {
-        remoteScreenContainer.current?.removeChild(el);
-      }
-      delete remoteVideoElMap.current[vid];
-    }
-  };
-
-  const listenRtcEvents = () => {
-    if (!aliRtcEngine.current) return;
-
-    showMessage('注册rtc事件监听')
-
-    aliRtcEngine.current.on('remoteUserOnLineNotify', (userId: string) => {
-      showMessage(`用户 ${userId} 加入课堂`);
-      console.log('用户上线通知:', userId);
-    });
-
-    aliRtcEngine.current.on('remoteUserOffLineNotify', (userId: string) => {
-      showMessage(`用户 ${userId} 离开课堂`);
-      console.log('用户下线通知:', userId);
-      removeRemoteVideo(userId, 'camera');
-      removeRemoteVideo(userId, 'screen');
-    });
-
-    aliRtcEngine.current.on('videoSubscribeStateChanged', (
-      userId: string,
-      oldState: AliRtcSubscribeState,
-      newState: AliRtcSubscribeState,
-      interval: number,
-      channelId: string
-    ) => {
-      console.log(`视频订阅状态变化: 用户 ${userId}, 旧状态 ${oldState}, 新状态 ${newState}`);
-
-      switch(newState) {
-        case 3: // 订阅成功
-          try {
-            console.log('开始创建远程视频元素');
-            
-            if (remoteVideoElMap.current[`camera_${userId}`]) {
-              console.log(`用户 ${userId} 的视频元素已存在`);
-              return;
-            }
-            
-            const video = document.createElement('video');
-            video.autoplay = true;
-            video.playsInline = true;
-            video.className = 'w-80 h-45 mr-2 mb-2 bg-black';
-            
-            if (!remoteCameraContainer.current) {
-              console.error('摄像头视频容器未找到');
-              return;
-            }
-            
-            remoteCameraContainer.current.appendChild(video);
-            remoteVideoElMap.current[`camera_${userId}`] = video;
-            
-            aliRtcEngine.current!.setRemoteViewConfig(
-              video,
-              userId,
-              AliRtcVideoTrack.AliRtcVideoTrackCamera
-            );
-            
-            console.log(`已订阅用户 ${userId} 的视频流`);
-            showMessage(`已显示用户 ${userId} 的视频`);
-          } catch (err) {
-            console.error(`订阅用户 ${userId} 视频流失败:`, err);
-            showMessage(`订阅用户 ${userId} 视频流失败`);
-          }
-          break;
-          
-        case 1: // 取消订阅
-          console.log(`取消订阅用户 ${userId} 的视频流`);
-          showMessage(`取消订阅用户 ${userId} 的视频流`);
-          removeRemoteVideo(userId, 'camera');
-          break;
-          
-        case 2: // 订阅中
-          console.log(`正在订阅用户 ${userId} 的视频流...`);
-          break;
-          
-        default:
-          console.warn(`未知订阅状态: ${newState}`);
-      }
-    });
-
-    aliRtcEngine.current.on('screenShareSubscribeStateChanged', (
-      userId: string,
-      oldState: AliRtcSubscribeState,
-      newState: AliRtcSubscribeState,
-      elapseSinceLastState: number,
-      channel: string
-    ) => {
-      console.log(`屏幕分享订阅状态变更:uid=${userId}, oldState=${oldState}, newState=${newState}`);
-
-      switch(newState) {
-        case 3: // 订阅成功
-          try {
-            console.log('开始创建屏幕分享视频元素');
-            
-            if (remoteVideoElMap.current[`screen_${userId}`]) {
-              console.log(`用户 ${userId} 的屏幕分享元素已存在`);
-              return;
-            }
-            
-            const video = document.createElement('video');
-            video.autoplay = true;
-            video.playsInline = true;
-            video.className = 'w-full h-full bg-black';
-            
-            if (!remoteScreenContainer.current) {
-              console.error('屏幕共享容器未找到');
-              return;
-            }
-            
-            remoteScreenContainer.current.appendChild(video);
-            remoteVideoElMap.current[`screen_${userId}`] = video;
-            
-            aliRtcEngine.current!.setRemoteViewConfig(
-              video,
-              userId,
-              AliRtcVideoTrack.AliRtcVideoTrackScreen
-            );
-            
-            console.log(`已订阅用户 ${userId} 的屏幕分享流`);
-            showMessage(`已显示用户 ${userId} 的屏幕分享`);
-          } catch (err) {
-            console.error(`订阅用户 ${userId} 屏幕分享流失败:`, err);
-            showMessage(`订阅用户 ${userId} 屏幕分享流失败`);
-          }
-          break;
-          
-        case 1: // 取消订阅
-          console.log(`取消订阅用户 ${userId} 的屏幕分享流`);
-          showMessage(`取消订阅用户 ${userId} 的屏幕分享流`);
-          removeRemoteVideo(userId, 'screen');
-          break;
-          
-        case 2: // 订阅中
-          console.log(`正在订阅用户 ${userId} 的屏幕分享流...`);
-          break;
-          
-        default:
-          console.warn(`未知屏幕分享订阅状态: ${newState}`);
-      }
-    });
-  };
-
-  // 课堂操作方法
-  const login = async (role: Role): Promise<void> => {
-    if(!role) {
-      showToast('error', '角色不存在');
-      return;
-    }
-    const loginRole = role === Role.Teacher ? 'admin' : Role.Student;
-
-    try {
-      const { ImEngine: ImEngineClass } = window.AliVCInteraction;
-      const res = await aliyunClient.im_token.$post({
-        json: { role: loginRole }
-      });
-      if(!res.ok) { 
-        const { message } = await res.json()
-        throw new Error(message)
-      }
-      const {appId, appSign, timestamp, nonce, token} = await res.json();
-      imEngine.current = ImEngineClass.createEngine();
-      await imEngine.current.init({
-        deviceId: 'xxxx',
-        appId,
-        appSign,
-        logLevel: ERROR,
-      });
-      await imEngine.current.login({
-        user: {
-          userId,
-          userExtension: JSON.stringify({ nickname: user?.nickname || user?.username || '' })
-        },
-        userAuth: {
-          nonce,
-          timestamp,
-          token,
-          role: loginRole
-        }
-      });
-      
-      aliRtcEngine.current = AliRtcEngine.getInstance();
-      AliRtcEngine.setLogLevel(0);
-      
-      listenImEvents();
-      listenRtcEvents();
-      
-      setIsLoggedIn(true);
-      setErrorMessage('');
-      showToast('success', '登录成功');
-    } catch (err: any) {
-      setErrorMessage(`登录失败: ${err.message}`);
-      showToast('error', '登录失败');
-    }
-  };
-
-  const joinClass = async (classId: string): Promise<void> => {
-    if (!imEngine.current || !aliRtcEngine.current) return;
-    
-    // // 优先使用URL参数中的classId和role
-    // const { id: pathClassId, role: pathRole } = useParams();
-    // const finalClassId = (classId || pathClassId) as string;
-    // if (pathRole && ['teacher', 'student'].includes(pathRole)) {
-    //   setRole(pathRole === 'teacher' ? Role.Teacher : Role.Student);
-    // }
-    
-    // if (!finalClassId) {
-    //   setErrorMessage('课堂ID不能为空');
-    //   showToast('error', '请输入有效的课堂ID');
-    //   return;
-    // }
-
-    try {
-      const gm = imEngine.current.getGroupManager();
-      const mm = imEngine.current.getMessageManager();
-      imGroupManager.current = gm || null;
-      imMessageManager.current = mm || null;
-      await gm!.joinGroup(classId);
-      listenGroupEvents();
-      listenMessageEvents();
-
-      await joinRtcChannel(classId);
-
-      buildShareLink(classId)
-
-      setIsJoinedClass(true);
-      setErrorMessage('');
-      showToast('success', '加入课堂成功');
-    } catch (err: any) {
-      setErrorMessage(`加入课堂失败: ${err.message}`);
-      showToast('error', '加入课堂失败');
-      
-      if (imGroupManager.current) {
-        try {
-          await imGroupManager.current.leaveGroup(classId);
-        } catch (leaveErr) {
-          console.error('离开IM群组失败:', leaveErr);
-        }
-      }
-    }
-  };
-
-  const leaveClass = async (): Promise<void> => {
-    try {
-      if (imGroupManager.current && classId) {
-        await imGroupManager.current.leaveGroup(classId);
-      }
-      if (aliRtcEngine.current) {
-        await leaveRtcChannel();
-      }
-      
-      setIsJoinedClass(false);
-      showToast('info', '已离开课堂');
-    } catch (err) {
-      console.error('离开课堂失败:', err);
-      showToast('error', '离开课堂时发生错误');
-    }
-  };
-
-  const sendMessage = async (): Promise<void> => {
-    if (!imMessageManager.current || !classId) return;
-
-    if (!msgText.trim()) {
-      showToast('error', '消息不能为空');
-      return;
-    }
-
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: msgText,
-        type: 88888,
-        level: NORMAL,
-      });
-      setMsgText('');
-      setErrorMessage('');
-    } catch (err: any) {
-      setErrorMessage(`消息发送失败: ${err.message}`);
-    }
-  };
-
-  const startClass = async (): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({ action: 'start_class' }),
-        type: 88889,
-        level: HIGH,
-      });
-      setClassStatus(ClassStatus.IN_PROGRESS);
-      showToast('success', '课堂已开始');
-    } catch (err: any) {
-      setErrorMessage(`开始上课失败: ${err.message}`);
-    }
-  };
-
-  const endClass = async (): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({ action: 'end_class' }),
-        type: 88889,
-        level: HIGH,
-      });
-      setClassStatus(ClassStatus.ENDED);
-      showToast('success', '课堂已结束');
-      
-      try {
-        await leaveRtcChannel();
-      } catch (err: any) {
-        console.error('离开RTC频道失败:', err);
-        showToast('error', '离开RTC频道失败');
-      }
-    } catch (err: any) {
-      setErrorMessage(`结束上课失败: ${err.message}`);
-    }
-  };
-
-  const toggleMuteMember = async (userId: string, mute: boolean): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({
-          action: 'toggle_mute',
-          userId,
-          mute
-        }),
-        type: 88890,
-        level: HIGH,
-      });
-      showToast('info', mute ? `已静音用户 ${userId}` : `已取消静音用户 ${userId}`);
-    } catch (err: any) {
-      setErrorMessage(`操作失败: ${err.message}`);
-    }
-  };
-
-  const buildShareLink = (classId: string) => {
-    const getBaseUrl = () => {
-      const protocol = window.location.protocol;
-      const host = window.location.host;
-      return `${protocol}//${host}`;
-  }
-    // const baseUrl = window.location.href.split('?')[0].replace(/\/[^/]*$/, '');
-    const baseUrl = getBaseUrl();
-    setShareLink(`${baseUrl}/mobile/classroom/${classId}/student`);
-  }
-
-  const createClass = async (className: string, maxMembers = 200): Promise<string | null> => {
-    if (!imEngine.current || !isLoggedIn || role !== Role.Teacher) {
-      showToast('error', '只有老师可以创建课堂');
-      return null;
-    }
-    
-    try {
-      const groupManager = imEngine.current.getGroupManager();
-      if (!groupManager) {
-        throw new Error('群组管理器未初始化');
-      }
-
-      showToast('info', '正在创建课堂...');
-      
-      const response = await groupManager.createGroup({
-        groupName: className,
-        groupMeta: JSON.stringify({
-          classType: 'interactive',
-          creator: userId,
-          createdAt: Date.now(),
-          maxMembers
-        })
-      });
-
-      if (!response?.groupId) {
-        throw new Error('创建群组失败: 未返回群组ID');
-      }
-
-      try {
-        await groupManager.joinGroup(response.groupId);
-        
-        showToast('success', '课堂创建并加入成功');
-        showMessage(`课堂 ${className} 创建成功,ID: ${response.groupId}`);
-        
-        setClassId(response.groupId);
-        setIsJoinedClass(true);
-        
-        const messageManager = imEngine.current.getMessageManager();
-        if (messageManager) {
-          imMessageManager.current = messageManager;
-          listenMessageEvents();
-        }
-        
-        await joinRtcChannel(response.groupId);
-        
-        // const baseUrl = window.location.href.split('?')[0].replace(/\/[^/]*$/, '');
-        // setShareLink(`${baseUrl}/mobile/classroom/${response.groupId}/student`);
-        buildShareLink(response.groupId)
-        
-        return response.groupId;
-      } catch (joinErr: any) {
-        throw new Error(`创建成功但加入失败: ${joinErr.message}`);
-      }
-    } catch (err: any) {
-      const errorMsg = err.message.includes('alreadyExist')
-        ? '课堂已存在'
-        : `课堂创建失败: ${err.message}`;
-      
-      setErrorMessage(errorMsg);
-      showToast('error', errorMsg);
-      return null;
-    }
-  };
-
-  const joinRtcChannel = async (classId: string, publishOptions?: {
-    publishVideo?: boolean
-    publishAudio?: boolean
-    publishScreen?: boolean
-  }) => {
-    if (!aliRtcEngine.current) return;
-    const {
-      publishVideo = false,
-      publishAudio = false,
-      publishScreen = false,
-    } = publishOptions || {};
-    const res = await aliyunClient.rtc_token.$post({
-      json: { channelId: classId }
-    });
-    if(!res.ok) { 
-      const { message } = await res.json()
-      throw new Error(message)
-    }
-    const { appId, token, timestamp } = await res.json()
-    await aliRtcEngine.current.publishLocalVideoStream(publishVideo);
-    await aliRtcEngine.current.publishLocalAudioStream(publishAudio);
-    await aliRtcEngine.current.publishLocalScreenShareStream(publishScreen);
-    await aliRtcEngine.current.joinChannel(
-      {
-        channelId: classId,
-        userId,
-        appId,
-        token,
-        timestamp,
-      },
-      userId
-    );
-  };
-
-  const leaveRtcChannel = async () => {
-    if (!aliRtcEngine.current) return;
-    await aliRtcEngine.current.leaveChannel();
-  };
-
-  // 切换摄像头状态
-  const toggleCamera = async () => {
-    if(!aliRtcEngine.current?.isInCall){
-      showToast('error', '先加入课堂');
-      return;
-    }
-
-    try {
-      if (isCameraOn) {
-        await aliRtcEngine.current?.stopPreview();
-        await aliRtcEngine.current?.enableLocalVideo(false)
-        await aliRtcEngine.current?.publishLocalVideoStream(false)
-      } else {
-        await aliRtcEngine.current?.setLocalViewConfig('localPreviewer', AliRtcVideoTrack.AliRtcVideoTrackCamera);
-        await aliRtcEngine.current?.enableLocalVideo(true)
-        await aliRtcEngine.current?.startPreview();
-        await aliRtcEngine.current?.publishLocalVideoStream(true)
-   
-      }
-      setIsCameraOn(!isCameraOn);
-    } catch (err) {
-      console.error('切换摄像头状态失败:', err);
-      showToast('error', '切换摄像头失败');
-    }
-  };
-
-  // 切换音频状态
-  const toggleAudio = async () => {
-    if(!aliRtcEngine.current?.isInCall){
-      showToast('error', '先加入课堂');
-      return;
-    }
-
-    try {
-      if (isAudioOn) {
-        await aliRtcEngine.current?.stopAudioCapture()
-        await aliRtcEngine.current?.publishLocalAudioStream(false);
-      } else {
-        await aliRtcEngine.current?.publishLocalAudioStream(true);
-      }
-      setIsAudioOn(!isAudioOn);
-    } catch (err) {
-      console.error('切换麦克风状态失败:', err);
-      showToast('error', '切换麦克风失败');
-    }
-  };
-
-  // 切换屏幕分享状态
-  const toggleScreenShare = async () => {
-    if(!aliRtcEngine.current?.isInCall){
-      showToast('error', '先加入课堂');
-      return;
-    }
-
-    try {
-      if (isScreenSharing) {
-        await aliRtcEngine.current?.publishLocalScreenShareStream(false)
-        await aliRtcEngine.current?.stopScreenShare()
-      } else {
-        await aliRtcEngine.current?.publishLocalScreenShareStream(true)
-        await aliRtcEngine.current?.setLocalViewConfig(
-          'screenPreviewer',
-          AliRtcVideoTrack.AliRtcVideoTrackScreen
-        );
-      }
-      setIsScreenSharing(!isScreenSharing);
-    } catch (err) {
-      console.error('切换屏幕分享失败:', err);
-      showToast('error', '切换屏幕分享失败');
-    }
-  };
-
-  const handUp = async (question?: string): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== 'student') return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({
-          action: 'hand_up',
-          studentId: userId,
-          timestamp: Date.now(),
-          question
-        }),
-        type: 88891,
-        level: NORMAL,
-      });
-    } catch (err: any) {
-      setErrorMessage(`举手失败: ${err.message}`);
-    }
-  };
-
-  const muteStudent = async (studentId: string): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({
-          action: 'toggle_mute',
-          userId: studentId,
-          mute: true
-        }),
-        type: 88890,
-        level: HIGH,
-      });
-      showToast('info', `已静音学生 ${studentId}`);
-    } catch (err: any) {
-      setErrorMessage(`静音失败: ${err.message}`);
-    }
-  };
-
-  const kickStudent = async (studentId: string): Promise<void> => {
-    if (!imGroupManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imGroupManager.current.leaveGroup(classId);
-      showToast('info', `已移出学生 ${studentId}`);
-    } catch (err: any) {
-      setErrorMessage(`移出失败: ${err.message}`);
-    }
-  };
-
-  const answerHandUp = async (studentId: string): Promise<void> => {
-    if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: JSON.stringify({
-          action: 'answer_hand_up',
-          studentId
-        }),
-        type: 88893,
-        level: HIGH,
-      });
-      showToast('info', `已应答学生 ${studentId} 的举手`);
-    } catch (err: any) {
-      setErrorMessage(`应答失败: ${err.message}`);
-    }
-  };
-
-  const sendQuestion = async (question: string): Promise<void> => {
-    if (!imMessageManager.current || !classId) return;
-    
-    try {
-      await imMessageManager.current.sendGroupMessage({
-        groupId: classId,
-        data: question,
-        type: 88892,
-        level: NORMAL,
-      });
-    } catch (err: any) {
-      setErrorMessage(`问题发送失败: ${err.message}`);
-    }
-  };
-
-  // 清理资源
-  useEffect(() => {
-    return () => {
-      if (imGroupManager.current) {
-        imGroupManager.current.removeAllListeners();
-      }
-      if (imMessageManager.current) {
-        imMessageManager.current.removeAllListeners();
-      }
-      if (imEngine.current) {
-        imEngine.current.removeAllListeners();
-      }
-      if (aliRtcEngine.current) {
-        aliRtcEngine.current.destroy();
-      }
-    };
-  }, []);
-
-  return {
-    // 状态
-    userId,
-    isCameraOn,
-    isAudioOn,
-    isScreenSharing,
-    className,
-    setClassName,
-    role,
-    setRole,
-    classId,
-    setClassId,
-    isLoggedIn,
-    isJoinedClass,
-    msgText,
-    setMsgText,
-    messageList,
-    errorMessage,
-    classStatus,
-    handUpList,
-    questions,
-    students,
-    shareLink,
-    remoteScreenContainer, // 重命名为remoteScreenContainer
-    remoteCameraContainer, // 导出摄像头容器ref
-    showCameraOverlay,
-    setShowCameraOverlay,
-
-    // 方法
-    login,
-    joinClass,
-    leaveClass,
-    sendMessage,
-    startClass,
-    endClass,
-    toggleMuteMember,
-    createClass,
-    toggleCamera,
-    toggleAudio,
-    toggleScreenShare,
-    handUp,
-    answerHandUp,
-    sendQuestion,
-    muteStudent,
-    kickStudent
-  };
-};
-

+ 0 - 43
src/client/live/components/ErrorPage.tsx

@@ -1,43 +0,0 @@
-import React from 'react';
-import { useRouteError, useNavigate } from 'react-router';
-import { Alert, Button } from 'antd';
-
-export const ErrorPage = () => {
-  const navigate = useNavigate();
-  const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
-  
-  return (
-    <div className="flex flex-col items-center justify-center flex-grow p-4"
-    >
-      <div className="max-w-3xl w-full">
-        <h1 className="text-2xl font-bold mb-4">发生错误</h1>
-        <Alert 
-          type="error"
-          message={error?.message || '未知错误'}
-          description={
-            error?.stack ? (
-              <pre className="text-xs overflow-auto p-2 bg-gray-100 dark:bg-gray-800 rounded">
-                {error.stack}
-              </pre>
-            ) : null
-          }
-          className="mb-4"
-        />
-        <div className="flex gap-4">
-          <Button 
-            type="primary" 
-            onClick={() => navigate(0)}
-          >
-            重新加载
-          </Button>
-          <Button 
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 438
src/client/live/components/Exam/ExamAdmin.tsx

@@ -1,438 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useSearchParams } from 'react-router';
-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 type {
-  QuizState,
-  ExamSocketRoomMessage
-} from './types.ts';
-
-import type { Answer, CumulativeResult } from './types.ts';
-
-type ColumnType = GetProp<TableProps,'columns'>[number]
-
-// 当前答题情况组件
-function CurrentAnswers({ answers, columns }: { answers: Answer[], columns: any[] }) {
-  return (
-    <div>
-      <Table 
-        columns={columns}
-        dataSource={answers}
-        rowKey={(record) => `${record.userId}-${record.date}`}
-        pagination={false}
-      />
-    </div>
-  );
-}
-
-// 每日统计组件
-function DailyStatistics({ dailyAnswers, columns }: { dailyAnswers: {[key: string]: Answer[]}, columns: any[] }) {
-  return (
-    <div>
-      <Table 
-        columns={columns}
-        dataSource={Object.keys(dailyAnswers).map(date => ({ date }))}
-        rowKey="date"
-        pagination={false}
-      />
-    </div>
-  );
-}
-
-// 累计结果组件
-function CumulativeResults({ results, columns }: { results: CumulativeResult[], columns: any[] }) {
-  return (
-    <div>
-      <Table
-        columns={columns}
-        dataSource={results}
-        rowKey="userId"
-        pagination={false}
-      />
-    </div>
-  );
-}
-
-// 二维码组件
-function QRCodeSection({ classroom }: { classroom: string }) {
-  return (
-    <div className="text-center">
-      <div className="text-gray-600 mb-2">扫码参与训练</div>
-      <div className="inline-block p-4 bg-white rounded-lg shadow-md">
-        <QRCode value={`${globalThis.location.origin}/mobile/exam/card?classroom=${classroom}`} />
-      </div>
-    </div>
-  );
-}
-
-export default function ExamAdmin() {
-  const [searchParams] = useSearchParams();
-  const classroom = searchParams.get('classroom');
-  const {
-    socketRoom: { joinRoom, leaveRoom, client },
-    answerManagement,
-    isConnected,
-  } = useSocketClient(classroom as string);
-
-  const [answers, setAnswers] = useState<Answer[]>([]);
-  const [dailyAnswers, setDailyAnswers] = useState<{[key: string]: Answer[]}>({});
-  const [currentDate, setCurrentDate] = useState('');
-  const [currentPrice, setCurrentPrice] = useState('0');
-  const [mark, setMark] = useState('');
-  const [activeTab, setActiveTab] = useState('current');
-  const [loading, setLoading] = useState(false);
-  const [error, setError] = useState<string | null>(null);
-
-  const initExamData = async () => {
-    if (!classroom) return;
-    setLoading(true);
-    setError(null);
-    try {
-      // 获取当前问题
-      const question = await answerManagement.getCurrentQuestion(classroom);
-      if (question) {
-        setCurrentDate(question.date);
-        setCurrentPrice(String(question.price));
-
-        // 获取答题记录
-        const answers = await answerManagement.getAnswers(
-          classroom,
-          ''
-        );
-        
-        const processedAnswers = answers.map(answer => ({
-          ...answer,
-          profitAmount: answer.profitAmount || 0,
-          profitPercent: answer.profitPercent || 0,
-          holdingStock: answer.holdingStock || '0',
-          holdingCash: answer.holdingCash || '0'
-        }));
-
-        const processedDailyAnswers:{[key: string]: Answer[]} = {};
-        processedAnswers.forEach(val => {
-          if(!processedDailyAnswers[val.date])
-            processedDailyAnswers[val.date] = [];
-          processedDailyAnswers[val.date].push(val)
-        })
-
-        setAnswers(processedAnswers);
-        setDailyAnswers(processedDailyAnswers);
-      }
-    } catch (err) {
-      console.error('初始化答题数据失败:', err);
-      setError('初始化答题数据失败');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  // 结算函数
-  const handleSettlement = async () => {
-    if (!classroom || answers.length === 0) return;
-    setLoading(true);
-    
-    try {
-      await answerManagement.sendSettleExam(classroom);
-      message.success('结算成功');
-    } catch (error) {
-      console.error('结算失败:', error);
-      message.error('结算失败');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  const handleSubmit = async () => {
-    if (!classroom || answers.length === 0) return;
-
-    try {
-      await answerManagement.cleanupRoom(classroom);
-      message.success('答案提交成功');
-      setAnswers([]);
-      setDailyAnswers({});
-      setCurrentDate('');
-      setCurrentPrice('0');
-    } catch (error: any) {
-      console.error('提交答案失败:', error);
-      message.error(error?.message || '提交答案失败');
-    }
-  };
-
-  const handleRestart = async () => {
-    if (!classroom) return;
-    try {
-      await answerManagement.cleanupRoom(classroom);
-      setAnswers([]);
-      setDailyAnswers({});
-      setCurrentDate('');
-      setCurrentPrice('0');
-      message.success('已重新开始');
-    } catch (error) {
-      console.error('重新开始失败:', error);
-      message.error('重新开始失败');
-    }
-  };
-
-  const columns = [
-    {
-      title: '昵称',
-      dataIndex: 'userId',
-      key: 'userId',
-    },
-    {
-      title: '日期',
-      dataIndex: 'date',
-      key: 'date',
-      render: (text: string) => text ? dayjs(text).format('YYYY-MM-DD') : '-',
-    },
-    {
-      title: '持股',
-      dataIndex: 'holdingStock',
-      key: 'holdingStock',
-    },
-    {
-      title: '持币',
-      dataIndex: 'holdingCash',
-      key: 'holdingCash',
-    },
-    {
-      title: '价格',
-      dataIndex: 'price',
-      key: 'price',
-      render: (text: string | undefined) => text ? parseFloat(text).toFixed(2) : '-',
-    },
-    {
-      title: '收益(元)',
-      dataIndex: 'profitAmount',
-      key: 'profitAmount',
-      render: (text: number | undefined) => text !== undefined ? text.toFixed(2) : '-',
-    },
-    {
-      title: '盈亏率',
-      dataIndex: 'profitPercent',
-      key: 'profitPercent',
-      render: (text: number | undefined) => text !== undefined ? `${text.toFixed(2)}%` : '-',
-    }
-  ];
-
-  const resultColumns: ColumnType[] = [
-    {
-      title: '昵称',
-      dataIndex: 'userId',
-      key: 'userId',
-    },
-    {
-      title: '累计盈亏(元)',
-      dataIndex: 'totalProfitAmount',
-      key: 'totalProfitAmount',
-      render: (text: number | undefined) => text !== undefined ? text.toFixed(2) : '-',
-    },
-    {
-      title: '累计盈亏率',
-      dataIndex: 'totalProfitPercent',
-      key: 'totalProfitPercent',
-      render: (text: number | undefined) => text !== undefined ? `${text.toFixed(2)}%` : '-',
-    },
-  ];
-
-  const dailyAnswersColumns = [
-    {
-      title: '日期',
-      dataIndex: 'date',
-      key: 'date',
-      render: (text: string) => dayjs(text).format('YYYY-MM-DD'),
-    },
-    {
-      title: '答题人数',
-      key: 'count',
-      render: (_: any, record: { date: string }) => dailyAnswers[record.date]?.length || 0,
-    },
-    {
-      title: '持股人数',
-      key: 'holdingStockCount',
-      render: (_: any, record: { date: string }) => 
-        dailyAnswers[record.date]?.filter((a: any) => a.holdingStock === '1').length || 0,
-    },
-    {
-      title: '持币人数',
-      key: 'holdingCashCount',
-      render: (_: any, record: { date: string }) => 
-        dailyAnswers[record.date]?.filter((a: any) => a.holdingCash === '1').length || 0,
-    }
-  ];
-
-  // 计算累计结果的函数
-  const calculateCumulativeResults = (dailyAnswers: {[key: string]: Answer[]}): CumulativeResult[] => {
-    const userResults = new Map<string, CumulativeResult>();
-
-    // 按日期排序
-    const sortedDates = Object.keys(dailyAnswers).sort((a: string, b: string) => 
-      new Date(a).getTime() - new Date(b).getTime()
-    );
-    
-    sortedDates.forEach(date => {
-      const answers = dailyAnswers[date] || [];
-      answers.forEach((answer: Answer) => {
-        const userId = answer.userId;
-        // 直接使用服务端计算好的收益数据
-        const profitAmount = answer.profitAmount || 0;
-        const profitPercent = answer.profitPercent || 0;
-
-        if (!userResults.has(userId)) {
-          userResults.set(userId, {
-            userId,
-            totalProfitAmount: 0,
-            totalProfitPercent: 0
-          });
-        }
-
-        const currentResult = userResults.get(userId)!;
-        currentResult.totalProfitAmount += profitAmount;
-        currentResult.totalProfitPercent += profitPercent;
-        userResults.set(userId, currentResult);
-      });
-    });
-
-    return Array.from(userResults.values());
-  };
-
-  const items = [
-    {
-      key: 'current',
-      label: '当前答题情况',
-      children: <CurrentAnswers answers={answers} columns={columns} />,
-    },
-    {
-      key: 'daily',
-      label: '每日答题统计',
-      children: <DailyStatistics dailyAnswers={dailyAnswers} columns={dailyAnswersColumns} />,
-    },
-    {
-      key: 'cumulative',
-      label: '累计结果',
-      children: <CumulativeResults
-        results={calculateCumulativeResults(dailyAnswers)}
-        columns={resultColumns}
-      />,
-    },
-  ];
-
-  
-  // 加入/离开房间
-  useEffect(() => {
-    if (!classroom) return;
-    
-    joinRoom(classroom);
-    initExamData();
-
-    return () => {
-      leaveRoom(classroom);
-    };
-  }, [classroom, joinRoom, leaveRoom]);
-
-
-  // 监听答题消息并更新答案
-  useEffect(() => {
-    if (!classroom || !currentDate || !client) return;
-
-    const handleAnswerMessage = async () => {
-      try {
-        const answers = await answerManagement.getAnswers(
-          classroom as string,
-          currentDate
-        );
-        
-        const processedAnswers = answers.map(answer => ({
-          ...answer,
-          profitAmount: answer.profitAmount || 0,
-          profitPercent: answer.profitPercent || 0,
-          holdingStock: answer.holdingStock || '0',
-          holdingCash: answer.holdingCash || '0'
-        }));
-
-        setAnswers(processedAnswers);
-        setDailyAnswers(prev => ({
-          ...prev,
-          [currentDate]: processedAnswers
-        }));
-      } catch (error) {
-        console.error('获取答案失败:', error);
-      }
-    };
-
-    client.on('exam:answerUpdated', handleAnswerMessage);
-
-    return () => {
-      if (!client) return;
-      client.off('exam:answerUpdated', handleAnswerMessage);
-    };
-  }, [classroom, currentDate, answerManagement, client]);
-
-  // 监听当前问题变化
-  useEffect(() => {
-    if (!client ) return;
-
-    const handleQuestionUpdate = (question:QuizState ) => {
-      setCurrentDate(question.date);
-      setCurrentPrice(String(question.price));
-    };
-
-    client.on('exam:question', handleQuestionUpdate);
-    return () => {
-      client.off('exam:question', handleQuestionUpdate);
-    };
-  }, [client]);
-
-  return (
-    <div className="p-6">
-      {!isConnected && (
-        <div className="bg-yellow-600 text-white text-center py-1 text-sm">
-          正在尝试连接答题卡服务...
-        </div>
-      )}
-      <div className="mb-6 flex justify-between items-center">
-        <div>
-          <h2 className="text-2xl font-bold">答题卡管理</h2>
-          <div className="mt-2 text-gray-600">
-            <span className="mr-4">教室号: {classroom}</span>
-            <span className="mr-4">当前日期: {currentDate}</span>
-            <span>当前价格: {currentPrice}</span>
-          </div>
-        </div>
-      </div>
-
-      {/* 主要内容区域 */}
-      <div className="mb-6">
-        <Tabs 
-          activeKey={activeTab} 
-          onChange={setActiveTab}
-          items={items}
-        />
-      </div>
-
-      {/* 底部按钮组 */}
-      <div className="flex items-center space-x-4 mb-8">
-        <Button onClick={handleSettlement} disabled={answers.length === 0}>
-          结算
-        </Button>
-        <Button type="primary" onClick={handleSubmit} disabled={answers.length === 0}>
-          收卷
-        </Button>
-        <Input
-          value={mark}
-          onChange={(e) => setMark(e.target.value)}
-          placeholder="标记"
-          style={{ width: 200 }}
-        />
-        <Button onClick={() => message.info('标记已保存')}>查看</Button>
-        <Button onClick={handleRestart}>重开</Button>
-      </div>
-
-      {/* 二维码区域 */}
-      <QRCodeSection classroom={classroom || ''} />
-    </div>
-  );
-} 

+ 0 - 360
src/client/live/components/Exam/ExamCard.tsx

@@ -1,360 +0,0 @@
-import React,{ useState, useCallback, useEffect } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { useSearchParams, useNavigate } from "react-router";
-import dayjs from 'dayjs';
-import { useSocketClient } from './hooks/useSocketClient';
-import { classroomDataClient } from '@/client/api';
-import type { QuizState } from './types';
-import type { AnswerRecord, Answer } from './types';
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-import { ClassroomStatus } from '@/server/modules/classroom/classroom-data.schema';
-import { toast } from 'react-toastify';
-
-// 答题卡页面
-export default function ExamCard() {
-  const { user } = useAuth();
-  const navigate = useNavigate();
-  const [searchParams] = useSearchParams();
-  const classroom = searchParams.get('classroom');
-  const {
-    socketRoom: { joinRoom, leaveRoom, client },
-    answerManagement,
-    isConnected,
-  } = useSocketClient(classroom as string);
-  const [currentDate, setCurrentDate] = useState('');
-  const [currentPrice, setCurrentPrice] = useState('0');
-  const [holdingStock, setHoldingStock] = useState('0');
-  const [holdingCash, setHoldingCash] = useState('0');
-  const [isStarted, setIsStarted] = useState(false);
-  const [answerRecords, setAnswerRecords] = useState<AnswerRecord[]>([]);
-
-  const { data: classroomData, isLoading } = useQuery({
-    queryKey: ['classroom', classroom],
-    queryFn: async () => {
-      if (!classroom) return null;
-      const response = await classroomDataClient.$get({
-        query: { filters: JSON.stringify({ classroomNo: classroom }) }
-      });
-      if (response.status !== 200) {
-        toast.error('获取教室数据失败');
-        return null;
-      }
-      const data = await response.json();
-      return data.data[0] || null;
-    },
-    enabled: !!classroom
-  });
-
-  // 初始化答题卡数据
-  const initExamCard = useCallback(async () => {
-    if (!classroom || !user?.username) {
-      toast.error('参数缺少');
-      // globalThis.location.href = '/exam';
-      navigate('/mobile')
-      return;
-    }
-    
-    if (classroomData && classroomData.status !== ClassroomStatus.OPEN) {
-      toast.error('该教室已关闭');
-      // globalThis.location.href = '/exam';
-      navigate('/mobile')
-      return;
-    }
-    
-    // 获取当前问题并更新状态
-    const question = await answerManagement.getCurrentQuestion(classroom);
-    setCurrentDate(question.date);
-    setCurrentPrice(String(question.price));
-    setIsStarted(true);
-    
-    // 获取用户回答记录
-    if (user?.id) {
-      try {
-        const answers = await answerManagement.getUserAnswers(classroom, String(user.id));
-        if (answers && answers.length > 0) {
-          const lastAnswer = answers[answers.length - 1];
-          setHoldingStock(lastAnswer.holdingStock);
-          setHoldingCash(lastAnswer.holdingCash);
-          
-          const records = answers.map((answer: Answer, index: number): AnswerRecord => ({
-            date: answer.date,
-            price: String(answer.price || '0'),
-            holdingStock: answer.holdingStock,
-            holdingCash: answer.holdingCash,
-            profitAmount: answer.profitAmount || 0,
-            profitPercent: answer.profitPercent || 0,
-            index: index + 1
-          }));
-          
-          setAnswerRecords(records);
-        }
-      } catch (error) {
-        console.error('获取用户回答记录失败:', error);
-      }
-    }
-  }, [classroom, user, classroomData, answerManagement]);
-
-  // 加入/离开房间
-  useEffect(() => {
-    if (!classroom) return;
-    
-    joinRoom(classroom);
-
-    initExamCard();
-    
-    return () => {
-      leaveRoom(classroom);
-    };
-  }, [classroom, joinRoom, leaveRoom]);
-
-  // // 处理房间消息
-  // useEffect(() => {
-  //   if (!lastMessage?.message) return;
-
-  //   const { type } = lastMessage.message;
-    
-  //   // 只处理重开消息的UI重置
-  //   if (type === 'restart') {
-  //     setCurrentDate('');
-  //     setCurrentPrice('0');
-  //     setHoldingStock('0');
-  //     setHoldingCash('0');
-  //     setIsStarted(false);
-  //     setAnswerRecords([]);
-  //   }
-  // }, [lastMessage]);
-
-  // 处理选择A(持股)
-  const handleChooseA = useCallback(async () => {
-    setHoldingStock('1');
-    setHoldingCash('0');
-    
-    if (classroom && user?.username && currentDate) {
-      const answer = {
-        date: currentDate,
-        holdingStock: '1',
-        holdingCash: '0',
-        userId: String(user.id),
-        price: currentPrice
-      };
-      
-      try {
-        await answerManagement.storeAnswer(
-          classroom as string,
-          currentDate,
-          String(user.id),
-          answer,
-          (answers) => {
-            const records = answers.map((answer: Answer, index: number): AnswerRecord => ({
-              date: answer.date,
-              price: String(answer.price || '0'),
-              holdingStock: answer.holdingStock,
-              holdingCash: answer.holdingCash,
-              profitAmount: answer.profitAmount || 0,
-              profitPercent: answer.profitPercent || 0,
-              index: index + 1
-            }));
-            setAnswerRecords(records);
-          }
-        );
-      } catch (error) {
-        toast.error('提交答案失败');
-      }
-    }
-  }, [classroom, user, currentDate, currentPrice, answerManagement]);
-
-  const handleChooseB = useCallback(async () => {
-    setHoldingStock('0');
-    setHoldingCash('1');
-    
-    if (classroom && user?.username && currentDate) {
-      const answer = {
-        date: currentDate,
-        holdingStock: '0',
-        holdingCash: '1',
-        userId: String(user.id),
-        price: currentPrice
-      };
-      
-      try {
-        await answerManagement.storeAnswer(
-          classroom as string,
-          currentDate,
-          String(user.id),
-          answer,
-          (answers) => {
-            const records = answers.map((answer: Answer, index: number): AnswerRecord => ({
-              date: answer.date,
-              price: String(answer.price || '0'),
-              holdingStock: answer.holdingStock,
-              holdingCash: answer.holdingCash,
-              profitAmount: answer.profitAmount || 0,
-              profitPercent: answer.profitPercent || 0,
-              index: index + 1
-            }));
-            setAnswerRecords(records);
-          }
-        );
-      } catch (error) {
-        toast.error('提交答案失败');
-      }
-    }
-  }, [classroom, user, currentDate, currentPrice, answerManagement]);
-
-  // 监听当前问题变化
-  useEffect(() => {
-    if (!client ) return;
-
-    const handleQuestionUpdate = (question:QuizState ) => {
-      setCurrentDate(question.date);
-      setCurrentPrice(String(question.price));
-      setIsStarted(true);
-    };
-
-    client.on('exam:question', handleQuestionUpdate);
-    return () => {
-      client.off('exam:question', handleQuestionUpdate);
-    };
-  }, [client]);
-
-  // 监听重开
-  useEffect(() => {
-    if (!client ) return;
-
-    const handleCleaned = () => {
-      setCurrentDate('');
-      setCurrentPrice('0');
-      setHoldingStock('0');
-      setHoldingCash('0');
-      setIsStarted(false);
-      setAnswerRecords([]);
-    };
-
-    client.on('exam:cleaned', handleCleaned);
-    return () => {
-      client.off('exam:cleaned', handleCleaned);
-    };
-  }, [client]);
-
-  // 监听结算消息
-  useEffect(() => {
-    if (!client) return;
-
-    const handleSettle = () => {
-      handleChooseB();
-    };
-
-    client.on('exam:settle', handleSettle);
-    return () => {
-      client.off('exam:settle', handleSettle);
-    };
-  }, [client, handleChooseB]);
-
-  if (isLoading || !classroomData) {
-    return <div className="flex items-center justify-center min-h-screen">加载中...</div>;
-  }
-
-  return (
-    <div className="flex flex-col items-center min-h-screen bg-gray-100 py-8">
-      {!isConnected && (
-        <div className="bg-yellow-600 text-white text-center py-1 text-sm">
-          正在尝试连接答题卡服务...
-        </div>
-      )}
-      {/* 选择区域 */}
-      <div className="w-full max-w-2xl">
-        <div className="text-center mb-8">
-          <h2 className="text-2xl font-bold mb-2">持股选A, 持币选B</h2>
-          <div className="flex justify-center space-x-4 text-gray-600">
-            {isStarted ? (
-              <>
-                <span>日期: {currentDate}</span>
-                <span>价格: {currentPrice}</span>
-              </>
-            ) : (
-              <div className="text-blue-600">
-                <div className="mb-2">等待训练开始...</div>
-                <div className="text-sm text-gray-500">
-                  训练日期: {dayjs(classroomData.trainingDate).format('YYYY-MM-DD')}
-                </div>
-              </div>
-            )}
-          </div>
-        </div>
-
-        {/* 选择按钮 */}
-        <div className="flex justify-center items-center space-x-4 mb-8 bg-white p-6 rounded-lg shadow-md">
-          <button
-            onClick={handleChooseA}
-            disabled={!isStarted}
-            className={`flex-1 py-8 text-3xl font-bold rounded-lg transition-colors ${
-              !isStarted 
-                ? 'bg-gray-200 text-gray-400 cursor-not-allowed'
-                : holdingStock === '1'
-                  ? 'bg-red-500 text-white'
-                  : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
-            }`}
-          >
-            A
-          </button>
-          <div className="text-xl font-medium text-gray-700">
-            {isStarted ? '开始' : '等待'}
-          </div>
-          <button
-            onClick={handleChooseB}
-            disabled={!isStarted}
-            className={`flex-1 py-8 text-3xl font-bold rounded-lg transition-colors ${
-              !isStarted 
-                ? 'bg-gray-200 text-gray-400 cursor-not-allowed'
-                : holdingCash === '1'
-                  ? 'bg-green-500 text-white'
-                  : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
-            }`}
-          >
-            B
-          </button>
-        </div>
-
-        {/* 信息显示 */}
-        <div className="bg-white p-6 rounded-lg shadow-md">
-          <div className="grid grid-cols-2 gap-4 mb-4">
-            <div className="text-gray-600">昵称: {user?.username || '未知用户'}</div>
-            <div className="text-gray-600">代码: {classroomData.code}</div>
-          </div>
-
-          {/* 表格头部 */}
-          <div className="grid grid-cols-8 gap-4 py-2 border-b border-gray-200 text-sm font-medium text-gray-600">
-            <div>序</div>
-            <div>训练日期</div>
-            <div>持股</div>
-            <div>持币</div>
-            <div>价格</div>
-            <div>收益(元)</div>
-            <div>盈亏率</div>
-            <div>号</div>
-          </div>
-
-          {/* 表格内容 */}
-          <div className="max-h-60 overflow-y-auto">
-            {[...answerRecords].reverse().map((record: AnswerRecord) => (
-              <div key={record.date} className="grid grid-cols-8 gap-4 py-2 text-sm text-gray-800 hover:bg-gray-50">
-                <div>{record.index}</div>
-                <div>{dayjs(record.date).format('YYYY-MM-DD')}</div>
-                <div className="text-red-500">{record.holdingStock}</div>
-                <div className="text-green-500">{record.holdingCash}</div>
-                <div>{record.price}</div>
-                <div className={record.profitAmount >= 0 ? 'text-red-500' : 'text-green-500'}>
-                  {record.profitAmount.toFixed(2)}
-                </div>
-                <div className={record.profitPercent >= 0 ? 'text-red-500' : 'text-green-500'}>
-                  {record.profitPercent.toFixed(2)}%
-                </div>
-                <div>{record.index}</div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}

+ 0 - 103
src/client/live/components/Exam/ExamIndex.tsx

@@ -1,103 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { useNavigate } from "react-router";
-import dayjs from 'dayjs';
-import { classroomDataClient } from '@/client/api';
-import { ClassroomStatus } from '@/server/modules/classroom/classroom-data.schema';
-import type { InferResponseType } from 'hono/client';
-import { toast } from 'react-toastify';
-
-type ClassroomDataResponse = InferResponseType<typeof classroomDataClient.$get, 200>;
-type ClassroomData = ClassroomDataResponse['data'][0];
-// 教室号输入页面
-function ExamIndex() {
-  const [classroom, setClassroom] = useState('');
-  const navigate = useNavigate();
-  
-  const [classroomData, setClassroomData] = useState<ClassroomData | null>(null);
-  const [isLoading, setIsLoading] = useState(false);
-
-  const handleJoinTraining = useCallback(async () => {
-    if (!classroom) {
-      toast.error('教室号不能为空');
-      return;
-    }
-
-    try {
-      setIsLoading(true);
-      const response = await classroomDataClient.$get({
-        query: { filters: JSON.stringify({ classroomNo: classroom }) }
-      });
-      
-      if (response.status !== 200) {
-        toast.error('获取教室数据失败');
-        setClassroomData(null);
-        return;
-      }
-      
-      const result = await response.json();
-      if (!result.data?.length) {
-        toast.error('教室不存在');
-        setClassroomData(null);
-        return;
-      }
-
-      const data = result.data[0];
-      setClassroomData(data);
-
-      if (data.status !== ClassroomStatus.OPEN) {
-        toast.error('该教室已关闭');
-        return;
-      }
-
-      // 将教室号作为参数传递到答题页
-      navigate(`/mobile/exam/card?classroom=${classroom}`);
-    } catch (error) {
-      toast.error('获取教室数据失败');
-      setClassroomData(null);
-    } finally {
-      setIsLoading(false);
-    }
-  }, [navigate, classroom]);
-
-  if (isLoading) {
-    return <div className="flex items-center justify-center min-h-screen">加载中...</div>;
-  }
-
-  return (
-    <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
-      <div className="w-full max-w-md space-y-8">
-        <div className="text-center">
-          <h2 className="text-3xl font-bold text-gray-900">股票训练答题卡系统</h2>
-          <p className="mt-2 text-gray-600">
-            {classroom ? `教室号: ${classroom}` : '请输入教室号开始答题'}
-          </p>
-          {classroomData && (
-            <div className="mt-2 text-sm text-gray-500">
-              <p>训练日期: {dayjs(classroomData.trainingDate).format('YYYY-MM-DD')}</p>
-              <p>代码: {classroomData.code}</p>
-            </div>
-          )}
-        </div>
-        
-        <div className="mt-8 space-y-4">
-          <input
-            type="text"
-            value={classroom}
-            onChange={(e) => setClassroom(e.target.value)}
-            placeholder="请输入教室号"
-            className="w-full px-4 py-3 text-lg border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
-          />
-        </div>
-
-        <button
-          onClick={handleJoinTraining}
-          className="w-full px-8 py-3 text-lg font-medium text-white bg-blue-500 rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
-        >
-          开始答题
-        </button>
-      </div>
-    </div>
-  );
-}
-
-export default ExamIndex;

+ 0 - 387
src/client/live/components/Exam/hooks/useSocketClient.ts

@@ -1,387 +0,0 @@
-import { useEffect, useState, useCallback } from 'react';
-import { useQuery, useQueryClient } from '@tanstack/react-query';
-import { io, Socket } from 'socket.io-client';
-import type {
-  QuizContent,
-  QuizState,
-  ExamSocketMessage,
-  ExamSocketRoomMessage,
-  Answer,
-  CumulativeResult
-} from '../types.ts';
-import { useAuth } from '@/client/live/hooks/AuthProvider.js';
-
-interface FullExamSocketMessage extends Omit<ExamSocketMessage, 'timestamp'> {
-  id: string;
-  from: string;
-  timestamp: string;
-}
-
-
-// 工具函数:统一错误处理
-const handleAsyncOperation = async <T>(
-  operation: () => Promise<T>,
-  errorMessage: string
-): Promise<T> => {
-  try {
-    return await operation();
-  } catch (error) {
-    console.error(`${errorMessage}:`, error);
-    throw error;
-  }
-};
-
-// 计算收益的辅助函数
-interface ProfitResult {
-  profitAmount: number;  // 金额收益
-  profitPercent: number; // 百分比收益
-}
-
-function calculateProfit(currentPrice: number, previousPrice: number, holdingStock: string): ProfitResult {
-  if (holdingStock === '1') {
-    const profitAmount = currentPrice - previousPrice;
-    const profitPercent = ((currentPrice - previousPrice) / previousPrice) * 100;
-    return { profitAmount, profitPercent };
-  }
-  return { profitAmount: 0, profitPercent: 0 };
-}
-
-// 提前声明函数
-function getAnswers(client: Socket | null, roomId: string, questionId: string): Promise<Answer[]> {
-  if (!client) return Promise.resolve([]);
-
-  return new Promise((resolve) => {
-    client.emit('exam:getAnswers', { roomId, questionId }, (answers: Answer[]) => {
-      resolve(answers || []);
-    });
-  });
-}
-
-export function useSocketClient(roomId: string | null) {
-  const { token } = useAuth();
-  const [socket, setSocket] = useState<Socket | null>(null);
-  const [isConnected, setIsConnected] = useState(false);
-
-  // 初始化socket连接
-  const { data: client } = useQuery({
-    queryKey: ['socket-client', token],
-    queryFn: async () => {
-      if (!token) return null;
-
-      const newSocket = io('/', {
-        path: '/socket.io',
-        transports: ['websocket'],
-        withCredentials: true,
-        query: {
-          socket_token: token
-        },
-        reconnection: true,
-        reconnectionAttempts: 5,
-        reconnectionDelay: 1000,
-      });
-
-      newSocket.on('connect', () => {
-        console.log('Socket connected');
-      setIsConnected(true);
-      });
-
-      newSocket.on('disconnect', () => {
-        console.log('Socket disconnected');
-        setIsConnected(false);
-      });
-
-      newSocket.on('error', (error) => {
-        console.error('Socket error:', error);
-      });
-
-      setSocket(newSocket);
-      return newSocket;
-    },
-    enabled: !!token && !!roomId,
-    staleTime: Infinity,
-    gcTime: 0,
-    retry: 3,
-  });
-
-  // 加入房间
-  const joinRoom = useCallback(async (roomId: string) => {
-    if (client) {
-      client.emit('exam:join', { roomId });
-    }
-  }, [client]);
-
-  // 离开房间
-  const leaveRoom = useCallback(async (roomId: string) => {
-    if (client) {
-      client.emit('exam:leave', { roomId });
-    }
-  }, [client]);
-
-  // // 发送房间消息
-  // const sendRoomMessage = useCallback(async (roomId: string, message: ExamSocketMessage) => {
-  //   if (client) {
-  //     client.emit('exam:message', { roomId, message });
-  //   }
-  // }, [client]);
-
-  // // 监听房间消息
-  // const onRoomMessage = useCallback((callback: (data: ExamSocketRoomMessage) => void) => {
-  //   if (client) {
-  //     client.on('exam:message', (data) => {
-  //       setLastMessage(data);
-  //       callback(data);
-  //     });
-  //   }
-  // }, [client]);
-
-  // 存储答案
-  const storeAnswer = useCallback(async (roomId: string, questionId: string, userId: string, answer: QuizContent, callback?: (success: Answer[]) => void) => {
-    if (!client) return;
-
-    return handleAsyncOperation(async () => {
-      // // 获取历史价格数据
-      // const pricesData = await new Promise<any>((resolve) => {
-      //   client.emit('exam:getPrices', { roomId }, resolve);
-      // });
-
-      // if (!pricesData) {
-      //   // 存储初始答案
-      //   const initialAnswer: Answer = {
-      //     ...answer,
-      //     userId,
-      //     holdingStock: '0',
-      //     holdingCash: '0',
-      //     profitAmount: 0,
-      //     profitPercent: 0,
-      //     totalProfitAmount: 0,
-      //     totalProfitPercent: 0
-      //   };
-        
-      //   client.emit('exam:storeAnswer', {
-      //     roomId,
-      //     questionId,
-      //     userId,
-      //     answer: initialAnswer
-      //   }, (success: boolean) => {
-      //     callback?.([initialAnswer]);
-      //   });
-      //   return;
-      // }
-
-      // 获取该用户的所有历史答案
-      // const dates = Object.keys(pricesData).sort();
-      const allUserAnswers = await getUserAnswers(roomId, userId);
-      const userAnswers = allUserAnswers
-        .filter((a: Answer) => a.date !== answer.date)
-        // .filter((a: Answer) => dates.includes(a.date || ''))
-        .map((a: Answer) => ({
-          ...a,
-          // price: pricesData[a.date || '']?.price || '0'
-        }))
-        .sort((a: Answer, b: Answer) => new Date(a.date || '').getTime() - new Date(b.date || '').getTime());
-
-      let totalProfitAmount = 0;
-      let totalProfitPercent = 0;
-      
-      if (userAnswers.length > 0) {
-        const prevAnswer = userAnswers[userAnswers.length - 1];
-        const { profitAmount, profitPercent } = calculateProfit(
-          parseFloat(String(answer.price)),
-          parseFloat(String(prevAnswer.price)),
-          prevAnswer.holdingStock as string
-        );
-        
-        totalProfitAmount = (prevAnswer.totalProfitAmount || 0) + profitAmount;
-        totalProfitPercent = (prevAnswer.totalProfitPercent || 0) + profitPercent;
-      }
-
-      // 存储带有收益信息的答案
-      const answerWithProfit: Answer = {
-        ...answer,
-        userId,
-        profitAmount: userAnswers.length > 0 ? totalProfitAmount - (userAnswers[userAnswers.length - 1].totalProfitAmount || 0) : 0,
-        profitPercent: userAnswers.length > 0 ? totalProfitPercent - (userAnswers[userAnswers.length - 1].totalProfitPercent || 0) : 0,
-        totalProfitAmount,
-        totalProfitPercent
-      };
-
-      client.emit('exam:storeAnswer', {
-        roomId,
-        questionId,
-        userId,
-        answer: answerWithProfit
-      }, (success: boolean) => {
-        callback?.([...userAnswers, answerWithProfit]);
-      });
-    }, '存储答案失败');
-  }, [client]);
-
-  // 清理房间数据
-  const cleanupRoom = useCallback(async (roomId: string, questionId?: string) => {
-    if (!client) return;
-
-    await handleAsyncOperation(async () => {
-      if (questionId) {
-        client.emit('exam:cleanup', { roomId, questionId });
-      } else {
-        client.emit('exam:cleanup', { roomId });
-      }
-    }, '清理房间数据失败');
-  }, [client]);
-
-  // // 发送下一题
-  // const sendNextQuestion = useCallback(async (roomId: string, state: QuizState) => {
-  //   if (!client) return;
-
-  //   return handleAsyncOperation(async () => {
-  //     const message: FullExamSocketMessage = {
-  //       id: `question-${Date.now()}`,
-  //       type: 'question',
-  //       from: 'system',
-  //       timestamp: Date.now().toString(),
-  //       content: {
-  //         date: state.date,
-  //         price: state.price,
-  //         holdingStock: '0',
-  //         holdingCash: '0',
-  //         userId: 'system'
-  //       }
-  //     };
-
-  //     // 存储当前问题状态
-  //     await storeAnswer(roomId, 'current_state', 'system', {
-  //       date: state.date,
-  //       price: state.price,
-  //       holdingStock: '0',
-  //       holdingCash: '0',
-  //       userId: 'system'
-  //     });
-
-  //     // 存储价格历史记录
-  //     client.emit('exam:storePrice', { 
-  //       roomId, 
-  //       date: state.date, 
-  //       price: state.price 
-  //     });
-
-  //     await sendRoomMessage(roomId, message);
-  //   }, '发送题目失败');
-  // }, [client, sendRoomMessage, storeAnswer]);
-
-
-  // 获取历史价格
-  const getPriceHistory = useCallback(async (roomId: string, date: string): Promise<string> => {
-    if (!client) return '0';
-
-    return handleAsyncOperation(async () => {
-      return new Promise((resolve) => {
-        client.emit('exam:getPrice', { roomId, date }, (price: string) => {
-          resolve(price || '0');
-        });
-      });
-    }, '获取历史价格失败');
-  }, [client]);
-
-  // 获取答案 (封装为useCallback)
-  const getAnswersCallback = useCallback((roomId: string, questionId: string): Promise<Answer[]> => {
-    if (!client) return Promise.resolve([]);
-    return handleAsyncOperation(async () => {
-      return getAnswers(client, roomId, questionId);
-    }, '获取答案失败');
-  }, [client]);
-
-  // 清理socket连接
-  useEffect(() => {
-    return () => {
-      if (socket) {
-        socket.disconnect();
-      }
-    };
-  }, [socket]);
-
-  // 导出所有功能作为单个对象
-  const socketRoom = {
-    client,
-    joinRoom,
-    leaveRoom,
-    // sendRoomMessage,
-    // onRoomMessage
-  };
-
-  // 获取用户答案
-  const getUserAnswers = useCallback(async (roomId: string, userId: string): Promise<Answer[]> => {
-    if (!client || !roomId || !userId) return Promise.resolve([]);
-
-    return handleAsyncOperation(async () => {
-      return new Promise((resolve) => {
-        client.emit('exam:getUserAnswers', { roomId, userId }, (answers: Answer[]) => {
-          resolve(answers || []);
-        });
-      });
-    }, '获取用户答案失败');
-  }, [client]);
-
-  const getCurrentQuestion = useCallback(async (roomId: string): Promise<QuizState> => {
-      if (!client) return Promise.reject(new Error('Socket not connected'));
-      
-      return handleAsyncOperation(async () => {
-        return new Promise((resolve, reject) => {
-          client.emit('exam:currentQuestion', { roomId }, (question: QuizState) => {
-            if (!question) {
-              reject(new Error('No current question available'));
-              return;
-            }
-            resolve(question);
-          });
-        });
-      }, '获取当前问题失败');
-  }, [client])
-
-  const sendSettleExam = async (roomId: string) => {
-      if (!client) return;
-      return handleAsyncOperation(async () => {
-        client.emit('exam:settle', { roomId });
-      }, '发送结算消息失败');
-    }
-
-  const answerManagement = {
-    storeAnswer,
-    getAnswers: getAnswersCallback,
-    cleanupRoom,
-    // sendNextQuestion,
-    getPriceHistory,
-    getUserAnswers,
-    getCurrentQuestion,
-    sendSettleExam,
-  };
-
-  // 计算累计结果
-  // const calculateCumulativeResults = useCallback((answers: Answer[]): CumulativeResult[] => {
-  //   const userResults = new Map<string, CumulativeResult>();
-    
-  //   answers.forEach((answer) => {
-  //     const userId = answer.userId;
-  //     if (!userResults.has(userId)) {
-  //       userResults.set(userId, {
-  //         userId,
-  //         totalProfitAmount: answer.totalProfitAmount || 0,
-  //         totalProfitPercent: answer.totalProfitPercent || 0
-  //       });
-  //     }
-  //   });
-
-  //   return Array.from(userResults.values());
-  // }, []);
-
-  return {
-    socketRoom,
-    answerManagement,
-    // calculateCumulativeResults,
-    // currentQuestion,
-    // setCurrentQuestion,
-    // lastMessage,
-    ...socketRoom,
-    ...answerManagement,
-    isConnected,
-  };
-}

+ 0 - 66
src/client/live/components/Exam/types.ts

@@ -1,66 +0,0 @@
-import type { SocketMessage as BaseSocketMessage, SocketMessageType } from '@d8d-appcontainer/types';
-
-// 基础答题记录
-export interface AnswerRecord {
-  date: string;
-  price: string;
-  holdingStock: string;
-  holdingCash: string;
-  profitAmount: number;
-  profitPercent: number;
-  index: number;
-}
-
-// 答题内容
-export interface QuizContent {
-  date: string;
-  price: number | string;
-  holdingStock: string;
-  holdingCash: string;
-  userId: string;
-}
-
-// 题目状态
-export interface QuizState {
-  date: string;
-  price: number | string;
-  id?: string; // 新增可选id字段
-}
-
-export type ExamSocketMessageType = SocketMessageType | 'question' | 'answer' | 'settlement' | 'submit' | 'restart';
-
-// Socket消息
-export interface ExamSocketMessage extends Omit<BaseSocketMessage, 'type' | 'content'> {
-  type: ExamSocketMessageType;
-  content: QuizContent;
-}
-
-// Socket房间消息
-export interface ExamSocketRoomMessage {
-  roomId: string;
-  message: ExamSocketMessage;
-}
-
-// 答案
-export interface Answer extends QuizContent {
-  userId: string;
-  profitAmount?: number;
-  profitPercent?: number;
-  totalProfitAmount?: number;
-  totalProfitPercent?: number;
-}
-
-// 教室数据
-export interface ClassroomData {
-  classroom_no: string;
-  status: string;
-  training_date: string;
-  code: string;
-}
-
-// 累计结果
-export interface CumulativeResult {
-  userId: string;
-  totalProfitAmount: number;
-  totalProfitPercent: number;
-} 

+ 0 - 26
src/client/live/components/NotFoundPage.tsx

@@ -1,26 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router';
-import { Button } from 'antd';
-
-export const NotFoundPage = () => {
-  const navigate = useNavigate();
-  
-  return (
-    <div className="flex flex-col items-center justify-center flex-grow p-4">
-      <div className="max-w-3xl w-full">
-        <h1 className="text-2xl font-bold mb-4">404 - 页面未找到</h1>
-        <p className="mb-6 text-gray-600 dark:text-gray-300">
-          您访问的页面不存在或已被移除
-        </p>
-        <div className="flex gap-4">
-          <Button 
-            type="primary" 
-            onClick={() => navigate('/admin')}
-          >
-            返回首页
-          </Button>
-        </div>
-      </div>
-    </div>
-  );
-};

+ 0 - 37
src/client/live/components/ProtectedRoute.tsx

@@ -1,37 +0,0 @@
-import React, { useEffect } from 'react';
-import { 
-  useNavigate,
-} from 'react-router';
-import { useAuth } from '../hooks/AuthProvider';
-
-
-
-
-
-export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
-  const { isAuthenticated, isLoading } = useAuth();
-  const navigate = useNavigate();
-  
-  useEffect(() => {
-    // 只有在加载完成且未认证时才重定向
-    if (!isLoading && !isAuthenticated) {
-      navigate('/mobile/login', { replace: true });
-    }
-  }, [isAuthenticated, isLoading, navigate]);
-  
-  // 显示加载状态,直到认证检查完成
-  if (isLoading) {
-    return (
-      <div className="flex justify-center items-center h-screen">
-        <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
-      </div>
-    );
-  }
-  
-  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
-  if (!isAuthenticated) {
-    return null;
-  }
-  
-  return children;
-};

+ 0 - 14
src/client/live/components/stock/components/stock-chart/mod.ts

@@ -1,14 +0,0 @@
-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 };

+ 0 - 75
src/client/live/components/stock/components/stock-chart/src/components/DrawingToolbar.tsx

@@ -1,75 +0,0 @@
-import React from 'react';
-import { ActiveType } from '../types/index'
-
-interface DrawingToolbarProps {
-  onStartDrawing: (type: ActiveType) => void;
-  onStopDrawing: () => void;
-  onClearLines: () => void;
-  className?: string;
-}
-
-export const DrawingToolbar: React.FC<DrawingToolbarProps> = ({
-  onStartDrawing,
-  onStopDrawing,
-  onClearLines,
-  className = ''
-}: DrawingToolbarProps) => {
-  const [activeType, setActiveType] = React.useState<ActiveType | null>(null);
-
-  const handleToolClick = (type: ActiveType) => {
-    if (activeType === type) {
-      setActiveType(null);
-      onStopDrawing();
-    } else {
-      setActiveType(type);
-      onStartDrawing(type);
-    }
-  };
-
-  const handleClearClick = () => {
-    setActiveType(null);
-    onStopDrawing();
-    onClearLines();
-  };
-
-  return (
-    <div className={`flex items-center space-x-2 ${className}`}>
-      <button
-        onClick={() => handleToolClick(ActiveType.HORIZONTAL)}
-        className={`px-3 py-1 text-sm font-medium rounded-md transition-colors
-          ${activeType === ActiveType.HORIZONTAL
-            ? 'bg-blue-600 text-white'
-            : 'bg-gray-700 text-gray-200 hover:bg-gray-600'
-          }`}
-      >
-        水平线
-      </button>
-      <button
-        onClick={() => handleToolClick(ActiveType.TREND)}
-        className={`px-3 py-1 text-sm font-medium rounded-md transition-colors
-          ${activeType === ActiveType.TREND
-            ? 'bg-blue-600 text-white'
-            : 'bg-gray-700 text-gray-200 hover:bg-gray-600'
-          }`}
-      >
-        斜横线
-      </button>
-      <button
-        onClick={() => handleToolClick(ActiveType.TREND_EXTENDED)}
-        className={`px-3 py-1 text-sm font-medium rounded-md transition-colors
-          ${activeType === ActiveType.TREND_EXTENDED
-            ? 'bg-blue-600 text-white'
-            : 'bg-gray-700 text-gray-200 hover:bg-gray-600'
-          }`}
-      >
-        趋势线
-      </button>
-      <button
-        onClick={handleClearClick}
-        className="px-3 py-1 text-sm font-medium text-gray-200 bg-red-600 rounded-md hover:bg-red-700 transition-colors"
-      >
-        清除
-      </button>
-    </div>
-  );
-}; 

+ 0 - 26
src/client/live/components/stock/components/stock-chart/src/components/MemoToggle.tsx

@@ -1,26 +0,0 @@
-import React from 'react';
-
-interface MemoToggleProps {
-  onToggle: (visible: boolean) => void;
-  className?: string;
-}
-
-export default function MemoToggle({ onToggle, className }: MemoToggleProps) {
-  const [visible, setVisible] = React.useState(true);
-
-  const handleClick = () => {
-    const newVisible = !visible;
-    setVisible(newVisible);
-    onToggle(newVisible);
-  };
-
-  return (
-    <button
-      type="button"
-      onClick={handleClick}
-      className={className}
-    >
-      {visible ? '隐藏提示' : '显示提示'}
-    </button>
-  );
-} 

+ 0 - 65
src/client/live/components/stock/components/stock-chart/src/components/ProfitDisplay.tsx

@@ -1,65 +0,0 @@
-import React from 'react';
-import type { ProfitSummary } from '../types/index';
-
-interface ProfitDisplayProps {
-  profitSummary: ProfitSummary;
-}
-
-export const ProfitDisplay: React.FC<ProfitDisplayProps> = ({ 
-  profitSummary 
-}: ProfitDisplayProps) => {
-  const { totalProfit, dailyStats } = profitSummary;
-  
-  return (
-    <div className="flex justify-between items-center p-4 bg-gray-800 text-white shadow-lg">
-      {/* 累计收益 */}
-      <div className="flex items-center space-x-2">
-        <span className="text-gray-400">累计收益</span>
-        <span className={`text-xl font-bold ${totalProfit >= 0 ? 'text-red-500' : 'text-green-500'}`}>
-          {totalProfit >= 0 ? '+' : ''}{totalProfit.toFixed(2)}
-        </span>
-      </div>
-
-      {/* 行情数据 */}
-      <div className="flex items-center space-x-6">
-        {/* 日期 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">日期</span>
-          <span className="font-medium">{dailyStats.date}</span>
-        </div>
-
-        {/* 开盘价 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">开</span>
-          <span className="font-medium">{dailyStats.open.toFixed(2)}</span>
-        </div>
-
-        {/* 最高价 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">高</span>
-          <span className="font-medium text-red-500">{dailyStats.high.toFixed(2)}</span>
-        </div>
-
-        {/* 收盘价 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">收</span>
-          <span className="font-medium">{dailyStats.close.toFixed(2)}</span>
-        </div>
-
-        {/* 最低价 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">低</span>
-          <span className="font-medium text-green-500">{dailyStats.low.toFixed(2)}</span>
-        </div>
-
-        {/* 涨跌幅 */}
-        <div className="flex flex-col items-center">
-          <span className="text-gray-400 text-sm">涨跌幅</span>
-          <span className={`font-medium ${dailyStats.change >= 0 ? 'text-red-500' : 'text-green-500'}`}>
-            {dailyStats.change >= 0 ? '+' : ''}{dailyStats.change.toFixed(2)}%
-          </span>
-        </div>
-      </div>
-    </div>
-  );
-}; 

+ 0 - 246
src/client/live/components/stock/components/stock-chart/src/components/StockChart.tsx

@@ -1,246 +0,0 @@
-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';
-import type { StockData, DateMemo, TradeRecord, ActiveType } from '../types/index';
-import { ChartDrawingTools } from '../lib/drawing/ChartDrawingTools';
-
-// 将 StockChartRef 接口移到 Props 定义之前
-interface StockChartRef {
-  toggleMemoVisibility: (visible: boolean) => void;
-  startDrawing: (type: ActiveType) => void;
-  stopDrawing: () => void;
-  clearDrawings: () => void;
-}
-
-interface StockChartProps {
-  stockData: StockData[];
-  memoData?: DateMemo[];
-  width?: string | number;
-  height?: string | number;
-  className?: string;
-  onChartReady?: (chart: EChartsType) => void;
-  trades?: TradeRecord[];
-}
-
-// 添加自定义类型定义
-interface ScatterDataItemOption {
-  value: [number, number];
-  symbol?: string;
-  symbolSize?: number;
-  symbolRotate?: number;
-  label?: {
-    show?: boolean;
-    formatter?: string;
-    position?: 'top' | 'bottom' | 'left' | 'right';
-    backgroundColor?: string;
-    borderColor?: string;
-    color?: string;
-    padding?: number;
-    borderRadius?: number;
-    shadowBlur?: number;
-    shadowColor?: string;
-  };
-  itemStyle?: {
-    color?: string;
-  };
-}
-
-// 修改组件定义为 forwardRef,添加解构参数的类型
-const StockChart = forwardRef<StockChartRef, StockChartProps>((
-  props: StockChartProps, 
-  ref: React.ForwardedRef<StockChartRef>
-) => {
-  const chartRef = useRef<HTMLDivElement>(null);
-  const chartInstanceRef = useRef<EChartsType | null>(null);
-  const stockChartRef = useRef<StockChartLib | null>(null);
-  const drawingToolsRef = useRef<ChartDrawingTools | null>(null);
-
-  // 初始化图表和工具 - 只执行一次
-  useEffect(() => {
-    if (!chartRef.current) return;
-
-    const chartInstance = echarts.init(chartRef.current);
-    chartInstanceRef.current = chartInstance;
-    
-    // 创建 StockChart 实例
-    const stockChart = new StockChartLib(props.stockData, props.memoData, chartInstance);
-    stockChartRef.current = stockChart;
-
-    // 初始化画线工具 - 只初始化一次
-    drawingToolsRef.current = new ChartDrawingTools(chartInstance);
-
-    // 设置初始配置
-    const option = stockChart.createChartOption();
-    chartInstance.setOption(option as EChartsOption);
-
-    // 在设置完图表配置后初始化绘图工具
-    // stockChart.initDrawingTools();
-
-    // // 绑定鼠标事件
-    // const zr = chartInstance.getZr();
-    // zr.on('click', (params: any) => {
-    //   stockChart.handleMouseEvent('click', params);
-    // });
-
-    // zr.on('mousedown', (params: any) => {
-    //   stockChart.handleMouseEvent('mousedown', params);
-    // });
-
-    // zr.on('mousemove', (params: any) => {
-    //   stockChart.handleMouseEvent('mousemove', params);
-    // });
-
-    // 通知外部图表已准备就绪
-    props.onChartReady?.(chartInstance);
-
-    // 清理函数
-    return () => {
-      // zr.off('click');
-      // zr.off('mousedown');
-      // zr.off('mousemove');
-      chartInstance.dispose();
-    };
-  }, []); // 空依赖数组,只执行一次
-
-  // 处理数据更新
-  useEffect(() => {
-    if (!chartRef.current || !chartInstanceRef.current || !stockChartRef.current) return;
-    
-    const chartInstance = chartInstanceRef.current;
-    const stockChart = stockChartRef.current;
-    
-    // 更新 StockChart 实例的数据
-    stockChart.updateData(props.stockData, props.memoData);
-    
-    // 更新图表数据
-    const option = stockChart.createChartOption();
-    // console.log('option', option);
-    // if (!option) return;
-
-    // 保持原有的 markLine 数据
-    const currentOption = chartInstance.getOption() as EChartsOption;
-    
-    if (currentOption && currentOption.series && Array.isArray(currentOption.series)) {
-      const series = currentOption.series as echarts.SeriesOption[];
-      const existingMarkLine = series[0] && (series[0] as any).markLine;
-      if (existingMarkLine) {
-        (option.series[0] as any).markLine = existingMarkLine;
-      }
-    }
-
-    chartInstance.setOption(option);
-    // console.log('currentOption', chartInstance.getOption());
-    // 重新绘制所有线条
-    drawingToolsRef.current?.redrawLines();
-  }, [props.stockData, props.memoData]);
-
-  // 处理窗口大小变化
-  useEffect(() => {
-    const handleResize = () => {
-      chartInstanceRef.current?.resize();
-    };
-
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
-  }, []);
-
-  // 将 toggleMemoVisibility 方法暴露给父组件
-  useImperativeHandle(ref, () => ({
-    toggleMemoVisibility: (visible: boolean) => {
-      if (!stockChartRef.current || !chartInstanceRef.current) return;
-
-      const currentOption = chartInstanceRef.current.getOption();
-      const stockChart = stockChartRef.current;
-
-      stockChart.toggleMemoVisibility(visible);
-      stockChart.updateMemoVisibility({
-        ...currentOption,
-        series: currentOption.series
-      });
-      chartInstanceRef.current.setOption(currentOption);
-    },
-    startDrawing: (type: ActiveType) => {
-      drawingToolsRef.current?.startDrawing(type);
-    },
-    stopDrawing: () => {
-      drawingToolsRef.current?.stopDrawing();
-    },
-    clearDrawings: () => {
-      drawingToolsRef.current?.clearAllLines();
-    }
-  }));
-
-  // 添加交易标记渲染
-  useEffect(() => {
-    if (!chartInstanceRef.current || !stockChartRef.current || !props.trades?.length) return;
-    
-    const tradeMarkSeries: echarts.ScatterSeriesOption = {
-      name: "Mark",
-      type: "scatter",
-      xAxisIndex: 0,
-      yAxisIndex: 0,
-      data: props.trades.map((trade: TradeRecord, index: number) => {
-        const dataIndex = props.stockData.findIndex((data: StockData) => data.d === trade.date);
-        if (dataIndex === -1) return null;
-
-        const dayData = props.stockData[dataIndex];
-        const price = trade.type === 'BUY' 
-          ? parseFloat(dayData.h)
-          : parseFloat(dayData.l);
-
-        return {
-          value: [dataIndex, price],
-          symbol: "triangle",
-          symbolSize: 10,
-          symbolRotate: trade.type === 'BUY' ? 180 : 0,
-          label: {
-            show: true,
-            formatter: trade.type === 'BUY' ? 'B' : 'S',
-            position: trade.type === 'BUY' ? 'top' : 'bottom',
-            backgroundColor: '#FFA500',
-            borderColor: '#ffffff',
-            color: '#ffffff',
-            padding: 2,
-            borderRadius: 2,
-            shadowBlur: 2,
-            shadowColor: '#333',
-          },
-          itemStyle: {
-            color: trade.type === 'BUY' ? '#00da3c' : '#ec0000',
-          }
-        } as ScatterDataItemOption;
-      }).filter((item: unknown): item is ScatterDataItemOption => item !== null),
-      tooltip: {
-        show: false
-      }
-    };
-
-    const currentOption = chartInstanceRef.current.getOption();
-    
-    // 找到并移除旧的 Mark 系列(如果存在)
-    const markSeriesIndex = currentOption.series.findIndex((s: { name?: string }) => s.name === 'Mark');
-    if (markSeriesIndex > -1) {
-      currentOption.series.splice(markSeriesIndex, 1);
-    }
-    
-    (currentOption.series as echarts.SeriesOption[]).push(tradeMarkSeries);
-    
-    chartInstanceRef.current.setOption(currentOption);
-  }, [props.trades, props.stockData]);
-
-  return (
-    <div
-      ref={chartRef}
-      style={{ width: "100%", height: "100%" }}
-      className={props.className}
-    />
-  );
-});
-
-// 添加显示名称
-StockChart.displayName = 'StockChart';
-
-// 导出组件和相关类型,移除重复的 StockChartRef 导出
-export default StockChart;
-export type { StockChartRef, StockChartProps };

+ 0 - 33
src/client/live/components/stock/components/stock-chart/src/components/TradePanel.tsx

@@ -1,33 +0,0 @@
-import React from 'react';
-
-interface TradePanelProps {
-  hasBought: boolean;
-  onToggleTrade: (type: 'BUY' | 'SELL') => void;
-}
-
-export const TradePanel: React.FC<TradePanelProps> = ({
-  hasBought,
-  onToggleTrade,
-}: TradePanelProps) => {
-  return (
-    <div className="flex items-center justify-center p-4 bg-gray-800 rounded-lg shadow-lg">
-      <div className="flex space-x-4">
-        {hasBought ? (
-          <button 
-            onClick={() => onToggleTrade('SELL')}
-            className="px-6 py-2 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors"
-          >
-            卖出
-          </button>
-        ) : (
-          <button 
-            onClick={() => onToggleTrade('BUY')}
-            className="px-6 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors"
-          >
-            买入
-          </button>
-        )}
-      </div>
-    </div>
-  );
-}; 

+ 0 - 85
src/client/live/components/stock/components/stock-chart/src/hooks/useProfitCalculator.ts

@@ -1,85 +0,0 @@
-import { useState, useCallback, useMemo } from 'react';
-import type { TradeRecord, DailyProfit, ProfitSummary, StockData } from '../types/index';
-
-export function useProfitCalculator(stockData: StockData[], trades: TradeRecord[]) {
-  const [currentDate, setCurrentDate] = useState<string>('');
-
-  // 计算每日收益
-  const dailyProfits = useMemo(() => {
-    const profitMap = new Map<string, DailyProfit>();
-    let accumulatedProfit = 0;  // 累计收益
-    
-    trades.forEach(trade => {
-      const date = trade.date;
-      if (!profitMap.has(date)) {
-        profitMap.set(date, {
-          date,
-          profit: accumulatedProfit,  // 使用当前累计收益作为初始值
-          trades: [],
-        });
-      }
-      
-      const dailyProfit = profitMap.get(date)!;
-      dailyProfit.trades.push(trade);
-      
-      if (trade.type === 'SELL' && trade.buyPrice !== undefined) {
-        // 本次收益 = (当天收盘价 - 买入收盘价) / 买入收盘价
-        const currentProfit = (trade.price - trade.buyPrice) / trade.buyPrice;
-        // 累计收益 = 之前的累计收益 + 本次收益
-        accumulatedProfit += currentProfit;
-        // 记录当日收益为累计收益
-        dailyProfit.profit = accumulatedProfit;
-      }
-    });
-    
-    return Array.from(profitMap.values());
-  }, [trades]);
-
-  // 计算当日行情
-  const profitSummary = useMemo(() => {
-    // 获取累计收益
-    const totalProfit = trades.reduce((sum, trade) => {
-      if (trade.type === 'SELL' && trade.buyPrice !== undefined) {
-        return sum + (trade.price - trade.buyPrice) / trade.buyPrice;
-      }
-      return sum;
-    }, 0);
-    
-    // 获取当日行情数据
-    // 如果没有指定currentDate,则使用最新一天的数据
-    const currentStockData = currentDate ? 
-      stockData.find((data: StockData) => data.d === currentDate) :
-      stockData[stockData.length - 1];
-
-    const dailyStats = currentStockData ? {
-      date: currentStockData.d, // 添加日期
-      open: parseFloat(currentStockData.o),
-      high: parseFloat(currentStockData.h),
-      close: parseFloat(currentStockData.c),
-      low: parseFloat(currentStockData.l),
-      change: parseFloat(currentStockData.zd),
-    } : {
-      date: '', // 添加日期
-      open: 0,
-      high: 0,
-      close: 0,
-      low: 0,
-      change: 0,
-    };
-
-    return {
-      totalProfit,
-      dailyStats,
-    };
-  }, [dailyProfits, currentDate, stockData, trades]);
-  // 更新当前日期
-  const updateCurrentDate = useCallback((date: string) => {
-    setCurrentDate(date);
-  }, []);
-
-  return {
-    dailyProfits,
-    profitSummary,
-    updateCurrentDate,
-  };
-} 

+ 0 - 62
src/client/live/components/stock/components/stock-chart/src/hooks/useStockDataFilter.ts

@@ -1,62 +0,0 @@
-import { useState, useCallback } from 'react';
-import type { StockData } from '../types/index';
-
-export function useStockDataFilter(fullData: StockData[]) {
-  const [dayNum, setDayNum] = useState(120); // 默认120天
-  const [offsetNum, setOffsetNum] = useState(120); // 默认偏移120天
-  const [isInitialized, setIsInitialized] = useState(false);
-
-  const filterData = useCallback(() => {
-    if (!isInitialized) {
-      return []; // 未初始化时返回空数组
-    }
-
-    const arrLen = fullData.length;
-    // 从最后一天开始往前数 offsetNum 天
-    let endIndex = arrLen - offsetNum;
-    // 从 endIndex 再往前数 dayNum 天
-    let startIndex = endIndex - dayNum;
-    
-    // 确保索引在有效范围内
-    startIndex = Math.max(0, startIndex);
-    endIndex = Math.max(dayNum, endIndex); // 确保至少显示 dayNum 天的数据
-    
-    return fullData.slice(startIndex, endIndex);
-  }, [fullData, dayNum, offsetNum, isInitialized]);
-
-  const moveToNextDay = useCallback(() => {
-    return new Promise<string>((resolve) => {
-      setOffsetNum((prev: number) => {
-        const newOffset = Math.max(0, prev - 1);
-        // 计算新的结束索引
-        const endIndex = fullData.length - newOffset;
-        // 返回最新日期
-        const nextDate = fullData[endIndex - 1]?.d || '';
-        resolve(nextDate);
-        return newOffset;
-      });
-    });
-  }, [fullData]);
-
-  const resetOffset = useCallback(() => {
-    setOffsetNum(0);
-  }, []);
-
-  const setDayNumWithOffset = useCallback((num: number) => {
-    setDayNum(num);
-    setOffsetNum(num);
-  }, []);
-
-  const initializeView = useCallback(() => {
-    setIsInitialized(true);
-  }, []);
-
-  return {
-    filteredData: filterData(),
-    moveToNextDay,
-    resetOffset,
-    setDayNum: setDayNumWithOffset,
-    initializeView,
-    isInitialized
-  };
-} 

+ 0 - 94
src/client/live/components/stock/components/stock-chart/src/hooks/useStockQueries.ts

@@ -1,94 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-import { stockDataClient } from '@/client/api';
-import { dateNotesClient } from '@/client/api';
-import type { StockData, DateMemo } from '../types/index';
-import { toast } from 'react-toastify';
-import { useEffect } from 'react';
-import type { InferResponseType } from 'hono/client';
-
-// 定义响应类型
-type StockHistoryResponse = InferResponseType<typeof stockDataClient.history[':code']['$get'], 200>;
-type DateNotesListResponse = InferResponseType<typeof dateNotesClient.$get, 200>;
-
-export function useStockQueries(code?: string) {
-  // 查询股票历史数据
-  const {
-    data: stockDataResponse,
-    isLoading: isLoadingStock,
-    error: stockError,
-    refetch: refetchStock
-  } = useQuery<StockHistoryResponse>({
-    queryKey: ['stockHistory', code],
-    queryFn: async () => {
-      if (!code) throw new Error('股票代码不能为空');
-      const response = await stockDataClient.history[':code'].$get({
-        param: { code }
-      });
-      if (!response.ok) {
-        throw new Error('获取股票历史数据失败');
-      }
-      return response.json();
-    },
-    enabled: !!code,
-    retry: 0,
-  });
-
-  // 查询备忘录数据
-  const {
-    data: memoDataResponse,
-    isLoading: isLoadingMemo,
-    error: memoError,
-    refetch: refetchMemo
-  } = useQuery<DateNotesListResponse>({
-    queryKey: ['memoData', code],
-    queryFn: () => dateNotesClient.$get({
-      query: {
-        filters: JSON.stringify(code ? { code } : {}),
-        page: 1,
-        pageSize: 1000
-      }
-    }).then(res => res.json()),
-    enabled: false,
-  });
-
-  // 转换数据格式
-  const stockData = (stockDataResponse?.data || []) as StockData[];
-
-  const memoData = (memoDataResponse?.data?.map(item => ({
-    _id: item.id,
-    代码: item.code,
-    日期: item.noteDate,
-    提示: item.note,
-  })) || []) as DateMemo[];
-
-  const isLoading = isLoadingStock || isLoadingMemo;
-  const error = stockError || memoError;
-
-  useEffect(() => {
-    if (isLoading) {
-      toast.loading('正在加载数据...', { toastId: 'stockLoading' })
-    } else {
-      toast.done('stockLoading')
-      if (error instanceof Error) {
-        toast.error('加载数据失败,请稍后重试')
-      }
-    }
-  }, [isLoading, error]);
-
-  // 提供一个函数来手动触发查询
-  const fetchData = async () => {
-    if (!code) return;
-    await Promise.all([
-      refetchStock(),
-      refetchMemo()
-    ]);
-  };
-
-  return {
-    stockData,
-    memoData,
-    isLoading,
-    error,
-    fetchData,
-  };
-}

+ 0 - 63
src/client/live/components/stock/components/stock-chart/src/hooks/useTradeRecords.ts

@@ -1,63 +0,0 @@
-import { useState, useCallback } from 'react';
-import type { TradeRecord, TradeRecordGroup, StockData } from '../types/index';
-
-export function useTradeRecords(stockData: StockData[]) {
-  const [trades, setTrades] = useState<TradeRecord[]>([]);
-  const [tradeGroups, setTradeGroups] = useState<TradeRecordGroup[]>([]);
-  const [hasBought, setHasBought] = useState(false);
-  const [buyPrice, setBuyPrice] = useState(0);
-
-  const toggleTrade = useCallback((type: 'BUY' | 'SELL') => {
-    if (type === 'BUY' && hasBought) return;
-    if (type === 'SELL' && !hasBought) return;
-
-    const currentDate = stockData[stockData.length - 1]?.d;
-    if (!currentDate) return;
-
-    const closePrice = parseFloat(stockData[stockData.length - 1].c);
-
-    if (type === 'BUY') {
-      setHasBought(true);
-      setBuyPrice(closePrice);
-    } else {
-      setHasBought(false);
-      setBuyPrice(0);
-    }
-
-    const newTrade: TradeRecord = {
-      type,
-      price: closePrice,
-      timestamp: Date.now(),
-      date: currentDate,
-      ...(type === 'SELL' ? { buyPrice: buyPrice } : {})
-    };
-
-    setTrades((prev: TradeRecord[]) => [...prev, newTrade]);
-    setTradeGroups((prevGroups: TradeRecordGroup[]) => {
-      const existingGroup = prevGroups.find(
-        (group: TradeRecordGroup) => group.date === currentDate && group.type === type
-      );
-
-      if (existingGroup) {
-        return prevGroups.map((group: TradeRecordGroup) => 
-          group === existingGroup
-            ? { ...group, records: [...group.records, newTrade] }
-            : group
-        );
-      }
-
-      return [...prevGroups, {
-        date: currentDate,
-        type,
-        records: [newTrade]
-      }];
-    });
-  }, [stockData, hasBought, buyPrice]);
-
-  return {
-    trades,
-    tradeGroups,
-    toggleTrade,
-    hasBought,
-  };
-} 

+ 0 - 77
src/client/live/components/stock/components/stock-chart/src/lib/DateMemoHandler.ts

@@ -1,77 +0,0 @@
-import type { DateMemo, ChartOption, SplitData } from '../types/index.ts';
-
-export class DateMemoHandler {
-  private dateMemos: DateMemo[];
-  private memoVisible: boolean = true;
-  
-  constructor(dateMemos: DateMemo[]) {
-    this.dateMemos = dateMemos;
-  }
-
-  public addDateMemoMarkers(option: ChartOption, splitData: SplitData): ChartOption {
-    const memoMarkers = this.dateMemos
-      .map(memo => {
-        const index = splitData.categoryData.indexOf(memo.日期);
-        
-        if (index !== -1) {
-          return {
-            value: [index, splitData.values[index].value[1]],
-            symbol: 'rect',
-            symbolSize: [20, 40],
-            label: {
-              show: this.memoVisible,
-              position: 'top',
-              formatter: memo.提示.split('').join('\n'),
-              color: 'red',
-              backgroundColor: 'yellow',
-              padding: 4,
-              borderRadius: 2,
-              fontWeight: 'bold'
-            },
-            itemStyle: {
-              color: 'transparent'
-            }
-          };
-        }
-        return null;
-      })
-      .filter(item => item !== null);
-
-    const existingSeriesIndex = option.series.findIndex(
-      series => series.name === 'DateMemo'
-    );
-
-    if (existingSeriesIndex !== -1) {
-      option.series[existingSeriesIndex].data = memoMarkers;
-    } else {
-      option.series.push({
-        name: 'DateMemo',
-        type: 'scatter',
-        data: memoMarkers,
-        tooltip: {
-          show: false
-        }
-      } as any);
-    }
-
-    return option;
-  }
-
-  public toggleMemoVisibility(visible: boolean): void {
-    this.memoVisible = visible;
-  }
-
-  public updateMemoVisibility(option: ChartOption): void {
-    const dateMemoSeries = option.series.find(
-      series => series.name === 'DateMemo'
-    );
-
-    if (dateMemoSeries && dateMemoSeries.data) {
-      dateMemoSeries.data.forEach((item: any) => {
-        if (item && item.label) {
-          item.label.show = this.memoVisible;
-        }
-      });
-    }
-  }
-} 

+ 0 - 114
src/client/live/components/stock/components/stock-chart/src/lib/StockChart.ts

@@ -1,114 +0,0 @@
-import { ChartBaseConfig } from './config/ChartBaseConfig';
-import { DataProcessor } from './data/DataProcessor';
-import { MarkerProcessor } from './markers/MarkerProcessor';
-import type { StockData, DateMemo, ChartOption, SplitData } from '../types/index';
-import { DrawingTools } from './drawing/DrawingTools';
-import { DateMemoHandler } from './DateMemoHandler';
-
-export class StockChart {
-  private readonly dataProcessor: DataProcessor;
-  private readonly markerProcessor: MarkerProcessor;
-  private data: StockData[];
-  private dateMemos: DateMemo[];
-  private memoVisible: boolean = true;
-  private readonly drawingTools: DrawingTools;
-  private dateMemoHandler: DateMemoHandler;
-
-  constructor(data: StockData[], dateMemos: DateMemo[] = [], chart: any) {
-    this.data = data;
-    this.dateMemos = dateMemos;
-    this.dataProcessor = new DataProcessor();
-    this.markerProcessor = new MarkerProcessor();
-    this.drawingTools = new DrawingTools(chart);
-    this.dateMemoHandler = new DateMemoHandler(dateMemos);
-  }
-
-  public createChartOption(): ChartOption {
-    const processedData = this.dataProcessor.processData(this.data);
-    const splitData = this.dataProcessor.splitData(processedData);
-    
-    const option = ChartBaseConfig.createBaseOption(splitData.categoryData);
-    const chartOption = option as ChartOption;
-
-    // 添加K线图系列
-    chartOption.series.push({
-      name: 'Values',
-      type: 'candlestick',
-      data: splitData.values
-    });
-
-    // 添加成交量系列
-    chartOption.series.push({
-      name: 'Volumes',
-      type: 'bar',
-      xAxisIndex: 1,
-      yAxisIndex: 1,
-      data: splitData.volumes,
-      ...ChartBaseConfig.getVolumeBarStyle()
-    });
-
-    this.markerProcessor.add2OnTopOfVolumeMarkers(chartOption, splitData);
-    this.markerProcessor.add3OnTopOfVolumeMarkers(chartOption, splitData);
-    this.markerProcessor.addMiddleLinesToChartOption(chartOption, splitData);
-
-    // 添加日期备注标记
-    return this.dateMemoHandler.addDateMemoMarkers(chartOption, splitData);
-  }
-
-  // 切换备注显示状态
-  public toggleMemoVisibility(visible: boolean): void {
-    this.dateMemoHandler.toggleMemoVisibility(visible);
-  }
-
-  // 更新图表配置中的备注可见性
-  public updateMemoVisibility(option: ChartOption): void {
-    this.dateMemoHandler.updateMemoVisibility(option);
-  }
-
-  private tooltipFormatter(params: any[]): string {
-    const param = params[0];
-    if (param.seriesName === 'Values') {
-      const value = param.value;
-      return `${param.name}<br/>
-        开: ${value[1]}<br/>
-        收: ${value[2]}<br/>
-        高: ${value[4]}<br/>
-        低: ${value[3]}<br/>
-        涨幅: ${value[5]}<br/>`;
-    } else if (param.seriesName === 'Volumes') {
-      return `${param.name}<br/>成交量: ${param.value[1]}`;
-    }
-    return '';
-  }
-
-  public getSplitData(): SplitData {
-    const processedData = this.dataProcessor.processData(this.data);
-    return this.dataProcessor.splitData(processedData);
-  }
-
-  public getMemoVisible(): boolean {
-    return this.memoVisible;
-  }
-
-  // 添加绘图工具按钮
-  public initDrawingTools(): void {
-    this.drawingTools.initDrawingTools();
-  }
-
-  // 处理鼠标事件
-  public handleMouseEvent(event: string, params: any): void {
-    this.drawingTools.handleMouseEvent(event, params);
-  }
-
-  // 清除所有绘制的线条
-  public clearDrawings(): void {
-    this.drawingTools.clearMarkLine();
-  }
-
-  // 添加更新方法
-  public updateData(data: StockData[], dateMemos: DateMemo[] = []): void {
-    this.data = data;
-    this.dateMemos = dateMemos;
-    this.dateMemoHandler = new DateMemoHandler(dateMemos);
-  }
-} 

+ 0 - 163
src/client/live/components/stock/components/stock-chart/src/lib/config/ChartBaseConfig.ts

@@ -1,163 +0,0 @@
-import type { EChartsOption } from 'echarts';
-// import type { CallbackDataParams } from 'echarts/types/src/util/types';
-import { CHART_COLORS } from '../constants/colors';
-
-export class ChartBaseConfig {
-  static createBaseOption(categoryData: string[]): EChartsOption {
-    return {
-      tooltip: {
-        trigger: 'axis',
-        axisPointer: { type: 'cross' },
-        formatter: (params) => {
-          // 确保 params 是数组
-          const paramArray = Array.isArray(params) ? params : [params];
-          const param = paramArray[0];
-          
-          if (param.seriesName === 'Values') {
-            const value = param.value as number[];
-            return `${param.name}<br/>
-              <!-- 序号: ${value[0]}<br/> -->
-              开: ${value[1]}<br/>
-              收: ${value[2]}<br/>
-              低: ${value[3]}<br/>
-              高: ${value[4]}<br/>
-              涨幅: ${value[5]}<br/>`;
-          } else if (param.seriesName === 'Volumes') {
-            const value = param.value as number[];
-            return `${param.name}<br/>成交量: ${value[1]}`;
-          }
-          return '';
-        }
-      },
-      dataZoom: [
-        {
-          type: 'inside',
-          xAxisIndex: [0, 1],
-          startValue: categoryData.length - 30,
-          endValue: categoryData.length - 1,
-          minValueSpan: 10,
-          maxValueSpan: 120
-        },
-        {
-          show: true,
-          xAxisIndex: [0, 1],
-          type: 'slider',
-          top: '85%',
-          startValue: categoryData.length - 30,
-          endValue: categoryData.length - 1,
-          minValueSpan: 10,
-          maxValueSpan: 120
-        }
-      ],
-      grid: [
-        {
-          left: '10%',
-          right: '8%',
-          height: '50%',
-          backgroundColor: 'rgba(0, 0, 0, 0.1)'
-        },
-        {
-          left: '10%',
-          right: '8%',
-          top: '63%',
-          height: '16%',
-          backgroundColor: 'rgba(0, 0, 0, 0.1)'
-        }
-      ],
-      xAxis: this.createXAxisConfig(categoryData),
-      yAxis: this.createYAxisConfig(),
-      axisPointer: {
-        link: [{ xAxisIndex: 'all' }],
-        label: { backgroundColor: '#777' }
-      },
-      series: [] as any[]
-    };
-  }
-
-  static getUpColor(): string {
-    return CHART_COLORS.UP;
-  }
-
-  static getDownColor(): string {
-    return CHART_COLORS.DOWN;
-  }
-
-  static getOperateColor(): string {
-    return CHART_COLORS.OPERATE;
-  }
-
-  static getLimitUpColor(): string {
-    return CHART_COLORS.LIMIT_UP;
-  }
-
-  static getLimitDownColor(): string {
-    return CHART_COLORS.LIMIT_DOWN;
-  }
-
-  static getMiddleLineColor(): string {
-    return CHART_COLORS.MIDDLE_LINE;
-  }
-
-  static getVolumeBarStyle() {
-    return {
-      itemStyle: {
-        color: (params: any) => {
-          return params.value[2] > 0 ? CHART_COLORS.UP : CHART_COLORS.DOWN;
-        }
-      }
-    };
-  }
-
-  private static createXAxisConfig(categoryData: string[]) {
-    return [
-      {
-        type: 'category' as const,
-        data: categoryData,
-        boundaryGap: false,
-        axisLine: { onZero: false },
-        splitLine: { show: false },
-        min: 'dataMin',
-        max: 'dataMax',
-        axisPointer: { z: 100 }
-      },
-      {
-        type: 'category' as const,
-        gridIndex: 1,
-        data: categoryData,
-        boundaryGap: false,
-        axisLine: { onZero: false },
-        axisTick: { show: false },
-        splitLine: { show: false },
-        axisLabel: { show: false },
-        min: 'dataMin',
-        max: 'dataMax'
-      }
-    ];
-  }
-
-  private static createYAxisConfig() {
-    return [
-      {
-        scale: true,
-        splitArea: { show: true },
-        splitLine: {
-          show: true,
-          lineStyle: {
-            type: 'dashed',
-            color: 'rgba(255, 255, 255, 0.6)', // 白色虚线,透明度60%
-            width: 1
-          }
-        }
-      },
-      {
-        scale: true,
-        gridIndex: 1,
-        splitNumber: 2,
-        axisLabel: { show: false },
-        axisLine: { show: false },
-        axisTick: { show: false },
-        splitLine: { show: false }
-      }
-    ];
-  }
-} 

+ 0 - 9
src/client/live/components/stock/components/stock-chart/src/lib/constants/colors.ts

@@ -1,9 +0,0 @@
-export const CHART_COLORS = {
-  UP_FILL: 'white',          // 上涨填充色
-  UP: '#ec0000',            // 上涨边框色
-  DOWN: '#00da3c',          // 下跌色
-  LIMIT_UP: '#ff1493',      // 涨停板色
-  LIMIT_DOWN: '#4169e1',    // 跌停板色
-  MIDDLE_LINE: '#333',      // 中间线颜色
-  OPERATE: '#FFA500'        // 操作标记颜色
-} as const; 

+ 0 - 67
src/client/live/components/stock/components/stock-chart/src/lib/data/DataProcessor.ts

@@ -1,67 +0,0 @@
-import type { StockData, ProcessedData, SplitData } from '../../types/index';
-import { CHART_COLORS } from '../constants/colors';
-
-export class DataProcessor {
-  private readonly upFillColor = CHART_COLORS.UP_FILL;
-  private readonly upColor = CHART_COLORS.UP;
-  private readonly downColor = CHART_COLORS.DOWN;
-  private readonly limitUpColor = CHART_COLORS.LIMIT_UP;
-  private readonly limitDownColor = CHART_COLORS.LIMIT_DOWN;
-
-  processData(data: StockData[]): ProcessedData[] {
-    return data.map(item => this.processStockItem(item));
-  }
-
-  splitData(processedData: ProcessedData[]): SplitData {
-    const categoryData: string[] = [];
-    const values: any[] = [];
-    const volumes: [number, string, number][] = [];
-
-    processedData.forEach((item, i) => {
-      categoryData.push(item.categoryData);
-      values.push(item.values);
-
-      const open = Number(item.values.value[0]);
-      const close = Number(item.values.value[1]);
-      const zd = Number(item.values.value[4]);
-      const direction = (zd < 0 || open > close) ? -1 : 1;
-
-      volumes.push([i, item.volumes, direction]);
-    });
-
-    return { categoryData, values, volumes };
-  }
-
-  private processStockItem(item: StockData): ProcessedData {
-    const open = Number(item.o);
-    const close = Number(item.c);
-    const zd = Number(item.zd);
-    const isDown = zd < 0 || open > close;
-    const isLimitUp = zd > 9.7;
-    const isLimitDown = zd < -9.7;
-
-    return {
-      categoryData: item.d,
-      values: {
-        value: [item.o, item.c, item.l, item.h, item.pc || item.zd],
-        itemStyle: {
-          color: this.getItemColor(isDown, isLimitUp, isLimitDown),
-          borderColor: this.getBorderColor(isDown, isLimitUp, isLimitDown)
-        }
-      },
-      volumes: item.v
-    };
-  }
-
-  private getItemColor(isDown: boolean, isLimitUp: boolean, isLimitDown: boolean): string {
-    if (isLimitUp) return this.limitUpColor;
-    if (isLimitDown) return this.limitDownColor;
-    return isDown ? this.downColor : this.upColor;
-  }
-
-  private getBorderColor(isDown: boolean, isLimitUp: boolean, isLimitDown: boolean): string {
-    if (isLimitUp) return this.limitUpColor;
-    if (isLimitDown) return this.limitDownColor;
-    return isDown ? this.downColor : this.upColor;
-  }
-} 

+ 0 - 552
src/client/live/components/stock/components/stock-chart/src/lib/drawing/ChartDrawingTools.ts

@@ -1,552 +0,0 @@
-import type { EChartsType, EChartsOption } from 'echarts';
-import {Decimal} from 'decimal.js';
-
-interface DrawingLine {
-  id: string;
-  type: 'horizontal' | 'trend' | 'trendExtended';
-  points: {
-    xRatio: number;
-    yValue: number;
-    dataIndex?: number;
-    date?: string;
-  }[];
-  style?: {
-    color?: string;
-    width?: number;
-    type?: 'solid' | 'dashed';
-  };
-}
-
-// 添加 ECharts X轴配置的类型定义
-interface XAxisOption {
-  data: string[];
-}
-
-// 在文件顶部添加 dataZoom 的类型定义
-interface DataZoomOption {
-  start?: number;
-  end?: number;
-  startValue?: number;
-  endValue?: number;
-}
-
-// 首先修复类型定义
-interface PreviewPoint {
-  xRatio: number;
-  yValue: number;
-  dataIndex?: number;
-}
-
-
-export class ChartDrawingTools {
-  private readonly chart: EChartsType;
-  private readonly lines: Map<string, DrawingLine>;
-  private isDrawing: boolean;
-  private currentLineType: 'horizontal' | 'trend' | 'trendExtended' | null;
-  private tempLine: DrawingLine | null;
-  private canStartNewLine: boolean = true;
-  private isTrendFirstPoint: boolean = false;
-
-  constructor(chart: EChartsType) {
-    this.chart = chart;
-    this.lines = new Map();
-    this.isDrawing = false;
-    this.currentLineType = null;
-    this.tempLine = null;
-
-    this.bindEvents();
-  }
-
-  // 开始绘制
-  public startDrawing(type: 'horizontal' | 'trend' | 'trendExtended'): void {
-    this.isDrawing = true;
-    this.currentLineType = type;
-    this.canStartNewLine = true;
-    this.isTrendFirstPoint = type === 'trend' || type === 'trendExtended';
-  }
-
-  // 停止绘制
-  public stopDrawing(): void {
-    this.isDrawing = false;
-    this.currentLineType = null;
-    this.tempLine = null;
-    this.canStartNewLine = true;
-    this.isTrendFirstPoint = false;
-  }
-
-  // 清除所有线条
-  public clearAllLines(): void {
-    this.lines.clear();
-    this.updateChart();
-  }
-
-  // 删除指定线条
-  public deleteLine(id: string): void {
-    this.lines.delete(id);
-    this.updateChart();
-  }
-
-  // 更新图表
-  private updateChart(): void {
-    const option = this.chart.getOption() as EChartsOption;
-    const markLineData: any[] = [];
-    const xAxis = (option.xAxis as XAxisOption[])[0];
-    const dataZoom = (option.dataZoom as DataZoomOption[]) || [];
-    const viewRange = {
-      start: dataZoom[0]?.startValue ?? 0,
-      end: dataZoom[0]?.endValue ?? (xAxis.data.length - 1)
-    };
-    const yRange = this.getYAxisRange();
-
-    this.lines.forEach(line => {
-      if (line.type === 'horizontal') {
-        markLineData.push({
-          yAxis: line.points[0].yValue,
-          lineStyle: {
-            ...line.style,
-            type: line.style?.type || 'solid'
-          }
-        });
-      } else if (line.type === 'trend' && line.points.length === 2) {
-        // 查找日期对应的索引
-        const startIndex = xAxis.data.indexOf(line.points[0].date!);
-        const endIndex = xAxis.data.indexOf(line.points[1].date!);
-        
-        // 只有当两个点的日期都能找到对应索引时才显示线条
-        if (startIndex !== -1 && endIndex !== -1) {
-          markLineData.push([{
-            coord: [startIndex, line.points[0].yValue]
-          }, {
-            coord: [endIndex, line.points[1].yValue]
-          }]);
-        }
-      } else if (line.type === 'trendExtended' && line.points.length === 2) {
-        const startIndex = xAxis.data.indexOf(line.points[0].date!);
-        const endIndex = xAxis.data.indexOf(line.points[1].date!);
-        
-        if (startIndex !== -1 && endIndex !== -1) {
-          // 使用抽取的方法计算延伸线坐标
-          const coords = this.calculateExtendedTrendLineCoords(
-            { x: startIndex, y: line.points[0].yValue },
-            { x: endIndex, y: line.points[1].yValue },
-            viewRange,
-            yRange
-          );
-
-          markLineData.push([{
-            coord: [coords.left.x, coords.left.y],
-            symbol: 'none',
-            lineStyle: {
-              color: '#0000ff', // 蓝色
-              width: 1,
-              type: 'solid'
-            }
-          }, {
-            coord: [coords.right.x, coords.right.y],
-            symbol: 'none',
-            lineStyle: {
-              color: '#0000ff', // 蓝色
-              width: 1,
-              type: 'solid'
-            }
-          }]);
-        }
-      }
-    });
-
-    const series = (option.series as any[]) || [];
-    
-    if (series[0]) {
-      series[0].markLine = {
-        animation: false,
-        symbol: ['none', 'none'],
-        lineStyle: {
-          width: 1,
-          type: 'solid'
-        },
-        data: markLineData.map(item => {
-          // 为趋势线设置蓝色,其他线条设置红色
-          if (Array.isArray(item)) {
-            // 趋势线或趋势延伸线
-            return item.map(point => ({
-              ...point,
-              lineStyle: {
-                color: '#0000ff', // 蓝色
-                width: 1,
-                type: 'solid'
-              }
-            }));
-          } else {
-            // 水平线
-            return {
-              ...item,
-              lineStyle: {
-                color: '#ff0000', // 红色
-                width: 1,
-                type: 'solid',
-                ...item.lineStyle
-              }
-            };
-          }
-        })
-      };
-    }
-
-    this.chart.setOption({
-      series: series
-    }, { replaceMerge: ['series'] });
-  }
-
-  // 获取实际X轴坐标
-  private getActualX(xRatio: number, xAxis: XAxisOption): number {
-    const dataCount = xAxis.data.length;
-    return Math.floor(xRatio * dataCount);
-  }
-
-  // 获取相对X轴位置
-  private getXRatio(x: number, xAxis: XAxisOption): number {
-    const dataCount = xAxis.data.length;
-    return x / dataCount;
-  }
-
-  // 绑定事件处理器
-  private bindEvents(): void {
-    const zr = this.chart.getZr();
-
-    zr.on('mousedown', (params: { offsetX: number; offsetY: number }) => {
-      if (!this.isDrawing || !this.currentLineType || !this.canStartNewLine) return;
-
-      // 如果是趋势线的第二个点,不创建新的 tempLine
-      if (this.tempLine && !this.isTrendFirstPoint) return;
-
-      const point = this.chart.convertFromPixel({ seriesIndex: 0 }, [
-        params.offsetX,
-        params.offsetY
-      ]);
-
-      if (!point) return;
-
-      const option = this.chart.getOption() as EChartsOption;
-      const xAxis = (option.xAxis as XAxisOption[])[0];
-      const xRatio = this.getXRatio(point[0], xAxis);
-      
-      // 记录日期信息
-      const dataIndex = Math.floor(point[0]);
-      const date = xAxis.data[dataIndex];
-
-      this.tempLine = {
-        id: crypto.randomUUID(),
-        type: this.currentLineType,
-        points: [{
-          xRatio,
-          yValue: point[1],
-          dataIndex,
-          date
-        }]
-      };
-
-      if (this.currentLineType === 'horizontal') {
-        this.updatePreview();
-      }
-    });
-
-    zr.on('mousemove', (params: { offsetX: number; offsetY: number }) => {
-      if (!this.isDrawing || !this.tempLine) return;
-
-      const point = this.chart.convertFromPixel({ seriesIndex: 0 }, [
-        params.offsetX,
-        params.offsetY
-      ]);
-
-      if (!point) return;
-
-      const option = this.chart.getOption() as EChartsOption;
-      const xAxis = (option.xAxis as XAxisOption[])[0];
-      const xRatio = this.getXRatio(point[0], xAxis);
-      const dataIndex = Math.floor(point[0]);  // 计算当前点的索引
-
-      if (this.tempLine.type === 'horizontal') {
-        this.tempLine.points = [{
-          xRatio: 0,
-          yValue: point[1]
-        }];
-        this.updatePreview();
-      } else if (this.tempLine.type === 'trend' || this.tempLine.type === 'trendExtended') {
-        if (this.tempLine.points.length > 0) {
-          const previewPoints = [
-            this.tempLine.points[0],
-            {
-              xRatio,
-              yValue: point[1],
-              dataIndex  // 添加 dataIndex
-            }
-          ];
-          
-          this.updatePreviewWithPoints(previewPoints);
-        }
-      }
-    });
-
-    zr.on('mouseup', (params: { offsetX: number; offsetY: number }) => {
-      if (!this.isDrawing || !this.tempLine) return;
-
-      if (this.tempLine.type === 'trend' || this.tempLine.type === 'trendExtended') {
-        if (this.isTrendFirstPoint) {
-          this.isTrendFirstPoint = false;
-          return;
-        }
-
-        const point = this.chart.convertFromPixel({ seriesIndex: 0 }, [
-          params.offsetX,
-          params.offsetY
-        ]);
-
-        if (!point) return;
-
-        const option = this.chart.getOption() as EChartsOption;
-        const xAxis = (option.xAxis as XAxisOption[])[0];
-        const xRatio = this.getXRatio(point[0], xAxis);
-        
-        // 记录第二个点的信息
-        const dataIndex = Math.floor(point[0]);
-        const date = xAxis.data[dataIndex];
-
-        // 确保两个点不重合
-        if (this.tempLine.points[0].xRatio === xRatio) {
-          this.tempLine = null;
-          return;
-        }
-
-        this.tempLine.points.push({
-          xRatio,
-          yValue: point[1],
-          dataIndex,
-          date
-        });
-      }
-
-      this.lines.set(this.tempLine.id, this.tempLine);
-      this.updateChart();
-      const currentType = this.tempLine.type;
-      this.tempLine = null;
-      this.canStartNewLine = true;
-      if (currentType === 'trend' || currentType === 'trendExtended') {
-        this.isTrendFirstPoint = true;
-      }
-    });
-
-    this.chart.on('datazoom', () => {
-      this.updateChart();
-    });
-
-  }
-
-  private getYAxisRange(): { min: number; max: number } {
-    const option = this.chart.getOption();
-    const dataZoom = (option.dataZoom as DataZoomOption[]) || [];
-    const series = (option.series as any[])[0];
-    
-    // 获取当前视图范围
-    const startIndex = dataZoom[0]?.startValue ?? 0;
-    const endIndex = dataZoom[0]?.endValue ?? (series.data.length - 1);
-    
-    // 获取可见区域的数据
-    const visibleData = series.data.slice(startIndex, endIndex + 1);
-    
-    // 计算可见区域的最大最小值
-    let yMin = Infinity;
-    let yMax = -Infinity;
-    
-    visibleData.forEach((item: any) => {
-      const values = item.value || item;
-      // K线数据格式为 [open, close, low, high]
-      const low = parseFloat(values[2]);  // low
-      const high = parseFloat(values[3]); // high
-      
-      if (!isNaN(low)) yMin = Math.min(yMin, low);
-      if (!isNaN(high)) yMax = Math.max(yMax, high);
-    });
-    
-    return {
-      min: yMin,
-      max: yMax
-    };
-  }
-  
-
-  // 添加新方法用于预览时的点更新
-  private updatePreviewWithPoints(points: PreviewPoint[]): void {
-    if (!this.tempLine) return;
-
-    const option = this.chart.getOption() as EChartsOption;
-    const xAxis = (option.xAxis as XAxisOption[])[0];
-    const series = (option.series as any[]) || [];
-    const currentSeries = series[0] || {};
-
-    let previewData;
-    
-    if (this.tempLine.type === 'trend') {
-      // 保持原有趋势线预览逻辑
-      previewData = [
-        {
-          coord: [
-            this.getActualX(points[0].xRatio, xAxis),
-            points[0].yValue
-          ]
-        },
-        {
-          coord: [
-            this.getActualX(points[1].xRatio, xAxis),
-            points[1].yValue
-          ]
-        }
-      ];
-    } else if (this.tempLine.type === 'trendExtended') {
-      const chartOption = this.chart.getOption();
-      const dataZoom = (chartOption.dataZoom as DataZoomOption[]) || [];
-      
-      const viewStartIndex = dataZoom[0]?.startValue ?? 0;
-      const viewEndIndex = dataZoom[0]?.endValue ?? (xAxis.data.length - 1);
-      
-      const actualStartX = this.getActualX(points[0].xRatio, xAxis);
-      const actualStartY = points[0].yValue;
-      const actualEndX = this.getActualX(points[1].xRatio, xAxis);
-      const actualEndY = points[1].yValue;
-
-      const { min, max } = this.getYAxisRange();
-
-      // 使用抽取的方法计算延伸线坐标
-      const coords = this.calculateExtendedTrendLineCoords(
-        { x: actualStartX, y: actualStartY },
-        { x: actualEndX, y: actualEndY },
-        { start: viewStartIndex, end: viewEndIndex },
-        { min, max }
-      );
-
-      previewData = [
-        {
-          coord: [coords.left.x, coords.left.y]
-        },
-        {
-          coord: [coords.right.x, coords.right.y]
-        }
-      ];
-    }
-
-    if (previewData) {  // 只在有预览数据时更新
-      this.chart.setOption({
-        series: [{
-          ...currentSeries,
-          markLine: {
-            animation: false,
-            symbol: ['none', 'none'],
-            lineStyle: {
-              width: 1,
-              type: 'dashed',
-              color: '#999'
-            },
-            data: [previewData]
-          }
-        }]
-      }, { replaceMerge: ['series'] });
-    }
-  }
-
-  // 更新预览线
-  private updatePreview(): void {
-    if (!this.tempLine) return;
-
-    const option = this.chart.getOption() as EChartsOption;
-    const previewData: any[] = [];
-
-    if (this.tempLine.type === 'horizontal') {
-      previewData.push({
-        yAxis: this.tempLine.points[0].yValue,
-        lineStyle: {
-          type: 'dashed',
-          color: '#999'
-        }
-      });
-    } else if (this.tempLine.points.length === 2) {
-      const xAxis = (option.xAxis as XAxisOption[])[0];
-      const start = this.getActualX(this.tempLine.points[0].xRatio, xAxis);
-      const end = this.getActualX(this.tempLine.points[1].xRatio, xAxis);
-
-      previewData.push([{
-        coord: [start, this.tempLine.points[0].yValue]
-      }, {
-        coord: [end, this.tempLine.points[1].yValue]
-      }]);
-    }
-
-    // 获取当前的系列配置
-    const series = (option.series as any[]) || [];
-    const currentSeries = series[0] || {};
-
-    // 更新或添加 markLine 到现有系列
-    this.chart.setOption({
-      series: [{
-        ...currentSeries,  // 保留现有系列的配置
-        markLine: {
-          animation: false,
-          symbol: ['none', 'none'],
-          lineStyle: {
-            width: 1,
-            type: 'dashed',
-            color: '#999'
-          },
-          data: previewData
-        }
-      }]
-    }, { replaceMerge: ['series'] });
-  }
-
-  // 添加重绘线条的方法
-  public redrawLines(): void {
-    if (this.lines.size > 0) {
-      this.updateChart();
-    }
-  }
-
-  // 添加计算延伸趋势线坐标的方法
-  private calculateExtendedTrendLineCoords(
-    startPoint: { x: number; y: number },
-    endPoint: { x: number; y: number },
-    viewRange: { start: number; end: number },
-    yRange: { min: number; max: number }
-  ): { left: { x: number; y: number }; right: { x: number; y: number } } {
-    // 计算斜率
-    const slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);
-    
-    // 计算左边延伸点
-    let leftX = viewRange.start;
-    let leftY = startPoint.y - slope * (startPoint.x - leftX);
-    
-    // 如果y值超出范围,锁定y到边界值并反推x
-    if (leftY < yRange.min || leftY > yRange.max) {
-      leftY = leftY < yRange.min ? yRange.min : yRange.max;
-      leftX = startPoint.x - (startPoint.y - leftY) / slope;
-    }
-    
-    // 计算右边延伸点
-    let rightX = viewRange.end;
-    let rightY = endPoint.y + slope * (rightX - endPoint.x);
-    
-    // 如果y值超出范围,锁定y到边界值并反推x
-    if (rightY < yRange.min || rightY > yRange.max) {
-      rightY = rightY < yRange.min ? yRange.min : yRange.max;
-      rightX = endPoint.x + (rightY - endPoint.y) / slope;
-    }
-
-    return {
-      left: { 
-        x: Math.ceil(leftX), 
-        y: leftY 
-      },
-      right: { 
-        x: Math.ceil(rightX), 
-        y: rightY 
-      }
-    };
-  }
-} 

+ 0 - 222
src/client/live/components/stock/components/stock-chart/src/lib/drawing/DrawingTools.ts

@@ -1,222 +0,0 @@
-import type { ChartOption } from '../../types/index';
-
-interface LineValue {
-  type: 'line' | 'dashline';
-  value: number | [{ name: string; coord: number[]; label: { show: boolean } }, { name: string; coord: number[]; label: { show: boolean } }];
-}
-
-export class DrawingTools {
-  private readonly lineValues = new Map<string, LineValue>();
-  private readonly buttons = new Map<string, any>();
-  private startPoint: number[] | null = null;
-  private lineId: string | null = null;
-  private enableDrawLine = false;
-  private enableDrawDashedLine = false;
-
-  constructor(private chart: any) {}
-
-  // 添加标记线
-  private addMutiLine(lineId: string, lineValue: LineValue): void {
-    this.lineValues.set(lineId, lineValue);
-
-    const markLine = {
-      series: [{
-        type: 'candlestick',
-        markLine: {
-          symbol: ['none', 'none'],
-          lineStyle: {
-            color: 'black',
-            width: 2,
-            type: 'solid'
-          },
-          data: [...this.lineValues.values()].map(lineValue => {
-            if (lineValue.type === 'line') {
-              return { yAxis: lineValue.value };
-            } else {
-              return lineValue.value;
-            }
-          })
-        }
-      }]
-    };
-
-    this.chart.setOption(markLine);
-  }
-
-  // 坐标转换
-  private convertFromPixel(params: { offsetX: number; offsetY: number }, seriesIndex = 0): number[] {
-    const pointInPixel = [params.offsetX, params.offsetY];
-    return this.chart.convertFromPixel({ seriesIndex }, pointInPixel);
-  }
-
-  // 清除标记线
-  clearMarkLine(): void {
-    this.lineValues.clear();
-    this.chart.setOption({ series: [{ markLine: { data: [] } }] });
-  }
-
-  // 添加水平线绘制功能
-  addDrawLineButton(): any {
-    const button = {
-      name: 'drawLineButton',
-      type: 'rect',
-      shape: {
-        x: 10,
-        y: 10,
-        width: 60,
-        height: 30
-      },
-      style: {
-        fill: this.enableDrawLine ? 'gray' : '#f00',
-        text: this.enableDrawLine ? '取消绘制' : '绘制横线',
-        font: 'bold 12px sans-serif'
-      },
-      onclick: () => this.toggleDrawLine()
-    };
-
-    return button;
-  }
-
-  // 添加斜线绘制功能
-  addDrawDashedLineButton(): any {
-    const button = {
-      name: 'drawDashedLineButton',
-      type: 'rect',
-      shape: {
-        x: 80,
-        y: 10,
-        width: 60,
-        height: 30
-      },
-      style: {
-        fill: this.enableDrawDashedLine ? 'gray' : '#f00',
-        text: this.enableDrawDashedLine ? '取消绘制' : '绘制斜线',
-        font: 'bold 12px sans-serif'
-      },
-      onclick: () => this.toggleDrawDashedLine()
-    };
-
-    return button;
-  }
-
-  // 添加清除按钮
-  addClearLinesButton(): any {
-    const button = {
-      name: 'clearLinesButton',
-      type: 'rect',
-      shape: {
-        x: 150,
-        y: 10,
-        width: 60,
-        height: 30
-      },
-      style: {
-        fill: '#f00',
-        text: '清除线条',
-        font: 'bold 12px sans-serif'
-      },
-      onclick: () => this.clearMarkLine()
-    };
-
-    return button;
-  }
-
-  // 更新图形元素
-  private updateGraphics(name: string, graphic: any): void {
-    this.buttons.set(name, graphic);
-
-    const option = {
-      graphic: {
-        elements: Array.from(this.buttons.values())
-      }
-    };
-
-    this.chart.setOption(option, { replaceMerge: 'graphic' });
-  }
-
-  // 切换水平线绘制状态
-  private toggleDrawLine(): void {
-    this.enableDrawLine = !this.enableDrawLine;
-    this.updateGraphics('drawLineButton', this.addDrawLineButton());
-  }
-
-  // 切换斜线绘制状态
-  private toggleDrawDashedLine(): void {
-    this.enableDrawDashedLine = !this.enableDrawDashedLine;
-    this.updateGraphics('drawDashedLineButton', this.addDrawDashedLineButton());
-  }
-
-  // 处理鼠标事件
-  handleMouseEvent(event: string, params: any): void {
-    if (event === 'click' && this.enableDrawLine) {
-      this.handleDrawLineClick(params);
-    } else if (this.enableDrawDashedLine) {
-      if (event === 'mousedown') {
-        this.handleDrawDashedLineMouseDown(params);
-      } else if (event === 'mousemove') {
-        this.handleDrawDashedLineMouseMove(params);
-      }
-    }
-  }
-
-  private handleDrawLineClick(params: any): void {
-    const pointInGrid = this.convertFromPixel(params);
-    if (pointInGrid) {
-      const yValue = pointInGrid[1];
-      this.lineId = crypto.randomUUID();
-      this.addMutiLine(this.lineId, { type: 'line', value: yValue });
-    }
-  }
-
-  private handleDrawDashedLineMouseDown(params: any): void {
-    if (!this.startPoint) {
-      this.startPoint = this.convertFromPixel(params);
-      this.lineId = crypto.randomUUID();
-    } else {
-      this.startPoint = null;
-      this.lineId = null;
-    }
-  }
-
-  private handleDrawDashedLineMouseMove(params: any): void {
-    if (!this.startPoint || !this.lineId) return;
-
-    const endPoint = this.convertFromPixel(params);
-    this.addMutiLine(this.lineId, {
-      type: 'dashline',
-      value: [
-        {
-          name: 'startPoint',
-          coord: this.startPoint,
-          label: { show: false }
-        },
-        {
-          name: 'endPoint',
-          coord: endPoint,
-          label: { show: false }
-        }
-      ]
-    });
-  }
-
-  public initDrawingTools(): void {
-    
-    // 创建并缓存所有按钮
-    const drawLineButton = this.addDrawLineButton();
-    const drawDashedLineButton = this.addDrawDashedLineButton();
-    const clearLinesButton = this.addClearLinesButton();
-
-    this.buttons.set('drawLineButton', drawLineButton);
-    this.buttons.set('drawDashedLineButton', drawDashedLineButton);
-    this.buttons.set('clearLinesButton', clearLinesButton);
-
-    // 使用所有按钮更新图表
-    const option = {
-      graphic: {
-        elements: Array.from(this.buttons.values())
-      }
-    };
-
-    this.chart.setOption(option, { replaceMerge: 'graphic' });
-  }
-} 

+ 0 - 3
src/client/live/components/stock/components/stock-chart/src/lib/index.ts

@@ -1,3 +0,0 @@
-export { StockChart } from './StockChart';
-export { DateMemoHandler } from './DateMemoHandler';
-export * from '../types/index'; 

+ 0 - 131
src/client/live/components/stock/components/stock-chart/src/lib/markers/MarkerProcessor.ts

@@ -1,131 +0,0 @@
-import type { ChartOption, SplitData } from '../../types/index';
-// import type { ScatterSeriesOption } from 'echarts/types/src/chart/scatter/ScatterSeries';
-// import type { LabelOption } from 'echarts/types/src/util/types';
-import { CHART_COLORS } from '../constants/colors';
-
-export class MarkerProcessor {
-  private readonly operateColor = CHART_COLORS.OPERATE;
-  private readonly middleLineColor = CHART_COLORS.MIDDLE_LINE;
-
-  add2OnTopOfVolumeMarkers(option: ChartOption, data: SplitData): void {
-    const markersData = data.volumes
-      .map((item, index) => {
-        const previousDayVolume = index > 0 ? Number(data.volumes[index - 1][1]) : 0;
-        const todayVolume = Number(item[1]);
-        
-        if (previousDayVolume > 0 && (previousDayVolume * 1) / 2 > todayVolume) {
-          return {
-            value: [index, todayVolume],
-            symbol: 'pin',
-            symbolSize: 10,
-            label: {
-              show: true,
-              formatter: '2',
-              position: 'top',
-              color: '#ffffff',
-              textBorderColor: '#000',
-              textBorderWidth: 2,
-              fontSize: 14,
-              fontWeight: 'bolder'
-            },
-            itemStyle: {
-              color: 'transparent'
-            }
-          };
-        }
-        return null;
-      })
-      .filter(item => item !== null);
-
-    option.series.push({
-      name: 'SpecificMarkers2',
-      type: 'scatter',
-      xAxisIndex: 1,
-      yAxisIndex: 1,
-      data: markersData,
-      tooltip: { show: false }
-    } as any);
-  }
-
-  add3OnTopOfVolumeMarkers(option: ChartOption, data: SplitData): void {
-    const markersData = data.volumes
-      .map((item, index) => {
-        const previousDayVolume = index > 0 ? Number(data.volumes[index - 1][1]) : 0;
-        const todayVolume = Number(item[1]);
-        const is3 = previousDayVolume > 0 && (previousDayVolume * 2) / 3 > todayVolume;
-        const is2 = previousDayVolume > 0 && (previousDayVolume * 1) / 2 > todayVolume;
-
-        if (is3 && !is2) {
-          return {
-            value: [index, todayVolume],
-            symbol: 'pin',
-            symbolSize: 10,
-            label: {
-              show: true,
-              formatter: '3',
-              position: 'top',
-              color: '#ffffff',
-              textBorderColor: '#000',
-              textBorderWidth: 2,
-              fontSize: 14,
-              fontWeight: 'bolder'
-            } as any,
-            itemStyle: {
-              color: 'transparent'
-            }
-          };
-        }
-        return null;
-      })
-      .filter(item => item !== null);
-
-    option.series.push({
-      name: 'SpecificMarkers3',
-      type: 'scatter',
-      xAxisIndex: 1,
-      yAxisIndex: 1,
-      data: markersData,
-      tooltip: { show: false }
-    } as any);
-  }
-
-  addMiddleLinesToChartOption(option: ChartOption, data: SplitData): void {
-    const middleLinesData = data.values.map((item, index) => {
-      const open = Number(item.value[0]);
-      const close = Number(item.value[1]);
-      const changePercent = Number(item.value[4]);
-      const lineHeight = 0.01;
-      const average = (open + close) / 2;
-
-      if (changePercent > 3 || changePercent < -3) {
-        return {
-          coords: [
-            [index, average - lineHeight],
-            [index, average + lineHeight]
-          ]
-        };
-      }
-      return {
-        coords: [
-          [index, average],
-          [index, average]
-        ]
-      };
-    });
-
-    option.series.push({
-      name: 'MiddleLines',
-      type: 'lines',
-      coordinateSystem: 'cartesian2d',
-      data: middleLinesData,
-      lineStyle: {
-        color: this.middleLineColor,
-        width: 10,
-        type: 'solid'
-      },
-      effect: { show: false },
-      tooltip: { show: false },
-      z: 3
-    });
-  }
-} 

+ 0 - 36
src/client/live/components/stock/components/stock-chart/src/services/api.ts

@@ -1,36 +0,0 @@
-import type { StockData, DateMemo } from '../types/index.ts';
-
-const API_BASE_URL = '/api';
-
-export const stockApi = {
-  // 获取股票历史数据
-  getStockHistory: async (code?: string): Promise<StockData[]> => {
-    const url = new URL(`${API_BASE_URL}/stock/history`, window.location.origin);
-    if (code) {
-      url.searchParams.set('code', code);
-    }
-    const response = await fetch(url);
-    if (!response.ok) {
-      throw new Error('Failed to fetch stock history');
-    }
-    return response.json();
-  },
-
-  // 获取备忘录数据
-  getMemoData: async (code?: string): Promise<DateMemo[]> => {
-    const url = new URL(`${API_BASE_URL}/stock/memos`, window.location.origin);
-    if (code) {
-      url.searchParams.set('code', code);
-    }
-    const response = await fetch(url);
-    if (!response.ok) {
-      throw new Error('Failed to fetch memo data');
-    }
-    const result = await response.json();
-    if(result.success) {
-      return result.data;
-    }
-    return [];
-
-  },
-}; 

+ 0 - 210
src/client/live/components/stock/components/stock-chart/src/types/index.ts

@@ -1,210 +0,0 @@
-import type { SeriesOption } from 'echarts';
-
-export enum ActiveType {
-  HORIZONTAL = 'horizontal',
-  TREND = 'trend',
-  TREND_EXTENDED = 'trendExtended',
-}
-
-// 定义基础数据类型
-export interface StockData {
-  o: string; // 开盘价
-  c: string; // 收盘价
-  h: string; // 最高价
-  l: string; // 最低价
-  v: string; // 成交量
-  d: string; // 日期
-  zd: string; // 涨跌幅
-  pc?: string; // 涨跌额
-}
-
-export interface DateMemo {
-  _id: number;
-  代码: string;
-  日期: string; 
-  提示: string;
-}
-
-export interface ProcessedData {
-  categoryData: string;
-  values: {
-    value: [string, string, string, string, string];
-    itemStyle: {
-      color: string;
-      borderColor: string;
-    }
-  };
-  volumes: string;
-}
-
-export interface SplitData {
-  categoryData: string[];
-  values: Array<{
-    value: number[];
-    itemStyle: {
-      color: string;
-      borderColor: string;  
-    }
-  }>;
-  volumes: [number, string, number][];
-}
-
-// 定义图表系列的具体类型
-export interface CandlestickSeries {
-  name: 'Values';
-  type: 'candlestick';
-  data: Array<{
-    value: number[];
-    itemStyle: {
-      color: string;
-      borderColor: string;
-    }
-  }>;
-}
-
-export interface VolumeSeries {
-  name: 'Volumes';
-  type: 'bar';
-  xAxisIndex: number;
-  yAxisIndex: number;
-  data: [number, string, number][];
-  itemStyle: {
-    color: string | ((params: { value: any }) => string);
-  };
-}
-
-export interface MarkerSeries {
-  name: 'SpecificMarkers' | 'DateMemo' | 'Mark';
-  type: 'scatter';
-  xAxisIndex?: number;
-  yAxisIndex?: number;
-  data: Array<{
-    value: [number, number];
-    symbol: string;
-    symbolSize: number;
-    label: {
-      show: boolean;
-      formatter: string;
-      position: 'top' | 'bottom' | 'left' | 'right';
-      color: string;
-    };
-    itemStyle: {
-      color: string;
-    };
-  }>;
-  tooltip: {
-    show: boolean;
-  };
-}
-
-export interface MiddleLineSeries {
-  name: 'MiddleLines';
-  type: 'lines';
-  coordinateSystem: 'cartesian2d';
-  data: MiddleLine[];
-  lineStyle: {
-    color: string;
-    width: number;
-    type: string;
-  };
-  effect: {
-    show: boolean;
-  };
-  tooltip: {
-    show: boolean;
-  };
-  z: number;
-}
-
-// 图表配置类型
-export interface ChartOption {
-  series: SeriesOption[];
-  [key: string]: any;
-  markPoint?: {
-    data: TradeMarker[];
-    label?: {
-      show: boolean;
-      position: 'top' | 'bottom';
-      formatter: string;
-    };
-  };
-}
-
-// 建议添加这些类型定义
-export interface VolumeMarker {
-  value: [number, number];
-  symbol: string;
-  symbolSize: number;
-  label: {
-    show: boolean;
-    formatter: string;
-    position: string;
-    color: string;
-    // ... 其他样式属性
-  };
-  itemStyle: {
-    color: string;
-  };
-}
-
-export interface MiddleLine {
-  coords: [[number, number], [number, number]];
-}
-
-// 添加新的类型定义
-export interface TradeRecord {
-  type: 'BUY' | 'SELL';
-  price: number;      // 交易价格(收盘价)
-  timestamp: number;
-  date: string;
-  buyPrice?: number;  // 仅卖出时需要记录买入价
-}
-
-// 添加买卖记录数组的类型
-export interface TradeRecordGroup {
-  date: string;
-  type: 'BUY' | 'SELL';
-  records: TradeRecord[];
-}
-
-// 添加买卖记录的响应类型
-export interface TradeResponse {
-  success: boolean;
-  data?: TradeRecord;
-  message?: string;
-}
-
-// 修改 MarkerSeries 接口以支持买卖标记
-export interface TradeMarker {
-  value: [number, number];  // [timestamp, price]
-  symbol: 'arrow' | 'arrow-down';  // 买入用上箭头,卖出用下箭头
-  symbolSize: number;
-  itemStyle: {
-    color: string;  // 买入红色,卖出绿色
-  };
-  label?: {
-    show: boolean;
-    formatter: string;
-    position: 'top' | 'bottom';
-    color: string;
-  };
-}
-
-// 添加收益相关的类型定义
-export interface DailyProfit {
-  date: string;
-  profit: number;  // 当日收益
-  trades: TradeRecord[];  // 当日交易记录
-}
-
-export interface ProfitSummary {
-  totalProfit: number;  // 累计收益
-  dailyStats: {  // 当日行情统计
-    date: string;  // 添加日期字段
-    open: number;
-    high: number;
-    close: number;
-    low: number;
-    change: number;  // 日涨幅
-  };
-} 

+ 0 - 96
src/client/live/components/stock/hooks/useStockSocketClient.ts

@@ -1,96 +0,0 @@
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-import { useEffect, useState, useCallback } from 'react';
-import { io, Socket } from 'socket.io-client';
-
-interface ExamData {
-  roomId: string;
-  question: {
-    date: string;
-    price: number;
-  };
-}
-
-interface StockSocketClient {
-  // connect(): void;
-  // disconnect(): void;
-  pushExamData(data: { roomId: string; question: { date: string; price: number } }): void;
-  error: Error | null;
-  isConnected: boolean;
-}
-
-export function useStockSocket(classRoom: string | null): StockSocketClient {
-  const { token } = useAuth();
-  const [socket, setSocket] = useState<Socket | null>(null);
-  const [error, setError] = useState<Error | null>(null);
-  const [isConnected, setIsConnected] = useState(false);
-
-  // 初始化socket连接
-  useEffect(() => {
-    if (!token || !classRoom) return;
-
-    const newSocket = io('/', {
-      path: '/socket.io',
-      transports: ['websocket'],
-      withCredentials: true,
-      query: { 
-        socket_token:token
-      },
-      reconnection: true,
-      reconnectionAttempts: 5,
-      reconnectionDelay: 1000,
-    });
-
-    newSocket.on('connect', () => {
-      console.log('Exam socket connected');
-      setIsConnected(true);
-    });
-
-    newSocket.on('disconnect', () => {
-      console.log('Exam socket disconnected');
-      setIsConnected(false);
-    });
-
-    newSocket.on('error', (err) => {
-      console.error('Exam socket error:', err);
-      setError(err);
-    });
-
-    setSocket(newSocket);
-
-    return () => {
-      newSocket.disconnect();
-    };
-  }, [token]);
-
-  // const connect = useCallback(() => {
-  //   if (socket && !socket.connected) {
-  //     socket.connect();
-  //   }
-  // }, [socket]);
-
-  // const disconnect = useCallback(() => {
-  //   if (socket && socket.connected) {
-  //     socket.disconnect();
-  //   }
-  // }, [socket]);
-
-  const pushExamData = useCallback((data: ExamData) => {
-    if (socket) {
-      socket.emit('exam:question', {
-        roomId: data.roomId,
-        question: {
-          date: data.question.date,
-          price: data.question.price
-        }
-      });
-    }
-  }, [socket]);
-
-  return {
-    // connect,
-    // disconnect,
-    pushExamData,
-    error,
-    isConnected,
-  };
-}

+ 0 - 250
src/client/live/components/stock/stock_main.tsx

@@ -1,250 +0,0 @@
-import React, { useRef, useState, useCallback, useEffect } from 'react';
-import { useStockSocket } from './hooks/useStockSocketClient.ts';
-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 type { StockChartRef } from './components/stock-chart/mod.ts';
-import { ActiveType } from "./components/stock-chart/src/types/index";
-
-export function StockMain() {
-  const chartRef = useRef<StockChartRef>(null);
-  const [searchParams] = useSearchParams();
-  const codeFromUrl = searchParams.get('code');
-  const [stockCode, setStockCode] = useState(codeFromUrl || undefined);//|| '001339'
-  const classroom = searchParams.get('classroom');
-  const {
-    pushExamData,
-    error,
-    isConnected
-  } = useStockSocket(classroom);
-  
-  const { 
-    stockData: fullStockData, 
-    memoData, 
-    fetchData ,
-  } = useStockQueries(stockCode);
-  const { 
-    filteredData: stockData,
-    moveToNextDay,
-    setDayNum,
-    initializeView,
-    isInitialized
-  } = useStockDataFilter(fullStockData);
-  const { 
-    trades, 
-    toggleTrade,
-    hasBought
-  } = useTradeRecords(stockData);
-
-  const { profitSummary, updateCurrentDate } = useProfitCalculator(
-    stockData,
-    trades
-  );
-
-  const handleNextDay = useCallback(async () => {
-    const nextDate = await moveToNextDay();
-    if (nextDate && profitSummary?.dailyStats) {
-      updateCurrentDate(nextDate);
-    }
-  }, [moveToNextDay, updateCurrentDate, profitSummary, pushExamData]);
-
-  useEffect(() => {
-    const currentDate = profitSummary.dailyStats.date;
-    if (classroom && isConnected && currentDate ) {
-      pushExamData({
-        roomId: classroom, 
-        question: {
-          date: currentDate,
-          price: profitSummary.dailyStats.close
-        }
-      });
-    }
-  }, [classroom, isConnected, profitSummary.dailyStats ]);
-
-  const handleDayNumChange = useCallback((days: number) => {
-    if (!isInitialized) {
-      initializeView();
-    } else {
-      setDayNum(days);
-    }
-  }, [isInitialized, initializeView, setDayNum]);
-
-  const handleQuery = useCallback(() => {
-    if(!stockCode){
-      toast.error('请先输入股票代码')
-      return;
-    }
-    if (stockCode && stockCode.trim()) {
-      fetchData().then(() => {
-        initializeView();
-      });
-    }
-  }, [stockCode, fetchData, initializeView]);
-
-  const handleStartDrawing = useCallback((type: ActiveType) => {
-    chartRef.current?.startDrawing(type);
-  }, []);
-
-  const handleStopDrawing = useCallback(() => {
-    chartRef.current?.stopDrawing();
-  }, []);
-
-  const handleClearLines = useCallback(() => {
-    chartRef.current?.clearDrawings();
-  }, []);
- 
-  // 错误处理
-  useEffect(() => {
-    if (error) {
-      toast.error(`Socket错误: ${error.message}`);
-    }
-  }, [error]);
-
-  useEffect(() => {
-    const handleKeyPress = (event: KeyboardEvent) => {
-      switch(event.key.toLowerCase()) {
-        case 'b':
-          if (!hasBought) toggleTrade('BUY');
-          break;
-        case 's':
-          if (hasBought) toggleTrade('SELL');
-          break;
-        case 'arrowright':
-          handleNextDay();
-          break;
-      }
-    };
-
-    globalThis.addEventListener('keydown', handleKeyPress);
-    return () => globalThis.removeEventListener('keydown', handleKeyPress);
-  }, [hasBought, toggleTrade, handleNextDay]);
-
-  useEffect(() => {
-    if (codeFromUrl && codeFromUrl !== stockCode) {
-      setStockCode(codeFromUrl);
-      fetchData().then(() => {
-        initializeView();
-      });
-    }
-  }, [codeFromUrl, stockCode, fetchData, initializeView]);
-
-  // if (isLoading) {
-  //   return (
-  //     <div className="flex items-center justify-center h-screen bg-gray-900">
-  //       <div className="text-white text-xl">加载中...</div>
-  //     </div>
-  //   );
-  // }
-
-  // if (error) {
-  //   return (
-  //     <div className="flex items-center justify-center h-screen bg-gray-900">
-  //       <div className="text-red-500 text-xl">错误: {error.message}</div>
-  //     </div>
-  //   );
-  // }
-
-  return (
-    <div className="flex flex-col h-screen bg-gray-900">
-      {!isConnected && classroom && (
-        <div className="bg-yellow-600 text-white text-center py-1 text-sm">
-          正在尝试连接答题卡服务...
-        </div>
-      )}
-      {/* 顶部行情和收益信息 */}
-      <ProfitDisplay profitSummary={profitSummary} />
-
-      {/* 主图表区域 */}
-      <div className="flex-1 relative overflow-hidden">
-        <StockChart
-          ref={chartRef}
-          stockData={stockData}
-          memoData={memoData}
-          trades={trades}
-        />
-        
-        {/* 添加画线工具栏 */}
-        <DrawingToolbar
-          className="absolute top-4 right-4"
-          onStartDrawing={handleStartDrawing}
-          onStopDrawing={handleStopDrawing}
-          onClearLines={handleClearLines}
-        />
-      </div>
-
-      {/* 底部控制面板 */}
-      <div className="flex items-center justify-between p-4 bg-gray-800 border-t border-gray-700">
-        {/* 左侧区域 */}
-        <div className="flex items-center space-x-6">
-          {/* 查询输入框 */}
-          <div className="flex items-center space-x-2">
-            <input
-              type="text"
-              value={stockCode}
-              onChange={(e) => setStockCode(e.target.value)}
-              placeholder="输入股票代码"
-              className="px-3 py-2 text-sm bg-gray-700 text-white rounded-md border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
-            />
-            <button
-              onClick={handleQuery}
-              className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
-            >
-              查询
-            </button>
-          </div>
-
-          {/* 交易面板 */}
-          <TradePanel
-            hasBought={hasBought}
-            onToggleTrade={toggleTrade}
-          />
-
-          {/* 下一天按钮 */}
-          <div className="flex items-center space-x-2">
-            <button 
-              onClick={handleNextDay}
-              disabled={!stockData.length}
-              className={`px-4 py-2 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors
-                ${!stockData.length 
-                  ? 'bg-gray-600 cursor-not-allowed' 
-                  : 'bg-blue-600 hover:bg-blue-700 focus:ring-blue-500'}`}
-            >
-              下一天
-            </button>
-            <span className="text-gray-400 text-xs">→</span>
-          </div>
-
-          {/* 天数快捷按钮组 */}
-          <div className="flex items-center space-x-2">
-            {[120, 30, 60].map((days) => (
-              <button
-                key={days}
-                onClick={() => handleDayNumChange(days)}
-                className="px-3 py-1 text-sm font-medium text-white bg-gray-700 rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors"
-              >
-                {days}天
-              </button>
-            ))}
-          </div>
-        </div>
-
-        {/* 右侧快捷键说明和能按钮 */}
-        <div className="flex items-center space-x-8">
-          <div className="text-xs text-gray-400 leading-relaxed">
-            <div>快捷键:</div>
-            <div>B - 买入</div>
-            <div>S - 卖出</div>
-            {/* <div>ESC - 取消</div> */}
-            <div>→ - 下一天</div>
-          </div>
-          <MemoToggle
-            onToggle={(visible: boolean) => {
-              chartRef.current?.toggleMemoVisibility(visible);
-            }}
-            className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors whitespace-nowrap"
-          />
-        </div>
-      </div>
-    </div>
-  );
-}

+ 0 - 65
src/client/live/components/stock/types/exam.ts

@@ -1,65 +0,0 @@
-import type { SocketMessage as BaseSocketMessage, SocketMessageType } from '@d8d-appcontainer/types';
-
-// 基础答题记录
-export interface AnswerRecord {
-  date: string;
-  price: string;
-  holdingStock: string;
-  holdingCash: string;
-  profitAmount: number;
-  profitPercent: number;
-  index: number;
-}
-
-// 答题内容
-export interface QuizContent {
-  date: string;
-  price: number | string;
-  holdingStock: string;
-  holdingCash: string;
-  userId: string;
-}
-
-// 题目状态
-export interface QuizState {
-  date: string;
-  price: number | string;
-}
-
-export type ExamSocketMessageType = SocketMessageType | 'question' | 'answer' | 'settlement' | 'submit' | 'restart';
-
-// Socket消息
-export interface ExamSocketMessage extends Omit<BaseSocketMessage, 'type' | 'content'> {
-  type: ExamSocketMessageType;
-  content: QuizContent;
-}
-
-// Socket房间消息
-export interface ExamSocketRoomMessage {
-  roomId: string;
-  message: ExamSocketMessage;
-}
-
-// 答案
-export interface Answer extends QuizContent {
-  userId: string;
-  profitAmount?: number;
-  profitPercent?: number;
-  totalProfitAmount?: number;
-  totalProfitPercent?: number;
-}
-
-// 教室数据
-export interface ClassroomData {
-  classroom_no: string;
-  status: string;
-  training_date: string;
-  code: string;
-}
-
-// 累计结果
-export interface CumulativeResult {
-  userId: string;
-  totalProfitAmount: number;
-  totalProfitPercent: number;
-} 

+ 0 - 140
src/client/live/hooks/AuthProvider.tsx

@@ -1,140 +0,0 @@
-import React, { useState, useEffect, createContext, useContext } from 'react';
-
-import {
-  useQuery,
-  useQueryClient,
-} from '@tanstack/react-query';
-import axios from 'axios';
-import 'dayjs/locale/zh-cn';
-import type {
-  AuthContextType
-} from '@/share/types';
-import { authClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
-
-export type User = InferResponseType<typeof authClient.me.$get, 200>;
-
-
-// 创建认证上下文
-const AuthContext = createContext<AuthContextType<User> | null>(null);
-
-// 认证提供器组件
-export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
-  const [user, setUser] = useState<User | null>(null);
-  const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
-  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
-  const queryClient = useQueryClient();
-
-  // 声明handleLogout函数
-  const handleLogout = async () => {
-    try {
-      // 如果已登录,调用登出API
-      if (token) {
-        await authClient.logout.$post();
-      }
-    } catch (error) {
-      console.error('登出请求失败:', error);
-    } finally {
-      // 清除本地状态
-      setToken(null);
-      setUser(null);
-      setIsAuthenticated(false);
-      localStorage.removeItem('token');
-      // 清除Authorization头
-      delete axios.defaults.headers.common['Authorization'];
-      console.log('登出时已删除全局Authorization头');
-      // 清除所有查询缓存
-      queryClient.clear();
-    }
-  };
-
-  // 使用useQuery检查登录状态
-  const { isLoading } = useQuery({
-    queryKey: ['auth', 'status', token],
-    queryFn: async () => {
-      if (!token) {
-        setIsAuthenticated(false);
-        setUser(null);
-        return null;
-      }
-
-      try {
-        // 设置全局默认请求头
-        axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
-        // 使用API验证当前用户
-        const res = await authClient.me.$get();
-        if (res.status !== 200) {
-          const result = await res.json();
-          throw new Error(result.message)
-        }
-        const currentUser = await res.json();
-        setUser(currentUser);
-        setIsAuthenticated(true);
-        return { isValid: true, user: currentUser };
-      } catch (error) {
-        return { isValid: false };
-      }
-    },
-    enabled: !!token,
-    refetchOnWindowFocus: false,
-    retry: false
-  });
-
-  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<User> => {
-    try {
-      // 使用AuthAPI登录
-      const response = await authClient.login.$post({
-        json: {
-          username,
-          password
-        }
-      })
-      if (response.status !== 200) {
-        const result = await response.json()
-        throw new Error(result.message);
-      }
-
-      const result = await response.json()
-
-      // 保存token和用户信息
-      const { token: newToken, user: newUser } = result;
-
-      // 设置全局默认请求头
-      axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
-
-      // 保存状态
-      setToken(newToken);
-      setUser(newUser);
-      setIsAuthenticated(true);
-      localStorage.setItem('token', newToken);
-      return newUser;
-    } catch (error) {
-      console.error('登录失败:', error);
-      throw error;
-    }
-  };
-
-  return (
-    <AuthContext.Provider
-      value={{
-        user,
-        token,
-        login: handleLogin,
-        logout: handleLogout,
-        isAuthenticated,
-        isLoading
-      }}
-    >
-      {children}
-    </AuthContext.Provider>
-  );
-};
-
-// 使用上下文的钩子
-export const useAuth = () => {
-  const context = useContext(AuthContext);
-  if (!context) {
-    throw new Error('useAuth必须在AuthProvider内部使用');
-  }
-  return context;
-};

+ 0 - 41
src/client/live/index.tsx

@@ -1,41 +0,0 @@
-import { createRoot } from 'react-dom/client'
-import { RouterProvider } from 'react-router';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import dayjs from 'dayjs';
-import weekday from 'dayjs/plugin/weekday';
-import localeData from 'dayjs/plugin/localeData';
-import 'dayjs/locale/zh-cn';
-
-import { AuthProvider } from './hooks/AuthProvider';
-import { router } from './routes';
-import { ToastContainer } from 'react-toastify';
-
-// 配置 dayjs 插件
-dayjs.extend(weekday);
-dayjs.extend(localeData);
-
-// 设置 dayjs 语言
-dayjs.locale('zh-cn');
-
-// 创建QueryClient实例
-const queryClient = new QueryClient();
-
-// 应用入口组件
-const App = () => {
-  return (
-    <QueryClientProvider client={queryClient}>
-      <AuthProvider>
-        <RouterProvider router={router} />
-      </AuthProvider>
-      <ToastContainer />
-    </QueryClientProvider>
-  )
-};
-
-const rootElement = document.getElementById('root')
-if (rootElement) {
-  const root = createRoot(rootElement)
-  root.render(
-    <App />
-  )
-}

+ 0 - 14
src/client/live/layouts/MainLayout.tsx

@@ -1,14 +0,0 @@
-import React, { useState, useEffect, useMemo } from 'react';
-import {
-  Outlet
-} from 'react-router';
-
-/**
- * 主布局组件
- * 包含侧边栏、顶部导航和内容区域
- */
-export const MainLayout = () => {
-  return (
-    <Outlet />
-  );
-};

+ 0 - 212
src/client/live/pages/ClassroomPage.tsx

@@ -1,212 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-import { useNavigate, useSearchParams } from 'react-router';
-import { Role, ClassStatus } from '@/client/live/components/Classroom/useClassroom';
-import { ClassroomLayout } from '@/client/live/components/Classroom/ClassroomLayout';
-import { AuthLayout } from '@/client/live/components/Classroom/AuthLayout';
-import { ClassroomProvider, useClassroomContext } from "@/client/live/components/Classroom/ClassroomProvider";
-import { ToastContainer } from 'react-toastify';
-
-const RoleSelection = () => {
-  const { setRole, login } = useClassroomContext();
-  const chooseRole = (role: Role) => {
-
-    setRole(role);
-    login(role);
-  }
-  
-  return (
-    <div className="flex flex-col items-center justify-center h-full">
-      <h2 className="text-2xl font-bold mb-8">请选择您的角色</h2>
-      <div className="flex space-x-4">
-        <button
-          type="button"
-          onClick={() => chooseRole(Role.Teacher)}
-          className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
-        >
-          我是老师
-        </button>
-        <button
-          type="button"
-          onClick={() => chooseRole(Role.Student)}
-          className="px-6 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors"
-        >
-          我是学生
-        </button>
-      </div>
-    </div>
-  );
-};
-
-const JoinClassSection = () => {
-  const { joinClass } = useClassroomContext();
-  const [classId, setClassId] = useState('');
-
-  const handleJoinClass = async () => {
-    if (!classId.trim()) return;
-    await joinClass(classId);
-  };
-
-  return (
-    <div className="bg-white p-4 rounded shadow mb-4">
-      <h3 className="font-bold mb-2">加入课堂</h3>
-      <div className="flex space-x-2">
-        <input
-          type="text"
-          value={classId}
-          onChange={(e) => setClassId(e.target.value)}
-          placeholder="输入课堂ID"
-          className="flex-1 px-3 py-2 border rounded"
-        />
-        <button
-          type="button"
-          onClick={handleJoinClass}
-          className="px-4 py-2 bg-blue-500 text-white rounded"
-        >
-          加入课堂
-        </button>
-      </div>
-    </div>
-  );
-};
-
-const CreateClassSection = () => {
-  const { classStatus, createClass, className, setClassName, role } = useClassroomContext();
-  const navigate = useNavigate();
-  
-  const handleCreateClass = async () => {
-    if (!className.trim()) return;
-    const classId = await createClass(className);
-    if (classId) {
-      navigate(`/mobile/classroom/${classId}/${role === Role.Teacher ? Role.Teacher : Role.Student}`, { replace: true });
-    }
-  };
-
-  if (classStatus !== ClassStatus.NOT_STARTED) return null;
-
-  return (
-    <div className="bg-white p-4 rounded shadow mb-4">
-      <h3 className="font-bold mb-2">创建新课堂</h3>
-      <div className="flex space-x-2">
-        <input
-          type="text"
-          value={className}
-          onChange={(e) => setClassName(e.target.value)}
-          placeholder="输入课堂名称"
-          className="flex-1 px-3 py-2 border rounded"
-        />
-        <button
-          type="button"
-          onClick={handleCreateClass}
-          className="px-4 py-2 bg-green-500 text-white rounded"
-        >
-          创建课堂
-        </button>
-      </div>
-    </div>
-  );
-};
-
-
-const Classroom = () => {
-  const context = useClassroomContext();
-  const { role, classStatus, isLoggedIn, login, classId, joinClass, setRole } = context;
-  const [searchParams] = useSearchParams();
-
-  useEffect(() => {
-    // 处理URL中的role参数
-    const urlRole = searchParams.get('role');
-    if (urlRole && !role) {
-      const roleValue = urlRole === Role.Teacher ? Role.Teacher : Role.Student;
-      setRole(roleValue);
-      login(roleValue);
-    }
-  }, [searchParams]);
-
-  useEffect(() => {
-    
-    if (!isLoggedIn && role && classId) {
-      (async () => {
-        await login(role);
-        await joinClass(classId);
-      })()
-      
-    }
-  }, [isLoggedIn, role , classId]);
-
-  if (!role) {
-    return (
-      <AuthLayout>
-        <RoleSelection />
-      </AuthLayout>
-    );
-  }
-
-  if (!isLoggedIn) {
-    return (
-      <AuthLayout>
-        <div className="flex items-center justify-center h-full">
-          <p>正在自动登录中...</p>
-        </div>
-      </AuthLayout>
-    );
-  }
-
-  if (role === Role.Teacher && !context.isJoinedClass && !classId) {
-    return (
-      <>
-        <AuthLayout>
-          <CreateClassSection />
-        </AuthLayout>
-      </>
-    );
-  }
-
-  if (role === Role.Student && !context.isJoinedClass && !classId) {
-    return (
-      <>
-        <AuthLayout>
-          <JoinClassSection />
-        </AuthLayout>
-      </>
-    );
-  }
-
-  if (classStatus === ClassStatus.ENDED) {
-    return (
-      <div className="text-center py-8">
-        <h2 className="text-xl font-bold">课堂已结束</h2>
-        <p>感谢参与本次课堂</p>
-      </div>
-    );
-  }
-
-  return (
-    <ClassroomLayout role={role}>
-      {/* {role === Role.Teacher ? <TeacherView /> : <StudentView />} */}
-      <></>
-    </ClassroomLayout>
-  );
-};
-
-export const ClassroomPage = () => {
-  const { user } = useAuth();
-  return (
-    <>
-      <ClassroomProvider user={user!}>
-        <Classroom />
-      </ClassroomProvider>
-      <ToastContainer
-        position="top-right"
-        autoClose={500}
-        hideProgressBar={false}
-        newestOnTop={false}
-        closeOnClick
-        rtl={false}
-        pauseOnFocusLoss
-        draggable
-        pauseOnHover
-      />
-    </>
-  )
-}

+ 0 - 133
src/client/live/pages/LoginPage.tsx

@@ -1,133 +0,0 @@
-import React, { useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-
-const LoginPage: React.FC = () => {
-  const { register, handleSubmit, formState: { errors } } = useForm();
-  const [showPassword, setShowPassword] = useState(false);
-  const [loading, setLoading] = useState(false);
-  const { login } = useAuth();
-  const navigate = useNavigate();
-
-  const onSubmit = async (data: any) => {
-    try {
-      setLoading(true);
-      await login(data.username, data.password);
-      navigate('/');
-    } catch (error) {
-      console.error('Login error:', error);
-      alert((error as Error).message || '登录失败,请检查用户名和密码');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  return (
-    <div className="flex justify-center items-center min-h-screen bg-gray-100">
-      <div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
-        <div className="p-6 sm:p-8">
-          <div className="text-center mb-8">
-            <h2 className="text-2xl font-bold text-gray-900">网站登录</h2>
-            <p className="mt-2 text-sm text-gray-600">登录您的账号以继续</p>
-          </div>
-          
-          <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
-            <div>
-              <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
-                用户名
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <UserIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="username"
-                  type="text"
-                  className={`w-full pl-10 pr-3 py-2 border ${errors.username ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入用户名"
-                  {...register('username', { 
-                    required: '用户名不能为空',
-                    minLength: { value: 3, message: '用户名至少3个字符' }
-                  })}
-                />
-              </div>
-              {errors.username && (
-                <p className="mt-1 text-sm text-red-600">{errors.username.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
-                密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="password"
-                  type={showPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.password ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入密码"
-                  {...register('password', { 
-                    required: '密码不能为空',
-                    minLength: { value: 6, message: '密码至少6个字符' }
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  {showPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.password && (
-                <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <button
-                type="submit"
-                disabled={loading}
-                className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
-              >
-                {loading ? '登录中...' : '登录'}
-              </button>
-            </div>
-          </form>
-          
-          <div className="mt-6">
-            <div className="relative">
-              <div className="absolute inset-0 flex items-center">
-                <div className="w-full border-t border-gray-300"></div>
-              </div>
-              <div className="relative flex justify-center text-sm">
-                <span className="px-2 bg-white text-gray-500">还没有账号?</span>
-              </div>
-            </div>
-            
-            <div className="mt-4">
-              <button
-                type="button"
-                onClick={() => navigate('/mobile/register')}
-                className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-              >
-                注册账号
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default LoginPage;

+ 0 - 149
src/client/live/pages/MemberPage.tsx

@@ -1,149 +0,0 @@
-import React from 'react';
-import { UserIcon, PencilIcon } from '@heroicons/react/24/outline';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-
-const MemberPage: React.FC = () => {
-  const navigate = useNavigate();
-  const { user, logout } = useAuth();
-
-  if (!user) {
-    return (
-      <div className="text-center py-12">
-        <h2 className="text-2xl font-bold text-gray-900 mb-4">用户不存在</h2>
-        <button
-          onClick={() => navigate('/')}
-          className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-        >
-          返回首页
-        </button>
-      </div>
-    );
-  }
-
-  return (
-    <div className="min-h-screen bg-gray-50">
-      <div className="container mx-auto px-4 py-8 max-w-4xl">
-        {/* 用户资料卡片 */}
-        <div className="bg-white rounded-lg shadow-sm p-6 mb-8">
-          <div className="flex flex-col items-center">
-            <div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center mb-4">
-              {user.avatar ? (
-                <img src={user.avatar} alt={user.nickname || user.username} className="h-full w-full object-cover rounded-full" />
-              ) : (
-                <UserIcon className="h-12 w-12 text-gray-500" />
-              )}
-            </div>
-            
-            <h1 className="text-2xl font-bold text-gray-900 mb-1">{user.nickname || user.username}</h1>
-            
-            <div className="flex space-x-8 my-4">
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">内容</p>
-              </div>
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">关注</p>
-              </div>
-              <div className="text-center">
-                <p className="text-2xl font-semibold text-gray-900">0</p>
-                <p className="text-sm text-gray-500">粉丝</p>
-              </div>
-            </div>
-            
-            <div className="flex">
-              <button
-                onClick={() => navigate('/profile/edit')}
-                className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
-              >
-                <PencilIcon className="w-4 h-4 mr-2" />
-                编辑资料
-              </button>
-              
-              <button
-                onClick={async () => {
-                  await logout();
-                  navigate('/');
-                }}
-                className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ml-4"
-              >
-                退出登录
-              </button>
-
-            </div>
-            
-            {(user as any).bio && (
-              <p className="mt-4 text-center text-gray-600 max-w-lg">
-                {(user as any).bio}
-              </p>
-            )}
-            
-            <div className="flex items-center mt-4 space-x-4">
-              {(user as any).location && (
-                <div className="flex items-center text-gray-600">
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
-                  </svg>
-                  <span className="text-sm">{(user as any).location}</span>
-                </div>
-              )}
-              {(user as any).website && (
-                <a
-                  href={(user as any).website}
-                  target="_blank"
-                  rel="noopener noreferrer"
-                  className="flex items-center text-blue-600 hover:text-blue-800"
-                >
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6v6m0 0v6m0-6h-6" />
-                  </svg>
-                  <span className="text-sm truncate max-w-[150px]">{(user as any).website}</span>
-                </a>
-              )}
-            </div>
-          </div>
-        </div>
-        
-        {/* 用户内容区域 */}
-        <div className="bg-white rounded-lg shadow-sm p-6">
-          <h2 className="text-xl font-semibold mb-6">个人资料</h2>
-          
-          <div className="space-y-4">
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">用户名</h3>
-              <p className="text-gray-900">{user.username}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">电子邮箱</h3>
-              <p className="text-gray-900">{user.email || '未设置'}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">注册时间</h3>
-              <p className="text-gray-900">{user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '未知'}</p>
-            </div>
-            
-            <div className="border-b border-gray-100 pb-4">
-              <h3 className="text-sm font-medium text-gray-500 mb-1">最后登录</h3>
-              <p className="text-gray-900">{user.updatedAt ? new Date(user.updatedAt).toLocaleString() : '从未登录'}</p>
-            </div>
-          </div>
-          
-          <div className="mt-8">
-            <button
-              onClick={() => navigate('/profile/security')}
-              className="w-full py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-            >
-              安全设置
-            </button>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default MemberPage;

+ 0 - 190
src/client/live/pages/RegisterPage.tsx

@@ -1,190 +0,0 @@
-import React, { useState } from 'react';
-import { useForm } from 'react-hook-form';
-import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '@/client/live/hooks/AuthProvider';
-import { authClient } from '@/client/api';
-
-const RegisterPage: React.FC = () => {
-  const { register, handleSubmit, watch, formState: { errors } } = useForm();
-  const [showPassword, setShowPassword] = useState(false);
-  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
-  const [loading, setLoading] = useState(false);
-  const { login } = useAuth();
-  const navigate = useNavigate();
-  const password = watch('password', '');
-
-  const onSubmit = async (data: any) => {
-    try {
-      setLoading(true);
-      
-      // 调用注册API
-      const response = await authClient.register.$post({
-        json: {
-          username: data.username,
-          password: data.password,
-        }
-      });
-      
-      if (response.status !== 201) {
-        const result = await response.json();
-        throw new Error(result.message || '注册失败');
-      }
-      
-      // 注册成功后自动登录
-      await login(data.username, data.password);
-      
-      // 跳转到首页
-      navigate('/');
-    } catch (error) {
-      console.error('Registration error:', error);
-      alert((error as Error).message || '注册失败,请稍后重试');
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  return (
-    <div className="flex justify-center items-center min-h-screen bg-gray-100">
-      <div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
-        <div className="p-6 sm:p-8">
-          <div className="text-center mb-8">
-            <h2 className="text-2xl font-bold text-gray-900">账号注册</h2>
-            <p className="mt-2 text-sm text-gray-600">创建新账号以开始使用</p>
-          </div>
-          
-          <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
-            <div>
-              <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
-                用户名
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <UserIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="username"
-                  type="text"
-                  className={`w-full pl-10 pr-3 py-2 border ${errors.username ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入用户名"
-                  {...register('username', { 
-                    required: '用户名不能为空',
-                    minLength: { value: 3, message: '用户名至少3个字符' },
-                    maxLength: { value: 20, message: '用户名不能超过20个字符' }
-                  })}
-                />
-              </div>
-              {errors.username && (
-                <p className="mt-1 text-sm text-red-600">{errors.username.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
-                密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="password"
-                  type={showPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.password ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请输入密码"
-                  {...register('password', { 
-                    required: '密码不能为空',
-                    minLength: { value: 6, message: '密码至少6个字符' },
-                    maxLength: { value: 30, message: '密码不能超过30个字符' }
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowPassword(!showPassword)}
-                >
-                  {showPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.password && (
-                <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
-                确认密码
-              </label>
-              <div className="relative">
-                <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
-                  <LockClosedIcon className="h-5 w-5 text-gray-400" />
-                </div>
-                <input
-                  id="confirmPassword"
-                  type={showConfirmPassword ? 'text' : 'password'}
-                  className={`w-full pl-10 pr-10 py-2 border ${errors.confirmPassword ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
-                  placeholder="请再次输入密码"
-                  {...register('confirmPassword', { 
-                    required: '请确认密码',
-                    validate: value => value === password || '两次密码输入不一致'
-                  })}
-                />
-                <button 
-                  type="button"
-                  className="absolute inset-y-0 right-0 pr-3 flex items-center"
-                  onClick={() => setShowConfirmPassword(!showConfirmPassword)}
-                >
-                  {showConfirmPassword ? (
-                    <EyeSlashIcon className="h-5 w-5 text-gray-400" />
-                  ) : (
-                    <EyeIcon className="h-5 w-5 text-gray-400" />
-                  )}
-                </button>
-              </div>
-              {errors.confirmPassword && (
-                <p className="mt-1 text-sm text-red-600">{errors.confirmPassword.message?.toString()}</p>
-              )}
-            </div>
-            
-            <div>
-              <button
-                type="submit"
-                disabled={loading}
-                className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
-              >
-                {loading ? '注册中...' : '注册'}
-              </button>
-            </div>
-          </form>
-          
-          <div className="mt-6">
-            <div className="relative">
-              <div className="absolute inset-0 flex items-center">
-                <div className="w-full border-t border-gray-300"></div>
-              </div>
-              <div className="relative flex justify-center text-sm">
-                <span className="px-2 bg-white text-gray-500">已有账号?</span>
-              </div>
-            </div>
-            
-            <div className="mt-4">
-              <button
-                type="button"
-                onClick={() => navigate('/mobile/login')}
-                className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
-              >
-                返回登录
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default RegisterPage;

+ 0 - 92
src/client/live/pages/StockHomePage.tsx

@@ -1,92 +0,0 @@
-import { useNavigate } from "react-router";
-import { useAuth } from "@/client/live/hooks/AuthProvider";
-import { UserType } from "@/server/modules/users/user.enum";
-
-export default function StockHomePage() {
-  const { user } = useAuth();
-  const navigate = useNavigate();
-
-  const handleClassroomClick = () => {
-    if (user?.userType === UserType.TEACHER) {
-      navigate('/mobile/classroom?role=' + UserType.TEACHER);
-    } else {
-      navigate('/mobile/classroom?role=' + UserType.STUDENT);
-    }
-  };
-
-  
-  return (
-    <div className="min-h-screen bg-gray-50 flex flex-col">
-      {/* 顶部导航 */}
-      <header className="bg-blue-600 text-white shadow-md fixed w-full z-10">
-        <div className="container mx-auto px-4 py-3 flex justify-between items-center">
-          <h1 className="text-xl font-bold">股票训练系统</h1>
-          {user ? (
-            <div className="flex items-center space-x-4">
-              <div className="flex items-center cursor-pointer" onClick={() => navigate(`/mobile/member`)}>
-                <div className="w-8 h-8 rounded-full bg-white text-blue-600 flex items-center justify-center mr-2">
-                  <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
-                    <path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
-                  </svg>
-                </div>
-                <span className="hidden md:inline">{user.username}</span>
-              </div>
-            </div>
-          ) : (
-            <div className="flex space-x-2">
-              <button 
-                onClick={() => navigate('/mobile/login')}
-                className="px-3 py-1 rounded text-sm bg-white text-blue-600 hover:bg-blue-50"
-              >
-                登录
-              </button>
-              <button 
-                onClick={() => navigate('/mobile/register')}
-                className="px-3 py-1 rounded text-sm bg-white text-blue-600 hover:bg-blue-50"
-              >
-                注册
-              </button>
-            </div>
-          )}
-        </div>
-      </header>
-      
-      {/* 主内容区 */}
-      <main className="flex-grow container mx-auto px-4 pt-24 pb-12">
-        
-        <div className="flex flex-col gap-4 max-w-md mx-auto">
-          <button
-            onClick={handleClassroomClick}
-            className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg shadow-md transition-colors duration-200 text-center"
-          >
-            解盘室
-          </button>
-          <button
-            onClick={() => navigate('/mobile/exam')}
-            className="bg-green-600 hover:bg-green-700 text-white font-medium py-3 px-6 rounded-lg shadow-md transition-colors duration-200 text-center"
-          >
-            考试模式
-          </button>
-          <button
-            onClick={() => navigate('/mobile/xunlian')}
-            className="bg-purple-600 hover:bg-purple-700 text-white font-medium py-3 px-6 rounded-lg shadow-md transition-colors duration-200 text-center"
-          >
-            训练模式
-          </button>
-        </div>
-      </main>
-      
-      {/* 页脚 */}
-      <footer className="bg-white border-t border-gray-200 py-4">
-        <div className="container mx-auto px-4 text-center text-gray-500 text-sm">
-          网站模板 ©{new Date().getFullYear()} Created with React & Tailwind CSS
-          <div className="mt-2 space-x-4">
-            <a href="/admin" className="text-blue-600 hover:underline">管理后台</a>
-            <span className="text-gray-300">|</span>
-            <a href="/ui" className="text-blue-600 hover:underline">Api</a>
-          </div>
-        </div>
-      </footer>
-    </div>
-  );
-}

+ 0 - 121
src/client/live/pages/XunlianPage.tsx

@@ -1,121 +0,0 @@
-import React, { useState } from "react";
-import dayjs from "dayjs";
-import { useQuery } from "@tanstack/react-query";
-// import type { XunlianCode } from "../share/types_stock.ts";
-import { useNavigate } from "react-router";
-import { stockXunlianCodesClient } from "@/client/api";
-import { InferResponseType } from "hono";
-
-type XunlianCode = InferResponseType<typeof stockXunlianCodesClient.$get, 200>['data'][0];
-
-export function XunlianPage() {
-  const [visibleStocks, setVisibleStocks] = useState<Record<number, boolean>>({});
-  const navigate = useNavigate();
-
-  const { data: codes, isLoading } = useQuery({
-    queryKey: ["xunlian-codes"],
-    queryFn: async (): Promise<XunlianCode[]> => {
-      const res = await stockXunlianCodesClient.$get({
-        query:{}
-      })
-      if(!res.ok){
-        const { message } = await res.json();
-        throw new Error(message)
-      }
-      const response = await res.json();
-      return response.data;
-    },
-    initialData: []
-  });
-
-  const toggleStockVisibility = (id: number, e: React.MouseEvent) => {
-    e.stopPropagation();
-    setVisibleStocks((prev: Record<number, boolean>) => ({
-      ...prev,
-      [id]: !prev[id]
-    }));
-  };
-
-  const handleCardClick = (code: XunlianCode) => {
-    navigate(`/mobile/stock?code=${code.code}`);
-  };
-
-  return (
-    <div className="p-4">
-      <div className="flex justify-between items-center mb-4">
-        <h1 className="text-2xl font-bold">训练案例</h1>
-      </div>
-
-      <div className="grid gap-4">
-        {isLoading ? (
-          <div className="text-center text-gray-500 py-8">
-            <div className="animate-spin inline-block w-6 h-6 border-[3px] border-current border-t-transparent text-blue-600 rounded-full" role="status" aria-label="loading">
-              <span className="sr-only">加载中...</span>
-            </div>
-            <div className="mt-2">加载中...</div>
-          </div>
-        ) : codes.length === 0 ? (
-          <div className="text-center text-gray-500 py-8">
-            暂无训练案例
-          </div>
-        ) : (
-          codes.map((code: XunlianCode) => (
-            <div
-              key={code.id}
-              className="border rounded-lg p-4 hover:shadow-lg transition-shadow cursor-pointer"
-              onClick={() => handleCardClick(code)}
-            >
-              <div className="flex justify-between items-start">
-                <div>
-                  <h2 className="text-lg font-semibold flex items-center gap-2">
-                    {visibleStocks[code.id] ? (
-                      <>
-                        {code.stockName} {code.name} ({code.code})
-                        <button
-                          onClick={(e) => toggleStockVisibility(code.id, e)}
-                          className="text-gray-500 hover:text-gray-700"
-                          title="隐藏股票信息"
-                        >
-                          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5">
-                            <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
-                            <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
-                          </svg>
-                        </button>
-                      </>
-                    ) : (
-                      <>
-                        {code.name}
-                        <button
-                          onClick={(e) => toggleStockVisibility(code.id, e)}
-                          className="text-gray-400 hover:text-gray-600"
-                          title="显示股票信息"
-                        >
-                          <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5">
-                            <path strokeLinecap="round" strokeLinejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
-                          </svg>
-                        </button>
-                      </>
-                    )}
-                  </h2>
-                  {code.description && (
-                    <p className="text-gray-600 mt-1">{code.description}</p>
-                  )}
-                </div>
-                <div className="text-sm text-gray-500">
-                  <div>{dayjs(code.tradeDate).format("YYYY-MM-DD")}</div>
-                </div>
-              </div>
-              {code.type && (
-                <div className="mt-2">
-                  <span className="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">
-                    {code.type}
-                  </span>
-                </div>
-              )}
-            </div>
-          ))
-        )}
-      </div>
-    </div>
-  );
-}

+ 0 - 105
src/client/live/routes.tsx

@@ -1,105 +0,0 @@
-import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router';
-import { ProtectedRoute } from './components/ProtectedRoute';
-import { ErrorPage } from './components/ErrorPage';
-import { NotFoundPage } from './components/NotFoundPage';
-// import { MainLayout } from './layouts/MainLayout';
-import { ClassroomPage } from './pages/ClassroomPage';
-// import { SubmissionRecordsPage } from './pages/SubmissionRecordsPage';
-// import { StockDataPage } from './pages/StockDataPage';
-// import { StockXunlianCodesPage } from './pages/StockXunlianCodesPage';
-// import { DateNotesPage } from './pages/DateNotesPage';
-import LoginPage from './pages/LoginPage';
-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';
-import RegisterPage from './pages/RegisterPage';
-import MemberPage from './pages/MemberPage';
-import { MainLayout } from './layouts/MainLayout';
-
-export const router = createBrowserRouter([
-  {
-    path: '/',
-    element: <Navigate to="/live" replace />
-  },
-  {
-    path: '/live/login',
-    element: <LoginPage />
-  },
-  {
-    path: '/live/register',
-    element: <RegisterPage />
-  },
-  {
-    path: '/live',
-    element: (
-      <ProtectedRoute>
-        <MainLayout />
-      </ProtectedRoute>
-    ),
-    children: [
-      {
-        path: '',
-        element: <StockHomePage />
-      },
-      {
-        path: 'member',
-        element: <MemberPage />
-      },
-      {
-        path: '*',
-        element: <NotFoundPage />,
-        errorElement: <ErrorPage />
-      },
-    ],
-  },
-  // {
-  //   path: '/live/classroom',
-  //   element: (
-  //     <ProtectedRoute>
-  //       <ClassroomPage />
-  //     </ProtectedRoute>
-  //   ),
-  // },
-  {
-    path: '/live/xunlian',
-    element: (
-      <ProtectedRoute>
-        <XunlianPage />
-      </ProtectedRoute>
-    ),
-  },
-  {
-    path: '/live/classroom/:id?/:role?',
-    element: <ProtectedRoute><ClassroomPage /></ProtectedRoute>,
-    errorElement: <ErrorPage />
-  },
-  {
-    path: '/live/stock',
-    element: <ProtectedRoute><StockMain /></ProtectedRoute>,
-    errorElement: <ErrorPage />
-  },
-  {
-    path: '/live/exam/card',
-    element: <ProtectedRoute><ExamCard /></ProtectedRoute>,
-    errorElement: <ErrorPage />
-  },
-  {
-    path: '/live/exam/:id',
-    element: <ProtectedRoute><ExamAdmin /></ProtectedRoute>,
-    errorElement: <ErrorPage />
-  },
-  {
-    path: '/live/exam',
-    element: <ProtectedRoute><ExamIndex /></ProtectedRoute>,
-    errorElement: <ErrorPage />
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  },
-]);