Forráskód Böngészése

Merge branch 'develop' of SEEC-II-FrontEnd-Team/SEEC-II-FrontEnd into master

WuXinyu 7 éve
szülő
commit
eceae63934
59 módosított fájl, 3499 hozzáadás és 780 törlés
  1. 23 5
      mock/modules/assignment/index.js
  2. 1 0
      mock/serializers/assignment/DocumentContentSerializer.js
  3. 3 2
      mock/serializers/assignment/DocumentResultSerializer.js
  4. 14 2
      mock/serializers/codehw/FunctionalTestSerializer.js
  5. 14 2
      mock/serializers/codehw/UnitTestSerializer.js
  6. 1 1
      mock/serializers/user/UserSerializer.js
  7. 227 227
      package-lock.json
  8. 3 1
      package.json
  9. 1 0
      src/App.vue
  10. 23 21
      src/api/assignment.js
  11. 35 3
      src/api/codehw.js
  12. 2 3
      src/api/course.js
  13. 39 4
      src/api/question.js
  14. 20 0
      src/api/resultstatistics.js
  15. 3 1
      src/api/review.js
  16. 96 70
      src/components/AssignmentCRUD/CreateAssignment/index.vue
  17. 3 13
      src/components/AssignmentCRUD/DeleteAssignment/index.vue
  18. 17 18
      src/components/AssignmentCRUD/UpdateAssignment/index.vue
  19. 22 2
      src/components/CheckList/index.vue
  20. 21 0
      src/components/CodeStateTag/index.vue
  21. 1 1
      src/components/DocumentStateTag/index.vue
  22. 226 0
      src/components/NoDataSvg.vue
  23. 23 0
      src/data/codeState.js
  24. 0 3
      src/layouts/LoginLayout.vue
  25. 30 3
      src/router/routes.js
  26. 69 0
      src/util/exportExcel.js
  27. 3 3
      src/views/login/Login.vue
  28. 1 1
      src/views/login/Register.vue
  29. 55 4
      src/views/student/Code/ProjectDetail/BuildDetail.vue
  30. 259 22
      src/views/student/Code/ProjectDetail/DeployDetail.vue
  31. 11 2
      src/views/student/Code/ProjectDetail/Test/FunctionalTestDetail.vue
  32. 13 2
      src/views/student/Code/ProjectDetail/Test/UnitTestDetail.vue
  33. 25 22
      src/views/student/Code/ProjectDetail/TestDetail.vue
  34. 2 1
      src/views/student/Code/ProjectDetail/index.vue
  35. 2 2
      src/views/student/Code/ProjectList/index.vue
  36. 22 6
      src/views/student/Code/index.vue
  37. 11 17
      src/views/student/Document/detail.vue
  38. 10 14
      src/views/student/Document/index.vue
  39. 529 2
      src/views/student/Home/index.vue
  40. 5 1
      src/views/student/Review/ReviewDetail/ReviewSelfDetail/index.vue
  41. 3 1
      src/views/student/Review/ReviewDocDetail/index.vue
  42. 9 8
      src/views/student/Review/index.vue
  43. 115 0
      src/views/teacher/Code/ProjectDetail/BuildDetail.vue
  44. 171 0
      src/views/teacher/Code/ProjectDetail/DeployDetail.vue
  45. 156 0
      src/views/teacher/Code/ProjectDetail/TestDetail.vue
  46. 62 51
      src/views/teacher/Code/detail.vue
  47. 12 14
      src/views/teacher/Code/index.vue
  48. 44 15
      src/views/teacher/Code/test.vue
  49. 0 9
      src/views/teacher/Dashboard/index.vue
  50. 35 17
      src/views/teacher/Document/detail.vue
  51. 16 7
      src/views/teacher/Document/docContent.vue
  52. 6 6
      src/views/teacher/Document/index.vue
  53. 529 2
      src/views/teacher/Home/index.vue
  54. 147 15
      src/views/teacher/Question/index.vue
  55. 8 2
      src/views/teacher/Question/questionDetail.vue
  56. 313 148
      src/views/teacher/Result/index.vue
  57. 1 1
      src/views/teacher/Review/ReviewDetail/GroupArgueDetail/index.vue
  58. 4 3
      src/views/teacher/Review/index.vue
  59. 3 0
      vue.config.js

+ 23 - 5
mock/modules/assignment/index.js

@@ -42,6 +42,14 @@ router.route("/teacher/course/:courseId/code").get((req, res) => {
   });
 });
 
+//老师查看单个代码作业
+router.route("/teacher/code/:codeId").get((req, res) => {
+  res.send({
+    code: 0,
+    data: CodeSerializer()
+  });
+});
+
 //学生查看代码作业列表
 router.route("/student/course/:courseId/code").get((req, res) => {
   res.send({
@@ -132,6 +140,14 @@ router.route("/teacher/course/:courseId/document").get((req, res) => {
   });
 });
 
+//老师查看单个文档作业
+router.route("/teacher/document/:documentId").get((req, res) => {
+  res.send({
+    code: 0,
+    data: DocumentSerializer()
+  });
+});
+
 //学生查看文档作业列表
 router.route("/student/course/:courseId/document").get((req, res) => {
   res.send({
@@ -179,11 +195,13 @@ router.route("/student/document/:documentId/result").get((req, res) => {
   });
 });
 
-router.route("/teacher/document/:documentId/content").get((req, res) => {
-  res.send({
-    code: 0,
-    data: DocumentContentSerializer()
+router
+  .route("/teacher/document/result/:documentResultId/content")
+  .get((req, res) => {
+    res.send({
+      code: 0,
+      data: DocumentContentSerializer()
+    });
   });
-});
 
 module.exports = router;

+ 1 - 0
mock/serializers/assignment/DocumentContentSerializer.js

@@ -12,6 +12,7 @@ module.exports = () => {
     id: makeId(),
     name: "购物车web项目",
     group: groupSerializer(),
+    result: "A",
     lastUpdateAt: dayjs("2018-10-21").unix(),
     content: makeLongMarkdown()
   };

+ 3 - 2
mock/serializers/assignment/DocumentResultSerializer.js

@@ -1,5 +1,6 @@
 const makeId = require("../../util/makeId");
 const groupSerializer = require("../course/GroupSerializer");
+const documentSerializer = require("../assignment/DocumentSerializer");
 /**
  * @description 文档作业成绩
  * @typedef {Object} DocumentResultSerializerType
@@ -18,11 +19,11 @@ const groupSerializer = require("../course/GroupSerializer");
 module.exports = () => {
   return {
     id: makeId(),
-    document: makeId(),
+    document: documentSerializer(),
     group: groupSerializer(),
     state: "互评中",
     result: "A",
     url: "https://github.com",
-    reviewGroup: [1, 2, 3]
+    reviewGroup: [groupSerializer(), groupSerializer(), groupSerializer()] //不显示,防止学生之间讨论
   };
 };

+ 14 - 2
mock/serializers/codehw/FunctionalTestSerializer.js

@@ -8,6 +8,7 @@ const dayjs = require("dayjs");
  * @property {number} time 测试时间
  * @property {boolean} isPassAll 是否全部通过
  * @property {string|number} deployId 对应的部署 id
+ * @property {string} consoleOutput 控制台输出
  * @property {OneFunctionalTestSerializerType[]} detail 所有测试用例列表
  */
 
@@ -16,7 +17,8 @@ const dayjs = require("dayjs");
  * @typedef {Object} OneFunctionalTestSerializerType
  * @property {number} id 一个测试用例的 id
  * @property {boolean} isPass 是否通过
- * @property {string} message 输出
+ * @property {string} message 输出(当测试用例失败才有)
+ * @property {string} trace 错误栈信息(当测试用例失败时才有)
  */
 
 /**
@@ -31,13 +33,23 @@ module.exports = ({ id = makeId(), deployId = makeId() }) => {
     id: makeId(),
     isPass: id % 2 === 1,
     message:
-      "One Function alTes tSerializerOne FunctionalTestSerializer OneFunc tionalT estSerializer"
+      "One Function alTes tSerializerOne FunctionalTestSerializer OneFunc tionalT estSerializer",
+    trace: `One Function alTes tSerializerOne FunctionalTestSeria
+    fdsafdsafds fdsaaf dsaf asf dsadfs adsf sadsaf  fads f
+    dfsa sa fdadfsafdsafdsaf safds dsf sadf 
+    f dsfds afdsafdsadfasfdsasa  df sa asfdsafdsa dsaf dsaf asdfgsafgdsaff
+    fd sa dfs asdfs`
   });
 
   return {
     id,
     time: dayjs("2018-9-21").unix(),
     deployId,
+    consoleOutput: `One Function alTes tSerializerOne FunctionalTestSeria
+    fdsafdsafds fdsaaf dsaf asf dsadfs adsf sadsaf  fads f
+    dfsa sa fdadfsafdsafdsaf safds dsf sadf 
+    f dsfds afdsafdsadfasfdsasa  df sa asfdsafdsa dsaf dsaf asdfgsafgdsaff
+    fd sa dfs asdfs`,
     isPassAll: id % 2 === 1,
     detail: [
       getOneFunctionalTestSerializer(),

+ 14 - 2
mock/serializers/codehw/UnitTestSerializer.js

@@ -8,6 +8,7 @@ const dayjs = require("dayjs");
  * @property {number} time 测试时间
  * @property {boolean} isPassAll 是否全部通过
  * @property {string|number} buildId 对应的构建 id
+ * @property {string} consoleOutput 控制台输出
  * @property {OneUnitTestSerializerType[]} detail 所有测试用例列表
  */
 
@@ -16,7 +17,8 @@ const dayjs = require("dayjs");
  * @typedef {Object} OneUnitTestSerializerType
  * @property {number} id 一个测试用例的 id
  * @property {boolean} isPass 是否通过
- * @property {string} message 输出
+ * @property {string} message 输出(当测试用例失败才有)
+ * @property {string} trace 错误栈信息(当测试用例失败时才有)
  */
 
 /**
@@ -31,7 +33,12 @@ module.exports = ({ id = makeId(), buildId = makeId() }) => {
     id: makeId(),
     isPass: id % 2 === 1,
     message:
-      "One Function alTes tSerializerOne FunctionalTestSerializer OneFunc tionalT estSerializer"
+      "One Function alTes tSerializerOne FunctionalTestSerializer OneFunc tionalT estSerializer",
+    trace: `One Function alTes tSerializerOne FunctionalTestSeria
+    fdsafdsafds fdsaaf dsaf asf dsadfs adsf sadsaf  fads f
+    dfsa sa fdadfsafdsafdsaf safds dsf sadf 
+    f dsfds afdsafdsadfasfdsasa  df sa asfdsafdsa dsaf dsaf asdfgsafgdsaff
+    fd sa dfs asdfs`
   });
 
   return {
@@ -39,6 +46,11 @@ module.exports = ({ id = makeId(), buildId = makeId() }) => {
     buildId,
     time: dayjs("2018-9-21").unix(),
     isPassAll: id % 2 === 1,
+    consoleOutput: `One Function alTes tSerializerOne FunctionalTestSeria
+    fdsafdsafds fdsaaf dsaf asf dsadfs adsf sadsaf  fads f
+    dfsa sa fdadfsafdsafdsaf safds dsf sadf 
+    f dsfds afdsafdsadfasfdsasa  df sa asfdsafdsa dsaf dsaf asdfgsafgdsaff
+    fd sa dfs asdfs`,
     detail: [
       getOneUnitTestSerializer(),
       getOneUnitTestSerializer(),

+ 1 - 1
mock/serializers/user/UserSerializer.js

@@ -14,7 +14,7 @@ const makeId = require("../../util/makeId");
  * @param role
  * @returns {UserSerializerType}
  */
-module.exports = (role = "TEACHER") => {
+module.exports = (role = "STUDENT") => {
   return {
     id: makeId(),
     role: role,

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 227 - 227
package-lock.json


+ 3 - 1
package.json

@@ -14,6 +14,7 @@
     "buefy": "^0.7.1",
     "chart.js": "^2.7.3",
     "dayjs": "^1.7.8",
+    "file-saver": "^2.0.1",
     "register-service-worker": "^1.5.2",
     "vue": "^2.5.17",
     "vue-chartjs": "^3.4.0",
@@ -22,7 +23,8 @@
     "vue-router": "^3.0.1",
     "vuelidate": "^0.7.4",
     "vuex": "^3.0.1",
-    "whatwg-fetch": "^3.0.0"
+    "whatwg-fetch": "^3.0.0",
+    "xlsx": "^0.14.1"
   },
   "devDependencies": {
     "@pollyjs/adapter-fetch": "^1.4.1",

+ 1 - 0
src/App.vue

@@ -7,5 +7,6 @@
 
 #app {
   background: $default-background;
+  word-wrap: break-word;
 }
 </style>

+ 23 - 21
src/api/assignment.js

@@ -71,13 +71,22 @@ export const getCodeAssignmentListByTeacher = courseId => {
   return request(`${ASSIGNMENT_MODULE}/teacher/course/${courseId}/code`);
 };
 
+/**
+ * 老师查看单个代码作业
+ * @param codeId
+ * @returns {Promise<{data:CodeSerializerType}>}
+ */
+export const getCodeAssignmentByTeacher = codeId => {
+  return request(`${ASSIGNMENT_MODULE}/teacher/code/${codeId}`);
+};
+
 /**
  * 学生查看代码作业列表
  * @param courseId
  * @returns {Promise<{data:CodeSerializerType[]}>}
  */
 export const getCodeAssignmentListByStu = courseId => {
-  return request(`${ASSIGNMENT_MODULE}/student/course/${courseId}}/code`);
+  return request(`${ASSIGNMENT_MODULE}/student/course/${courseId}/code`);
 };
 
 /**
@@ -89,15 +98,6 @@ export const getCodeAssignmentByStu = codeId => {
   return request(`${ASSIGNMENT_MODULE}/student/code/${codeId}`);
 };
 
-/**
- * 学生查看单个代码作业下的项目信息列表
- * @param codeId
- * @returns {Promise<{data:ProjectSerializerType}>}
- */
-export const getProjectListByCode = codeId => {
-  return request(`${ASSIGNMENT_MODULE}/student/code/${codeId}/project`);
-};
-
 /**
  * 学生查看单个代码作业下的成绩
  * @param codeId
@@ -129,15 +129,6 @@ export const getUnsubmittedCodeGroupsList = codeId => {
   );
 };
 
-/**
- * 老师查看单个代码作业的成绩列表(所有组的)
- * @param codeId
- * @returns {Promise<{data:CodeResultSerializerType}>}
- */
-export const getCodeResultListByCode = codeId => {
-  return request(`${ASSIGNMENT_MODULE}/teacher/code/${codeId}/result`);
-};
-
 /* 文档作业*/
 /**
  * 老师新建文档作业
@@ -210,6 +201,15 @@ export const getDocAssignmentListTeacherByCrs = courseId => {
   return request(`${ASSIGNMENT_MODULE}/teacher/course/${courseId}/document`);
 };
 
+/**
+ * 老师查看单个文档作业
+ * @param documentId
+ * @returns {Promise<{data:DocumentSerializerType}>}
+ */
+export const getDocAssignmentTeacherByCrs = documentId => {
+  return request(`${ASSIGNMENT_MODULE}/teacher/document/${documentId}`);
+};
+
 /**
  * 学生查看文档作业列表
  * @param courseId
@@ -264,6 +264,8 @@ export const getUnsubmittedDocGroupsByDocId = documentId => {
  * @param documentId
  * @return {Promise<data:DocumentContentSerializerType>}
  */
-export const getDocContentByDocId = documentId => {
-  return request(`${ASSIGNMENT_MODULE}/teacher/document/${documentId}/content`);
+export const getDocContentByDocId = documentResultId => {
+  return request(
+    `${ASSIGNMENT_MODULE}/teacher/document/result/${documentResultId}/content`
+  );
 };

+ 35 - 3
src/api/codehw.js

@@ -50,6 +50,28 @@ export const getBuildDetail = buildId => {
   return request(`${CODE_HOMEWORK_MODULE}/build/${buildId}`);
 };
 
+/**
+ * 获得最近n个构建的信息
+ * @param {string|number} projectId
+ * @return {Promise<{id:string,data:BuildSerializerType[]}>}
+ */
+export const getLatestBuildInfoList = (projectId, limit = 5) => {
+  return request(
+    `${CODE_HOMEWORK_MODULE}/project/${projectId}/build/record?limit=${limit}`
+  );
+};
+
+/**
+ * 获得最近n个部署的信息
+ * @param {string|number} projectId
+ * @return {Promise<{id:string,data:DeploySerializerType[]}>}
+ */
+export const getLatestDeployInfoList = (projectId, limit = 5) => {
+  return request(
+    `${CODE_HOMEWORK_MODULE}/project/deploy/record/${projectId}?limit=${limit}`
+  );
+};
+
 /**
  * 获得一次部署的详细信息
  * @param {string|number} deployId
@@ -59,6 +81,15 @@ export const getDeployDetail = deployId => {
   return request(`${CODE_HOMEWORK_MODULE}/deploy/${deployId}`);
 };
 
+/**
+ * 获得一次部署的日志信息
+ * @param {string|number} deployId
+ * @return {Promise<{id:string,data:string}>}
+ */
+export const getDeployLogInfo = deployId => {
+  return request(`${CODE_HOMEWORK_MODULE}/deploy/log/${deployId}`);
+};
+
 /**
  * 获得一次单元测试的详细信息
  * @param {string|number} unitId
@@ -90,15 +121,16 @@ export const getAvailableMirrorList = ({ homeworkId, projectId }) => {
 /**
  * TODO: arguments 不被允许
  * 触发一次部署
- * @param {string|number} homeworkId
+ * @param {string|number} replicas
  * @param {string|number} projectId
  * @param {string|number} mirrorId
  * @return {Promise<{data:{success:boolean}}>}
  */
-export const makeDeploy = ({ homeworkId, projectId, mirrorId }) => {
+export const makeDeploy = ({ projectId, mirrorId, replicas }) => {
+  console.log(mirrorId);
   return request(`${CODE_HOMEWORK_MODULE}/deploy`, {
     method: "POST",
-    body: { homeworkId, projectId, mirrorId }
+    body: { replicas, projectId, mirrorId }
   });
 };
 

+ 2 - 3
src/api/course.js

@@ -25,7 +25,6 @@ export const getStudentCourses = () => {
  * @throws {StatusException} "课程名重复" | "课程选课码重复"
  */
 export const postTeacherCourse = ({ name, code }) => {
-  console.log(`name${name} code ${code}`);
   return request(`${COURSE_MODULE}/teacher`, {
     method: "POST",
     body: {
@@ -42,10 +41,10 @@ export const postTeacherCourse = ({ name, code }) => {
  * @throws {StatusException} "选课码不存在" | "已经加入课程"
  */
 export const postStudentCourse = code => {
-  return request(`${COURSE_MODULE}/student`, {
+  return request(`${COURSE_MODULE}/register`, {
     method: "POST",
     body: {
-      code
+      code: code
     }
   });
 };

+ 39 - 4
src/api/question.js

@@ -25,17 +25,52 @@ export const getQuestionsByAssignType = ({ type, page, limit }) => {
  */
 export const getQuestionByQuesType = ({ type, page, limit }) => {
   return request(
-    `${QUESTION_MODULE}/teacher/questions/type?type=${type || ""}&page=${page ||
-      1}&limit=${limit || 10}`
+    `${QUESTION_MODULE}/teacher/questions/type?type=${type || ""}&page=${
+      page >= 0 ? page : 0
+    }&limit=${limit || 10}`
   );
 };
+
+/**
+ * 根据关键字及问题分类进行查找
+ * @param {string} keyword
+ * @param {string} type
+ * @returns {Promise<{data:QuestionSerializer[]}>}
+ */
+export const getTeacherKeywordQuestionsByType = (keyword = "", type = "") => {
+  return request(
+    `${QUESTION_MODULE}/teacher/keyword?keyword=${keyword}&type=${type}`
+  );
+};
+
+/**
+ * 根据关键字及问题分类进行查找
+ * @param {string} keyword
+ * @param {string} type
+ * @returns {Promise<{data:QuestionSerializer[]}>}
+ */
+export const getTeacherKeywordQuestionsByCode = (
+  keyword = "123",
+  type = "CODE"
+) => {
+  return request(
+    `${QUESTION_MODULE}/teacher/keyword?keyword=${keyword}&type=${type}`
+  );
+};
+
 /**
  * 根据关键字进行查找
  * @param {string} keyword
+ * @param {string} type
  * @returns {Promise<{data:QuestionSerializer[]}>}
  */
-export const getTeacherKeywordQuestions = (keyword = "123") => {
-  return request(`${QUESTION_MODULE}/teacher/keyword?keyword=${keyword}`);
+export const getTeacherKeywordQuestionsByDoc = (
+  keyword = "123",
+  type = "DOCUMENT"
+) => {
+  return request(
+    `${QUESTION_MODULE}/teacher/keyword?keyword=${keyword}&type=${type}`
+  );
 };
 
 /**

+ 20 - 0
src/api/resultstatistics.js

@@ -10,3 +10,23 @@ export const getStudentFinaleResult = (courseId = 123) => {
     method: "POST"
   });
 };
+
+/**
+ * 设置作业成绩比例
+ * @returns {Promise<{data:string}>}
+ */
+export const setResultPercentList = (
+  courseId,
+  docPercentList,
+  codePercentList,
+  reviewPercentList
+) => {
+  return request(`${RESULTSTATISTICS_MODULE}/course/${courseId}/percent`, {
+    method: "POST",
+    body: {
+      documents: docPercentList,
+      codes: codePercentList,
+      reviews: reviewPercentList
+    }
+  });
+};

+ 3 - 1
src/api/review.js

@@ -167,7 +167,9 @@ export const postStudentReviewWork = (reviewId, docReviewId, checkListBody) => {
     `${REVIEW_MODULE}/student/review/${reviewId}/doc-review/${docReviewId}`,
     {
       method: "POST",
-      body: checkListBody
+      body: {
+        data: checkListBody
+      }
     }
   );
 };

+ 96 - 70
src/components/AssignmentCRUD/CreateAssignment/index.vue

@@ -82,6 +82,7 @@
               placeholder="输入关键字模糊查找"
               type="search"
               v-model="$v.searchContent.$model"
+              @keyup.enter.native="search(searchContent)"
               expanded
             >
             </b-input>
@@ -104,67 +105,73 @@
       <b-notification type="is-danger" :active.sync="isServerError">
         {{ errorMassage }}
       </b-notification>
-      <div class="card">
-        <div class="title">选择题目</div>
-        <div class="content">
-          <div class="question-list">
-            <b-table :data="questions" detailed detail-key="id">
-              <template slot-scope="props">
-                <b-table-column
-                  field="id"
-                  label="ID"
-                  width="40"
-                  numeric
-                  sortable
-                >
-                  {{ props.row.id }}
-                </b-table-column>
-                <b-table-column label="题目名称">
-                  <a @click="selectAndCreate(props.row)">{{
-                    props.row.name
-                  }}</a>
-                </b-table-column>
-                <b-table-column label="题目描述" width="100">
-                  <div class="description">{{ props.row.description }}</div>
-                </b-table-column>
-                <b-table-column
-                  field="difficulty"
-                  label="题目难度"
-                  sortable
-                  centered
-                >
-                  <span
-                    :class="[
-                      'tag',
-                      `${getDifficultyClass(props.row.difficulty)}`
-                    ]"
+      <div>
+        <div class="card" v-if="isAnyQuestion">
+          <div class="title">选择题目</div>
+          <div class="content">
+            <div class="question-list">
+              <b-table :data="questions" detailed detail-key="id">
+                <template slot-scope="props">
+                  <b-table-column
+                    field="id"
+                    label="ID"
+                    width="40"
+                    numeric
+                    sortable
                   >
-                    {{ getDifficultyType(props.row.difficulty) }}
-                  </span>
-                </b-table-column>
+                    {{ props.row.id }}
+                  </b-table-column>
+                  <b-table-column label="题目名称">
+                    <a @click="selectAndCreate(props.row)">{{
+                      props.row.name
+                    }}</a>
+                  </b-table-column>
+                  <b-table-column label="题目描述" width="100">
+                    <div class="description">{{ props.row.description }}</div>
+                  </b-table-column>
+                  <b-table-column
+                    field="difficulty"
+                    label="题目难度"
+                    sortable
+                    centered
+                  >
+                    <span
+                      :class="[
+                        'tag',
+                        `${getDifficultyClass(props.row.difficulty)}`
+                      ]"
+                    >
+                      {{ getDifficultyType(props.row.difficulty) }}
+                    </span>
+                  </b-table-column>
 
-                <b-table-column
-                  label="平均时长"
-                  field="expectTime"
-                  sortable
-                  centered
-                >
-                  {{ props.row.expectTime }}分钟
-                </b-table-column>
-              </template>
-              <template slot="detail" slot-scope="props">
-                <div>{{ props.row.description }}</div>
-              </template>
-            </b-table>
+                  <b-table-column
+                    label="平均时长"
+                    field="expectTime"
+                    sortable
+                    centered
+                  >
+                    {{ props.row.expectTime }}分钟
+                  </b-table-column>
+                </template>
+                <template slot="detail" slot-scope="props">
+                  <div>{{ props.row.description }}</div>
+                </template>
+              </b-table>
+            </div>
           </div>
         </div>
+        <div class="card" v-else>
+          没有查找到题目,请<a @click="reResearch()">重新查找</a>
+        </div>
       </div>
     </b-modal>
   </div>
 </template>
 
 <script>
-import { getTeacherKeywordQuestions } from "@/api/question";
+import { getTeacherKeywordQuestionsByCode } from "@/api/question";
+import { getTeacherKeywordQuestionsByDoc } from "@/api/question";
 import { postDocAssignment } from "@/api/assignment";
 import { postCodeAssignment } from "@/api/assignment";
 import timeMixins from "@/mixins/time";
@@ -175,7 +182,7 @@ import { difficultyClass } from "@/data/questionDifficulty";
 export default {
   props: {
     courseId: {
-      type: String
+      type: Number
     },
     assignType: {
       type: Number
@@ -205,9 +212,13 @@ export default {
       problemName: "",
       searchContent: "",
       questions: [],
+      isAnyQuestion: false,
+
+      // 控制modal活动
       isCreateModalActive: false,
       isSearchModalActive: false,
       isProblemListModalActive: false,
+
       startAtConfig: {
         ...this.getDefaultConfig(),
         minDate: "today",
@@ -221,6 +232,8 @@ export default {
       errorMassage: null,
       isServerError: false,
       submitting: false,
+
+      // 转圈圈
       loadingSearchQuestions: false,
       loadingCreateAssignment: false
     };
@@ -244,11 +257,20 @@ export default {
         this.loadingSearchQuestions = true;
         this.submitting = true;
         try {
-          getTeacherKeywordQuestions(content).then(res => {
+          let questionPromise = null;
+          if (this.assignType === 1) {
+            questionPromise = getTeacherKeywordQuestionsByDoc(content);
+          } else {
+            questionPromise = getTeacherKeywordQuestionsByCode(content);
+          }
+          questionPromise.then(res => {
             this.questions = res.data;
             this.isSearchModalActive = false;
             this.loadingSearchQuestions = false;
             this.isProblemListModalActive = true;
+            if (this.questions.length !== 0) {
+              this.isAnyQuestion = true;
+            }
           });
         } catch (e) {
           this.errorMassage = e.message;
@@ -264,6 +286,15 @@ export default {
       this.isProblemListModalActive = false;
       this.isCreateModalActive = true;
     },
+
+    /**
+     * 重新查找
+     */
+    reResearch() {
+      this.isProblemListModalActive = false;
+      this.isSearchModalActive = true;
+    },
+
     /**
      * 确认新建
      */
@@ -274,31 +305,30 @@ export default {
         this.loadingCreateAssignment = true;
         this.submitting = true;
         this.params["type"] = this.postParam;
-        this.parseParams();
+        // this.parseParams();
+        const param = {
+          ...this.params,
+          startAt: parseInt(this.params.startAt),
+          endAt: parseInt(this.params.endAt)
+        };
         if (this.assignType === 1) {
           // 文档作业
-          this.myPromise = postDocAssignment(
-            this.params,
-            parseInt(this.courseId)
-          );
+          this.myPromise = postDocAssignment(param, this.courseId);
         } else {
           // 代码作业
-          this.myPromise = postCodeAssignment(
-            this.params,
-            parseInt(this.courseId)
-          );
+          this.myPromise = postCodeAssignment(param, this.courseId);
         }
         this.myPromise.then(res => {
+          //创建成功
+          this.isCreateModalActive = false;
           if (res.code === 0) {
-            //创建成功
-            this.$emit("sendCreatedAssignment", res.data);
-            this.isCreateModalActive = false;
-            this.loadingCreateAssignment = false;
             if (this.assignType === 1) {
               this.$router.push({
                 path: `${this.prefix}/review?documentHwId=${res.data.id}`
               });
             }
+            this.$emit("sendCreatedAssignment", res.data);
+            this.loadingCreateAssignment = false;
           } else {
             //创建失败
             this.isCreateModalActive = false;
@@ -319,10 +349,6 @@ export default {
     },
     getDifficultyClass(level) {
       return difficultyClass(level);
-    },
-    parseParams() {
-      this.params.startAt = parseInt(this.params.startAt);
-      this.params.endAt = parseInt(this.params.endAt);
     }
   },
   mixins: [timeMixins, pathMixins],

+ 3 - 13
src/components/AssignmentCRUD/DeleteAssignment/index.vue

@@ -1,12 +1,5 @@
 <template>
-  <div>
-    <a
-      style="color:#b34141"
-      :class="{ 'is-loading': loadingDelAssignment }"
-      @click="confirmDelete()"
-      >删除</a
-    >
-  </div>
+  <div><a style="color:#827b7b">删除</a></div>
 </template>
 
 <script>
@@ -25,17 +18,15 @@ export default {
   data() {
     return {
       myPromise: null,
-      submitting: false,
-      loadingDelAssignment: false
+      submitting: false
     };
   },
   methods: {
     confirmDelete() {
       if (this.submitting) return;
-      this.loadingDelAssignment = true;
       this.submitting = true;
       this.$dialog.confirm({
-        message: "确认删除?",
+        message: `确认删除?`,
         confirmText: "删除",
         type: "is-danger",
         onConfirm: () => {
@@ -56,7 +47,6 @@ export default {
                 hasIcon: true
               });
             }
-            this.loadingDelAssignment = false;
           });
         }
       });

+ 17 - 18
src/components/AssignmentCRUD/UpdateAssignment/index.vue

@@ -27,17 +27,11 @@
               v-model="$v.params.description.$model"
             ></b-input>
           </b-field>
-          <b-field
-            label="开始时间"
-            :type="$v.params.startAt.$error ? 'is-danger' : ''"
-            :message="$v.params.startAt.$error ? '请填写作业开始时间' : ''"
-          >
-            <flat-pickr
-              placeholder="Click to select..."
-              class="input"
-              v-model="$v.params.startAt.$model"
-              :config="startAtConfig"
-            ></flat-pickr>
+          <b-field label="开始时间">
+            <b-input
+              disabled
+              :value="displayUnixTime(params.startAt)"
+            ></b-input>
           </b-field>
           <b-field
             label="结束时间"
@@ -69,7 +63,9 @@ import timeMixins from "@/mixins/time";
 import { putDocAssignment } from "@/api/assignment";
 import { putCodeAssignment } from "@/api/assignment";
 import { required } from "vuelidate/lib/validators";
+import BInput from "buefy/src/components/input/Input";
 export default {
+  components: { BInput },
   props: {
     updateItem: {
       type: Object
@@ -119,6 +115,8 @@ export default {
       this.isUpdateModalActive = true;
       this.params.name = this.updateItem.name;
       this.params.description = this.updateItem.description;
+      this.params.startAt = this.updateItem.startAt;
+      this.params.endAt = this.updateItem.endAt;
     },
     /**
      * 确认修改
@@ -130,11 +128,16 @@ export default {
         this.loadingUpdateAssignment = true;
         this.submitting = true;
         this.isUpdateModalActive = false;
-        this.parseParams();
+        // this.parseParams();
+        const param = {
+          ...this.params,
+          startAt: parseInt(this.params.startAt),
+          endAt: parseInt(this.params.endAt)
+        };
         if (this.assignType === 1) {
-          this.myPromise = putDocAssignment(this.params, this.updateItem.id);
+          this.myPromise = putDocAssignment(param, this.updateItem.id);
         } else {
-          this.myPromise = putCodeAssignment(this.params, this.updateItem.id);
+          this.myPromise = putCodeAssignment(param, this.updateItem.id);
         }
         this.myPromise.then(res => {
           if (res.code === 0) {
@@ -152,10 +155,6 @@ export default {
         });
         this.submitting = true;
       }
-    },
-    parseParams() {
-      this.params.startAt = parseInt(this.params.startAt);
-      this.params.endAt = parseInt(this.params.endAt);
     }
   },
   validations: {

+ 22 - 2
src/components/CheckList/index.vue

@@ -11,7 +11,7 @@
           class="checkbox-section"
           v-model="item.level"
           :type="item.state === 'SOLVING' ? 'is-primary' : 'is-success'"
-          :disabled="item.state === 'PENDING' || item.state === 'ARGUED'"
+          :disabled="judgeDisable(item)"
         >
           <b-tooltip :label="item.explain" position="is-top">
             {{ item.content }}
@@ -37,7 +37,7 @@
           'input-custom--warning': item.state === 'PENDING',
           'input-custom--end': item.state === 'ARGUED'
         }"
-        :disabled="item.state === 'PENDING' || item.state === 'ARGUED'"
+        :disabled="judgeDisable(item)"
         type="text"
         placeholder="填写备注"
         style="box-shadow:none;border-color:white"
@@ -83,6 +83,16 @@ export default {
     value: {
       type: Array,
       default: () => []
+    },
+    review: {
+      type: Object,
+      default: () => ({
+        state: "DEFAULT"
+      })
+    },
+    isTeacher: {
+      type: Boolean,
+      default: false
     }
   },
   data() {
@@ -101,6 +111,16 @@ export default {
     }
   },
   methods: {
+    judgeDisable(item) {
+      if (this.isTeacher) {
+        return false;
+      }
+      return (
+        item.state === "PENDING" ||
+        item.state === "ARGUED" ||
+        this.review.state === "ARGUING"
+      );
+    },
     async acceptArgue(item) {
       this.acceptArgueLoading = true;
       await this.$emit("accept-argue", item);

+ 21 - 0
src/components/CodeStateTag/index.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="tag" :class="tagDetail.styledClass">{{ tagDetail.name }}</div>
+</template>
+
+<script>
+import getCodeState from "../../data/codeState";
+export default {
+  name: "DocumentStateTag",
+  props: {
+    state: {
+      type: String,
+      default: "已失效"
+    }
+  },
+  computed: {
+    tagDetail() {
+      return getCodeState(this.state);
+    }
+  }
+};
+</script>

+ 1 - 1
src/components/DocumentStateTag/index.vue

@@ -9,7 +9,7 @@ export default {
   props: {
     state: {
       type: String,
-      default: "已失效"
+      default: "无状态"
     }
   },
   computed: {

+ 226 - 0
src/components/NoDataSvg.vue

@@ -0,0 +1,226 @@
+<template>
+  <!--当没有数据时页面展示-->
+  <div class="no-data">
+    <svg
+      id="f20e0c25-d928-42cc-98d1-13cc230663ea"
+      data-name="Layer 1"
+      xmlns="http://www.w3.org/2000/svg"
+      xmlns:xlink="http://www.w3.org/1999/xlink"
+      width="620.16"
+      height="500.81"
+      viewBox="0 0 820.16 780.81"
+    >
+      <defs>
+        <linearGradient
+          id="07332201-7176-49c2-9908-6dc4a39c4716"
+          x1="539.63"
+          y1="734.6"
+          x2="539.63"
+          y2="151.19"
+          gradientTransform="translate(-3.62 1.57)"
+          gradientUnits="userSpaceOnUse"
+        >
+          <stop offset="0" stop-color="gray" stop-opacity="0.25" />
+          <stop offset="0.54" stop-color="gray" stop-opacity="0.12" />
+          <stop offset="1" stop-color="gray" stop-opacity="0.1" />
+        </linearGradient>
+        <linearGradient
+          id="0ee1ab3f-7ba2-4205-9d4a-9606ad702253"
+          x1="540.17"
+          y1="180.2"
+          x2="540.17"
+          y2="130.75"
+          gradientTransform="translate(-63.92 7.85)"
+          xlink:href="#07332201-7176-49c2-9908-6dc4a39c4716"
+        />
+        <linearGradient
+          id="abca9755-bed1-4a97-b027-7f02ee3ffa09"
+          x1="540.17"
+          y1="140.86"
+          x2="540.17"
+          y2="82.43"
+          gradientTransform="translate(-84.51 124.6) rotate(-12.11)"
+          xlink:href="#07332201-7176-49c2-9908-6dc4a39c4716"
+        />
+        <linearGradient
+          id="2632d424-e666-4ee4-9508-a494957e14ab"
+          x1="476.4"
+          y1="710.53"
+          x2="476.4"
+          y2="127.12"
+          gradientTransform="matrix(1, 0, 0, 1, 0, 0)"
+          xlink:href="#07332201-7176-49c2-9908-6dc4a39c4716"
+        />
+        <linearGradient
+          id="97571ef7-1c83-4e06-b701-c2e47e77dca3"
+          x1="476.94"
+          y1="156.13"
+          x2="476.94"
+          y2="106.68"
+          gradientTransform="matrix(1, 0, 0, 1, 0, 0)"
+          xlink:href="#07332201-7176-49c2-9908-6dc4a39c4716"
+        />
+        <linearGradient
+          id="7d32e13e-a0c7-49c4-af0e-066a2f8cb76e"
+          x1="666.86"
+          y1="176.39"
+          x2="666.86"
+          y2="117.95"
+          gradientTransform="matrix(1, 0, 0, 1, 0, 0)"
+          xlink:href="#07332201-7176-49c2-9908-6dc4a39c4716"
+        />
+      </defs>
+      <title>no data</title>
+      <rect
+        x="317.5"
+        y="142.55"
+        width="437.02"
+        height="603.82"
+        transform="translate(-271.22 62.72) rotate(-12.11)"
+        fill="#e0e0e0"
+      />
+      <g opacity="0.5">
+        <rect
+          x="324.89"
+          y="152.76"
+          width="422.25"
+          height="583.41"
+          transform="translate(-271.22 62.72) rotate(-12.11)"
+          fill="url(#07332201-7176-49c2-9908-6dc4a39c4716)"
+        />
+      </g>
+      <rect
+        x="329.81"
+        y="157.1"
+        width="411.5"
+        height="570.52"
+        transform="translate(-270.79 62.58) rotate(-12.11)"
+        fill="#fafafa"
+      />
+      <rect
+        x="374.18"
+        y="138.6"
+        width="204.14"
+        height="49.45"
+        transform="translate(-213.58 43.93) rotate(-12.11)"
+        fill="url(#0ee1ab3f-7ba2-4205-9d4a-9606ad702253)"
+      />
+      <path
+        d="M460.93,91.9c-15.41,3.31-25.16,18.78-21.77,34.55s18.62,25.89,34,22.58,25.16-18.78,21.77-34.55S476.34,88.59,460.93,91.9ZM470.6,137A16.86,16.86,0,1,1,483.16,117,16.66,16.66,0,0,1,470.6,137Z"
+        transform="translate(-189.92 -59.59)"
+        fill="url(#abca9755-bed1-4a97-b027-7f02ee3ffa09)"
+      />
+      <rect
+        x="375.66"
+        y="136.55"
+        width="199.84"
+        height="47.27"
+        transform="translate(-212.94 43.72) rotate(-12.11)"
+        fill="#6c63ff"
+      />
+      <path
+        d="M460.93,91.9a27.93,27.93,0,1,0,33.17,21.45A27.93,27.93,0,0,0,460.93,91.9ZM470.17,135a16.12,16.12,0,1,1,12.38-19.14A16.12,16.12,0,0,1,470.17,135Z"
+        transform="translate(-189.92 -59.59)"
+        fill="#6c63ff"
+      />
+      <rect
+        x="257.89"
+        y="116.91"
+        width="437.02"
+        height="603.82"
+        fill="#e0e0e0"
+      />
+      <g opacity="0.5">
+        <rect
+          x="265.28"
+          y="127.12"
+          width="422.25"
+          height="583.41"
+          fill="url(#2632d424-e666-4ee4-9508-a494957e14ab)"
+        />
+      </g>
+      <rect x="270.65" y="131.42" width="411.5" height="570.52" fill="#fff" />
+      <rect
+        x="374.87"
+        y="106.68"
+        width="204.14"
+        height="49.45"
+        fill="url(#97571ef7-1c83-4e06-b701-c2e47e77dca3)"
+      />
+      <path
+        d="M666.86,118c-15.76,0-28.54,13.08-28.54,29.22s12.78,29.22,28.54,29.22,28.54-13.08,28.54-29.22S682.62,118,666.86,118Zm0,46.08a16.86,16.86,0,1,1,16.46-16.86A16.66,16.66,0,0,1,666.86,164Z"
+        transform="translate(-189.92 -59.59)"
+        fill="url(#7d32e13e-a0c7-49c4-af0e-066a2f8cb76e)"
+      />
+      <rect
+        x="377.02"
+        y="104.56"
+        width="199.84"
+        height="47.27"
+        fill="#6c63ff"
+      />
+      <path
+        d="M666.86,118a27.93,27.93,0,1,0,27.93,27.93A27.93,27.93,0,0,0,666.86,118Zm0,44.05A16.12,16.12,0,1,1,683,145.89,16.12,16.12,0,0,1,666.86,162Z"
+        transform="translate(-189.92 -59.59)"
+        fill="#6c63ff"
+      />
+      <g opacity="0.5">
+        <rect x="15.27" y="737.05" width="3.76" height="21.33" fill="#47e6b1" />
+        <rect
+          x="205.19"
+          y="796.65"
+          width="3.76"
+          height="21.33"
+          transform="translate(824.47 540.65) rotate(90)"
+          fill="#47e6b1"
+        />
+      </g>
+      <g opacity="0.5">
+        <rect x="451.49" width="3.76" height="21.33" fill="#47e6b1" />
+        <rect
+          x="641.4"
+          y="59.59"
+          width="3.76"
+          height="21.33"
+          transform="translate(523.63 -632.62) rotate(90)"
+          fill="#47e6b1"
+        />
+      </g>
+      <path
+        d="M961,832.15a4.61,4.61,0,0,1-2.57-5.57,2.22,2.22,0,0,0,.1-.51h0a2.31,2.31,0,0,0-4.15-1.53h0a2.22,2.22,0,0,0-.26.45,4.61,4.61,0,0,1-5.57,2.57,2.22,2.22,0,0,0-.51-.1h0a2.31,2.31,0,0,0-1.53,4.15h0a2.22,2.22,0,0,0,.45.26,4.61,4.61,0,0,1,2.57,5.57,2.22,2.22,0,0,0-.1.51h0a2.31,2.31,0,0,0,4.15,1.53h0a2.22,2.22,0,0,0,.26-.45,4.61,4.61,0,0,1,5.57-2.57,2.22,2.22,0,0,0,.51.1h0a2.31,2.31,0,0,0,1.53-4.15h0A2.22,2.22,0,0,0,961,832.15Z"
+        transform="translate(-189.92 -59.59)"
+        fill="#4d8af0"
+        opacity="0.5"
+      />
+      <path
+        d="M326.59,627.09a4.61,4.61,0,0,1-2.57-5.57,2.22,2.22,0,0,0,.1-.51h0a2.31,2.31,0,0,0-4.15-1.53h0a2.22,2.22,0,0,0-.26.45,4.61,4.61,0,0,1-5.57,2.57,2.22,2.22,0,0,0-.51-.1h0a2.31,2.31,0,0,0-1.53,4.15h0a2.22,2.22,0,0,0,.45.26,4.61,4.61,0,0,1,2.57,5.57,2.22,2.22,0,0,0-.1.51h0a2.31,2.31,0,0,0,4.15,1.53h0a2.22,2.22,0,0,0,.26-.45A4.61,4.61,0,0,1,325,631.4a2.22,2.22,0,0,0,.51.1h0a2.31,2.31,0,0,0,1.53-4.15h0A2.22,2.22,0,0,0,326.59,627.09Z"
+        transform="translate(-189.92 -59.59)"
+        fill="#fdd835"
+        opacity="0.5"
+      />
+      <path
+        d="M855,127.77a4.61,4.61,0,0,1-2.57-5.57,2.22,2.22,0,0,0,.1-.51h0a2.31,2.31,0,0,0-4.15-1.53h0a2.22,2.22,0,0,0-.26.45,4.61,4.61,0,0,1-5.57,2.57,2.22,2.22,0,0,0-.51-.1h0a2.31,2.31,0,0,0-1.53,4.15h0a2.22,2.22,0,0,0,.45.26,4.61,4.61,0,0,1,2.57,5.57,2.22,2.22,0,0,0-.1.51h0a2.31,2.31,0,0,0,4.15,1.53h0a2.22,2.22,0,0,0,.26-.45,4.61,4.61,0,0,1,5.57-2.57,2.22,2.22,0,0,0,.51.1h0a2.31,2.31,0,0,0,1.53-4.15h0A2.22,2.22,0,0,0,855,127.77Z"
+        transform="translate(-189.92 -59.59)"
+        fill="#fdd835"
+        opacity="0.5"
+      />
+      <circle cx="812.64" cy="314.47" r="7.53" fill="#f55f44" opacity="0.5" />
+      <circle cx="230.73" cy="746.65" r="7.53" fill="#f55f44" opacity="0.5" />
+      <circle cx="735.31" cy="477.23" r="7.53" fill="#f55f44" opacity="0.5" />
+      <circle cx="87.14" cy="96.35" r="7.53" fill="#4d8af0" opacity="0.5" />
+      <circle cx="7.53" cy="301.76" r="7.53" fill="#47e6b1" opacity="0.5" />
+    </svg>
+    <span>暂无相关数据</span>
+  </div>
+</template>
+<style lang="scss" scoped>
+.no-data {
+  display: flex;
+  align-items: center;
+  span {
+    font-size: 2rem;
+    font-weight: bold;
+    color: #6c63ff;
+  }
+}
+</style>

+ 23 - 0
src/data/codeState.js

@@ -0,0 +1,23 @@
+export const DEFAULT = "DEFAULT";
+export const PREPARING = "PREPARING";
+export const UNAVAILABLE = "UNAVAILABLE";
+export const READY = "READY";
+export const AVAILABLE = "AVAILABLE";
+export const FINISHED = "FINISHED";
+
+export default function(state) {
+  switch (state) {
+    case PREPARING:
+      return { name: "准备中", styledClass: "is-info" };
+    case UNAVAILABLE:
+      return { name: "创建失败", styledClass: "is-warning" };
+    case READY:
+      return { name: "未开始", styledClass: "is-light" };
+    case AVAILABLE:
+      return { name: "正在进行", styledClass: "is-success" };
+    case FINISHED:
+      return { name: "已结束", styledClass: "is-danger" };
+    default:
+      return { name: "无状态", styledClass: "is-dark" };
+  }
+}

+ 0 - 3
src/layouts/LoginLayout.vue

@@ -11,9 +11,6 @@
         </div>
       </div>
     </section>
-    <!--<footer class="footer is-transparent" style="background: #f4f7fb">-->
-    <!--<div class="content has-text-centered">@nju software</div>-->
-    <!--</footer>-->
   </section>
 </template>
 

+ 30 - 3
src/router/routes.js

@@ -85,8 +85,35 @@ export default [
         component: () => import("@/views/teacher/Code/detail")
       },
       {
-        path: "code/:codeId/test",
-        component: () => import("@/views/teacher/Code/test")
+        path: "code/:homeworkId/:projectId",
+        component: () => import("@/views/teacher/Code/test"),
+        children: [
+          {
+            path: "",
+            component: () =>
+              import("@/views/teacher/Code/ProjectDetail/BuildDetail")
+          },
+          {
+            path: "deploy",
+            component: () =>
+              import("@/views/teacher/Code/ProjectDetail/DeployDetail")
+          },
+          {
+            path: "test",
+            component: () =>
+              import("@/views/teacher/Code/ProjectDetail/TestDetail")
+          }
+        ]
+      },
+      {
+        path: "code/:homeworkId/:projectId/test/unit/:unitTestId",
+        component: () =>
+          import("@/views/student/Code/ProjectDetail/Test/UnitTestDetail")
+      },
+      {
+        path: "code/:homeworkId/:projectId/test/functional/:functionalTestId",
+        component: () =>
+          import("@/views/student/Code/ProjectDetail/Test/FunctionalTestDetail")
       },
 
       {
@@ -98,7 +125,7 @@ export default [
         component: () => import("@/views/teacher/Document/detail")
       },
       {
-        path: "document/:documentId/content",
+        path: "document/:documentId/content/:groupWorkId",
         component: () => import("@/views/teacher/Document/docContent")
       },
       {

+ 69 - 0
src/util/exportExcel.js

@@ -0,0 +1,69 @@
+import XLSX from "xlsx";
+import { saveAs } from "file-saver";
+
+/**
+ * fn 字符串转字符流
+ * @param s
+ * @returns {ArrayBuffer}
+ */
+function s2ab(s) {
+  let buf = new ArrayBuffer(s.length);
+  let view = new Uint8Array(buf);
+  for (let i = 0; i !== s.length; ++i) {
+    view[i] = s.charCodeAt(i) & 0xff;
+  }
+  return buf;
+}
+
+function data2ws(data) {
+  const ws = {};
+  const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
+  for (let R = 0; R !== data.length; ++R) {
+    for (let C = 0; C !== data[R].length; ++C) {
+      if (range.s.r > R) range.s.r = R;
+      if (range.s.c > C) range.s.c = C;
+      if (range.e.r < R) range.e.r = R;
+      if (range.e.c < C) range.e.c = C;
+      const cell = { v: data[R][C] };
+      if (cell.v == null) continue;
+      const cellRef = XLSX.utils.encode_cell({ c: C, r: R });
+      if (typeof cell.v === "number") cell.t = "n";
+      else if (typeof cell.v === "boolean") {
+        cell.t = "b";
+      } else {
+        cell.t = "s";
+      }
+      ws[cellRef] = cell;
+    }
+  }
+  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
+  return ws;
+}
+
+function Workbook() {
+  if (!(this instanceof Workbook)) {
+    return new Workbook();
+  }
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+
+export const toExcel = ({ th, data, fileName, fileType, sheetName }) => {
+  data.unshift(th);
+  const wb = new Workbook();
+  const ws = data2ws(data);
+  sheetName = sheetName || "sheet1";
+  wb.SheetNames.push(sheetName);
+  wb.Sheets[sheetName] = ws;
+  fileType = fileType || "xlsx";
+  var wbout = XLSX.write(wb, {
+    bookType: fileType,
+    bookSST: false,
+    type: "binary"
+  });
+  fileName = fileName || "列表";
+  saveAs(
+    new Blob([s2ab(wbout)], { type: "application/octet-stream" }),
+    `${fileName}.${fileType}`
+  );
+};

+ 3 - 3
src/views/login/Login.vue

@@ -36,7 +36,7 @@
         class="button login"
         @click="loginAction"
       >
-        登 
+        登 
       </button>
     </div>
     <div>
@@ -65,7 +65,7 @@ export default {
       username: "",
       password: "",
       submitting: false,
-      errorMassage: null,
+      errorMassage: "登录失败",
       isServerError: false
     };
   },
@@ -99,7 +99,7 @@ export default {
           this.$store.commit(SET_PROFILE, data);
           this.$router.push({ name: HOME });
         } catch (e) {
-          this.errorMassage = e.message;
+          this.errorMassage = e.message || "登录失败";
           this.isServerError = true;
           this.submitting = false;
         }

+ 1 - 1
src/views/login/Register.vue

@@ -82,7 +82,7 @@
       :class="{ 'is-text': true }"
       class="button login"
       type="submit"
-      >已有账户,登
+      >已有账户,登
     </router-link>
   </div>
 </template>

+ 55 - 4
src/views/student/Code/ProjectDetail/BuildDetail.vue

@@ -2,8 +2,16 @@
   <div>
     <div v-if="detailLoading"><page-section-loading show /></div>
     <div v-else>
+      <!--空数据处理-->
       <div
-        v-for="item in projectDetail.buildList"
+        class="page-section"
+        v-if="!(projectDetail.buildList && projectDetail.buildList.length > 0)"
+        style="height: 60vh;"
+      >
+        <no-data-svg></no-data-svg>
+      </div>
+      <div
+        v-for="(item, index) in projectDetail.buildList"
         :key="item.id"
         class="gap-section"
       >
@@ -12,14 +20,32 @@
             <div class="level-left">
               <build-tag :build-state="item.buildState" />
               <strong style="padding-left: 10px">
-                {{ item.commitMessage }} · #{{ item.branchId }}
+                {{ item.commitMessage }} · #构建ID:{{ item.id }}
+              </strong>
+            </div>
+            <div class="level-right">
+              <strong>完成时间:{{ item.buildTime }}</strong>
+            </div>
+          </div>
+          <div class="level">
+            <div class="level-left">
+              <strong style="padding-left: 10px">
+                #branchID:{{ item.branchId }}
               </strong>
             </div>
             <div v-if="item.buildState === 1" class="level-right">
               <strong>mirror : {{ item.mirrorId }}</strong>
             </div>
           </div>
-          <div class="code-section">{{ item.buildMessage }}</div>
+          <a
+            class="button is-info is-small"
+            @click="changeBuildMessageStatus(index)"
+            aria-controls="contentIdForA11y2"
+            >查看详细编译信息</a
+          >
+          <div class="code-section" v-show="buildMessageIsOpen[index]">
+            {{ item.buildMessage }}
+          </div>
         </div>
       </div>
     </div>
@@ -29,9 +55,10 @@
 <script>
 import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 import BuildTag from "@/views/student/Code/ProjectDetail/components/BuildTag";
+import NoDataSvg from "@/components/NoDataSvg";
 export default {
   name: "BuildDetail",
-  components: { BuildTag, PageSectionLoading },
+  components: { BuildTag, PageSectionLoading, NoDataSvg },
   props: {
     projectDetail: {
       type: Object,
@@ -41,6 +68,30 @@ export default {
       type: Boolean,
       default: false
     }
+  },
+  computed: {},
+  watch: {
+    projectDetail: function(newVal) {
+      let buildMessageIsOpen = [];
+      for (let item of newVal.buildList) {
+        //处理buildTime格式
+        item.buildTime = item.buildTime.replace("T", " ") + "ms";
+        buildMessageIsOpen.push(false);
+      }
+      this.buildMessageIsOpen = buildMessageIsOpen;
+    }
+  },
+  data() {
+    return {
+      buildMessageIsOpen: []
+    };
+  },
+  mounted() {},
+  methods: {
+    changeBuildMessageStatus(index) {
+      let status = !this.buildMessageIsOpen[index];
+      this.buildMessageIsOpen.splice(index, 1, status);
+    }
   }
 };
 </script>

+ 259 - 22
src/views/student/Code/ProjectDetail/DeployDetail.vue

@@ -1,67 +1,301 @@
 <template>
   <div class="data-table">
     <section>
+      <div class="mirror-subtitle" v-if="deployStatus != 'RUNNING'">
+        选择镜像
+      </div>
+      <div class="select-mirror" v-if="deployStatus != 'RUNNING'">
+        <div class="latest-build">
+          <div class="test-item" :key="item.id" v-for="item in latestBuildList">
+            <div>
+              <b-radio v-model="radio" :native-value="`b${item.id}`"> </b-radio>
+              <strong>构建ID: #</strong> {{ item.id }}
+              <span style="padding-left: 10px">
+                <strong>镜像ID: #</strong> {{ item.mirrorId }}
+              </span>
+            </div>
+          </div>
+          <div
+            class="test-item"
+            :key="item.id"
+            v-for="item in latestDeployList"
+          >
+            <div>
+              <b-radio v-model="radio" :native-value="`d${item.id}`"> </b-radio>
+              <strong>构建ID: #</strong> {{ item.id }}
+              <span style="padding-left: 10px">
+                <strong>镜像ID: #</strong> {{ item.mirrorId }}
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
       <button
-        :class="['button', 'block', 'is-link', { 'is-loading': isActive }]"
-        @click="isActive = !isActive"
+        :class="['button', 'block', 'is-link', { 'is-loading': isRunning }]"
+        @click="startDeploy"
+        style="margin-top: 20px;"
       >
         开始部署
       </button>
       <b-message
         title="部署进行中..."
-        :active.sync="isActive"
+        :active.sync="isRunning"
         type="is-warning"
       >
         部署已经开始,一次部署需要大约5-10分钟,请5-10分钟后刷新页面获取部署结果
       </b-message>
     </section>
     <!--部署记录-->
-    <div class="deploy-list">
-      <div class="subtitle">部署结果</div>
+    <div class="deploy-list" v-if="deployInfo">
+      <div class="subtitle">
+        {{ isRunning ? "当前部署情况" : "上次部署结果" }}
+      </div>
       <div class="deploy-item">本次部署ID: #{{ deployInfo.id }}</div>
-      <div class="deploy-item">本次部署时间: 2019-01-14 15:05</div>
       <div class="deploy-item">
-        部署结果:
-        <div class="tag is-success">部署成功</div>
+        本次部署创建时间: {{ handleTime(deployInfo.createdAt) }}
       </div>
-      <div class="deploy-item">
-        <div class="subtitle">项目部署地址</div>
-        <a :href="`http://${deployInfo.pageUrl}`" target="_blank">查看详情</a>
+      <div class="deploy-item" v-if="deployInfo.endAt">
+        本次部署结束时间: {{ handleTime(deployInfo.endAt) }}
       </div>
       <div class="deploy-item">
-        <div class="subtitle">配置信息</div>
-        <div>暂无项目配置信息</div>
+        部署结果:
+        <div :class="['tag', type(deployInfo.status)]">
+          {{ deployInfo.status }}
+        </div>
       </div>
+      <div class="subtitle">项目部署地址</div>
+      <div class="deploy-item">{{ deployInfo.url }}</div>
       <div class="deploy-item">
         <div class="subtitle">部署输出信息</div>
-        <div class="code-section">
-          <div class="code-content">{{ deployInfo.deployMessage }}</div>
+        <a
+          class="button is-small is-info"
+          @click="unfoldDeployLogInfo = !unfoldDeployLogInfo"
+        >
+          {{ unfoldDeployLogInfo ? "收起部署详细信息" : "查看部署详细信息" }}</a
+        >
+        <div class="code-section" v-show="unfoldDeployLogInfo">
+          <div class="code-content" v-html="deployLog"></div>
         </div>
-        <!--<div class="code-section">{{deployInfo.deployMessage}}</div>-->
       </div>
     </div>
   </div>
 </template>
 <script>
-import { getDeployDetail } from "@/api/codehw";
+import {
+  getDeployDetail,
+  getDeployLogInfo,
+  getLatestBuildInfoList,
+  makeDeploy,
+  getLatestDeployInfoList
+} from "@/api/codehw";
 
 export default {
   data() {
     return {
       isActive: false,
-      deployInfo: {}
+      isRunning: false,
+      deployStatus: "",
+      deployInfo: {},
+      haveDeployRecord: false,
+      deployLog: "",
+      unfoldDeployLogInfo: false,
+      latestBuildList: [],
+      latestDeployList: [],
+      latestMirrorIdList: [],
+      radio: -1
     };
   },
+  props: {
+    projectDetail: {
+      type: Object,
+      default: () => {}
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    projectDetail: function(newVal) {
+      console.log(newVal);
+      //获取最新一次部署详细信息
+      if (newVal.latestDeploy && newVal.latestDeploy.id) {
+        this.haveDeployRecord = true;
+        this.getLatestDeployInfo(newVal.latestDeploy.id);
+        // this.getLatestDeployInfo(17);
+        this.getDeployLogInfo(this.projectDetail.latestDeploy.id);
+      }
+      this.getLatestBuildInfo(this.projectDetail && this.projectDetail.id, 5);
+      this.getLastNDeployInfoList(
+        this.projectDetail && this.projectDetail.id,
+        5
+      );
+    }
+  },
   mounted() {
-    getDeployDetail(123).then(res => {
-      console.log(res.data);
-      this.deployInfo = res.data;
-    });
+    if (this.projectDetail.latestDeploy) {
+      this.getLatestDeployInfo(this.projectDetail.latestDeploy.id);
+      this.getDeployLogInfo(this.projectDetail.latestDeploy.id);
+    }
+    if (this.projectDetail.id) {
+      this.getLatestBuildInfo(this.projectDetail.id, 5);
+      this.getLastNDeployInfoList(this.projectDetail.id, 5);
+    }
+  },
+  methods: {
+    //获取最新一次部署详细信息
+    getLatestDeployInfo(deployId) {
+      getDeployDetail(deployId)
+        .then(res => {
+          this.deployInfo = res.data;
+          this.deployStatus = res.data.status;
+          let isRunning = this.deployStatus == "RUNNING" ? true : false;
+          this.isRunning = isRunning;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || "服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //处理时间格式
+    handleTime(time) {
+      if (!time) {
+        return "";
+      }
+      return time.replace("T", " ") + "ms";
+    },
+    //开始部署
+    startDeploy() {
+      if (this.radio == -1) {
+        this.$toast.open({
+          message: "请先选择镜像",
+          type: "is-danger"
+        });
+        return;
+      } else {
+        let mirrorId = this.getSelectedMirrorId();
+        //触发部署
+        makeDeploy({
+          projectId: this.projectDetail.id,
+          mirrorId: mirrorId,
+          replicas: 1
+        })
+          .then(res => {
+            console.log(res);
+            this.$toast.open({
+              message: "开始部署",
+              type: "is-success"
+            });
+
+            this.isRunning = true;
+          })
+          .then(e => {
+            this.$toast.open({
+              message: e.message || "服务器未知错误",
+              type: "is-danger"
+            });
+          });
+      }
+    },
+    //获取最近n次部署成功记录
+    getLastNDeployInfoList(projectId, limit) {
+      getLatestDeployInfoList(projectId, limit)
+        .then(res => {
+          this.latestDeployList = res.data;
+          // this.latestMirrorIdList = this.latestMirrorIdList.concat(res.data);
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || " 服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //获取最近n个构建的信息
+    getLatestBuildInfo(projectId, limit) {
+      getLatestBuildInfoList(projectId, limit)
+        .then(res => {
+          this.latestBuildList = res.data;
+          // this.latestMirrorIdList = this.latestMirrorIdList.concat(res.data);
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || "服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //获取部署日志
+    getDeployLogInfo(deployId) {
+      getDeployLogInfo(deployId)
+        .then(res => {
+          this.deployLog = res.data;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || "服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //部署类型
+    type(value) {
+      if (value == "FAILURE") {
+        return "is-danger";
+      } else if (value == "RUNNING") {
+        return "is-warning";
+      } else if (value == "SUCCESS") {
+        return "is-success";
+      } else {
+        return "";
+      }
+    },
+    //选择镜像ID
+    getSelectedMirrorId() {
+      let list =
+        this.radio[0] == "b" ? this.latestBuildList : this.latestDeployList;
+      let id = this.radio.substring(1);
+      for (let item of list) {
+        if (item.id == id) {
+          return item.mirrorId;
+        }
+      }
+    }
   }
 };
 </script>
 <style lang="scss" scoped>
 @import "../../../../assets/scss/app";
+.mirror-subtitle {
+  font-size: 1.25rem;
+  width: 100%;
+  font-weight: bold;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #dbdbdb;
+}
+.select-mirror {
+  width: 100%;
+  display: flex;
+  .latest-build {
+    width: 100%;
+  }
+}
+.test-item {
+  width: 100%;
+  padding: $default-margin $default-margin-between;
+  margin: 0 -#{$default-margin-between};
+  cursor: pointer;
+}
+/*.test-item:hover,*/
+/*.test-item:nth-child(even):hover {*/
+/*background-color: #dde3ef;*/
+/*}*/
+
+.test-item:nth-child(even) {
+  background-color: rgba(244, 247, 252, 0.6);
+}
 .deploy-list {
   margin-top: 1.5rem;
   .deploy-item {
@@ -71,6 +305,9 @@ export default {
       margin-left: 10px;
       margin-right: 10px;
     }
+    a {
+      margin-bottom: 10px;
+    }
   }
   .subtitle {
     margin-top: 1.5rem;

+ 11 - 2
src/views/student/Code/ProjectDetail/Test/FunctionalTestDetail.vue

@@ -24,6 +24,7 @@
         <div class="tag">{{ displayUnixTime(testDetail.time) }}</div>
       </template>
     </page-header>
+    <!--<div class="console-section">{{ testDetail.consoleOutput }}</div>-->
     <page-body>
       <div v-if="testDetailLoading"><page-section-loading show /></div>
       <div v-else>
@@ -34,12 +35,14 @@
         >
           <div class="page-section">
             <div>
-              <pass-tag :pass="item.isPass" />
+              <pass-tag :pass="item.pass" />
               <strong style="padding-left: 10px">
                 页面测试用例 : #{{ item.id }}
               </strong>
             </div>
-            <div class="code-section">{{ item.message }}</div>
+            <div v-if="!item.pass" class="code-section">
+              {{ item.message }} <br />
+            </div>
           </div>
         </div>
       </div>
@@ -90,4 +93,10 @@ export default {
   background-color: #2d3044;
   color: #cacff1;
 }
+
+.console-section {
+  padding: $default-margin-between $default-margin-between * 1.5;
+  background-color: #2d3044;
+  color: #cacff1;
+}
 </style>

+ 13 - 2
src/views/student/Code/ProjectDetail/Test/UnitTestDetail.vue

@@ -24,6 +24,7 @@
         <div class="tag">{{ displayUnixTime(testDetail.time) }}</div>
       </template>
     </page-header>
+    <div class="console-section">{{ testDetail.consoleOutput }}</div>
     <page-body>
       <div v-if="testDetailLoading"><page-section-loading show /></div>
       <div v-else>
@@ -34,12 +35,16 @@
         >
           <div class="page-section">
             <div>
-              <pass-tag :pass="item.isPass" />
+              <pass-tag :pass="item.pass" />
               <strong style="padding-left: 10px">
                 页面测试用例 : #{{ item.id }}
               </strong>
             </div>
-            <div class="code-section">{{ item.message }}</div>
+            <div v-if="!item.pass" class="code-section">
+              {{ item.message }} <br />
+              <br />
+              {{ item.trace }}
+            </div>
           </div>
         </div>
       </div>
@@ -90,4 +95,10 @@ export default {
   background-color: #2d3044;
   color: #cacff1;
 }
+
+.console-section {
+  padding: $default-margin-between $default-margin-between * 1.5;
+  background-color: #2d3044;
+  color: #cacff1;
+}
 </style>

+ 25 - 22
src/views/student/Code/ProjectDetail/TestDetail.vue

@@ -4,6 +4,9 @@
       <div v-if="detailLoading"><page-section-loading show /></div>
       <div v-else class="page-section">
         <h5 class="title is-5">单元测试:</h5>
+        <div v-if="!(unitTestList && unitTestList.length > 0)">
+          <strong>当前暂无单元测试数据</strong>
+        </div>
         <router-link
           class="test-item"
           :key="item.id"
@@ -25,14 +28,17 @@
         <h5 class="test-title title is-5">
           页面测试:
           <div
-            v-if="functionTestList && functionTestList.length > 0"
             :class="{ 'is-loading': testing }"
             @click="makeTest"
+            :disabled="!(projectDetail.latestDeploy && projectDetail.latestDeploy.status == 'SUCCESS')"
             class="button is-primary test-title__action"
           >
             触发测试
           </div>
         </h5>
+        <div v-if="!(functionTestList && functionTestList.length > 0)">
+          <strong>当前暂无页面测试数据</strong>
+        </div>
         <router-link
           class="test-item"
           :key="item.id"
@@ -87,28 +93,25 @@ export default {
   mixins: [timeMixins],
   methods: {
     async makeTest() {
-      const { functionTestList } = this;
-      if (functionTestList && functionTestList.length > 0) {
-        this.testing = true;
-        const { deployId } = functionTestList[0];
-        const { homeworkId, projectId } = this.$route.params;
-        const { data } = await makeFunctionalTest({
-          homeworkId,
-          projectId,
-          deployId
+      this.testing = true;
+      const deployId = this.projectDetail.latestDeploy.id;
+      const { homeworkId, projectId } = this.$route.params;
+      const { data } = await makeFunctionalTest({
+        homeworkId,
+        projectId,
+        deployId
+      });
+      this.testing = false;
+      if (data.success) {
+        this.$toast.open({
+          message: `测试触发成功`,
+          type: "is-success"
+        });
+      } else {
+        this.$toast.open({
+          message: `测试触发失败`,
+          type: "is-danger"
         });
-        this.testing = false;
-        if (data.success) {
-          this.$toast.open({
-            message: `测试触发成功`,
-            type: "is-success"
-          });
-        } else {
-          this.$toast.open({
-            message: `测试触发失败`,
-            type: "is-danger"
-          });
-        }
       }
     },
     sortTestList(list) {

+ 2 - 1
src/views/student/Code/ProjectDetail/index.vue

@@ -31,7 +31,7 @@
       </template>
       <template slot="content">
         <strong>git 地址 : </strong>
-        <a :href="projectDetail.url">{{ projectDetail.url }}</a>
+        <a :href="projectDetail.url" target="_blank">{{ projectDetail.url }}</a>
       </template>
     </page-header>
     <page-body>
@@ -62,6 +62,7 @@ export default {
     const { projectId } = this.$route.params;
     this.detailLoading = true;
     const detail = await getProjectDetail(projectId);
+    console.log(detail.data);
     this.projectDetail = detail.data;
     this.detailLoading = false;
   }

+ 2 - 2
src/views/student/Code/ProjectList/index.vue

@@ -22,7 +22,7 @@
           v-for="item in projectList"
           class="column is-4"
         >
-          <router-link :to="`${$route.path}/${item.projectId}`">
+          <router-link :to="`${$route.path}/${item.id}`">
             <card hoverable>{{ item.name }}</card>
           </router-link>
         </div>
@@ -56,7 +56,7 @@ export default {
   },
   async mounted() {
     this.projectListLoading = true;
-    const list = await getProjectList(this.course.id);
+    const list = await getProjectList(this.$route.params.homeworkId);
     this.projectList = list.data;
     this.projectListLoading = false;
   }

+ 22 - 6
src/views/student/Code/index.vue

@@ -18,14 +18,25 @@
         </div>
         <div
           v-else
-          :key="item.homeworkId"
+          :key="item.id"
           v-for="item in codeHomeworkList"
           class="column is-4-desktop"
         >
-          <router-link :to="`${$route.path}/${item.homeworkId}`">
-            <card hoverable>{{ item.description }}</card>
+          <router-link :to="`${$route.path}/${item.id}`">
+            <card hoverable>
+              {{ item.name }}
+              <div><code-state-tag :state="item.status"></code-state-tag></div>
+            </card>
           </router-link>
         </div>
+        <!--当没有代码作业时的处理-->
+        <no-data-svg
+          style="margin-top: 20px;"
+          v-if="
+            !codeHomeworkListLoading &&
+              !(codeHomeworkList && codeHomeworkList.length > 0)
+          "
+        ></no-data-svg>
       </div>
     </page-body>
   </div>
@@ -33,15 +44,19 @@
 
 <script>
 import { mapState } from "vuex";
-import { getCodeHomeworkList } from "@/api/codehw";
 import Card from "@/components/Card";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
+import { getCodeAssignmentListByStu } from "@/api/assignment";
+import CodeStateTag from "@/components/DocumentStateTag";
+import noDataSvg from "@/components/NoDataSvg";
 export default {
   components: {
     PageBody,
     PageHeader,
-    Card
+    Card,
+    noDataSvg,
+    CodeStateTag
   },
   computed: {
     ...mapState({
@@ -56,7 +71,8 @@ export default {
   },
   async mounted() {
     this.codeHomeworkListLoading = true;
-    const list = await getCodeHomeworkList(this.course.id);
+    const { courseId } = this.$route.params;
+    const list = await getCodeAssignmentListByStu(courseId);
     this.codeHomeworkList = list.data;
     this.codeHomeworkListLoading = false;
   }

+ 11 - 17
src/views/student/Document/detail.vue

@@ -8,10 +8,10 @@
       ]"
     >
       <template slot="title">
-        {{ assignmentInfo.name }}
+        {{ detail.name }}
       </template>
       <template slot="content">
-        {{ assignmentInfo.description }}
+        {{ detail.description }}
       </template>
       <template slot="action" v-if="loadingDocResult">
         <a class="button is-primary">成绩:{{ docResult.result }}</a>
@@ -28,7 +28,9 @@
               <div class="subtitle is-6" v-if="isAvailable">
                 {{ detail.problem ? detail.problem.name : "" }}
               </div>
-              <div class="subtitle is-6" v-else>题目开始进行才可查看</div>
+              <div class="subtitle is-6" v-else>
+                <s>题目开始进行才可查看</s>
+              </div>
             </div>
             <div class="column">
               <h5 class="title is-5">状态:</h5>
@@ -82,14 +84,6 @@ import DocumentStateTag from "@/components/DocumentStateTag";
 import timeMixins from "@/mixins/time";
 import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
-const assignmentInfo = {
-  id: 0,
-  name: "",
-  description: "",
-  startAt: 0,
-  endAt: 0
-};
-
 export default {
   components: {
     PageSectionLoading,
@@ -99,25 +93,25 @@ export default {
   },
   mixins: [timeMixins],
   mounted() {
+    const { documentId } = this.$route.params;
     this.loadingDocDetail = true;
-    this.assignmentInfo = this.$route.query.assignmentInfo;
-    getDocAssignmentDetailByDocuId(this.assignmentInfo.id).then(res => {
+    getDocAssignmentDetailByDocuId(documentId).then(res => {
       this.detail = res.data;
       this.loadingDocDetail = false;
       this.isAvailable =
         this.detail.status === "AVAILABLE" || this.detail.status === "FINISHED";
     });
-    getDocResultById(this.assignmentInfo.id).then(res => {
+    getDocResultById(documentId).then(res => {
       this.docResult = res.data;
       // 有成绩
-      if (parseInt(this.docResult.result) !== -1) {
-        this.loadingDocResult = true;
+      // 修改为String
+      if (this.docResult.result !== null) {
+        this.loadingDocResult = true; //不显示
       }
     });
   },
   data() {
     return {
-      assignmentInfo,
       detail: {},
       docResult: {},
       loadingDocResult: false,

+ 10 - 14
src/views/student/Document/index.vue

@@ -22,12 +22,7 @@
           v-for="item in docList"
           class="column is-4-desktop"
         >
-          <router-link
-            :to="{
-              path: `${$route.path}/${item.id}`,
-              query: { assignmentInfo: item }
-            }"
-          >
+          <router-link :to="{ path: `${$route.path}/${item.id}` }">
             <card hoverable>
               {{ item.name }}
               <div>
@@ -36,39 +31,40 @@
             </card>
           </router-link>
         </div>
+        <no-data-svg
+          style="margin-top: 20px;"
+          v-if="!loadingDocList && !(docList && docList.length > 0)"
+        ></no-data-svg>
       </div>
     </page-body>
   </div>
 </template>
 
 <script>
-import { mapState } from "vuex";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getDocAssignmentListByCrs } from "@/api/assignment";
 import timeMixins from "@/mixins/time";
 import Card from "@/components/Card";
 import DocumentStateTag from "@/components/DocumentStateTag";
+import NoDataSvg from "@/components/NoDataSvg";
 export default {
   components: {
     PageBody,
     PageHeader,
     Card,
-    DocumentStateTag
+    DocumentStateTag,
+    NoDataSvg
   },
   mixins: [timeMixins],
   mounted() {
     this.loadingDocList = true;
-    getDocAssignmentListByCrs(this.course.id).then(res => {
+    const { courseId } = this.$route.params;
+    getDocAssignmentListByCrs(courseId).then(res => {
       this.docList = res.data;
       this.loadingDocList = false;
     });
   },
-  computed: {
-    ...mapState({
-      course: state => state.course.currentCourse
-    })
-  },
   data() {
     return {
       docList: [],

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 529 - 2
src/views/student/Home/index.vue


+ 5 - 1
src/views/student/Review/ReviewDetail/ReviewSelfDetail/index.vue

@@ -13,9 +13,13 @@
         <div class="right-part">
           <page-section-loading :show="selfDetailLoading" />
           <div v-if="!selfDetailLoading">
+            <div v-if="!!detail.level" class="notification is-success">
+              <strong>您的得分 : </strong>{{ detail.level }}
+            </div>
+
             <result-check-list-item
               v-for="item in detail.checkListResult"
-              :arguable="true"
+              :arguable="detail.review.state === 'ARGUING'"
               :key="item.id"
               :check-result-item="item"
               @argue="argue"

+ 3 - 1
src/views/student/Review/ReviewDocDetail/index.vue

@@ -39,6 +39,7 @@
                 提 交
               </div>
               <check-list
+                :review="detail.review"
                 v-model="detail.checkList"
                 @submit-solution="submitSolution"
                 @accept-argue="acceptArgue"
@@ -81,7 +82,8 @@ export default {
         checkList: []
       },
       submitLoading: false,
-      loadingDocContent: false
+      loadingDocContent: false,
+      fetchLoading: false
     };
   },
   mixins: [timeMixins],

+ 9 - 8
src/views/student/Review/index.vue

@@ -29,29 +29,29 @@
             </card>
           </router-link>
         </div>
+        <no-data-svg
+          style="margin-top: 20px;"
+          v-if="!loadingReviewList && !(review && review.length > 0)"
+        ></no-data-svg>
       </div>
     </page-body>
   </div>
 </template>
 
 <script>
-import { mapState } from "vuex";
 import { getStudentCourseReviewList } from "@/api/review";
 import Card from "@/components/Card";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import ReviewStateTag from "@/components/ReviewStateTag/index";
+import NoDataSvg from "@/components/NoDataSvg";
 export default {
   components: {
     ReviewStateTag,
     PageBody,
     PageHeader,
-    Card
-  },
-  computed: {
-    ...mapState({
-      course: state => state.course.currentCourse
-    })
+    Card,
+    NoDataSvg
   },
   data() {
     return {
@@ -62,7 +62,8 @@ export default {
   },
   mounted() {
     this.loadingReviewList = true;
-    getStudentCourseReviewList(this.courses.id).then(value => {
+    const { courseId } = this.$route.params;
+    getStudentCourseReviewList(courseId).then(value => {
       this.review = value.data;
       this.loadingReviewList = false;
     });

+ 115 - 0
src/views/teacher/Code/ProjectDetail/BuildDetail.vue

@@ -0,0 +1,115 @@
+<template>
+  <div>
+    <div v-if="detailLoading"><page-section-loading show /></div>
+    <div v-else>
+      <!--空数据处理-->
+      <div
+        class="page-section"
+        v-if="!(projectDetail.buildList && projectDetail.buildList.length > 0)"
+        style="height: 60vh;"
+      >
+        <no-data-svg></no-data-svg>
+      </div>
+      <div
+        v-for="(item, index) in projectDetail.buildList"
+        :key="item.id"
+        class="gap-section"
+      >
+        <div class="page-section">
+          <div class="level">
+            <div class="level-left">
+              <build-tag :build-state="item.buildState" />
+              <strong style="padding-left: 10px">
+                {{ item.commitMessage }} · #构建ID:{{ item.id }}
+              </strong>
+            </div>
+            <div class="level-right">
+              <strong>完成时间:{{ item.buildTime }}</strong>
+            </div>
+          </div>
+          <div class="level">
+            <div class="level-left">
+              <strong style="padding-left: 10px">
+                #branchID:{{ item.branchId }}
+              </strong>
+            </div>
+            <div v-if="item.buildState === 1" class="level-right">
+              <strong>mirror : {{ item.mirrorId }}</strong>
+            </div>
+          </div>
+          <a
+            class="button is-info is-small"
+            @click="changeBuildMessageStatus(index)"
+            aria-controls="contentIdForA11y2"
+            >查看详细编译信息</a
+          >
+          <div class="code-section" v-show="buildMessageIsOpen[index]">
+            {{ item.buildMessage }}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+import BuildTag from "@/views/student/Code/ProjectDetail/components/BuildTag";
+import NoDataSvg from "@/components/NoDataSvg";
+export default {
+  name: "BuildDetail",
+  components: { BuildTag, PageSectionLoading, NoDataSvg },
+  props: {
+    projectDetail: {
+      type: Object,
+      default: () => {}
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {},
+  watch: {
+    projectDetail: function(newVal) {
+      let buildMessageIsOpen = [];
+      for (let item of newVal.buildList) {
+        //处理buildTime格式
+        item.buildTime = item.buildTime.replace("T", " ") + "ms";
+        buildMessageIsOpen.push(false);
+      }
+      this.buildMessageIsOpen = buildMessageIsOpen;
+    }
+  },
+  data() {
+    return {
+      buildMessageIsOpen: []
+    };
+  },
+  mounted() {},
+  methods: {
+    changeBuildMessageStatus(index) {
+      let status = !this.buildMessageIsOpen[index];
+      this.buildMessageIsOpen.splice(index, 1, status);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../assets/scss/app";
+.gap-section {
+  padding-bottom: $default-margin-between;
+}
+
+.gap-section:last-child {
+  padding-bottom: 0;
+}
+
+.code-section {
+  margin: $default-margin-between -#{$default-margin-between} -#{$default-margin-between};
+  padding: $default-margin-between;
+  background-color: #2d3044;
+  color: #cacff1;
+}
+</style>

+ 171 - 0
src/views/teacher/Code/ProjectDetail/DeployDetail.vue

@@ -0,0 +1,171 @@
+<template>
+  <div class="data-table">
+    <section>
+      <b-message
+        title="部署进行中..."
+        :active.sync="isRunning"
+        type="is-warning"
+      >
+        部署已经开始,一次部署需要大约5-10分钟,请5-10分钟后刷新页面获取部署结果
+      </b-message>
+    </section>
+    <!--部署记录-->
+    <div class="deploy-list" v-if="deployInfo">
+      <div class="subtitle">
+        {{ isRunning ? "当前部署情况" : "上次部署结果" }}
+      </div>
+      <div class="deploy-item">本次部署ID: #{{ deployInfo.id }}</div>
+      <div class="deploy-item">
+        本次部署创建时间: {{ handleTime(deployInfo.createdAt) }}
+      </div>
+      <div class="deploy-item" v-if="deployInfo.endAt">
+        本次部署结束时间: {{ handleTime(deployInfo.endAt) }}
+      </div>
+      <div class="deploy-item">
+        部署结果:
+        <div :class="['tag', type(deployInfo.status)]">
+          {{ deployInfo.status }}
+        </div>
+      </div>
+      <div class="subtitle">项目部署地址</div>
+      <div class="deploy-item">{{ deployInfo.url }}</div>
+      <div class="deploy-item">
+        <div class="subtitle">部署输出信息</div>
+        <a
+          class="button is-small is-info"
+          @click="unfoldDeployLogInfo = !unfoldDeployLogInfo"
+        >
+          {{ unfoldDeployLogInfo ? "收起部署详细信息" : "查看部署详细信息" }}</a
+        >
+        <div class="code-section" v-show="unfoldDeployLogInfo">
+          <div class="code-content" v-html="deployLog"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { getDeployDetail, getDeployLogInfo } from "@/api/codehw";
+
+export default {
+  data() {
+    return {
+      isActive: false,
+      isRunning: false,
+      deployStatus: "",
+      deployInfo: {},
+      haveDeployRecord: false,
+      deployLog: "",
+      unfoldDeployLogInfo: false
+    };
+  },
+  props: {
+    projectDetail: {
+      type: Object,
+      default: () => {}
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    projectDetail: function(newVal) {
+      console.log(newVal);
+      //获取最新一次部署详细信息
+      if (newVal.latestDeploy && newVal.latestDeploy.id) {
+        this.haveDeployRecord = true;
+        this.getLatestDeployInfo(newVal.latestDeploy.id);
+        this.getDeployLogInfo(this.projectDetail.latestDeploy.id);
+      }
+    }
+  },
+  mounted() {
+    if (this.projectDetail.latestDeploy) {
+      this.getLatestDeployInfo(this.projectDetail.latestDeploy.id);
+      this.getDeployLogInfo(this.projectDetail.latestDeploy.id);
+    }
+  },
+  methods: {
+    //获取最新一次部署详细信息
+    getLatestDeployInfo(deployId) {
+      getDeployDetail(deployId)
+        .then(res => {
+          this.deployInfo = res.data;
+          this.deployStatus = res.data.status;
+          let isRunning = this.deployStatus == "RUNNING" ? true : false;
+          this.isRunning = isRunning;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || "服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //处理时间格式
+    handleTime(time) {
+      if (!time) {
+        return "";
+      }
+      return time.replace("T", " ") + "ms";
+    },
+    //获取部署日志
+    getDeployLogInfo(deployId) {
+      getDeployLogInfo(deployId)
+        .then(res => {
+          this.deployLog = res.data;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || "服务器未知错误",
+            type: "is-danger"
+          });
+        });
+    },
+    //部署类型
+    type(value) {
+      if (value == "FAILURE") {
+        return "is-danger";
+      } else if (value == "RUNNING") {
+        return "is-warning";
+      } else if (value == "SUCCESS") {
+        return "is-success";
+      } else {
+        return "";
+      }
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+@import "../../../../assets/scss/app";
+.deploy-list {
+  margin-top: 1.5rem;
+  .deploy-item {
+    margin-top: 10px;
+    font-weight: bold;
+    .tag {
+      margin-left: 10px;
+      margin-right: 10px;
+    }
+    a {
+      margin-bottom: 10px;
+    }
+  }
+  .subtitle {
+    margin-top: 1.5rem;
+    width: 100%;
+    font-weight: bold;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #dbdbdb;
+  }
+}
+.code-section {
+  background-color: #2d3044;
+  padding: 20px 20px 20px 20px;
+  .code-content {
+    color: #cacff1;
+  }
+}
+</style>

+ 156 - 0
src/views/teacher/Code/ProjectDetail/TestDetail.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="columns is-desktop">
+    <div class="column">
+      <div v-if="detailLoading"><page-section-loading show /></div>
+      <div v-else class="page-section">
+        <h5 class="title is-5">单元测试:</h5>
+        <div v-if="!(unitTestList && unitTestList.length > 0)">
+          <strong>当前暂无单元测试数据</strong>
+        </div>
+        <router-link
+          class="test-item"
+          :key="item.id"
+          :to="`${$route.path}/unit/${item.id}`"
+          v-for="item in unitTestList"
+          tag="div"
+        >
+          <div><strong>UNIT TEST : #</strong> {{ item.id }}</div>
+          <pass-tag :pass="item.isPassAll" />
+          <span style="padding-left: 10px">{{
+            displayUnixTime(item.time)
+          }}</span>
+        </router-link>
+      </div>
+    </div>
+    <div class="column">
+      <div v-if="detailLoading"><page-section-loading show /></div>
+      <div v-else class="page-section">
+        <h5 class="test-title title is-5">
+          页面测试:
+          <!--<div-->
+          <!--:class="{ 'is-loading': testing }"-->
+          <!--@click="makeTest"-->
+          <!--:disabled="!(functionTestList && functionTestList.length > 0)"-->
+          <!--class="button is-primary test-title__action"-->
+          <!--&gt;-->
+          <!--触发测试-->
+          <!--</div>-->
+        </h5>
+        <div v-if="!(functionTestList && functionTestList.length > 0)">
+          <strong>当前暂无页面测试数据</strong>
+        </div>
+        <router-link
+          class="test-item"
+          :key="item.id"
+          :to="`${$route.path}/functional/${item.id}`"
+          v-for="item in functionTestList"
+          tag="div"
+        >
+          <div><strong>FUNCTIONAL TEST : #</strong> {{ item.id }}</div>
+          <pass-tag :pass="item.isPassAll" />
+          <span style="padding-left: 10px">{{
+            displayUnixTime(item.time)
+          }}</span>
+        </router-link>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import PassTag from "@/views/student/Code/ProjectDetail/components/PassTag";
+import timeMixins from "@/mixins/time";
+import { makeFunctionalTest } from "@/api/codehw";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+export default {
+  components: { PageSectionLoading, PassTag },
+  props: {
+    projectDetail: {
+      type: Object,
+      default: () => ({
+        unitTestList: [],
+        functionTestList: []
+      })
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    unitTestList() {
+      return this.sortTestList(this.projectDetail.unitTestList);
+    },
+    functionTestList() {
+      return this.sortTestList(this.projectDetail.functionTestList);
+    }
+  },
+  data() {
+    return {
+      testing: false
+    };
+  },
+  mixins: [timeMixins],
+  methods: {
+    async makeTest() {
+      const { functionTestList } = this;
+      if (functionTestList && functionTestList.length > 0) {
+        this.testing = true;
+        const { deployId } = functionTestList[0];
+        const { homeworkId, projectId } = this.$route.params;
+        const { data } = await makeFunctionalTest({
+          homeworkId,
+          projectId,
+          deployId
+        });
+        this.testing = false;
+        if (data.success) {
+          this.$toast.open({
+            message: `测试触发成功`,
+            type: "is-success"
+          });
+        } else {
+          this.$toast.open({
+            message: `测试触发失败`,
+            type: "is-danger"
+          });
+        }
+      }
+    },
+    sortTestList(list) {
+      console.log(list);
+      if (!list) return [];
+      const copyList = list.slice(0);
+      copyList.sort((a, b) => b.time - a.time);
+      return copyList;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../assets/scss/app";
+.test-item {
+  padding: $default-margin $default-margin-between;
+  margin: 0 -#{$default-margin-between};
+  cursor: pointer;
+}
+
+.test-title {
+  position: relative;
+  &__action {
+    position: absolute;
+    right: 0;
+    transform: translateY(-20%);
+  }
+}
+
+.test-item:hover,
+.test-item:nth-child(even):hover {
+  background-color: #dde3ef;
+}
+
+.test-item:nth-child(even) {
+  background-color: rgba(244, 247, 252, 0.6);
+}
+</style>

+ 62 - 51
src/views/teacher/Code/detail.vue

@@ -4,14 +4,14 @@
       :loading="loadingCommittedGroups"
       :routes="[
         { name: '代码作业', path: 'code' },
-        { name: '作业详情', path: 'code/${assignmentInfo.id}' }
+        { name: '作业详情', path: `${$route.path}` }
       ]"
     >
       <template slot="title">
-        {{ assignmentInfo.name }}
+        {{ problemInfo.name }}
       </template>
       <template slot="content">
-        作业描述信息作业描述信息作业描述信息作业描述信息作业描述信息作业描述信息作业描述信息
+        {{ problemInfo.description }}
       </template>
     </page-header>
 
@@ -38,43 +38,58 @@
                   {{ props.row.group.id }}
                 </b-table-column>
                 <b-table-column label="构建" centered>
-                  <span :class="geneTitleAndTag(props.row.buildStatus.key).tag">
-                    {{ geneTitleAndTag(props.row.buildStatus.key).title }}
+                  <span :class="geneTag(props.row.buildStatus.key)">
+                    {{
+                      props.row.buildStatus.key === 0
+                        ? "未构建"
+                        : props.row.buildStatus.key === -1
+                        ? "失败"
+                        : "成功"
+                    }}
                   </span>
                 </b-table-column>
 
                 <b-table-column label="部署" centered>
-                  <span
-                    :class="geneTitleAndTag(props.row.deployStatus.key).tag"
-                  >
-                    {{ geneTitleAndTag(props.row.deployStatus.key).title }}
+                  <span :class="geneTag(props.row.deployStatus.key)">
+                    {{
+                      props.row.deployStatus.key === 0
+                        ? "未部署"
+                        : props.row.deployStatus.key === -1
+                        ? "失败"
+                        : "成功"
+                    }}
                   </span>
                 </b-table-column>
-                <b-table-column label="单元测试">
-                  <span
-                    :class="geneTitleAndTag(props.row.unitTestStatus.key).tag"
-                  >
-                    {{ props.row.unitTestStatus.passCount }}/{{
-                      props.row.unitTestStatus.testCount
+                <b-table-column label="单元测试" centered>
+                  <span :class="geneTag(props.row.unitTestStatus.key)">
+                    {{
+                      props.row.unitTestStatus.key === 0
+                        ? "未测试"
+                        : props.row.unitTestStatus.key === -1
+                        ? "构建失败"
+                        : props.row.unitTestStatus.passCount +
+                          "/" +
+                          props.row.unitTestStatus.testCount
                     }}
                   </span>
                 </b-table-column>
-                <b-table-column label="功能测试">
-                  <span
-                    :class="
-                      geneTitleAndTag(props.row.functionTestStatus.key).tag
-                    "
-                  >
-                    {{ props.row.functionTestStatus.passCount }}/{{
-                      props.row.functionTestStatus.testCount
+                <b-table-column label="功能测试" centered>
+                  <span :class="geneTag(props.row.functionTestStatus.key)">
+                    {{
+                      props.row.functionTestStatus.key === 0
+                        ? "未测试"
+                        : props.row.functionTestStatus.key === -1
+                        ? "未知状态"
+                        : props.row.functionTestStatus.passCount +
+                          "/" +
+                          props.row.functionTestStatus.testCount
                     }}
                   </span>
                 </b-table-column>
-                <b-table-column label="详情">
+                <b-table-column label="详情" centered>
                   <router-link
                     :to="{
-                      path: `/course-teacher/:courseId/code/detail/test`,
-                      query: { assignmentInfo: props.row }
+                      path: `${$route.path}/${props.row.id}`
                     }"
                     >查看详情</router-link
                   >
@@ -136,15 +151,9 @@ import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getCodeProcedureListByCodeId } from "@/api/assignment";
 import { getUnsubmittedCodeGroupsList } from "@/api/assignment";
-import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+import { getCodeAssignmentByTeacher } from "@/api/assignment";
 
-const assignmentInfo = {
-  id: 0,
-  name: "",
-  description: "",
-  startAt: 0,
-  endAt: 0
-};
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 const tag = {
   success: "tag is-success",
@@ -165,15 +174,19 @@ export default {
     PageSectionLoading
   },
   mounted() {
+    const { codeId } = this.$route.params;
+    this.codeId = codeId;
     this.loadingUncommittedGroups = true;
     this.loadingCommittedGroups = true;
-    this.assignmentInfo = this.$route.query.assignmentInfo;
-    getCodeProcedureListByCodeId(123).then(res => {
+    getCodeAssignmentByTeacher(codeId).then(res => {
+      this.problemInfo = res.data.problem;
+    });
+    getCodeProcedureListByCodeId(codeId).then(res => {
       this.codeProcedureList = res.data;
       this.submitInfo.yes = "已提交:" + this.codeProcedureList.length;
       this.loadingCommittedGroups = false;
     });
-    getUnsubmittedCodeGroupsList(123).then(res => {
+    getUnsubmittedCodeGroupsList(codeId).then(res => {
       this.unsubmitedGroups = res.data;
       this.submitInfo.no = "未提交:" + this.unsubmitedGroups.length;
       this.loadingUncommittedGroups = false;
@@ -181,10 +194,14 @@ export default {
   },
   data() {
     return {
+      codeId: null,
       codeProcedureList: [],
       unsubmitedGroups: [],
-      showResult: [],
-      assignmentInfo,
+      problemInfo: {
+        id: null,
+        name: null,
+        description: null
+      },
       tag,
       status,
       submitInfo: {
@@ -196,22 +213,16 @@ export default {
     };
   },
   methods: {
-    geneTitleAndTag(state) {
-      let data = {
-        title: "",
-        tag: ""
-      };
+    geneTag(state) {
+      let showTag = "";
       if (state === -1) {
-        data.title = status.failed;
-        data.tag = tag.failed;
+        showTag = tag.failed;
       } else if (state === 0) {
-        data.title = status.uncommitted;
-        data.tag = tag.uncommitted;
+        showTag = tag.uncommitted;
       } else {
-        data.title = status.success;
-        data.tag = tag.success;
+        showTag = tag.success;
       }
-      return data;
+      return showTag;
     }
   }
 };

+ 12 - 14
src/views/teacher/Code/index.vue

@@ -29,17 +29,14 @@
           detail-key="id"
         >
           <template slot-scope="props">
-            <b-table-column field="id" label="ID" numeric sortable centered>
-              {{ props.row.id }}
-            </b-table-column>
+            <!--<b-table-column field="id" label="ID" numeric sortable centered>-->
+            <!--{{ props.row.id }}-->
+            <!--</b-table-column>-->
 
             <b-table-column label="作业名称" centered>
               <router-link
                 :to="{
-                  path: `code/${props.row.id}`,
-                  query: {
-                    assignmentInfo: props.row.problem
-                  }
+                  path: `code/${props.row.id}`
                 }"
               >
                 {{ props.row.name }}
@@ -94,8 +91,8 @@
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getCodeAssignmentListByTeacher } from "@/api/assignment";
-import timeMixins from "@/mixins/time";
 import { mapState } from "vuex";
+import timeMixins from "@/mixins/time";
 import DeleteAssignment from "@/components/AssignmentCRUD/DeleteAssignment";
 import UpdateAssignment from "@/components/AssignmentCRUD/UpdateAssignment";
 import CreateAssignment from "@/components/AssignmentCRUD/CreateAssignment";
@@ -113,7 +110,8 @@ export default {
   mixins: [timeMixins],
   mounted() {
     this.loadingCodeList = true;
-    getCodeAssignmentListByTeacher(this.course.id).then(res => {
+    const { courseId } = this.$route.params;
+    getCodeAssignmentListByTeacher(courseId).then(res => {
       this.codeData = res.data;
       this.loadingCodeList = false;
     });
@@ -125,6 +123,11 @@ export default {
       loadingCodeList: false
     };
   },
+  computed: {
+    ...mapState({
+      course: state => state.course.currentCourse
+    })
+  },
   methods: {
     pushNewAssignment(item) {
       this.codeData.push(item);
@@ -147,11 +150,6 @@ export default {
         }
       }
     }
-  },
-  computed: {
-    ...mapState({
-      course: state => state.course.currentCourse
-    })
   }
 };
 </script>

+ 44 - 15
src/views/teacher/Code/test.vue

@@ -3,25 +3,47 @@
     <page-header
       :routes="[
         { name: '代码作业', path: 'code' },
-        { name: '作业详情', path: `code/${assignmentInfo.id}` },
+        { name: '作业详情', path: `code/${$route.params.codeId}` },
         { name: '测试信息', path: `${$route.path}` }
       ]"
+      :tab-list="[
+        {
+          name: '构建',
+          path: `code/${$route.params.homeworkId}/${$route.params.projectId}`
+        },
+        {
+          name: '部署',
+          path: `code/${$route.params.homeworkId}/${
+            $route.params.projectId
+          }/deploy`
+        },
+        {
+          name: '测试',
+          path: `code/${$route.params.homeworkId}/${
+            $route.params.projectId
+          }/test`
+        }
+      ]"
     >
       <template slot="title">
-        测试信息
+        测试信息: {{ projectDetail.name }}
       </template>
       <template slot="content">
-        可查看最近五条测试数据
+        <strong>git 地址 : </strong>
+        <a :href="projectDetail.url">{{ projectDetail.url }}</a>
       </template>
     </page-header>
-
     <page-body>
-      <div class="data-table">和学生代码作业的测试信息的显示格式和接口相同</div>
+      <router-view
+        :project-detail="projectDetail"
+        :detail-loading="detailLoading"
+      />
     </page-body>
   </div>
 </template>
 
 <script>
+import { getProjectDetail } from "@/api/codehw";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 
@@ -30,23 +52,30 @@ export default {
     PageBody,
     PageHeader
   },
-  mounted() {
-    this.assignmentInfo = this.$route.query.assignmentInfo;
-  },
   data() {
     return {
-      assignmentInfo: {}
+      projectDetail: {},
+      detailLoading: false
     };
+  },
+  async mounted() {
+    // console.log(
+    //   "code/" +
+    //     this.$route.params.homeworkId +
+    //     "/" +
+    //     this.$route.params.projectId
+    // );
+    const { projectId } = this.$route.params;
+    this.detailLoading = true;
+    const detail = await getProjectDetail(projectId);
+    this.projectDetail = detail.data;
+    this.detailLoading = false;
   }
 };
 </script>
 
 <style scoped>
-.breadcrumb a {
-  /*color: #7957d5;*/
-  font-weight: bold;
-}
-.collapseeeeeee {
-  margin-bottom: 10px;
+.top-nav-section {
+  padding: 0 40px;
 }
 </style>

+ 0 - 9
src/views/teacher/Dashboard/index.vue

@@ -7,15 +7,6 @@
       <template slot="content">
         当前<strong>&nbsp;5&nbsp;</strong>份作业进行中
       </template>
-      <!--<template slot="extraContent">-->
-      <!--Here might be a page title-->
-      <!--</template>-->
-      <!--<template slot="logo">-->
-      <!--Here might-->
-      <!--</template>-->
-      <!--<template slot="action">-->
-      <!--Here might be a page title-->
-      <!--</template>-->
     </page-header>
     <page-body>
       <div class="data-table">

+ 35 - 17
src/views/teacher/Document/detail.vue

@@ -4,17 +4,19 @@
       :loading="loadingCommittedGroups"
       :routes="[
         { name: '文档作业', path: 'document' },
-        { name: `作业详情`, path: `document` }
+        { name: `作业详情`, path: `${$route.path}` }
       ]"
     >
       <template slot="title">
-        {{ question.name }}
+        {{ problem ? problem.name : "" }}
       </template>
       <template slot="content">
-        题目描述:{{ question.description }}
+        题目描述:{{ problem ? problem.description : "" }}
       </template>
       <template slot="extraContent">
-        题目进行阶段:{{ status }}
+        <b-tag type="is-info" style="margin-right: 5px" size="is-medium"
+          >阶段:{{ status }}</b-tag
+        >
       </template>
     </page-header>
 
@@ -45,19 +47,20 @@
                 </b-table-column>
 
                 <b-table-column label="互评负责组" centered>
-                  <span v-for="groupId in props.row.reviewGroup" :key="groupId"
-                    >{{ groupId }}&emsp;</span
+                  <span v-for="group in props.row.reviewGroup" :key="group.id"
+                    >{{ group.id }}&emsp;</span
                   >
                 </b-table-column>
 
                 <b-table-column label="分数" centered>
-                  {{ props.row.result }}
+                  {{
+                    props.row.result === null ? "暂无成绩" : props.row.result
+                  }}
                 </b-table-column>
                 <b-table-column field="difficulty" label="详情" centered
                   ><router-link
                     :to="{
-                      path: `${$route.path}/content`,
-                      query: { assignId: docId }
+                      path: `${$route.path}/content/${props.row.id}`
                     }"
                     >查看详情</router-link
                   >
@@ -79,6 +82,21 @@
                         <br />
                         <strong>git地址:</strong
                         ><small>{{ props.row.url }}</small> <br />
+                        <strong>互评小组:</strong
+                        ><small>
+                          <li
+                            v-for="reviewGroupp of props.row.reviewGroup"
+                            :key="reviewGroupp.id"
+                          >
+                            ID : {{ reviewGroupp.id }} &emsp; 小组成员:
+                            <small
+                              v-for="member of reviewGroupp.members"
+                              :key="member.id"
+                            >
+                              {{ member.username }}&emsp;
+                            </small>
+                          </li>
+                        </small>
                       </p>
                     </div>
                   </div>
@@ -123,6 +141,7 @@ import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getSubmittedDocResultListByDocId } from "@/api/assignment";
 import { getUnsubmittedDocGroupsByDocId } from "@/api/assignment";
+import { getDocAssignmentTeacherByCrs } from "@/api/assignment";
 import { mapState } from "vuex";
 import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
@@ -137,20 +156,20 @@ export default {
     PageHeader,
     PageSectionLoading
   },
-  created() {
-    this.docId = this.$route.query.docId;
-    this.question = this.$route.query.question;
-  },
   mounted() {
+    const { documentId } = this.$route.params;
     this.loadingCommittedGroups = true;
     this.loadingUncommittedGroups = true;
-    getSubmittedDocResultListByDocId(this.docId).then(res => {
+    getDocAssignmentTeacherByCrs(documentId).then(res => {
+      this.problem = res.data.problem;
+    });
+    getSubmittedDocResultListByDocId(documentId).then(res => {
       this.status = res.data.status;
       this.submittedData = res.data.result;
       this.submitInfo.yes = "已提交:" + this.submittedData.length;
       this.loadingCommittedGroups = false;
     });
-    getUnsubmittedDocGroupsByDocId(this.docId).then(res => {
+    getUnsubmittedDocGroupsByDocId(documentId).then(res => {
       this.unsubmitedGroups = res.data;
       this.submitInfo.no = "未提交:" + this.unsubmitedGroups.length;
       this.loadingUncommittedGroups = false;
@@ -158,11 +177,10 @@ export default {
   },
   data() {
     return {
+      problem: null,
       submittedData: [],
       unsubmitedGroups: [],
       status: "",
-      question: null,
-      docId: null,
       submitInfo: {
         yes: "已提交",
         no: "未提交"

+ 16 - 7
src/views/teacher/Document/docContent.vue

@@ -6,7 +6,7 @@
         { name: '文档作业', path: 'document' },
         {
           name: `作业详情`,
-          path: `document/${docId}`
+          path: `document/${$route.params.documentId}`
         },
         { name: `文档内容`, path: `${$route.path}` }
       ]"
@@ -24,7 +24,17 @@
         >
       </template>
       <template slot="extraContent">
-        上次修改时间:{{ displayUnixTime(contentResult.lastUpdateAt) }}
+        <b-tag type="is-light" style="margin-right: 5px"
+          >上次修改时间:{{
+            displayUnixTime(contentResult.lastUpdateAt)
+          }}</b-tag
+        >
+        <br />
+        <b-tag type="is-info" style="margin-right: 5px" size="is-medium"
+          >成绩:{{
+            contentResult.result === null ? "暂无" : contentResult.result
+          }}</b-tag
+        >
       </template>
     </page-header>
     <page-body>
@@ -57,22 +67,21 @@ export default {
   },
   data() {
     return {
-      docId: null,
       contentResult: {
         content: "",
         group: "",
+        result: null,
         lastUpdateAt: "",
         name: ""
       },
       loadingDocContent: false
     };
   },
-  created() {
-    this.docId = this.$route.query.assignId;
-  },
+
   mounted() {
     this.loadingDocContent = true;
-    getDocContentByDocId(this.docId).then(res => {
+    const { groupWorkId } = this.$route.params;
+    getDocContentByDocId(groupWorkId).then(res => {
       this.contentResult = res.data;
       this.loadingDocContent = false;
     });

+ 6 - 6
src/views/teacher/Document/index.vue

@@ -25,15 +25,14 @@
         <div v-if="!loadingDocList" class="data-table">
           <b-table :data="docData" detailed detail-key="id">
             <template slot-scope="props">
-              <b-table-column field="id" label="ID" numeric sortable centered>
-                {{ props.row.id }}
-              </b-table-column>
+              <!--<b-table-column field="id" label="ID" numeric sortable centered>-->
+              <!--{{ props.row.id }}-->
+              <!--</b-table-column>-->
 
               <b-table-column label="作业名称" centered>
                 <router-link
                   :to="{
-                    path: `${$route.path}/${props.row.id}`,
-                    query: { docId: props.row.id, question: props.row.problem }
+                    path: `${$route.path}/${props.row.id}`
                   }"
                 >
                   {{ props.row.name }}
@@ -113,7 +112,8 @@ export default {
   mixins: [timeMixins],
   mounted() {
     this.loadingDocList = true;
-    getDocAssignmentListTeacherByCrs(this.course.id).then(res => {
+    const { courseId } = this.$route.params;
+    getDocAssignmentListTeacherByCrs(courseId).then(res => {
       this.docData = res.data;
       this.loadingDocList = false;
     });

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 529 - 2
src/views/teacher/Home/index.vue


+ 147 - 15
src/views/teacher/Question/index.vue

@@ -16,7 +16,7 @@
         <div class="form-group">
           <!--下拉框-->
           <b-field>
-            <b-select placeholder="选择题目类型">
+            <b-select v-model="selectedType">
               <option
                 v-for="(option, index) in questionType"
                 :value="option"
@@ -45,7 +45,12 @@
         </div>
         <!--题目table-->
         <div class="question-list">
-          <b-table :data="data" detailed detail-key="id">
+          <b-table
+            :data="data"
+            detailed
+            detail-key="id"
+            v-if="data && data.length > 0"
+          >
             <template slot-scope="props">
               <b-table-column field="id" label="ID" width="40" numeric sortable>
                 {{ props.row.id }}
@@ -60,6 +65,7 @@
                       questionId: props.row.id
                     }
                   }"
+                  target="_blank"
                   >{{ props.row.name }}</router-link
                 >
               </b-table-column>
@@ -87,7 +93,7 @@
                 sortable
                 centered
               >
-                {{ props.row.expect_time }}分钟
+                {{ props.row.expectTime }}分钟
               </b-table-column>
             </template>
 
@@ -126,6 +132,49 @@
               </article>
             </template>
           </b-table>
+          <!--表格页标-->
+          <div
+            class="level-item"
+            v-if="paginationInfo.display && data && data.length > 0"
+          >
+            <div class="pagination">
+              <a
+                role="button"
+                href="#"
+                :disabled="{ disabled: paginationInfo.currentPage == 0 }"
+                class="pagination-previous"
+                ><span class="icon"
+                  ><i class="mdi mdi-chevron-left mdi-24px"></i></span
+              ></a>
+              <a
+                role="button"
+                href="#"
+                :disabled="{
+                  disabled:
+                    paginationInfo.currentPage == paginationInfo.totalPage
+                }"
+                class="pagination-next"
+                ><span class="icon"
+                  ><i class="mdi mdi-chevron-right mdi-24px"></i></span
+              ></a>
+              <!--页数较少的显示情况-->
+              <ul class="pagination-list" v-if="paginationInfo.totalPage < 6">
+                <li v-for="index in paginationInfo.pageList" :key="index">
+                  <a
+                    role="button"
+                    href="#"
+                    :class="[
+                      'pagination-link',
+                      { 'is-current': paginationInfo.currentPage == index }
+                    ]"
+                    >1</a
+                  >
+                </li>
+              </ul>
+            </div>
+          </div>
+          <!--页面无数据展示-->
+          <div v-if="data.length <= 0"><no-data-svg></no-data-svg></div>
         </div>
       </div>
     </page-body>
@@ -136,17 +185,22 @@ import { mapState } from "vuex";
 import {
   getQuestionsByAssignType,
   getTeacherQuestionType,
-  getTeacherKeywordQuestions
+  getTeacherKeywordQuestionsByType,
+  getQuestionByQuesType
 } from "@/api/question";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+import NoDataSvg from "@/components/NoDataSvg";
+
+const pageLimit = 10;
 
 export default {
   components: {
     PageHeader,
     PageBody,
-    PageSectionLoading
+    PageSectionLoading,
+    NoDataSvg
   },
   computed: {
     ...mapState({
@@ -161,37 +215,106 @@ export default {
       data: [],
       keyWord: "",
       errorMessage: "服务器错误,请稍后再试",
-      loadingQuestionList: true
+      loadingQuestionList: true,
+      selectedType: "全部类型",
+      paginationInfo: {
+        display: true,
+        totalPage: 0,
+        currentPage: 0,
+        pageList: []
+      }
     };
   },
+  watch: {
+    selectedType(val) {
+      let type = val;
+      type = type == "全部类型" ? "" : type;
+      this.getQuestionsByQType(type, 0, pageLimit);
+    }
+  },
   mounted() {
     //获取全部题目分类
     this.getQuestionType();
     //获取全部题目
-    this.getQuestions();
+    this.getQuestions("", 0, pageLimit);
 
     this.loadingQuestionList = false;
   },
   methods: {
+    //分页信息处理
+    pagnationHandler(total, current, elements) {
+      if (elements == 0) {
+        this.paginationInfo = {
+          display: false,
+          totalPage: 0,
+          currentPage: 0,
+          pageList: []
+        };
+      } else {
+        let paginationInfo = {};
+        let pageList = [];
+        paginationInfo.totalPage = total;
+        paginationInfo.currentPage = current;
+        for (let i = 0; i < total; i++) {
+          pageList.push(i);
+        }
+        paginationInfo.pageList = pageList;
+        paginationInfo.display = true;
+        this.paginationInfo = paginationInfo;
+      }
+    },
     //获取全部题目分类
     getQuestionType() {
       getTeacherQuestionType()
         .then(value => {
-          this.questionType = value.data;
+          // this.questionType = value.data;
+          let questionType = value.res;
+          questionType.unshift("全部类型");
+          this.questionType = questionType;
         })
         .catch(e => {
           this.$toast.open({
             message: e.message,
-            type: "is-link"
+            type: "is-danger"
           });
         });
     },
-    //获取全部题目
-    getQuestions() {
-      getQuestionsByAssignType({ type: "DOCUMENT", page: 0, limit: 10 })
+    //获取指定题目类型的题目
+    getQuestionsByQType(questionType, pageNum, pageLimit) {
+      getQuestionByQuesType({
+        type: questionType,
+        page: pageNum,
+        limit: pageLimit
+      })
         .then(value => {
-          // this.data = value.res.content;
-          this.data = value.data;
+          this.data = value.data.content;
+          this.pagnationHandler(
+            value.data.totalPages,
+            pageNum,
+            value.data.umberOfElements
+          );
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message,
+            type: "is-danger"
+          });
+        });
+    },
+    //获取指定作业类型的题目
+    getQuestions(questionType, pageNum, pageLimit) {
+      getQuestionsByAssignType({
+        type: questionType,
+        page: pageNum,
+        limit: pageLimit
+      })
+        .then(value => {
+          this.data = value.data.content;
+          this.pagnationHandler(
+            value.data.totalPages,
+            pageNum,
+            value.data.umberOfElements
+          );
         })
         .catch(e => {
           this.$toast.open({
@@ -202,7 +325,7 @@ export default {
     },
     //根据关键字查找题目
     getQuestionsByKey() {
-      getTeacherKeywordQuestions(this.keyWord)
+      getTeacherKeywordQuestionsByType(this.keyWord, "")
         .then(value => {
           this.data = value.data;
         })
@@ -245,6 +368,15 @@ export default {
   /*color: #7957d5;*/
   font-weight: bold;
 }
+.no-data {
+  display: flex;
+  align-items: center;
+  span {
+    font-size: 2rem;
+    font-weight: bold;
+    color: #6c63ff;
+  }
+}
 /*.data-table {*/
 /*width: 100%;*/
 /*padding: 30px;*/

+ 8 - 2
src/views/teacher/Question/questionDetail.vue

@@ -12,10 +12,10 @@
       </template>
       <template slot="content">
         <b-tag class="tag" type="is-link" rounded>{{
-          questionInfo.assignmentType ? questionInfo.assignmentType.value : ""
+          questionInfo.assignmentType || ""
         }}</b-tag>
         <b-tag class="tag" type="is-link" rounded>{{
-          questionInfo.type ? questionInfo.type.value : ""
+          questionInfo.questionType || ""
         }}</b-tag>
       </template>
     </page-header>
@@ -50,6 +50,12 @@
                 <a>{{ item.filename }}</a>
               </div>
             </div>
+            <div
+              class="empty"
+              v-if="!(questionInfo.file && questionInfo.file.length > 0)"
+            >
+              当前题目暂无文件信息
+            </div>
           </div>
         </div>
       </div>

+ 313 - 148
src/views/teacher/Result/index.vue

@@ -15,20 +15,30 @@
         <!--下拉框&搜索框-->
         <div class="form-group">
           <!--下拉框-->
-          <b-field> <a class="button is-link">导出作业成绩</a> </b-field>
           <b-field>
-            <a class="button is-link" @click="isCardModalActive = true"
-              >成绩比例调控</a
-            >
+            <a class="button is-link" :disabled="isNoResult" @click="exportStudentResult">导出作业成绩</a>
+          </b-field>
+          <b-field>
+            <a
+              class="button is-link"
+              @click="isCardModalActive = true"
+              :disabled="isNoResult"
+              >成绩比例调控
+            </a>
           </b-field>
         </div>
         <!--成绩图表-->
         <div class="chart">
           <canvas id="result-chart" style="width: 100% !important;"></canvas>
         </div>
-        <!--题目table-->
-        <div class="question-list">
-          <b-table :data="data" paginated per-page="5" detailed detail-key="id">
+        <div class="result-list">
+          <b-table
+            :data="studentFinalResultList"
+            paginated
+            per-page="50"
+            detailed
+            detail-key="id"
+          >
             <template slot-scope="props">
               <b-table-column field="id" label="ID" width="40" numeric sortable>
                 {{ props.row.id }}
@@ -37,24 +47,25 @@
                 {{ props.row.user.nickname }}
               </b-table-column>
               <b-table-column label="文档作业" centered sortable>
-                {{ average(props.row.documents) }}
+                {{ addSum(props.row.documents) }}
               </b-table-column>
 
               <b-table-column label="代码作业" centered sortable>
-                {{ average(props.row.codes) }}
+                {{ addSum(props.row.codes) }}
               </b-table-column>
 
               <b-table-column label="互评作业" centered sortable>
-                {{ average(props.row.reviews) }}
+                {{ addSum(props.row.reviews) }}
               </b-table-column>
               <b-table-column
-                field="finalResult"
+                field="finalRes"
                 label="总评"
                 sortable
+                numeric
                 centered
               >
-                <span :class="['tag', type(getFinalResult(props.row))]">
-                  {{ getFinalResult(props.row) }}
+                <span :class="['tag', type(props.row.finalRes)]">
+                  {{ props.row.finalRes }}
                 </span>
               </b-table-column>
             </template>
@@ -106,69 +117,35 @@
       </div>
     </page-body>
     <!-- 成绩比例调控模态框 -->
-    <b-modal :active.sync="isCardModalActive" width="420px" scroll="keep">
+    <b-modal :active.sync="isCardModalActive" scroll="keep">
       <div class="card">
         <div class="title">
           成绩比例调控<small>&nbsp;&nbsp;请按百分比填写</small>
         </div>
         <div class="content">
-          <div class="homework-item">
-            <label>文档作业</label>
-            <div class="homework-percent">
-              <b-field position="is-centered">
-                <b-input placeholder="第一次作业百分比"> </b-input>
-                <p class="control">
-                  <button class="button is-light">%</button>
-                </p>
-              </b-field>
-            </div>
-            <div class="homework-percent">
-              <b-field position="is-centered">
-                <b-input placeholder="第二次作业百分比"> </b-input>
-                <p class="control">
-                  <button class="button is-light">%</button>
-                </p>
-              </b-field>
-            </div>
-            <div class="homework-percent">
-              <b-field
-                :type="$v.percent.$error ? 'is-danger' : ''"
-                :message="$v.percent.$error ? '请填写1-100之间的数字' : ''"
-                position="is-centered"
-              >
+          <b-table
+            :data="resultPercentList"
+            class="customer-table"
+            :striped="true"
+            :narrowed="true"
+          >
+            <template slot-scope="props">
+              <b-table-column field="id" label="作业ID" width="50" numeric>
+                {{ props.row.id }}
+              </b-table-column>
+              <b-table-column field="name" label="作业名称">
+                {{ props.row.name }}
+              </b-table-column>
+              <b-table-column field="type" label="作业类型" email>
+                {{ props.row.type }}
+              </b-table-column>
+              <b-table-column field="percent" label="当前比例">
                 <b-input
-                  v-model="$v.percent.$model"
-                  placeholder="第三次作业百分比"
-                >
-                </b-input>
-                <p class="control">
-                  <button class="button is-light">%</button>
-                </p>
-              </b-field>
-            </div>
-          </div>
-          <div class="homework-item">
-            <label>互评作业</label>
-            <div class="homework-percent">
-              <b-field position="is-centered">
-                <b-input placeholder="第一次作业百分比"> </b-input>
-                <p class="control">
-                  <button class="button is-light">%</button>
-                </p>
-              </b-field>
-            </div>
-          </div>
-          <div class="homework-item">
-            <label>代码作业</label>
-            <div class="homework-percent">
-              <b-field position="is-centered">
-                <b-input placeholder="第一次作业百分比"> </b-input>
-                <p class="control">
-                  <button class="button is-light">%</button>
-                </p>
-              </b-field>
-            </div>
-          </div>
+                  v-model="resultPercentList[props.index].percent"
+                ></b-input>
+              </b-table-column>
+            </template>
+          </b-table>
         </div>
         <a
           :class="['button', 'is-link', { 'is-loading': submitting }]"
@@ -186,8 +163,24 @@ import PageBody from "@/components/PageBody/index";
 import Chart from "chart.js";
 import BInput from "buefy/src/components/input/Input";
 import { required, numeric, between } from "vuelidate/lib/validators";
-import { getStudentFinaleResult } from "@/api/resultstatistics";
+import {
+  getStudentFinaleResult,
+  setResultPercentList
+} from "@/api/resultstatistics";
 import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+import { toExcel } from "@/util/exportExcel";
+
+const RESULT_SECTION = [
+  "0分",
+  "1-29分",
+  "30-59分段",
+  "60-69分段",
+  "70-79分段",
+  "80-89分段",
+  "90-99分段",
+  "100分"
+];
+const RESULT_PARTITION = [0, 29, 59, 69, 79, 89, 99, 100];
 
 export default {
   components: {
@@ -210,21 +203,130 @@ export default {
   },
   data() {
     return {
-      name: "",
-      courses: [],
-      review: [],
+      isNoResult: true,
       data: [],
       isCardModalActive: false,
       myChart2: {},
-      percent: "",
-      finalResultPercentage: [20, 20, 20, 10, 10, 10, 10],
       submitting: false,
-      isServerError: false,
-      errorMessage: "服务器错误,请稍后重试",
-      isResultLoading: true
+      isResultLoading: true,
+      resultPercentList: [],
+      studentFinalResultList: [],
+      studentResultChartData: [],
+      resultChart: {}
     };
   },
   methods: {
+    //导出学生作业成绩
+    exportStudentResult() {
+      const th = [
+        "ID",
+        "姓名",
+        "文档作业成绩",
+        "互评作业成绩",
+        "代码作业成绩",
+        "总评"
+      ];
+      // const data = this.studentFinalResultList.map(v => filterVal.map(k => v[k]));
+      // console.log(data);
+      let data = [];
+      for (let item of this.studentFinalResultList) {
+        let docResString = "";
+        let reviewResString = "";
+        let codeResString = "";
+        for (let docRes of item.documents) {
+          docResString += `${docRes.name}:${docRes.score};`;
+        }
+        for (let reviewRes of item.reviews) {
+          reviewResString += `${reviewRes.name}:${reviewRes.score}`;
+        }
+        for (let codeRes of item.codes) {
+          codeResString += `${codeRes.name}:${codeRes.score}`;
+        }
+        let outPutInfo = [
+          item.id,
+          item.user.nickname,
+          docResString,
+          reviewResString,
+          codeResString,
+          item.finalRes
+        ];
+        data.push(outPutInfo);
+      }
+      console.log(data);
+      const [fileName, fileType, sheetName] = ["学生成绩", "xlsx", "成绩"];
+      toExcel({ th, data, fileName, fileType, sheetName });
+    },
+    //绘制成绩图表
+    drawResultChart() {
+      let myChart2 = new Chart(this.resultChart, {
+        type: "bar",
+        data: {
+          labels: RESULT_SECTION,
+          datasets: [
+            {
+              label: "人数",
+              backgroundColor: "rgba(54,162,235,0.3)",
+              borderColor: "rgba(54,162,235,1)",
+              borderWidth: 1,
+              pointStrokeColor: "#fff",
+              pointStyle: "crossRot",
+              data: this.studentResultChartData,
+              cubicInterpolationMode: "monotone",
+              spanGaps: "false",
+              fill: "false"
+            }
+          ]
+        },
+        options: {}
+      });
+      this.myChart2 = myChart2;
+    },
+    //生成成绩百分比
+    createResultPercentList() {
+      let resultPercentList = [];
+      let resultData = this.data[0];
+      for (let codesHw of resultData.codes) {
+        codesHw.type = "CODE";
+        resultPercentList.push(codesHw);
+      }
+      for (let documentsHw of resultData.documents) {
+        documentsHw.type = "DOCUMENT";
+        resultPercentList.push(documentsHw);
+      }
+      for (let reviewHw of resultData.reviews) {
+        reviewHw.type = "REVIEW";
+        resultPercentList.push(reviewHw);
+      }
+      this.resultPercentList = resultPercentList;
+      console.log(resultPercentList);
+    },
+    //统计学生成绩分段
+    studenResultStatistics(studentResults) {
+      let resultStatisticsData = [];
+      for (let i = 0; i < RESULT_PARTITION.length; i++) {
+        let count = 0;
+        if (i == 0) {
+          for (let result of studentResults) {
+            if (result.finalRes == 0) count++;
+          }
+        } else if (i == RESULT_PARTITION.length - 1) {
+          for (let result of studentResults) {
+            if (result.finalRes == 100) count++;
+          }
+        } else {
+          for (let result of studentResults) {
+            if (
+              result.finalRes > RESULT_PARTITION[i - 1] &&
+              result.finalRes <= RESULT_PARTITION[i]
+            )
+              count++;
+          }
+        }
+        resultStatisticsData.push(count);
+      }
+      this.studentResultChartData = resultStatisticsData;
+      // return resultStatisticsData;
+    },
     type(value) {
       const number = parseFloat(value);
       if (number < 60) {
@@ -235,83 +337,146 @@ export default {
         return "is-success";
       }
     },
-    //计算平均成绩
-    average(resultList) {
-      let res = 0;
+    //计算每种作业平均成绩
+    addSum(resultList) {
+      let finalRes = 0;
       for (let item of resultList) {
-        res += item.score;
+        finalRes += item.calScore;
       }
-      return res / resultList.length;
+      return finalRes;
     },
-    //计算总评
-    getFinalResult(result) {
-      let finalRes = 0;
-      let currentIndex = 0;
-      for (let docRes of result.documents) {
-        finalRes +=
-          docRes.score * (this.finalResultPercentage[currentIndex] / 100);
-        currentIndex++;
-      }
-      for (let codeRes of result.codes) {
-        finalRes +=
-          codeRes.score * (this.finalResultPercentage[currentIndex] / 100);
-        currentIndex++;
-      }
-      for (let reviewRes of result.reviews) {
-        finalRes +=
-          reviewRes.score * (this.finalResultPercentage[currentIndex] / 100);
-        currentIndex++;
+    //计算学生总评
+    getFinalResult(studentResultList) {
+      let studentFinalResultList = [];
+      for (let i = 0; i < studentResultList.length; i++) {
+        let studentRes = studentResultList[i];
+        let resultPercentList = this.resultPercentList;
+        let index = 0;
+        let codesResList = [];
+        let docsResList = [];
+        let reviewsResList = [];
+        let finalRes = 0;
+        for (let codeRes of studentRes.codes) {
+          //根据成绩比例计算分数
+          codeRes.calScore = codeRes.score
+            ? (codeRes.score * resultPercentList[index].percent) / 100
+            : 0;
+          // console.log(codeRes.calScore);
+          codesResList.push(codeRes);
+          finalRes += codeRes.calScore;
+          index++;
+        }
+        studentRes.codes = codesResList;
+        for (let docRes of studentRes.documents) {
+          docRes.calScore = docRes.score
+            ? (docRes.score * resultPercentList[index].percent) / 100
+            : 0;
+          docsResList.push(docRes);
+          finalRes += docRes.calScore;
+          index++;
+        }
+        studentRes.documents = docsResList;
+        for (let reviewRes of studentRes.reviews) {
+          reviewRes.calScore = reviewRes.score
+            ? (reviewRes.score * resultPercentList[index].percent) / 100
+            : 0;
+          reviewsResList.push(reviewRes);
+          finalRes += reviewRes.calScore;
+          index++;
+        }
+        studentRes.reviews = reviewsResList;
+        studentRes.finalRes = finalRes;
+        studentFinalResultList.push(studentRes);
       }
-      return parseInt(finalRes);
+      this.studentFinalResultList = studentFinalResultList;
     },
+    //调整成绩比例
     confirmPercent() {
-      this.submitting = true;
-      this.$toast.open({
-        message: "成绩比例调整成功",
-        type: "is-success"
-      });
+      //检查是否满足100%
+      let percentSum = 0;
+      let docPercentList = [];
+      let codePercentList = [];
+      let reviewPercentList = [];
+      for (let item of this.resultPercentList) {
+        percentSum += parseInt(item.percent);
+        switch (item.type) {
+          case "DOCUMENT":
+            docPercentList.push({ id: item.id, percent: item.percent });
+            break;
+          case "CODE":
+            codePercentList.push({ id: item.id, percent: item.percent });
+            break;
+          case "REVIEW":
+            reviewPercentList.push({ id: item.id, percent: item.percent });
+            break;
+        }
+      }
+      if (percentSum != 100) {
+        this.$toast.open({
+          message: "请使比例加和为100%",
+          type: "is-warning"
+        });
+      } else {
+        //提交成绩修改结果
+        this.submitting = true;
+        setResultPercentList(
+          this.$route.params.courseId,
+          docPercentList,
+          codePercentList,
+          reviewPercentList
+        )
+          .then(res => {
+            console.log(res);
+            this.$toast.open({
+              message: "成绩比例调整成功",
+              type: "is-success"
+            });
+            this.isCardModalActive = false;
+            this.submitting = false;
+            //重新计算成绩比例
+            this.getFinalResult(this.data);
+            this.studenResultStatistics(this.studentFinalResultList);
+            let myChart2 = this.myChart2;
+            console.log(myChart2);
+            myChart2.data.datasets[0].data = this.studentResultChartData;
+            myChart2.update();
+          })
+          .catch(e => {
+            this.$toast.open({
+              message: e.message || "服务器未知错误",
+              type: "is-danger"
+            });
+          });
+      }
     }
   },
-  mounted() {
+  async mounted() {
     //获取成绩信息
-    getStudentFinaleResult(this.courses.id).then(res => {
-      console.log(res.data);
-      this.data = res.data;
-      this.isResultLoading = false;
-    });
-    //绘制图表
-    let resultChart = document.getElementById("result-chart");
-    let myChart2 = new Chart(resultChart, {
-      type: "bar",
-      data: {
-        labels: [
-          "0分",
-          "1-29分",
-          "30-59分段",
-          "60-69分段",
-          "70-79分段",
-          "80-89分段",
-          "90-99分段",
-          "100分"
-        ],
-        datasets: [
-          {
-            label: "人数",
-            backgroundColor: "rgba(54,162,235,0.3)",
-            borderColor: "rgba(54,162,235,1)",
-            borderWidth: 1,
-            pointStrokeColor: "#fff",
-            pointStyle: "crossRot",
-            data: [2, 6, 10, 34, 50, 80, 30, 2],
-            cubicInterpolationMode: "monotone",
-            spanGaps: "false",
-            fill: "false"
-          }
-        ]
-      },
-      options: {}
-    });
-    this.myChart2 = myChart2;
+    const resultData = await getStudentFinaleResult(
+      this.$route.params.courseId
+    );
+    this.isResultLoading = false;
+    this.data = resultData.data;
+    if (resultData.data && resultData.data.length > 0) {
+      this.isNoResult = false;
+      // console.log(this.data);
+      //初始化默认成绩比例
+      // this.initialResultPercent(this.data);
+      //初始化各作业比例
+      this.createResultPercentList();
+      //统计学生总评
+      this.getFinalResult(resultData.data);
+      //统计学生分数
+      this.studenResultStatistics(this.data);
+      //绘制图表
+      let resultChart = document.getElementById("result-chart");
+      this.resultChart = resultChart;
+      this.drawResultChart();
+    } else {
+      let resultChart = document.getElementById("result-chart");
+      this.resultChart = resultChart;
+      this.drawResultChart();
+    }
   }
 };
 </script>
@@ -323,7 +488,7 @@ export default {
 .chart {
   margin-top: 1.5rem;
 }
-.question-list {
+.result-list {
   margin-top: 3rem;
 }
 .form-group {

+ 1 - 1
src/views/teacher/Review/ReviewDetail/GroupArgueDetail/index.vue

@@ -45,7 +45,7 @@
             >
               提 交
             </div>
-            <check-list v-model="detail.checkList" />
+            <check-list :isTeacher="true" v-model="detail.checkList" />
           </div>
         </div>
       </div>

+ 4 - 3
src/views/teacher/Review/index.vue

@@ -135,7 +135,7 @@ import Card from "@/components/Card";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import ReviewStateTag from "@/components/ReviewStateTag/index";
-import { getDocAssignmentListByCrs } from "@/api/assignment";
+import { getDocAssignmentListTeacherByCrs } from "@/api/assignment";
 import timeMixins from "@/mixins/time";
 import { required } from "vuelidate/lib/validators";
 export default {
@@ -257,13 +257,14 @@ export default {
   },
   methods: {
     async getReviewList() {
-      const { data } = await getTeacherCourseReviewList(this.courses.id);
+      const { courseId } = this.$route.params;
+      const { data } = await getTeacherCourseReviewList(courseId);
       this.review = data;
     },
     async getDocHomework() {
       this.docHomeworkLoading = true;
       const { courseId } = this.$route.params;
-      const { data } = await getDocAssignmentListByCrs(courseId);
+      const { data } = await getDocAssignmentListTeacherByCrs(courseId);
       this.docHomework = data;
       this.docHomeworkLoading = false;
     },

+ 3 - 0
vue.config.js

@@ -4,6 +4,9 @@ module.exports = {
       "^/api": {
         target: "http://localhost:4000/",
         // target: "http://10.1.1.198:8080/",
+        // target: "http://192.168.0.101:8080",
+        // target: "http://10.1.2.3:8080/",
+
         ws: true,
         changeOrigin: true
       }

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott