Explorar el Código

feat "内容上传"

Leonezhurui hace 2 años
padre
commit
d37e30fbeb
Se han modificado 35 ficheros con 1500 adiciones y 260 borrados
  1. 75 0
      package-lock.json
  2. 1 0
      package.json
  3. 8 0
      src/apis/ai.ts
  4. 11 0
      src/apis/assignment.ts
  5. 0 1
      src/apis/axios.config.ts
  6. 32 0
      src/apis/dictionary.ts
  7. 16 1
      src/apis/engagement.ts
  8. 7 2
      src/apis/index.ts
  9. 18 18
      src/components/AIChatter/AIChatter.tsx
  10. 0 1
      src/components/Button/Button.tsx
  11. 54 0
      src/components/CorrectDashboard/CorrectDashboard.tsx
  12. 7 16
      src/components/ViewsWordEditor/ViewsWordEditor.tsx
  13. 0 3
      src/components/WordEditor/WordEditor.tsx
  14. 1 0
      src/pages/App.tsx
  15. 526 0
      src/pages/CorrectPage/CorrectPage.tsx
  16. 77 0
      src/pages/CorrectPage/components/LeftPanel.tsx
  17. 80 0
      src/pages/CorrectPage/components/RightPanel.tsx
  18. 181 0
      src/pages/CorrectPage/index.scss
  19. 4 6
      src/pages/CourseSquare/component/CourseDetails/index.tsx
  20. 65 0
      src/pages/EditPage/EditPage.tsx
  21. 22 0
      src/pages/EditPage/index.scss
  22. 45 5
      src/pages/Home/component/Dictionaries/index.tsx
  23. 1 0
      src/pages/Home/component/Silder/index.scss
  24. 82 37
      src/pages/Home/component/Silder/index.tsx
  25. 1 1
      src/pages/Home/component/TeachersHomeWork/index.tsx
  26. 1 1
      src/pages/Home/component/TeachersLWorkList/index.tsx
  27. 53 17
      src/pages/Home/component/TeachersWorkDetails/index.tsx
  28. 0 10
      src/pages/LoginPage/LoginPage.tsx
  29. 1 1
      src/pages/LoginPage/component/Login.tsx
  30. 3 137
      src/pages/Test/Test.tsx
  31. 12 0
      src/router/index.tsx
  32. 6 0
      src/types/dialogue.ts
  33. 3 3
      src/types/user.ts
  34. 50 0
      src/types/vo.ts
  35. 57 0
      yarn.lock

+ 75 - 0
package-lock.json

@@ -22,6 +22,7 @@
         "ahooks": "^3.7.10",
         "antd": "^5.16.1",
         "axios": "^1.6.7",
+        "docx-preview": "^0.3.0",
         "lodash": "^4.17.21",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
@@ -7152,6 +7153,14 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/docx-preview": {
+      "version": "0.3.0",
+      "resolved": "https://r.cnpmjs.org/docx-preview/-/docx-preview-0.3.0.tgz",
+      "integrity": "sha512-uLrLhytJkXe420Q0C/cyYuZYcKr+2bh7aXjwR/XclEIuGq9ifDRH1/GVqLBn7+ZX29CKC1bZovtjyfhETjdKKg==",
+      "dependencies": {
+        "jszip": ">=3.0.0"
+      }
+    },
     "node_modules/dom-accessibility-api": {
       "version": "0.5.16",
       "resolved": "https://r.cnpmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@@ -9412,6 +9421,11 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.0.6",
+      "resolved": "https://r2.cnpmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+    },
     "node_modules/immer": {
       "version": "9.0.21",
       "resolved": "https://r.cnpmjs.org/immer/-/immer-9.0.21.tgz",
@@ -12061,6 +12075,49 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/jszip": {
+      "version": "3.10.1",
+      "resolved": "https://r.cnpmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "dependencies": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
+    "node_modules/jszip/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://r2.cnpmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+    },
+    "node_modules/jszip/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://r.cnpmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/jszip/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://r2.cnpmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "node_modules/jszip/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://r2.cnpmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://r.cnpmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -12138,6 +12195,14 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lie": {
+      "version": "3.3.0",
+      "resolved": "https://r2.cnpmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "dependencies": {
+        "immediate": "~3.0.5"
+      }
+    },
     "node_modules/lilconfig": {
       "version": "2.1.0",
       "resolved": "https://r.cnpmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -12926,6 +12991,11 @@
         "node": ">=6"
       }
     },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://r.cnpmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+    },
     "node_modules/param-case": {
       "version": "3.0.4",
       "resolved": "https://r2.cnpmjs.org/param-case/-/param-case-3.0.4.tgz",
@@ -16145,6 +16215,11 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://r2.cnpmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+    },
     "node_modules/setprototypeof": {
       "version": "1.2.0",
       "resolved": "https://r2.cnpmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "ahooks": "^3.7.10",
     "antd": "^5.16.1",
     "axios": "^1.6.7",
+    "docx-preview": "^0.3.0",
     "lodash": "^4.17.21",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",

+ 8 - 0
src/apis/ai.ts

@@ -14,6 +14,14 @@ const aiApis = {
             }
         })
     },
+    getRewriteResult(assignmentId: number, userId: number) {
+        return axiosInstance.get(`${AI_PREFIX}/rewrite/result`, {
+            params: {
+                assignmentId,
+                userId
+            }
+        })
+    },
     requestAI(dialogueId: string, model: string, messages: IDialogue[]) {
         return axiosInstance.put(`${AI_PREFIX}`, {
             model: model,

+ 11 - 0
src/apis/assignment.ts

@@ -0,0 +1,11 @@
+import axiosInstance from "@/apis/axios.config";
+
+const ASSIGNMENT_PREFIX = "/api/assignment"
+
+const assignmentApis = {
+    getAssignmentById(assignmentId: number) {
+        return axiosInstance.get(`${ASSIGNMENT_PREFIX}/byAssignment/${assignmentId}`)
+    }
+}
+
+export default assignmentApis

+ 0 - 1
src/apis/axios.config.ts

@@ -1,5 +1,4 @@
 import axios from "axios";
-import useStore from "@/store";
 
 // const BASEURL = 'http://47.111.23.171:8081'
 const BASEURL = 'http://localhost:8080'

+ 32 - 0
src/apis/dictionary.ts

@@ -0,0 +1,32 @@
+import axiosInstance from "@/apis/axios.config";
+
+const DICT_PREFIX = "/api/translate"
+
+interface dictQueryType {
+    q: string;
+    from: string;
+    to: string;
+}
+
+const dictApis = {
+    requestDictionary(query: dictQueryType, assignmentId: number, studentId: number) {
+        return axiosInstance.post(`${DICT_PREFIX}`, {
+            ...query
+        }, {
+            params: {
+                assignmentId,
+                studentId
+            }
+        })
+    },
+    getTranslations(assignmentId: number, studentId: number) {
+        return axiosInstance.get(`${DICT_PREFIX}`, {
+            params: {
+                assignmentId,
+                studentId
+            }
+        })
+    }
+}
+
+export default dictApis

+ 16 - 1
src/apis/engagement.ts

@@ -11,6 +11,14 @@ const engagementApis = {
             }
         })
     },
+    getDetailEngagement: (studentId: number, assignmentId: number) => {
+        return axiosInstance.get(ENGAGE_PREFIX+"/detail", {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    },
     engagementSubmit: (studentId: number, assignmentId: number) => {
         return axiosInstance.put(ENGAGE_PREFIX+"/submit", {}, {
             params: {
@@ -27,7 +35,14 @@ const engagementApis = {
             }
         })
     },
-
+    engagementCorrect: (studentId: number, assignmentId: number, correctDTO: {score: number, remark: string}) => {
+        return axiosInstance.put(`${ENGAGE_PREFIX}/correct`, correctDTO, {
+            params: {
+                studentId,
+                assignmentId
+            }
+        })
+    }
 }
 
 export default engagementApis

+ 7 - 2
src/apis/index.ts

@@ -1,8 +1,10 @@
 import userApis from "@/apis/user";
 import testApis from "@/apis/test";
 import editorApis from "@/apis/editor";
-import engagementApis from "@/apis/engagement";
+import dictApis from "@/apis/dictionary";
 import aiApis from "@/apis/ai";
+import engagementApis from "@/apis/engagement";
+import assignmentApis from "@/apis/assignment";
 
 
 const apis = {
@@ -10,9 +12,12 @@ const apis = {
     ...testApis,
     ...editorApis,
     ...engagementApis,
-    ...aiApis
+    ...aiApis,
+    ...dictApis,
+    ...assignmentApis
 }
 
+// hook
 const useApis = () => {
     return apis
 }

+ 18 - 18
src/components/AIChatter/AIChatter.tsx

@@ -19,11 +19,11 @@ const AIChatter:React.FC<IProps> = ({dialogueId, messages}) => {
 
     const apis = useApis()
 
-    const modelOptions = ["ChatGLM3", "QWen14B"]
+    const modelOptions = ["ChatGPT", "ChatGLM3", "QWen14B"]
     const questionTemplates = [
-        "请问您提供哪些服务?",
-        "您的营业时间是?",
-        "我想了解更多关于产品A的信息。",
+        "xxx是什么意思?",
+        "英文如何描述xxx",
+        "请用英文描述一个场景或一个观点:xxx",
         // 更多问题模板...
     ];
 
@@ -97,20 +97,20 @@ const AIChatter:React.FC<IProps> = ({dialogueId, messages}) => {
         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 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}))

+ 0 - 1
src/components/Button/Button.tsx

@@ -1,5 +1,4 @@
 import {Button as AntdButton, ButtonProps, ConfigProvider} from 'antd';
-import { BaseButtonProps } from 'antd/es/button/button';
 import React from 'react';
 
 export function Button({ children, ...youth }: ButtonProps) {

+ 54 - 0
src/components/CorrectDashboard/CorrectDashboard.tsx

@@ -0,0 +1,54 @@
+import React, {useEffect, useRef} from 'react';
+import { Tag } from 'antd';
+import {engagementDetailVO} from "@/types/vo";
+import {useNavigate} from "react-router";
+
+interface DashboardProps {
+    engagementDetails: engagementDetailVO[];
+    onClose: () => void; // 关闭仪表盘的函数
+}
+
+const CorrectDashboard: React.FC<DashboardProps> = ({ engagementDetails, onClose }) => {
+    const nav = useNavigate()
+
+    const dashboardRef = useRef<HTMLDivElement>(null);
+
+    useEffect(() => {
+        function handleClickOutside(event: MouseEvent) {
+            if (dashboardRef.current && !dashboardRef.current.contains(event.target as Node)) {
+                onClose();
+            }
+        }
+
+        document.addEventListener('mousedown', handleClickOutside);
+        return () => {
+            document.removeEventListener('mousedown', handleClickOutside);
+        };
+    }, [onClose]);
+
+
+    const jumpToEngagement = (assignmentId: number, userId: number) => {
+        // 跳转到其他页面
+        nav(`/assignment/${assignmentId}/student/${userId}/correct`)
+    }
+
+    return (
+        <div ref={dashboardRef}
+            style={{ position: 'fixed', top: '10px', right: '10px', backgroundColor: 'white', padding: '20px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', zIndex: 1000, maxWidth: '300px'}}>
+            <h2 style={{marginBottom: "20px"}}>作业仪表盘</h2>
+            <div>
+                {engagementDetails.map((engagement) => (
+                    <Tag color={engagement.status === 'CORRECTED' ? 'green' : 'volcano'} key={engagement.userVO.id}
+                         onClick={() => jumpToEngagement(engagement.assignmentVO.assignmentId, engagement.userVO.id)}
+                         style={{cursor: "pointer"}}
+                    >
+                        {engagement.userVO.name}
+                    </Tag>
+                ))}
+            </div>
+            {/*<button onClick={onClose} style={{ marginTop: '20px' }}>关闭</button>*/}
+        </div>
+    );
+};
+
+export default CorrectDashboard

+ 7 - 16
src/components/ViewsWordEditor/ViewsWordEditor.tsx

@@ -1,4 +1,3 @@
-import WordEditor from '@/components/WordEditor/WordEditor';
 import { Modal } from 'antd';
 import { ModalProps } from 'antd/es/modal/interface';
 import "./index.scss"
@@ -6,18 +5,13 @@ import "./index.scss"
 
 interface ViewsWordEditorInterface {
   alert: ModalProps;
-  word: {
-    onValueChange?: (state: boolean) => void;
-    documentKey: string;
-    documentTitle: string;
-    documentUrl: string;
-    userId: number;
-    username: string;
-    mode?: string;
-  };
+  docxInfo: {
+    docxUrl: string;
+    docxTitle: string;
+  }
 }
 
-export function ViewsWordEditor({ alert, word }: ViewsWordEditorInterface) {
+export function ViewsWordEditor({ alert, docxInfo }: ViewsWordEditorInterface) {
   return <Modal
     centered
     width="60%"
@@ -25,12 +19,9 @@ export function ViewsWordEditor({ alert, word }: ViewsWordEditorInterface) {
     cancelText="取消"
     {...alert}
   >
-    <WordEditor
-      mode="view"
-      {...word}
-    />
+    <iframe src={"https://view.officeapps.live.com/op/view.aspx?src="+docxInfo.docxUrl} title={docxInfo.docxTitle} style={{width: "100%", height: '100%'}}/>
   </Modal>
-  
+
 }
 
 

+ 0 - 3
src/components/WordEditor/WordEditor.tsx

@@ -8,7 +8,6 @@ interface IProps {
   documentUrl: string;
   userId: number;
   username: string;
-  mode?: string;
 }
 
 const WordEditor: React.FC<IProps> = React.memo((
@@ -19,7 +18,6 @@ const WordEditor: React.FC<IProps> = React.memo((
     documentUrl,
     userId,
     username,
-    mode = 'edit',
   },
 ) => {
 
@@ -77,7 +75,6 @@ const WordEditor: React.FC<IProps> = React.memo((
         id  : userId + '', // 4
         name: username, // 5
       },
-      mode,
     },
   }), [documentKey, documentTitle, documentUrl, userId, username]);
 

+ 1 - 0
src/pages/App.tsx

@@ -3,6 +3,7 @@ import React from 'react';
 import Content from './Home/Home';
 
 function App() {
+
   return (
     <Layout>
       {

+ 526 - 0
src/pages/CorrectPage/CorrectPage.tsx

@@ -0,0 +1,526 @@
+import Arrow from "@/image/arrow.png";
+import {useNavigate, useParams} from "react-router";
+import React, {useEffect, useState} from "react";
+import useApis from "@/apis";
+import {engagementDetailVO, ITranslationVO} from "@/types/vo";
+import {Avatar, Input, InputNumber, message, Spin, Typography} from "antd";
+import {Button} from "@/components/Button/Button";
+import "./index.scss"
+import useStore from "@/store";
+import {IDialogue, IRewriteResult} from "@/types/dialogue";
+import CorrectDashboard from "@/components/CorrectDashboard/CorrectDashboard";
+
+// 老师可以点击进入该作业的批改页面,则意味着该作业已经完成提交(作业状态已变为“已提交”)
+
+const CorrectPage = () => {
+
+    const nav = useNavigate()
+
+    const apis = useApis()
+
+    const store = useStore()
+
+    const {studentId, assignmentId} = useParams()
+
+    const [data, setData] = useState<{
+        engagementDetail: engagementDetailVO,
+        messages: IDialogue[],
+        rewriteResult: IRewriteResult[],
+        translations: ITranslationVO[]
+    }>({
+        engagementDetail: {
+            userVO: {},
+            assignmentVO: {}
+        } as engagementDetailVO,
+        messages: [],
+        rewriteResult: [],
+        translations: []
+    })
+
+    const [isDashboardVisible, setDashboardVisible] = useState(false)
+
+
+    const engagementDetails: engagementDetailVO[] = [
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 2,
+                name: "张洪胤",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 5,
+            remark: null,
+            score: null,
+            status: "SUBMITTED",
+            version: 4
+        },
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 3,
+                name: "苏韬叡",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 6,
+            remark: null,
+            score: null,
+            status: "CORRECTED",
+            version: 4
+        },
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 4,
+                name: "王万峰",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 7,
+            remark: null,
+            score: null,
+            status: "SUBMITTED",
+            version: 4
+        },
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 5,
+                name: "朱睿",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 8,
+            remark: null,
+            score: null,
+            status: "SUBMITTED",
+            version: 4
+        },
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 6,
+                name: "夏泽霖",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 9,
+            remark: null,
+            score: null,
+            status: "SUBMITTED",
+            version: 4
+        },
+        {
+            assignmentVO: {
+                assignmentId: 1,
+                assignmentName: "英文读写第一次作业:乡土中国调研(聚焦中国江苏)",
+                attachments: null,
+                courseId: 7,
+                createTime: "2024-03-16T03:51:29.000+00:00",
+                description: "乡土中国,致敬每一寸土地;养育我们的祖国,抚养我们的土地。苏杭之名自古有,江苏的土地养育着一方儿女,聚焦江苏土地和农业发展。",
+                descriptionFile: "http://eai-files.oss-cn-shanghai.aliyuncs.com/uploads/First%20English%20Writing%20Requirement.docx",
+                endTime: "2024-03-31T16:00:00.000+00:00",
+                startTime: "2024-03-16T16:00:00.000+00:00",
+                status: "PROCEEDING",
+                teacherId: 5
+            },
+            userVO: {
+                birthday: null,
+                contentEmail: null,
+                createTime: "2024-02-20T07:49:35.000+00:00",
+                englishName: "story2",
+                gender: null,
+                id: 7,
+                name: "苏韬叡一号",
+                officialEmail: "181250126@smail.nju.edu.cn",
+                officialNumber: "181250126",
+                phone: null,
+                role: "STUDENT",
+                userAvatar: "https://pixabay.com/zh/photos/cat-feline-whiskers-pet-chair-8436843/"
+            },
+            fileKey: "1_2_4",
+            fileUrl: "http://eai-files.oss-cn-shanghai.aliyuncs.com/1_2.docx",
+            id: 10,
+            remark: null,
+            score: null,
+            status: "SUBMITTED",
+            version: 4
+        }
+    ];
+
+    const [select, setSelect] = useState(0);
+
+    const [score, setScore] = useState(0)
+    const [remark, setRemark] = useState("")
+
+    const [loading, setLoading] = useState(false)
+
+    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,
+        });
+    };
+
+    const handleGoBack = () => {
+        nav(-1)
+    }
+
+    const fetchData = async () => {
+        await apis.getDetailEngagement(Number(studentId), Number(assignmentId))
+            .then(_res => {
+                console.log("getDetailEngagement", _res.data.data)
+                setData(prev => ({...prev, engagementDetail: _res.data.data}))
+            })
+
+        await apis.getAIDialogues(Number(assignmentId), Number(studentId))
+            .then(_res => {
+                console.log("getAIDialogues", _res.data.data)
+                setData(prev => ({...prev, messages: _res.data.data.messages.content}))
+            })
+
+        await apis.getRewriteResult(Number(assignmentId), Number(studentId))
+            .then(_res => {
+                console.log("getRewriteResult", _res.data.data)
+                setData(prev => ({...prev, rewriteResult: _res.data.data}))
+            })
+
+        await apis.getTranslations(Number(assignmentId), Number(studentId))
+            .then(_res => {
+                console.log("getTranslations", _res.data.data)
+                setData(prev => ({...prev, translations: _res.data.data}))
+            })
+    }
+
+    useEffect(() => {
+        fetchData()
+    }, [studentId, assignmentId])
+
+
+    const onChange = (value: number|null) => {
+        console.log('changed', value);
+        setScore(value!)
+    }
+
+    const handleFinishCorrect = () => {
+        setLoading(true)
+        apis.engagementCorrect(Number(studentId), Number(assignmentId), {score: score, remark: remark})
+            .then(_res => {
+                console.log("engagementCorrect", _res.data.data)
+                success("批改作业完成!2秒钟后返回主页")
+                setLoading(false)
+                setTimeout(()=> {
+                    nav("/")
+                }, 2000)
+            }).catch(_err => {
+            console.error(_err)
+            error("批改作业失败!")
+        })
+    }
+
+
+    return (
+        <div className="word-details">
+            <div className="word-return-icon" onClick={handleGoBack}>
+                <img src={Arrow} alt=""/>返回
+            </div>
+            <div className="word-container">
+                <div className="word-left">
+                    <div className="edit-container-box">
+                        <div>
+                            {data.engagementDetail.assignmentVO.assignmentName}
+                        </div>
+                        <div className="edit-text">
+                            <Typography.Paragraph
+                                ellipsis={{
+                                    rows      : 2,
+                                    expandable: true,
+                                    symbol    : '展开',
+                                }}
+                            >
+                                {data.engagementDetail.assignmentVO.description}
+                            </Typography.Paragraph>
+                        </div>
+
+                        <div className="edit-details">
+                            <div className="edit-details-item">学生姓名:{data.engagementDetail.userVO.name}</div>
+                            <div className="edit-details-item">学生学号:{data.engagementDetail.userVO.officialNumber}</div>
+                            <div className="edit-details-item">
+                                <>提交时间:{new Date().toDateString()}</>
+                            </div>
+                        </div>
+
+                    </div>
+                    <div className="edit-composition">
+                        <div>作文内容</div>
+                        <div className="edit-box-show">
+                            {
+                                data.engagementDetail &&
+                                <iframe src={"https://view.officeapps.live.com/op/view.aspx?src="+data.engagementDetail.fileUrl} title={data.engagementDetail.assignmentVO.assignmentName} style={{width: "100%", height: '100%'}}/>
+                            }
+                        </div>
+                    </div>
+                </div>
+
+                <div className="word-right">
+                    <div className="word-right-container">
+                        <div className="record">
+                            <div>写作过程记录</div>
+                            <div className="record-select">
+                                <div className="sidebar">
+                                    <div className={`sidebar-item ${select === 0 ? 'active' : ''}`} onClick={() => setSelect(0)}>AI对话</div>
+                                    <div className={`sidebar-item ${select === 1 ? 'active' : ''}`} onClick={() => setSelect(1)}>字典搜索</div>
+                                    <div className={`sidebar-item ${select === 2 ? 'active' : ''}`} onClick={() => setSelect(2)}>智能评价</div>
+                                </div>
+                                <div className="select-content">
+                                    {
+                                        select === 0 &&
+                                        <div className={"message-box"}>
+                                            {
+                                                data.messages.map((item, index) => {
+                                                    return (
+                                                        <div className={"item"} key={index}>
+                                                            <div>
+                                                                {/* 分类展示用户和AI的消息 */}
+                                                                {
+                                                                    index % 2 === 0 ?
+                                                                        <Avatar
+                                                                            size={32}
+                                                                            shape={"circle"}
+                                                                            src={data.engagementDetail.userVO.userAvatar || ""}>{data.engagementDetail.userVO.name.charAt(0)}</Avatar> :
+                                                                        <Avatar
+                                                                            size={32}
+                                                                            shape={"circle"}
+                                                                        >{"AI"}</Avatar>
+                                                                }
+                                                            </div>
+                                                            <div style={{marginLeft: "15px"}}>
+                                                                <div style={{marginBottom: "2px"}}>{index % 2 === 0? "You": "AI"}</div>
+                                                                <div className={"content"}>{item.content}</div>
+                                                            </div>
+                                                        </div>
+                                                    )
+                                                })
+                                            }
+                                        </div>
+                                    }
+                                    {
+                                        select === 1 &&
+                                        <div className={"message-box"}>
+                                            {
+                                                data.translations.map((item, index) => {
+                                                    return (
+                                                        <>
+                                                            <div className={"item"} key={index+"1"}>
+                                                                <div>
+                                                                    <Avatar
+                                                                        size={32}
+                                                                        shape={"circle"}
+                                                                        src={data.engagementDetail.userVO.userAvatar || ""}>{data.engagementDetail.userVO.name.charAt(0)}</Avatar>
+                                                                </div>
+                                                                <div style={{marginLeft: "15px"}}>
+                                                                    <div style={{marginBottom: "2px"}}>{"You"}</div>
+                                                                    <div className={"content"}>{item.queryText}</div>
+                                                                </div>
+                                                            </div>
+                                                            <div className={"item"} key={index+"2"}>
+                                                                <div>
+                                                                    <Avatar
+                                                                        size={32}
+                                                                        shape={"circle"}
+                                                                    >{"AI"}</Avatar>
+                                                                </div>
+                                                                <div style={{marginLeft: "15px"}}>
+                                                                    <div style={{marginBottom: "2px"}}>{"AI"}</div>
+                                                                    <div className={"content"}>{item.queryResult}</div>
+                                                                </div>
+                                                            </div>
+                                                        </>
+                                                    )
+                                                })
+                                            }
+                                        </div>
+                                    }
+                                    {
+                                        select === 2 &&
+                                        <div className={'remark'}>{
+                                            data.rewriteResult[data.rewriteResult.length - 1].role === 'assistant' &&
+                                            data.rewriteResult[data.rewriteResult.length - 1].content
+                                        }</div>
+                                    }
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div className="word-right-bottom">
+                        <div className="scoring">
+                            <span style={{marginRight: "10px"}}>最终得分: </span><InputNumber min={1} max={100} defaultValue={80} onChange={onChange} />
+                        </div>
+
+
+                        <div className="edit-box">
+                            <div>修改意见与评价</div>
+                            <div className="edit-root">
+                                <Input.TextArea
+                                    bordered={false}
+                                    value={remark}
+                                    onChange={(e) => setRemark(e.target.value)}
+                                    placeholder=" 在这里输入修改意见与评价..."
+                                    style={{ height: '100%', minHeight: '300px', width: '99%', resize: 'vertical',outline: 'none',border:'none',fontSize:'16px',borderRadius: '10px' }}
+                                />
+                            </div>
+                            <div className="button-right">
+                                <Button onClick={handleFinishCorrect}>
+                                    {
+                                        !loading ? <>完成批改</>: <Spin></Spin>
+                                    }
+                                </Button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <Button
+                type="primary"
+                shape="circle"
+                icon={<span>仪</span>} // 这里简单用文字代替图标,你可以替换为具体的图标
+                size="large"
+                style={{ position: 'fixed', right: '15px', top: '20px', zIndex: 1000, backgroundColor: "#597d3b"}}
+                onClick={() => setDashboardVisible(!isDashboardVisible)}
+            />
+            {isDashboardVisible && <CorrectDashboard engagementDetails={engagementDetails} onClose={() => setDashboardVisible(false)} />}
+            {contextHolder}
+        </div>
+    )
+}
+
+export default CorrectPage

+ 77 - 0
src/pages/CorrectPage/components/LeftPanel.tsx

@@ -0,0 +1,77 @@
+
+import {Typography} from "antd";
+import {Button} from "@/components/Button/Button";
+import {engagementDetailVO} from "@/types/vo";
+import {useEffect, useState} from "react";
+
+interface IProps{
+    engagementDetail: engagementDetailVO
+}
+
+const LeftPanel:React.FC<IProps> = ({engagementDetail}) => {
+
+    const article =
+        '这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字';
+
+    // const [data, setData] = useState<{
+    //     engagementDetail: engagementDetailVO
+    // }>({
+    //     engagementDetail: {} as engagementDetailVO
+    // })
+    //
+    // useEffect(() => {
+    //     setData(prev => ({
+    //         ...prev,
+    //         engagementDetail: engagementDetail
+    //     }))
+    // }, [engagementDetail])
+
+
+    return (
+        <div className="word-left">
+            <div className="edit-container-box">
+                <div>
+                    写作题目:
+                </div>
+                <div className="edit-text">
+                    <Typography.Paragraph
+                        ellipsis={{
+                            rows      : 2,
+                            expandable: true,
+                            symbol    : '展开',
+                        }}
+                    >
+                        {article}
+                    </Typography.Paragraph>
+                    <div className="down">
+                        <Button type="link">下载相关文件</Button>
+                    </div>
+                </div>
+                <div className="edit-details">
+                        {
+                            engagementDetail &&
+                            <>
+                                <div className="edit-details-item">学生姓名:{engagementDetail.userVO.name}</div>
+                                <div className="edit-details-item">学生学号:{engagementDetail.userVO.officialNumber}</div>
+                                <div className="edit-details-item">
+                                    <>提交时间:{new Date()}</>
+                                </div>
+                            </>
+                        }
+
+                </div>
+            </div>
+            <div className="edit-composition">
+                <div>作文内容</div>
+                <div className="edit-box">
+                    {
+                        engagementDetail &&
+                        <iframe src={"https://view.officeapps.live.com/op/view.aspx?src="+engagementDetail.fileUrl} title={engagementDetail.assignmentVO.assignmentName} style={{width: "100%", height: '100%'}}/>
+                    }
+                </div>
+            </div>
+        </div>
+    )
+}
+
+export default LeftPanel;

+ 80 - 0
src/pages/CorrectPage/components/RightPanel.tsx

@@ -0,0 +1,80 @@
+import {useState} from "react";
+import {IDomEditor, IEditorConfig, IToolbarConfig} from "@wangeditor/editor";
+import {Editor, Toolbar} from "@wangeditor/editor-for-react";
+import {Button} from "@/components/Button/Button";
+
+const RightPanel = () => {
+    const [editor, setEditor] = useState<IDomEditor | null>(null);
+    const [html, setHtml] = useState('');
+    const [select, setSelect] = useState(0);
+
+    const toolbarConfig: Partial<IToolbarConfig> = {};
+
+    const editorConfig: Partial<IEditorConfig> = {
+        placeholder: '请输入内容...',
+    };
+
+    return (
+        <div className="word-right">
+            <div className="word-right-container">
+                <div className="scoring">
+                    Ai评分:
+                    <div className="circle">78</div>
+                </div>
+                <div className="record">
+                    <div>写作过程记录</div>
+                    <div className="record-select">
+                        <div className="sidebar">
+                            <div className="sidebar-item" onClick={() => setSelect(0)}>AI对话</div>
+                            <div className="sidebar-item" onClick={() => setSelect(1)}>字典搜索</div>
+                            <div className="sidebar-item" onClick={() => setSelect(2)}>AI评价</div>
+                        </div>
+                        <div className="select-content">
+                            {
+
+                                select === 0 && <div>Ai对话</div>
+                            }
+                            {
+                                select === 1 && <div>字典搜索</div>
+                            }
+                            {
+                                select === 2 && <div>AI评价</div>
+                            }
+                        </div>
+                    </div>
+                </div>
+                <div className="scoring">
+                    最终得分: <span>999999</span>
+                </div>
+            </div>
+            <div className="word-right-bottom">
+                <div className="edit-box">
+                    <div>修改意见与评价</div>
+                    <div className="edit-root">
+                        <Toolbar
+                            editor={editor}
+                            defaultConfig={toolbarConfig}
+                            mode="default"
+                            style={{ borderBottom: '1px solid #ccc' }}
+                        />
+                        <div className="edit-toolbar-box">
+                            <Editor
+                                value={html}
+                                defaultConfig={editorConfig}
+                                onCreated={setEditor}
+                                onChange={(editor) => setHtml(editor.getHtml())}
+                                mode="default"
+                                style={{ height: '300px', overflowY: 'hidden' }}
+                            />
+                        </div>
+                    </div>
+                    <div className="button-right">
+                        <Button>智能批改</Button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    )
+}
+
+export default RightPanel

+ 181 - 0
src/pages/CorrectPage/index.scss

@@ -0,0 +1,181 @@
+$border: solid 1px #d5d5d5;
+
+.word-details {
+  width: 95%;
+  margin: 0 auto;
+  height: 100%;
+  padding: 10px 0;
+  box-sizing: border-box;
+  overflow: hidden;
+
+  .word-return-icon {
+    display: inline-flex;
+    align-items: center;
+    gap: 10px;
+    cursor: pointer;
+
+    img {
+      width: 20px;
+    }
+  }
+
+  .word-container {
+    padding: 20px;
+    box-sizing: border-box;
+    height: 100%;
+    display: flex;
+    width: 100%;
+
+
+    .word-left {
+      width: 50%;
+      padding-right: 20px;
+      box-sizing: border-box;
+      display: flex;
+      flex-direction: column;
+
+      .edit-text {
+        margin-left: 0;
+      }
+
+      .edit-details {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+      }
+
+      .edit-composition {
+        margin-top: 20px;
+        height: 100%;
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+
+        .edit-box-show {
+          border: $border;
+          border-radius: 10px;
+          margin-top: 20px;
+          height: 100%;
+        }
+      }
+    }
+
+    .word-right {
+      width: 50%;
+      padding-left: 20px;
+      box-sizing: border-box;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      border-left: $border;
+      overflow-y: auto;
+      overflow-x: hidden;
+
+      .word-right-container {
+        display: flex;
+        flex-direction: column;
+        gap: 5px;
+        margin-bottom: 20px;
+      }
+
+      .word-right-bottom {
+        margin-top: 10px;
+
+        .edit-root{
+          border-radius: 10px;
+          padding: 5px;
+        }
+      }
+
+      .scoring {
+        display: flex;
+        align-items: center;
+        margin-bottom: 30px;
+
+        .circle {
+          width: 35px;
+          height: 35px;
+          border-radius: 35px;
+          background: #96B87C;
+          text-align: center;
+          line-height: 35px;
+          margin-left: 30px;
+        }
+
+        span {
+          text-decoration: underline
+        }
+      }
+
+      .record {
+        .record-select {
+          display: flex;
+          width: 99%;
+          height: 300px;
+          margin-top: 20px;
+          border: $border;
+          border-radius: 8px;
+
+          .sidebar {
+            width: 100px;
+            min-width: 100px;
+            border-right: $border;
+
+            .active {
+              background-color: #597d3b;
+              color: white;
+            }
+
+            .sidebar-item {
+              text-align: center;
+              padding: 5px 0;
+              border-bottom: $border;
+              cursor: pointer;
+            }
+          }
+
+          // TODO select-content右侧的黑线不见了(应该是被覆盖了)
+          .select-content {
+            padding: 10px 5px;
+            width: 100%; // 注意,要message-box宽度占满父组件的宽度,这里必须设置宽度
+
+            .message-box {
+              box-sizing: border-box;
+              width: 100%;
+              height: 100%;
+              overflow: auto;
+
+              .item {
+                width: 100%;
+                display: flex;
+                align-items: flex-start;
+                margin-bottom: 15px;
+
+                .content {
+                  background-color: #f0f0f0;
+                  padding: 8px 12px;
+                  border-radius: 16px;
+                  word-break: break-word;
+                  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+                }
+              }
+            }
+          }
+
+          .remark {
+            width: 100%;
+            height: 100%;
+            border-radius: 10px;
+            overflow-y: auto; /* 添加垂直滚动 */
+            white-space: pre-wrap; /* 保留换行和空格,允许自动换行 */
+            background-color: #f0f0f0;
+          }
+
+
+
+        }
+
+      }
+    }
+  }
+}

+ 4 - 6
src/pages/CourseSquare/component/CourseDetails/index.tsx

@@ -103,13 +103,11 @@ export function CourseDetails() {
         onOk    : () => setOpen(false),
         onCancel: () => setOpen(false),
       }}
-      word={{
-        documentKey  : 'Khirz6zTPdfd7',
-        documentTitle: 'Example Document Title.docx',
-        documentUrl  : 'https://example.com/url-to-example-document.docx',
-        userId       : 1,
-        username     : '111',
+      docxInfo={{
+        docxUrl: "https://example.com/url-to-example-document.docx",
+        docxTitle: "test.docx"
       }}
+
     />
 
     <div className="course-details">

+ 65 - 0
src/pages/EditPage/EditPage.tsx

@@ -0,0 +1,65 @@
+import { Dictionaries } from "@/pages/Home/component/Dictionaries";
+import { Edit } from "@/pages/Home/component/Edit";
+import { Slider } from "@/pages/Home/component/Silder";
+import AIChatter from "@/components/AIChatter/AIChatter";
+import "./index.scss";
+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 EditPage() {
+    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">
+                <AIChatter dialogueId={data.dialogueId} messages={data.messages}/>
+                <Dictionaries />
+            </div>
+            <div className="my-work-input">
+                <Edit engagement={data.engagement}/>
+            </div>
+            <Slider dialogueId={data.dialogueId}/>
+        </div>
+    );
+}

+ 22 - 0
src/pages/EditPage/index.scss

@@ -0,0 +1,22 @@
+.my-work {
+  padding: 20px 0 20px 12px;
+  box-sizing: border-box;
+  height: 100%;
+  display: flex;
+  gap: 20px;
+
+  .my-work-slider {
+    width: 25%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    flex-shrink: 0;
+  }
+
+  .my-work-input {
+    width: 100%;
+    flex: 1;
+    max-width: 100%;
+  }
+}

+ 45 - 5
src/pages/Home/component/Dictionaries/index.tsx

@@ -1,12 +1,39 @@
 import { LanguageIcon } from '@/components/languageIcon/languageIcon';
-import { SearchOutlined } from '@ant-design/icons';
-import { Input } from 'antd';
-import { useState } from 'react';
+import {ArrowUpOutlined, SearchOutlined} from '@ant-design/icons';
+import {Button, Input, Spin} from 'antd';
+import React, { useState } from 'react';
 import './index.scss';
+import useApis from "@/apis";
+import useStore from "@/store";
+import styles from "@/components/AIChatter/AIChatter.module.scss";
+
+
 
 export function Dictionaries() {
+  const apis = useApis()
+  const store = useStore()
+
   const [currentLanguage, setCurrentLanguage] = useState('zh');
 
+  const [content, setContent] = useState("")
+
+  const [res, setRes] = useState("")
+
+  const [loading, setLoading] = useState(false)
+
+  const search = () => {
+    setLoading(true)
+    apis.requestDictionary({
+      q: content,
+      from: 'auto',
+      to: currentLanguage === 'zh'? 'zh-cn': 'en'
+    }, 1, store.user.id)
+        .then(_res => {
+          console.log("requestDictionary", _res)
+          setRes(_res.data.data)
+          setLoading(false)
+        })
+  }
 
   return <div className="dictionaries">
     <div className="dic-box-title">
@@ -15,11 +42,24 @@ export function Dictionaries() {
     <div className="box-dialog">
       <Input
         placeholder="请输入搜索内容"
-        suffix={<SearchOutlined/>}
+        suffix={
+          <>
+            {loading && <Spin />}
+            {!loading &&<SearchOutlined onClick={search}/>}
+          </>
+        }
         size="middle"
         // @ts-ignore
-        prefix={<LanguageIcon initValue={currentLanguage} onChange={setCurrentLanguage}/>}
+        prefix={<LanguageIcon initValue={currentLanguage} onChange={setCurrentLanguage} style={{marginRight: '5px'}}/>}
+        value={content}
+        onChange={e=> setContent(e.target.value)}
       />
+
+      {/*TODO 页面修改:翻译内容的展示*/}
+      <div>
+        {res}
+      </div>
     </div>
+
   </div>;
 }

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

@@ -72,6 +72,7 @@
     overflow-y: auto; /* 添加垂直滚动 */
     white-space: pre-wrap; /* 保留换行和空格,允许自动换行 */
     background-color: #f0f0f0;
+    padding: 10px;
   }
 }
 

+ 82 - 37
src/pages/Home/component/Silder/index.tsx

@@ -1,10 +1,12 @@
+import { MenuUnfoldOutlined } from "@ant-design/icons";
+import {Spin, Typography} from "antd";
+import { Button } from "@/components/Button/Button";
+import {useEffect, useState} from "react";
+import "./index.scss";
+import {IAssignment} from "@/types/vo";
 import useApis from '@/apis';
-import { Button } from '@/components/Button/Button';
 import { ViewsWordEditor } from '@/components/ViewsWordEditor/ViewsWordEditor';
 import useStore from '@/store';
-import { MenuUnfoldOutlined } from '@ant-design/icons';
-import { Spin, Typography } from 'antd';
-import { useState } from 'react';
 import './index.scss';
 
 
@@ -12,15 +14,20 @@ interface ISliderProps {
   dialogueId: string;
 }
 
-export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
-  const apis = useApis();
-  const store = useStore();
+interface IPreviewProps {
+    url: string;
+    title: string;
+}
+
+export const Slider:React.FC<ISliderProps> = ({dialogueId}) => {
+    const apis = useApis()
+    const store = useStore()
 
   const [isShrink, setShrink] = useState(false);
 
   const [aiRemark, setAiRemark] = useState('');
 
-  const [loading, setLoading] = useState(true);
+  const [loading, setLoading] = useState(false);
 
   const [open, setOpen] = useState(false);
 
@@ -28,8 +35,26 @@ export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
 
   const article =
     '这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字';
+    const [data, setData] = useState<{
+        assignment: IAssignment,
+        currentPreview: IPreviewProps
+    }>({
+        assignment: {} as IAssignment,
+        currentPreview: {} as IPreviewProps
+    })
+
+
+  const rootClassName = `slider-content ${isShrink ? "shrink" : ""}`;
+
 
-  const rootClassName = `slider-content ${isShrink ? 'shrink' : ''}`;
+  // 请求重写文章内容
+  useEffect(() => {
+      apis.getAssignmentById(1)
+          .then((_res: any) => {
+              console.log(_res)
+              setData(prev => ({...prev, assignment:_res.data.data}))
+          })
+  }, [apis])
 
 
   // 请求重写文章内容
@@ -46,9 +71,13 @@ export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
     });
   };
 
-  function onClickOpenEditor(content: string) {
+  function onClickOpenEditor(previewProps: IPreviewProps) {
     return () => {
       setOpen(true);
+      setData(prev => ({
+          ...prev,
+          currentPreview: previewProps
+      }))
     };
   }
 
@@ -61,15 +90,15 @@ export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
       <div className="slider-item-box">
         {isShrink && (
           <>
-            <div className="slider-item">写作</div>
-            <div className="slider-item">范文</div>
-            <div className="slider-item">文献</div>
-            <div className="slider-item">批改</div>
+            <div className="slider-item" onClick={() => setShrink(!isShrink)}>描述</div>
+            {data.assignment.descriptionFile && <div className="slider-item" onClick={() => setShrink(!isShrink)}>要求</div>}
+            {data.assignment.attachments && <div className="slider-item" onClick={() => setShrink(!isShrink)}>附件</div>}
+            <div className="slider-item" onClick={() => setShrink(!isShrink)}>批改</div>
           </>
         )}
         {!isShrink && (
           <>
-            <div>写作题目</div>
+            <div>{data.assignment.assignmentName}</div>
             <div className="edit-text">
               <Typography.Paragraph
                 ellipsis={{
@@ -83,18 +112,34 @@ export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
                   },
                 }}
               >
-                {article}
+                {data.assignment.description}
               </Typography.Paragraph>
               <div className="down">
                 <Button type="link">下载</Button>
               </div>
             </div>
-            <div>范文、介绍</div>
-            <div className="slider-tag">
-              <div className="slider-tag-item" onClick={onClickOpenEditor('introduction')}>introduction</div>
-              <div className="slider-tag-item" onClick={onClickOpenEditor('methodology')}>methodology</div>
-              <div className="slider-tag-item" onClick={onClickOpenEditor('范文样例')}>范文样例</div>
-            </div>
+              {
+                  (data.assignment.descriptionFile || data.assignment.attachments) &&
+
+                  <>
+                      <div>范文、介绍</div>
+                      <div className="slider-tag">
+                          <>
+                              {
+                                  data.assignment.descriptionFile &&
+                                  <div className="slider-tag-item" onClick={onClickOpenEditor({url: data.assignment.descriptionFile, title:data.assignment.assignmentName})}>作业要求</div>
+                              }
+                              {
+                                  data.assignment.attachments &&
+                                  data.assignment.attachments.map((item, index) => {
+                                      return <div className="slider-tag-item" onClick={onClickOpenEditor({url: item, title: '附件'+index})}>附件{index}</div>
+                                  })
+                              }
+                          </>
+                      </div>
+                  </>
+              }
+
             <div className="edit-box-header">
               智能批改修改意见
               <Button onClick={handleRewrite}>智能批改</Button>
@@ -118,20 +163,20 @@ export const Slider: React.FC<ISliderProps> = ({ dialogueId }) => {
         )}
       </div>
 
-      <ViewsWordEditor
-        alert={{
-          open    : open,
-          onOk    : () => setOpen(false),
-          onCancel: () => setOpen(false),
-        }}
-        word={{
-          documentKey  : 'Khirz6zTPdfd7',
-          documentTitle: 'Example Document Title.docx',
-          documentUrl  : 'https://example.com/url-to-example-document.docx',
-          userId       : 1,
-          username     : '111',
-        }}
-      />
+        {
+            data.assignment.descriptionFile &&
+            <ViewsWordEditor
+                alert={{
+                    open    : open,
+                    onOk    : () => setOpen(false),
+                    onCancel: () => setOpen(false),
+                }}
+                docxInfo={{
+                    docxUrl:  data.currentPreview.url,
+                    docxTitle: data.currentPreview.title
+            }}
+            />
+        }
     </div>
   );
-};
+}

+ 1 - 1
src/pages/Home/component/TeachersHomeWork/index.tsx

@@ -15,7 +15,7 @@ export default function TeachersHomeWork() {
       {!current ? (
         <TeachersLWorkList onChange={onClick}/>
       ) : (
-        <TeachersWorkDetails onConfirm={onClick} current={current}/>
+        <TeachersWorkDetails onConfirm={onClick} current={current} engagementInfo={{studentId: 2, assignmentId:1}}/>
       )}
     </div>
   );

+ 1 - 1
src/pages/Home/component/TeachersLWorkList/index.tsx

@@ -1,6 +1,6 @@
 import { Button, Select, Space, Table } from "antd";
 import "./index.scss";
-import { useCallback, useMemo } from "react";
+import { useMemo } from "react";
 
 interface TeachersLWorkListProps {
   onChange?: (value: any) => void;

+ 53 - 17
src/pages/Home/component/TeachersWorkDetails/index.tsx

@@ -1,14 +1,24 @@
 import { Button } from '@/components/Button/Button';
-import WordEditor from '@/components/WordEditor/WordEditor';
+import { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
+import { Editor, Toolbar } from '@wangeditor/editor-for-react';
 import { Typography } from 'antd';
-import { useState } from 'react';
+import {useEffect, useState} from 'react';
 import Arrow from '../../../../image/arrow.png';
 import './index.scss';
+import useApis from "@/apis";
+import {engagementDetailVO} from "@/types/vo";
 
 
 interface TeachersWorkDetailsLeftProps {
   current: Record<string, any>;
+  engagementInfo: {
+    studentId: number;
+    assignmentId: number;
+  }
 }
+interface TeachersWorkDetailsRightProps {
+  current: Record<string, any>;
+ }
 
 interface TeachersWorkDetailsProps {
   onConfirm: (value: null) => void;
@@ -19,7 +29,24 @@ interface TeachersWorkDetailsProps {
  * @param current
  * @constructor
  */
-export function TeachersWorkDetailsLeft({ current }: TeachersWorkDetailsLeftProps) {
+export const TeachersWorkDetailsLeft:React.FC<TeachersWorkDetailsLeftProps> = ({ current, engagementInfo }) => {
+  const apis = useApis()
+
+  const [data, setData] = useState<{
+    engagementDetail: engagementDetailVO
+  }>({
+    engagementDetail: {} as engagementDetailVO
+  })
+
+  useEffect(() => {
+    console.log("此处应该请求")
+    apis.getDetailEngagement(engagementInfo.studentId, engagementInfo.assignmentId)
+        .then(_res => {
+          console.log(_res)
+          setData(prev => ({...prev, engagementDetail: _res.data.data}))
+        })
+  }, [])
+
   const article =
     '这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字这是一段文字';
 
@@ -47,29 +74,28 @@ export function TeachersWorkDetailsLeft({ current }: TeachersWorkDetailsLeftProp
         </div>
       </div>
       <div className="edit-details">
-        <div className="edit-details-item">学生姓名:{current.user}</div>
-        <div className="edit-details-item">学生学号:123546489</div>
+        <div className="edit-details-item">学生姓名:{data.engagementDetail.userVO.name}</div>
+        <div className="edit-details-item">学生学号:{data.engagementDetail.userVO.officialNumber}</div>
         <div className="edit-details-item">
-          提交时间:{current.date}
+          <>提交时间:{new Date()}</>
         </div>
       </div>
     </div>
     <div className="edit-composition">
       <div>作文内容</div>
       <div className="edit-box">
-        <WordEditor
-          mode="view"
-          documentKey="Khirz6zTPdfd7"
-          documentTitle="Example Document Title.docx"
-          documentUrl="https://example.com/url-to-example-document.docx"
-          userId={1}
-          username="111"
-        />
+        <iframe src={"https://view.officeapps.live.com/op/view.aspx?src="+data.engagementDetail.fileUrl} title={data.engagementDetail.assignmentVO.assignmentName} style={{width: "100%", height: '100%'}}/>
       </div>
     </div>
   </div>;
 }
 
+const toolbarConfig: Partial<IToolbarConfig> = {};
+
+const editorConfig: Partial<IEditorConfig> = {
+  placeholder: '请输入内容...',
+};
+
 /**
  * 教师批改作文右侧
  * @param onConfirm
@@ -137,15 +163,25 @@ export function TeachersWorkDetailsRight({
   </div>;
 }
 
-export function TeachersWorkDetails({ onConfirm, current }: TeachersWorkDetailsProps & TeachersWorkDetailsLeftProps) {
+
+interface IPropsDetails {
+  engagementInfo: {
+    studentId: number;
+    assignmentId: number;
+  }
+}
+
+// 老师可以点击进入该作业的批改页面,则意味着该作业已经完成提交(作业状态已变为“已提交”)
+
+export function TeachersWorkDetails({ onConfirm, current,  engagementInfo}: TeachersWorkDetailsProps & TeachersWorkDetailsLeftProps & IPropsDetails) {
 
   return <div className="word-details">
     <div className="word-return-icon" onClick={onConfirm.bind(null, null)}>
       <img src={Arrow} alt=""/>返回
     </div>
     <div className="word-container">
-      <TeachersWorkDetailsLeft current={current}/>
-      <TeachersWorkDetailsRight current={current}/>
+      <TeachersWorkDetailsLeft current={current} engagementInfo={engagementInfo}/>
+      <TeachersWorkDetailsRight current={current} engagementInfo={engagementInfo}/>
     </div>
   </div>;
 }

+ 0 - 10
src/pages/LoginPage/LoginPage.tsx

@@ -2,17 +2,7 @@ import React, {useState} from "react";
 import "./index.scss"
 import {Login} from "@/pages/LoginPage/component/Login";
 
-interface ILoginForm {
-    username: string,
-    password: string
-}
-
 const LoginPage = () => {
-    const [data, setData] = useState<ILoginForm>({
-        username: '',
-        password: ''
-    })
-
     const [isLogin, setLogin] = useState(true)
 
     return (

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

@@ -1,4 +1,4 @@
-import {Button, Form, Input, notification, Space} from "antd";
+import {Button, Form, Input, Space} from "antd";
 import React, {ChangeEvent, useState} from "react";
 import "./index.scss"
 import {useCountdown} from "@/hooks/useCountdown";

+ 3 - 137
src/pages/Test/Test.tsx

@@ -1,141 +1,7 @@
-import WordEditor from "@/components/WordEditor/WordEditor";
-import styles from "./Test.module.scss"
-import useApis from "@/apis";
-import {useNavigate} from "react-router";
-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 [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 = () => {
-        setData(prev => ({...prev, isModalOpen: true}));
-    };
-
-    const handleOk = () => {
-        setData(prev => ({...prev, isModalOpen: false}));
-    };
-
-    const handleCancel = () => {
-        setData(prev => ({...prev, isModalOpen: false}));
-    };
-
-    const handleClick = () => {
-        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)=> {
-        setData(prev => ({...prev, isModified: state}))
-    }
-
-    if (isEmpty(data.engagement)) {
-        return <div>Loading...</div>; // 或者其他加载中的提示
-    }
-
-    // TODO 离开页面检测,进行弹框提醒
-
+const Test  = () => {
     return (
-        <>
-            {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>
-        </>
+        <div></div>
     )
 }
 
-export default Test;
+export default Test

+ 12 - 0
src/router/index.tsx

@@ -3,6 +3,10 @@ import App from "@/pages/App";
 import LoginPage from "@/pages/LoginPage/LoginPage";
 import Sign from "@/pages/Sign/Sign"
 import Test from "@/pages/Test/Test";
+import {MyHomework} from "@/pages/Home/component/MyHomework";
+import {TeachersWorkDetails} from "@/pages/Home/component/TeachersWorkDetails";
+import {EditPage} from "@/pages/EditPage/EditPage";
+import CorrectPage from "@/pages/CorrectPage/CorrectPage";
 
 const router = createBrowserRouter([
     {
@@ -20,6 +24,14 @@ const router = createBrowserRouter([
     {
         path: '/test',
         element: <Test></Test>
+    },
+    {
+        path: '/assignment/:assignmentId/edit',
+        element: <EditPage></EditPage>
+    },
+    {
+        path: '/assignment/:assignmentId/student/:studentId/correct',
+        element: <CorrectPage></CorrectPage>
     }
 ])
 

+ 6 - 0
src/types/dialogue.ts

@@ -2,3 +2,9 @@ export interface IDialogue {
     role: string;
     content: string;
 }
+
+export interface IRewriteResult {
+    role: string;
+    content: string;
+    timestamp: number;
+}

+ 3 - 3
src/types/user.ts

@@ -8,9 +8,9 @@ export interface IUser {
     createTime: string,
     englishName: string,
     gender: "MAN"|"FEMALE"|null,
-    birthday: string,
-    phone: string,
-    contentEmail: string,
+    birthday: string|null,
+    phone: string|null,
+    contentEmail: string|null,
 }
 
 export interface ILoginUserFrom {

+ 50 - 0
src/types/vo.ts

@@ -1,3 +1,5 @@
+import {IUser} from "@/types/user";
+
 export interface engagementVO {
     "id": number,
     "assignmentId": number,
@@ -9,3 +11,51 @@ export interface engagementVO {
     "status": string,
     "fileKey": string,
 }
+
+export interface engagementDetailVO {
+    "id": number,
+    "assignmentVO": IAssignment,
+    "userVO": IUser,
+    "score": number|null,
+    "remark": string|null,
+    "fileUrl": string,
+    "version": number,
+    "status": string,
+    "fileKey": string,
+}
+
+export interface IAssignment {
+    assignmentId: number;
+
+    assignmentName: string;
+
+    description: string;
+
+    descriptionFile: string|null; // 作业要求,Word/Pdf文件
+
+    attachments: string[]|null; // 作业附件
+
+    teacherId: number; // 发布作业的老师的Id
+
+    courseId: number; // 哪个课程下的assignment
+
+    startTime: string;
+
+    endTime: string;
+
+    createTime: string;
+
+    status: string;
+}
+
+
+export interface ITranslationVO {
+    queryText: string;
+
+    queryResult: string;
+
+    targetLanguage: string; // 目标语言类型
+
+    queryTime: string;
+}
+

+ 57 - 0
yarn.lock

@@ -4397,6 +4397,13 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+docx-preview@^0.3.0:
+  version "0.3.0"
+  resolved "https://r.cnpmjs.org/docx-preview/-/docx-preview-0.3.0.tgz"
+  integrity sha512-uLrLhytJkXe420Q0C/cyYuZYcKr+2bh7aXjwR/XclEIuGq9ifDRH1/GVqLBn7+ZX29CKC1bZovtjyfhETjdKKg==
+  dependencies:
+    jszip ">=3.0.0"
+
 dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
   version "0.5.16"
   resolved "https://r.cnpmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz"
@@ -5429,6 +5436,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"
@@ -5862,6 +5874,11 @@ ignore@^5.2.0:
   resolved "https://r.cnpmjs.org/ignore/-/ignore-5.3.1.tgz"
   integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
 
+immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://r2.cnpmjs.org/immediate/-/immediate-3.0.6.tgz"
+  integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
 immer@^9.0.6, immer@^9.0.7, immer@>=9.0.6:
   version "9.0.21"
   resolved "https://r.cnpmjs.org/immer/-/immer-9.0.21.tgz"
@@ -6941,6 +6958,16 @@ jsonpointer@^5.0.0:
     object.assign "^4.1.4"
     object.values "^1.1.6"
 
+jszip@>=3.0.0:
+  version "3.10.1"
+  resolved "https://r.cnpmjs.org/jszip/-/jszip-3.10.1.tgz"
+  integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+  dependencies:
+    lie "~3.3.0"
+    pako "~1.0.2"
+    readable-stream "~2.3.6"
+    setimmediate "^1.0.5"
+
 keyv@^4.5.3:
   version "4.5.4"
   resolved "https://r.cnpmjs.org/keyv/-/keyv-4.5.4.tgz"
@@ -7004,6 +7031,13 @@ levn@~0.3.0:
     prelude-ls "~1.1.2"
     type-check "~0.3.2"
 
+lie@~3.3.0:
+  version "3.3.0"
+  resolved "https://r2.cnpmjs.org/lie/-/lie-3.3.0.tgz"
+  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+  dependencies:
+    immediate "~3.0.5"
+
 lilconfig@^2.0.3, lilconfig@^2.1.0:
   version "2.1.0"
   resolved "https://r.cnpmjs.org/lilconfig/-/lilconfig-2.1.0.tgz"
@@ -7671,6 +7705,11 @@ p-try@^2.0.0:
   resolved "https://r2.cnpmjs.org/p-try/-/p-try-2.2.0.tgz"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
+pako@~1.0.2:
+  version "1.0.11"
+  resolved "https://r.cnpmjs.org/pako/-/pako-1.0.11.tgz"
+  integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
 param-case@^3.0.4:
   version "3.0.4"
   resolved "https://r2.cnpmjs.org/param-case/-/param-case-3.0.4.tgz"
@@ -9050,6 +9089,19 @@ readable-stream@^3.0.6:
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
+readable-stream@~2.3.6:
+  version "2.3.8"
+  resolved "https://r.cnpmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
+  integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
 readdirp@~3.6.0:
   version "3.6.0"
   resolved "https://r2.cnpmjs.org/readdirp/-/readdirp-3.6.0.tgz"
@@ -9531,6 +9583,11 @@ set-function-name@^2.0.0, set-function-name@^2.0.1:
     functions-have-names "^1.2.3"
     has-property-descriptors "^1.0.0"
 
+setimmediate@^1.0.5:
+  version "1.0.5"
+  resolved "https://r2.cnpmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
+  integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
+
 setprototypeof@1.1.0:
   version "1.1.0"
   resolved "https://r2.cnpmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz"