123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- <!-- eslint-disable vue/no-v-html -->
- <template>
- <div class="answer">
- <div class="title">{{ share_record_info.share_record_name }}</div>
- <div v-if="curStatisticsType === statisticsList[1].value" 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>
- <ul class="statistics-type-list">
- <li
- v-for="{ label, value } in statisticsList"
- :key="value"
- :class="[{ active: value === curStatisticsType }]"
- @click="changeStatistics(value)"
- >
- {{ label }}
- </li>
- </ul>
- <!-- 按题统计 -->
- <template v-if="curStatisticsType === statisticsList[0].value">
- <el-table :data="question_user_answer_stat_list" height="100%" @sort-change="sortChange">
- <el-table-column prop="index" label="题号" width="70">
- <template slot-scope="{ row }">{{ row.question_number }}</template>
- </el-table-column>
- <el-table-column prop="name" label="题型" width="130" />
- <el-table-column label="主客观" width="90">
- <template slot-scope="{ row }">{{ row.is_objective === 'true' ? '客观题' : '主观题' }}</template>
- </el-table-column>
- <el-table-column label="题干" width="600">
- <div slot-scope="{ row }" class="rich-text" v-html="sanitizeHTML(row.stem)"></div>
- </el-table-column>
- <el-table-column label="回答正确" width="100">
- <template slot-scope="{ row }">
- <template v-if="row.answer_right_person_name_list.length > 0">
- <el-popover placement="bottom" width="270" trigger="hover">
- <ul class="name-list">
- <li v-for="(name, i) in row.answer_right_person_name_list" :key="i">{{ name }}</li>
- </ul>
- <span slot="reference">{{ row.is_objective === 'true' ? row.answer_right_person_count : '-' }}</span>
- </el-popover>
- </template>
- <span v-else>{{ row.is_objective === 'true' ? row.answer_right_person_count : '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="回答错误" width="100">
- <template slot-scope="{ row }">
- <template v-if="row.answer_error_person_name_list.length > 0">
- <el-popover placement="bottom" width="270" trigger="hover">
- <ul class="name-list">
- <li v-for="(name, i) in row.answer_error_person_name_list" :key="i">{{ name }}</li>
- </ul>
- <span slot="reference">{{ row.is_objective === 'true' ? row.answer_error_person_count : '-' }}</span>
- </el-popover>
- </template>
- <span v-else>{{ row.is_objective === 'true' ? row.answer_error_person_count : '-' }} </span>
- </template>
- </el-table-column>
- <el-table-column
- label="正确率"
- sortable="custom"
- prop="right_percent"
- :sort-orders="['descending', 'ascending', null]"
- >
- <template slot-scope="{ row }">
- <template v-if="row.is_objective === 'true'">
- {{ row.right_percent }}% ({{ row.answer_right_person_count }}/{{ row.answer_person_count }})
- </template>
- <template v-else>-</template>
- </template>
- </el-table-column>
- <el-table-column label="操作" fixed="right" width="100">
- <template slot-scope="{ row }">
- <span class="link" @click="viewExerciseQuestion(row.question_id, row.exercise_id, row.question_type)">
- 查看
- </span>
- </template>
- </el-table-column>
- </el-table>
- </template>
- <!-- 按人统计 -->
- <template v-else-if="curStatisticsType === statisticsList[1].value">
- <el-table
- :data="answer_record_list"
- height="100%"
- :header-cell-class-name="handleHeaderClass"
- @sort-change="sortChangePerson"
- >
- <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"
- sortable="custom"
- :sort-orders="['ascending', 'descending', null]"
- />
- <el-table-column
- prop="answer_duration"
- label="耗时"
- width="180"
- sortable="custom"
- :sort-orders="['ascending', 'descending', null]"
- >
- <template slot-scope="{ row }">
- <span>{{ row.finish_time ? secondFormatConversion(row.answer_duration, 'chinese') : '' }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="right_count" label="正确" width="160">
- <template slot-scope="{ row }">
- <span>{{ row.finish_time ? row.right_count : '' }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="error_count" label="错误" width="160">
- <template slot-scope="{ row }">
- <span>{{ row.finish_time ? row.error_count : '' }}</span>
- </template>
- </el-table-column>
- <el-table-column
- label="正确率"
- sortable="custom"
- :sort-orders="['ascending', 'descending', null]"
- prop="right_percent"
- >
- <template slot-scope="{ row }">
- <span>{{ row.finish_time ? row.right_percent + '%' : '' }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="operation" label="操作" fixed="right" width="200">
- <template v-if="row.finish_time" slot-scope="{ row }">
- <span class="link" @click="viewUserAnswerRecordLis(row.exercise_share_record_id, row.id)">查看</span>
- </template>
- </el-table-column>
- </el-table>
- <PaginationPage ref="pagination" :total="total" @getList="pageQueryExerciseUserAnswerRecordList" />
- </template>
- </div>
- </template>
- <script>
- import { PageQueryExerciseUserAnswerRecordList, GetExerciseQuestionUserAnswerStatList } from '@/api/exercise';
- import { secondFormatConversion } from '@/utils/transform';
- import DOMPurify from 'dompurify';
- 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 },
- ],
- secondFormatConversion,
- curStatisticsType: 'question', // 当前统计类型
- // 统计类型列表
- statisticsList: [
- { label: '按题统计', value: 'question' },
- { label: '按人统计', value: 'person' },
- ],
- // 题目用户答题统计列表
- question_user_answer_stat_list: [],
- order_column_list: [], // 排序列
- };
- },
- 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 };
- },
- sort() {
- return this.order_column_list.map((item) => {
- const [prop, order] = item.split(':');
- return { prop, order: order === 'desc' ? 'descending' : 'ascending' };
- });
- },
- },
- created() {
- this.getExerciseQuestionUserAnswerStatList();
- },
- methods: {
- getPageList() {
- this.$refs.pagination.getList();
- },
- /**
- * 查看用户答题记录列表
- * @param {string} exercise_share_record_id 练习分享记录id
- * @param {string} answer_record_id 答题记录id
- */
- viewUserAnswerRecordLis(exercise_share_record_id, answer_record_id) {
- this.$router.push({
- path: '/answer_record_list',
- query: {
- exercise_share_record_id,
- answer_record_id,
- exercise_id: this.searchData.exercise_id,
- share_record_id: this.searchData.share_record_id,
- },
- });
- },
- /**
- * 查看练习题题目答题用户列表
- */
- viewExerciseQuestion(question_id, exercise_id, question_type) {
- this.$router.push({
- path: '/exercise_answer_user_list',
- query: {
- search_exercise_id: this.searchData.exercise_id,
- exercise_id,
- share_record_id: this.searchData.share_record_id,
- question_id,
- question_type,
- },
- });
- },
- pageQueryExerciseUserAnswerRecordList(data) {
- PageQueryExerciseUserAnswerRecordList({ ...data, ...this.searchData, order_column_list: this.order_column_list })
- .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(() => {});
- },
- /**
- * 按人统计表格排序修改事件
- * @param {string} prop 列属性
- * @param {string} order 排序方式
- */
- sortChangePerson({ prop, order }) {
- this.changeOrderColumnList(prop, order);
- this.getPageList();
- },
- /**
- * 切换统计类型
- * @param {string} value 统计类型
- */
- changeStatistics(value) {
- this.curStatisticsType = value;
- this.order_column_list = [];
- if (value === 'question') {
- this.getExerciseQuestionUserAnswerStatList();
- }
- },
- getExerciseQuestionUserAnswerStatList() {
- GetExerciseQuestionUserAnswerStatList({
- share_record_id: this.searchData.share_record_id,
- order_column_list: this.order_column_list,
- }).then(({ share_record_info, sum_info, question_user_answer_stat_list }) => {
- this.question_user_answer_stat_list = question_user_answer_stat_list;
- this.share_record_info = share_record_info;
- this.sum_info = sum_info;
- });
- },
- /**
- * 按题统计表格排序修改事件
- * @param {string} prop 列名称
- * @param {string} order 排序方式
- */
- sortChange({ prop, order }) {
- this.changeOrderColumnList(prop, order);
- this.getExerciseQuestionUserAnswerStatList();
- },
- /**
- * 修改 order_column_list 排序列
- * @param {string} prop 列名称
- * @param {string} order 排序方式
- */
- changeOrderColumnList(prop, order) {
- const findIndex = this.order_column_list.findIndex((item) => item.includes(prop));
- if (order) {
- const order_column = `${prop}${order === 'descending' ? ':desc' : ''}`;
- if (findIndex === -1) {
- this.order_column_list.push(order_column);
- } else {
- this.order_column_list.splice(findIndex, 1, order_column);
- }
- } else {
- this.order_column_list = this.order_column_list.filter((item) => !item.includes(prop));
- }
- },
- /**
- * 处理表头样式,为表头添加排序样式
- * @param {object} column 列对象
- */
- handleHeaderClass({ column }) {
- const find = this.order_column_list.find((item) => item.includes(column.property));
- if (!find) {
- column.order = null;
- return;
- }
- column.order = find.split(':')[1] === undefined ? 'ascending' : 'descending';
- },
- /**
- * 过滤 html,防止 xss 攻击
- * @param {string} html 需要过滤的html
- * @returns {string} 过滤后的html
- */
- sanitizeHTML(html) {
- return DOMPurify.sanitize(html);
- },
- },
- };
- </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;
- }
- }
- }
- .statistics-type-list {
- display: flex;
- column-gap: 12px;
- font-size: 14px;
- li {
- padding: 5px 12px;
- color: $font-light-color;
- cursor: pointer;
- &.active {
- color: $main-color;
- background-color: $fill-color;
- border-radius: 40px;
- }
- }
- }
- .rich-text {
- @include rich-text(12pt);
- font-size: 14px !important;
- color: #1d2129 !important;
- text-decoration: none !important;
- background-color: transparent !important;
- :deep p {
- margin: 0;
- font-size: 14px !important;
- color: #1d2129 !important;
- text-decoration: none !important;
- background-color: transparent !important;
- }
- :deep span {
- font-size: 14px !important;
- color: #1d2129 !important;
- text-decoration: none !important;
- background-color: transparent !important;
- }
- :deep strong {
- font-weight: normal !important;
- color: #1d2129 !important;
- text-decoration: none !important;
- background-color: transparent !important;
- }
- :deep em {
- font-style: normal !important;
- color: #1d2129 !important;
- text-decoration: none !important;
- background-color: transparent !important;
- }
- }
- }
- </style>
- <style lang="scss">
- .el-popover {
- padding: 8px;
- .name-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- }
- </style>
|