trainChapter.vue 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. <template>
  2. <div class="chapter-container">
  3. <div v-if="!state.hasRead" class="chapter-describe">
  4. <div class="describe-cnt" v-html="state.readHtml"></div>
  5. <div class="describe-readed">
  6. <a class="readed-btn" :class="{ reading: state.readingTime > 0 }" @click="hasReadClick">知道了{{ state.readingTime > 0 ? ` (${state.readingTime}s)` : '' }}</a>
  7. </div>
  8. </div>
  9. <div v-if="state.hasRead && state.showCnt" class="chapter-box">
  10. <NavMenus :back-confirm="state.lastChapterNum != maxChapterNum"></NavMenus>
  11. <div class="chapter-content">
  12. <!-- 原本的左侧菜单 -->
  13. <!-- <div class="menu-box" :class="{ fold: state.menuFold }">
  14. <div class="menu-title">{{ state.chapterTree[state.currentStep]?.name ?? '' }}</div>
  15. <div class="menu-list">
  16. <el-scrollbar :max-height="state.menuListHeight">
  17. <el-steps direction="vertical" space="3rem" :active="state.currentStep">
  18. <el-step v-for="(item, idx) in state.chapterTree" :title="item.name" @click="menuClick($event, idx)">
  19. <template #icon>
  20. <div class="step-icon">
  21. <div class="step-icon-inner"></div>
  22. </div>
  23. </template>
  24. <template #description v-if="item.children?.length > 0">
  25. <el-steps
  26. direction="vertical"
  27. space="1.8rem"
  28. :active="idx < state.currentStep ? item.children.length : idx > state.currentStep ? -1 : state.activeSecondMenu"
  29. >
  30. <el-step v-for="item1 in item.children" :title="item1.name" @click="menuClick($event, idx, item1.id)">
  31. <template #icon><div class="step-icon-second"></div></template>
  32. </el-step>
  33. </el-steps>
  34. </template>
  35. </el-step>
  36. </el-steps>
  37. </el-scrollbar>
  38. <div class="menu-switch" @click="menuSwitch"></div>
  39. </div>
  40. </div> -->
  41. <!-- 新的左侧菜单 -->
  42. <div :class="'menuLeft ' + (menuLeftLook == true ? 'menuLeftOpenEvent' : 'menuLeftExitEvent')" >
  43. <div class="menuTitle" >
  44. <div class="logoLeft"></div>
  45. <div class="txtRight">知识学习</div>
  46. </div>
  47. <div class="menuList commonsScrollbarNew" >
  48. <div class="menuListMain" v-for="(item, index) of state.chapterTree" :key="index" >
  49. <div class="menuListpParent" @click="menuClick($event, index)" >
  50. <div class="menuListpParentLeft">
  51. <div class="logoMenu" ></div>
  52. <!-- <div class="logoTime" ></div> -->
  53. <!-- <div class="logoNo" ></div> -->
  54. </div>
  55. <div class="menuListpParentCenter">{{ item.name }} {{ item.name }} {{ item.name }}</div>
  56. <div class="menuListpParentRight" v-if="item.children != null && item.children != undefined && item.children.length > 0" >
  57. +
  58. </div>
  59. </div>
  60. <div class="menuListpChildren"
  61. @click="menuClick($event, index, item_1.id)"
  62. v-if="item.children?.length > 0" v-for="(item_1, index_1) of item.children" :key="index_1" >
  63. <div class="menuListpChildrenKong" ></div>
  64. <div class="menuListpChildrenLeft" >
  65. <!-- <div class="logoMenu" ></div>
  66. <div class="logoTime" ></div> -->
  67. <div class="logoNo" ></div>
  68. </div>
  69. <div class="menuListpChildrenRight">
  70. {{ item_1.name }} {{ item_1.name }}
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <div :class="'menuLook ' + (menuLeftLook == true ? 'menuLookOpenEvent ' : 'menuLookExit menuLookExitEvent')"
  77. @click="(menuLeftLook = menuLeftLook == true ? false : true)" ></div>
  78. <div v-if="state.currentChapter.type != 30" class="chapter-detail">
  79. <div class="detail-title">{{ state.currentChapter.name }}</div>
  80. <div class="detail-cnt" v-html="htmlContent"></div>
  81. </div>
  82. <!-- <div v-else class="chapter-3d">
  83. <CourseChapter3d :config="state.chapterTree[state.currentStep]?.threeDimensionalConfig ?? ''"></CourseChapter3d>
  84. <CourseChapter3dView></CourseChapter3dView>
  85. </div> -->
  86. </div>
  87. <!-- 后台交互,显示,上一步下一步逻辑UI -->
  88. <StepTips
  89. v-if="
  90. stepTipsLook == true
  91. && (
  92. (
  93. courseChapter3dShow().show.showToastViewBool == false
  94. && courseChapter3dShow().show.showLinkOkNextBool == false
  95. )
  96. || (
  97. courseChapter3dShow().show.showToastViewBool == true
  98. && courseChapter3dShow().show.showToastState == ''
  99. && courseChapter3dShow().show.showLinkOkNextBool == false
  100. )
  101. )
  102. "
  103. :msg="state.tipsMsg"
  104. :btns="state.tipsBtns"
  105. :countdown="tipsCountdown"
  106. :key="state.tipsKey"
  107. ></StepTips>
  108. </div>
  109. <!-- 3d组件放在这里,让它可以隐藏起来,提前加载好场景。要不然后续切换3d场景每次要重新加载,比较麻烦 -->
  110. <!-- <div v-show="state.hasRead && state.showCnt && state.currentChapter.type == 30" class="chapter-3d"> -->
  111. <div v-if="courseChapter3dViewBool == true" class="chapter-3d">
  112. <div v-show="state.hasRead && state.showCnt && state.currentChapter.type == 30">
  113. <!-- 老版本的三维逻辑了,用不到了 -->
  114. <!-- <CourseChapter3d
  115. :config="state?.currentChapter ?? ''"
  116. :studentTaskIdList="studentTaskIdList"
  117. @tipsBtnsUpOpen="tipsBtnsUpOpenEvent"
  118. @tipsBtnsDownOpen="tipsBtnsDownOpenEvent"
  119. ></CourseChapter3d>
  120. <CourseChapter3dView
  121. @showOperationHelpExitEvent="showOperationHelpExitEvent"
  122. @carCameraScreenshot="carCameraScreenshotEvent"
  123. @carShutDown="carShutDownEvent"
  124. ></CourseChapter3dView> -->
  125. <QingXiCheAndQvMain :initTime="initTimeQingXiCarAndQv" :processUiOpen="(stepTipsLook == true ? false : true)"
  126. @callbackProgress="callbackProgressQingXiCheAndQv" ></QingXiCheAndQvMain>
  127. </div>
  128. </div>
  129. </div>
  130. </template>
  131. <script setup lang="ts">
  132. import { onMounted, reactive, computed, watch, onUnmounted, nextTick, ref } from 'vue';
  133. import NavMenus from '../components/navMenus.vue';
  134. import StepTips from '../components/stepTips.vue';
  135. import { useRoute } from 'vue-router';
  136. import { getCourseInfo, getCourseChapterTree, studyReport, getCurrentChapter, updateProgress } from '@/api/student/trainChapter';
  137. import { studentTaskOptionPipDefectListByStudentId } from '@/api/student/studentTaskOptionPipDefect';
  138. import CourseChapter3d from '@/components/student/CourseChapter3d.vue';
  139. import CourseChapter3dView from '@/components/student/courseChapter3d/view.vue';
  140. import QingXiCheAndQvMain from '@/components/ThreeWorldEventQingXiCheAndQv/QingXiCheAndQvMain.vue';
  141. import { getTrainDetail } from '@/api/student/trainList';
  142. import router from '@/router/index';
  143. import { courseChapter3dShow } from '@/stores/courseChapter3dShow.ts';
  144. import { threeWorld } from '@/stores/threeWorld.ts';
  145. import { uploadFileOss, getLiteMeta } from '../../../util/fileUtils';
  146. import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
  147. import { studentTaskPhoto } from '@/api/techer/taskStudentScore';
  148. import { ModelHtml5Audio } from "../../../components/student/event/ModelHtml5Audio.ts";
  149. const route = useRoute();
  150. const taskId = route.params.taskId as string;
  151. const studentTaskId = route.params.studentTaskId as string;
  152. const state: anyObj = reactive({
  153. hasRead: true,
  154. readingTime: 10,
  155. readHtml: '',
  156. showCnt: false,
  157. menuFold: false,
  158. menuListHeight: ``,
  159. tipsKey: 0,
  160. tipsMsg: '',
  161. tipsBtns: [],
  162. chapterTree: [],
  163. currentStep: 0,
  164. currentNode: 0,
  165. currentChapter: { type: 10 },
  166. activeSecondMenu: -1,
  167. lastChapterNum: -1,
  168. });
  169. // 控制左侧菜单是否展示
  170. let menuLeftLook = ref(true)
  171. // 播放声音
  172. let objAudio = new ModelHtml5Audio();
  173. let chapterQueue: Array<Array<anyObj>> = [];
  174. let maxChapterNum = 0;
  175. // 获取当前学生任务ID缺陷列表
  176. let studentTaskIdList: any = ref([]);
  177. // 用于控制3d场景什么时候加载显示
  178. let courseChapter3dViewBool = ref(false);
  179. /**
  180. * 因为引入了其他三维场景
  181. * 三维场景里,自带了上一步,下一步的逻辑UI
  182. * 所以控制,原先前端UI的 上一步,下一步的UI逻辑是否显示还是隐藏
  183. */
  184. let stepTipsLook = ref(true);
  185. // 控制清洗车回到第一个流程逻辑
  186. let initTimeQingXiCarAndQv = ref("0");
  187. const buildChapterQueue = (data: anyObj[], optionChapters: anyObj[], root = true): anyObj[] => {
  188. let chapters: anyObj[] = [];
  189. data?.forEach((item, idx) => {
  190. const children = item.children;
  191. //delete item.children;
  192. const opt = optionChapters.find((x) => x.chapterId == item.id);
  193. if (opt) item.durationStudyMin = opt.durationStudyMin;
  194. chapters.push(item);
  195. if (children && children.length > 0) {
  196. const childs = buildChapterQueue(children, optionChapters, false);
  197. chapters = chapters.concat(childs);
  198. }
  199. if (root) {
  200. chapterQueue[idx] = [...chapters];
  201. chapters = [];
  202. }
  203. });
  204. if (root) maxChapterNum = (chapterQueue.length - 1) * 100 + chapterQueue[chapterQueue.length - 1].length - 1;
  205. return chapters;
  206. };
  207. const menuSwitch = () => {
  208. state.menuFold = !state.menuFold;
  209. };
  210. const menuClick = (evt: Event, step: number, nodeId?: string) => {
  211. if ((evt.target as HTMLElement).classList.contains('el-step__title')) {
  212. let chapterNum = step * 100,
  213. node = 0;
  214. if (nodeId) {
  215. node = chapterQueue[step].findIndex((x) => x.id == nodeId);
  216. if (node > -1) chapterNum += node;
  217. }
  218. if (chapterNum <= state.lastChapterNum) {
  219. state.currentStep = step;
  220. state.currentNode = node;
  221. state.currentChapter = chapterQueue[step][node];
  222. } else {
  223. ElMessage({
  224. message: '该章节还未完成学习,请先按顺序完成学习!',
  225. type: 'warning',
  226. });
  227. }
  228. }
  229. evt.stopPropagation();
  230. };
  231. const hasReadClick = () => {
  232. state.hasRead = true;
  233. state.showCnt = true;
  234. setMenuListHeight();
  235. };
  236. const setMenuListHeight = () => {
  237. nextTick(() => {
  238. let height = document.querySelector('.menu-box')?.clientHeight ?? 0;
  239. if (height) state.menuListHeight = `calc(${height}px - 12rem)`;
  240. });
  241. };
  242. const countdown = (name: string, callback?: Function) => {
  243. if (state[name]) {
  244. const interval = setInterval(() => {
  245. state[name]--;
  246. if (state[name] == 0) {
  247. clearInterval(interval);
  248. if (callback) callback();
  249. }
  250. }, 1000);
  251. }
  252. };
  253. const tipsCountdown = computed(() => {
  254. let countdown = state.currentChapter.durationStudyMin ?? 0;
  255. const chapterNum = state.currentStep * 100 + state.currentNode;
  256. if (chapterNum <= state.lastChapterNum) countdown = 0;
  257. return countdown;
  258. });
  259. const htmlContent = computed(() => {
  260. if (state.currentChapter.type == 10) {
  261. return state.currentChapter.richText;
  262. } else if (state.currentChapter.type == 20) {
  263. return `<iframe src='${state.currentChapter.videoUrl}' width='100%' height='99%' scrolling='no' frameborder='0'><iframe>`;
  264. } else return '';
  265. });
  266. const prevStep = () => {
  267. if (state.currentNode == 0) {
  268. state.currentStep--;
  269. state.currentNode = chapterQueue[state.currentStep].length - 1;
  270. } else state.currentNode--;
  271. state.currentChapter = chapterQueue[state.currentStep][state.currentNode];
  272. };
  273. const nextStep = () => {
  274. updateStudyProgress();
  275. state.currentNode++;
  276. if (state.currentNode >= chapterQueue[state.currentStep].length) {
  277. state.currentStep++;
  278. state.currentNode = 0;
  279. }
  280. state.currentChapter = chapterQueue[state.currentStep][state.currentNode];
  281. };
  282. const completeStudy = () => {
  283. updateStudyProgress();
  284. router.push({ path: `/train/main/${taskId}` });
  285. };
  286. const updateStudyProgress = () => {
  287. //更新学习进度
  288. const num = state.currentStep * 100 + state.currentNode;
  289. if (num > state.lastChapterNum) {
  290. state.lastChapterNum = num;
  291. updateProgress(studentTaskId, state.currentChapter.id);
  292. }
  293. };
  294. const initTips = () => {
  295. state.tipsKey++;
  296. state.tipsMsg = state.currentChapter.tips;
  297. // 得到三维的参数
  298. let threeDimensionalConfig = state.currentChapter.threeDimensionalConfig;
  299. console.log("获取课程树 ---", state.chapterTree)
  300. console.log(
  301. "initTips ===> 每次进入不同流程后,进行处理的逻辑",
  302. state.tipsMsg, state.currentChapter, threeDimensionalConfig
  303. );
  304. // /**
  305. // * 因为加入了其他三维场景
  306. // * 他们有自带的上一步,下一步的UI控制
  307. // * 所以这里进行特殊处理
  308. // */
  309. // if (typeof threeDimensionalConfig == "string" && threeDimensionalConfig == "仿真模拟_清洗车") {
  310. // var thisTime = new Date().getTime();
  311. // initTimeQingXiCarAndQv.value = "" + thisTime;
  312. // // 此时进入三维场景,就默认进入到最后一个步骤
  313. // state.tipsBtns = [
  314. // {
  315. // name: '上一步',
  316. // click: prevStep,
  317. // attr: { type: 'primary', plain: 'plain' },
  318. // },
  319. // {
  320. // name: '完成并前往编制报告',
  321. // click: completeStudy,
  322. // attr: { type: 'primary' },
  323. // },
  324. // ];
  325. // // 同时先隐藏UI
  326. // stepTipsLook.value = false;
  327. // return;
  328. // }
  329. // 显示UI
  330. stepTipsLook.value = true;
  331. if (state.currentStep == 0 && state.currentNode == 0) {
  332. state.tipsBtns = [
  333. {
  334. name: '下一步',
  335. click: nextStep,
  336. attr: { type: 'primary' },
  337. },
  338. ];
  339. } else if (state.currentStep == chapterQueue.length - 1 && state.currentNode == chapterQueue[state.currentStep].length - 1) {
  340. state.tipsBtns = [
  341. {
  342. name: '上一步',
  343. click: prevStep,
  344. attr: { type: 'primary', plain: 'plain' },
  345. },
  346. {
  347. name: '完成并前往编制报告',
  348. click: completeStudy,
  349. attr: { type: 'primary' },
  350. },
  351. ];
  352. } else {
  353. state.tipsBtns = [
  354. {
  355. name: '上一步',
  356. click: prevStep,
  357. attr: { type: 'primary', plain: 'plain' },
  358. },
  359. {
  360. name: '下一步',
  361. click: nextStep,
  362. attr: { type: 'primary' },
  363. },
  364. ];
  365. }
  366. /**
  367. * 因为加入了其他三维场景
  368. * 他们有自带的上一步,下一步的UI控制
  369. * 所以这里进行特殊处理
  370. */
  371. if (typeof threeDimensionalConfig == "string" && threeDimensionalConfig == "仿真模拟_清洗车") {
  372. var thisTime = new Date().getTime();
  373. initTimeQingXiCarAndQv.value = "" + thisTime;
  374. // 隐藏 上一步,下一步 UI
  375. stepTipsLook.value = false;
  376. }
  377. };
  378. let studyInterval: NodeJS.Timeout;
  379. // 开场音乐
  380. let musicIndex = new URL("./../../../assets/music/index.mp3", import.meta.url).href;
  381. onMounted(() => {
  382. // // 第一次进来播放指定的声音基本上
  383. // objAudio.pause();
  384. // // 设置播放音乐的文件,或者地址
  385. // objAudio.setUrl(musicIndex);
  386. // // 不需要循环播放
  387. // objAudio.loopClose();
  388. // // 继续播放音乐
  389. // objAudio.start();
  390. threeWorld().loadSuccess = false;
  391. courseChapter3dViewBool.value = false;
  392. //学习时间上报
  393. studyInterval = setInterval(() => {
  394. const type = state.currentChapter.type != 30 ? 1 : 2;
  395. studyReport(studentTaskId, type);
  396. }, 1000 * 100);
  397. //获取任务信息
  398. getTrainDetail(taskId).then((res) => {
  399. //获取课程信息
  400. getCourseInfo(res.data.data.courseId).then((res1) => {
  401. if (res1.data.data.description) {
  402. state.hasRead = false;
  403. state.readHtml = res1.data.data.description;
  404. countdown('readingTime');
  405. } else {
  406. state.hasRead = true;
  407. state.showCnt = true;
  408. setMenuListHeight();
  409. }
  410. window.addEventListener('resize', () => {
  411. setMenuListHeight();
  412. });
  413. });
  414. //获取课程树
  415. getCourseChapterTree(res.data.data.courseId).then((res2) => {
  416. state.chapterTree = res2.data.data;
  417. buildChapterQueue(res2.data.data, res.data.data.optionChapters);
  418. //需先根据学习进度确定开始章节:currentStep和currentNode,默认第一章第一节
  419. //获取学习到的章节
  420. getCurrentChapter(studentTaskId).then((res3) => {
  421. const chapterId = res3.data.data;
  422. if (chapterId) {
  423. for (let i = 0; i < chapterQueue.length; i++) {
  424. const idx = chapterQueue[i].findIndex((x) => x.id == chapterId);
  425. if (idx > -1) {
  426. state.currentStep = i;
  427. state.currentNode = idx;
  428. break;
  429. }
  430. }
  431. state.lastChapterNum = state.currentStep * 100 + state.currentNode;
  432. }
  433. state.currentChapter = chapterQueue[state.currentStep][state.currentNode];
  434. initTips();
  435. });
  436. });
  437. // 根据学生id任务获取缺陷数据
  438. studentTaskOptionPipDefectListByStudentId(studentTaskId)
  439. .then((studentTaskOptionPipDefectListByStudentIdRes) => {
  440. studentTaskIdList.value = studentTaskOptionPipDefectListByStudentIdRes?.data?.data;
  441. courseChapter3dViewBool.value = true;
  442. // console.log(
  443. // "根据学生id任务获取缺陷数据",
  444. // studentTaskIdList.value,
  445. // studentTaskId
  446. // );
  447. })
  448. .catch((error) => {
  449. courseChapter3dViewBool.value = true;
  450. });
  451. });
  452. });
  453. onUnmounted(() => {
  454. if (studyInterval) clearInterval(studyInterval);
  455. });
  456. watch(
  457. () => state.currentChapter,
  458. (newVal) => {
  459. initTips();
  460. //滚动当前课程菜单至可视区域
  461. nextTick(() => {
  462. const target = document.querySelector('.el-step__description .is-process') as HTMLElement;
  463. target?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  464. });
  465. //课程菜单默认显示二级,兼容课程树层级大于二级的情况
  466. if (state.currentNode == 0) state.activeSecondMenu = -1;
  467. else {
  468. const idx = (state.chapterTree[state.currentStep].children as anyObj[])?.findIndex((x) => x.id == newVal.id);
  469. if (idx > -1) state.activeSecondMenu = idx;
  470. }
  471. }
  472. );
  473. /**
  474. * 检测实训注意事项 知道了点击回调
  475. */
  476. courseChapter3dShow().show.showMatterGotItCallback = function () {
  477. // console.log(
  478. // "检测实训注意事项 知道了点击回调", res
  479. // );
  480. // 触发下一步逻辑
  481. nextStep();
  482. };
  483. /**
  484. * 监听弹出提示隐藏后来触发不同的逻辑
  485. */
  486. watch(
  487. () => courseChapter3dShow().show.showToastViewBool,
  488. (newVal, oldVal) => {
  489. if (courseChapter3dShow().show.showToastViewBool == true) {
  490. return;
  491. }
  492. // console.log(
  493. // " () => courseChapter3dShow().show.showToastState 1111111111 ",
  494. // courseChapter3dShow().show.showToastState
  495. // );
  496. switch (courseChapter3dShow().show.showToastState) {
  497. case '立即穿戴':
  498. // // 触发下一步逻辑
  499. // nextStep();
  500. break;
  501. }
  502. }
  503. );
  504. // 记录 tipsBtnsUpOpenEvent 事件是否显示上一步按钮
  505. let tipsBtnsUpOpenEventBool: Boolean = true;
  506. /**
  507. * 上一步是否显示
  508. * open true - 显示, false - 隐藏
  509. */
  510. const tipsBtnsUpOpenEvent = (open: Boolean) => {
  511. // console.log("上一步是否显示", open);
  512. tipsBtnsUpOpenEventBool = open;
  513. tipsBtnsUpAndDownOpenEvent();
  514. };
  515. // 记录 tipsBtnsUpOpenEvent 事件是否显示下一步按钮
  516. let tipsBtnsDownOpenEventBool: Boolean = true;
  517. /**
  518. * 下一步是否显示
  519. * open true - 显示, false - 隐藏
  520. */
  521. const tipsBtnsDownOpenEvent = (open: Boolean) => {
  522. // console.log("下一步是否显示", open);
  523. tipsBtnsDownOpenEventBool = open;
  524. tipsBtnsUpAndDownOpenEvent();
  525. };
  526. /**
  527. * 通过3d传来的事件来控制上一步下一步是否显示逻辑
  528. */
  529. const tipsBtnsUpAndDownOpenEvent = () => {
  530. let newArray = [];
  531. if (tipsBtnsUpOpenEventBool == true) {
  532. // newArray.push({
  533. // name: '上一步',
  534. // click: prevStep,
  535. // attr: { type: 'primary', plain: 'plain' },
  536. // });
  537. }
  538. if (tipsBtnsDownOpenEventBool == true) {
  539. newArray.push({
  540. name: '下一步',
  541. click: nextStep,
  542. attr: { type: 'primary' },
  543. });
  544. }
  545. // console.log(
  546. // " ========= state.tipsBtns ========= ",
  547. // state.tipsBtns
  548. // );
  549. // 如果出现 完成并前往编制报告 则不替换
  550. try {
  551. for (let i = 0; i < state.tipsBtns.length; i++) {
  552. let thisTipsBtns = state.tipsBtns[i];
  553. if (thisTipsBtns.name.indexOf('完成') >= 0) {
  554. return;
  555. }
  556. }
  557. } catch (e) {}
  558. state.tipsBtns = newArray;
  559. };
  560. // 操作帮助点击退出
  561. const showOperationHelpExitEvent = () => {
  562. // 触发下一步逻辑
  563. nextStep();
  564. };
  565. /**
  566. * 车的完成下井实验
  567. */
  568. const carShutDownEvent = () => {
  569. // 触发下一步逻辑
  570. nextStep();
  571. };
  572. /**
  573. * 车的相机截图
  574. * img base64位图片
  575. */
  576. const carCameraScreenshotEvent = (img: any) => {
  577. // console.log(
  578. // "车的相机截图", img
  579. // );
  580. // 将 base64为转换成,Blob格式
  581. let objBlob = dataURLtoBlob(img);
  582. // blob 转换成 file对象
  583. const file = new File([objBlob], 'example.png', { type: 'text/plain' });
  584. // 优先上传文件
  585. // @ts-ignore
  586. uploadFileOss(file, 'course/cover', null)
  587. .then(function (e: any) {
  588. // console.log(
  589. // "上传文件", e
  590. // );
  591. studentTaskPhoto({
  592. studentTaskId: studentTaskId,
  593. imageUrl: e,
  594. remark: '',
  595. })
  596. .then(function (studentTaskPhotoRes: any) {
  597. // console.log(
  598. // "上传文件成功", studentTaskPhotoRes
  599. // );
  600. // ElMessage({
  601. // message: '截图上传完成',
  602. // type: 'success',
  603. // });
  604. courseChapter3dShow().show.showToastViewBool = false;
  605. courseChapter3dShow().show.showPromptEvent('截图上传完成', null, function (res: any) {
  606. // console.log("yes", res);
  607. });
  608. })
  609. .catch(function (studentTaskPhotoResError: any) {
  610. // ElMessage({
  611. // message: '上传失败',
  612. // type: 'warning',
  613. // });
  614. courseChapter3dShow().show.showToastViewBool = false;
  615. courseChapter3dShow().show.showPromptEvent('上传失败', null, function (res: any) {
  616. // console.log("yes", res);
  617. });
  618. });
  619. })
  620. .catch(function (e: any) {
  621. // ElMessage({
  622. // message: e || '上传失败',
  623. // type: 'warning',
  624. // });
  625. courseChapter3dShow().show.showToastViewBool = false;
  626. courseChapter3dShow().show.showPromptEvent('上传失败', null, function (res: any) {
  627. // console.log("yes", res);
  628. });
  629. });
  630. };
  631. /**
  632. * 将base64转换为blob
  633. * @param dataurl base64位图片
  634. */
  635. const dataURLtoBlob = (dataurl: any) => {
  636. let arr = dataurl.split(','),
  637. mime = arr[0].match(/:(.*?);/)[1],
  638. bstr = atob(arr[1]),
  639. n = bstr.length,
  640. u8arr = new Uint8Array(n);
  641. while (n--) {
  642. u8arr[n] = bstr.charCodeAt(n);
  643. }
  644. return new Blob([u8arr], { type: mime });
  645. };
  646. /**
  647. * 清洗车自定义回调步骤
  648. */
  649. const callbackProgressQingXiCheAndQv = (json : any) => {
  650. console.log("callbackProgressQingXiCheAndQv ===>", json);
  651. // 完成所有步骤
  652. if (json.name == "equipmentRepositioningWork") {
  653. stepTipsLook.value = true;
  654. }
  655. }
  656. </script>
  657. <style lang="scss" scoped>
  658. @use './css/animationEvent.scss';
  659. @use './css/commonsScrollbarNew.scss';
  660. .chapter-container {
  661. position: absolute;
  662. width: 100%;
  663. height: 100%;
  664. display: flex;
  665. justify-content: center;
  666. align-items: center;
  667. z-index: 1;
  668. .chapter-describe {
  669. position: relative;
  670. width: 70%;
  671. height: 90%;
  672. background-image: url(/src/assets/student/bg_describe.png);
  673. background-size: 100% 100%;
  674. &:before {
  675. content: '';
  676. position: absolute;
  677. top: 3%;
  678. left: 28%;
  679. height: 6%;
  680. width: 70%;
  681. background-image: url(/src/assets/student/training.png);
  682. background-size: auto 100%;
  683. background-repeat: no-repeat;
  684. }
  685. .describe-cnt {
  686. overflow: auto;
  687. box-sizing: border-box;
  688. position: absolute;
  689. inset: 10rem 0 10rem 6rem;
  690. padding-right: 4rem;
  691. }
  692. .describe-readed {
  693. position: absolute;
  694. bottom: 3rem;
  695. width: 100%;
  696. display: flex;
  697. justify-content: center;
  698. .readed-btn {
  699. width: 20rem;
  700. height: 5.4rem;
  701. line-height: 5.4rem;
  702. text-align: center;
  703. color: white;
  704. font-size: 2rem;
  705. border-radius: 0.8rem;
  706. background: linear-gradient(90deg, #6cd9e9 0%, #4b90dd 100%);
  707. cursor: pointer;
  708. &.reading {
  709. pointer-events: none;
  710. }
  711. }
  712. }
  713. }
  714. .chapter-box {
  715. width: 100%;
  716. height: 100%;
  717. }
  718. .chapter-content {
  719. position: absolute;
  720. width: 100%;
  721. height: 100%;
  722. .menu-box {
  723. position: absolute;
  724. top: 3.2rem;
  725. left: 2rem;
  726. width: 20rem;
  727. height: calc(100% - 18rem);
  728. transition-property: left;
  729. transition-duration: 0.6s;
  730. z-index: 2;
  731. &.fold {
  732. left: -20rem;
  733. .menu-switch {
  734. background-image: url(/src/assets/student/training/menu_switch1.png);
  735. }
  736. }
  737. .menu-switch {
  738. position: absolute;
  739. width: 2rem;
  740. height: 5rem;
  741. background-image: url(/src/assets/student/training/menu_switch.png);
  742. background-size: 100% 100%;
  743. right: -2rem;
  744. top: 50%;
  745. margin-top: -2.5rem;
  746. cursor: pointer;
  747. }
  748. .menu-title {
  749. height: 5rem;
  750. background-image: url(/src/assets/student/training/menu_title.png);
  751. background-size: 100% 100%;
  752. color: white;
  753. // font-size: 2rem;
  754. font-size: 1.8rem;
  755. overflow: hidden;
  756. &:before {
  757. content: '';
  758. display: inline-block;
  759. vertical-align: middle;
  760. height: 5rem;
  761. width: 8rem;
  762. background-image: url(/src/assets/student/training/menu_icon.png);
  763. background-size: auto 50%;
  764. background-repeat: no-repeat;
  765. background-position: center center;
  766. }
  767. }
  768. .menu-list {
  769. position: relative;
  770. min-height: 20rem;
  771. max-height: calc(100% - 6rem);
  772. margin-top: 1rem;
  773. padding: 3rem 0 3rem 2rem;
  774. background-image: url(/src/assets/student/training/menu_list.png);
  775. background-size: 100% 100%;
  776. box-sizing: border-box;
  777. --step-item-size: 2rem;
  778. --step-icon-size: 1.2rem;
  779. --step-line-left: 0.9rem;
  780. --step-border-width: 0.2rem;
  781. .step-icon {
  782. width: var(--step-item-size);
  783. height: var(--step-item-size);
  784. border-radius: 50%;
  785. border: var(--step-border-width) solid #5faaf4;
  786. box-sizing: border-box;
  787. transition: 0.15s ease-out;
  788. display: flex;
  789. justify-content: center;
  790. align-items: center;
  791. .step-icon-inner {
  792. width: var(--step-icon-size);
  793. height: var(--step-icon-size);
  794. background-size: 100% 100%;
  795. }
  796. }
  797. :deep(.el-step__head) {
  798. width: var(--step-item-size);
  799. .el-step__line {
  800. width: var(--step-border-width);
  801. top: var(--step-item-size);
  802. left: var(--step-line-left);
  803. background-color: #5faaf4;
  804. }
  805. .el-step__icon {
  806. width: unset;
  807. height: unset;
  808. background: transparent;
  809. display: flex;
  810. }
  811. &.is-process .step-icon-inner {
  812. background-image: url(/src/assets/student/training/step_process.png);
  813. }
  814. &.is-finish .step-icon-inner {
  815. background-image: url(/src/assets/student/training/step_finish.png);
  816. }
  817. }
  818. :deep(.el-step__main) {
  819. padding-left: 2rem;
  820. padding-right: 2rem;
  821. .el-step__title {
  822. // font-size: var(--step-item-size);
  823. font-size: 1.5rem;
  824. color: white;
  825. font-weight: unset;
  826. line-height: var(--step-item-size);
  827. cursor: pointer;
  828. &.is-process,
  829. &.is-finish {
  830. text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
  831. background: linear-gradient(180deg, #82ccdd 0%, #589cf2 100%);
  832. background-clip: text;
  833. -webkit-background-clip: text;
  834. -webkit-text-fill-color: transparent;
  835. }
  836. }
  837. .el-step__description {
  838. margin-left: -1.1rem;
  839. // --step-item-size: 0.9rem;
  840. --step-item-size: 1.1rem;
  841. --step-icon-size: 0.6rem;
  842. .el-steps {
  843. margin: 0.8rem 0;
  844. }
  845. .el-step__head {
  846. width: var(--step-icon-size);
  847. &.is-process .step-icon-second {
  848. background-color: #5ff49b;
  849. }
  850. &.is-finish .step-icon-second {
  851. background-color: #5fa9f4;
  852. }
  853. }
  854. .el-step__icon {
  855. height: var(--step-item-size);
  856. line-height: var(--step-item-size);
  857. }
  858. .step-icon-second {
  859. width: var(--step-icon-size);
  860. height: var(--step-icon-size);
  861. border-radius: 50%;
  862. box-sizing: border-box;
  863. background-color: #ccc;
  864. }
  865. .el-step__line {
  866. width: 0.1rem;
  867. left: 0.25rem;
  868. top: 0.6rem;
  869. bottom: -0.4rem;
  870. margin-bottom: unset !important;
  871. &:after {
  872. display: none;
  873. }
  874. }
  875. .el-step:last-of-type .el-step__line {
  876. display: none;
  877. }
  878. .el-step__main {
  879. padding-left: 0.6rem;
  880. }
  881. .el-step__title {
  882. font-size: var(--step-item-size);
  883. &.is-process {
  884. color: #5ff49b;
  885. -webkit-text-fill-color: unset;
  886. }
  887. &.is-finish {
  888. color: #5fa9f4;
  889. -webkit-text-fill-color: unset;
  890. }
  891. }
  892. }
  893. }
  894. :deep(.el-step:last-of-type) {
  895. .el-step__line {
  896. display: block;
  897. margin-bottom: 1rem;
  898. &:after {
  899. content: '';
  900. position: absolute;
  901. border: 0.8rem solid transparent;
  902. border-top-color: #5faaf4;
  903. bottom: -1rem;
  904. left: -0.7rem;
  905. }
  906. .el-step__line-inner {
  907. transition-delay: 150ms;
  908. border-width: 0px;
  909. height: 0%;
  910. }
  911. }
  912. }
  913. }
  914. }
  915. .chapter-detail {
  916. position: absolute;
  917. top: 3.2rem;
  918. left: 28rem;
  919. width: 88rem;
  920. height: 56rem;
  921. max-height: calc(100% - 12rem);
  922. background-image: url(/src/assets/student/training/window_bg.png);
  923. background-size: 100% 100%;
  924. .detail-title {
  925. height: 18%;
  926. font-size: 3rem;
  927. text-align: center;
  928. color: white;
  929. display: flex;
  930. justify-content: center;
  931. align-items: center;
  932. }
  933. .detail-cnt {
  934. height: 76%;
  935. padding: 0 5rem;
  936. box-sizing: border-box;
  937. overflow: auto;
  938. :deep(img) {
  939. max-width: 100%;
  940. }
  941. }
  942. }
  943. // 左侧菜单
  944. .menuLeft {
  945. position: fixed;
  946. top: 10.92rem;
  947. bottom: 18rem;
  948. left: 2.28rem;
  949. width: 21.64rem;
  950. z-index: 2;
  951. .menuTitle {
  952. position: absolute;
  953. width: 100%;
  954. top: 0px;
  955. left: 0px;
  956. height: 5.71rem;
  957. background-image: url(/src/assets/menuLeft/title.webp);
  958. background-size: 100% 100%;
  959. .logoLeft, .txtRight {
  960. position: absolute;
  961. }
  962. .logoLeft {
  963. width: 2.28rem;
  964. height: 2.92rem;
  965. background-image: url(/src/assets/menuLeft/gps.webp);
  966. background-size: 100% 100%;
  967. top: 1.6rem;
  968. left: 2.07rem;
  969. }
  970. .txtRight {
  971. top: 0px;
  972. height: 5.71rem;
  973. line-height: 5.71rem;
  974. left: 5.64rem;
  975. right: 3rem;
  976. font-weight: 400;
  977. font-size: 2.5rem;
  978. color: #FFFFFF;
  979. white-space: nowrap; /* 不换行 */
  980. overflow: hidden; /* 隐藏超出部分 */
  981. text-overflow: ellipsis; /* 显示省略号 */
  982. }
  983. }
  984. .menuList {
  985. position: absolute;
  986. width: 100%;
  987. top: 7rem;
  988. bottom: 0px;
  989. background-image: url(/src/assets/menuLeft/menuBottom.webp);
  990. background-size: 100% 100%;
  991. padding: 1rem 0.5rem 1rem 0.5rem;
  992. .menuListMain {
  993. position: relative;
  994. width: 100%;
  995. height: auto;
  996. .menuListpParent, .menuListpChildren {
  997. position: relative;
  998. width: 90%;
  999. left: 5%;
  1000. margin: 0.35rem 0rem 0.35rem 0rem;
  1001. padding: 0.35rem 0rem 0.35rem 0rem;
  1002. // 手势
  1003. cursor:pointer;
  1004. }
  1005. .logoMenu {
  1006. width: 1rem;
  1007. height: 1rem;
  1008. background-image: url(/src/assets/menuLeft/yes.webp);
  1009. background-size: 100% 100%;
  1010. }
  1011. .logoTime {
  1012. width: 1rem;
  1013. height: 1rem;
  1014. background-image: url(/src/assets/menuLeft/time.webp);
  1015. background-size: 100% 100%;
  1016. }
  1017. .logoNo {
  1018. width: 1rem;
  1019. height: 1rem;
  1020. background-image: url(/src/assets/menuLeft/no.webp);
  1021. background-size: 100% 100%;
  1022. }
  1023. .menuListpParent {
  1024. font-weight: 400;
  1025. font-size: 1.28rem;
  1026. color: #C8C8C8;
  1027. background: #386064;
  1028. height: 2.5rem;
  1029. .menuListpParentLeft, .menuListpParentCenter, .menuListpParentRight {
  1030. position: absolute;
  1031. height: 100%;
  1032. top: 0px;
  1033. }
  1034. .menuListpParentLeft {
  1035. left: 0px;
  1036. width: 2.5rem;
  1037. /* 水平垂直居中,支持文字自动换行 */
  1038. display: flex;
  1039. flex-direction: column;
  1040. justify-content: center;
  1041. align-items: center;
  1042. }
  1043. .menuListpParentCenter {
  1044. left: 2.5rem;
  1045. right: 2.5rem;
  1046. line-height: 2.5rem;
  1047. white-space: nowrap; /* 不换行 */
  1048. overflow: hidden; /* 隐藏超出部分 */
  1049. text-overflow: ellipsis; /* 显示省略号 */
  1050. }
  1051. .menuListpParentRight {
  1052. width: 2.5rem;
  1053. right: 0px;
  1054. background: #2BA987;
  1055. color: #FFFFFF;
  1056. font-size: 2rem;
  1057. /* 水平垂直居中,支持文字自动换行 */
  1058. display: flex;
  1059. flex-direction: column;
  1060. justify-content: center;
  1061. align-items: center;
  1062. }
  1063. }
  1064. .menuListpParentYes {
  1065. color: #ffffff;
  1066. }
  1067. .menuListpParentSelect {
  1068. color: #ffffff;
  1069. }
  1070. .menuListpChildren {
  1071. font-weight: 400;
  1072. font-size: 1.14rem;
  1073. color: #C8C8C8;
  1074. .menuListpChildrenKong, .menuListpChildrenLeft, .menuListpChildrenRight {
  1075. display: inline-flex;
  1076. }
  1077. .menuListpChildrenKong {
  1078. width: 1%;
  1079. }
  1080. .menuListpChildrenLeft {
  1081. width: 16%;
  1082. /* 水平垂直居中,支持文字自动换行 */
  1083. flex-direction: column;
  1084. justify-content: center;
  1085. align-items: center;
  1086. }
  1087. .menuListpChildrenRight {
  1088. width: 83%;
  1089. }
  1090. }
  1091. .menuListpChildrenYes {
  1092. color: #2CD36B;
  1093. }
  1094. .menuListpChildrenSelect {
  1095. color: #FFFFFF;
  1096. background: linear-gradient( 90deg, rgba(39,171,151,0.6) 0%, rgba(39,171,151,0.2) 100%);
  1097. }
  1098. }
  1099. }
  1100. }
  1101. .menuLook {
  1102. position: fixed;
  1103. width: 2rem;
  1104. height: 5.71rem;
  1105. left: 23.5rem;
  1106. top: 50%;
  1107. margin: -2.855rem 0px 0px 0px;
  1108. z-index: 2;
  1109. background-image: url(/src/assets/menuLeft/menuOpen.webp);
  1110. background-size: 100% 100%;
  1111. // 手势
  1112. cursor:pointer;
  1113. }
  1114. .menuLookExit {
  1115. background-image: url(/src/assets/menuLeft/menuExit.webp);
  1116. background-size: 100% 100%;
  1117. }
  1118. }
  1119. }
  1120. </style>