Przeglądaj źródła

feat "Edit页面编写"

Leonezhurui 2 lat temu
rodzic
commit
d5d4745cf3

+ 18 - 10
package-lock.json

@@ -22,6 +22,7 @@
         "ahooks": "^3.7.10",
         "antd": "^5.14.2",
         "axios": "^1.6.7",
+        "lodash": "^4.17.21",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
         "react-router": "^6.22.1",
@@ -35,7 +36,8 @@
         "zustand": "^4.5.1"
       },
       "devDependencies": {
-        "@craco/craco": "^7.1.0"
+        "@craco/craco": "^7.1.0",
+        "@types/lodash": "^4.17.0"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -98,9 +100,9 @@
       }
     },
     "node_modules/@ant-design/icons": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.3.1.tgz",
-      "integrity": "sha512-85zROTJCCApQn0Ee6L9561+Vd7yVKtSWNm2TpmOsYMrumchbzaRK83x1WWHv2VG+Y1ZAaKkDwcnnSPS/eSwNHA==",
+      "version": "5.3.4",
+      "resolved": "https://r.cnpmjs.org/@ant-design/icons/-/icons-5.3.4.tgz",
+      "integrity": "sha512-U5eTSeSFr2V9SeJzYOo5mybAZfsoNuiIA8bvFoZUe+h9LBLs8UwrVaVwcMQC4AhBuojXkLMlmtnIlvUczXXHaQ==",
       "dependencies": {
         "@ant-design/colors": "^7.0.0",
         "@ant-design/icons-svg": "^4.4.0",
@@ -4274,6 +4276,12 @@
       "resolved": "https://r2.cnpmjs.org/@types/json5/-/json5-0.0.29.tgz",
       "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.0",
+      "resolved": "https://r.cnpmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
+      "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
+      "dev": true
+    },
     "node_modules/@types/mime": {
       "version": "1.3.5",
       "resolved": "https://r.cnpmjs.org/@types/mime/-/mime-1.3.5.tgz",
@@ -5185,17 +5193,17 @@
       }
     },
     "node_modules/antd": {
-      "version": "5.15.2",
-      "resolved": "https://registry.npmmirror.com/antd/-/antd-5.15.2.tgz",
-      "integrity": "sha512-EByEiCQknPKJVYfD+zneXwEvjdFzvMw8CZrsxw9nq19ftC4uMcMkZ2irasW7RQQGg9i7XsAZpAwYz3anuFX+EA==",
+      "version": "5.15.3",
+      "resolved": "https://r.cnpmjs.org/antd/-/antd-5.15.3.tgz",
+      "integrity": "sha512-53dpdGbfwipHVbqITmppp8N16i+BscMzz8NUNwaJgxwSvO9VQh/NfC/90lqGq3I2oBmxQ8TzRIxzFVKD/9OhlQ==",
       "dependencies": {
         "@ant-design/colors": "^7.0.2",
         "@ant-design/cssinjs": "^1.18.4",
-        "@ant-design/icons": "^5.3.1",
+        "@ant-design/icons": "^5.3.3",
         "@ant-design/react-slick": "~1.0.2",
         "@babel/runtime": "^7.24.0",
         "@ctrl/tinycolor": "^3.6.1",
-        "@rc-component/color-picker": "~1.5.2",
+        "@rc-component/color-picker": "~1.5.3",
         "@rc-component/mutate-observer": "^1.1.0",
         "@rc-component/tour": "~1.14.2",
         "@rc-component/trigger": "^2.0.0",
@@ -5211,7 +5219,7 @@
         "rc-dropdown": "~4.2.0",
         "rc-field-form": "~1.42.1",
         "rc-image": "~7.6.0",
-        "rc-input": "~1.4.3",
+        "rc-input": "~1.4.5",
         "rc-input-number": "~9.0.0",
         "rc-mentions": "~2.11.1",
         "rc-menu": "~9.13.0",

+ 3 - 1
package.json

@@ -17,6 +17,7 @@
     "ahooks": "^3.7.10",
     "antd": "^5.14.2",
     "axios": "^1.6.7",
+    "lodash": "^4.17.21",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-router": "^6.22.1",
@@ -54,6 +55,7 @@
     ]
   },
   "devDependencies": {
-    "@craco/craco": "^7.1.0"
+    "@craco/craco": "^7.1.0",
+    "@types/lodash": "^4.17.0"
   }
 }

+ 37 - 0
src/apis/ai.ts

@@ -0,0 +1,37 @@
+import axiosInstance from "@/apis/axios.config";
+import {IDialogue} from "@/types/dialogue";
+
+const AI_PREFIX = "/api/ai"
+
+const aiApis = {
+    getAIDialogues(assignmentId: number, userId: number, page: number=0, size: number=20) {
+        return axiosInstance.get(`${AI_PREFIX}`, {
+            params: {
+                assignmentId,
+                userId,
+                page,
+                size
+            }
+        })
+    },
+    requestAI(dialogueId: string, model: string, messages: IDialogue[]) {
+        return axiosInstance.put(`${AI_PREFIX}`, {
+            model: model,
+            messages: messages
+        }, {
+            params: {
+                dialogueId: dialogueId
+            }
+        })
+    },
+    rewrite(assignmentId: number, userId: number) {
+        return axiosInstance.post(`${AI_PREFIX}/rewrite`, {}, {
+            params: {
+                assignmentId,
+                studentId: userId
+            }
+        })
+    }
+}
+
+export default aiApis

+ 6 - 6
src/apis/axios.config.ts

@@ -1,8 +1,8 @@
 import axios from "axios";
 import useStore from "@/store";
 
-const BASEURL = 'http://47.111.23.171:8081'
-// const BASEURL = 'http://localhost:8080'
+// const BASEURL = 'http://47.111.23.171:8081'
+const BASEURL = 'http://localhost:8080'
 
 const axiosInstance = axios.create({
     baseURL: BASEURL,
@@ -14,7 +14,7 @@ const axiosInstance = axios.create({
 //请求拦截器
 axiosInstance.interceptors.request.use(
     config => {
-        const token = localStorage.getItem('token')
+        const token = JSON.parse(sessionStorage.getItem('user-storage') as string).state.token
         // 如果存在token,则每次请求前加入到请求头中
         if (token) config.headers["Authorization"] = token
         return config
@@ -26,9 +26,9 @@ axiosInstance.interceptors.request.use(
 
 axiosInstance.interceptors.response.use(
     response => {
-        if (response.data.code === 200) {
-            return response.data.data
-        }
+        // if (response.data.code === 200) {
+        //     return response.data.data as any
+        // }
         return response
     },
     error => {

+ 33 - 0
src/apis/engagement.ts

@@ -0,0 +1,33 @@
+import axiosInstance from "@/apis/axios.config";
+
+const ENGAGE_PREFIX = "/api/assignment/engage"
+
+const engagementApis = {
+    getEngagement: (studentId: number, assignmentId: number) => {
+        return axiosInstance.get(ENGAGE_PREFIX, {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    },
+    engagementSubmit: (studentId: number, assignmentId: number) => {
+        return axiosInstance.put(ENGAGE_PREFIX+"/submit", {}, {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    },
+    engagementStage: (studentId: number, assignmentId: number) => {
+        return axiosInstance.put(ENGAGE_PREFIX+"/stage", {}, {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    },
+
+}
+
+export default engagementApis

+ 5 - 1
src/apis/index.ts

@@ -1,12 +1,16 @@
 import userApis from "@/apis/user";
 import testApis from "@/apis/test";
 import editorApis from "@/apis/editor";
+import engagementApis from "@/apis/engagement";
+import aiApis from "@/apis/ai";
 
 
 const apis = {
     ...userApis,
     ...testApis,
-    ...editorApis
+    ...editorApis,
+    ...engagementApis,
+    ...aiApis
 }
 
 const useApis = () => {

+ 4 - 2
src/apis/user.ts

@@ -1,11 +1,13 @@
 import axiosInstance from "@/apis/axios.config";
 import {ILoginUserFrom} from "@/types/user";
 
-const USER_PREFIX = "/user"
+const USER_PREFIX = "/api/user"
 
 const userApis = {
     login: (userForm: ILoginUserFrom) => {
-        return axiosInstance.post(`${USER_PREFIX}`, userForm)
+        return axiosInstance.post(`${USER_PREFIX}/login`, {
+            ...userForm
+        })
     }
 }
 

+ 13 - 12
src/components/AIChatter/AIChatter.module.scss

@@ -1,9 +1,10 @@
 .container {
   position: relative;
   width: 100%;
-  height: 100%;
+  height: 60%;
   padding: 10px;
   box-sizing: border-box;
+  margin-bottom: 15px;
 
   .header {
     position: relative;
@@ -24,7 +25,7 @@
   .message-box {
     width: 100%;
     height: 80%;
-    overflow-y: auto;
+    overflow: auto;
 
     .item {
       display: flex;
@@ -48,16 +49,16 @@
     right: 10px;
     height: 10%;
 
-    &::before {
-      content: "";
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: -10px; /* 将阴影向上偏移 */
-      height: 10px; /* 控制阴影的高度 */
-      box-shadow: 0 -8px 8px -4px rgba(0,0,0,0.2); /* 自定义阴影的大小和颜色,注意垂直偏移和扩展半径为负值 */
-      z-index: -1;
-    }
+    //&::before {
+    //  content: "";
+    //  position: absolute;
+    //  left: 0;
+    //  right: 0;
+    //  top: -10px; /* 将阴影向上偏移 */
+    //  height: 10px; /* 控制阴影的高度 */
+    //  box-shadow: 0 -8px 8px -4px rgba(0,0,0,0.2); /* 自定义阴影的大小和颜色,注意垂直偏移和扩展半径为负值 */
+    //  z-index: -1;
+    //}
 
     .input-group {
       display: flex;

+ 129 - 66
src/components/AIChatter/AIChatter.tsx

@@ -1,13 +1,25 @@
-import React, {useRef, useState} from "react";
+import React, {useEffect, useRef, useState} from "react";
 import EDropdownSelect from "@/components/EDropdownSelect/EDropdownSelect";
 import styles from "./AIChatter.module.scss"
 import {Avatar, Button, Dropdown, Input, Menu, MenuProps, Spin} from "antd";
 import {ArrowUpOutlined, DownOutlined} from "@ant-design/icons";
+import useStore from "@/store";
+import {IDialogue} from "@/types/dialogue";
+import useApis from "@/apis";
 
-const AIChatter = () => {
-    const modelOptions = ["Seecoder AI", "ChatGPT3.5", "ChatGLM3"]
-    const AIAvatar = "https://cdn.pixabay.com/photo/2023/11/14/11/07/sparrow-8387465_1280.jpg"
-    const userAvatar = "https://cdn.pixabay.com/photo/2023/07/22/08/54/european-shorthair-8142959_1280.jpg"
+
+interface IProps {
+    dialogueId: string;
+    messages: IDialogue[]
+}
+
+const AIChatter:React.FC<IProps> = ({dialogueId, messages}) => {
+
+    const store = useStore()
+
+    const apis = useApis()
+
+    const modelOptions = ["ChatGLM3", "QWen14B"]
     const questionTemplates = [
         "请问您提供哪些服务?",
         "您的营业时间是?",
@@ -22,72 +34,108 @@ const AIChatter = () => {
         )
     }))
 
-    const inputRef = useRef<any>(null); // 创建引用
+    const inputRef = useRef<any>(null) // 创建引用
+
+    const messagesEndRef = useRef<HTMLDivElement>(null)
+
+    const containerTopRef = useRef<HTMLDivElement>(null);
 
 
-    const [data, setData] = useState({
+    const [data, setData] = useState<{
+        model: string;
+        questionContent: string;
+        messages: IDialogue[];
+        loading: boolean;
+        loadedPage: number;
+        pageSize: number;
+    }>({
         model: modelOptions[0],
         questionContent: '',
-        messages: [
-            {
-                "content": "你好,你是什么AI工具?",
-                "senderType": "User",
-                "timestamp": new Date("2024-02-29 18:00:00")
-            },
-            {
-                "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
-                "senderType": "AI",
-                "timestamp": new Date("2024-02-29 18:01:10")
-            },
-            {
-                "content": "你好,你是什么AI工具?",
-                "senderType": "User",
-                "timestamp": new Date("2024-02-29 18:00:00")
-            },
-            {
-                "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
-                "senderType": "AI",
-                "timestamp": new Date("2024-02-29 18:01:10")
-            },
-            {
-                "content": "你好,你是什么AI工具?",
-                "senderType": "User",
-                "timestamp": new Date("2024-02-29 18:00:00")
-            },
-            {
-                "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
-                "senderType": "AI",
-                "timestamp": new Date("2024-02-29 18:01:10")
-            },
-            {
-                "content": "你好,你是什么AI工具?",
-                "senderType": "User",
-                "timestamp": new Date("2024-02-29 18:00:00")
-            },
-            {
-                "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
-                "senderType": "AI",
-                "timestamp": new Date("2024-02-29 18:01:10")
-            }
-        ],
+        messages: [],
         loading: false, // 请求加载过程
+        loadedPage: 0, //默认加载第一页
+        pageSize: 20, //默认加载20条内容,这个不变
     });
 
+
+    useEffect(() => {
+        if (messages)
+            setData(prev => ({...prev, messages: messages}))
+        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+    }, [messages])
+
+
+    // 保证本地添加数据时,可以自动下拉
+    useEffect(() => {
+        messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+    }, [data.messages])
+
+
+    useEffect(() => {
+        const handleScroll = () => {
+            if (containerTopRef.current) {
+                const { top } = containerTopRef.current.getBoundingClientRect() // 获取目标组件的位置
+                const reachedTop = top >= 0 // 检查组件是否到达顶部
+
+                // 确保,不重复加载数据
+                if (reachedTop && !data.loading) {
+                    console.log("触顶了")
+                    // setData(prev => ({...prev, loadedPage: prev.loadedPage+1}))
+                    // loadData()
+                }
+            }
+        };
+
+        window.addEventListener('scroll', handleScroll)
+
+        return () => window.removeEventListener('scroll', handleScroll)
+    }, [data.loading]);
+
+
     // 更新选中值的函数
     const setValue = (model: string) => {
-        console.log("父组件中接受到的值", model)
         setData(prev => ({ ...prev, model }));
     };
 
+    const loadData = () => {
+        // 假设这是从后端加载数据的函数
+        apis.getAIDialogues(1, store.user.id, data.loadedPage, data.pageSize)
+            .then((_res:any) => {
+                const newData = _res.data.data.content // 请求到的数据
+
+                // 和原有数组拼接,注意得拼接在前面
+                setData(prev => ({
+                    ...prev,
+                    messages: newData.concat(prev.messages)
+                }))
+            })
+
+    }
+
     const handleSendQuestion = () => {
         setData(prev => ({...prev, loading: true}))
-        let question = data.questionContent;
-        console.log("发送请求,请求参数为", question);
-        setData(prev => ({...prev, questionContent: ''}))
-        // TODO 请求方法,设置loading
-        setTimeout(() => {
-            setData(prev => ({...prev, loading: false}))
-        }, 3000)
+
+        // 注意:setData是异步的
+        let requestBody = data.messages
+        requestBody.push({
+            role: 'user',
+            content: data.questionContent
+        })
+
+        apis.requestAI(dialogueId, data.model, requestBody)
+            .then((_res:any) => {
+                const resDialogue = _res.data.data
+                console.log(resDialogue)
+                setData(prev => ({
+                    ...prev,
+                    messages: prev.messages.concat(resDialogue)
+                }))
+            }).catch((_err:any) => {
+            console.log(_err)
+        }).finally(() => {
+            setData(prev => ({...prev, questionContent: '', loading: false}))
+        })
+
     }
 
     const handleMenuClick = ({ key }: any) => {
@@ -98,6 +146,7 @@ const AIChatter = () => {
 
     return (
         <div className={styles.container}>
+            {/* 大语言模型选择器 */}
             <div className={styles.header}>
                 <div className={styles["model-selector"]}>
                     <EDropdownSelect
@@ -107,36 +156,50 @@ const AIChatter = () => {
                     ></EDropdownSelect>
                 </div>
             </div>
-            <div className={styles["message-box"]}>
+
+            {/* 消息区域,采用ChatGPT的交互模式 */}
+            <div className={styles["message-box"]} ref={containerTopRef}>
                 {
                     data.messages.map((item, index) => (
                         <div className={styles["item"]} key={index}>
                             <div>
-                                <Avatar
-                                    size={24}
-                                    shape={"circle"}
-                                    src={index%2 === 0 && item.senderType === "User"?userAvatar: AIAvatar}></Avatar>
+                                {/* 分类展示用户和AI的消息 */}
+                                {
+                                    index % 2 === 0 ?
+                                        <Avatar
+                                            size={32}
+                                            shape={"circle"}
+                                            src={store.user.userAvatar || ""}>{store.user.name.charAt(0)}</Avatar> :
+                                        <Avatar
+                                            size={32}
+                                            shape={"circle"}
+                                        >{data.model.charAt(0)}</Avatar>
+                                }
                             </div>
                             <div style={{marginLeft: "15px"}}>
-                                <div>{item.senderType==="User"? "You": data.model}</div>
+                                <div style={{marginBottom: "2px"}}>{index % 2 === 0? "You": data.model}</div>
                                 <div className={styles["content"]}>{item.content}</div>
                             </div>
-
                         </div>
                     ))
                 }
+                <div ref={messagesEndRef} />
             </div>
+
+            {/* 对话框区域,用于AI提问和提示词模版展现 */}
             <div className={styles["question-box"]}>
                 <div className={styles["input-group"]}>
-                    <Input
+                    <Input.TextArea
                         className={styles["input"]}
                         ref={inputRef}
                         variant={"borderless"}
                         size={"large"}
                         placeholder={"Message "+data.model+" ..."}
+                        autoSize={{ minRows: 1, maxRows: 2 }}
                         value={data.questionContent}
                         onChange={(e) => setData(prev => ({...prev, questionContent: e.target.value}))}
-                    />
+                    ></Input.TextArea>
+
                     {data.loading && <Spin style={{marginRight: "10px"}}/>}
                     {
                         !data.loading &&

+ 2 - 2
src/components/Button/Button.tsx

@@ -1,8 +1,8 @@
-import { Button as AntdButton, ConfigProvider } from 'antd';
+import {Button as AntdButton, ButtonProps, ConfigProvider} from 'antd';
 import { BaseButtonProps } from 'antd/es/button/button';
 import React from 'react';
 
-export function Button({ children, ...youth }: BaseButtonProps) {
+export function Button({ children, ...youth }: ButtonProps) {
   return <ConfigProvider
     theme={{
       components: {

+ 31 - 30
src/components/WordEditor/WordEditor.tsx

@@ -1,23 +1,22 @@
 import { DocumentEditor } from "@onlyoffice/document-editor-react";
-import React, {useState} from "react";
-import useStore from "@/store";
+import React, {useEffect, useMemo} from "react";
 
 interface IProps {
     onValueChange: (state: boolean) => void;
-    key: string;
-    title: string;
-    url: string;
+    documentKey: string;
+    documentTitle: string;
+    documentUrl: string;
+    userId: number;
+    username: string;
 }
 
-const WordEditor:React.FC<IProps> = ({onValueChange, key, title, url}) => {
+const WordEditor:React.FC<IProps> = React.memo(({onValueChange, documentKey, documentTitle, documentUrl, userId, username}) => {
 
-    const store = useStore()
+    // OnlyOffice Document Server地址
+    const documentServerUrl = "http://47.111.23.171:8082/"
 
-
-    const [data, setData] = useState({
-        documentServerUrl: "http://47.111.23.171:8082/",
-
-        config: {
+    // OnlyOffice编辑器配置,采用useMemo的方式,避免不必要的渲染
+    let config = useMemo(() => ({
             width: '100%',
             height: '100%',
             type: 'desktop',
@@ -25,9 +24,9 @@ const WordEditor:React.FC<IProps> = ({onValueChange, key, title, url}) => {
             mode: 'edit',
             document: {
                 fileType: "docx",
-                key, // 1
-                title, //2
-                url, // 3
+                key: documentKey, // 1
+                title: documentTitle, //2
+                url: documentUrl, // 3
                 permissions: {
                     "chat": false,
                     "comment": false,
@@ -64,14 +63,14 @@ const WordEditor:React.FC<IProps> = ({onValueChange, key, title, url}) => {
                     "toolbarNoTabs": false,
                     "uiTheme": "theme-light", // 编辑页面的主题色
                 },
-                // TODO 替换成系统用户信息
-                "user": { // 启用用户信息
-                    "id": store.user.id+"", // 4
-                    "name": store.user.name // 5
+                user: { // 启用用户信息
+                    id: userId+"", // 4
+                    name: username // 5
                 },
             },
-        },
-    });
+        }),[documentKey, documentTitle, documentUrl, userId, username])
+
+    useEffect(() => {}, [config])
 
     const onDocumentReady = function (event: any) {
         console.log("Document is loaded");
@@ -99,16 +98,18 @@ const WordEditor:React.FC<IProps> = ({onValueChange, key, title, url}) => {
 
     return (
         <>
-            <DocumentEditor
-                id="docxEditor"
-                documentServerUrl={data.documentServerUrl}
-                config={data.config}
-                events_onDocumentReady={onDocumentReady}
-                onLoadComponentError={onLoadComponentError}
-                events_onDocumentStateChange={onDocumentStateChange}
-            ></DocumentEditor>
+            {documentKey && documentTitle && documentUrl && (
+                <DocumentEditor
+                    id="docxEditor"
+                    documentServerUrl={documentServerUrl}
+                    config={config}
+                    events_onDocumentReady={onDocumentReady}
+                    onLoadComponentError={onLoadComponentError}
+                    events_onDocumentStateChange={onDocumentStateChange}
+                ></DocumentEditor>
+            )}
         </>
     )
-}
+})
 
 export default WordEditor;

+ 26 - 8
src/layout/Header/Header.tsx

@@ -1,5 +1,6 @@
 import { Avatar, ConfigProvider, Menu, MenuProps } from 'antd';
 import './index.scss';
+import useStore from "@/store";
 
 interface HeaderProps {
   items: MenuProps['items'];
@@ -9,6 +10,8 @@ interface HeaderProps {
 
 function Header({ items, current, onChange }: HeaderProps) {
 
+  const store = useStore();
+
   function onClick(...args: any[]) {
     onChange?.(...args);
   }
@@ -36,14 +39,29 @@ function Header({ items, current, onChange }: HeaderProps) {
         <Menu onClick={onClick} selectedKeys={current} mode="horizontal" items={items}/>
       </ConfigProvider>
     </div>
-    <div className="header-userName">
-      <Avatar size={32}>
-        user
-      </Avatar>
-      <div className="nickName">
-        user
-      </div>
-    </div>
+    {
+      store.token !== '' ?
+          <div className="header-userName">
+            {
+              store.user.userAvatar ?
+                  <Avatar size={32} src={store.user.userAvatar} /> :
+                    <Avatar size={32}>
+                      user
+                    </Avatar>
+            }
+            <div className="nickName">
+              {store.user.name}
+            </div>
+          </div> :
+          <div className="header-userName">
+            <Avatar size={32}>
+              user
+            </Avatar>
+            <div className="nickName">
+              user
+            </div>
+          </div>
+    }
   </div>;
 }
 

+ 38 - 1
src/pages/CourseSquare/component/Course/index.tsx

@@ -3,6 +3,7 @@ import {Swiper, SwiperSlide} from "swiper/react";
 import React, {useRef} from "react";
 import ArrowFrom from "@/image/arrow.png";
 import {SwiperClass} from "swiper/swiper-react";
+import {Image} from "antd";
 
 
 interface CourseInterface {
@@ -105,4 +106,40 @@ export function Course({title, onChange}: CourseInterface) {
             </Swiper>
         </div>
     </div>
-}
+}
+
+
+interface ICourseItemProps {
+    item: {
+        courseId: number;
+        courseName: string;
+        description: string;
+        teacherIds: number[];
+        courseImages: string[];
+        targetGrade: string | null;
+        startTime: Date;
+        endTime: Date;
+    }
+}
+
+const CourseItem:React.FC<ICourseItemProps> = ({item}) => {
+
+    const defaultImg = "https://cdn.pixabay.com/photo/2024/03/04/16/44/barberry-8612696_960_720.jpg"
+    return (
+        <div>
+            <div className="item-image">
+                {
+                    <Image src={item.courseImages.length === 0? defaultImg:item.courseImages[0]}/>
+                }
+            </div>
+            <div className="item-text">
+                <div className="item-text-title">
+                    {item.courseName}
+                </div>
+                <div className="item-text-desc">
+                    任课老师:XXX
+                </div>
+            </div>
+        </div>
+    )
+}

+ 3 - 1
src/pages/Home/Home.tsx

@@ -2,13 +2,15 @@ import {MyHomework} from "@/pages/Home/component/MyHomework";
 import TeachersHomeWork from "./component/TeachersHomeWork";
 import "./index.scss";
 import {CourseSquare} from "@/pages/CourseSquare";
+import useStore from "@/store";
 
 interface HomeProps {
     current: string;
 }
 
 function Home({current}: HomeProps) {
-    const state: "teachers" | "student" = "student";
+    const store = useStore()
+    const state: "teachers" | "student" = store.user.role === 'STUDENT'? 'student': 'teachers';
 
     console.log(current)
     return (

+ 54 - 1
src/pages/Home/component/AiDialogBox/index.tsx

@@ -1,8 +1,24 @@
 import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
 import { Avatar, Input } from 'antd';
 import './index.scss';
+import {useEffect, useState} from "react";
+import {IDialogue} from "@/types/dialogue";
 
-export function AiDialogBox() {
+
+interface IProps {
+  dialogueId: string;
+  messages: IDialogue[]
+}
+
+export const AiDialogBox:React.FC<IProps> = ({messages}) => {
+
+  const [data, setData] = useState<{dialogues: IDialogue[]}>({
+    dialogues: []
+  })
+
+  useEffect(() => {
+    setData(prev => ({...prev, dialogues: messages}))
+  }, [messages])
 
   return <div className="ai-box">
     <div className="ai-box-title">
@@ -10,6 +26,18 @@ export function AiDialogBox() {
     </div>
     <div className="box-dialog">
       <div className="box-content">
+        {/*{*/}
+        {/*  data.dialogues.map((dialogue, index) => {*/}
+        {/*    <div className={"user-dialog-item"}>*/}
+        {/*      <Avatar className="user-avatar">user</Avatar>*/}
+        {/*      <div className="user-dialog-text">*/}
+        {/*        这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容*/}
+        {/*        这是文字内容文字内容*/}
+        {/*      </div>*/}
+        {/*    </div>*/}
+        {/*  })*/}
+        {/*}*/}
+
         <div className="user-dialog-item">
           <Avatar className="user-avatar">user</Avatar>
           <div className="user-dialog-text">
@@ -24,6 +52,11 @@ export function AiDialogBox() {
             这是文字内容文字内容
           </div>
         </div>
+        <DialogueItem
+            username={"苏"}
+            content={"这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容 这是文字内容文字内容\n" +
+                "            这是文字内容文字内容"}
+        ></DialogueItem>
         <div className="user-dialog-item">
           <Avatar className="user-avatar">user</Avatar>
           <div className="user-dialog-text">
@@ -45,3 +78,23 @@ export function AiDialogBox() {
     </div>
   </div>;
 }
+
+interface IDialogueItemProps {
+  username: string;
+  avatarUrl?: string|null;
+  content: string;
+}
+const DialogueItem:React.FC<IDialogueItemProps> = ({username, avatarUrl=null, content}) => {
+  return (
+      <div className="user-dialog-item">
+        {
+          avatarUrl ?
+              <Avatar className="user-avatar" src={avatarUrl}></Avatar>:
+              <Avatar className="user-avatar">{username}</Avatar>
+        }
+        <div className="user-dialog-text">
+          {content}
+        </div>
+      </div>
+  )
+}

+ 1 - 1
src/pages/Home/component/Dictionaries/index.scss

@@ -1,5 +1,5 @@
 .dictionaries {
-  height: 50%;
+  height: 40%;
 
   .box-dialog {
     border: solid 1px #e1e1e1;

+ 109 - 10
src/pages/Home/component/Edit/index.tsx

@@ -2,30 +2,129 @@ import { Button } from "@/components/Button/Button";
 import { useEffect, useState } from "react";
 import WordEditor from "@/components/WordEditor/WordEditor";
 import "./index.scss";
+import useStore from "@/store";
+import useApis from "@/apis";
+import {engagementVO} from "@/types/vo";
+import {useNavigate} from "react-router";
+import {message} from "antd";
 
-export function Edit() {
-  const [isModified, setIsModified] = useState(false);
+interface IEditProps {
+  engagement: engagementVO
+}
+
+
+export const Edit:React.FC<IEditProps> = ({engagement}) => {
+
+  const apis = useApis()
+  const store = useStore() //获取用户信息
+  const nav = useNavigate()
+
+  const [data, setData] = useState<{
+    isModified: boolean,
+    hasModified: boolean,
+    isModalOpen: boolean,
+    fileTitle: string,
+  }>({
+    isModified: false,
+    hasModified: false,
+    isModalOpen: false,
+    fileTitle: '',
+  })
+
+
+  useEffect(() => {
+    setData(prev => ({...prev, fileTitle: store.user.name+"_"+"第一次作业"+".docx"}))
+  }, [])
+
+  const [messageApi, contextHolder] = message.useMessage();
+
+  /**
+   * 成功操作的消息提示
+   * @param msg
+   */
+  const success = (msg: string) => {
+    messageApi.open({
+      type: 'success',
+      content: msg
+    })
+  }
+
+  /**
+   * 失败操作的消息提示
+   * @param msg
+   */
+  const error = (msg: string) => {
+    console.log("失败操作", msg)
+    messageApi.open({
+      type: 'error',
+      content: msg,
+    });
+  };
+
+  // 用于检查,onlyoffice文本框是否被修改(同步的)
   const handleValueChange = (state: boolean) => {
-    setIsModified(state);
+    if (state) data.hasModified = true
+    setData(prev => ({...prev, isModified: state}));
   };
 
+  const handleSubmit = () => {
+    // 内容已保存,且已经修改过,则可以点击提交
+    if (!data.isModified && data.hasModified) {
+      apis.engagementSubmit(store.user.id, 1)
+          .then(_res => {
+            console.log(_res);
+            success(_res.data.data)
+            nav("/") // TODO 跳转到主页面(课程页面,或者我的作业页面)
+          })
+    } else if (data.isModified) {
+      error('编辑内容未保存,请保存后提交!')
+    } else if(!data.hasModified) {
+      error('尚未编辑,不可提交!')
+    } else {
+      error('未知错误!')
+    }
+  }
+
+  const handleStage = () => {
+    // apis.engagementStage(store.user.id, 1)
+    //     .then(_res => {
+    //       console.log(_res);
+    //       success(_res.data.data)
+    //     })
+    if (!data.isModified && data.hasModified) {
+      apis.engagementStage(store.user.id, 1)
+          .then(_res => {
+            console.log(_res);
+            success(_res.data.data)
+          })
+    } else if (data.isModified) {
+      error('编辑内容未保存,请保存后提交!')
+    } else if(!data.hasModified) {
+      error('尚未编辑,不可提交!')
+    } else {
+      error('未知错误!')
+    }
+  }
+
   return (
     <div className="edit">
+      {contextHolder}
       <div className="edit-box">
         <div>编辑区</div>
         <div className="edit-root">
           <WordEditor
             onValueChange={handleValueChange}
-            key={"13412453353_123524534324"}
-            title={"13412453353_123524534324.docx"}
-            url={
-              "http://eai-files.oss-cn-shanghai.aliyuncs.com/13412453353_123524534324.docx"
-            }
+            documentKey={engagement.fileKey}
+            documentTitle={data.fileTitle}
+            documentUrl={engagement.fileUrl}
+            userId={store.user.id}
+            username={store.user.name}
           ></WordEditor>
         </div>
         <div className="button-right">
-          <Button>提交</Button>
-          <Button>暂存</Button>
+
+          <Button onClick={handleSubmit}>提交</Button>
+          <Button onClick={handleStage}>暂存</Button>
         </div>
       </div>
     </div>

+ 52 - 3
src/pages/Home/component/MyHomework/index.tsx

@@ -3,18 +3,67 @@ import { Dictionaries } from "@/pages/Home/component/Dictionaries";
 import { Edit } from "@/pages/Home/component/Edit";
 import "./index.scss";
 import { Slider } from "@/pages/Home/component/Silder";
+import AIChatter from "@/components/AIChatter/AIChatter";
+import {useEffect, useState} from "react";
+import {IDialogue} from "@/types/dialogue";
+import useApis from "@/apis";
+import useStore from "@/store";
+import {useNavigate} from "react-router";
+import {engagementVO} from "@/types/vo";
 
 export function MyHomework() {
+    const apis = useApis()
+    const store = useStore()
+    const nav = useNavigate()
+
+    const [data, setData] = useState<{
+        messages: IDialogue[],
+        dialogueId: string;
+        engagement: engagementVO
+    }>({
+        messages: [],
+        dialogueId: '',
+        engagement: {} as engagementVO
+    })
+
+    const fetchData = async () => {
+        await apis.getAIDialogues(1, store.user.id)
+            .then((_res:any) => {
+                const data = _res.data.data
+                setData(prev => ({
+                    ...prev,
+                    dialogueId: data.dialogueId,
+                    messages: data.messages.content
+                }))
+            })
+
+        await apis.getEngagement(store.user.id, 1)
+            .then((_res:any) => {
+                console.log(_res)
+                setData(prev => ({...prev, engagement: _res.data.data}))
+            }).catch((_err:any) => {
+                console.log(_err)
+                nav('/login')
+            })
+    }
+
+    useEffect( () => {
+
+        fetchData()
+
+    }, [])
+
   return (
     <div className="my-work">
       <div className="my-work-slider">
-        <AiDialogBox />
+        {/*<AiDialogBox />*/}
+          <AIChatter dialogueId={data.dialogueId} messages={data.messages}/>
         <Dictionaries />
       </div>
       <div className="my-work-input">
-        <Edit />
+        <Edit engagement={data.engagement}/>
       </div>
-      <Slider />
+      <Slider dialogueId={data.dialogueId}/>
     </div>
   );
 }

+ 9 - 0
src/pages/Home/component/Silder/index.scss

@@ -63,6 +63,15 @@
     background-color: #d9d9d9;
     border-radius: 10px;
   }
+
+  .remark {
+    width: 100%;
+    height: 100%;
+    border-radius: 10px;
+    overflow-y: auto; /* 添加垂直滚动 */
+    white-space: pre-wrap; /* 保留换行和空格,允许自动换行 */
+    background-color: #f0f0f0;
+  }
 }
 
 

+ 42 - 4
src/pages/Home/component/Silder/index.tsx

@@ -1,17 +1,47 @@
 import { MenuUnfoldOutlined } from "@ant-design/icons";
-import { Typography } from "antd";
+import {Spin, Typography} from "antd";
 import { Button } from "@/components/Button/Button";
 import { useState } from "react";
 import "./index.scss";
+import useApis from "@/apis";
+import store from "@/store";
+import useStore from "@/store";
+
+
+interface ISliderProps {
+    dialogueId: string
+}
+
+export const Slider:React.FC<ISliderProps> = ({dialogueId}) => {
+    const apis = useApis()
+    const store = useStore()
 
-export function Slider() {
   const [isShrink, setShrink] = useState(false);
 
+    const [aiRemark, setAiRemark] = useState("")
+
+    const [loading, setLoading] = useState(false)
+
   const article =
     "这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字";
 
   const rootClassName = `slider-content ${isShrink ? "shrink" : ""}`;
 
+
+   // 请求重写文章内容
+  const handleRewrite = () => {
+      setLoading(true)
+      apis.rewrite(1, store.user.id)
+          .then((_res: any) => {
+              console.log(_res)
+              setAiRemark(_res.data.data.content)
+          }).catch((_err: any) => {
+          console.log(_err)
+      }).finally(() => {
+          setLoading(false)
+      })
+  }
+
   return (
     <div className={rootClassName}>
       <div className="slider-icons" onClick={() => setShrink(!isShrink)}>
@@ -51,9 +81,17 @@ export function Slider() {
             </div>
             <div className="edit-box-header">
               智能批改修改意见
-              <Button>智能批改</Button>
+              <Button onClick={handleRewrite}>智能批改</Button>
             </div>
-            <div className="capacity-item"></div>
+
+                  {
+                      aiRemark === "" ?
+                          <div className="capacity-item"></div>:
+                          <>
+                              <Spin spinning={loading} style={{height: '100%', width: '100%'}}></Spin>
+                              {!loading && <div className={"remark"}>{aiRemark}</div>}
+                          </>
+                  }
           </>
         )}
       </div>

+ 1 - 1
src/pages/LoginPage/LoginPage.tsx

@@ -36,7 +36,7 @@ const LoginPage = () => {
                                 className={`box-tabs-item ${!isLogin ? 'active' : ''}`}
                                 onClick={setLogin.bind(null, false)}
                             >
-                                手机验证码
+                                邮箱验证码
                             </div>
                         </div>
                         <Login isCode={!isLogin}/>

+ 56 - 8
src/pages/LoginPage/component/Login.tsx

@@ -1,8 +1,10 @@
-import {Button, Form, Input, Space} from "antd";
-import React from "react";
+import {Button, Form, Input, notification, Space} from "antd";
+import React, {ChangeEvent, useState} from "react";
 import "./index.scss"
 import {useCountdown} from "@/hooks/useCountdown";
 import {useNavigate} from "react-router";
+import useApis from "@/apis";
+import useStore from "@/store";
 
 interface LoginProps {
     // 是否是手机号登录
@@ -14,9 +16,17 @@ interface LoginProps {
 }
 
 export function Login({isCode, onCode, onSubmit}: LoginProps) {
+    const apis = useApis()
+    const store = useStore()
+
     const {start, isStart, countdown} = useCountdown()
     const nav = useNavigate()
 
+    const [data, setData] = useState({
+        officialNumber: "",
+        password: "",
+    })
+
     function onClickSing() {
         nav("/sign")
     }
@@ -38,9 +48,37 @@ export function Login({isCode, onCode, onSubmit}: LoginProps) {
         onSubmit?.(value)
     }
 
+    const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
+        console.log(e.target.name, e.target.value)
+        setData(prev => ({...prev, [e.target.name]: e.target.value}))
+    }
+
+    const handleLogin = () => {
+        apis.login({
+            officialNumber: data.officialNumber,
+            password: data.password
+        })
+            .then(_res => {
+                console.log(_res);
+                const data = _res.data.data
+                if (_res.data.code === 200) {
+                    store.setUser(data);
+                    store.setToken(data?.token)
+                    localStorage.setItem('Authorization', data?.token);
+                    localStorage.setItem('User', data);
+                    nav('/')
+                } else {
+                    console.log("登陆失败")
+                }
+            }).catch(_err => {
+            console.log("登陆失败")
+        })
+    }
+
+
     return <div className="box-vessel">
         <div className="vessel-title">
-            {!isCode ? '登录' : '手机号登录'}
+            {!isCode ? '账号登录' : '邮箱登录'}
         </div>
         <Form
             name="basic"
@@ -50,9 +88,13 @@ export function Login({isCode, onCode, onSubmit}: LoginProps) {
         >
             <Form.Item
                 name="username"
-                rules={[{required: true, message: isCode ? '手机号不能为空' : '账号不能为空'}]}
+                rules={[{required: true, message: isCode ? '邮箱不能为空' : '账号不能为空'}]}
             >
-                <Input placeholder="请输入账号"/>
+                <Input placeholder="请输入账号"
+                       name={"officialNumber"}
+                       value={data.officialNumber}
+                       onChange={handleInputChange}
+                />
             </Form.Item>
             <Form.Item
                 name="password"
@@ -68,12 +110,18 @@ export function Login({isCode, onCode, onSubmit}: LoginProps) {
                         >
                             {isStart ? countdown : '获取手机验证码'}
                         </Button>
-                    </Space.Compact> : <Input.Password placeholder="请输入密码"/>
+                    </Space.Compact> :
+                        <Input.Password
+                            placeholder="请输入密码"
+                            name={"password"}
+                            value={data.password}
+                            onChange={handleInputChange}
+                        />
                 }
             </Form.Item>
             <div className="forget">忘记密码?</div>
             <Form.Item>
-                <Button type="primary" htmlType="submit" block>
+                <Button type="primary" htmlType="submit" block onClick={handleLogin}>
                     用户登录
                 </Button>
             </Form.Item>
@@ -82,4 +130,4 @@ export function Login({isCode, onCode, onSubmit}: LoginProps) {
             </div>
         </Form>
     </div>
-}
+}

+ 107 - 24
src/pages/Test/Test.tsx

@@ -2,56 +2,139 @@ import WordEditor from "@/components/WordEditor/WordEditor";
 import styles from "./Test.module.scss"
 import useApis from "@/apis";
 import {useNavigate} from "react-router";
-import {useState} from "react";
-import {Modal} from "antd";
+import {useEffect, useState} from "react";
+import {message, Modal, notification} from "antd";
+import useStore from "@/store";
+import {engagementVO} from "@/types/vo";
+import { isEmpty } from 'lodash';
+
+interface IData {
+    engagement: engagementVO| {},
+    isModified: boolean,
+    isModalOpen: boolean,
+    fileTitle: string,
+}
+
 
 const Test = () => {
 
     const apis = useApis()
+    const store = useStore()
 
     const nav = useNavigate()
 
-    const [isModified, setIsModified] = useState(false)
-    const [isModalOpen, setIsModalOpen] = useState(false);
+    const [data, setData] = useState<IData>({
+        engagement: {},
+        isModified: false,
+        isModalOpen: false,
+        fileTitle: '',
+    })
+
+    useEffect(() => {
+        // 进入页面请求数据
+        apis.getEngagement(store.user.id, 1)
+            .then(_res => {
+                console.log(_res.data.data)
+                setData(prevState => ({...prevState, engagement: _res.data.data, fileTitle: store.user.name+"_"+"第一次作业"+".docx"}))
+            })
+    }, [])
+
 
     const showModal = () => {
-        setIsModalOpen(true);
+        setData(prev => ({...prev, isModalOpen: true}));
     };
 
     const handleOk = () => {
-        setIsModalOpen(false);
+        setData(prev => ({...prev, isModalOpen: false}));
     };
 
     const handleCancel = () => {
-        setIsModalOpen(false);
+        setData(prev => ({...prev, isModalOpen: false}));
     };
 
     const handleClick = () => {
-        if (isModified) showModal()
+        if (data.isModified) {
+            showModal()
+        }
         else nav("/")
     }
 
+    const [messageApi, contextHolder] = message.useMessage();
+
+    const error = () => {
+        messageApi.open({
+            type: 'error',
+            content: '编辑内容未保存,请保存后提交!',
+        });
+    };
+
+    const handleSubmit = () => {
+        if (!data.isModified) {
+            apis.engagementSubmit(store.user.id, 1)
+                .then(_res => {
+                    console.log(_res);
+                    nav("/")
+                })
+        } else {
+            error()
+        }
+    }
+    const handleStage = () => {
+        if (!data.isModified) {
+            apis.engagementStage(store.user.id, 1)
+                .then(_res => {
+                    console.log(_res);
+                    nav("/")
+                })
+        } else {
+            error()
+        }
+    }
+
+    const handleGoBack = () => {
+        if (!data.isModified) {
+            nav("/")
+        } else {
+            setData(prev => ({...prev, isModalOpen: true}))
+        }
+    }
+
+
     const handleValueChange = (state: boolean)=> {
-        setIsModified(state)
+        setData(prev => ({...prev, isModified: state}))
     }
 
+    if (isEmpty(data.engagement)) {
+        return <div>Loading...</div>; // 或者其他加载中的提示
+    }
+
+    // TODO 离开页面检测,进行弹框提醒
+
     return (
-        <div className={styles.container}>
-            <div className={styles["editor-box"]}>
-                <WordEditor
-                    onValueChange={handleValueChange}
-                    key={"13412453353_123524534324"}
-                    title={"13412453353_123524534324.docx"}
-                    url={"http://eai-files.oss-cn-shanghai.aliyuncs.com/13412453353_123524534324.docx"}
-                ></WordEditor>
+        <>
+            {contextHolder}
+            <div className={styles.container}>
+                <div className={styles["editor-box"]}>
+                    <WordEditor
+                        onValueChange={handleValueChange}
+                        documentKey={(data.engagement as engagementVO).fileKey}
+                        documentTitle={data.fileTitle}
+                        documentUrl={(data.engagement as engagementVO).fileUrl}
+                        userId={store.user.id}
+                        username={store.user.name}
+                    ></WordEditor>
+                </div>
+                <button onClick={handleSubmit}>提交</button>
+                <button onClick={handleStage}>暂存</button>
+                <button onClick={handleGoBack}>返回</button>
+
+                <Modal title="Basic Modal" open={data.isModalOpen} onOk={handleOk} onCancel={handleCancel}>
+                    <p>Some contents...</p>
+                    <p>Some contents...</p>
+                    <p>Some contents...</p>
+                </Modal>
             </div>
-            <button onClick={handleClick}>提交</button>
-            <Modal title="Basic Modal" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
-                <p>Some contents...</p>
-                <p>Some contents...</p>
-                <p>Some contents...</p>
-            </Modal>
-        </div>
+        </>
     )
 }
 

+ 0 - 5
src/router/index.tsx

@@ -3,7 +3,6 @@ import App from "@/pages/App";
 import LoginPage from "@/pages/LoginPage/LoginPage";
 import Sign from "@/pages/Sign/Sign"
 import Test from "@/pages/Test/Test";
-import {Edit} from "@/pages/Home/component/Edit";
 
 const router = createBrowserRouter([
     {
@@ -21,10 +20,6 @@ const router = createBrowserRouter([
     {
         path: '/test',
         element: <Test></Test>
-    },
-    {
-        path: '/edit',
-        element: <Edit></Edit>
     }
 ])
 

+ 11 - 6
src/store/index.ts

@@ -1,22 +1,27 @@
 import {create} from "zustand";
 import {IUser} from "@/types/user";
+import {persist, createJSONStorage} from "zustand/middleware";
 
 interface IUseStore {
     user: IUser;
     token: string;
     setToken: (token:string) => void;
-    setUser: (user:IUser) => void;
+    setUser: (user:any) => void;
 }
 
 
-const useStore = create<IUseStore>(set => ({
+const useStore = create<IUseStore>() (persist((set, get) => ({
     user: {
-        id: 123125345,
-        name: "苏韬叡"
+        id: 7,
+        name: "本小朱"
     } as IUser,
     token: '',
     setToken: (token: string) => set(() => ({ token })),
-    setUser: (user: IUser) => set(() => ({ user }))
-}))
+    setUser: (user: IUser) => set(() => ({ user })),
+}),
+    {
+        name: 'user-storage', // unique name
+        storage: createJSONStorage(() => sessionStorage), // specify localStorage as the storage
+    }))
 
 export default useStore

+ 4 - 0
src/types/dialogue.ts

@@ -0,0 +1,4 @@
+export interface IDialogue {
+    role: string;
+    content: string;
+}

+ 9 - 1
src/types/user.ts

@@ -3,9 +3,17 @@ export interface IUser {
     name: string;
     officialEmail: string;
     officialNumber: string;
+    role: "STUDENT"|"TEACHER"|"ADMIN",
+    userAvatar: string,
+    createTime: string,
+    englishName: string,
+    gender: "MAN"|"FEMALE"|null,
+    birthday: string,
+    phone: string,
+    contentEmail: string,
 }
 
 export interface ILoginUserFrom {
-    officialEmail: string;
+    officialNumber: string;
     password: string;
 }

+ 11 - 0
src/types/vo.ts

@@ -0,0 +1,11 @@
+export interface engagementVO {
+    "id": number,
+    "assignmentId": number,
+    "studentId": number,
+    "score": number,
+    "remark": string,
+    "fileUrl": string,
+    "version": number,
+    "status": string,
+    "fileKey": string,
+}

+ 22 - 12
yarn.lock

@@ -50,10 +50,10 @@
   resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz"
   integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
 
-"@ant-design/icons@^5.3.1":
-  version "5.3.1"
-  resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.3.1.tgz"
-  integrity sha512-85zROTJCCApQn0Ee6L9561+Vd7yVKtSWNm2TpmOsYMrumchbzaRK83x1WWHv2VG+Y1ZAaKkDwcnnSPS/eSwNHA==
+"@ant-design/icons@^5.3.1", "@ant-design/icons@^5.3.3":
+  version "5.3.4"
+  resolved "https://r.cnpmjs.org/@ant-design/icons/-/icons-5.3.4.tgz"
+  integrity sha512-U5eTSeSFr2V9SeJzYOo5mybAZfsoNuiIA8bvFoZUe+h9LBLs8UwrVaVwcMQC4AhBuojXkLMlmtnIlvUczXXHaQ==
   dependencies:
     "@ant-design/colors" "^7.0.0"
     "@ant-design/icons-svg" "^4.4.0"
@@ -1746,7 +1746,7 @@
     schema-utils "^3.0.0"
     source-map "^0.7.3"
 
-"@rc-component/color-picker@~1.5.2":
+"@rc-component/color-picker@~1.5.3":
   version "1.5.3"
   resolved "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.5.3.tgz"
   integrity sha512-+tGGH3nLmYXTalVe0L8hSZNs73VTP5ueSHwUlDC77KKRaN7G4DS4wcpG5DTDzdcV/Yas+rzA6UGgIyzd8fS4cw==
@@ -2265,6 +2265,11 @@
   resolved "https://r2.cnpmjs.org/@types/json5/-/json5-0.0.29.tgz"
   integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
 
+"@types/lodash@^4.17.0":
+  version "4.17.0"
+  resolved "https://r.cnpmjs.org/@types/lodash/-/lodash-4.17.0.tgz"
+  integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==
+
 "@types/mime@*", "@types/mime@^1":
   version "1.3.5"
   resolved "https://r.cnpmjs.org/@types/mime/-/mime-1.3.5.tgz"
@@ -2970,17 +2975,17 @@ ansi-styles@^6.1.0:
   integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
 
 antd@^5.14.2:
-  version "5.15.2"
-  resolved "https://registry.npmmirror.com/antd/-/antd-5.15.2.tgz"
-  integrity sha512-EByEiCQknPKJVYfD+zneXwEvjdFzvMw8CZrsxw9nq19ftC4uMcMkZ2irasW7RQQGg9i7XsAZpAwYz3anuFX+EA==
+  version "5.15.3"
+  resolved "https://r.cnpmjs.org/antd/-/antd-5.15.3.tgz"
+  integrity sha512-53dpdGbfwipHVbqITmppp8N16i+BscMzz8NUNwaJgxwSvO9VQh/NfC/90lqGq3I2oBmxQ8TzRIxzFVKD/9OhlQ==
   dependencies:
     "@ant-design/colors" "^7.0.2"
     "@ant-design/cssinjs" "^1.18.4"
-    "@ant-design/icons" "^5.3.1"
+    "@ant-design/icons" "^5.3.3"
     "@ant-design/react-slick" "~1.0.2"
     "@babel/runtime" "^7.24.0"
     "@ctrl/tinycolor" "^3.6.1"
-    "@rc-component/color-picker" "~1.5.2"
+    "@rc-component/color-picker" "~1.5.3"
     "@rc-component/mutate-observer" "^1.1.0"
     "@rc-component/tour" "~1.14.2"
     "@rc-component/trigger" "^2.0.0"
@@ -2996,7 +3001,7 @@ antd@^5.14.2:
     rc-dropdown "~4.2.0"
     rc-field-form "~1.42.1"
     rc-image "~7.6.0"
-    rc-input "~1.4.3"
+    rc-input "~1.4.5"
     rc-input-number "~9.0.0"
     rc-mentions "~2.11.1"
     rc-menu "~9.13.0"
@@ -5424,6 +5429,11 @@ fs.realpath@^1.0.0:
   resolved "https://r2.cnpmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
   integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
 
+fsevents@^2.3.2, fsevents@~2.3.2:
+  version "2.3.3"
+  resolved "https://r.cnpmjs.org/fsevents/-/fsevents-2.3.3.tgz"
+  integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
 function-bind@^1.1.2:
   version "1.1.2"
   resolved "https://r.cnpmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -8603,7 +8613,7 @@ rc-input-number@~9.0.0:
     rc-input "~1.4.0"
     rc-util "^5.28.0"
 
-rc-input@~1.4.0, rc-input@~1.4.3:
+rc-input@~1.4.0, rc-input@~1.4.5:
   version "1.4.5"
   resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.4.5.tgz"
   integrity sha512-AjzykhwnwYTRSwwgCu70CGKBIAv6bP2nqnFptnNTprph/TF1BAs0Qxl91mie/BR6n827WIJB6ZjaRf9iiMwAfw==