index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <div class="m-right-block mh576">
  3. <ICourseInfoXsExtra v-if="tpl.isTranExam==2"
  4. :extraXs="extraXs" :info="info"
  5. @updateInfo="updateInfo"
  6. :tpl="tpl" @startExamTest="startExamTest" @startExam="startExam"></ICourseInfoXsExtra>
  7. <ICourseInfoTest v-else-if="tpl.isTranExam>0" :examList="examList" :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfoTest>
  8. <ICourseInfo v-else
  9. :info="info"
  10. :tpl="tpl"
  11. :extraXs="extraXs"
  12. @printCert="printCert" @startExam="startExam" @startExamTest="startExamTest"></ICourseInfo>
  13. <div class="right-block-bd ng-scope" ui-view="myStudyContent" style="position: relative;">
  14. <div class="m-account">
  15. <div class="account-tit" style="height:30px">
  16. <a :class="{'current':show===1}" @click="show=1" class="">正在学习</a>
  17. <a :class="{'current':show===2}" @click="show=2" class="">已经完成</a>
  18. <a :class="{'current':show===3}" @click="show=3" class="" v-if="tpl.examGroupId>0">考试记录 </a>
  19. </div>
  20. <div v-if="show<3" class="lwh-ul-form mt20">
  21. <!-- 列表展示 -->
  22. <el-table :data="showList" border stripe style="width: 100%;" >
  23. <el-table-column label="NO." prop="id" width="50" align="center">
  24. <template v-slot="{$index}">
  25. <span> {{$index+1}}</span>
  26. </template>
  27. </el-table-column>
  28. <el-table-column label="课程名称" prop="name" min-width="200">
  29. </el-table-column>
  30. <el-table-column label="类型" prop="type" align="center" width="80">
  31. <span>必修</span>
  32. </el-table-column>
  33. <el-table-column label="学时" prop="xs" align="center" width="80">
  34. <template v-slot="{row}">
  35. <span> {{row.xs/10}}</span>
  36. </template>
  37. </el-table-column>
  38. <el-table-column label="学习进度" prop="useTime" align="left" width="220">
  39. <template v-slot="{row}">
  40. <span style="display: flex;" v-if="show==1">
  41. <el-progress
  42. type="line"
  43. style="width: 120px;"
  44. :stroke-width="26"
  45. :text-inside="true"
  46. :show-text="false"
  47. :percentage="row.percent"
  48. :class="row.percent|showProgressColor">
  49. </el-progress>
  50. <span style="margin-left: 10px;"> {{row.percent}}% </span>
  51. </span>
  52. <span v-else>
  53. <span style="margin-left: 10px;color: green;">视频学习完成</span>
  54. </span>
  55. </template>
  56. </el-table-column>
  57. <el-table-column label="操作" fixed="right" prop="xs" align="center" width="80">
  58. <template v-slot="{row, $index}">
  59. <el-button @click="loadMedia(row, $index)" type="text">播放</el-button>
  60. </template>
  61. </el-table-column>
  62. </el-table>
  63. </div>
  64. <div v-if="show===3" class="lwh-ul-form mt20">
  65. <exam-list :courseId="courseId"> </exam-list>
  66. </div>
  67. </div>
  68. </div>
  69. <el-dialog title="输入认证" center :visible.sync="editRzcode" >
  70. <el-form label-width="120px" ref="elAdd">
  71. <el-form-item label="认证编号">
  72. <el-input v-model="info.rzCode" type="text" disabled></el-input>
  73. </el-form-item>
  74. </el-form>
  75. <div class="dialog-footer" slot="footer">
  76. <el-button @click="cancelRzCode">取 消</el-button>
  77. <el-button @click="doAddRzCode" type="primary">确定打印</el-button>
  78. </div>
  79. </el-dialog>
  80. </div>
  81. </template>
  82. <script>
  83. import {httpServer } from "@/components/httpServer/httpServer.js";
  84. import Media from "./components/media.vue";
  85. import ExamList from "./components/ExamList.vue";
  86. import ICourseInfo from "./components/iCourseInfo.vue";
  87. import ICourseInfoTest from "./components/iCourseInfoTest.vue";
  88. import ICourseInfoXsExtra from "./components/iCourseInfoXsExtra.vue";
  89. import ICourseAnswerTest from "./components/iCourseAnswerTest.vue";
  90. import {getPercent} from '@/utils/index.js'
  91. import {delExam} from '../exam/components/util.js'
  92. import md5 from 'js-md5';
  93. export default {
  94. name: "Index",
  95. data() {
  96. return {
  97. courseId: 0,
  98. show:1,
  99. mediaDialog: false,
  100. mediaType:'hls',
  101. mediaUrl:'',
  102. editRzcode: false,
  103. testExamDalog: false,
  104. info: {score:0, percent:0},
  105. tpl:{},
  106. media:{},
  107. extraXs:{},
  108. counter:{
  109. 1:150,
  110. 2:156,
  111. 3:149,
  112. 4:45,
  113. },
  114. options:{
  115. controls:true,
  116. autoplay: true, // 如果true,浏览器准备好时开始回放。
  117. muted: true, // 默认情况下将会消除任何音频。
  118. loop: false, // 导致视频一结束就重新开始。
  119. preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
  120. language: 'zh-CN',
  121. aspectRatio: '4:3', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
  122. fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
  123. sources: [],
  124. poster: '', // 你的封面地址
  125. notSupportedMessage: '无法播放媒体源', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
  126. playtimes: '',
  127. controlBar: {
  128. timeDivider: true,
  129. durationDisplay: true,
  130. remainingTimeDisplay: false,
  131. fullscreenToggle: true,
  132. volumePanel: true
  133. }
  134. },
  135. list: [],
  136. examList:[],
  137. showList:[],
  138. };
  139. },
  140. watch:{
  141. show(val){
  142. console.log("show", val)
  143. this.updateShowList()
  144. }
  145. },
  146. components:{Media,ExamList,ICourseInfo,ICourseInfoTest,ICourseAnswerTest,ICourseInfoXsExtra},
  147. beforeMount() {
  148. console.log('beforeMount')
  149. this.courseId = +this.$route.params.courseId
  150. this.getData()
  151. this._onVisible = () => {
  152. if (document.visibilityState === 'visible') {
  153. console.log('visible')
  154. this.getData()
  155. }
  156. }
  157. document.addEventListener('visibilitychange', this._onVisible)
  158. },
  159. beforeDestroy() {
  160. document.removeEventListener('visibilitychange', this._onVisible)
  161. },
  162. filters:{
  163. showProgressColor: function (val) {
  164. if (val == '进行中') {
  165. return val = 'el-bg-inner-running';
  166. } else if (val == '失败') {
  167. return val = 'el-bg-inner-error'
  168. } else if (val == '完成') {
  169. return val = 'el-bg-inner-done'
  170. }
  171. }
  172. },
  173. methods: {
  174. changeMedia( val ){
  175. if( !val ) return;
  176. let mediaUrl = this.mediaUrl;
  177. if( val == 'ld'){
  178. mediaUrl = mediaUrl.replace('/hls/', '/ld/');
  179. }else{
  180. mediaUrl = mediaUrl.replace('/ld/', '/hls/');
  181. }
  182. this.mediaType = val;
  183. this.options.sources = [{src:mediaUrl,type: "application/x-mpegURL"}];
  184. this.options.playtimes = this.media.position||0;
  185. this.options.autoplay = true;
  186. this.mediaDialog = true;
  187. },
  188. updateInfo( param ){
  189. this.info = Object.assign(this.info, param);
  190. },
  191. startExamTest(){
  192. if( !this.isStudyFinish() ){
  193. this.$message.errorMsg(" 完成学习才能专项练习", 2)
  194. return;
  195. }
  196. let groupId = this.tpl.testGroupId
  197. this.testExamDalog = true;
  198. if( groupId == 0 || groupId == 10) return;
  199. httpServer('course.GetCourseExamInfo', {examId:groupId}).then( res=>{
  200. if( res.code != 200) return;
  201. this.counter[1] = res.data.judge||0;
  202. this.counter[2] = res.data.single||0;
  203. this.counter[3] = res.data.multi||0;
  204. this.counter[4] = res.data.anli||0;
  205. })
  206. },
  207. isStudyFinish(){
  208. let {getXs,totalXs} = this.info
  209. let {testXs, examXs} = this.tpl;
  210. return getXs>=totalXs -testXs - examXs
  211. },
  212. startExam( groupId ){
  213. let courseId = this.courseId
  214. let endDate = new Date( )
  215. if( !this.isStudyFinish() ){
  216. this.$message.errorMsg(" 完成学习才能考试", 2)
  217. return;
  218. }
  219. delExam()
  220. this.$router.push({path:`/center/exam/${courseId}`, query:{groupId}});
  221. },
  222. printCert( ){
  223. if( this.info.getXs < this.info.totalXs ){
  224. this.$message.errorMsg("课程还未完成", 2)
  225. return;
  226. }
  227. if( this.tpl.examGroupId >0 && this.info.score < 60){
  228. this.$message.errorMsg("考试未通过", 2)
  229. return;
  230. }
  231. let {id, rzCode} = this.info
  232. // this.editRzcode = true
  233. httpServer('Cert.printCert', {rzCode, id}).then( res => {
  234. if( res.code == 200 ){
  235. this.editRzcode = false;
  236. this.info.tmpRzCode="";
  237. window.open( res.data.url )
  238. }
  239. })
  240. },
  241. cancelRzCode(){
  242. this.info.rzCode= this.info.tmpRzCode;
  243. this.editRzcode = false;
  244. },
  245. doAddRzCode(){
  246. let {type, rzCode, id} = this.info;
  247. if( !rzCode ) rzCode = "sm"+id
  248. httpServer('Cert.printCert', {rzCode, id}).then( res => {
  249. if( res.code == 200 ){
  250. this.editRzcode = false;
  251. this.info.tmpRzCode="";
  252. window.open( res.data.url )
  253. }
  254. })
  255. },
  256. formatFinish() {
  257. let {gxs,axs} = this.info;
  258. if( !axs ) axs = 1;
  259. return `获得${gxs}学时, 总共${axs}学时`;
  260. },
  261. formatExam() {
  262. let {score} = this.info
  263. if( score == -1 ) return `已答0次,未通过`;
  264. return `最高${score<1?0:score}分, ${score>=60?'通过':'未通过'}`;
  265. },
  266. formatPass() {
  267. let {score} = this.info
  268. if( score == -1 ) return `未参加`;
  269. return `${score||0}分,${score>=60?'通过':'未通过'}`;
  270. },
  271. formatString(val){
  272. return ()=> val;
  273. },
  274. getData() {
  275. let param = { courseId: this.courseId }
  276. httpServer("course.getCourse", param).then(res => {
  277. if (res.code == 200) {
  278. let {info, extra, list, tpl, extraXs, examList} = res.data;
  279. this.info = Object.assign( info, extra||{});
  280. this.info.score1 = +this.info.score1||0
  281. this.info.score2 = +this.info.score2||0
  282. this.info.score3 = +this.info.score3||0
  283. this.info.score4 = +this.info.score4||0
  284. this.info.score5 = +this.info.score5||0
  285. this.info.score6 = +this.info.score6||0
  286. this.tpl = tpl||{};
  287. this.extraXs = extraXs||{};
  288. this.examList = examList||[];
  289. this.list = list.map( (item)=>{
  290. item.percent = getPercent(item)||0;
  291. return item;
  292. })
  293. this.updateShowList()
  294. }
  295. })
  296. },
  297. updateShowList(){
  298. let show = this.show;
  299. this.showList = this.list.filter( item=>{
  300. if( show == 1) return !item.isFinish
  301. if( show == 2) return !!item.isFinish
  302. return false
  303. })
  304. //
  305. let gxs = 0
  306. let axs = 0;
  307. this.info.tcount = this.list.length;
  308. this.info.fcount =0;
  309. this.info.percent = 0;
  310. for( let i = 0; i< this.list.length;i++){
  311. let item = this.list[i]
  312. axs += item.xs
  313. if( item.isFinish){
  314. gxs += item.xs
  315. this.info.fcount++
  316. }
  317. }
  318. this.info.gxs = gxs/10
  319. this.info.axs = axs/10
  320. this.info.percent = (this.info.gxs*10000/this.info.axs)/100;
  321. },
  322. updateOption( param ){
  323. this.options = Object.assign( this.options, param);
  324. },
  325. updateExtraXs( param ){
  326. this.extraXs = Object.assign( this.extraXs, param);
  327. },
  328. // 加载媒体
  329. loadMedia( item, index ) {
  330. let courseId = this.courseId
  331. let mediaId = item.mediaId;
  332. let uid = localStorage.uid
  333. let token = localStorage.token
  334. let href = `/play.html?courseId=${courseId}&uid=${uid}&token=${token}&mediaId=${mediaId}`
  335. // const {href} = this.$router.resolve({name:'playMedia', params:{courseId}, query:{mediaId}})
  336. window.open(href, '_blank');
  337. }
  338. }
  339. };
  340. </script>
  341. <style>
  342. @import "../../../assets/css/content.css";
  343. @import "../../../assets/css/course.css";
  344. @import url("../../../assets/css/m-account.css");
  345. @import url("./index.css");
  346. .media-dialog .el-dialog__header{
  347. /* display: none !important; */
  348. }
  349. .media-dialog .el-dialog__body{
  350. padding: 0 0 16px 0 !important;
  351. }
  352. .media-dialog .el-scrollbar__wrap {
  353. overflow-x: hidden!important;
  354. }
  355. .mprocess{
  356. margin: 0 auto;
  357. width: 150px !important;
  358. }
  359. </style>
  360. <style scoped>
  361. ::v-deep .media-dialog .el-dialog__body {
  362. padding: 0 0 16px 0 !important;
  363. }
  364. ::v-deep .media-dialog .el-scrollbar__wrap {
  365. overflow-x: hidden !important;
  366. }
  367. ::v-deep .mprocess {
  368. margin: 0 auto;
  369. width: 150px !important;
  370. }
  371. ::v-deep .el-progress {
  372. width: 100%;
  373. }
  374. ::v-deep .el-progress__text {
  375. font-size: 16px;
  376. font-weight: 700;
  377. line-height: 1.5;
  378. color: #3290D4;
  379. }
  380. ::v-deep .el-progress-bar__outer {
  381. height: 20px !important;
  382. border: 1px solid #78335f;
  383. background-color: transparent;
  384. }
  385. ::v-deep .el-bg-inner-running .el-progress-bar__inner {
  386. background-color: unset;
  387. background-image: linear-gradient(to right, #3587d8, #6855ff);
  388. }
  389. ::v-deep .el-bg-inner-error .el-progress-bar__inner {
  390. background-image: linear-gradient(to right, #3587d8, #fb3a7e);
  391. }
  392. ::v-deep .el-bg-inner-done .el-progress-bar__inner {
  393. background-image: linear-gradient(to right, #3587d8, #53ff54);
  394. }
  395. @media screen and (max-width: 768px) {
  396. .m-right-block {
  397. padding: 0 10px;
  398. }
  399. .m-account .account-tit {
  400. display: flex;
  401. }
  402. .m-account .account-tit a {
  403. flex: 1;
  404. text-align: center;
  405. padding: 8px 0;
  406. font-size: 14px;
  407. }
  408. ::v-deep .el-table {
  409. width: 100% !important;
  410. }
  411. ::v-deep .el-table__header-wrapper,
  412. ::v-deep .el-table__body-wrapper {
  413. overflow-x: auto;
  414. -webkit-overflow-scrolling: touch;
  415. }
  416. ::v-deep .el-table .cell {
  417. padding: 0 6px;
  418. font-size: 13px;
  419. }
  420. ::v-deep .el-table .el-table__row td {
  421. padding: 6px 0;
  422. }
  423. ::v-deep .el-progress {
  424. width: 80px !important;
  425. }
  426. }
  427. </style>