فهرست منبع

Merge branch 'master' into correctPage-byXY

xiaoyu 2 سال پیش
والد
کامیت
8629372518

+ 0 - 1
package-lock.json

@@ -1693,7 +1693,6 @@
       "version": "0.3.2",
       "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.3.2.tgz",
       "integrity": "sha512-YRsyiiejdauCQ2boKNHKjJMiIhOCXs643+NCHnmbCM31e7JWqmPiobtzlmHOnv4i+ft9w+ajPEK1hK7VymyRXQ==",
-      "license": "Apache-2.0",
       "dependencies": {
         "jszip": ">=3.0.0"
       }

+ 1 - 0
src/apis/assignment.ts

@@ -19,6 +19,7 @@ const assignmentApis = {
         })
     },
     createAssignment(courseId:number, assignmentDTO: AssignmentDTO){
+        console.log(assignmentDTO)
         return axiosInstance.post(`${ASSIGNMENT_PREFIX}`, assignmentDTO, {
             params: {
                 courseId

+ 2 - 3
src/apis/axios.config.ts

@@ -1,7 +1,7 @@
 import axios from "axios";
 
 // const BASEURL = 'http://47.111.23.171:8081'
-const BASEURL = '/api'
+const BASEURL = 'http://localhost:8080'
 // const BASEURL = 'http://8.130.126.86:8080'
 
 const axiosInstance = axios.create({
@@ -19,8 +19,7 @@ axiosInstance.interceptors.request.use(
         if(sessionStorage.getItem('user-storage')){
             token = JSON.parse(sessionStorage.getItem('user-storage') as string).token
         }
-        
-        console.log(token, "axios.configs")
+
         // 如果存在token,则每次请求前加入到请求头中
         if (token) config.headers["Authorization"] = token
         return config

+ 18 - 0
src/apis/file.ts

@@ -0,0 +1,18 @@
+import axiosInstance from "@/apis/axios.config";
+
+const FILE_PREFIX = "/api/file"
+
+const fileApis = {
+    uploadFile(file) {
+        const formData = new FormData();
+        formData.append('file', file);
+
+        return axiosInstance.post(`${FILE_PREFIX}`, formData, {
+            headers: {
+                'Content-Type': 'multipart/form-data'
+            }
+        });
+    },
+}
+
+export default fileApis

+ 3 - 1
src/apis/index.ts

@@ -6,6 +6,7 @@ import aiApis from "@/apis/ai";
 import engagementApis from "@/apis/engagement";
 import assignmentApis from "@/apis/assignment";
 import courseApis from "@/apis/course";
+import fileApis from "@/apis/file";
 
 
 const apis = {
@@ -16,7 +17,8 @@ const apis = {
     ...aiApis,
     ...dictApis,
     ...assignmentApis,
-    ...courseApis
+    ...courseApis,
+    ...fileApis
 }
 
 // hook

+ 3 - 1
src/components/WordEditor/WordEditor.vue

@@ -62,7 +62,9 @@
     },
     editorConfig: {
       lang: 'zh-CN',
-      callbackUrl: 'http://139.196.252.184:8081/api/onlyoffice/callback',
+      //不要用localhost和127.0.0.1访问,用局域网IP访问
+      //TODO:项目部署要改
+      callbackUrl: 'http://192.168.137.1:8080/api/onlyoffice/callback',
       customization: {
         autosave: false,
         comments: false,

+ 18 - 3
src/types/vo.ts

@@ -75,8 +75,9 @@ export interface AssignmentVO {
 
 export interface IAssignmentTableColumn {
     title: string;
-    dataIndex: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId';
-    key: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId';
+    dataIndex: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
+    key: 'desc' | 'publisher' | 'startDate' | 'endDate' | 'details' | 'writing' | 'assignmentId' | 'teacherId';
+    width: number
 }
 
 export interface IAssignmentTableItem {
@@ -85,7 +86,8 @@ export interface IAssignmentTableItem {
     startDate: string;
     endDate: string;
     assignmentId: number;
-    descriptionFile?: string
+    descriptionFile?: string;
+    teacherId: string
 }
 
 
@@ -111,3 +113,16 @@ export interface CourseVO {
     description: string;
 }
 
+export interface CourseDetailVO {
+    courseId: number;
+    courseName: string;
+    teacherIds: number[];
+    teacherNames: string[];
+    courseImages: string[];
+    targetGrade: string;
+    startTime: string;
+    endTime: string;
+    createTime: string;
+    description: string;
+}
+

+ 5 - 4
src/views/CorrectPage/AssignmentPage.vue

@@ -34,13 +34,13 @@
       </el-button>
     </div>
 
-    <el-table stripe :data="pageInfo.tableData" style="width: 100%" height="100%">
+    <el-table stripe :data="pageInfo.tableData" style="width: 100%" height="100%" show-overflow-tooltip>
       <template #empty>
         暂无作业
       </template>
       <el-table-column type="index"/>
-      <el-table-column prop="assignmentName" label="作业名称"/>
-      <el-table-column prop="description" label="描述"/>
+      <el-table-column prop="assignmentName" label="作业名称" width="400"/>
+      <el-table-column prop="description" label="描述" width="500"/>
       <el-table-column prop="endTime" label="截止时间" sortable/>
       <el-table-column prop="status" label="状态" sortable/>
       <el-table-column label="操作">
@@ -104,7 +104,8 @@
   let courseInfo = reactive<CourseVO>({} as CourseVO)
 
   let query = reactive<AssignmentQuery>({
-    courseId: +route.query.courseId
+    courseId: +route.query.courseId,
+    teacherId: store.user.id
   } as AssignmentQuery)
 
   const fetchCourseInfo = () => {

+ 6 - 6
src/views/CorrectPage/CorrectAssignmentPage.vue

@@ -29,15 +29,15 @@
       </el-select>
     </div>
 
-    <el-table :data="pageInfo.tableData" style="width: 100%;" height="100%" :row-style="{height: '30px'}" v-if="status == ''">
+    <el-table :data="pageInfo.tableData" style="width: 100%;" height="100%" :row-style="{height: '30px'}" v-if="status == ''" show-overflow-tooltip>
       <template #empty>
         暂无作业
       </template>
       <el-table-column type="index"/>
-      <el-table-column prop="statusName" label="状态" sortable/>
-      <el-table-column prop="studentName" label="学生姓名"/>
-      <el-table-column prop="score" label="得分" sortable/>
-      <el-table-column prop="remark" label="评价"/>
+      <el-table-column prop="statusName" label="状态" sortable width="150"/>
+      <el-table-column prop="studentName" label="学生姓名" width="150"/>
+      <el-table-column prop="score" label="得分" sortable width="150"/>
+      <el-table-column prop="remark" label="评价" width="750"/>
       <el-table-column label="操作">
         <template #default="scope">
           <el-button @click="handleCorrect(scope.row.assignmentId, scope.row.studentId)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
@@ -48,7 +48,7 @@
       </el-table-column>
     </el-table>
 
-    <el-table :data="getSlice(queryInfo.pageNow, queryInfo.pageSize)" style="width: 100%;" height="100%" :row-style="{height: '30px'}" v-if="status != ''">
+    <el-table show-overflow-tooltip :data="getSlice(queryInfo.pageNow, queryInfo.pageSize)" style="width: 100%;" height="100%" :row-style="{height: '30px'}" v-if="status != ''">
       <template #empty>
         暂无作业
       </template>

+ 3 - 3
src/views/CorrectPage/CoursePage.vue

@@ -16,13 +16,13 @@
       查询
     </el-button>
 
-    <el-table :data="pageInfo.records" style="width: 100%; max-height: 70vh" height="100%" :row-style="{height: '30px'}">
+    <el-table :data="pageInfo.records" style="width: 100%; max-height: 70vh" height="100%" :row-style="{height: '30px'}" show-overflow-tooltip>
       <template #empty>
         暂无课程
       </template>
       <el-table-column type="index"/>
-      <el-table-column prop="courseName" label="课程名称"/>
-      <el-table-column prop="description" label="描述"/>
+      <el-table-column prop="courseName" label="课程名称" width="300"/>
+      <el-table-column prop="description" label="描述" width="600"/>
       <el-table-column prop="endTime" label="截止时间" sortable/>
 <!--      <el-table-column prop="endTime" label="状态" sortable/>-->
       <el-table-column label="操作">

+ 84 - 5
src/views/CoursePage/CoursePage.vue

@@ -15,13 +15,13 @@
       查询
     </el-button>
 
-    <el-table :data="pageInfo.records" style="width: 100%" height="100%">
+    <el-table :data="pageInfo.records" style="width: 100%" height="100%" show-overflow-tooltip>
       <template #empty>
         暂无课程
       </template>
       <el-table-column type="index"/>
-      <el-table-column prop="courseName" label="课程名称"/>
-      <el-table-column prop="description" label="描述"/>
+      <el-table-column prop="courseName" label="课程名称" width="300"/>
+      <el-table-column prop="description" label="描述" width="600"/>
       <el-table-column prop="endTime" label="截止时间" sortable/>
       <el-table-column label="操作">
         <template #default="scope">
@@ -67,6 +67,33 @@
       <el-form-item label="截止时间">
         <el-date-picker v-model="newCourse.endTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
+<!--      <el-form-item label="课程图片">-->
+<!--        <el-upload-->
+<!--            ref="uploadRef"-->
+<!--            drag-->
+<!--            action= "http://127.0.0.1:8080/api/file"-->
+<!--            multiple-->
+<!--            v-model:file-list="fileList"-->
+<!--            :limit="1"-->
+<!--            :on-success="handleSuccess"-->
+<!--            :on-error="handleError"-->
+<!--            :on-exceed="handleExceed"-->
+<!--            :on-change="handleChange"-->
+<!--            list-type="picture-card"-->
+<!--            accept=".png, .jpg"-->
+<!--            :auto-upload="false"-->
+<!--        >-->
+<!--          <el-icon class="el-icon&#45;&#45;upload"><upload-filled /></el-icon>-->
+<!--          <div class="el-upload__text">-->
+<!--            拖拽上传或<em>点击此处上传</em>-->
+<!--          </div>-->
+<!--          <template #tip>-->
+<!--            <div class="el-upload__tip">-->
+<!--              jpg/png 文件不大于 500kb-->
+<!--            </div>-->
+<!--          </template>-->
+<!--        </el-upload>-->
+<!--      </el-form-item>-->
     </el-form>
     <template #footer>
       <div class="dialog-footer">
@@ -82,10 +109,17 @@
   import {useStore} from "@/store";
   import {inject, onMounted, reactive, ref} from "vue";
   import type {CourseVO} from "@/types/vo";
-  import {Refresh, Search, ZoomIn} from "@element-plus/icons-vue";
+  import {Refresh, Search, UploadFilled, ZoomIn} from "@element-plus/icons-vue";
   import router from "@/router";
   import type {CourseDTO} from "@/types/dto";
-  import {ElNotification} from "element-plus";
+  import {
+    ElMessage,
+    ElNotification,
+    type UploadFile,
+    type UploadFiles,
+    type UploadProps,
+    type UploadUserFile
+  } from "element-plus";
   import type {CourseQuery} from "@/types/query";
   import {useFormatDate} from "@/hooks/useFormatDate";
 
@@ -96,6 +130,7 @@
   const role:'teachers' | 'student' = store.user.role === 'STUDENT' ?   'student':'teachers';
 
   let visible = ref(false)
+  let uploadRef = ref()
 
   const queryInfo = reactive({
     pageNow: 1,
@@ -135,6 +170,42 @@
     semester:null,
   })
 
+  // 用于编辑课程展示预览图片
+  const fileList = ref<UploadUserFile[]>([])
+
+  const handleSuccess: UploadProps['onSuccess'] = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    newCourse.courseImages[0] = response.data
+  }
+
+  const handleError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    console.log(error, uploadFile, uploadFiles)
+  }
+
+  const handleExceed = (files, fileList) => {
+    // console.log(files)
+    uploadRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
+    const file = files[0]
+    uploadRef.value.handleStart(file)  //重新上传
+  }
+
+  //选择文件
+  const handleChange = (file,fileList) => {
+    // console.log(file)
+    const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+    const isImageType = ['png','jpg'];
+    const isLt5KB = file.size / 1024  < 500;  //判断图片格式与大小
+    if (isImageType.indexOf(fileImgType)==-1) {
+      ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
+      uploadRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+    if (!isLt5KB) {
+      ElMessage.error('文件不能超过500kb!')  //限制文件大小
+      uploadRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+  }
+
   let courseName = ref('')
   const handleSearch = () => {
     if (role === 'student') {
@@ -170,6 +241,8 @@
   const handleSubmit = () => {
     newCourse.endTime = formatDate(newCourse.endTime)
     newCourse.startTime = formatDate(newCourse.startTime)
+    uploadRef.value.submit()
+    console.log(newCourse)
     apis.createCourse(newCourse)
         .then((res:any) => {
           if(res.data.code===200) {
@@ -275,3 +348,9 @@ export default {
   name: "CoursePage"
 }
 </script>
+
+<style scoped>
+:deep(.el-upload-dragger) {
+  padding: 0px 0px;
+}
+</style>

+ 13 - 2
src/views/CourseSquare/component/Course/index.vue

@@ -20,8 +20,8 @@
         </div>
         <div class="course-item-content">
           <div style="font-weight: bold">{{item.courseName}}</div>
-          <div>{{item.description}}</div>
-          <div>{{item.teacherIds.join("\n")}}</div>
+          <div class="collapsed">课程描述:{{item.description}}</div>
+          <div>任课教师:{{item.teacherIds.join(",")}}</div>
         </div>
       </el-card>
       <template v-if="getCoursesSlice().length < 5">
@@ -228,3 +228,14 @@ export default {
   name: "Course"
 }
 </script>
+
+<style scoped>
+.collapsed {
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2; /* Limit to 2 lines */
+  max-height: 4em; /* Height for 2 lines of text */
+  min-height: 4em;
+}
+</style>

+ 212 - 20
src/views/CourseSquare/component/CourseDetails/component/CourseHomeworkTable.vue

@@ -9,21 +9,32 @@
       <el-button color="#597d3b" @click="cleanNewAssignmentInfo(); changeDialog('add')" v-if="props.isShowNewHomework">新建作业</el-button>
     </template>
     <template v-if="props.tableData.length > 0">
-      <el-table :data="props.tableData" stripe style="width: 100%" height="100%">
+      <el-table :data="props.tableData" stripe style="width: 100%" height="100%" show-overflow-tooltip>
+        <el-table-column type="index"></el-table-column>
         <el-table-column
             v-for="column in props.columns"
+            :key="column.key"
             :prop="column.dataIndex"
             :label="column.title"
+            :width="column.width"
         >
         </el-table-column>
-        <el-table-column label="详情">
+        <el-table-column label="详情" width="60">
           <template #default="scope">
-            <el-button color="#888" circle @click="openEditor(scope.row.descriptionFile, scope.row.assignmentName)"/>
+            <el-button color="#597d3b" circle @click="openEditor(scope.row.descriptionFile, scope.row.assignmentName)">
+              <el-icon :size="18">
+                <Search />
+              </el-icon>
+            </el-button>
           </template>
         </el-table-column>
-        <el-table-column :label="role == 'teachers'? '编辑':'写作'">
+        <el-table-column :label="role == 'teachers'? '编辑':'写作'" width="60">
           <template #default="scope">
-            <el-button color="#888" circle @click="handleFunctionButton(scope.row.assignmentId)" :disabled="props.disabled"/>
+            <el-button color="#597d3b" circle @click="handleFunctionButton(scope.row.assignmentId)" :disabled="props.disabled || isDisabled(scope.row.teacherId, scope.row.endDate)">
+              <el-icon :size="18">
+                <Edit />
+              </el-icon>
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -34,7 +45,7 @@
   </el-card>
 
   <el-dialog v-model="dialogConfig.visible" :title="dialogConfig.title">
-    <el-form :model="newAssignmentInfo">
+    <el-form :model="newAssignmentInfo" label-width="auto">
       <!--      <el-form-item label="课程图片">-->
       <el-form-item label="作业名称">
         <el-input v-model="newAssignmentInfo.assignmentName" placeholder="请输入名称"></el-input>
@@ -42,18 +53,83 @@
       <el-form-item label="作业描述">
         <el-input v-model="newAssignmentInfo.description" placeholder="请输入描述"></el-input>
       </el-form-item>
-      <el-form-item label="描述文件">
-        <el-input v-model="newAssignmentInfo.descriptionFile" placeholder="请输入文件地址"></el-input>
-      </el-form-item>
-      <el-form-item label="课程图片">
-        <el-input v-model="newAssignmentInfo.attachments[0]" placeholder="请输入图片链接"></el-input>
-      </el-form-item>
       <el-form-item label="开始时间">
         <el-date-picker v-model="newAssignmentInfo.startTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
       <el-form-item label="截止时间">
         <el-date-picker v-model="newAssignmentInfo.endTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
+      <el-form-item label="描述文件" v-if="dialogConfig.type == 'edit'">
+        <el-upload
+            ref="uploadDescribeRef"
+            action= "http://127.0.0.1:8080/api/file"
+            multiple
+            v-model:file-list="describeFileList"
+            :limit="1"
+            :on-success="handleDescribeSuccess"
+            :on-error="handleDescribeError"
+            :on-exceed="handleDescribeExceed"
+            :on-change="handleDescribeChange"
+            accept=".doc, .docx"
+            :auto-upload="false"
+        >
+          <el-button type="primary">点击上传</el-button>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="附件" v-if="dialogConfig.type == 'edit'">
+        <el-upload
+            ref="uploadPictureRef"
+            action= "http://127.0.0.1:8080/api/file"
+            multiple
+            v-model:file-list="pictureFileList"
+            :limit="1"
+            :on-success="handlePictureSuccess"
+            :on-error="handlePictureError"
+            :on-exceed="handlePictureExceed"
+            :on-change="handlePictureChange"
+            accept=".doc, .docx"
+            :auto-upload="false"
+        >
+          <el-button type="primary">点击上传</el-button>
+          <template #tip>
+            <div class="el-upload__tip">
+              doc/docx 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+<!--      <el-form-item label="作业图片" v-if="dialogConfig.type == 'edit'">-->
+<!--        <el-upload-->
+<!--            ref="uploadPictureRef"-->
+<!--            drag-->
+<!--            action= "http://127.0.0.1:8080/api/file"-->
+<!--            multiple-->
+<!--            v-model:file-list="pictureFileList"-->
+<!--            :limit="1"-->
+<!--            :on-success="handlePictureSuccess"-->
+<!--            :on-error="handlePictureError"-->
+<!--            :on-exceed="handlePictureExceed"-->
+<!--            :on-change="handlePictureChange"-->
+<!--            list-type="picture-card"-->
+<!--            accept=".png, .jpg"-->
+<!--            :auto-upload="false"-->
+<!--        >-->
+<!--          <el-icon class="el-icon&#45;&#45;upload"><upload-filled /></el-icon>-->
+<!--          <div class="el-upload__text">-->
+<!--            拖拽上传或<em>点击此处上传</em>-->
+<!--          </div>-->
+<!--          <template #tip>-->
+<!--            <div class="el-upload__tip">-->
+<!--              jpg/png 文件不大于 500kb-->
+<!--            </div>-->
+<!--          </template>-->
+<!--        </el-upload>-->
+<!--      </el-form-item>-->
     </el-form>
     <template #footer>
       <div class="dialog-footer">
@@ -72,10 +148,18 @@ import {defineProps, inject, onMounted, reactive, ref} from 'vue';
   import router from "@/router";
   import type {AssignmentDTO} from "@/types/dto";
   import useApis from "@/apis";
-  import {ElNotification} from "element-plus";
+  import {
+  ElMessage,
+  ElNotification,
+  type UploadFile,
+  type UploadFiles,
+  type UploadProps,
+  type UploadUserFile
+} from "element-plus";
   import {useFormatDate} from "@/hooks/useFormatDate";
-  import assignment from "@/apis/assignment";
-import ViewsWordEditor from "@/components/ViewsWordEditor/ViewsWordEditor.vue";
+  import ViewsWordEditor from "@/components/ViewsWordEditor/ViewsWordEditor.vue";
+  import {useStore} from "@/store";
+  import {Search, Edit, UploadFilled, Plus} from "@element-plus/icons-vue"
 
   const props = defineProps<{
     tableData: IAssignmentTableItem[],
@@ -87,8 +171,84 @@ import ViewsWordEditor from "@/components/ViewsWordEditor/ViewsWordEditor.vue";
   }>()
 
   const apis = useApis()
+  const store = useStore()
   const doRefresh:Function = inject("reload")
   const {formatDate} = useFormatDate()
+  let uploadDescribeRef = ref()
+  let uploadPictureRef = ref()
+
+  // 用于编辑课程展示预览图片
+  const describeFileList = ref<UploadUserFile[]>([])
+  const pictureFileList = ref<UploadUserFile[]>([])
+
+  const handleDescribeSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    Object.assign(newAssignmentInfo, {
+      assignmentName: newAssignmentInfo.assignmentName,
+      description: newAssignmentInfo.description,
+      descriptionFile: response.data,
+      attachments: newAssignmentInfo.attachments == null? ['']:newAssignmentInfo.attachments,
+      startTime: newAssignmentInfo.startTime,
+      endTime: newAssignmentInfo.endTime
+    })
+  }
+  const handlePictureSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    newAssignmentInfo.attachments[0] = response.data
+  }
+
+  const handleDescribeError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    console.log(error, uploadFile, uploadFiles)
+  }
+  const handlePictureError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+    console.log(error, uploadFile, uploadFiles)
+  }
+
+  const handleDescribeExceed = (files, fileList) => {
+    // console.log(files)
+    uploadDescribeRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
+    const file = files[0]
+    uploadDescribeRef.value.handleStart(file)  //重新上传
+  }
+  const handlePictureExceed = (files, fileList) => {
+    // console.log(files)
+    uploadPictureRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
+    const file = files[0]
+    uploadPictureRef.value.handleStart(file)  //重新上传
+  }
+
+  //选择文件
+  const handleDescribeChange = (file,fileList) => {
+    // console.log(file)
+    const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+    const isImageType = ['doc','docx'];
+    const isLt500KB = file.size / 1024  < 500;  //判断图片格式与大小
+    if (isImageType.indexOf(fileImgType)==-1) {
+      ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
+      uploadDescribeRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+    if (!isLt500KB) {
+      ElMessage.error('文件不能超过500kb!')  //限制文件大小
+      uploadDescribeRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+  }
+  const handlePictureChange = (file,fileList) => {
+    // console.log(file)
+    const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+    // const isImageType = ['png','jpg'];
+    const isImageType = ['doc','docx'];
+    const isLt500KB = file.size / 1024  < 500;  //判断图片格式与大小
+    if (isImageType.indexOf(fileImgType)==-1) {
+      ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
+      uploadDescribeRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+    if (!isLt500KB) {
+      ElMessage.error('文件不能超过500kb!')  //限制文件大小
+      uploadDescribeRef.value.clearFiles()  //清空上传文件
+      return false;
+    }
+  }
 
   const dialogConfig = reactive({
     type: "",
@@ -181,6 +341,7 @@ const openEditor = (url: string, title: string) => {
       startTime: assignment.startTime,
       endTime: assignment.endTime
     })
+    // console.log("获取作业信息", newAssignmentInfo)
   }
 
   // 用于记录当前编辑的作业的id
@@ -192,6 +353,7 @@ const openEditor = (url: string, title: string) => {
       apis.getAssignmentById(assignmentId)
           .then((res:any) => {
             setNewAssignmentInfo(res.data.data)
+            // console.log("功能按钮方法内部",newAssignmentInfo)
             IAssignmentId.value = res.data.data.assignmentId
           })
       changeDialog('edit')
@@ -199,17 +361,22 @@ const openEditor = (url: string, title: string) => {
   }
 
   const handleWriting = (assignmentId:number) => {
-    router.push(`/home/assignment/${assignmentId}/edit`);
+    router.push(`/home/homeworkDetail/${assignmentId}`);
     // Location.reload()
   }
 
 
-  const handleSubmit = () => {
+  const handleSubmit = async () => {
+    // console.log("提交方法最开始",newAssignmentInfo)
     newAssignmentInfo.endTime = formatDate(newAssignmentInfo.endTime);
     newAssignmentInfo.startTime = formatDate(newAssignmentInfo.startTime);
+    await uploadDescribeRef.value.submit()
+    await uploadPictureRef.value.submit()
+    // console.log("提交方法,请求前",newAssignmentInfo)
     if(dialogConfig.type === 'add'){
       apis.createAssignment(props.courseId, newAssignmentInfo)
           .then((res) => {
+            console.log("新增作业请求内部",newAssignmentInfo)
             if(res.data.code===200) {
               ElNotification({
                 title: '新增成功',
@@ -222,20 +389,24 @@ const openEditor = (url: string, title: string) => {
               })
             }
             dialogConfig.visible = false
-            cleanNewAssignmentInfo()
-
+            // cleanNewAssignmentInfo()
           })
           .catch((err) => {
             console.log(err)
           })
     } else if(dialogConfig.type === 'edit'){
+      // console.log("编辑作业请求前一刻",newAssignmentInfo)
       apis.updateAssignment(IAssignmentId.value, newAssignmentInfo)
           .then((res:any) => {
+            // console.log("编辑作业请求内部",newAssignmentInfo)
             if(res.data.code===200) {
               ElNotification({
                 title: '修改成功',
                 type: 'success',
               })
+              // console.log("编辑作业请求内部22",newAssignmentInfo)
+              setTimeout(() => apis.updateAssignment(IAssignmentId.value, newAssignmentInfo), 300)
+              // console.log("编辑作业请求内部33",newAssignmentInfo)
             }else {
               ElNotification({
                 title: '修改失败',
@@ -243,7 +414,7 @@ const openEditor = (url: string, title: string) => {
               })
             }
             dialogConfig.visible = false
-            cleanNewAssignmentInfo()
+            // cleanNewAssignmentInfo()
           })
           .catch((err) => {
             console.log(err)
@@ -252,6 +423,17 @@ const openEditor = (url: string, title: string) => {
     doRefresh();
   }
 
+  const isDisabled = (teacherId: string, endDate: string) => {
+    if(props.role == 'teachers'){// 是教师且不是作业发布人则禁用
+      if(+teacherId != store.user.id){
+        return true
+      }
+    } else { // 是学生且超时则禁用
+      if(new Date() > new Date(endDate)) return true
+    }
+    return false
+  }
+
   onMounted(() => {
     console.log(props)
   })
@@ -262,3 +444,13 @@ export default {
   name: "CourseHomeworkTable"
 }
 </script>
+
+<style scoped>
+  .el-icon.avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+</style>

+ 146 - 38
src/views/CourseSquare/component/CourseDetails/index.vue

@@ -14,7 +14,7 @@
           {{dataForm.courseName}}
         </div>
         <div class="item">
-          任课老师:{{dataForm.teacherIds?.join(",")}}
+          任课老师:{{dataForm.teacherNames?.join(",")}}
         </div>
         <div class="item">
           目标学生:{{dataForm.targetGrade}}
@@ -28,7 +28,7 @@
             <el-button color="#597d3b" @click="enroll">加入课程</el-button>
           </template>
           <template v-if="showEdit && role == 'teachers'">
-            <el-button color="#597d3b" @click="visible = true">编辑课程</el-button>
+            <el-button color="#597d3b" @click="visible = true; fileList[0] = {name: '壁纸', url: newCourseInfo.courseImages[0]}">编辑课程</el-button>
           </template>
         </div>
       </div>
@@ -50,7 +50,7 @@
   </div>
 
   <el-dialog v-model="visible" title="编辑课程">
-    <el-form :model="newCourseInfo">
+    <el-form :model="newCourseInfo" label-width="auto">
 <!--      <el-form-item label="课程图片">-->
       <el-form-item label="课程名称">
         <el-input v-model="newCourseInfo.courseName" placeholder="请输入名称"></el-input>
@@ -61,9 +61,6 @@
       <el-form-item label="描述">
         <el-input v-model="newCourseInfo.description" placeholder="请输入描述"></el-input>
       </el-form-item>
-      <el-form-item label="课程图片">
-        <el-input v-model="newCourseInfo.courseImages[0]" placeholder="请输入图片链接"></el-input>
-      </el-form-item>
       <el-form-item label="开始时间">
         <el-date-picker v-model="newCourseInfo.startTime" type="datetime" placeholder="截止时间" style="width: 180px"/>
       </el-form-item>
@@ -73,6 +70,36 @@
       <el-form-item label="选课码">
         <el-input v-model="newCourseInfo.enrollCode" placeholder="请输入选课码"></el-input>
       </el-form-item>
+<!--      <el-form-item label="课程图片">-->
+<!--        <el-input v-model="newCourseInfo.courseImages[0]" placeholder="请输入图片链接"></el-input>-->
+<!--      </el-form-item>-->
+      <el-form-item label="课程图片">
+        <el-upload
+            ref="uploadRef"
+            drag
+            action= "http://127.0.0.1:8080/api/file"
+            multiple
+            v-model:file-list="fileList"
+            :limit="1"
+            :on-success="handleSuccess"
+            :on-error="handleError"
+            :on-exceed="handleExceed"
+            :on-change="handleChange"
+            list-type="picture-card"
+            accept=".png, .jpg"
+            :auto-upload="false"
+        >
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text">
+            拖拽上传或<em>点击此处上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              jpg/png 文件不大于 500kb
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
     </el-form>
     <template #footer>
       <div class="dialog-footer">
@@ -85,16 +112,27 @@
 
 <script setup lang="ts">
 import {onMounted, reactive, ref} from 'vue';
-import {ElTabs, ElTabPane, ElTable, ElTableColumn, ElAvatar, ElMessage, ElNotification} from 'element-plus';
+import {
+  ElTabs,
+  ElTabPane,
+  ElTable,
+  ElTableColumn,
+  ElAvatar,
+  ElMessage,
+  ElNotification,
+  type UploadFiles, type UploadFile
+} from 'element-plus';
 import './index.scss'
-import type {CourseVO, IAssignment, IAssignmentTableColumn, IAssignmentTableItem} from "@/types/vo";
+import type {CourseDetailVO, CourseVO, IAssignment, IAssignmentTableColumn, IAssignmentTableItem} from "@/types/vo";
 import {useRoute} from "vue-router";
 import useApis from "@/apis";
 import CourseHomeworkTable from "@/views/CourseSquare/component/CourseDetails/component/CourseHomeworkTable.vue";
 import type {AssignmentQuery} from "@/types/query";
 import {useStore} from "@/store";
 import type {CourseUpdateDTO, EnrollDTO} from "@/types/dto";
-import {useFormatDate} from "@/hooks/useFormatDate";
+import {UploadFilled} from "@element-plus/icons-vue"
+import type { UploadProps, UploadUserFile } from 'element-plus'
+import axiosInstance from "@/apis/axios.config";
 
 const apis = useApis()
 const store = useStore()
@@ -107,12 +145,14 @@ let showCode = ref(true)
 let enrollCode = ref(null)
 let showEdit = ref(false)
 let visible = ref(false)
+let uploadRef = ref()
 
 // 用于展示课程信息
-let dataForm = reactive<CourseVO>({
+let dataForm = reactive<CourseDetailVO>({
   courseId: null,
   courseName: '',
   teacherIds: [],
+  teacherNames: [],
   courseImages: [''],
   targetGrade: '',
   startTime: '',
@@ -148,37 +188,88 @@ const items = ref([
 ]);
 
 const columns = ref<IAssignmentTableColumn[]>([
-  {
-    title: '作业id',
-    dataIndex: 'assignmentId',
-    key: 'assignmentId',
-  },
   {
     title: '作业名称',
     dataIndex: 'desc',
     key: 'desc',
-  },
-  {
-    title: '发布人',
-    dataIndex: 'publisher',
-    key: 'publisher',
+    width: 250
   },
   {
     title: '截止日期',
     dataIndex: 'endDate',
     key: 'endDate',
+    width: 180
+  },
+  {
+    title: '发布人',
+    dataIndex: 'publisher',
+    key: 'publisher',
+    width: 100
   },
 ]);
 
 // 用于展示课程所含的作业
 let homeworkList = ref<IAssignmentTableItem[]>([]);
 
+// 用于编辑课程展示预览图片
+const fileList = ref<UploadUserFile[]>([])
+
+const handleSuccess: UploadProps['onSuccess'] = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+  newCourseInfo.courseImages[0] = response.data
+}
+
+const handleError: UploadProps['onError'] = (error: Error, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+  console.log(error, uploadFile, uploadFiles)
+}
+
+const handleExceed = (files, fileList) => {
+  // console.log(files)
+  uploadRef.value.clearFiles()  //清空上传文件(限制一个,所以直接清空即可)
+  const file = files[0]
+  uploadRef.value.handleStart(file)  //重新上传
+}
+
+//选择文件
+const handleChange = (file,fileList) => {
+  // console.log(file)
+  const fileImgType = file.name.match(/\.([^\.]+)$/)[1];  //匹配文件格式(最后一个'.'后的格式)或者匹配file.raw.type
+  const isImageType = ['png','jpg'];
+  const isLt5KB = file.size / 1024  < 500;  //判断图片格式与大小
+  if (isImageType.indexOf(fileImgType)==-1) {
+    ElMessage.error('文件仅支持png、jpg格式')  //限制文件类型
+    uploadRef.value.clearFiles()  //清空上传文件
+    return false;
+  }
+  if (!isLt5KB) {
+    ElMessage.error('文件不能超过500kb!')  //限制文件大小
+    uploadRef.value.clearFiles()  //清空上传文件
+    return false;
+  }
+}
+
+
+/*
+  用于显示教师姓名
+   */
+const processCourses = () => {
+  for(let i = 0; i < dataForm.teacherIds.length; i++){
+    apis.findUserById(dataForm.teacherIds[i])
+        .then((res:any) => {
+          dataForm.teacherNames[i] = res.data.data.records[0].name
+        })
+        .catch((err:any) => {
+          console.log("错误信息",err)
+        })
+  }
+}
+
 const getCourse = () => {
   // 获取课程信息
   apis.getCourseByCourseId(courseId)
       .then((res: any) => {
         let data = res.data.data.records[0]
         Object.assign(dataForm, {...data})
+        processCourses()
         getNewCourseInfo(dataForm)
       })
       .catch((err: any) => {
@@ -205,11 +296,11 @@ const getHomeworkList = () => {
       .then((res: any) => {
         let data = res.data.data.records
         homeworkList.value = convertHomeworkList(data)
-        // TODO
-        for(let i = 0; i < data.length; i++){
-          apis.findUserById(data[i].teacherId)
+        console.log(homeworkList.value)
+        for(let i = 0; i < homeworkList.value.length; i++){
+          apis.findUserById(+homeworkList.value[i].publisher)
               .then((res: any) => {
-                data[i].teacherId = res.data.data.records[0].name
+                homeworkList.value[i].publisher = res.data.data.records[0].name
               })
         }
       })
@@ -274,18 +365,23 @@ const enroll = () => {
 const isShowEdit = () => {
   apis.getCourseByTeacherId(store.user.id)
       .then((res: any) => {
-        let data = res.data.data.records
-        for(let i = 0 ; i < data.length; i++){
-          if(data[i].courseId == courseId){
-            showEdit.value = true;
-            break;
-          }
-        }
+        let total = res.data.data.total
+        apis.getCourseByTeacherId(store.user.id, 1, total)
+            .then((res: any) => {
+              let data = res.data.data.records
+              for(let i = 0 ; i < data.length; i++){
+                if(data[i].courseId == courseId){
+                  showEdit.value = true;
+                  break;
+                }
+              }
+            })
+            .catch((err: any) => {
+              console.error(err);
+            })
+            .finally(() => {});
       })
-      .catch((err: any) => {
-        console.error(err);
-      })
-      .finally(() => {});
+
 }
 
 /*
@@ -300,7 +396,8 @@ const convertHomeworkList = (data:IAssignment[]) => {
       startDate: formatDate(data[i].startTime),
       endDate: formatDate(data[i].endTime),
       assignmentId: data[i].assignmentId,
-      descriptionFile: data[i].descriptionFile != null ? data[i].descriptionFile: null
+      descriptionFile: data[i].descriptionFile != null ? data[i].descriptionFile: null,
+      teacherId: data[i].teacherId.toString(),
     }
     newHomeworkList.push(item)
   }
@@ -324,6 +421,9 @@ function formatDate(date:string) {
 const handleSubmit = () => {
   newCourseInfo.endTime = formatDate(newCourseInfo.endTime)
   newCourseInfo.startTime = formatDate(newCourseInfo.startTime)
+  console.log(uploadRef.value!.submit())
+  console.log(newCourseInfo)
+  // TODO: 更新图片需要更新两次,不知道为什么
   apis.updateCourse(dataForm.courseId, newCourseInfo)
     .then((res:any) => {
       if(res.data.code===200) {
@@ -332,6 +432,11 @@ const handleSubmit = () => {
           type: 'success',
         })
         visible.value = false
+        apis.getCourseByCourseId(dataForm.courseId)
+            .then((res:any) => {
+              console.log(res.data.data.records[0])
+            })
+        setTimeout(() => apis.updateCourse(dataForm.courseId, newCourseInfo), 500)
       }else {
         ElNotification({
           title: '修改失败',
@@ -340,7 +445,7 @@ const handleSubmit = () => {
         visible.value = false
       }
     })
-    .catch((err) => {
+    .catch((err:any) => {
       console.log(err)
     })
 }
@@ -380,5 +485,8 @@ onMounted(() => {
 ::v-deep .el-tabs__active-bar {
   background-color: #597D3B;
 }
-</style>
 
+:deep(.el-upload-dragger) {
+  padding: 0px 0px;
+}
+</style>

+ 1 - 1
src/views/Home/component/Edit/index.vue

@@ -81,7 +81,7 @@
   const handleValueChange = (state: boolean) => {
     if (state) data.hasModified = true;
     data.isModified = state;
-    alert("1")
+    // alert("1")
   };
 
   // 提交操作

+ 1 - 1
src/views/Home/component/Silder/index.vue

@@ -35,7 +35,7 @@
               <div class="slider-tag-item" @click="openEditor(data.assignment.descriptionFile, data.assignment.assignmentName)">作业要求</div>
             </template>
             <template v-if="data.assignment.attachments">
-              <div v-for="(item, index) in data.assignment.attachments" :key="index" class="slider-tag-item" @click="openEditor(item, '附件' + index)">附件{{ index }}</div>
+              <div v-for="(item, index) in data.assignment.attachments" :key="index" class="slider-tag-item" @click="openEditor(item, '附件' + index)">附件{{ index+1 }}</div>
             </template>
           </div>
         </template>

+ 12 - 7
src/views/HomeworkPage/component/HomeworkDetail.vue

@@ -22,7 +22,7 @@
         </div>
         <div class="item">
           <el-link type="primary" @click="console.log('文件下载')">相关文件下载</el-link>
-          <el-button color="#597d3b" size="large" style="margin-left: 660px" @click="handleWriting()">开始写作</el-button>
+          <el-button color="#597d3b" size="large" style="margin-left: 660px" @click="handleWriting()" :disabled="new Date() > new Date(data.endTime)">开始写作</el-button>
         </div>
       </div>
     </div>
@@ -40,7 +40,7 @@
       </div>
       <div>
         <el-input type="textarea" style="width: 100%;margin: 20px 0"></el-input>
-        <el-button color="#597d3b" size="large" style="float: right">发布</el-button>
+        <el-button color="#597d3b" size="large" style="float: right" :disabled="true">发布</el-button>
       </div>
     </div>
   </div>
@@ -86,11 +86,16 @@ async function getEngagement(){
         console.log(_res)
         //还未参加作业,则先加入作业
         if(_res.data.data == null){
-          apis.engageAssignment(assignmentId);
-          ElMessage({
-            message: '参加作业成功',
-            type: 'success',
-          })
+          apis.engageAssignment(assignmentId)
+              .then((res:any) => {
+                ElMessage({
+                  message: '参加作业成功',
+                  type: 'success',
+                })
+              })
+              .catch((_err:any) => {
+                console.log("参加做业的错误",_err)
+              })
         }
       }).catch((_err:any) => {
         console.log(_err)

+ 7 - 7
src/views/HomeworkPage/index.vue

@@ -25,16 +25,16 @@
       查询
     </el-button>
 
-    <el-table :data="pageInfo.tableData" style="width: 100%" height="100%">
+    <el-table :data="pageInfo.tableData" style="width: 100%" height="100%" show-overflow-tooltip>
       <template #empty>
         暂无作业
       </template>
       <el-table-column type="index"/>
-      <el-table-column prop="assignmentName" label="课程名称"/>
-      <el-table-column prop="description" label="描述"/>
-      <el-table-column prop="endTime" label="截止时间" sortable/>
-      <el-table-column prop="status" label="状态" sortable/>
-      <el-table-column label="操作">
+      <el-table-column prop="assignmentName" label="课程名称" width="300"/>
+      <el-table-column prop="description" label="描述" width="400"/>
+      <el-table-column prop="endTime" label="截止时间" sortable  width="200"/>
+      <el-table-column prop="status" label="状态" sortable width="150"/>
+      <el-table-column label="操作" width="100">
         <template #default="scope">
           <el-button @click="handleShowDetail(scope.row.assignmentId)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
             <el-icon style="margin-right: 3px"><ZoomIn/></el-icon>
@@ -42,7 +42,7 @@
           </el-button>
         </template>
       </el-table-column>
-      <el-table-column label="查看">
+      <el-table-column label="查看" width="150">
         <template #default="scope">
           <el-button @click="viewCorrection(scope.row.assignmentId,store.user.id)" style="margin-right: 0; margin-left: 0;padding-left: 10px; padding-right: 10px" color="#597D3B">
             <el-icon style="margin-right: 3px"><ZoomIn/></el-icon>

+ 5 - 20
vite.config.ts

@@ -3,26 +3,8 @@ import { fileURLToPath, URL } from 'node:url'
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
 
-//vite.config.ts 和 axios.config.ts和baiqi的版本有些许出入,若保留其版本无法正常登陆
-//目前用的还是xiaoyu的版本
-
 // https://vitejs.dev/config/
 export default defineConfig({
-
-    //配置代理,解决跨域问题。详见EAI问题文档
-    server: {
-      host: '127.0.0.1',
-      port: 4000,
-      proxy: {
-        '/api': {  
-          target: 'http://localhost:8080',
-          changeOrigin: true,
-          rewrite: (path) => path.replace(/^\/api/, ''),
-        },
-      },
-    },
-  
-
   plugins: [
     vue(),
   ],
@@ -31,5 +13,8 @@ export default defineConfig({
       '@': fileURLToPath(new URL('./src', import.meta.url))
     }
   },
- 
-})
+  server: {
+    host: '127.0.0.1',
+    port: 4000
+  }
+})