|
@@ -0,0 +1,412 @@
|
|
|
+<template>
|
|
|
+ <div class="layout-pd h100">
|
|
|
+ <div class="layout-padding-auto layout-padding-view pd20">
|
|
|
+ <splitpanes class="h100" :horizontal="horizontal">
|
|
|
+ <pane size="30">
|
|
|
+ <el-tabs v-model="state.activeName" stretch @tab-change="tabChange">
|
|
|
+ <el-tab-pane label="培训知识" name="0" :disabled="state.loading"></el-tab-pane>
|
|
|
+ <el-tab-pane label="习题" name="1" :disabled="state.loading" v-if="state.trainPracticeQuestionsDtos.length > 0" ></el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ <el-scrollbar style="height: calc(100% - 100px);" ref="scrollBarRef">
|
|
|
+ <el-skeleton :loading="state.loading" animated :rows="10" v-if="state.activeName === '0'">
|
|
|
+ <p class="knowladgeDtosItem" v-for="(item) in state.trainPracticeKnowladgeDtos" @click="onClickKnowladge(item.id)">
|
|
|
+ <el-icon color="#409efc" class="el-input__icon">
|
|
|
+ <ele-Notebook />
|
|
|
+ </el-icon>
|
|
|
+ {{ item.title }}
|
|
|
+ </p>
|
|
|
+ </el-skeleton>
|
|
|
+ <el-skeleton :loading="state.loading" animated :rows="10" v-if="state.activeName === '1'">
|
|
|
+ <p class="knowladgeDtosItem" v-for="(item) in state.trainPracticeQuestionsDtos" @click="onSelQuestion(item.id)">
|
|
|
+ <el-icon color="#409efc" class="el-input__icon">
|
|
|
+ <ele-Tickets />
|
|
|
+ </el-icon>
|
|
|
+ {{ item.title }}
|
|
|
+ </p>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-scrollbar>
|
|
|
+ </pane>
|
|
|
+ <pane class="h100" style="display: flex;flex-direction: column;flex: 1;">
|
|
|
+ <el-card shadow="never" v-if="state.activeName === '0' && state.KnowladgeInfo">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="headerText">{{state.trainTitle}}</span>
|
|
|
+ <el-button class="headerBtn" type="primary" @click="onSubmit" :loading="state.loading">结束培训</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton-item />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <h1 class="font18 mb10" style="text-align: center">{{ state.KnowladgeInfo.title }}</h1>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ <el-row class="color-info flex-center-between" :gutter="20">
|
|
|
+ <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton-item />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <span class="mr30">创建时间:{{ formatDate(state.KnowladgeInfo.creationTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
|
|
|
+ <span class="mr30">创建人:{{ state.KnowladgeInfo.creatorName }}</span>
|
|
|
+ <span>创建部门:{{ state.KnowladgeInfo.creatorOrgName }}</span>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" style="display: flex; align-items: center; justify-content: center">
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton-item />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <span class="mr20 flex-center-align" v-if="state.KnowladgeInfo.knowledgeTypeText"
|
|
|
+ >知识分类:
|
|
|
+ <el-tooltip effect="dark" content="Top Left prompts info" :disabled="state.KnowladgeInfo.knowledgeTypeText.length < 8">
|
|
|
+ <span style="display: block; width: 100px" class="text-no-wrap">{{ state.KnowladgeInfo.knowledgeTypeText }}</span>
|
|
|
+ <template #content>
|
|
|
+ {{ state.KnowladgeInfo.knowledgeTypeText }}
|
|
|
+ </template>
|
|
|
+ </el-tooltip>
|
|
|
+ </span>
|
|
|
+ <span class="mr30">是否公开:{{ state.KnowladgeInfo.isPublic ? '是' : '否' }}</span>
|
|
|
+ <span>已阅:{{ state.KnowladgeInfo.pageView }}</span>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row class="color-info flex-center-between">
|
|
|
+ <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton-item />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <span class="mr30" v-if="state.KnowladgeInfo.documentNo">文档号:{{ state.KnowladgeInfo.documentNo }}</span>
|
|
|
+ <span class="mr30" v-if="state.KnowladgeInfo.versionDescription">版本说明:{{ state.KnowladgeInfo.versionDescription }}</span>
|
|
|
+ <span class="mr30" v-if="state.KnowladgeInfo.indexNo">索引号:{{ state.KnowladgeInfo.indexNo }}</span>
|
|
|
+ <span v-if="state.KnowladgeInfo.fileNo">文号:{{ state.KnowladgeInfo.fileNo }}</span>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-divider />
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton :rows="5" />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <div class="mt5 editor-content-view">
|
|
|
+ <div v-html="state.KnowladgeInfo.content" class="lineHeight24"></div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ <template v-if="state.KnowladgeInfo.knowledgeDtos && state.KnowladgeInfo.knowledgeDtos.length > 0">
|
|
|
+ <el-divider />
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-skeleton-item />
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ 关联知识
|
|
|
+ <div class="mt5">
|
|
|
+ <div v-for="(item, index) in state.KnowladgeInfo.knowledgeDtos" :key="index" @click="onPreview(item)" class="relevance">
|
|
|
+ <el-link type="primary">{{ item.title }}</el-link>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </template>
|
|
|
+ <template>
|
|
|
+ <el-divider />
|
|
|
+ <el-skeleton :loading="state.knowladgeLoading" animated>
|
|
|
+ <template #template>
|
|
|
+ <el-form-item label="附件">
|
|
|
+ <el-skeleton-item />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ <template #default>
|
|
|
+ <div class="mt10">
|
|
|
+ <el-form-item label="附件">
|
|
|
+ <annex-list name="知识附件" v-model="state.KnowladgeInfo.files" readonly classify="知识附件" />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </template>
|
|
|
+ </el-card>
|
|
|
+ <el-card shadow="never" v-if="state.activeName === '1'">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="headerText">{{state.trainTitle}}</span>
|
|
|
+ <el-button class="headerBtn" type="primary" @click="onSubmit" :loading="state.loading">结束培训</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-skeleton :loading="state.questionLoading" animated :rows="5">
|
|
|
+ <template #default>
|
|
|
+ <div class="questionBox" v-if="state.questionDetail">
|
|
|
+ <p class="questionTitle">题干:{{ state.questionDetail.title }}</p>
|
|
|
+ <div class="questionAnswer">
|
|
|
+ <div v-if="[0,2].includes(state.questionDetail.questionType)">
|
|
|
+ <el-radio-group style="display: block;" v-model="state.selRadioValue">
|
|
|
+ <el-form-item :label="item.label + '. '" v-for="item in state.questionDetail.trainPracticeOptionsDtos" style="margin-bottom: 10px;">
|
|
|
+ <el-radio :label="item.content" :value="item.questionOptionId" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="state.questionDetail.questionType == 1">
|
|
|
+ <el-checkbox-group v-model="state.selCheckboxValue">
|
|
|
+ <el-form-item :label="item.label + '. '" v-for="item in state.questionDetail.trainPracticeOptionsDtos" style="margin-bottom: 10px;">
|
|
|
+ <el-checkbox :label="item.content" :value="item.questionOptionId" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="state.questionDetail.questionType == 2"></div>
|
|
|
+ <div v-else-if="state.questionDetail.questionType == 3">
|
|
|
+ <el-form-item label="答:">
|
|
|
+ <el-input type="textarea" v-model="state.answerValue" :rows="3" placeholder="请输入答案,如果有多个请依照顺序用英文 , 隔开" clearable></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="state.questionDetail.questionType == 4">
|
|
|
+ <el-form-item label="答:">
|
|
|
+ <el-input type="textarea" v-model="state.answerValue" :rows="3" placeholder="请输入答案" clearable></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="referenceAnswer" v-if="state.questionDetail && state.questionDetail.trainPracticeKnowladgeDtos.length > 0">
|
|
|
+ <span>关联知识:</span>
|
|
|
+ <p v-for="item in state.questionDetail.trainPracticeKnowladgeDtos" @click="onKnowladgeTo(item)">{{item.title}}</p>
|
|
|
+ </div>
|
|
|
+ <div class="referenceAnswer" v-if="state.questionDetail && state.questionDetail.trainPracticeSourcewareDtos.length > 0">
|
|
|
+ <span>关联课件:</span>
|
|
|
+ <p v-for="item in state.questionDetail.trainPracticeSourcewareDtos" @click="onSourcewareTo(item)">{{item.name}}</p>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-card>
|
|
|
+ </pane>
|
|
|
+ </splitpanes>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="trainStudy">
|
|
|
+import { defineAsyncComponent, nextTick, onMounted, reactive, ref, computed } from 'vue';
|
|
|
+import type { FormInstance } from 'element-plus';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import mittBus from '@/utils/mitt';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { Splitpanes, Pane } from 'splitpanes';
|
|
|
+import 'splitpanes/dist/splitpanes.css';
|
|
|
+import { formatDate } from '@/utils/formatTime';
|
|
|
+import { Local } from '@/utils/storage';
|
|
|
+import other from '@/utils/other';
|
|
|
+import { throttle, transformFile, guid } from '@/utils/tools';
|
|
|
+import { fileDownloadByUrl } from '@/api/public/file';
|
|
|
+import { getTrainInfo, answerUserTrain, getTrainQuestion, submitUserTrain } from '@/api/examTrain/userTrain';
|
|
|
+import { KnowledgeInfo} from '@/api/knowledge';
|
|
|
+
|
|
|
+const router = useRouter(); //路由
|
|
|
+const route = useRoute(); // 获取路由参数
|
|
|
+const horizontal = ref(false);
|
|
|
+
|
|
|
+// 定义变量内容
|
|
|
+const state = reactive<any>({
|
|
|
+ loading: false, // 加载状态
|
|
|
+ knowladgeLoading: false, // 问题加载状态
|
|
|
+ questionLoading: false, // 问题加载状态
|
|
|
+ trainTitle: '我的培训', // 培训标题
|
|
|
+ activeName: '0', //tab切换 默认关联知识
|
|
|
+ isContainsPractice: false, // 是否包含习题
|
|
|
+ trainPracticeKnowladgeDtos: [], // 知识库数据
|
|
|
+ KnowladgeInfo: null,
|
|
|
+ // trainPracticeSourcewareDtos: [], // 课件数据
|
|
|
+ trainPracticeQuestionsDtos: [], // 习题数据
|
|
|
+ questionsData: [] as any[], // 试题集合
|
|
|
+ selQuestionID: '', // 选择的问题id
|
|
|
+ questionDetail: null, // 选择的问题详情
|
|
|
+ selRadioValue: '', // 单选题选择的值
|
|
|
+ selCheckboxValue: [] as any[], // 多选题选择的值
|
|
|
+ answerValue: '', // 填空、问答填写的值
|
|
|
+});
|
|
|
+
|
|
|
+// 初始化数据
|
|
|
+const getInfo = async () => {
|
|
|
+ state.loading = true;
|
|
|
+ state.trainTitle = route.params.trainName;
|
|
|
+ console.log(state.trainName);
|
|
|
+ const res: any = await getTrainInfo(route.params.id);
|
|
|
+ state.trainPracticeKnowladgeDtos = res.result.knowladgeDtos;
|
|
|
+ // state.trainPracticeSourcewareDtos = res.result.trainPracticeSourcewareDtos;
|
|
|
+ state.trainPracticeQuestionsDtos = res.result.questionDtos;
|
|
|
+ state.loading = false;
|
|
|
+};
|
|
|
+// tab切换
|
|
|
+const tabChange = () => {
|
|
|
+
|
|
|
+};
|
|
|
+// 知识点击
|
|
|
+const onClickKnowladge = async(id: any) => {
|
|
|
+ state.knowladgeLoading = true;
|
|
|
+ const res: any = await KnowledgeInfo(id, { isAddPv: 'true' });
|
|
|
+ state.KnowladgeInfo = res.result ?? {};
|
|
|
+ state.KnowladgeInfo.files = transformFile(state.KnowladgeInfo.files);
|
|
|
+ state.knowladgeLoading = false;
|
|
|
+};
|
|
|
+// 知识预览
|
|
|
+const onPreview = (row: any) => {
|
|
|
+ router.push({
|
|
|
+ name: 'knowledgePreview',
|
|
|
+ params: {
|
|
|
+ id: row.id,
|
|
|
+ tagsViewName: '知识查看',
|
|
|
+ },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 问题点击
|
|
|
+// 选择的问题id 提交当前试题答案,再请求下一道题详情 value: 请求下一题详情id
|
|
|
+const onSelQuestion = async (value: any) => {
|
|
|
+ state.questionLoading = true;
|
|
|
+ if (state.questionDetail){
|
|
|
+ // 保存当前题目选项
|
|
|
+ const submitObj = {
|
|
|
+ trainRecordId: route.params.id,
|
|
|
+ trainPracticeId: state.questionDetail.id,
|
|
|
+ addTrainRecordOptionDtos: [] as any[],
|
|
|
+ addTrainRecordAnswerDto: null as any
|
|
|
+ };
|
|
|
+ if ([0,2].includes(state.questionDetail.questionType)){
|
|
|
+ if (state.selRadioValue){
|
|
|
+ submitObj.addTrainRecordOptionDtos.push({questionOptionId: state.selRadioValue})
|
|
|
+ }
|
|
|
+ }else if (state.questionDetail.questionType == 1){
|
|
|
+ if (state.selCheckboxValue.length > 0){
|
|
|
+ state.selCheckboxValue.forEach(it => {
|
|
|
+ submitObj.addTrainRecordOptionDtos.push({questionOptionId: it})
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }else if ([3,4].includes(state.questionDetail.questionType)){
|
|
|
+ submitObj.addTrainRecordAnswerDto = {answer: state.answerValue};
|
|
|
+ }
|
|
|
+ answerUserTrain(submitObj)
|
|
|
+ .then((res) => {
|
|
|
+ onGetQuestionDetail(value);
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ state.questionLoading = false;
|
|
|
+ });
|
|
|
+ }else {
|
|
|
+ onGetQuestionDetail(value);
|
|
|
+ }
|
|
|
+}
|
|
|
+const onGetQuestionDetail = async (value) => {
|
|
|
+ if (!value){
|
|
|
+ state.questionLoading = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 初始化选择选择/填写的答案
|
|
|
+ state.selRadioValue = '';
|
|
|
+ state.selCheckboxValue = [];
|
|
|
+ // 请求下一道题
|
|
|
+ state.selQuestionID = value;
|
|
|
+ try {
|
|
|
+ const { result } = await getTrainQuestion(route.params.id, state.selQuestionID);
|
|
|
+ state.questionDetail = result;
|
|
|
+ if ([0,2].includes(state.questionDetail.questionType)){
|
|
|
+ let obj = state.questionDetail.trainPracticeOptionsDtos.find((x: any) => x.isSelected === true);
|
|
|
+ state.selRadioValue = obj ? obj.questionOptionId : '';
|
|
|
+ }else if (state.questionDetail.questionType == 1){
|
|
|
+ let arr = [] as any[];
|
|
|
+ state.questionDetail.trainPracticeOptionsDtos.forEach(it => {if (it.isSelected) arr.push(it);})
|
|
|
+ state.selCheckboxValue = arr ? arr.map((x: any) => x.questionOptionId) : [];
|
|
|
+ console.log(state.selCheckboxValue)
|
|
|
+ }else {
|
|
|
+ state.answerValue = state.questionDetail.answer || '';
|
|
|
+ }
|
|
|
+ state.questionLoading = false;
|
|
|
+ } catch (error) {
|
|
|
+ // 打印错误信息
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+}
|
|
|
+// 跳转知识详情页面
|
|
|
+const onKnowladgeTo = (row: any) => {
|
|
|
+ router.push({
|
|
|
+ name: 'knowledgePreview',
|
|
|
+ params: {
|
|
|
+ id: row.knowladgeId,
|
|
|
+ isAddPv: 'isAddPv',
|
|
|
+ tagsViewName: row.title,
|
|
|
+ },
|
|
|
+ });
|
|
|
+};
|
|
|
+// 课件预览下载
|
|
|
+const onSourcewareTo = (row: any) => {
|
|
|
+ ElMessageBox.confirm(`您确定要下载课件,是否继续?`, '提示', {
|
|
|
+ confirmButtonText: '确认',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ draggable: true,
|
|
|
+ cancelButtonClass: 'default-button',
|
|
|
+ autofocus: false,
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ fileDownloadByUrl({
|
|
|
+ Source: 'hotline',
|
|
|
+ Id: row.attachmentId,
|
|
|
+ }).then((res: any) => {
|
|
|
+ let blob: Blob = new Blob([res.data], { type: res.data.type }); // 创建blob 设置blob文件类型 data 设置为后端返回的文件(例如mp3,jpeg) type:这里设置后端返回的类型 为 mp3
|
|
|
+ let down: HTMLAnchorElement = document.createElement('a'); // 创建A标签
|
|
|
+ let href: string = window.URL.createObjectURL(blob); // 创建下载的链接
|
|
|
+ down.href = href; // 下载地址
|
|
|
+ down.download = row.name // 下载文件名
|
|
|
+ document.body.appendChild(down);
|
|
|
+ down.click(); // 模拟点击A标签
|
|
|
+ document.body.removeChild(down); // 下载完成移除元素
|
|
|
+ window.URL.revokeObjectURL(href); // 释放blob对象
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+}
|
|
|
+// 结束培训
|
|
|
+const onSubmit = async () => {
|
|
|
+ state.loading = true;
|
|
|
+ await onSelQuestion('');
|
|
|
+ submitUserTrain({
|
|
|
+ id: route.params.id,
|
|
|
+ isComplete: true
|
|
|
+ }).then(() => {
|
|
|
+ mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
|
|
|
+ mittBus.emit('clearCache', 'userTrain');
|
|
|
+ router.push({
|
|
|
+ path: '/examTrain/train/userTrain',
|
|
|
+ });
|
|
|
+ state.loading = false;
|
|
|
+ }).catch(() => {
|
|
|
+ state.loading = false;
|
|
|
+ });
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ getInfo();
|
|
|
+});
|
|
|
+</script>
|
|
|
+<style lang="scss">
|
|
|
+ .knowladgeDtosItem{color: var(--el-color-primary);margin-bottom: 10px;cursor: pointer;}
|
|
|
+ .questionBox{padding: 20px 10px 0;}
|
|
|
+ .questionBox .questionTitle{margin-bottom: 10px;font-size: 16px;}
|
|
|
+ .questionBox .questionAnswer{padding: 0 20px;}
|
|
|
+ .questionBox .referenceAnswer{background-color: #ebf9ff;margin: 15px 40px 15px 30px;padding: 10px;}
|
|
|
+ .questionBox .referenceAnswer span{vertical-align: top;display: inline-block;}
|
|
|
+ .questionBox .referenceAnswer p{width: calc(100% - 80px);display: inline-block;}
|
|
|
+ .referenceAnswer{background-color: #ebf9ff;margin: 15px 40px 15px 30px;padding: 10px;}
|
|
|
+ .referenceAnswer span{display: block;}
|
|
|
+ .referenceAnswer p{width: calc(100% - 80px);display: block;margin-top: 10px;color: #1890ff;cursor: pointer;}
|
|
|
+ .el-card__header {padding: 7px 20px 8px;}
|
|
|
+ .card-header{text-align: center;position: relative;}
|
|
|
+ .headerText{font-size: 20px;color: #000;font-weight: bolder;}
|
|
|
+ .headerBtn{position: absolute;top: -7px;right: 0px;}
|
|
|
+</style>
|