trainChapter.vue 39 KB

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