trainChapter.vue 37 KB

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