Преглед изворни кода

回收和分享页,修改预览的答题格式

dusenyao пре 1 година
родитељ
комит
1425342f94

+ 14 - 0
src/api/exercise.js

@@ -91,3 +91,17 @@ export function CopyExerciseToPublicStore(data) {
 export function CopyExerciseToPersonalStore(data) {
   return http.post(`/TeachingServer/ExerciseManager/CopyExerciseToPersonalStore`, data);
 }
+
+/**
+ * 分页查询练习题分享记录列表
+ */
+export function PageQueryExerciseShareRecordList(data) {
+  return http.post(`/TeachingServer/ExerciseManager/PageQueryExerciseShareRecordList`, data);
+}
+
+/**
+ * 分页查询练习题用户答题记录列表
+ */
+export function PageQueryExerciseUserAnswerRecordList(data) {
+  return http.post(`/TeachingServer/ExerciseManager/PageQueryExerciseUserAnswerRecordList`, data);
+}

+ 52 - 0
src/components/common/PaginationPage.vue

@@ -0,0 +1,52 @@
+<template>
+  <el-pagination
+    background
+    :current-page="cur_page"
+    :page-sizes="[10, 20, 30, 40, 50]"
+    :page-size="page_capacity"
+    layout="total, prev, pager, next, sizes, jumper"
+    :total="total"
+    @prev-click="changePage"
+    @next-click="changePage"
+    @current-change="changePage"
+    @size-change="changePageSize"
+  />
+</template>
+
+<script>
+export default {
+  name: 'PaginationPage',
+  props: {
+    pageSize: {
+      type: Number,
+      default: 10,
+    },
+    total: {
+      type: Number,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      cur_page: 1,
+      page_capacity: this.pageSize,
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    changePage(number) {
+      this.cur_page = number;
+      this.getList();
+    },
+    changePageSize(size) {
+      this.page_capacity = size;
+      this.getList();
+    },
+    getList() {
+      this.$emit('getList', { cur_page: this.cur_page, page_capacity: this.page_capacity });
+    },
+  },
+};
+</script>

+ 6 - 2
src/components/common/SoundRecord.vue

@@ -1,6 +1,6 @@
 <!-- 录音 -->
 <template>
-  <div>
+  <div class="sound-record">
     <span @click="startRecord">开始录音</span>
   </div>
 </template>
@@ -42,4 +42,8 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.sound-record {
+  display: flex;
+}
+</style>

+ 28 - 9
src/layouts/default/breadcrumb/index.vue

@@ -2,9 +2,11 @@
   <div v-if="isShowBreadcrumb" class="breadcrumb">
     <SvgIcon :icon-class="breadcrumb[0].meta.icon" />
     <ul>
-      <li v-for="({ meta: { title, path } }, i) in breadcrumb" :key="path">
+      <li v-for="({ meta: { title }, path, redirect }, i) in breadcrumb" :key="title">
         <span class="separator">/</span>
-        <span :key="i" class="breadcrumb-name">{{ title }}</span>
+        <span :key="i" class="breadcrumb-name" @click="$router.push(path.length > 0 ? path : redirect)">
+          {{ title }}
+        </span>
       </li>
     </ul>
   </div>
@@ -47,14 +49,31 @@ export default {
   height: 56px;
   padding: 16px 24px;
 
-  .separator {
-    margin: 0 8px;
-    color: #c9cdd4;
-  }
+  > ul {
+    display: flex;
+
+    li {
+      font-weight: 400;
+      color: $font-light-color;
+
+      .separator {
+        margin: 0 8px;
+        color: #c9cdd4;
+      }
+
+      .breadcrumb-name {
+        font-size: 14px;
+      }
+
+      &:not(:last-child) {
+        cursor: pointer;
+      }
 
-  &-name {
-    font-size: 14px;
-    font-weight: bold;
+      &:nth-last-child(1) {
+        font-weight: bold;
+        color: $font-color;
+      }
+    }
   }
 }
 </style>

+ 10 - 2
src/router/modules/basic.js

@@ -31,19 +31,27 @@ export const HomePage = {
   path: '/',
   component: DEFAULT,
   redirect: '/home',
+  meta: { title: '练习管理', icon: 'practice' },
   children: [
     {
       path: 'home',
       name: 'Home',
-      meta: { title: '练习管理', icon: 'practice' },
       component: () => import('@/views/home/public_question'),
     },
     {
       path: 'personal_question',
       name: 'Personal',
-      meta: { title: '练习管理', icon: 'practice' },
       component: () => import('@/views/home/personal_question'),
     },
+    {
+      path: 'recovery',
+      meta: { title: '回收' },
+      component: () => import('@/views/home/recovery/index.vue'),
+    },
+    {
+      path: 'answer_data',
+      component: () => import('@/views/home/recovery/AnswerData.vue'),
+    },
   ],
 };
 

+ 1 - 1
src/styles/element.scss

@@ -27,7 +27,7 @@ div.el-dialog {
 }
 
 .el-form-item {
-  color: #4e5969;
+  color: $font-light-color;
 
   & > &__label {
     padding-right: 16px;

+ 12 - 0
src/styles/mixin.scss

@@ -40,3 +40,15 @@
     }
   }
 }
+
+// 列表
+@mixin list {
+  display: flex;
+  flex-direction: column;
+  row-gap: 16px;
+  height: calc(100% - 16px);
+  padding: 24px;
+  margin: 0 24px 16px;
+  background: #fff;
+  border-radius: 4px;
+}

+ 1 - 0
src/styles/variables.scss

@@ -5,6 +5,7 @@ $main-hover-color: #3371ff;
 $danger-color: #f53f3f;
 $danger-hover-color: #f56060;
 $font-color: #1d2129;
+$font-light-color: #4e5969;
 $fill-color: #f2f3f5;
 $text-color: #86909c;
 $border-color: #e5e6eb;

+ 13 - 0
src/utils/index.js

@@ -15,3 +15,16 @@ export function downloadFile(url, downloadName) {
   a.click();
   a.remove();
 }
+
+/**
+ * 柯里化函数(将接受多个参数的函数转换为一系列接受单个参数的函数)
+ * @param {Function} fn 需要柯里化的函数
+ * @returns Function
+ */
+export let curry = (fn) => {
+  if (typeof fn !== 'function') throw new Error('No function provided');
+  return function curriedFn(...args) {
+    if (args.length < fn.length) return (...args2) => curriedFn(...args, ...args2);
+    return fn(...args);
+  };
+};

+ 1 - 1
src/views/exercise_questions/create/components/exercises/JudgeQuestion.vue

@@ -219,7 +219,7 @@ export default {
         justify-content: center;
         width: 16px;
         height: 16px;
-        color: #4e5969;
+        color: $font-light-color;
         cursor: pointer;
         background-color: #d7d7d7;
         border-radius: 2px;

+ 4 - 4
src/views/exercise_questions/preview/JudgePreview.vue

@@ -54,15 +54,15 @@ export default {
   },
   methods: {
     isAnswer(mark, option_type) {
-      return this.answer.some((li) => li.mark === mark && li.option_type === option_type);
+      return this.answer.select_list.some((li) => li.mark === mark && li.option_type === option_type);
     },
 
     selectAnswer(mark, option_type) {
-      const index = this.answer.findIndex((li) => li.mark === mark);
+      const index = this.answer.select_list.findIndex((li) => li.mark === mark);
       if (index === -1) {
-        this.answer.push({ mark, option_type });
+        this.answer.select_list.push({ mark, option_type });
       } else {
-        this.answer[index].option_type = option_type;
+        this.answer.select_list[index].option_type = option_type;
       }
     },
   },

+ 2 - 2
src/views/exercise_questions/preview/MatchingPreview.vue

@@ -102,7 +102,7 @@ export default {
           item.every((li) => li.length <= 0) ? emptyStringArray.push(i) : '';
         });
         if (column_number === 2) {
-          this.answer = arr;
+          this.answer.select_list = arr;
           return;
         }
 
@@ -133,7 +133,7 @@ export default {
             arr[emptyStringArray[i]] = item;
           });
         }
-        this.answer = arr;
+        this.answer.select_list = arr;
       },
       deep: true,
     },

+ 5 - 5
src/views/exercise_questions/preview/SelectPreview.vue

@@ -40,18 +40,18 @@ export default {
   },
   methods: {
     isAnswer(mark) {
-      return this.answer.indexOf(mark) !== -1;
+      return this.answer.select_list.indexOf(mark) !== -1;
     },
     selectAnswer(mark) {
-      const index = this.answer.indexOf(mark);
+      const index = this.answer.select_list.indexOf(mark);
       if (this.data.property.select_type === selectTypeList[0].value) {
-        this.answer = [mark];
+        this.answer.select_list = [mark];
       }
       if (this.data.property.select_type === selectTypeList[1].value) {
         if (index === -1) {
-          this.answer.push(mark);
+          this.answer.select_list.push(mark);
         } else {
-          this.answer.splice(index, 1);
+          this.answer.select_list.splice(index, 1);
         }
       }
     },

+ 1 - 1
src/views/exercise_questions/preview/components/PreviewMixin.js

@@ -14,7 +14,7 @@ const PreviewMixin = {
   },
   data() {
     return {
-      answer: [], // 答案
+      answer: { select_list: [] }, // 答案
     };
   },
   methods: {

+ 2 - 2
src/views/home/common.vue

@@ -110,7 +110,7 @@ export default {
     &-item {
       padding: 5px 16px;
       font-size: 14px;
-      color: #4e5969;
+      color: $font-light-color;
       cursor: pointer;
 
       &.active {
@@ -130,7 +130,7 @@ export default {
 
     &-name {
       font-size: 14px;
-      color: #4e5969;
+      color: $font-light-color;
     }
   }
 }

+ 26 - 3
src/views/home/personal_question/components/ShareDialog.vue

@@ -3,7 +3,13 @@
     <div class="share-condition">
       <div class="condition-top">
         <span>开始日期</span>
-        <el-date-picker v-model="begin_date" type="date" placeholder="选择日期" style="width: 100%" />
+        <el-date-picker
+          v-model="begin_date"
+          value-format="yyyy-MM-dd"
+          type="date"
+          placeholder="选择日期"
+          style="width: 100%"
+        />
         <span>有效期</span>
         <el-input v-model.number="effective_days" placeholder="请输入有效期">
           <span slot="suffix">天</span>
@@ -14,6 +20,11 @@
         </el-input>
       </div>
 
+      <div class="memo">
+        <div class="label">备注</div>
+        <el-input v-model="memo" placeholder="请输入" type="textarea" />
+      </div>
+
       <div
         class="condition-bottom"
         :style="{ 'grid-template': `30px 32px / repeat(auto-fill, ${send_type === sendModes[0].type ? 272 : 300}px` }"
@@ -122,6 +133,7 @@ export default {
       begin_date: this.getNowDate(),
       effective_days: 50,
       answer_time_limit_minute: 30,
+      memo: '',
     };
   },
   methods: {
@@ -139,6 +151,7 @@ export default {
         answer_mode: this.answer_mode,
         access_popedom: this.access_popedom,
         max_person_count: this.max_person_count,
+        memo: this.memo,
       }).then(({ status, ...data }) => {
         this.$emit('generateLink', { ...data, type });
       });
@@ -155,6 +168,7 @@ export default {
       this.begin_date = this.getNowDate();
       this.effective_days = 50;
       this.answer_time_limit_minute = 30;
+      this.memo = '';
     },
   },
 };
@@ -173,8 +187,13 @@ export default {
 
   :deep &__body {
     padding: 40px 24px;
+    color: $font-light-color;
 
     .share-condition {
+      display: flex;
+      flex-direction: column;
+      row-gap: 20px;
+
       .condition-top {
         display: grid;
         grid-template: 30px 32px / repeat(auto-fill, 210px);
@@ -182,11 +201,15 @@ export default {
         column-gap: 16px;
       }
 
+      .memo {
+        .label {
+          height: 30px;
+        }
+      }
+
       .condition-bottom {
         display: grid;
         grid-auto-flow: column;
-        padding: 0 16px;
-        margin-top: 36px;
 
         .el-radio-group {
           display: flex;

+ 13 - 6
src/views/home/personal_question/index.vue

@@ -23,11 +23,18 @@
           </template>
         </el-table-column>
 
-        <el-table-column prop="operation" label="操作" fixed="right" width="200">
+        <el-table-column prop="operation" label="操作" fixed="right" width="300">
           <template slot-scope="{ row }">
             <span class="link" @click="$router.push({ path: '/exercise', query: { id: row.id } })">编辑</span>
             <span class="link" @click="share(row.id)">分享</span>
             <span class="link" @click="copyExerciseToPublicStore(row.id)">公开</span>
+            <span
+              v-if="row.is_has_share_record === 'true'"
+              class="link"
+              @click="$router.push({ path: '/recovery', query: { exercise_id: row.id } })"
+            >
+              回收
+            </span>
             <span class="link danger" @click="deleteExercise(row.id)">删除</span>
           </template>
         </el-table-column>
@@ -87,12 +94,12 @@ export default {
         status_list: [],
       },
       statusType: [
-        { label: '未发布', value: 0 },
-        { label: '已发布', value: 1 },
+        { label: '未公开', value: 0 },
+        { label: '已公开', value: 1 },
       ],
       statusList: [
-        { name: '未发布', class: 'unpublished' },
-        { name: '已发布', class: 'published' },
+        { name: '未公开', class: 'unpublished' },
+        { name: '已公开', class: 'published' },
       ],
       // 表格数据
       exercise_list: [],
@@ -135,6 +142,7 @@ export default {
     copyExerciseToPublicStore(exercise_id) {
       CopyExerciseToPublicStore({ exercise_id }).then(() => {
         this.$message.success('公开成功');
+        this.getPageList();
       });
     },
     share(id) {
@@ -146,7 +154,6 @@ export default {
     },
     generateLink(data) {
       this.shareData = data;
-      this.share;
       this.visibleShare = false;
       this.visibleLink = true;
     },

+ 0 - 2
src/views/home/public_question/index.vue

@@ -110,5 +110,3 @@ export default {
   },
 };
 </script>
-
-<style lang="scss" scoped></style>

+ 187 - 0
src/views/home/recovery/AnswerData.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="answer">
+    <div class="title">{{ share_record_info.share_record_url }}</div>
+
+    <div class="search">
+      <span class="search-name">搜索</span>
+      <el-input
+        v-model="searchData.search_content"
+        placeholder="全部"
+        suffix-icon="el-icon-search"
+        @keyup.enter.native="getPageList"
+      />
+
+      <span class="search-name">状态</span>
+      <el-select v-model="searchData.status_list" multiple placeholder="全部" @change="getPageList">
+        <el-option v-for="{ label, value } in statusType" :key="value" :label="label" :value="value" />
+      </el-select>
+    </div>
+
+    <div class="sum-info">
+      <div class="info-item">
+        <span>收到邀请</span>
+        <span class="info">
+          <span>{{ sum_info.finish_person_count + sum_info.unfinish_person_count }}</span>
+          <span class="light">人</span>
+        </span>
+      </div>
+      <div class="info-item">
+        <span>完成练习</span>
+        <span class="info">
+          <span>{{ sum_info.finish_person_count }}</span>
+          <span class="light">人</span>
+        </span>
+      </div>
+      <div class="info-item">
+        <span>平均正确率</span>
+        <span class="info">{{ sum_info.avg_right_percent }} <span class="light">%</span></span>
+      </div>
+      <div class="info-item">
+        <span>平均耗时</span>
+        <span class="info">
+          <template v-if="avgTime.hour"> {{ avgTime.hour }} <span class="light">时</span> </template>
+          <template v-if="avgTime.minute"> {{ avgTime.minute }} <span class="light">分</span> </template>
+          {{ avgTime.second }} <span class="light">秒</span>
+        </span>
+      </div>
+    </div>
+
+    <el-table :data="answer_record_list" height="100%">
+      <el-table-column prop="index" label="序号" width="70">
+        <template slot-scope="{ $index }">{{ $index + 1 }}</template>
+      </el-table-column>
+      <el-table-column prop="user_real_name" label="用户" width="280" />
+      <el-table-column prop="finish_time" label="完成时间" width="180" />
+      <el-table-column prop="answer_duration" label="耗时" width="180" />
+      <el-table-column prop="right_count" label="正确" width="160" />
+      <el-table-column prop="error_count" label="错误" width="160" />
+      <el-table-column label="正确率">
+        <template slot-scope="{ row }">
+          <span>{{ row.right_percent }}</span>
+        </template>
+      </el-table-column>
+
+      <el-table-column prop="operation" label="操作" fixed="right" width="200">
+        <span class="link">查看</span>
+      </el-table-column>
+    </el-table>
+
+    <PaginationPage ref="pagination" :total="total" @getList="pageQueryExerciseUserAnswerRecordList" />
+  </div>
+</template>
+
+<script>
+import { PageQueryExerciseUserAnswerRecordList } from '@/api/exercise';
+
+import PaginationPage from '@/components/common/PaginationPage.vue';
+
+export default {
+  name: 'AnswerData',
+  components: {
+    PaginationPage,
+  },
+  data() {
+    const { query } = this.$route;
+
+    return {
+      total: 0,
+      answer_record_list: [],
+      share_record_info: { exercise_name: '', share_record_url: '' },
+      sum_info: { finish_person_count: 0, unfinish_person_count: 0, avg_right_percent: 0, avg_answer_duration: 0 },
+      searchData: {
+        exercise_id: query.exercise_id,
+        share_record_id: query.share_record_id,
+        search_content: '',
+        status_list: [],
+      },
+      statusType: [
+        { label: '已完成', value: 0 },
+        { label: '未完成', value: 1 },
+      ],
+    };
+  },
+  computed: {
+    avgTime() {
+      let duration = this.sum_info.avg_answer_duration;
+      let hour = Math.floor(duration / 3600);
+      let minute = Math.floor((duration - hour * 3600) / 60);
+      let second = duration - hour * 3600 - minute * 60;
+
+      return { hour, minute, second };
+    },
+  },
+  methods: {
+    getPageList() {
+      this.$refs.pagination.getList();
+    },
+    pageQueryExerciseUserAnswerRecordList(data) {
+      PageQueryExerciseUserAnswerRecordList({ ...data, ...this.searchData })
+        .then(({ total_count, share_record_info, sum_info, answer_record_list }) => {
+          this.answer_record_list = answer_record_list;
+          this.total = total_count;
+          this.share_record_info = share_record_info;
+          this.sum_info = sum_info;
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.answer {
+  @include list;
+
+  .title {
+    font-size: 20px;
+    font-weight: bold;
+  }
+
+  .search {
+    display: grid;
+    grid-template: 30px 32px / repeat(auto-fill, 210px);
+    grid-auto-flow: column;
+    column-gap: 24px;
+
+    &-name {
+      font-size: 14px;
+      color: $font-light-color;
+    }
+  }
+
+  .sum-info {
+    display: flex;
+    column-gap: 160px;
+    align-items: center;
+    justify-content: center;
+    min-height: 90px;
+    padding: 16px;
+    background-color: $main-background-color;
+    border-radius: 2px;
+
+    .info-item {
+      display: flex;
+      flex-direction: column;
+
+      :first-child {
+        text-align: right;
+      }
+
+      .info {
+        height: 33px;
+        font-size: 22px;
+        font-weight: bold;
+        text-align: right;
+      }
+
+      .light {
+        font-size: 12px;
+        font-weight: normal;
+        color: $font-light-color;
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
src/views/home/recovery/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="recovery">
+    <div class="title nowrap-ellipsis" :title="exercise_info.exercise_name">{{ exercise_info.exercise_name }}</div>
+
+    <el-table :data="list" height="100%">
+      <el-table-column prop="index" label="序号" width="70">
+        <template slot-scope="{ $index }">{{ $index + 1 }}</template>
+      </el-table-column>
+      <el-table-column prop="url" label="链接" width="700" />
+      <el-table-column prop="memo" label="备注" width="180" />
+      <el-table-column prop="finish_person_count" label="完成练习" width="180" />
+      <el-table-column prop="begin_date" label="开始日期" width="120" />
+      <el-table-column prop="end_date" label="截止日期" min-width="120" />
+
+      <el-table-column prop="operation" label="操作" fixed="right" width="200">
+        <template slot-scope="{ row }">
+          <span
+            class="link"
+            @click="
+              $router.push({ path: '/answer_data', query: { share_record_id: row.id, exercise_id: exercise_id } })
+            "
+          >
+            查看
+          </span>
+          <span class="link danger">停止</span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <PaginationPage ref="pagination" :total="total" @getList="pageQueryExerciseShareRecordList" />
+  </div>
+</template>
+
+<script>
+import { PageQueryExerciseShareRecordList } from '@/api/exercise';
+
+import PaginationPage from '@/components/common/PaginationPage.vue';
+
+export default {
+  name: 'RecoveryList',
+  components: {
+    PaginationPage,
+  },
+  data() {
+    const { query } = this.$route;
+
+    return {
+      exercise_id: query.exercise_id,
+      exercise_info: {
+        exercise_name: '',
+      },
+      total: 0,
+      list: [],
+    };
+  },
+  methods: {
+    pageQueryExerciseShareRecordList(data) {
+      PageQueryExerciseShareRecordList({ ...data, exercise_id: this.exercise_id })
+        .then(({ total_count, exercise_info, share_record_list }) => {
+          this.list = share_record_list;
+          this.exercise_info = exercise_info;
+          this.total = total_count;
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.recovery {
+  @include list;
+
+  .title {
+    width: 217px;
+    height: 28px;
+    font-size: 20px;
+    font-weight: bold;
+  }
+}
+</style>