Kaynağa Gözat

[修改]老师代码作业详情

soleil 7 yıl önce
ebeveyn
işleme
fa8bb2da3c

+ 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>

+ 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" };
+  }
+}

+ 3 - 3
src/router/routes.js

@@ -91,17 +91,17 @@ export default [
           {
             path: "",
             component: () =>
-              import("@/views/student/Code/ProjectDetail/BuildDetail")
+              import("@/views/teacher/Code/ProjectDetail/BuildDetail")
           },
           {
             path: "deploy",
             component: () =>
-              import("@/views/student/Code/ProjectDetail/DeployDetail")
+              import("@/views/teacher/Code/ProjectDetail/DeployDetail")
           },
           {
             path: "test",
             component: () =>
-              import("@/views/student/Code/ProjectDetail/TestDetail")
+              import("@/views/teacher/Code/ProjectDetail/TestDetail")
           }
         ]
       },

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

@@ -23,7 +23,10 @@
           class="column is-4-desktop"
         >
           <router-link :to="`${$route.path}/${item.id}`">
-            <card hoverable>{{ item.name }}</card>
+            <card hoverable>
+              {{ item.name }}
+              <div><code-state-tag :state="item.status"></code-state-tag></div>
+            </card>
           </router-link>
         </div>
         <!--当没有代码作业时的处理-->
@@ -45,13 +48,15 @@ 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,
-    noDataSvg
+    noDataSvg,
+    CodeStateTag
   },
   computed: {
     ...mapState({

+ 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>

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

@@ -0,0 +1,321 @@
+<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"-->
+      <!--&gt;-->
+      <!--<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': isRunning }]"-->
+      <!--@click="startDeploy"-->
+      <!--style="margin-top: 20px;"-->
+      <!--&gt;-->
+      <!--开始部署-->
+      <!--</button>-->
+      <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
+      // 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() {
+    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 {
+    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>