trainChapter.vue 41 KB

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