Jelajahi Sumber

开启画中画

playMedia

证书编号

学习

promise

去除primise

www

show

自动播放

percent

页面修改

新版本

www

同步

master

证书编号

余额

订单详情

默认

nd

播放器

默认抬头

页面调整

支付调整

证书

备案

页面调整

购买界面

支付

无需证书

简单调整

nd

smapi

首页丢失

setting

批量同步

修改时间

下载证书

import

最后5秒放行

最后5秒放行

证书查询

同步证书

修改提示

短信发送中

背景调整

禁止多出学习视频

填写用户的学历

只显示个人购买

apply

正则表达式

typeinfo

可以使用

匹配类型

匹配承诺书

匹配

匹配类型

考试取消限制

短信校验码失败

短信校验码异常

补充编辑与删除

教育培训

安全生产

样式调整

页面调整

优化岗位

添加搜索功能

配置

小程序样式

新版本

时间调换

修改 准考证

img=》 text

friendlink

link

link

img

友情链接

修改提示语

试试更新

获得学时

未完成不能提交

禁止webdriver

禁止 debug

承诺书新架构

学习截至时间调整

别名

适配图片

手机适配部分功能

footer

标题修改

八大员提醒

温馨提示

230=>260px

证书选择

承诺书

request

三秒后在拖动

去除下载承诺书接口

燃气去除承诺书

水印

icon

监控
y595705120 3 tahun lalu
induk
melakukan
5d3541fec4
90 mengubah file dengan 5493 tambahan dan 1135 penghapusan
  1. 2 2
      config/index.js
  2. TEMPAT SAMPAH
      demo.png
  3. 2 2
      index.html
  4. 7 8
      src/App.vue
  5. TEMPAT SAMPAH
      src/assets/aishu.png
  6. 6 7
      src/assets/css/course.css
  7. 1 1
      src/assets/css/m-account.css
  8. 8 3
      src/assets/css/main.css
  9. 37 0
      src/assets/css/mobile.css
  10. TEMPAT SAMPAH
      src/assets/images/banner_bg.jpg
  11. TEMPAT SAMPAH
      src/assets/wxapp.jpg
  12. 12 4
      src/components/footer/index.vue
  13. 8 7
      src/components/httpServer/httpServer.js
  14. 39 48
      src/components/httpServer/request.js
  15. 234 0
      src/components/nav/navbar.css
  16. 28 3
      src/components/nav/navbar.vue
  17. 62 0
      src/containers/apply/index.css
  18. 257 0
      src/containers/apply/index.vue
  19. 2 0
      src/containers/center/center.vue
  20. 6 9
      src/containers/center/class/train/index.vue
  21. 1 1
      src/containers/center/components/menu/index.css
  22. 23 4
      src/containers/center/components/menu/index.vue
  23. 8 5
      src/containers/center/components/menu/menu.js
  24. 12 7
      src/containers/center/components/navbar/index.vue
  25. 0 1
      src/containers/center/components/pay/IBuyCourse.vue
  26. 2 2
      src/containers/center/exam/components/headline.vue
  27. 24 3
      src/containers/center/exam/indexTest.vue
  28. 5 5
      src/containers/center/home/index.vue
  29. 36 0
      src/containers/center/market/index.css
  30. 297 329
      src/containers/center/market/index.vue
  31. 6 4
      src/containers/center/market/promise.js
  32. 89 67
      src/containers/center/market/trainIndex.vue
  33. 99 27
      src/containers/center/myOrder/index.vue
  34. 5 12
      src/containers/center/play/components/iCourseAnswerTest.vue
  35. 118 87
      src/containers/center/play/components/iCourseInfo.vue
  36. 6 9
      src/containers/center/play/components/iCourseInfoTest.vue
  37. 154 0
      src/containers/center/play/components/iCourseInfoTranExam.vue
  38. 1 3
      src/containers/center/play/components/iCourseInfoXsExtra.vue
  39. 280 130
      src/containers/center/play/components/media.vue
  40. 103 119
      src/containers/center/play/index.vue
  41. 9 0
      src/containers/center/play/play.css
  42. 965 0
      src/containers/center/play/play.vue
  43. 16 8
      src/containers/center/setting/index.vue
  44. 96 0
      src/containers/center/userCert/index.vue
  45. 10 10
      src/containers/center/userSeat/components/iPrintCert.vue
  46. 1 1
      src/containers/center/userSeat/index.vue
  47. 106 0
      src/containers/certificate/fjCertificate.css
  48. 130 0
      src/containers/certificate/fjCertificate.vue
  49. 271 0
      src/containers/certificate/index.css
  50. 194 0
      src/containers/certificate/index.vue
  51. 44 14
      src/containers/login/components/friendlink/friendlink.vue
  52. 3 3
      src/containers/login/components/isLogin.vue
  53. 29 0
      src/containers/login/components/learnTime/learnTime.css
  54. 10 5
      src/containers/login/components/learnTime/learnTime.vue
  55. 8 2
      src/containers/login/components/loginBase.css
  56. 4 3
      src/containers/login/components/unLogin.vue
  57. 39 1
      src/containers/login/login.css
  58. 52 59
      src/containers/login/login.vue
  59. 0 41
      src/containers/login/loginData.js
  60. 91 26
      src/containers/register/register.vue
  61. 8 0
      src/containers/reset/reset.vue
  62. 10 8
      src/containers/sign/index.css
  63. 3 2
      src/containers/sign/index.vue
  64. 114 0
      src/containers/weixin/index.css
  65. 316 0
      src/containers/weixin/index.vue
  66. 1 0
      src/main.js
  67. 39 4
      src/router/index.js
  68. 3 2
      src/setting.js
  69. 0 2
      src/settings.js
  70. 12 1
      src/store/module/user.js
  71. 897 0
      src/tailwind.css
  72. 4 0
      src/utils/date.js
  73. 1 1
      src/utils/index.js
  74. 0 2
      src/utils/plugins.js
  75. 8 0
      static/config.js
  76. TEMPAT SAMPAH
      static/favicon.ico
  77. TEMPAT SAMPAH
      static/images/friendlink/link1.png
  78. TEMPAT SAMPAH
      static/images/friendlink/link5.png
  79. TEMPAT SAMPAH
      static/images/friendlink/三明市住房和城乡建设局.jpg
  80. TEMPAT SAMPAH
      static/images/friendlink/三明市住房和城乡建设行政审批系统.jpg
  81. TEMPAT SAMPAH
      static/images/friendlink/三明市建设人才服务中心.jpg
  82. TEMPAT SAMPAH
      static/images/friendlink/福建省建设从业人员综合服务平台.jpg
  83. TEMPAT SAMPAH
      static/images/header-bg-1.jpg
  84. TEMPAT SAMPAH
      static/images/header-bg.jpg.bak.jpg
  85. TEMPAT SAMPAH
      static/images/login-banner-1.jpg
  86. TEMPAT SAMPAH
      static/images/login-banner.jpg
  87. TEMPAT SAMPAH
      static/images/wxpay.jpg
  88. 0 30
      static/index.html
  89. 1 1
      sync.bat
  90. 18 0
      tailwind.config.js

+ 2 - 2
config/index.js

@@ -4,7 +4,7 @@
 //
 // const TARGET = "http://study.ndjsxh.cn:8888/"
 // const TARGET = "http://localhost:8000/"
-const TARGET = "https://sm.hqedust.com/"
+const TARGET = "https://ndapi.hqedust.com:18443/"
 const path = require('path')
 
 module.exports = {
@@ -34,7 +34,7 @@ module.exports = {
 
     // Various Dev Server settings
     host: 'localhost', // can be overwritten by process.env.HOST
-    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    port: 8086, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
     autoOpenBrowser: false,
     errorOverlay: true,
     notifyOnErrors: true,

TEMPAT SAMPAH
demo.png


+ 2 - 2
index.html

@@ -8,8 +8,8 @@
     <meta http-equiv="X-UA-Compatible" content="IE=9;IE=EDGE">
     <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
     <!-- <meta name="referrer" content="origin"> -->
-
-    <title>三明市建设人才服务中心</title>
+    <script type="text/javascript" src="./static/config.js?id=2"></script>
+    <title>宁德市建筑业协会建设行业从业人员继续教育网络培训平台</title>
   </head>
   <body style="margin:0px;">
     <div id="app"></div>

+ 7 - 8
src/App.vue

@@ -4,17 +4,17 @@
         <router-view />
     </div>
 
-    <div class="ng-scope">
+    <div class="p-wrap-full">
       <div class="footer ng-scope" style="z-index: 10">
         <div class="footer-bd">
-          <p>技术支持:三明市建设人才服务中心【0598-5157296】</p>
+          <p>技术支持:{{config.it}}</p>
           <p>
               <span> ©2021 </span>
               <!-- <a href="http://beian.miit.gov.cn/" target="_blank">闽ICP备19021760号-1</a> -->
               <img class="beian" src="./assets/images/beian_hashc9b229c96d.png" alt="">
               <!-- <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=35010202001216" target="_blank"> -->
               <a href="http://beian.miit.gov.cn/" target="_blank">
-                网站备案:闽ICP备19021760号-2
+                网站备案:{{config.beian}}
               </a>
            </p>
         </div>
@@ -27,11 +27,10 @@
   import { mapActions } from "vuex";
   export default {
     name: 'App',
-    methods:{
-      ...mapActions("user", ["loadTypeList"]),
-    },
-    created(){
-      this.loadTypeList()
+    data() {
+      return {
+        config: window.baseConfig,
+      }
     }
   }
 </script>

TEMPAT SAMPAH
src/assets/aishu.png


+ 6 - 7
src/assets/css/course.css

@@ -1,6 +1,4 @@
-.m-course-list {
-    width: 1020px;
-  }
+
   .m-course-list li {
     width: 248px;
     height: 190px;
@@ -311,10 +309,11 @@
 
 .m-history-course {
   position: relative;
-  width: 48.5%;
+  width: 460px;
   height: 100px;
   border: 1px solid #ddd;
-  padding: 5px;
+  margin-top: 10px;
+  padding: 2px;
 }
 .m-history-course:after {
   content: "";
@@ -526,11 +525,11 @@
     margin-top: 4px;
     text-overflow: ellipsis;
     white-space: nowrap;
-    width: 60%;
+    width: 70%;
   }
   .classlist li .info .w2{
     text-align: left;
-    width: 40%;
+    width: 30%;
     margin-top: 4px;
     overflow: hidden;
     text-overflow: ellipsis;

+ 1 - 1
src/assets/css/m-account.css

@@ -13,7 +13,7 @@
   font-size: 16px;
   display: inline-block;
   line-height: 29px;
-  margin-right: 30px;
+  margin-right: 20px;
   padding: 0 15px 5px;
 }
 .m-account .account-tit a:hover {

+ 8 - 3
src/assets/css/main.css

@@ -67,6 +67,10 @@ form {
     margin-left: auto;
     margin-right: auto;
 }
+
+
+
+
 .btn-r {
     width: 80px;
     height: 30px;
@@ -196,6 +200,7 @@ input:focus{
 }
 .title-box {
     border-bottom: 1px solid #e6e6e6;
+    margin-top: 20px;
 }
 .clear {
    clear: both;
@@ -206,9 +211,8 @@ input:focus{
   position: relative;
   height: 110px;
   width: 100%;
-  
   bottom: 0;
-  /* padding-top:30px; */
+  padding-top:20px;
   position: relative;
   text-align: center;
   background-color: #f5f5f5;
@@ -226,6 +230,7 @@ a, span {
 
 .m-right-block {
   width: 1009px;
+  float: right;
   min-height: 700px;
   background: #fff;
   border-left: 1px solid #e8e8e8;
@@ -275,7 +280,7 @@ a, span {
   z-index: 1;
 }
 .opacity-bg {
-  border-radius: 10px;
+  /* border-radius: 10px; */
   background:rgba(129, 184, 246, 0.9) ;
   /* background: rgba(0,0,0,0.2); */
   /* background: url("../../assets/images/opacity-bg_hash548c17198c.png"); */

+ 37 - 0
src/assets/css/mobile.css

@@ -0,0 +1,37 @@
+@media screen and   (max-width: 639px){
+  .p-wrap-full{
+      width: 100%;
+  }
+  .m-login{
+    margin: 20px auto;
+    float:center;
+  }
+  .m-header{
+    width:100%;
+  }
+  .waphide{
+    display:none;
+  }
+  .header .logo {
+    font-size: 30px;
+  }
+  .m-left-block{
+    width: 0px;
+  }
+  .m-right-block{
+    width:100%;
+    float:none;
+    margin: 10px auto;
+  }
+  .m-course-list{
+    width: 100%;
+    margin: 10px auto;
+  }
+  .mh576{
+    width: 100%;
+  }
+  .setting-img{
+   width: 150px;
+  height: 150px;
+  }
+}

TEMPAT SAMPAH
src/assets/images/banner_bg.jpg


TEMPAT SAMPAH
src/assets/wxapp.jpg


+ 12 - 4
src/components/footer/index.vue

@@ -1,18 +1,26 @@
 <template>
   <div class="footerView" class="ng-scope">
-    <div class="footer ng-scope" style="z-index: 10">
+    <div class="footer ng-scope" style="z-index: 10;padding-top: 20px;">
       <div class="footer-bd">
-        <p>技术支持:三明市建设人才服务中心</p>
+        <p>技术支持:{{config.it}}</p>
         <p><a href="http://beian.miit.gov.cn/" target="_blank">闽ICP备08103886号-2</a>
         <img class="beian" src="./assets/images/beian_hashc9b229c96d.png" alt="">
-         <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=35010202001216" target="_blank">
-              备案编号:闽ICP备19021760号</a></p>
+         <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=35010202001216"
+          target="_blank">
+              备案编号:{{config.beian}}</a></p>
       </div>
     </div>
   </div>
 </template>
 
 <script>
+  export default {
+    data() {
+      return {
+        config: window.baseConfig,
+      };
+    }
+  }
 </script>
 
 <style>

+ 8 - 7
src/components/httpServer/httpServer.js

@@ -1,9 +1,9 @@
-import request from './request.js'
-import md5 from 'js-md5'
+import md5 from 'js-md5';
+import request from './request.js';
 const version = '1.0'
 const secret = '017c0743819088468e590246464cc75f';
 
-export function httpServer(method, data) {
+export function httpServer(method, data, hideLoading) {
   let uid = localStorage.uid||0;
   let token = localStorage.token||''
   let role = 'user'
@@ -16,13 +16,14 @@ export function httpServer(method, data) {
   let body = JSON.stringify(data);
   let mtime = parseInt(Date.now() / 1000);
   let sigureStr = "hall_" + version + token + body + method + mtime + secret
-  
+
   let sign = md5( sigureStr )
-  let url = `/rental/${method}?t=${mtime}&u=${uid}&v=${version}&s=${sign}`
+  let url = `https://ndapi.hqedust.com:18443/rental/${method}?t=${mtime}&u=${uid}&v=${version}&s=${sign}`
   return request({
     url: url,
     method: 'post',
-    data: body
+    data: body,
+    hideLoading
   })
 }
 
@@ -34,7 +35,7 @@ export function uploadImg( rand, formData ) {
   if( !token || !uid ) return;
   let sign = "hall_" + version + token + rand  + mtime + secret;
   return request({
-    url:`/upload?t=${mtime}&u=${uid}&v=${version}&s=${sign}&r=${rand}`,
+    url:`https://ndapi.hqedust.com:18443/upload?t=${mtime}&u=${uid}&v=${version}&s=${sign}&r=${rand}`,
     method: 'post',
     data:formData,
     dataType:"multipart/form-data"

+ 39 - 48
src/components/httpServer/request.js

@@ -1,83 +1,74 @@
 import axios from 'axios'
-import { MessageBox, Message } from 'element-ui'
+import { MessageBox, Message, Loading } from 'element-ui'
+
+let loadingInstance = null
+let requestCount = 0
 
-let acitveAxios = 0
-let timer
 const showLoading = () => {
-  acitveAxios++
-  if (timer) {
-      clearTimeout(timer)
+  requestCount++
+  if (requestCount === 1) {
+    loadingInstance = Loading.service({
+      target: '.pc-player-container',
+      text: '加载中...',
+      background: 'rgba(255,255,255,0.6)'
+    })
   }
-  timer = setTimeout(() => {
-      if (acitveAxios > 0) {
-          context.$bus.emit("showLoading")
-      }
-  }, 400);
 }
 
 const closeLoading = () => {
-  acitveAxios--
-  if (acitveAxios <= 0) {
-      clearTimeout(timer)
-      context.$bus.emit("closeLoading")
+  requestCount--
+  if (requestCount <= 0) {
+    requestCount = 0
+    if (loadingInstance) {
+      loadingInstance.close()
+      loadingInstance = null
+    }
   }
 }
 
-// create an axios instance
 const service = axios.create({
-  baseURL: '/', // url = base url + request url
-  // withCredentials: true, // send cookies when cross-domain requests
-  // timeout: 5000 // request timeout
+  baseURL: '/',
 })
 
-// request interceptor
 service.interceptors.request.use(
   config => {
-    // if (store.getters.token) {
-    //   config.headers['x-token'] = localStorage.token
-    //   config.headers['x-userId'] = localStorage.userId
-    // }
+    if (!config.hideLoading) {
+      showLoading()
+    }
     return config
   },
   error => {
-    // do something with request error
-    console.log(error) // for debug
     return Promise.reject(error)
   }
 )
 
-// response interceptor
 service.interceptors.response.use(
-  /**
-   * If you want to get http information such as headers or status
-   * Please return  response => response
-  */
-
-  /**
-   * Determine the request status by custom code
-   * Here is just an example
-   * You can also judge the status by HTTP Status Code
-   */
   response => {
-    const res = response.data||{}
-    // if the custom code is not 200, it is judged as an error.
+    if (!response.config.hideLoading) {
+      closeLoading()
+    }
+    const res = response.data || {}
     if (res.code == 200 || res.code == 20000) {
-      return Promise.resolve(res  )
-    } else if( res.code == 401){
-      // localStorage.removeItem('token')
-      // localStorage.removeItem('paperId')
-      // return Promise.reject( res )
-      return Promise.resolve( res )
-    }else {
+      return Promise.resolve(res)
+    } else if (res.code == 401) {
+      Message({
+        message: '用户被踢下线,请重新登入',
+        type: 'error',
+        duration: 5000
+      })
+      window.location.href = '/'
+      return Promise.resolve(res)
+    } else {
       Message({
         message: res.msg || 'Error',
         type: 'error',
-        duration:  2000
+        duration: 2000
       })
-      return Promise.resolve( res )
+      return Promise.resolve(res)
     }
   },
   error => {
+    closeLoading()
     Message({
       message: error.message,
       type: 'error',

+ 234 - 0
src/components/nav/navbar.css

@@ -0,0 +1,234 @@
+.m-header {
+  min-width: 1200px;
+  height: 150px;
+  background: #fff url("/static/images/header-bg-1.jpg") no-repeat center center;
+  background-size: cover;
+  position: relative;
+  z-index: 3;
+}
+.m-header .top-wrap {
+  height: 30px;
+  line-height: 30px;
+  border-bottom: 1px solid #479bd8;
+  color: #fff;
+  font-size: 12px;
+}
+.m-header .top-wrap a {
+  color: #fff;
+  height: 30px;
+}
+.m-header .top-wrap .right a {
+  padding: 0 10px;
+}
+.m-header .top-wrap .right a:hover {
+  text-decoration: underline;
+}
+.m-header .top-wrap .appico {
+  background-position: -150px -270px;
+  width: 20px;
+  height: 20px;
+  margin-right: 5px;
+  position: relative;
+  top: -1px;
+}
+.m-header .top-wrap .appico-1 {
+  background-position: -180px -270px;
+}
+.m-header .top-wrap .top-app:hover .hover-block {
+  display: block;
+}
+.m-header .top-wrap .top-app .hover-block {
+  position: absolute;
+  top: 33px;
+  left: -30px;
+  line-height: 1.5;
+  transition: all 0.4s;
+  padding-right: 10px;
+  z-index: 10;
+  display: none;
+}
+.m-header .top-wrap .top-app .hover-block .hover-bd {
+  background-color: #fff;
+  text-align: left;
+  color: #333;
+  padding: 10px;
+  border-radius: 5px;
+  -moz-box-shadow: 0px 0px 7px #ccc;
+  -webkit-box-shadow: 0px 0px 7px #ccc;
+  box-shadow: 0px 0px 7px #ccc;
+}
+.m-header .top-wrap .top-app .hover-block .hover-bd span {
+  display: inline-block;
+}
+.m-header .top-wrap .top-app .hover-block .hover-bd .arrowtop {
+  width: 13px;
+  height: 7px;
+  position: absolute;
+  left: 50%;
+  top: -7px;
+  margin-left: -6px;
+  display: inline-block;
+  background: url("/assets/images/icon_hash3c59d1b2af.png") no-repeat -322px -23px;
+}
+.m-header .logo {
+  padding-top: 42px;
+}
+.m-header .channel-txt {
+  font-size: 30px;
+  color: #3290d4;
+  font-weight: bold;
+  border-left: 1px solid #3290d4;
+  padding-left: 25px;
+  margin-left: 25px;
+  height: 60px;
+  line-height: 52px;
+  margin-top: 22px;
+}
+.m-nav {
+  min-width: 1200px;
+  height: 56px;
+  border-bottom: 1px solid #ddd;
+  -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  position: relative;
+  z-index: 2;
+  background-color: #fff;
+}
+.m-nav .nav-bd a {
+  line-height: 56px;
+  display: block;
+  float: left;
+  color: #333;
+  font-size: 16px;
+  padding: 0 15px;
+  margin-right: 15px;
+  min-width: 150px;
+  text-align: center;
+  box-sizing: border-box;
+  border-bottom: 3px solid transparent;
+  position: relative;
+  font-weight: bold;
+}
+.m-nav .nav-bd a:after {
+  content: " ";
+  display: block;
+  width: 100%;
+  height: 0;
+  border-bottom: 3px solid transparent;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  transition: all 0.4s;
+  -moz-transition: all 0.4s;
+  -webkit-transition: all 0.4s;
+  -o-transition: all 0.4s;
+}
+.m-nav .nav-bd a:hover,
+.m-nav .nav-bd a.current {
+  color: #ff914b;
+}
+.m-nav .nav-bd a.current:after {
+  border-color: #ff914b;
+}
+.m-nav .nav-bd a:focus {
+  outline: none;
+  text-decoration: none;
+}
+.m-nav .nav-bd .fr {
+  float: right;
+  margin-right: 0;
+}
+.m-nav .nav-bd-1 a {
+  padding: 0 36px;
+}
+.pos-nav {
+  color: #666;
+  line-height: 36px;
+  height: 36px;
+  padding-top: 5px;
+}
+.pos-nav .return-ico {
+  background-position: -211px -221px;
+  width: 24px;
+  height: 18px;
+  margin-right: 6px;
+}
+.pos-nav a {
+  color: #666;
+}
+.pos-nav a:hover {
+  color: #3290d4;
+  text-decoration: underline;
+}
+.pos-nav a:hover .return-ico {
+  background-position: -211px -240px;
+}
+.pos-nav span {
+  padding: 0 3px;
+}
+.pos-nav .location-ico {
+  width: 18px;
+  height: 22px;
+  background-position: -128px -382px;
+}
+.pos-nav .trigger {
+  display: inline-block;
+  margin-left: 20px;
+}
+.pos-nav .trigger .conditions-btn {
+  display: inline-block;
+  background-color: #fff;
+  padding-left: 8px;
+  padding-right: 30px;
+  line-height: 24px;
+  height: 24px;
+  font-size: 12px;
+  color: #333;
+  margin-top: 5px;
+  margin-right: 5px;
+  border: 1px solid #ddd;
+  position: relative;
+  vertical-align: top;
+}
+.pos-nav .trigger .conditions-btn .txt {
+  color: #3290d4;
+}
+.pos-nav .trigger .conditions-btn .close-ico {
+  background-position: -94px -374px;
+  width: 24px;
+  height: 24px;
+  display: inline-block;
+  margin-left: 5px;
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+.pos-nav .trigger .conditions-btn:hover {
+  border: 1px solid #3290d4;
+  text-decoration: none;
+}
+.pos-nav .trigger .conditions-btn:hover:hover {
+  text-decoration: none;
+}
+.pos-nav .trigger .conditions-btn:hover .close-ico {
+  background-position: -94px -389px;
+  background-color: #3290d4;
+}
+.pos-nav .trigger .conditions-btn-2 {
+  padding-right: 5px;
+}
+.pos-nav .trigger .clear-btn {
+  display: inline-block;
+  color: #666;
+  font-size: 12px;
+}
+.pos-nav .trigger .clear-btn:hover {
+  color: #3290d4;
+  text-decoration: none;
+}
+.logo-box-img {
+  height: 40px;
+  width: 100px;
+  z-index: 9999 !important;
+}

+ 28 - 3
src/components/nav/navbar.vue

@@ -2,18 +2,43 @@
   <div>
     <div class="m-header ng-scope">
       <div class="p-wrap-full tc" @click="goto('/')">
-        <span class="logo " style="color: white;font-size: 56px;">
-
-          三明市建设人才服务中心  </span>
+        <span class="logo " style="color: white;font-size: 36px;">
+          {{config.title}}  </span>
       </div>
     </div>
 
+    <div class="m-nav">
+      <div class="p-wrap-full clear">
+        <div class="nav-bd">
+          <a v-for="(item, index) in navList" :key="index"
+             :class="{ current: isActive(item) }"
+             @click="goto(item.path)"
+             v-if="!item.hidden">
+            {{ item.name }}
+          </a>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
   export default {
+    data() {
+      return {
+        config: window.baseConfig,
+        navList: [
+          { name: '首页', path: '/' },
+          { name: '证书查询', path: '/fj-certificate' },
+          { name: '学时认证', path: '/nd-sign' },
+          { name: '文件通知', path: '/main/postlist/notice' },
+        ]
+      };
+    },
     methods:{
+      isActive(item) {
+        return this.$route.path === item.path
+      },
       goto( path ){
         if( this.$route.path != path){
           this.$router.push({path})

+ 62 - 0
src/containers/apply/index.css

@@ -0,0 +1,62 @@
+
+.sign-body {
+    background: url("/static/sign/bg.jpg") no-repeat fixed bottom;
+    background-size: 100% auto;
+    background-color:#4eaceb;
+    margin:0;
+    font-family:"Arial";
+    padding-bottom: 20px;
+}
+@media screen and  (min-width: 640px){
+    .sign-body {
+        font-size: 85.4px;
+    }
+}
+
+@media screen and  (min-width: 414px) and (max-width: 639px){
+    .sign-body {
+        font-size: 55.2px;
+    }
+}
+
+@media screen and (min-width:375px) and (max-width:413px){
+    .sign-body {
+        font-size: 50px;
+    }
+}
+
+@media screen and (min-width:320px) and (max-width:374px){
+    .sign-body {
+        font-size: 42.7px;
+    }
+}
+
+.sign-title-2{
+  width: 100%;
+  font-size: 28px;
+  text-align: center;
+  /* margin: 30px auto; */
+  color: white;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  display: block;
+  font-weight: bold;
+}
+
+.sign-content-box {
+    width: 80%;
+    background-color: white;
+    border-radius: .6rem;
+    margin-left: auto;
+    margin-right: auto;
+    padding: .9rem;
+    margin-bottom: 1.2rem;
+}
+
+.sign-content-form {
+    width: 80%;
+    background-color: white;
+    border-radius: .6rem;
+    margin-left: auto;
+    margin-right: auto;
+}

+ 257 - 0
src/containers/apply/index.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="sign-body">
+    <span class="sign-title-2"> 批量开通申请表</span>
+    <div class="sign-content-box">
+      <el-row  :gutter="24" align="center" >
+          <el-col :span="9">
+            <span style="color: #909399;font-size: 1rem;">信用统一代码:</span>
+            <el-button  type="text" @click="editCompany" > {{info.companyNo}} </el-button>
+          </el-col>
+          <el-col :span="9">
+             <span style="color: #909399;font-size: 1rem;">邮件地址:</span>
+             <el-button  type="text"  @click="editCompany" >{{info.email}}</el-button>
+          </el-col>
+          <el-col :span="6">
+            <el-button type="primary" class="el-icon-plus"  @click="addNewUser">
+              添加用户
+            </el-button>
+          </el-col>
+      </el-row>
+
+      <el-table v-loading="listLoading" :data="list.slice( page*size-size, page*size)" border fit highlight-current-row>
+          <el-table-column label="姓名" align="center" prop="nickname" />
+          <el-table-column label="手机号" align="center" prop="phone" />
+          <el-table-column label="最高学历" align="center" prop="education" />
+          <el-table-column label="岗位" align="center" prop="courseName" />
+          <el-table-column label="年度" align="center" prop="nd" />
+          <el-table-column label="证书编号" align="center" prop="rzCode" />
+          <el-table-column label="操作" fixed="right" align="center" width="120">
+              <template slot-scope="{row}">
+                <el-button @click="editRow( row )" type="text" size="small">修改</el-button>
+                <el-button @click="deleteRow( row )" type="text" size="small" style="color: brown;">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+      </el-table>
+
+      <el-pagination @current-change="handleCurrentChange" :current-page="page" :page-size="size"
+        layout="total, prev,pager, next" :total="list.length">
+      </el-pagination>
+
+
+      <el-dialog title="修改企业信息" center :visible.sync="updateCompanyDialog" width="500px">
+        	<el-form :model="info" :rules="rules2" label-width="120px" ref="elForm2" >
+        		<el-form-item label="信用统一代码" prop="companyNo" >
+        			<el-input v-model="info.companyNo" type="text" style="width: 250px;" />
+        		</el-form-item>
+
+            <el-form-item label="邮件地址" prop="email" >
+            	<el-input v-model="info.email" type="text" style="width: 250px;" />
+            </el-form-item>
+        	</el-form>
+          <div class="dialog-footer" slot="footer">
+          	<el-button @click="updateCompanyDialog = false">取 消</el-button>
+          	<el-button @click="doEditCompany()" type="primary">确 定</el-button>
+          </div>
+        </el-dialog>
+      </div>
+
+      <el-dialog title="添加用户" center :visible.sync="addUserDialog" width="500px">
+      	<el-form :model="form" :rules="rules" label-width="120px" ref="elForm" >
+
+          <el-form-item label="用户名字" prop="nickname">
+      			<el-input v-model="form.nickname" type="text" style="width: 260px;" />
+      		</el-form-item>
+      		<el-form-item label="身份证号" prop="cardId" >
+      			<el-input v-model="form.cardId" type="text" style="width: 260px;" />
+      		</el-form-item>
+          <el-form-item label="证书编号" prop="rzCode" >
+          	<el-input v-model="form.rzCode" type="text" style="width: 170px;" />
+            <el-button style="width: 80px;" type="primary" :disabled="form.cardId.length==18 && form.nickname!=''" >查询</el-button>
+          </el-form-item>
+          <el-form-item label="最高学历" prop="education" >
+            <el-select  v-model="form.education" style="width: 260px;" >
+            	<el-option v-for="item in educationList" :value="item" :label="item" :key="item"/>
+            </el-select>
+          </el-form-item>
+      		<el-form-item label="手机号码" prop="phone" >
+      			<el-input v-model="form.phone" type="text" style="width: 260px;" />
+      		</el-form-item>
+      		<el-form-item label="选择岗位" prop="courseId">
+      			<el-select  v-model="form.courseId" style="width: 260px;" >
+              <el-option  :value="0" label="选择课程" />
+      				<el-option v-for="item in courseList" :value="item.courseId" :label="item.courseName+'|'+item.nd" :key="item.id"/>
+      			</el-select>
+      		</el-form-item>
+
+      	</el-form>
+        <div class="dialog-footer" slot="footer">
+        	<el-button @click="addUserDialog = false">取 消</el-button>
+        	<el-button @click="doAddUser()" type="primary">确 定</el-button>
+        </div>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+<script>
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
+  import { MessageBox } from "element-ui";
+  const defaultForm = {
+      cardId:'',
+      nickname:'',
+      email:'',
+      phone:'',
+      courseId: 0,
+      education:'',
+      rzCode: ''
+  };
+  export default {
+    data() {
+      return {
+        param: {
+          taskId: 0,
+          secret: ''
+        },
+        info:{
+          company:'',
+          email:'',
+          companyNo:''
+        },
+        educationList: window.baseConfig.educationList,
+        listLoading: false,
+        addUserDialog: false,
+        updateCompanyDialog: false,
+        page: 1,
+        size: 10,
+        list: [],
+        courseList: [],
+        form: Object.assign({}, defaultForm),
+        rules: {
+          nickname: [{required: true, message: "请输入名字", trigger: "blur", min: 2 }],
+          education: [{ required: true, message: "请选择学历", trigger: "blur"}],
+          cardId: [
+            { required: true, message: '请输入身份证号码', trigger: 'blur' },
+            {
+              validator: (rule, value, callback) => {
+                if (!value) {
+                  return callback(new Error('请输入身份证号码'));
+                }
+                const cleanValue = value.replace(/[\s()]/g, '').toUpperCase();
+                const mainlandReg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
+                const hkReg = /^(?:C(?:\d{8}|[A-HJ-NP-Z]\d{7})|[HM](?:\d{10}|\d{8}))$/;
+                if (mainlandReg.test(cleanValue) || hkReg.test(cleanValue)) {
+                  callback();
+                } else {
+                  callback(new Error('请输入正确的大陆身份证(18位)或香港身份证(如 A1234567)'));
+                }
+              },
+              trigger: 'blur'
+            }
+          ],
+          phone: [ { required: true, message: "请输入手机号", trigger: "blur", min: 11, max:11  }],
+          courseId: [ { required: true, message: "请选择岗位", trigger: "blur" }]
+        },
+        rules2: {
+          companyNo: [{required: true, message: "请输入信用统一代码", trigger: "blur", min: 2 }],
+          email: [{ required: true, message: "请输入邮箱地址", trigger: "blur", min: 6 }],
+        }
+      }
+    },
+    methods: {
+      gotoDetail() {
+        let param = this.param;
+        httpServer("User.GetOpenCourseList", param).then(res => {
+          if (res.code != 200) return;
+          this.info =  res.data.info||{}
+          this.list = res.data.list || []
+          this.courseList = res.data.courseList||[]
+        })
+      },
+      editCompany(){
+          this.updateCompanyDialog = true
+      },
+      doAddUser(){
+        let cardId = this.form.cardId;
+        this.$refs["elForm"].validate((valid) => {
+          if (!valid) return;
+          let param = Object.assign({}, this.form, this.param);
+          httpServer("User.AddOpenCourseInfo", param).then( res=>{
+            if (res.code != 200) return;
+            this.addUserDialog = false;
+            this.$message.successMsg("申请完成", 3)
+            this.gotoDetail();
+          })
+        })
+      },
+      doEditCompany(){
+        this.$refs["elForm2"].validate((valid) => {
+          if (!valid) return;
+          let param = Object.assign({}, this.info, this.param);
+          httpServer("User.UpdateOpenCourseTask", param).then( res=>{
+            if (res.code != 200) return;
+            this.updateCompanyDialog = false;
+            this.$message.successMsg("更新成功", 3)
+          })
+        })
+      },
+      doDeleteUser(row, cb){
+        let param = Object.assign({}, {id:row.id}, this.param);
+        httpServer("User.DelOpenCourseInfo", param).then( res=>{
+          if (res.code != 200) return;
+          this.$message.successMsg("删除完成", 3)
+          cb&&cb();
+          this.gotoDetail();
+        })
+      },
+      editRow(row){
+        this.form = Object.assign(this.form, row);
+        this.addUserDialog = true
+      },
+      deleteRow(row){
+        let that = this
+        MessageBox({
+          title: "删除提醒",
+          message: "删除动作不可恢复,请确认是否删除",
+          showCancelButton: true,
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          beforeClose: (action, instance, done) => {
+            if (action === "confirm") {
+              instance.confirmButtonLoading = true;
+              instance.confirmButtonText = '提交中...';
+              that.doDeleteUser(row, done);
+              done();
+              instance.confirmButtonLoading = false;
+              that.addUserDialog = false;
+            } else{
+              done();
+            }
+          }
+        })
+      },
+      addNewUser(){
+        this.form = Object.assign(this.form, defaultForm);
+        if(!this.info.email){
+          this.$message.successMsg("请先补充企业邮箱地址", 3)
+          return;
+        }
+        this.addUserDialog = true
+      },
+      handleCurrentChange: function(page) {
+        this.page = page
+      }
+    },
+    created() {
+      this.param.taskId = +this.$route.query.id || '';
+      this.param.secret = this.$route.query.secret || '';
+      if (this.param.taskId && this.param.secret) {
+        this.gotoDetail();
+      }
+    }
+  }
+</script>
+<style>
+  @import url("./index.css");
+</style>

+ 2 - 0
src/containers/center/center.vue

@@ -24,6 +24,8 @@ export default {
   computed: {
   	...mapGetters("user", ["userInfo"])
   },
+  created(){
+  },
   beforeMount(){
     console.log("enter", this.userInfo )
     if( !this.userInfo || !this.userInfo.token){

+ 6 - 9
src/containers/center/class/train/index.vue

@@ -1,13 +1,10 @@
 <template>
-  <div class="m-right-block fr mh576">
-    <!-- uiView: myStudyContent -->
-
-
+  <div class="m-right-block  mh576">
     <div class="right-block-bd ng-scope" ui-view="myStudyContent" style="position: relative;">
       <div class="panel-tit clear">
-        <p class="fl">课程</p>
-        <div class="tc">
-          <strong class="tc" style="color: red;font-size: 1.2rem;">{{setting.noticeMsg}}</strong>
+        <p class="fl"></p>
+        <div class="tc waphide">
+          <strong class="tc" style="color: red;font-size: 0.9rem;">{{setting.noticeMsgTrain}}</strong>
         </div>
       </div>
 
@@ -29,10 +26,10 @@
                <p class="w1"><span class="p-ico c1"></span> {{item.type}}</p>
                <p class="w2"><span class="p-ico c2"></span>{{item.nd||'2020'}}</p>
                <p class="w1">
-                <span class="p-ico c3"></span> 获得<strong style="color: orange;">{{item.getXs/10}}</strong>学时
+                  <span class="p-ico c3"></span>获得<strong style="color: orange;"> {{item.getXs/10}} </strong>学时
                </p>
                <p class="w2" >
-                 <span class="p-ico c3"></span> 总共 <strong style="color: orange;">{{item.totalXs/10}}</strong>学时
+                 <span class="p-ico c3"></span>学时<strong style="color: orange;"> {{item.totalXs/10}}</strong>
                </p>
             </div>
           </div>

+ 1 - 1
src/containers/center/components/menu/index.css

@@ -4,7 +4,7 @@
 }
 .m-left-list {
   margin-top: 20px;
-  min-height: 400px;
+  min-height: 665px;
   border-top: 1px solid #f8f8f8;
 }
 .m-left-list a {

+ 23 - 4
src/containers/center/components/menu/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="m-left-block fl lwh-sider" center-sider="">
+  <div class="m-left-block fl lwh-sider waphide" center-sider="">
     <div class="user-block" style="width:150px;cursor:pointer">
       <img style="width:140px;height:160px"
         class="user-img"
@@ -11,14 +11,14 @@
     </div>
 
     <ul class="m-left-list">
-      <li v-for="(item, index) in menu" :key="index"  :class="{'current':item.active}">
+      <li v-for="(item, index) in menu" :key="index"  :class="{'current':item.active||item.show}">
 
         <a href="javascript:void(0)" style="text-decoration: none" @click="goState(item)"
           class="ng-binding"><span class="p-ico2" :class="item.icon" ></span>{{item.name}}</a>
 
         <div class="sub-list ng-scope" v-if="item.children" >
           <a v-for="subItem in item.children" :key="subItem.path" v-if="!subItem.hidden"
-            @click="goSubState(subItem)" :class="{'current':subItem.active}" href="javascript:void(0)"
+            @click="goState2(subItem, item)" :class="{'current':$route.path == subItem.path}" href="javascript:void(0)"
             class="ng-binding ng-scope">{{subItem.name}}</a>
         </div>
       </li>
@@ -61,21 +61,40 @@
           if( item.path != path ){
             item.active = false
           }
+          this.unActiveChild(path, this.menu[i].children)
+        }
+      },
+      unActiveChild( path, children ){
+        if(!children) return;
+        for( let i in children){
+          let item = children[i];
+          item.active  = item.path == path;
         }
       },
       goState( item ){
+        this.allUnactive(this.$route.path)
         if( item.children ){
           item.active = !item.active;
           if( item.active ){
             this.allUnactive( item.path )
+            item.children[0].active = true
           }
         }else{
-          this.goSubState( item );
+          this.goSubState(item);
         }
       },
+      goState2(child, parent){
+        parent.active = true;
+        this.unActiveChild( child.path, parent.children);
+        this.goSubState(child)
+      },
       goSubState(item){
         if(item.path == this.$route.path) return;
         item.active = true;
+        window.scrollTo({
+             top: 0,
+             behavior: 'smooth' // 平滑滚动效果
+        });
         this.$router.push(item.path);
       }
     }

+ 8 - 5
src/containers/center/components/menu/menu.js

@@ -21,16 +21,14 @@ const menu = [
    },
    {
      path: '/center/markets',
-     name: '推荐课程',
+     name: '课程分类',
+     show: true,
      icon: 'ico-course',
      children: [
        {
          path: '/center/market',
+         active: true,
          name: '继续教育'
-       },
-       {
-         path: '/center/trainMarket',
-         name: '培训测试'
        }
      ]
    },
@@ -43,6 +41,7 @@ const menu = [
      path: '/center/exam',
      name: '考试培训',
      icon: 'ico-files',
+     show: true,
      children: [
        {
          path: '/center/examSeat',
@@ -55,6 +54,10 @@ const menu = [
        {
          path: '/center/examScore',
          name: '成绩查询'
+       },
+       {
+         path: '/center/userCert',
+         name: '我的证书'
        }
      ]
     }

+ 12 - 7
src/containers/center/components/navbar/index.vue

@@ -3,8 +3,8 @@
     <div class="p-wrap-full clear">
       <div class="logo fl" style="padding-top: 24px;">
         <a href="javascript:void(0)" style="text-decoration: none"
-            class="rel ng-binding" @click="$router.push('/')">
-           三明市建设人才服务中心
+            class="rel ng-binding waphide" @click="$router.push('/')">
+           {{config.title}}
           <div class="hover-block" style="padding-top: 24px;">
 
             <div class="hover-bd ng-scope" style="width:137px;margin-top: -20px" @click="$router.push('/')">
@@ -17,17 +17,17 @@
       </div>
       <div class="nav fr">
         <a href="javascript:void(0)" @click="gotoPage('/')" >
-          <span class="p-ico2 ico-index"></span>
+          <span class="p-ico2 ico-index" ></span>
           <span class="nav-txt">回到主页</span>
         </a>
-        <a href="javascript:void(0)" class="ng-scope current">
+        <a href="javascript:void(0)"  @click="gotoPage('/center/market')" :class="{'current': $route.path == '/center/market'}">
           <span class="p-ico2 ico-center"></span>
-          <span class="nav-txt">学习中心</span>
+          <span class="nav-txt">继续教育</span>
         </a>
 
-        <a href="javascript:void(0)" @click="gotoPage('/center/sign')">
+        <a href="javascript:void(0)" @click="gotoPage('/center/trainMarket')" :class="{'current': $route.path == '/center/trainMarket'}">
           <span class="p-ico2 ico-bmpx"></span>
-          <span class="nav-txt">学时证明</span>
+          <span class="nav-txt">教育培训</span>
         </a>
 
         <span class="p-ico2 ico-line"></span>
@@ -52,6 +52,11 @@
         }
       }
     },
+    data(){
+      return {
+        config: window.baseConfig,
+      }
+    },
     methods:{
       ...mapActions("user", [ "doLogout"]),
       gotoPage( route ){

+ 0 - 1
src/containers/center/components/pay/IBuyCourse.vue

@@ -133,7 +133,6 @@
     },
     watch:{
       courseId(){
-        console.log("change", this.courseId)
         this.loadPayInfo();
       }
     },

+ 2 - 2
src/containers/center/exam/components/headline.vue

@@ -58,7 +58,7 @@ export default {
 <style>
 .htitle {
   margin-top: 16px;
-  font-size: 36px;
+  font-size: 26px;
   font-weight: 900;
   padding: 8px;
   text-align: center;
@@ -84,7 +84,7 @@ export default {
 }
 .ltime{
   font-size: 20px;
-  font-weight:560;  
+  font-weight:560;
 }
 
 .head-item {

+ 24 - 3
src/containers/center/exam/indexTest.vue

@@ -95,7 +95,7 @@
                 <h3 class="p5">单项选择题</h3>
                 <el-row :gutter="24" class="ml20">
                   <el-col :span="3" v-for="(item,index) in info.answers" :key="index" class="select-item" v-if="item.type==2">
-                    <el-button :class="'b-grid '+getStyle(index)" @click="gotoQuestion(index)" >
+                    <el-button :class="'b-grid '+getStyle(index)" @click="gotoQuestion(index)" :id="'select-'+index" >
                        {{index+1}}
                        <span class="red">
                         {{isMarked(index)}}
@@ -318,9 +318,17 @@ export default {
     },
     submitPaper() {
       let that = this
+      let ufinish= this.getUnFinishExamTest()
+      let content = '你已经做完所有题目';
+      if(ufinish >-1) content = `第${+ufinish+1}题还未完成,是否确认提交`
+      // if( ufinish  >-1 ) {
+      //   this.$message.error(, 2);
+      //   document.getElementById( `select-${ufinish}`).click()
+      //   return;
+      // }
       MessageBox({
         title: "确认交卷",
-        message: "是否确认交卷",
+        message: content,
         showCancelButton: true,
         confirmButtonText: "确定",
         cancelButtonText: "取消",
@@ -332,7 +340,6 @@ export default {
             done();
             instance.confirmButtonLoading = false;
             that.dialogVisible = true;
-
           } else{
             done();
           }
@@ -340,6 +347,20 @@ export default {
         }
       })
     },
+    getUnFinishExamTest(){
+      for( let i in this.questionList){
+        let item = this.questionList[i];
+        if(item.type!= 4){
+          if( !getSelect(item.answer) ) return i;
+        }else{
+          for( let j in item.child){
+            if(!item.child[j] || !item.child[j].answerId) return i;
+            if( !getSelect(item.answer) ) return i;
+          }
+        }
+      }
+      return -1
+    },
     dosubmit( cb ) {
       let param = { examId: this.info.examId, result: { }};
       for( let i in this.questionList){

+ 5 - 5
src/containers/center/home/index.vue

@@ -28,7 +28,7 @@
         </div>
 
         <div class="tc mt20 clear">
-          <strong class="tc" style="color: red;font-size: 1.2rem;">{{setting.noticeMsg}}</strong>
+          <strong class="tc" style="color: red;font-size: 0.9rem;">{{setting.noticeMsgTrain}}</strong>
         </div>
 
         <div class="panel-tit mt30 clear">
@@ -42,7 +42,7 @@
               <span class="p-ico2"></span>
             </a>
             <div class="history-course-bd" style="width:276px">
-              <p class="tit ng-binding">{{item.name}}[{{item.nd}}]</p>
+              <p class="tit ng-binding"><strong v-if="item.type.indexOf('新培')>-1">【新培】</strong>{{item.name}}[{{item.nd}}]</p>
               <div class="info" v-if="curDate>=item.startDate" >课程进度
                 <span class="num ml10 ng-binding">{{getPercent(item)}}</span>
                 <span class="t-b">%</span>
@@ -68,16 +68,16 @@
     httpServer
   } from "@/components/httpServer/httpServer.js";
   import setting from '@/setting';
-  import {curDate} from '@/utils/date.js'
+  import {curDatetime} from '@/utils/date.js'
   export default {
     name: "Index",
     data() {
       return {
         page: 1,
-        size: 6,
+        size: 12,
         total: 1,
         media: {},
-        curDate:curDate(),
+        curDate:curDatetime(),
         list: [],
         setting
       };

+ 36 - 0
src/containers/center/market/index.css

@@ -21,10 +21,46 @@
   padding: 4px 40px 4px 40px;
   border: 1px solid #EBEEF5;
   text-align: center;
+}
 
+.filterName{
+  margin-left: 20px;
+  height: 34px;
+  width: 220px;
+  margin-top: 4px;
 }
 
 .nav-btn-years:hover{
   background: none;
   color: #5a9fd9;
 }
+
+.type_item{
+    min-width: 160px;
+    height: 36px;
+    margin: 4px;
+    padding: 4 4px;
+}
+.type_active{
+    color: white !important;
+    background-color: #5a9fd9 !important;
+    transform: translate(2px);
+}
+
+.manual-tooltip {
+    position: fixed;
+    z-index: 9999;
+    background-color: rgba(0, 0, 0, 0.7);
+    color: white;
+    padding: 4px 8px;
+    border-radius: 4px;
+    font-size: 12px;
+    pointer-events: none;
+    white-space: nowrap;
+  }
+
+  /* 仅添加必要的激活状态样式,不改变尺寸 */
+  .type_active {
+    color: #409EFF;
+    font-weight: 500;
+  }

+ 297 - 329
src/containers/center/market/index.vue

@@ -3,68 +3,60 @@
     <div class="right-block-bd ng-scope" ui-view="myStudyContent" style="position: relative;">
       <div class="panel-tit clear">
         <p class="fl">推荐课程</p>
-        <ul class="fr tips-intro" style="color:royalblue" @mouseover="showTip=true" @mouseleave="showTip=false" >
-          <li class="fs15 tit">
-            <i class="p-ico questiong-ico" style="margin-right: 0px;"/> 已将近退休年龄,是否还需要参加继续教育?
+        <ul class="fr tips-intro" style="color:royalblue" @mouseover="showTip=true" @mouseleave="showTip=false">
+          <li class="fs15 tit" style="margin-right: 20px;">
+            <i class="p-ico questiong-ico" style="margin-right: 0px;" />  温馨提醒
           </li>
         </ul>
 
-        <div class="fr tip-card" v-show="showTip" >
+        <div class="fr tip-card" v-show="showTip">
           <div class="p20">
-            <h3 >一、建设从业人员:</h3>
-            <h5 style="margin: 8px;">1、安管人员:</h5>
-            <h5 style="margin-left: 16px;">①企业主要负责人(A证):年龄18周岁以上,分管安全的技术负责人、安全总监到60岁,法定代表人没有年龄限制。</h5>
-            <h5 style="margin-left: 16px;">②项目负责人(B证):年龄18周岁以上,65周岁以下。</h5>
-            <h5 style="margin-left: 16px;">③安全生产管理人员(C证):年龄18周岁以上,60周岁以下。</h5>
-            <h4 style="margin: 8px;">2、特种作业人员:年龄18周岁以上,男性55周岁以下,女性50周岁以下。</h4>
-            <h4 style="margin: 8px;">3、八大员:年龄20周岁以上,60周岁以下。</h4>
-
-            <h2 style="margin:8px 0 16px 0px;">二、执业注册人员:</h2>
-            <h4 style="margin: 8px;">1、注册建造师(一级、二级)、注册监理工程师:年龄65周岁以下;</h4>
-            <h4 style="margin: 8px;">2、其他执业资格注册证件无年龄要求。</h4>
+            <h3>温馨提醒:</h3>
+            <h2 style="margin: 8px; 0 16px 0px;">一、福建省内可报</h2>
+            <h4 style="margin: 8px;">1.施工现场专业人员</h4>
+            <h4 style="margin: 8px;">2.20周岁以上,且男性小于60周岁,女性小于55周岁。</h4>
+
+            <h2 style="margin:8px 0 16px 0px;">二、宁德内可报:</h2>
+            <h4 style="margin: 8px;">1.安全生产管理人员</h4>
+            <h4 style="margin: 8px;">2.建筑施工特种作业人员</h4>
+            <h4 style="margin: 8px;">3.企业安全生产培训</h4>
           </div>
         </div>
       </div>
 
       <el-row class="pt-line mt20">
-        <el-col :span="6" v-for="item in typeList"  :key="item.id" v-if="item.isOpen && !item.isNew && !item.link">
-          <span :class="{active:item.name==type }" class="nav-btn w220" @click="type=item.name">{{item.name}} </span>
+        <el-col :span="4" :xs="12">
+          <el-button :class="{type_active : type==''}" class="type_item" type="text" @click="type=''">所有</el-button>
         </el-col>
+
+        <el-col :span="item.span" :xs="12" v-for="item in typeList" :key="item.id" v-if="item.isOpen && !item.link">
+          <el-button :class="{type_active:item.name==type }" class="type_item" type="text"
+            @click="type=item.name">{{item.name}}</el-button>
+        </el-col>
+
+
       </el-row>
-      <div class="mt10" v-if="type=='施工现场专业人员'">
-        <p style="color: red;font-size: 16px;">
-          报名对象为:2020年度及以后由培训单位核发的和2021年度换发的“电子培训合格证”且每年度需参加继续教育人员
-        </p>
-        <p style="color: red;font-size: 16px;">
-          注意:继续教育学员请先下载
-          <a style="color: blue;cursor: hand;" href="https://sm.hqedust.com/cdn/%E7%BB%A7%E7%BB%AD%E6%95%99%E8%82%B2%E5%A7%94%E6%89%98%E5%9F%B9%E8%AE%AD%E3%80%81%E6%89%BF%E8%AF%BA%E5%87%BD.doc">
-            《继续教育委托培训、承诺函》
-          </a>
-            ,填写并加盖单位公章后,在申请开通课程界面上传扫描件、并缴费
-        </p>
-      </div>
-	  <div class="mt10" v-if="type=='燃气经营企业'">
-	    <p style="color: red;font-size: 16px;">
-	      注意:继续教育学员请先下载
-	      <a style="color: blue;cursor: hand;" href="https://sm.hqedust.com/cdn/%E7%87%83%E6%B0%94%E4%BA%BA%E5%91%98%E4%BA%BA%E5%91%98%E5%B2%97%E4%BD%8D%E7%BB%A7%E7%BB%AD%E6%95%99%E8%82%B2%E5%A7%94%E6%89%98%E5%9F%B9%E8%AE%AD%E3%80%81%E6%89%BF%E8%AF%BA%E5%87%BD.doc">
-	        《燃气人员人员岗位继续教育委托培训、承诺函》
-	      </a>
-	        ,填写并加盖单位公章后,在申请开通课程界面上传扫描件、并缴费
-	    </p>
-	  </div>
 
 
       <ul class="pt-line mt20" v-if="ndList.length>1">
-        <li style="float: left;font-size: 16px;margin: 4px;padding: 4px;" >
-            <span>课程年度:</span>
+        <li style="float: left;font-size: 16px;margin: 4px;padding: 4px;">
+          <span>课程年度:</span>
+        </li>
+        <li style="float: left;">
+          <span :class="{type_active:nd==''}" class="nav-btn-years" @click="nd=''">所有</span>
         </li>
         <li v-for="item in ndList" style="float: left;">
-          <span :class="{active:item==nd }" class="nav-btn-years" @click="nd=item">{{item}} </span>
+          <span :class="{type_active:item==nd }" class="nav-btn-years" @click="nd=item">{{item}} </span>
+        </li>
+        <li style="float: left;">
+          <input type="text" style="line-height: 1;padding: 10px" v-model="filterName"
+            class="ui-ipt ng-pristine ng-valid filterName" placeholder="输入名字查找">
         </li>
       </ul>
 
       <ul class="m-course-list clear mt10 classlist">
-        <li v-for="(item,index) in list.slice(page*size-size, page*size)" :key="item.course_id" class="pt-sola mt20 pb10">
+        <li v-for="(item,index) in list.slice(page*size-size, page*size)" :key="item.course_id"
+          class="pt-sola mt20 pb10">
           <div>
             <span v-if="item.isFinish==1" class="p-ico2 ico-passed"></span>
             <div class="img">
@@ -73,27 +65,35 @@
                 <span v-if="item.is_pay==0 && item.buyImg =='' && item.payNo == ''">
                   还未购买
                 </span>
+                <span v-else-if="item.is_pay == 2 && item.is_import ==9">
+                  预购买中
+                </span>
                 <span v-else-if="item.is_pay==0" style="background-color: orange;">
                   等待审核
                 </span>
                 <span v-else-if="item.is_pay==2" style="background-color: #78335F;">申请驳回</span>
-                <span v-else-if="!item.is_pass" style="background-color: blue;padding: 2px;border-radius: 2px;">在学习中</span>
+                <span v-else-if="!item.is_pass"
+                  style="background-color: blue;padding: 2px;border-radius: 2px;">在学习中</span>
                 <span v-else style="background-color: green;padding: 2px;border-radius: 2px;">已经完成</span>
               </div>
 
               <div class="hover-block">
                 <div class="mask-bg"></div>
-                <el-button @click="gotoPlay(item.course_id)" v-if="item.is_pay==1" class="ui-btn btn-blue">进入学习</el-button>
-                <el-button @click="goPay(item)" v-else-if="item.is_pay==2 || !!item.buyImg" class="ui-btn btn-w">补充材料</el-button>
+                <el-button @click="gotoPlay(item.course_id)" v-if="item.is_pay==1"
+                  class="ui-btn btn-blue">进入学习</el-button>
+                <el-button @click="goPay(item)" v-else-if="item.is_pay==2 || !!item.buyImg"
+                  class="ui-btn btn-w">补充材料</el-button>
                 <el-button @click="goPay(item)" v-else-if="item.payErr" class="ui-btn btn-gr">申请购买</el-button>
                 <el-button @click="goPay(item)" v-else class="ui-btn btn-o">购买课程</el-button>
 
               </div>
             </div>
 
-            <div class="tit"><a>{{item.name}}</a></div>
+            <div class="tit"><a>{{item.displayName || item.name}}</a>
+            </div>
+
             <div class="info">
-              <p class="w1"><span class="p-ico c1"></span> {{item.type}}</p>
+              <p class="w1"><span class="p-ico c1"></span> {{item.type}} </p>
               <p class="w2"><span class="p-ico c2"></span>{{item.nd||'2020'}}</p>
               <p class="w1">
                 <span class="p-ico c5"></span> ¥{{item.fee/100}}元
@@ -105,161 +105,112 @@
           </div>
         </li>
       </ul>
+      <div v-if="list.length==0" class="tc mt20 red">
+        <span style="color: red;padding:40px;font-size: 26px;">已经最后一页了</span>
+      </div>
 
-      <el-pagination class="m-pages"
-        @current-change="(page)=>{this.page=page}"
-        :current-page="page"
-        :page-size="size"
-        layout="total, prev, pager, next"
-        :total="list.length">
+      <el-pagination class="m-pages" @current-change="(page)=>{this.page=page}" :current-page="page" :page-size="size"
+        layout="total, prev, pager, next" :total="list.length">
       </el-pagination>
 
     </div>
 
-    <el-dialog class="fc tc" :visible.sync="loginDialog" :close-on-click-modal="false" width="500px" @close="loginDialog=false">
-      <div class="login-dialog m-login">
-        <un-login @login="login"></un-login>
-      </div>
-    </el-dialog>
+    <el-dialog title="申请开通课程" :visible.sync="buyCourseDialog" width="550px" top="100px" align="left">
+      <template v-if="!showPay">
+        <el-form label-width="100px" :inline="false" :model="buyForm" :rules="rules"
+          ref="refBuyCourse">
+          <el-form-item label="账户余额" prop="balance" >
+            <p style="margin-left: 20px;font-size: 16px;">
+              <span v-if="userInfo.balance>=buyForm.fee" style="color: green;"> ¥{{userInfo.balance/100}}元</span>
+              <span v-else style="color: red;"> ¥{{userInfo.balance/100}}元</span>
+            </p>
+          </el-form-item>
 
+          <el-form-item label="报考岗位" prop="courseName">
+            <el-input type="text" placeholder="报考岗位" v-model="buyForm.courseName+'-'+buyForm.nd"
+            style="width: 360px;" />
+          </el-form-item>
 
-    <el-dialog title="申请开通课程"  :visible.sync="buyCourseDialog"
-    width="800px" top="20px" align="center">
-      <el-form label-width="80px" label-position="right" :inline="true" :model="buyForm" status-icon :rules="rules" ref="elForm">
-        <el-row align="left">
-          <el-col :lg="12" class="pl20 pr20">
-            <el-form-item lable="报考岗位" prop="courseName">
-              <label slot="label">报考岗位</label>
-              <el-input v-model="buyForm.courseName" type="text" placeholder="报考岗位" style="width: 220px;"></el-input>
-            </el-form-item>
 
-          </el-col>
+          <el-form-item label="证书编号" prop="rzCode">
+            <el-input v-model="buyForm.rzCode" type="text" style="width: 360px;">
+              <template #suffix>
+                <el-button @click="openShowAllRzcodeDialog" type="text" style="margin-right: 10px;">修改</el-button>
+              </template>
+            </el-input>
+          </el-form-item>
 
-          <el-col :lg="12" class="pl20 pr20">
-            <el-form-item lable="证书编号" prop="rzCode">
-              <label slot="label">证书编号</label>
-              <el-input v-model="buyForm.rzCode" type="text" placeholder="证书编号" style="width: 220px;"></el-input>
+          <el-divider></el-divider>
+          <h3 class="tc" style="margin-bottom: 16px;">发票信息</h3>
+          <el-form-item label="是否需要开票">
+            <el-radio-group v-model="buyForm.needInvoice">
+              <el-radio :label="1">是</el-radio>
+              <el-radio :label="0">否</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <template v-if="buyForm.needInvoice">
+            <el-form-item label="开票类型">
+              <el-radio-group v-model="buyForm.invoiceType">
+                <el-radio label="personal">个人</el-radio>
+                <el-radio label="enterprise">企业</el-radio>
+              </el-radio-group>
             </el-form-item>
-          </el-col>
-
-<!--          <el-row>
-            <span style="color: red;padding:10px"> 请认真核实报考岗位与证书编号,本课程不支持退款</span>
-          </el-row> -->
-
-          <el-col :lg="12" class="pl20 pr20">
-            <el-form-item lable="邮箱地址" prop="email">
-              <label slot="label">邮箱地址</label>
-              <el-input v-model="buyForm.email" type="textarea" placeholder="邮箱地址" style="width: 220px;"></el-input>
+            <el-form-item label="发票抬头" prop="title">
+              <el-input v-model="buyForm.title" :placeholder="buyForm.invoiceType === 'enterprise'?'请输入单位名称':'请输入真实名字'" style="width: 360px;"></el-input>
             </el-form-item>
-          </el-col>
-
-
-          <el-col :span="12" class="pl20 pr20">
-            <el-form-item lable="订单号" prop="payNo">
-              <label slot="label">订单编号</label>
-              <el-input v-model="buyForm.payNo" placeholder="输入订单编号后6位" style="width: 220px;" :minlength="6" :maxlength="6" show-word-limit ></el-input>
+            <el-form-item label="手机号码" prop="phone">
+              <el-input v-model="buyForm.phone" placeholder="请输入手机号码" style="width: 360px;"></el-input>
             </el-form-item>
-          </el-col>
-        </el-row>
-         <el-row>
-           <el-col :span="12" class="pl20 pr20">
-             <el-form-item lable="工作单位" prop="company">
-               <label slot="label">工作单位</label>
-               <el-input v-model="buyForm.company" type="textarea" placeholder="工作单位" style="width: 220px;"></el-input>
-             </el-form-item>
-           </el-col>
-          <el-col :span="12" class="pl20 pr20">
-            <el-form-item lable="备注信息" prop="marks">
-              <label slot="label">备注信息</label>
-              <el-input v-model="buyForm.marks" type="textarea" placeholder="姓名+工种名称,例:张三+土建施工员" style="width: 220px;"></el-input>
+            <el-form-item label="邮箱地址" prop="email">
+              <el-input v-model="buyForm.email" placeholder="请输入邮箱地址" style="width: 360px;"></el-input>
             </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row v-if="hasPromise(type)">
-            <div style="width: 700px;margin: 0 auto;text-align: left;color: red;">
-              提示:继续教育承诺函企业名称应与个人报名系统企业名称一致,如已变更单位的,
-            请先在个人系统帐号设置进入额外信息栏进行修改工作单位与报名承诺函工作单位相符后方可上传承诺函确认
-            </div>
-           <img :src="buyForm.buyImg" :onerror="errorImg" style="width: 160px;text-align: center;">
-           <do-upload @onFinish="(url)=>{buyForm.buyImg=url}" placeholder="上传承诺书" width="160px"> </do-upload>
-        </el-row>
+          </template>
 
-
-        <el-row>
+          <el-divider></el-divider>
           <h2 class="tc"> 本课程费用: <strong style="color: red;"> {{buyForm.fee/100}} 元 </strong> </h2>
-          <div style="width: 300px; margin: 0px auto;">
-            <img src="/static/images/wxpay.jpg"   width="300px" @click="showImg(buyForm.buyImg)">
-          </div>
-        </el-row>
 
-         <el-row class="tc fc">
-          <el-form-item >
+          <el-form-item label-width="0" style="margin: 30px auto;text-align: center;">
             <el-button @click="cancelBuyCourse">取 消</el-button>
-            <el-button @click="buyCourse" type="primary">确 定</el-button>
+            <el-button @click="generatePayQrcode" type="primary">提交订单</el-button>
+            <el-button v-if="userInfo.balance >= buyForm.fee" @click="doAccountPay" type="success">余额付款</el-button>
           </el-form-item>
-        </el-row>
-
-      </el-form>
-
-    </el-dialog>
-
-
-
-    <el-dialog title="承诺书" class="fc tc" :visible.sync="commitmentDialog">
-
-        <div>
-          <div v-if="hasPromise(type)">
-            <div class="commitment-header"> 致:三明市城市建设项目服务中心 </div>
-            <div class="tl commitment">
-                {{promise.promise_edu}}
-            </div>
-            <div class="commitment-footer">
-              特此诺书!
-            </div>
-          </div>
-
-          <div v-else-if="type=='建筑施工企业特种作业人员'">
-            <div class="tl commitment">
-              {{promise.promise_special}}
-            </div>
-
-          </div>
 
+        </el-form>
+      </template>
 
-          <div v-else-if="type=='园林绿化技术工人'">
-            <div class="tl commitment">
-              {{promise.promise_plant}}
-            </div>
-          </div>
-
-          <div v-else>
-            <div class="tl commitment">
-              {{promise.promise_other}}
-            </div>
-          </div>
+      <template v-if="showPay">
+        <h2 class="tc"> 本课程费用: <strong style="color: red;"> {{buyForm.fee/100}} 元 </strong> </h2>
+        <div style="width: 300px; margin: 20px auto;">
+          <img :src="wxPayUrl" width="300px" @click="showImg(wxPayUrl)">
+        </div>
+        <p class="tc">请使用微信扫码支付</p>
+        <el-row class="tc fc">
+          <el-button @click="cancelBuyCourse">关 闭</el-button>
+        </el-row>
+      </template>
 
 
-          <div class="tl commitment-content mt20">
-            <el-radio-group v-model="isCommitment">
-              <el-row class="mt10">
-                <el-radio :label="true">本人作出上述承诺</el-radio>
-              </el-row>
-              <el-row class="mt10">
-                <el-radio :label="false">本人不作出上述承诺</el-radio>
-              </el-row>
-            </el-radio-group>
-          </div>
+    </el-dialog>
 
-          <span slot="footer" class="dialog-footer">
-            <el-button @click="commitmentDialog = false">取 消</el-button>
-            <el-button type="primary" :disabled="isCommitment?false:true" @click="buyCourseDialog=true">确 定</el-button>
-          </span>
-        </div>
 
+    <el-dialog append-to-body close-on-click-modal :visible.sync="showImgDialog" style="margin-top: 0px;"
+      :width="width">
+      <img :src="imgUrl" @load="onLoad" alt="" />
     </el-dialog>
 
-    <el-dialog append-to-body close-on-click-modal :visible.sync="showImgDialog" style="margin-top: 0px;" :width="width">
-      <img :src="imgUrl" @load="onLoad" alt="" />
+    <el-dialog append-to-body close-on-click-modal :visible.sync="showAllRzcodeDialog" width="1024px">
+      <el-table v-if="buyForm.list.length>0" :data="buyForm.list" border>
+        <el-table-column label="证书编号" prop="certificateNum" align="center" />
+        <el-table-column label="岗位名称" prop="positionName" align="center" />
+        <el-table-column label="证书状态" prop="certificateStatus" align="center" />
+        <el-table-column label="发证机关" prop="certificateOrgan" align="center" />
+        <el-table-column label="发证时间" prop="startTime" align="center" />
+        <el-table-column label="选择" prop="reviewTime" align="center">
+          <template slot-scope="{row}">
+            <el-button @click="selectRzcode(row)" type="primary">选择</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
     </el-dialog>
   </div>
 </template>
@@ -269,17 +220,15 @@
     httpServer
   } from "@/components/httpServer/httpServer.js";
 
-  import promise from "./promise.js";
   import {
     MessageBox
   } from "element-ui";
-  import unLogin from "../../login/components/unLogin.vue";
   import {
     parseTime
   } from "@/utils";
   import DoUpload from '@/components/upload/index.vue'
   import exampleImg from '@/assets/template.png'
-
+  import {curDatetime} from '@/utils/date.js'
 
   import {
     mapGetters,
@@ -298,35 +247,40 @@
         showTip: false,
         width: "",
         type: '',
+        filterName: '',
         list: [],
-        nd: new Date().getFullYear()+'',
-        ndList:[],
-        allList:[],
+        nd: new Date().getFullYear() + '',
+        ndList: [],
+        allList: [],
         isCommitment: "",
         buyCourseDialog: false,
         showExample: false,
         showImgDialog: false,
+        showAllRzcodeDialog: false,
+        showPay: false,
         buyForm: {
-          marks: '',
+          payType:'wx',
           rzCode: '',
-          payNo:'',
-          email:'',
-          company:'',
-          buyImg:'',
-          name:'',
-          courseName: ''
+          email: '',
+          title: '',
+          phone: '',
+          courseName: '',
+          list: [],
+          fee: 0,
+          courseId: '',
+          needInvoice: 0,
+          invoiceType: 'personal'
         },
-        promise,
         imgUrl: '',
         qrcodeUrl: "",
+        wxPayUrl:"",
+        aliPayUrl:"",
         outTradeNo: "",
         courseFee: 0,
         title: '',
         timer: null,
         tickCount: 0,
         listLoading: false,
-        loginDialog: false,
-        isLoginOk: false,
         media: {},
         commitmentDialog: false,
         exampleImg: exampleImg,
@@ -335,18 +289,14 @@
             required: true,
             message: '请输入证书编号'
           }],
-          payNo:[
-            {
-              required: true,
-              message: '请输入订单编号后六位'
-            }
-          ],
-          marks:[
-            {
-              required: true,
-              message: '请输入备注信息'
-            }
-          ],
+          payNo: [{
+            required: true,
+            message: '请输入订单编号后六位'
+          }],
+          title: [{
+            required: true,
+            message: '抬头必须填写'
+          }],
           email: [{
               required: true,
               message: '请输入有效邮箱'
@@ -356,6 +306,7 @@
               message: '邮箱格式不正确',
               trigger: 'blur',
               transform(value) {
+                value = value.trim();
                 if (!/^\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)) {
                   return true
                 } else {}
@@ -364,7 +315,15 @@
           ],
           company: [{
             required: true,
-            message: '请输入所在企业'
+            message: '请输入单位名称'
+          }],
+          phone: [{
+            required: true,
+            message: '请输入手机号码'
+          }, {
+            pattern: /^1[3-9]\d{9}$/,
+            message: '手机号码格式不正确',
+            trigger: 'blur'
           }],
           courseName: [{
             required: true,
@@ -378,7 +337,6 @@
       ...mapGetters("user", ["typeList", "userInfo"])
     },
     components: {
-      unLogin,
       DoUpload
     },
     filters: {
@@ -389,33 +347,22 @@
     watch: {
       type(val) {
         this.page = 1
-        this.nd = new Date().getFullYear()+''
-        this.getData()
+        this.nd = new Date().getFullYear() + ''
+        this.filterData()
       },
-      nd(val){
+      nd(val) {
         this.filterData()
       },
-      isCommitment(val) {
-        console.log(val)
+      filterName() {
+        this.page = 1
+        this.filterData()
       }
     },
     beforeMount() {
-      this.type = this.$route.query.type||''
-      if( this.type ){
-        this.getData();
-      }
-    },
-    mounted(){
-      if (!this.type && this.typeList.length > 0) {
-        for( let i in this.typeList){
-          let item = this.typeList[i];
-          if(item.isOpen && !item.isNew ){
-            this.type = item.name;
-            break;
-          }
-        }
-      }
+      this.type = this.$route.query.type || ''
+      this.getData();
     },
+
     beforeDestroy() {
       if (this.timer) window.clearInterval(this.timer);
     },
@@ -436,131 +383,150 @@
         }
         this.width = width + "px";
       },
+      initTimer() {
+        if (this.timer) window.clearInterval(this.timer);
+        this.tickCount = 0;
+        this.timer = window.setInterval(() => {
+          this.tickCount++;
+          this.tickTimer();
+        }, 3000);
+      },
+      tickTimer() {
+        if (this.tickCount > 100) {
+          if (this.timer) window.clearInterval(this.timer);
+          this.$message.warning("支付超时,请重新支付");
+          this.buyCourseDialog = false;
+          return;
+        }
+        let outTradeNo = this.outTradeNo;
+        httpServer("weixin.isPayOk", { outTradeNo }).then((res) => {
+          if (res.code == 200 && !!res.data) {
+            if (this.timer) window.clearInterval(this.timer);
+            this.$message.successMsg("支付成功", 2);
+            this.buyCourseDialog = false;
+            this.getData();
+          }
+        });
+      },
+      doAccountPay(){
+        let param = Object.assign({}, this.buyForm);
+        httpServer("weixin.doAccountPay", {courseId}).then((res) => {
+          if (res.code != 200) return;
+          this.$message.successMsg("支付成功", 2);
+          this.buyCourseDialog = false;
+          this.getData();
+        });
+      },
+      generatePayQrcode() {
+        this.$refs["refBuyCourse"].validate((valid) => {
+          if(!valid) return;
+          let payType = this.buyForm.payType;
+          let apiName = payType === 'wx' ? "weixin.DoWxpay" : "weixin.DoaliPay";
+          let {
+            courseId,
+            courseName,
+            title,
+            email,
+            phone,
+            needInvoice,
+            invoiceType
+          } = this.buyForm;
+          let param = {
+            courseId,
+            title,
+            phone,
+            email,
+            needInvoice,
+            invoiceType
+          };
+          httpServer(apiName, param).then((res) => {
+            if (res.code != 200) return;
+            let { outTradeNo, showUrl } = res.data;
+            this.outTradeNo = outTradeNo;
+            if (payType === 'wx') {
+              this.wxPayUrl = showUrl;
+            } else {
+              this.aliPayUrl = showUrl;
+            }
+            this.showPay = true
+            this.initTimer();
+          });
+        })
+      },
       getData() {
-        if (!this.type) return;
         let param = {
-          size: 100,
-          type: this.type,
+          size: 1000,
+          trainType: 2,
           from: 0
         }
         httpServer("course.getcoursemarket", param).then((res) => {
           if (res.code == 200) {
             this.allList = res.data.list;
-            this.ndList = [];
-            for( let i in this.allList ){
-              let nd = this.allList[i].nd;
-              if(this.ndList.indexOf( nd ) == -1 ) this.ndList.push( nd );
-            }
             this.filterData()
           }
         });
       },
-      filterData(){
-        let nd = this.nd;
-        this.list = this.allList.filter( res=>{
-          return res.nd == nd;
-        })
-      },
-      cancelBuyCourse(){
-        this.$refs["elForm"].resetFields();
-        this.buyCourseDialog=false
-      },
-      buyCourse() {
-        this.$refs["elForm"].validate((valid) => {
-          if (!valid) return;
-          let { marks, company, payNo, course_id, courseName, rzCode, name,  email, buyImg} = this.buyForm;
-          if (courseName != name) {
-            this.$message.errorMsg("报考岗位有误", 1)
-            return;
+      filterData() {
+        let nd = this.nd || '';
+        let filterName = this.filterName || '';
+        let type = this.type || ''
+        this.ndList = [];
+        this.list = [];
+        for (let i in this.allList) {
+          let item = this.allList[i];
+          if (type != '' && item.type != type) continue;
+          if (this.ndList.indexOf(item.nd) == -1) {
+            this.ndList.push(item.nd);
           }
-          let param = { marks, payNo, company,courseId: course_id, courseName,rzCode,email,buyImg }
-          httpServer("course.buyCourse", param).then((res) => {
-            if (res.code == 200) {
-              this.$message.successMsg("申请购买成功", 1);
-              this.buyForm.temp.marks = this.buyForm.marks
-              Object.assign( this.buyForm.temp, param);
-              this.$refs["elForm"].resetFields();
-              this.buyForm.temp.is_pay = 0
-              this.buyCourseDialog = false
-            }
-          });
-        })
+          if (filterName!='' && item.name.indexOf(filterName) == -1) continue;
+          if (nd != '' && item.nd != nd) continue;
+          this.list.push(item)
+        }
       },
-      gotoDetail(courseId) {
-        this.$router.push(`/main/course/${courseId}`);
+      cancelBuyCourse() {
+        if (this.timer) window.clearInterval(this.timer);
+        this.$refs["elForm"] && this.$refs["elForm"].resetFields();
+        this.buyCourseDialog = false
+        this.showPay = false;
       },
-      uploadBuyImg(url) {
-        this.buyForm.buyImg = url;
+      openShowAllRzcodeDialog() {
+        this.showAllRzcodeDialog = true;
       },
-      hasPromise( type ){
-          return type == '施工现场专业人员' || type == '燃气经营企业'
+      selectRzcode(row) {
+        this.buyForm.rzCode = row.certificateNum;
+        this.showAllRzcodeDialog = false;
       },
-      testBuyOK(item){
-        this.stage = 2;
-        this.isCompanyPay = 1;
-        this.isCommitment=''
-        this.buyForm.rzCode = item.rzCode;
-        this.buyForm.company = 'ssss';
-        this.buyForm.courseName = item.courseName;
-        this.buyCourseDialog = true;
+      gotoDetail(courseId) {
+        this.$router.push(`/main/course/${courseId}`);
       },
-      goPay( item ) {
+      goPay(item) {
+        this.showPay = false;
         this.buyForm = Object.assign(this.buyForm, item)
-        this.buyForm.company = this.userInfo.company;
+        this.buyForm.phone = this.userInfo.phone;
         this.buyForm.temp = item;
         this.buyForm.courseName = item.name;
-        let isEdu = this.hasPromise(item.type);
-        // this.testBuyOK(item)
-        // return;
+        this.buyForm.courseId = item.course_id;
+        this.buyForm.fee = item.fee;
         // 检查
-        item.payErr = true;
         httpServer("weixin.docheckpay", {
           courseId: item.course_id
         }).then((res) => {
-          if (res.code == 200) {
-            this.stage = 2;
-            this.isCompanyPay = 0;
-            this.isCommitment=''
-            Object.assign( this.buyForm, res.data);
-
-            if( isEdu){
-              this.buyCourseDialog = true;
-            }else{
-              this.commitmentDialog = true;
-            }
-          }else{
-            console.log("payErr")
-            item.payErr = true;
-          }
+          if (res.code != 200) return;
+          Object.assign(this.buyForm, res.data);
+          this.buyCourseDialog = true;
         });
       },
-      gotoPlay(courseId) {
-        this.$router.push(`/center/class/train`);
+      gotoPlay(item) {
+        let courseId = item.courseId;
+        if( curDatetime() < item.startDate){
+          this.$message.warning("还未到学习时间");
+          return
+        }
+        this.$router.push({name:'play', params:{courseId}})
       },
       handleCurrentChange(page) {
         this.page = page;
-        this.getData()
-      },
-      login(param) {
-        httpServer("Auth.LoginStudy", param).then(res => {
-          if (res.code == 200) {
-            let {
-              nickname,
-              token,
-              uid
-            } = res.data;
-            localStorage.nickname = nickname;
-            localStorage.token = token;
-            localStorage.uid = uid
-            this.user = {
-              nickname,
-              token,
-              uid
-            };
-            this.loginOk = true
-            this.loginDialog = false
-          }
-        })
+        this.filterData()
       }
     },
   };
@@ -598,21 +564,23 @@
   .commitment-content {
     font-size: 16px;
   }
-  .tip-card{
+
+  .tip-card {
     border-radius: 20px;
-    font-size:14px;
+    font-size: 14px;
     float: right;
     width: 420px;
     right: 20px;
     position: absolute;
-    top:50px;
+    top: 50px;
     color: white;
-    background-color: #7fbae4 ;
+    background-color: #7fbae4;
     z-index: 1000;
   }
+
   .tips-intro {
-      color: #468cf2;
-      cursor: pointer;
-      position: relative;
+    color: #468cf2;
+    cursor: pointer;
+    position: relative;
   }
 </style>

+ 6 - 4
src/containers/center/market/promise.js

@@ -15,9 +15,11 @@ export default {
               每年不得少于24学时。本人自愿选择参加三明市城市建设项目服务中心组织的特种操作人员继续教育,
               对报名相关条件、提交的材料及收费情况均已知晓!且所提交的材料真实有效,
               如有存在虚假,所导致的一切后果本人自愿承担。`,
-  'promise_edu':`根据福建省住房和城乡建设厅《关于做好施工现场专业人员换发电子培训合格证相关工作的通知》(
-              闽建人函〔2021〕6号)的文件精神,施工现场专业人员符合换证条件的按要求参加不少于32学时的继续教育。
-              按照明建人才〔2021〕8号关于施工现场专业人员岗位证书继续教育的通知,
-              本人现自愿委托参加贵中心所组织的继续教育培训学习,委托培训费用_3_元/学时。`,
+  'promise_edu':`我司自愿委托贵中心对本企业施工现场专业人员进行专业知识继续教育8学时学习培训工作,培训费用为3元/学时。
+              我司承诺:已按照《关于做好施工现场专业人员换发电子培训合格证相关工作的通知》(闽建人函〔2021〕6号)要求,
+              分别完成企业组织的本年度施工现场专业人员专业知识不少于22学时,
+              累计不少于24学时的继续教育培训。
+              如有不实,本企业愿意承担相应责任。`,
+  'notice_hangong':` 根据(闽建办人函〔2023〕6号)关于建筑施工企业安全生产管理人员和特种作业人员岗位考核及证书变更有关事项的通知,“施工现场电焊工”未列入《国家职业资格目录(2021 年版)》,各地住建部门不再开展考核发证。原持有的《建筑施工特种作业操作资格证(施工现场电焊工)》有效期到期后自动注销。`,
 
 }

+ 89 - 67
src/containers/center/market/trainIndex.vue

@@ -27,10 +27,14 @@
       </div>
 
       <el-row class="pt-line mt20">
-        <el-col :span="6" v-for="item in typeList"  :key="item.id" v-if="item.isOpen && item.isNew==1">
-          <span :class="{active:item.name==type }" class="nav-btn w220" @click="type=item.name">{{item.name}} </span>
+        <el-col :span="4" :xs="12">
+          <el-button :class="{type_active : type==''}" class="type_item" type="text" @click="type=''">所有</el-button>
+        </el-col>
+        <el-col :span="4" :xs="12" v-for="item in typeList"  :key="item.id" v-if="item.isOpen && item.isNew==1">
+          <el-button :class="{type_active:item.name==type }" class="type_item" type="text" @click="type=item.name">{{item.name}} </el-button>
         </el-col>
       </el-row>
+
       <div class="mt10" v-if="type=='施工现场专业人员'">
         <p style="color: red;font-size: 16px;">
           报名对象为:2020年度及以后由培训单位核发的和2021年度换发的“电子培训合格证”且每年度需参加继续教育人员
@@ -48,9 +52,23 @@
         <li style="float: left;font-size: 16px;margin: 4px;padding: 4px;" >
             <span>课程年度:</span>
         </li>
+
+        <li  style="float: left;">
+          <span :class="{type_active:nd==''}" class="nav-btn-years" @click="nd=''">所有</span>
+        </li>
+
         <li v-for="item in ndList" style="float: left;">
-          <span :class="{active:item==nd }" class="nav-btn-years" @click="nd=item">{{item}} </span>
+          <span :class="{type_active:item==nd }" class="nav-btn-years" @click="nd=item">{{item}} </span>
+        </li>
+
+        <li style="float: left;">
+          <input type="text" style="line-height: 1;padding: 10px"
+            v-model="filterName"
+            class="ui-ipt ng-pristine ng-valid filterName"
+            placeholder="输入名字查找">
         </li>
+
+
       </ul>
 
       <ul class="m-course-list clear mt10 classlist">
@@ -194,41 +212,19 @@
 
 
 
-    <el-dialog title="承诺书" class="fc tc" :visible.sync="commitmentDialog">
-
+    <el-dialog :title="promiseType[promise.showType]" class="fc tc" :visible.sync="commitmentDialog">
         <div>
-          <div v-if="type=='施工现场专业人员'">
-            <div class="commitment-header"> 致:三明市城市建设项目服务中心 </div>
+          <div>
+            <div class="commitment-header"> {{promise.title}} </div>
             <div class="tl commitment">
-                {{promise.promise_edu}}
+                {{promise.content}}
             </div>
             <div class="commitment-footer">
-              特此诺书!
-            </div>
-          </div>
-
-          <div v-else-if="type=='建筑施工企业特种作业人员'">
-            <div class="tl commitment">
-              {{promise.promise_special}}
-            </div>
-
-          </div>
-
-
-          <div v-else-if="type=='园林绿化技术工人'">
-            <div class="tl commitment">
-              {{promise.promise_plant}}
+              {{promise.footer}}
             </div>
           </div>
 
-          <div v-else>
-            <div class="tl commitment">
-              {{promise.promise_other}}
-            </div>
-          </div>
-
-
-          <div class="tl commitment-content mt20">
+          <div class="tl commitment-content mt20" v-if="promise.showType==2">
             <el-radio-group v-model="isCommitment">
               <el-row class="mt10">
                 <el-radio :label="true">本人作出上述承诺</el-radio>
@@ -239,12 +235,16 @@
             </el-radio-group>
           </div>
 
-          <span slot="footer" class="dialog-footer">
+          <span slot="footer" class="dialog-footer" v-if="promise.showType==2">
             <el-button @click="commitmentDialog = false">取 消</el-button>
             <el-button type="primary" :disabled="isCommitment?false:true" @click="buyCourseDialog=true">确 定</el-button>
           </span>
-        </div>
 
+          <div slot="footer" class="dialog-footer mt20" v-else>
+            <el-button @click="commitmentDialog = false">取 消</el-button>
+            <el-button type="primary"  @click="buyCourseDialog=true">继续购买</el-button>
+          </div>
+        </div>
     </el-dialog>
 
     <el-dialog append-to-body close-on-click-modal :visible.sync="showImgDialog" style="margin-top: 0px;" :width="width">
@@ -288,9 +288,12 @@
         width: "",
         type: '',
         list: [],
+        promiseType:{1:'温馨提示', 2:'承诺书',  3:'委 托 书'},
         nd: new Date().getFullYear()+'',
+        filterName: '',
         ndList:[],
         allList:[],
+        typeInfo:{},
         isCommitment: "",
         buyCourseDialog: false,
         showExample: false,
@@ -364,7 +367,7 @@
       };
     },
     computed: {
-      ...mapGetters("user", ["typeList", "userInfo"])
+      ...mapGetters("user", ["typeList", "userInfo", "promiseList"])
     },
     components: {
       unLogin,
@@ -377,12 +380,15 @@
     },
     watch: {
       type(val) {
-        if( !val ) return;
         this.page = 1
         this.nd = new Date().getFullYear()+''
-        this.getData()
+        this.filterData()
       },
-      nd(val){
+      nd(){
+        this.filterData()
+      },
+      filterName(){
+        this.page = 1
         this.filterData()
       },
       isCommitment(val) {
@@ -391,21 +397,7 @@
     },
     beforeMount() {
       this.type = this.$route.query.type||''
-      if( this.type ){
-        this.getData();
-      }
-    },
-    mounted(){
-      console.log( this.type, this.typeList)
-      if (!this.type && this.typeList.length > 0) {
-        for( let i in this.typeList){
-          let item = this.typeList[i];
-          if(item.isOpen && item.isNew ){
-            this.type = item.name;
-            break;
-          }
-        }
-      }
+      this.getData();
     },
     beforeDestroy() {
       if (this.timer) window.clearInterval(this.timer);
@@ -428,29 +420,35 @@
         this.width = width + "px";
       },
       getData() {
-        if (!this.type) return;
         let param = {
-          size: 100,
-          type: this.type,
+          size: 1000,
+          trainType: 1,
           from: 0
         }
         httpServer("course.getcoursemarket", param).then((res) => {
           if (res.code == 200) {
             this.allList = res.data.list;
-            this.ndList = [];
-            for( let i in this.allList ){
-              let nd = this.allList[i].nd;
-              if(this.ndList.indexOf( nd ) == -1 ) this.ndList.push( nd );
-            }
             this.filterData()
           }
         });
       },
       filterData(){
-        let nd = this.nd;
-        this.list = this.allList.filter( res=>{
-          return res.nd == nd;
-        })
+        let nd = this.nd||'';
+        let filterName = this.filterName||'';
+        let type = this.type||''
+        this.ndList = [];
+        this.list  = [];
+        for(let i in this.allList){
+          let item = this.allList[i];
+          if( type != '' && item.type != type) continue;
+          if(this.ndList.indexOf(item.nd) == -1){
+            this.ndList.push(item.nd);
+          }
+          if(filterName!='' && item.name.indexOf(filterName) == - 1) continue;
+          if(nd != ''  && item.nd != nd ) continue;
+          this.list.push(item)
+        }
+
       },
       cancelBuyCourse(){
         this.$refs["elForm"].resetFields();
@@ -483,13 +481,37 @@
       uploadBuyImg(url) {
         this.buyForm.buyImg = url;
       },
+      matchPromise( row ){
+        let {name,type} = row;
+        let isMatch = false
+        for( let i in this.promiseList){
+          let item = this.promiseList[i];
+          if( name == item.type || item.type == type ){
+            this.promise = Object.assign( this.promise, item);
+            console.log("promise", this.promise);
+            isMatch = true
+          } else if( !isMatch && item.type == '默认类型'){
+            this.promise = Object.assign( this.promise, item);
+          }
+        }
+      },
+      matchType(type) {
+        for (let i in this.typeList) {
+          let item = this.typeList[i];
+          if(item.name == type) {
+            this.typeInfo = Object.assign(this.typeInfo, item);
+            return;
+          }
+        }
+      },
       goPay( item ) {
         this.buyForm = Object.assign(this.buyForm, item)
         this.buyForm.company = this.userInfo.company;
         this.buyForm.temp = item;
         this.buyForm.courseName = item.name;
         let isEdu = this.type == '施工现场专业人员';
-
+        this.matchPromise(item);
+        this.matchType(item.type)
         // 绕开检查
         // this.stage = 2;
         // this.isCompanyPay = 1;
@@ -516,11 +538,11 @@
         });
       },
       gotoPlay(courseId) {
-        this.$router.push(`/center/class/train`);
+        this.$router.push({name:'play', params:{courseId}})
       },
       handleCurrentChange(page) {
         this.page = page;
-        this.getData()
+        this.filterData()
       },
       login(param) {
         httpServer("Auth.LoginStudy", param).then(res => {

+ 99 - 27
src/containers/center/myOrder/index.vue

@@ -1,55 +1,48 @@
 <template>
   <div class="m-right-block fr mh576" style="position: relative;">
     <div class="right-block-bd">
-      <div class="mb10 tc mt20">
-        <span class="p-ico2 worm-ico-o"></span>
-        注:未付款的订单设置3小时时限,3小时过后未付的订单就不再显示。
-      </div>
 
       <div  class="ng-scope">
-
         <el-table v-loading="listLoading" :data="list" border fit highlight-current-row>
 
-          <el-table-column label="交易订单" >
+          <el-table-column label="交易订单" prop="outTradeNo" align="center" />
+          <el-table-column label="岗位名称" align="center" >
             <template slot-scope="{row, $index}">
-              <span>{{row.id}}</span>
+              <span>{{row.courseName}}</span>
             </template>
           </el-table-column>
-          <el-table-column label="培训内容" >
+
+          <el-table-column label="证书编号" align="left" min-width="100" >
             <template slot-scope="{row, $index}">
-              <span>{{row.name}}</span>
+              <span>{{row.rzCode}}</span>
             </template>
           </el-table-column>
 
-          <el-table-column label="证书类型" >
-            <template slot-scope="{row, $index}">
-              <span>{{row.type}}-{{row.nd}}</span>
+          <el-table-column label="金额(元)" align="center" >
+            <template slot-scope="{row}">
+              <span>{{ row.payFee|money }} </span>
             </template>
           </el-table-column>
 
-          <el-table-column label="学时">
+          <el-table-column label="提交时间" align="center" >
             <template slot-scope="{row, $index}">
-              <span>{{row.totalXs/10}}</span>
+              <span>{{row.createAt}}</span>
             </template>
           </el-table-column>
 
-          <el-table-column label="金额(元)">
-            <template slot-scope="{row}">
-              <span>{{ row.fee|money }} </span>
+          <el-table-column label="操作" fixed="right" align="center" min-width="80">
+            <template  slot-scope="{row}">
+              <el-button v-if="row.refundStatus==0" @click="payRefund(row)" type="primary"  size="small" >退款</el-button>
+              <el-button v-else-if="row.refundStatus==1" @click="getPayInfo(row )"  type="text" size="small">退款中</el-button>
+              <el-button v-else-if="row.refundStatus==2" @click="getPayInfo(row )" type="text" size="small">退款通过</el-button>
+              <el-button v-else-if="row.refundStatus==3" @click="getPayInfo(row )" type="text" style="color: green;" size="small" >到账</el-button>
+              <el-button v-else type="text" size="small" @click="getPayInfo(row )" style="color: red;">拒绝</el-button>
             </template>
           </el-table-column>
 
-          <el-table-column label="订单状态" min-width="120">
-            <template slot-scope="{row}">
-              <span v-if="row.isPay==1" style="color: green;">支付完成</span>
-              <span v-if="row.isPay==0" >待审核</span>
-              <span v-if="row.isPay==2" style="color: red;" @click="rebuyInfo(row)">{{row.marks||'拒绝'}}
-                <el-button  type="text">修改</el-button>
-              </span>
-            </template>
-          </el-table-column>
         </el-table>
 
+
         <el-pagination
             @current-change="handleCurrentChange"
             :current-page="page"
@@ -63,6 +56,45 @@
           <IBuyCourse :courseId="courseId" @finish="reload"></IBuyCourse>
         </el-dialog>
 
+        <el-dialog title="退款申请"  :visible.sync="payRefundDialog" :append-to-body="true"  width="500px" top="20px" >
+          <el-form :model="form" :rules="rules" label-width="120px" ref="elAdd">
+          		<el-form-item label="订单信息" prop="type">
+                <span> {{form.title}} </span>
+          		</el-form-item>
+              <el-form-item label="退款金额" prop="type">
+                <span> ¥{{form.payFee/100}} 元 </span>
+              </el-form-item>
+          		<el-form-item label="退款原因" prop="footer">
+          			<el-input v-model="form.reason" type="textarea" style="width: 300px;" />
+          		</el-form-item>
+          	</el-form>
+          	<div class="dialog-footer" slot="footer" >
+          		<el-button @click="closeDialog('payRefundDialog')">取 消</el-button>
+          		<el-button @click="doPayRefund()" type="primary">确 定</el-button>
+          	</div>
+          </el-dialog>
+        </el-dialog>
+
+
+        <el-dialog  :visible.sync="refundInfoDialog" :append-to-body="true"
+        width="800px" top="20px" align="center">
+            <h2 style="text-align: center;">
+                <span style="margin: 0 20px;">编号:{{refundInfo.outTradeNo}}</span>
+                <span style="margin: 0 20px;">费用:{{refundInfo.payFee|money}}</span>
+                <span style="margin: 0 20px;">状态:
+                <strong style="color: red;">
+                {{refundInfo.refundStatus|status}}
+                </strong>
+                </span>
+            </h2>
+            <el-table :data="refundList" border fit highlight-current-row style="margin-top: 10px;">
+              <el-table-column label="编号" prop="id"  />
+              <el-table-column label="动作" prop="action" />
+              <el-table-column label="内容" prop="context" />
+              <el-table-column label="时间" prop="createAt"/>
+            </el-table>
+        </el-dialog>
+
       </div>
     </div>
   </div>
@@ -85,7 +117,12 @@
         total: 1,
         courseId:0,
         list: [],
+        form: {id:0, title:'', reason:'', payFee:0},
+        refundInfo:{},
+        refundList:[],
         buyCourseDialog: false,
+        refundInfoDialog: false,
+        payRefundDialog:false,
         listLoading: false
       };
     },
@@ -96,6 +133,15 @@
       money(val){
         return '¥'+ (val/100);
       },
+      status(val){
+        let map ={
+          1: '申请中',
+          2: '审核通过',
+          3: '退款成功',
+          4: '拒绝退款'
+        }
+        return map[val];
+      },
       payStatus(item){
         if(item.status == 2){
           return '支付完成';
@@ -116,9 +162,35 @@
         this.buyCourseDialog = false;
         this.getData();
       },
+      payRefund(row){
+        this.form.logId = row.id;
+        this.form.payFee = row.payFee;
+        this.form.reason = ''
+        this.form.title = `${row.type} - ${row.courseName} - ${row.nd}`
+        this.payRefundDialog = true;
+      },
+      doPayRefund(){
+        httpServer('Weixin.PayRefund', this.form).then(res => {
+          if(res.code != 200) return;
+          this.$message.successMsg("申请成功", 2);
+          this.getData()
+          this.payRefundDialog = false;
+        })
+      },
+      getPayInfo(row){
+        httpServer('Weixin.GetPayLogInfo', {logId:row.id}).then(res=>{
+          if(res.code != 200) return;
+          this.refundInfo = res.data.info||{};
+          this.refundList = res.data.list||[];
+          this.refundInfoDialog = true;
+        })
+      },
+      closeDialog(modal){
+        this[model] = false;
+      },
       getData() {
         let param = {from: (this.page - 1) * this.size,size:this.size}
-        httpServer('course.GetSmPayCourseList', param).then(({
+        httpServer('course.GetPayLogList', param).then(({
           data,
           code
         }) => {

+ 5 - 12
src/containers/center/play/components/iCourseAnswerTest.vue

@@ -2,16 +2,16 @@
   <div style="display: flex;">
 
     <ul style="width: 180px;">
-      <li class="mt10">
+      <li class="mt10" v-if="counter[1]">
         <el-button type="primary" @click="type=1" class="w130" > 判断题 {{counter[1]}}题</el-button>
       </li>
-      <li class="mt10">
+      <li class="mt10" v-if="counter[2]">
         <el-button type="warning" @click="type=2" class="w130" >单选题 {{counter[2]}}题</el-button>
       </li>
-      <li class="mt10">
+      <li class="mt10" v-if="counter[3]">
         <el-button type="success" @click="type=3"  class="w130">多选题 {{counter[3]}}题</el-button>
       </li>
-      <li class="mt10">
+      <li class="mt10" v-if="counter[4]">
         <el-button type="danger" @click="type=4" class="w130">案例题 {{counter[4]}}题</el-button>
       </li>
     </ul>
@@ -101,7 +101,6 @@
     data() {
       return {
         index: 0,
-        groupId: 10,
         id: 0,
         type: 1,
         finish: false,
@@ -115,16 +114,10 @@
           answer2:'',
           answer3:'',
           answer4:'',
-        },
-        counter:{
-          1:150,
-          2:156,
-          3:149,
-          4:45
         }
       }
     },
-    props: ['info', 'extraXs'],
+    props: ['info', 'extraXs', 'groupId', 'counter'],
     mounted() {
       this.id = this.info.id || 0;
       this.index = this.extraXs.index1 || 0;

+ 118 - 87
src/containers/center/play/components/iCourseInfo.vue

@@ -1,112 +1,143 @@
-
 <template>
-    <el-row class="p20">
-      <el-col :lg="6" :md="6" :span="6">
-        <el-progress
-          class="mprocess"
-          type="circle"
-          :width="150"
-          :format="formatFinish"
-          :stroke-width="18"
-          :percentage="info.percent"
-          >
-        </el-progress>
+  <el-row class="p20 waphide">
+    <el-col :lg="6" :md="6" :span="6">
+      <el-progress class="mprocess" type="circle" :width="150" :format="formatFinish" :stroke-width="18"
+        :percentage="info.percent">
+      </el-progress>
 
-      </el-col>
+    </el-col>
 
-      <el-col :lg="6"  :md="6" :span="6">
-        <el-progress v-if="tpl.examGroupId>0"
-           class="mprocess"
-           type="circle"
-           :width="150"
-           :format="formatExam"
-           :stroke-width="18"
-           :percentage="info.score"
-           >
-        </el-progress>
+    <el-col :lg="6" :md="6" :span="6">
+      <el-progress v-if="tpl.testGroupId>0" class="mprocess" type="circle" :width="150" :format="formatTest"
+        :stroke-width="18" :percentage="finishCount">
+      </el-progress>
 
-        <el-progress v-else
-           class="mprocess"
-           type="circle"
-           :width="150"
-           :format="formatString('--')"
-           :stroke-width="18"
-           :percentage="0"
-           >
-        </el-progress>
-      </el-col>
+      <el-progress v-else-if="tpl.examGroupId>0" class="mprocess" type="circle" :width="150" :format="formatExam"
+        :stroke-width="18" :percentage="info.score">
+      </el-progress>
 
-      <el-col :lg="6" :md="6" :span="6">
-        <img src="../../../../assets/wxapp.jpg"  width="150px"  />
-      </el-col>
+      <el-progress v-else class="mprocess" type="circle" :width="150" :format="formatString('--')" :stroke-width="18"
+        :percentage="0">
+      </el-progress>
+    </el-col>
 
-      <el-col :lg="6" :md="6" :span="6">
-        <div>
-          <p style="font-size: 24px;"> 说明 </p>
-          <p class="mt10" >岗位名称:{{tpl.name}}<strong style="color: red;">{{tpl.nd}}</strong></p>
-          <p class="mt10">
-            学习截至时间:
-            <strong style="color: red;">
-            {{info.startDate|add80Date}}
-            </strong>
-          </p>
-          <p class="mt10" style="color: red;" v-if="tpl.examGroupId>0" >报名成功,80天内完成学习与考试</p>
-          <p class="mt10" v-if="tpl.examGroupId==0" >学完所有课程,即可打印学时证明</p>
+    <el-col :lg="6" :md="6" :span="6">
+      <img src="../../../../assets/wxapp.jpg" width="150px" />
+    </el-col>
 
-          <div>
-              <el-button type="primary" class="mt10" style="font-size: 14px;" @click="startExam" v-if="tpl.examGroupId>0" :disabled="info.score>=60">
-                参加考试
-              </el-button>
-              <el-button type="primary" class="mt10" style="font-size: 14px;" @click="printCert" v-if="tpl.tplId>0">
-                学时证明
-              </el-button>
-          </div>
-         </div>
-      </el-col>
+    <el-col :lg="6" :md="6" :span="6">
+      <div>
+        <p style="font-size: 24px;"> 说明 </p>
+        <p class="mt10">岗位名称:{{tpl.name}}<strong style="color: red;">{{tpl.nd}}</strong></p>
+        <p class="mt10">
+          学习截至时间:
+          <strong style="color: red;">
+            {{info.canStudyDate}}
+          </strong>
+        </p>
+        <p class="mt10" v-if="tpl.testGroupId>0">
+          参与练习<strong style="color: red;"> {{finishCount}}</strong>题,满100题可得
+          <strong style="color: red;">{{tpl.testXs/10}}</strong>学时
+        </p>
+        <p class="mt10" style="color: red;" v-if="tpl.examGroupId>0">报名成功,80天内完成学习与考试</p>
+        <p class="mt10" v-if="tpl.examGroupId==0">完成所有视频,即可打印学时证明</p>
+
+        <div>
+          <el-button type="primary" class="mt10" style="font-size: 14px;" @click="startExam" v-if="tpl.examGroupId>0"
+            :disabled="info.score>=60">
+            参加考试
+          </el-button>
+          <el-button type="primary" class="mt10" style="font-size: 14px;" @click="startExamTest"
+            v-if="tpl.testGroupId>0" :disabled="info.score>=60">
+            在线练习
+          </el-button>
+          <el-button type="primary" class="mt10" style="font-size: 14px;" @click="printCert" v-if="tpl.tplId>0">
+            学时证明
+          </el-button>
+        </div>
+      </div>
+    </el-col>
 
-    </el-row>
-    </template>
+  </el-row>
+</template>
 
-  <script>
-  import { toDate } from "@/utils/date";
+<script>
+  import {
+    toDate
+  } from "@/utils/date";
   export default {
     name: "Index",
     data() {
       return {
+        finishCount:0
       }
     },
-    filters:{
-      add80Date( date ){
-        let val = new Date(date).getTime() + 80*86400*1000;
-        return toDate( val )
+    filters: {
+      add80Date(date) {
+        let val = new Date(date).getTime() + 80 * 86400 * 1000;
+        return toDate(val)
 
+      },
+      testCount(val) {
+        let finish= (+val.index1) + (+val.index2) + (+val.index3) + (+val.index4) || 0
+        return finish;
       }
     },
-    props:['tpl', 'info'],
-     methods: {
-        startExam(){
-          if( this.tpl.isClosed ==1 ) {
-             this.$message.errorMsg("课程已经关闭", 2)
-             return;
-          }
-          this.$emit("startExam",  0)
+    props: ['tpl', 'info', 'extraXs'],
+    mounted(){
+      this.recalcXs()
+    },
+    watch:{
+      extraXs:{
+        handler(){
+          this.recalcXs()
         },
-        printCert(){
-            this.$emit("printCert")
-         },
-         formatFinish() {
-        let {gxs,axs} = this.info;
-        if( !axs ) axs = 1;
-        return `获得${gxs}学时, 总共${axs}学时`;
+        deep:true
+      }
+    },
+    methods: {
+      recalcXs(){
+        this.finishCount = (this.extraXs.index1) + (+this.extraXs.index2) + (+this.extraXs.index3) + (+this.extraXs.index4)||0
+      },
+      startExam() {
+        if (this.tpl.isClosed == 1) {
+          this.$message.errorMsg("课程已经关闭", 2)
+          return;
+        }
+        this.$emit("startExam", 0)
+      },
+      startExamTest() {
+        this.recalcXs()
+        this.$emit("startExamTest")
+      },
+      printCert() {
+        this.recalcXs()
+        this.$emit("printCert")
+      },
+      formatFinish() {
+        let {
+          gxs,
+          axs
+        } = this.info;
+        let {testXs}= this.tpl
+        let attrch = this.finishCount>=100?testXs/10:0;
+        if (!axs) axs = 1;
+        return `获得${gxs+attrch}学时, 总共${axs+testXs/10}学时`;
       },
       formatExam() {
-        let {score} = this.info
-        if( score == -1 ) return `已答0次,未通过`;
+        let {
+          score
+        } = this.info
+        if (score == -1) return `已答0次,未通过`;
         return `最高${score<1?0:score}分, ${score>=60?'通过':'未通过'}`;
       },
-      formatString(val){
-        return ()=> val;
+      formatTest() {
+        let finishCount = this.finishCount
+        return `练习${finishCount}题, ${finishCount>=100?'完成':'未完成'}`;
+      },
+      formatString(val) {
+        return () => val;
       }
-     }
+    }
   }
-  </script>
+</script>

+ 6 - 9
src/containers/center/play/components/iCourseInfoTest.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-row class="p20">
+  <el-row class="p20 waphide">
     <el-col :span="6">
       <img :src="tpl.tb" width="200px" style="border-radius: 20px;" />
     </el-col>
@@ -34,7 +34,7 @@
         <p class="mt10">
           学习截至时间:
           <strong style="color: red;">
-          {{info.startDate|add80Date}}
+          {{info.canStudyDate}}
           </strong>
         </p>
         <p class="mt10">
@@ -64,12 +64,11 @@
     <el-col :span="9">
       <div>
         <p style="font-size: 24px;"> 考试情况 </p>
-
-        <section v-for="(item,index) in setting.examList">
+        <section v-for="(item,index) in examList">
           <span style="width:240px;margin: 0px;padding: 0px;">
-            {{item}}: <strong style="color: red;">{{info['score'+index]}} </strong>分
+            {{item.name}}: <strong style="color: red;">{{info['score'+(index+1)]}} </strong>分
           </span>
-          <el-button v-if="info['score'+index]<60" @click="startExam(index)" type="text">开始考试</el-button>
+          <el-button v-if="info['score'+(index+1)]<60" @click="startExam(item.id)" type="text">开始考试</el-button>
           <el-button v-else type="text" style="color: #006600;">考试通过</el-button>
         </section>
       </div>
@@ -87,13 +86,11 @@
 </template>
 
 <script>
-  import setting from '@/setting';
   import { getTime,packTime,toDate } from "@/utils/date";
   export default {
     name: "iCourseInfoTest",
     data() {
       return {
-        setting,
         timer: 0,
         end: {
           d: 0,
@@ -103,7 +100,7 @@
         }
       }
     },
-    props: ['tpl', 'info'],
+    props: ['tpl', 'info', "examList"],
     destroyed(){
       this.timer &&  window.clearInterval(this.timer)
     },

+ 154 - 0
src/containers/center/play/components/iCourseInfoTranExam.vue

@@ -0,0 +1,154 @@
+<template>
+  <el-row class="p20 waphide">
+    <el-col :span="6">
+      <img :src="tpl.tb" width="200px" style="border-radius: 20px;" />
+    </el-col>
+    <el-col :span="6">
+      <div>
+        <p style="font-size: 24px;"> 课程信息 </p>
+        <p class="mt10">岗位名称:{{tpl.name}} </p>
+        <p class="mt10">课程学时:<strong style="color: red;">{{tpl.xs/10}} </strong>学时
+        </p>
+        <p class="mt10" v-if="tpl.examGroupId">课程年度:
+          <strong style="color: red;">{{tpl.nd}} </strong>
+        </p>
+        <p class="mt10">学习情况:
+          完成 <strong style="color: red;">{{info.getXs/10}} </strong>
+          共计 <strong style="color: red;">{{info.totalXs/10}}</strong>
+        </p>
+        <p class="mt10" v-if="tpl.examGroupId">最高分数:
+          <strong style="color: red;">{{info.score}} </strong>
+          分
+        </p>
+        <p class="mt10" v-if="tpl.examGroupId">开始时间:
+          <strong style="color: red;">{{info.startDate}} </strong>
+        </p>
+        <p class="mt10" >考试备注:<br>
+          <span style="padding:0 2rem;color: red;">
+            考试开始,三天内必须完成
+          </span>
+          <span style="padding:0 2rem;color: red;">
+            报名成功,80天内完成学习
+          </span>
+        </p>
+        <p class="mt10">
+          学习截至时间:
+          <strong style="color: red;">
+          {{info.canStudyDate}}
+          </strong>
+        </p>
+        <p class="mt10">
+          剩余考试时间:
+          <span  v-if="info.examTime>0">
+            <span v-if="end.d>0">
+              <span style="color: red;">{{end.d}}</span>
+              天
+            </span>
+            <span style="color: red;">{{end.h}}</span>
+            时
+            <span style="color: red;">{{end.m}}</span>
+            分
+            <span v-if="end.d==0">
+              <span style="color: red;">{{end.s}}</span>
+              秒
+            </span>
+          </span>
+          <span v-else>
+            还未开考
+          </span>
+        </p>
+
+      </div>
+    </el-col>
+
+    <el-col :span="9">
+      <div>
+        <p style="font-size: 24px;"> 考试情况 </p>
+
+        <section v-for="(item,index) in setting.examList">
+          <span style="width:240px;margin: 0px;padding: 0px;">
+            {{item}}: <strong style="color: red;">{{info['score'+index]}} </strong>分
+          </span>
+          <el-button v-if="info['score'+index]<60" @click="startExam(index)" type="text">开始考试</el-button>
+          <el-button v-else type="text" style="color: #006600;">考试通过</el-button>
+        </section>
+      </div>
+    </el-col>
+
+    <el-col :span="3">
+      <div>
+        <el-button type="primary" style="font-size: 14px;margin-top: ;" @click="printCert" v-if="tpl.tplId>0">
+          学时证明
+        </el-button>
+      </div>
+    </el-col>
+
+  </el-row>
+</template>
+
+<script>
+  import setting from '@/setting';
+  import { getTime,packTime,toDate } from "@/utils/date";
+  export default {
+    name: "iCourseInfoTest",
+    data() {
+      return {
+        setting,
+        timer: 0,
+        end: {
+          d: 0,
+          h: 0,
+          m: 0,
+          s: 0
+        }
+      }
+    },
+    props: ['tpl', 'info'],
+    destroyed(){
+      this.timer &&  window.clearInterval(this.timer)
+    },
+    created(){
+      this.initTimer()
+    },
+    filters:{
+      add80Date( date ){
+        let val = new Date(date).getTime() + 80*86400*1000;
+        return toDate( val )
+
+      }
+    },
+    methods: {
+      initTimer() {
+        if (this.timer) {
+          window.clearInterval(this.timer);
+        }
+        this.tickTimer();
+        this.timer = window.setInterval(() => {
+          this.tickTimer();
+        }, 1000);
+      },
+      tickTimer(  ) {
+        let nowSec = getTime();
+        let timeLast =  this.info.examTime -nowSec;
+        this.end = packTime(timeLast);
+        if (timeLast < 0) {
+          window.clearInterval(this.timer);
+        }
+      },
+      startExam(index) {
+        if( this.end.v <=0 && this.info.examTime>0 ) {
+          this.$message.errorMsg("开考超过三天,不能继续考试")
+          return;
+        }
+        if( this.tpl.isClosed ==1 ) {
+          this.$message.errorMsg("课程已经关闭", 2)
+          return;
+        }
+        this.$emit("startExam", +index)
+      },
+      printCert() {
+        this.$emit("printCert")
+      }
+    }
+  }
+</script>

+ 1 - 3
src/containers/center/play/components/iCourseInfoXsExtra.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-row class="p20">
+  <el-row class="p20 waphide">
     <el-col :span="6">
       <img :src="tpl.tb" width="200px" style="border-radius: 20px;" />
     </el-col>
@@ -81,14 +81,12 @@
 </template>
 
 <script>
-  import setting from '@/setting';
   import { getTime,packTime,toDate } from "@/utils/date";
   import { mapGetters, mapActions } from "vuex";
   export default {
     name: "iCourseInfoTest",
     data() {
       return {
-        setting,
         timer: 0,
         end: {
           d: 0,

+ 280 - 130
src/containers/center/play/components/media.vue

@@ -1,35 +1,39 @@
 <template>
   <div>
     <!--  -->
-    <video-player
-      class="video-player-box"
-      ref="videoPlayer"
-      :playsinline="true"
-      @pause="onPlayerPause($event)"
-      @timeupdate="onPlayerTimeupdate($event)"
-      @play="onPlayerStart($event)"
-      @ready="playerReadied"
-      @ended="onPlayerEnded($event)"
-      :globalOptions="{controls:true}"
-      :options="options">
+
+    <div class="v-wrap-marks"></div>
+    <video-player id="myVideo" class="video-player-box" ref="videoPlayer" :playsinline="true"
+      @pause="onPlayerPause($event)" @timeupdate="onPlayerTimeupdate($event)" @play="onPlayerStart($event)"
+      @ready="playerReadied" @ended="onPlayerEnded($event)" @error="onPlayerError($event)"
+      :globalOptions="{controls:true}" :options="options">
     </video-player>
     <div class="dialog-footer pt30">
       <el-row class="media-footer">
-        <el-col :span="10" class="media-time">
+        <el-col :span="8" class="media-time">
           <span>{{curTimes|useTime}}</span>
           <strong>/</strong>
           <span>{{media.duration|useTime}}</span>
         </el-col>
         <el-col :span="4" class="media-center">
-          <el-button class="bicon" v-if="!onPlay" type="primary" @click="doPlay" icon="el-icon-video-play" circle></el-button>
-          <el-button class="bicon" v-else  type="warning" @click="doPause" icon="el-icon-video-pause" circle></el-button>
+          <el-button class="bicon" v-if="!onPlay" type="primary" @click="doPlay" icon="el-icon-video-play"
+            circle></el-button>
+          <el-button class="bicon" v-else type="warning" @click="doPause" icon="el-icon-video-pause" circle></el-button>
+        </el-col>
+        <el-col :span="6" class="media-center">
+          <span class="volume-control">
+            <span class="vol-icon" @click="toggleMute">音量</span>
+            <el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
+              @input="setVolume"></el-slider>
+          </span>
         </el-col>
-        <el-col :span="10" class="media-select">
-          <el-button class="bicon"  type="danger" icon="el-icon-close" @click="onClose" style="float: right;" >
+        <el-col :span="6" class="media-select">
+          <el-button class="bicon" type="danger" icon="el-icon-close" @click="onClose" style="float: right;">
           </el-button>
           <div style="margin-top:0px;float: right;">
-            <el-select  placeholder="流畅" v-model="selectMediaType" class="media-el-select" @change="(val)=>{$emit('changeMedia', val)}">
-              <el-option label="流畅" value="ld" ></el-option>
+            <el-select placeholder="流畅" :value="mediaType" class="media-el-select"
+              @change="(val)=>{$emit('changeMedia', val)}">
+              <el-option label="流畅" value="ld"></el-option>
               <el-option label="标清" value="hls"></el-option>
             </el-select>
           </div>
@@ -41,51 +45,60 @@
 </template>
 
 <script>
-  import {httpServer} from "@/components/httpServer/httpServer.js";
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
   import md5 from 'js-md5';
-  import {videoPlayer } from 'vue-video-player';
+  import {
+    videoPlayer
+  } from 'vue-video-player';
   import 'video.js/dist/video-js.css'
-  import { MessageBox } from "element-ui";
+  import {
+    MessageBox
+  } from "element-ui";
   export default {
     name: "Index",
     data() {
       return {
         timer: false,
         tickNum: 0,
-        prevTime:0,
-        isReady:false,
-        selectMediaType:'hls',
-        curTimes:0,
-        onPlay: false
+        prevTime: 0,
+        isReady: false,
+        curTimes: 0,
+        onPlay: false,
+        volume: 0.5,
+        isMuted: false
       }
     },
     components: {
       videoPlayer
     },
-    watch:{
-      mediaType( ){
-        this.selectMediaType = this.mediaType;
-        if( !this.mediaType ) return;
+    watch: {
+      mediaType() {
+        if (!this.mediaType) return;
       },
-      dialog( showDilog ){
-        if( !showDilog){
+      dialog(showDilog) {
+        if (!showDilog) {
           this.doPause()
           this.isReady = false
         }
       }
     },
     props: {
-      mediaType:{
+      mediaType: {
         type: String,
         default: ''
       },
-      media:{
+      media: {
         type: Object,
-        default: ()=>{
-          return {id: '', percent:0}
+        default: () => {
+          return {
+            id: '',
+            percent: 0
+          }
         }
       },
-      dialog:{
+      dialog: {
         type: Boolean,
         default: true
       },
@@ -96,21 +109,16 @@
         }
       }
     },
-    mounted() {
-      setTimeout(() => {
-        this.$emit("updateOption", {muted:false})
-      }, 3000)
-    },
-    filters:{
-      useTime(val){
+    filters: {
+      useTime(val) {
         let timestr = ""
-        let hour = parseInt(val/3600);
-        let min = parseInt(val/60 % 60);
-        let sec = parseInt(val%60);
-        if( hour <10) hour = "0"+hour;
-        if( min <10) min = "0"+min;
-        if( sec <10) sec = "0"+sec;
-        return hour+":"+min+":"+sec
+        let hour = parseInt(val / 3600);
+        let min = parseInt(val / 60 % 60);
+        let sec = parseInt(val % 60);
+        if (hour < 10) hour = "0" + hour;
+        if (min < 10) min = "0" + min;
+        if (sec < 10) sec = "0" + sec;
+        return hour + ":" + min + ":" + sec
       }
     },
     beforeDestroy() {
@@ -120,86 +128,129 @@
     computed: {
       player() {
         return this.$refs.videoPlayer.player
-      }
+      },
     },
     created() {
-      this.startTick()
-      // this.startMonitor()
+      this.startMonitor()
     },
     methods: {
-      startTick(){
+      setMarks() {
+        console.log("setMarks")
+        if (!this.options.marks) return;
+        var div = document.getElementById('myVideo')
+        var div1 = div.firstChild
+        var div3 = document.createElement("div");
+        div3.setAttribute("class", "resize-drag");
+        if (this.mediaType == 'ld') {
+          div3.style.cssText =
+            "position:absolute;top:52px;left:10px; background-color: #f4f4f4;width:50px; height:50px;border-radius: 50%;";
+        } else {
+          div3.style.cssText =
+            "position:absolute;top:80px;left:18px; background-color: #f4f4f4;width:50px; height:50px;border-radius: 50%;";
+        }
+        div1.appendChild(div3)
+      },
+      startTick() {
         let tick = this.tryTick;
-        if( this.timer ) clearInterval(this.timer);
+        if (this.timer) clearInterval(this.timer);
         this.timer = setInterval(tick, 5 * 1000);
       },
-      stopTick(){
-        if( this.timer ) clearInterval(this.timer);
+      stopTick() {
+        if (this.timer) clearInterval(this.timer);
       },
-      tryTick(){
+      tryTick() {
         let that = this;
         try {
           that.tick()
-        }catch(err){
-          that.reportErr("play", ''+err.message )
+        } catch (err) {
+          that.reportErr("play", '' + err.message)
         }
       },
-      playerReadied(audio) {
-        console.log("playerReadied", audio)
+      playerReadied(player) {
         let that = this;
-        if( this.media.position){
-          this.setposition( this.media.position );
-        }
         this.isReady = true
+        setTimeout(() => this.setMarks(), 600);
+
+        if (this.media.position > 5 && this.media.position < this.media.duration) {
+          setTimeout(function() {
+            that.setposition(that.media.position)
+          }, 300)
+        }
+
+        setTimeout(() => {
+          if (that.player && that.player.volume) {
+            that.player.volume(that.volume)
+            that.player.muted(that.isMuted)
+          }
+        }, 1000)
       },
-      onPlayerTimeupdate( player ){
+      onPlayerTimeupdate(player) {
         let myPlayer = this.$refs.videoPlayer.player;
         let curTimes = player.cache_.currentTime;
-        this.curTimes = curTimes||0
-        if( this.media.isFinish ){
+        this.curTimes = curTimes || 0
+        if (this.media.isFinish) {
           return;
         }
       },
-      setposition( position ) {
-        if( position > this.media.duration) position = this.media.duration;
+      setposition(position) {
+        console.log("setposition", position)
+        if (position > this.media.duration) position = this.media.duration;
         let player = this.$refs.videoPlayer.player;
-        player.currentTime( position );
-        if( this.media.isFinish) return;
-        if( this.media.position >= this.media.duration-10 && !this.media.isFinish){
-          this.tick( true )
+        player.currentTime(position);
+        if (this.media.isFinish) return;
+        if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
+          this.tick(true)
         }
         // this.onPlay = true
       },
       onPlayerPause(event) {
-        this.reportErr("play", 'pause' );
+        this.reportErr("play", 'pause');
         this.stopTick()
         this.onPlay = false
       },
-      onPlayerEnded( event ){
-        this.reportErr("play", 'end' );
-        this.tick( true )
+      onPlayerError(event) {
+        this.reportErr("play", 'error');
       },
-      onClose(){
+      onPlayerEnded(event) {
+        this.reportErr("play", 'end');
+        this.tick(true)
+      },
+      onClose() {
         this.reportErr("play", 'close')
         this.doPause()
         this.$emit("close")
       },
-      doPause( ){
+      doPause() {
         // console.log("doPause")
         this.stopTick()
         this.onPlay = false
         let myPlayer = this.$refs.videoPlayer.player;
         myPlayer && myPlayer.pause()
       },
-      doPlay(){
-        // console.log("doPlay")
+      doPlay() {
         this.onPlay = true
         this.startTick();
-        if( !this.$refs.videoPlayer || !this.$refs.videoPlayer.player ) return;
-        if( !this.dialog ) return this.doPause();
+        if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
+        if (!this.dialog) return this.doPause();
         let myPlayer = this.$refs.videoPlayer.player;
-        myPlayer && myPlayer.play()
+        let p = myPlayer && myPlayer.play()
+        if (p && p.catch) p.catch(function() {})
         this.tickNum = 0
       },
+      setVolume(val) {
+        this.volume = val
+        this.isMuted = val === 0
+        if (this.player && this.player.volume) {
+          this.player.volume(val)
+          this.player.muted(this.isMuted)
+        }
+      },
+      toggleMute() {
+        this.isMuted = !this.isMuted
+        if (this.player && this.player.volume) {
+          this.player.muted(this.isMuted)
+        }
+      },
       onPlayerStart() {
         // console.log("onPlayerStart")
         this.reportErr("play", 'start');
@@ -210,12 +261,40 @@
         let that = this
         document.addEventListener("visibilitychange", function() {
           // || document.hidden
-          if( document.visibilityState == "hidden"){
-            that.doPause( )
-          }else{
-            that.doPlay()
+          if (document.visibilityState == "hidden") {
+            // that.doPause( )
+            that.reportErr("play", 'hidden');
+          } else {
+            that.reportErr("play", 'show');
+            // that.doPlay()
+          }
+        });
+        // 监听F12
+        document.addEventListener("keydown", function(e) {
+          if (e.key == "F12") {
+            that.reportErr("play", 'keydownF12');
           }
         });
+
+        // setInterval(function() { check() }, 1000);
+
+        // var check = function() {
+        //   function doCheck(a) {
+        //     if (("" + a / a)["length"] !== 1 || a % 20 === 0) {
+        //       (function() {}
+        //         ["constructor"]("debugger")())
+        //     } else {
+        //       (function() {}
+        //         ["constructor"]("debugger")())
+        //     }
+        //     doCheck(++a)
+        //   }
+        //   try {
+        //     doCheck(0)
+        //   } catch (err) {}
+        // };
+
+
       },
       // startMonitor() {
       //   let that = this
@@ -226,7 +305,7 @@
       //     that.doPlay()
       //   }
       // },
-      tickWait(){
+      tickWait() {
         this.doPause()
         let that = this
         MessageBox({
@@ -239,57 +318,79 @@
             if (action === "confirm") {
               that.doPlay();
               done();
-            } else{
+            } else {
               done();
             }
           }
         })
       },
-      reportErr( action, msg ){
-        httpServer("course.report", {action, msg})
+      reportErr(action, msg) {
+        httpServer("course.report", {
+          action,
+          msg
+        })
       },
-      tick( force = false) {
+      tick(force = false) {
         let media = this.media;
-        this.tickNum ++
+        this.tickNum++
         // 已经完成
-        if( this.media.isFinish ) {
+        if (this.media.isFinish) {
           console.log("finish")
           return;
         }
         // 主动暂停
         let myPlayer = this.$refs.videoPlayer.player;
+
         let curTimes = parseInt(myPlayer.currentTime());
-        // 后退无心跳
-        if( this.media.position > curTimes && !force ) return;
-        if( curTimes< 4) {
+        if (curTimes < 4) {
           console.log("curTimes")
           return;
         }
 
-        let isFinish = force?1:0
-        if( curTimes >= media.duration ) isFinish = 1;
-         //  拉到后面
-        if( !isFinish ){
-          if ( !this.onPlay ) return;
+        let isFinish = force ? 1 : 0
+        if (curTimes >= media.duration-15) isFinish = 1;
+        //  拉到后面
+        if (!isFinish) {
+          if (!this.onPlay) return;
+          if(curTimes <= media.position) return;
+        }
+        if(window.navigator.webdriver){
+          this.reportErr("play", 'webdriver');
+          this.doPause()
+          return
         }
         // 强制完成
-        let param ={id: media.id, position:curTimes, isFinish};
-        httpServer("course.tick", param, true).then( res => {
+        let param = {
+          id: media.id,
+          position: curTimes,
+          isFinish
+        };
+        httpServer("course.tick", param, true).then(res => {
           if (res.code == 200) {
-            let {skip, position, pause, closed} = res.data
-            if( pause || closed ) {
+            let {
+              skip,
+              position,
+              pause,
+              closed
+            } = res.data
+            if (pause || closed) {
               this.doPause();
               this.$emit("close")
-              if( closed ) {
-                this.$message.errorMsg("课程关闭学习", 5);
-              }else if( pause ){
-                this.$message.errorMsg("多处同时播放视频", 5);
+              if (closed) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
+              } else if (pause) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
               }
               return
-             }
-            if( !skip ) this.setposition( position );
-            Object.assign(param, res.data )
-            this.$emit("update", param )
+            }
+            if (!skip) {
+              let diff = Math.abs(position - curTimes);
+              if (diff > 3) {
+                this.setposition(position);
+              }
+            }
+            Object.assign(param, res.data)
+            this.$emit("update", param)
           }
         })
       }
@@ -298,13 +399,15 @@
 </script>
 
 <style>
-  .video-js{
-    .vjs-control-bar{
+  .video-js {
+    touch-action: none;
+    .vjs-control-bar {
       .vjs-icon-custombutton {
         font-family: VideoJS;
         font-weight: normal;
         font-style: normal;
-       }
+      }
+
       .vjs-icon-custombutton:before {
         content: "\f108";
         font-size: 1.8em;
@@ -312,39 +415,86 @@
       }
     }
   }
-  .p-process{
+
+  .p-process {
     width: 100%;
-    margin:20px auto;
+    margin: 20px auto;
     height: 30px;
   }
-  .media-footer{
+
+  .media-footer {
     padding: 0px 30px;
     text-align: left;
     line-height: 40px !important;
     bottom: -10px;
   }
-  .media-center{
+
+  .media-center {
     text-align: center;
     padding: 0px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+  }
+
+  .volume-control {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
   }
-  .media-time{
+
+  .vol-icon {
+    font-size: 12px;
+    font-weight: bold;
+    cursor: pointer;
+    color: #606266;
+    user-select: none;
+    line-height: 1;
+  }
+
+  .vol-icon:hover {
+    color: #409eff;
+  }
+
+  .volume-slider {
+    width: 80px !important;
+  }
+
+  .volume-slider .el-slider__runway {
+    height: 4px;
+  }
+
+  .volume-slider .el-slider__bar {
+    height: 4px;
+  }
+
+  .volume-slider .el-slider__button {
+    width: 12px;
+    height: 12px;
+  }
+
+  .media-time {
     font-size: 18px;
     vertical-align: center;
   }
-  .media-select{
-    white-space:nowrap;
+
+  .media-select {
+    white-space: nowrap;
     text-align: right;
     line-height: 40px !important;
     float: right;
     margin: 0px !important;
   }
-  .bicon{
+
+  .bicon {
     font-size: 28px !important;
-    padding: 4px  !important;
+    padding: 4px !important;
   }
+
   .media-el-select {
     font-size: 28px !important;
     width: 80px;
-    padding: -4px auto  !important;
+    padding: -4px auto !important;
   }
 </style>

+ 103 - 119
src/containers/center/play/index.vue

@@ -1,22 +1,30 @@
 <template>
-  <div class="m-right-block fr mh576">
-    <ICourseInfoTest  v-if="tpl.type=='检测试验人员'" :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfoTest>
-    <ICourseInfoXsExtra v-else-if="tpl.testXs>0"
+  <div class="m-right-block mh576">
+
+
+    <ICourseInfoXsExtra v-if="tpl.isTranExam==2"
     :extraXs="extraXs"  :info="info"
     @updateInfo="updateInfo"
     :tpl="tpl" @startExamTest="startExamTest" @startExam="startExam"></ICourseInfoXsExtra>
 
-    <ICourseInfo v-else :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfo>
+    <ICourseInfoTest  v-else-if="tpl.isTranExam>0" :examList="examList" :info="info" :tpl="tpl" @printCert="printCert" @startExam="startExam"></ICourseInfoTest>
+
+
+    <ICourseInfo v-else
+    :info="info"
+     :tpl="tpl"
+     :extraXs="extraXs"
+     @printCert="printCert" @startExam="startExam" @startExamTest="startExamTest"></ICourseInfo>
 
     <div class="right-block-bd ng-scope" ui-view="myStudyContent" style="position: relative;">
       <div class="m-account">
         <div class="account-tit" style="height:30px">
           <a :class="{'current':show===1}"  @click="show=1" class="">正在学习</a>
           <a :class="{'current':show===2}"  @click="show=2" class="">已经完成</a>
-          <a :class="{'current':show===3}"  @click="show=3" class="" v-if="tpl.examGroupId>0||tpl.type=='检测试验人员'">考试记录</a>
+          <a :class="{'current':show===3}"  @click="show=3" class="" v-if="tpl.examGroupId>0">考试记录 </a>
         </div>
 
-        <div v-if="show <3" class="lwh-ul-form mt20">
+        <div v-if="show<3" class="lwh-ul-form mt20">
           <!-- 列表展示 -->
           <el-table :data="showList" border stripe style="width: 100%;"  >
             <el-table-column label="NO." prop="id" width="50" align="center">
@@ -40,7 +48,7 @@
 
             <el-table-column label="学习进度" prop="useTime" align="left" width="220">
               <template v-slot="{row}">
-                <span style="display: flex;">
+                <span style="display: flex;" v-if="show==1">
                   <el-progress
                     type="line"
                     style="width: 120px;"
@@ -52,10 +60,13 @@
                   </el-progress>
                   <span style="margin-left: 10px;"> {{row.percent}}% </span>
                 </span>
+                <span v-else>
+                  <span style="margin-left: 10px;color: green;">视频学习完成</span>
+                </span>
               </template>
             </el-table-column>
 
-            <el-table-column label="操作" prop="xs" align="center" width="80">
+            <el-table-column label="操作" fixed="right" prop="xs" align="center" width="80">
               <template v-slot="{row, $index}">
                 <el-button @click="loadMedia(row, $index)" type="text">播放</el-button>
               </template>
@@ -64,37 +75,12 @@
           </el-table>
         </div>
 
-        <div v-if="show ==3" class="lwh-ul-form mt20">
+        <div v-if="show===3" class="lwh-ul-form mt20">
           <exam-list :courseId="courseId"> </exam-list>
         </div>
-
-        <el-dialog class="previewDialog" :visible.sync="testExamDalog"
-          top="50px" width="1024px"
-          @close="testExamDalog=false">
-          <ICourseAnswerTest :info="info" :extraXs="extraXs" @updateExtraXs="updateExtraXs"> </ICourseAnswerTest>
-        </el-dialog>
       </div>
      </div>
-    <el-dialog
-     class="media-dialog"
-     :close-on-click-modal="false"
-     :visible.sync="mediaDialog"
-     top="50px"
-     :title="media.name"
-     :width="mediaType=='hls'?'720px':'540px'"
-     >
-        <Media
-          :options="options"
-          :dialog="mediaDialog"
-          :media="media"
-          :duration="info.duration"
-          @close="closeMedia"
-          :mediaType="mediaType"
-          @changeMedia="changeMedia"
-          @updateOption = "updateOption"
-          @update="update">
-        </Media>
-    </el-dialog>
+
 
     <el-dialog title="输入认证" center :visible.sync="editRzcode"  >
       <el-form  label-width="120px" ref="elAdd">
@@ -137,6 +123,12 @@
         tpl:{},
         media:{},
         extraXs:{},
+        counter:{
+          1:150,
+          2:156,
+          3:149,
+          4:45,
+        },
         options:{
           controls:true,
           autoplay: true, // 如果true,浏览器准备好时开始回放。
@@ -154,43 +146,36 @@
             timeDivider: true,
             durationDisplay: true,
             remainingTimeDisplay: false,
-            fullscreenToggle: true  //全屏按钮
-            // currentTimeDisplay: true, // 当前时间
-            // timeDivider: true, // 时间分割线
-            // durationDisplay: true, // 总时间
-            // progressControl: true, // 进度条
-            // remainingTimeDisplay: true, //
-            // customControlSpacer: true, //
-            // fullscreenToggle: true, // 全屏按钮
-            // volumePanel: true
+            fullscreenToggle: true,
+            volumePanel: true
           }
         },
         list: [],
+        examList:[],
         showList:[],
       };
     },
+    watch:{
+      show(val){
+        console.log("show", val)
+        this.updateShowList()
+      }
+    },
     components:{Media,ExamList,ICourseInfo,ICourseInfoTest,ICourseAnswerTest,ICourseInfoXsExtra},
     beforeMount() {
+      console.log('beforeMount')
       this.courseId = +this.$route.params.courseId
       this.getData()
-    },
-    watch:{
-      show(val){
-        this.updateShowList()
+      this._onVisible = () => {
+        if (document.visibilityState === 'visible') {
+          console.log('visible')
+          this.getData()
+        }
       }
-      // mediaType(val){
-      //   if( !val ) return;
-      //   let mediaUrl = this.mediaUrl;
-      //   if( val == 'ld'){
-      //     mediaUrl = mediaUrl.replace('/hls/', '/ld/');
-      //   }else{
-      //     mediaUrl = mediaUrl.replace('/ld/', '/hls/');
-      //   }
-      //   this.options.sources = [{src:mediaUrl,type: "application/x-mpegURL"}];
-      //   this.options.playtimes = this.media.position||0;
-      //   this.options.autoplay = this.options.playtimes>0;
-      //   this.mediaDialog = true;
-      // }
+      document.addEventListener('visibilitychange', this._onVisible)
+    },
+    beforeDestroy() {
+      document.removeEventListener('visibilitychange', this._onVisible)
     },
     filters:{
       showProgressColor: function (val) {
@@ -226,7 +211,16 @@
           this.$message.errorMsg(" 完成学习才能专项练习", 2)
           return;
         }
+        let groupId = this.tpl.testGroupId
         this.testExamDalog = true;
+        if( groupId == 0 || groupId == 10) return;
+        httpServer('course.GetCourseExamInfo', {examId:groupId}).then( res=>{
+            if( res.code != 200) return;
+            this.counter[1] = res.data.judge||0;
+            this.counter[2] = res.data.single||0;
+            this.counter[3] = res.data.multi||0;
+            this.counter[4] = res.data.anli||0;
+        })
       },
       isStudyFinish(){
         let {getXs,totalXs} = this.info
@@ -299,10 +293,17 @@
         let param = { courseId: this.courseId }
         httpServer("course.getCourse", param).then(res => {
           if (res.code == 200) {
-            let {info, extra, list, tpl, extraXs} = res.data;
+            let {info, extra, list, tpl, extraXs, examList} = res.data;
             this.info =  Object.assign( info, extra||{});
+            this.info.score1 = +this.info.score1||0
+            this.info.score2 = +this.info.score2||0
+            this.info.score3 = +this.info.score3||0
+            this.info.score4 = +this.info.score4||0
+            this.info.score5 = +this.info.score5||0
+            this.info.score6 = +this.info.score6||0
             this.tpl = tpl||{};
             this.extraXs = extraXs||{};
+            this.examList = examList||[];
             this.list = list.map( (item)=>{
               item.percent = getPercent(item)||0;
               return item;
@@ -342,40 +343,12 @@
       updateExtraXs( param ){
         this.extraXs = Object.assign( this.extraXs, param);
       },
-      //
-      update( item ){
-        if( item.position> this.media.position) {
-          this.media.position = item.position
-        }
-        this.media.isFinish = item.isFinish
-        this.media.id = item.id
-        this.media.percent = getPercent(this.media);
-        this.updateShowList()
-      },
-      closeMedia(){
-        this.mediaType = '';
-        this.mediaDialog=false;
-      },
       // 加载媒体
       loadMedia( item, index ) {
-        this.media = item;
-        this.media.index = index
-        httpServer('course.GetMedia', {id:item.id}).then( res => {
-          if( res.code != 200) return;
-          let {mediaUrl, id, position} = res.data||{};
-          this.mediaUrl = res.data.mediaUrl;
-          if(  this.mediaUrl.indexOf('/hls/') == -1){
-            this.mediaType = 'ld'
-          }else{
-            this.mediaType = 'hls'
-          }
-          this.media.position = position;
-          this.media.id = id;
-          this.options.sources = [{src:this.mediaUrl,type: "application/x-mpegURL"}];
-          this.options.playtimes = position||1;
-          this.options.autoplay  = position>0;
-          this.mediaDialog = true;
-        });
+          let courseId = this.courseId
+          let mediaId = item.mediaId;
+          const {href} = this.$router.resolve({name:'playMedia', params:{courseId}, query:{mediaId}})
+          window.open(href, '_blank');
       }
     }
   };
@@ -399,31 +372,42 @@
      margin: 0 auto;
      width: 150px !important;
    }
-   .el-progress{
-     width: 100%;
-   }
-   .el-progress__text{
-     font-size: 16px;
-     font-weight: 700;
-     line-height:1.5;
-     color: #3290D4;
-   }
-   .el-progress-bar__outer{
-     height: 20px!important;
-     border: 1px solid #78335f;
-     background-color:transparent;
-   }
 
-   /* 渐变进度条 */
-   .el-bg-inner-running .el-progress-bar__inner{
-     background-color: unset;
-     background-image: linear-gradient(to right, #3587d8 , #6855ff);
-   }
-   .el-bg-inner-error .el-progress-bar__inner{
-     background-image: linear-gradient(to right, #3587d8 , #fb3a7e);
-   }
-   .el-bg-inner-done .el-progress-bar__inner{
-     background-image: linear-gradient(to right, #3587d8 , #53ff54);
-   }
+</style>
 
+<style scoped>
+  ::v-deep .media-dialog .el-dialog__body {
+    padding: 0 0 16px 0 !important;
+  }
+  ::v-deep .media-dialog .el-scrollbar__wrap {
+    overflow-x: hidden !important;
+  }
+  ::v-deep .mprocess {
+    margin: 0 auto;
+    width: 150px !important;
+  }
+  ::v-deep .el-progress {
+    width: 100%;
+  }
+  ::v-deep .el-progress__text {
+    font-size: 16px;
+    font-weight: 700;
+    line-height: 1.5;
+    color: #3290D4;
+  }
+  ::v-deep .el-progress-bar__outer {
+    height: 20px !important;
+    border: 1px solid #78335f;
+    background-color: transparent;
+  }
+  ::v-deep .el-bg-inner-running .el-progress-bar__inner {
+    background-color: unset;
+    background-image: linear-gradient(to right, #3587d8, #6855ff);
+  }
+  ::v-deep .el-bg-inner-error .el-progress-bar__inner {
+    background-image: linear-gradient(to right, #3587d8, #fb3a7e);
+  }
+  ::v-deep .el-bg-inner-done .el-progress-bar__inner {
+    background-image: linear-gradient(to right, #3587d8, #53ff54);
+  }
 </style>

+ 9 - 0
src/containers/center/play/play.css

@@ -0,0 +1,9 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+}

+ 965 - 0
src/containers/center/play/play.vue

@@ -0,0 +1,965 @@
+<template>
+  <div class="pc-player-container">
+    <div class="pc-player-wrapper">
+      <div class="pc-player-left">
+        <div class="video-section">
+          <div class="video-title">{{ media.name || tpl.name }}</div>
+          <div class="video-player-wrap">
+            <video-player id="myVideo1" ref="videoPlayer" :playsinline="true" @pause="onPlayerPause($event)"
+              @timeupdate="onPlayerTimeupdate($event)" @play="onPlayerStart($event)" @ready="playerReadied"
+              @ended="onPlayerEnded($event)" @error="onPlayerError($event)"
+              :globalOptions="{controls:true}" :options="options">
+            </video-player>
+          </div>
+          <div class="dialog-footer pt30">
+            <el-row class="media-footer">
+              <el-col :span="6" class="media-time">
+                <span>{{curTimes|useTime}}</span>
+                <strong>/</strong>
+                <span>{{media.duration|useTime}}</span>
+              </el-col>
+              <el-col :span="12" class="media-center">
+                <span class="volume-control">
+                  <span class="vol-icon" @click="toggleMute">音量</span>
+                  <el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
+                    @input="setVolume"></el-slider>
+                </span>
+                <span class="pip-control">
+                  <span class="pip-icon" :class="{ 'is-active': isPiP }" @click="togglePiP">{{ isPiP ? '退出画中画' : '画中画' }}</span>
+                </span>
+              </el-col>
+              <el-col :span="6" class="media-select">
+                <div style="margin-top:0px;float: right;">
+                  <el-select placeholder="流畅" :value="mediaType" class="media-el-select"  @change="changeMedia">
+                    <el-option label="流畅" value="ld"></el-option>
+                    <el-option label="标清" value="hls"></el-option>
+                  </el-select>
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+        </div>
+      </div>
+
+      <div class="panel-toggle" @click="showPanel = !showPanel">
+        <i :class="showPanel ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'"></i>
+      </div>
+
+      <div class="pc-player-right" v-show="showPanel">
+        <div class="playlist-header">
+          <div class="playlist-title">
+            <i class="el-icon-video-list"></i>
+            课程列表
+          </div>
+          <div class="playlist-header-row">
+            <div class="playlist-count">
+              共 {{ list.length }} 个视频
+              <span class="finished-count" v-if="finishedCount">,已完成 {{ finishedCount }} 个</span>
+            </div>
+            <el-switch
+              v-model="hideFinished"
+              active-text="隐藏已完成"
+              inactive-text=""
+              size="small"
+            ></el-switch>
+          </div>
+        </div>
+        <div class="playlist-body">
+          <div class="playlist-item" v-for="(item, index) in filteredList" :key="item.id" :class="{
+              'is-active': media.id === item.id,
+              'is-finished': item.isFinish == 1,
+              'is-current-playing': media.id === item.id && onPlay
+            }" @click="loadMedia(item)">
+            <div class="item-index" :class="{ 'active-index': media.id === item.id }">
+              <span v-if="item.isFinish == 1" class="finish-badge">
+                <i class="el-icon-check"></i>
+              </span>
+              <span v-else>{{ index + 1 }}</span>
+            </div>
+            <div class="item-info">
+              <div class="item-name">{{ item.name }}</div>
+              <div class="item-meta">
+                <span class="item-duration">学时:{{ item.xs / 10 }}</span>
+                <span class="item-progress">{{item|percent}}%</span>
+              </div>
+            </div>
+            <div class="item-action">
+              <el-button v-if="media.id === item.id && onPlay" type="warning" size="mini" icon="el-icon-video-pause"
+                circle @click.stop="doPause"></el-button>
+              <el-button v-else-if="media.id === item.id" type="primary" size="mini" icon="el-icon-video-play" circle
+                @click.stop="doPlay"></el-button>
+              <el-button v-else type="text" size="mini" icon="el-icon-video-play"
+                @click.stop="loadMedia(item)">播放</el-button>
+            </div>
+          </div>
+          <div class="playlist-empty" v-if="list.length === 0">
+            <i class="el-icon-document"></i>
+            <p>暂无视频</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+  import { mapActions } from "vuex";
+  import {
+    getPercent
+  } from '@/utils/index.js'
+  import {
+    videoPlayer
+  } from 'vue-video-player';
+  import 'video.js/dist/video-js.css'
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
+  export default {
+    data() {
+      return {
+        courseId: 0,
+        mediaId: 0,
+        media: {},
+        options: {
+          controls: true,
+          autoplay: true,
+          muted: false,
+          loop: false,
+          preload: "auto",
+          language: 'zh-CN',
+          aspectRatio: '16:9',
+          fluid: true,
+          sources: [],
+          poster: '',
+          notSupportedMessage: '无法播放媒体源',
+          playtimes: '',
+          controlBar: {
+            timeDivider: true,
+            durationDisplay: true,
+            remainingTimeDisplay: false,
+            fullscreenToggle: true
+          }
+        },
+        curTimes: 0,
+        tpl: {},
+        info: {isBan:0},
+        list: [],
+        timer: false,
+        tickNum: 0,
+        prevTime: 0,
+        isReady: false,
+        onPlay: false,
+        volume: 0.5,
+        isMuted: false,
+        mediaType: 'hls',
+        hasSeeked: false,
+        hideFinished: false,
+        showPanel: true,
+        hiddenStartTime: 0,
+        hiddenBasePosition: 0,
+        isPiP: false
+      };
+    },
+    components: {
+      videoPlayer
+    },
+    computed: {
+      finishedCount() {
+        return this.list.filter(i => i.isFinish == 1).length;
+      },
+      filteredList() {
+        if (this.hideFinished) {
+          return this.list.filter(i => i.isFinish != 1)
+        }
+        return this.list
+      },
+      playPercent() {
+        if (!this.media.duration) return 0;
+        if(this.media.isFinish ==1) return 100;
+        return +((this.curTimes / this.media.duration) * 100).toFixed(1);
+      }
+    },
+    filters: {
+      percent(item){
+        if( item.isFinish == 1) return 100;
+        return (item.position * 100 / item.duration).toFixed(1)
+      },
+      useTime(val) {
+        val = parseInt(val) || 0
+        let timestr = ""
+        let hour = parseInt(val / 3600);
+        let min = parseInt(val / 60 % 60);
+        let sec = parseInt(val % 60);
+        if (hour < 10) hour = "0" + hour;
+        if (min < 10) min = "0" + min;
+        if (sec < 10) sec = "0" + sec;
+        return hour + ":" + min + ":" + sec
+      }
+    },
+    methods: {
+      getCourse() {
+        let param = {
+          courseId: this.courseId
+        }
+        httpServer("course.getCourse", param).then(res => {
+          if (res.code == 200) {
+            let {
+              info,
+              extra,
+              list,
+              tpl
+            } = res.data;
+            this.info = Object.assign(info, extra || {});
+            this.tpl = tpl || {};
+            this.list = list.map((item) => {
+              item.percent = getPercent(item) || 0;
+              return item;
+            })
+            let target = null
+            if (this.mediaId) {
+              target = this.list.find(i => i.id == this.mediaId || i.mediaId == this.mediaId)
+            }
+            if (!target) {
+              target = this.list.find(i => i.isFinish != 1)
+            }
+            if (!target && this.list.length) {
+              target = this.list[0]
+            }
+            if (target) {
+              this.loadMedia(target)
+            }
+          }
+        })
+      },
+      handleVisibilityChange() {
+        if (document.hidden) {
+          this.enterPiP()
+          let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+          if (myPlayer && myPlayer.currentTime) {
+            this.hiddenStartTime = Date.now()
+            this.hiddenBasePosition = myPlayer.currentTime() || 0
+          }
+        } else {
+          this.exitPiP()
+        }
+      },
+      onEnterPiP() {
+        this.isPiP = true
+      },
+      onLeavePiP() {
+        this.isPiP = false
+      },
+      async enterPiP() {
+        if (!document.pictureInPictureEnabled || this.isPiP) return
+        let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+        if (!player) return
+        let video = player.tech_ && player.tech_.el_
+        if (video && video.requestPictureInPicture) {
+          try { await video.requestPictureInPicture() } catch (e) {}
+        }
+      },
+      async exitPiP() {
+        if (document.pictureInPictureElement) {
+          try { await document.exitPictureInPicture() } catch (e) {}
+        }
+      },
+      togglePiP() {
+        if (document.pictureInPictureElement) {
+          this.exitPiP()
+        } else {
+          this.enterPiP()
+        }
+      },
+      setVolume(val) {
+        this.volume = val
+        this.isMuted = val === 0
+        let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+        if (player && player.volume) {
+          player.volume(val)
+          player.muted(this.isMuted)
+        }
+      },
+      changeMedia(val) {
+        if (!val || !this.mediaUrl) return;
+        let mediaUrl = this.mediaUrl;
+        if (val == 'ld') {
+          mediaUrl = mediaUrl.replace('/hls/', '/ld/');
+        } else {
+          mediaUrl = mediaUrl.replace('/ld/', '/hls/');
+        }
+        this.mediaType = val;
+        this.stopTick()
+        this.onPlay = false
+        this.curTimes = 0
+        this.hasSeeked = false
+        this.options = {
+          ...this.options,
+          sources: [{ src: mediaUrl, type: "application/x-mpegURL" }],
+          playtimes: this.media.position || 0,
+          autoplay: true
+        }
+      },
+      startTick() {
+        let tick = this.tryTick;
+        if (this.timer) clearInterval(this.timer);
+        this.timer = setInterval(tick, 5 * 1000);
+      },
+      stopTick() {
+        if (this.timer) clearInterval(this.timer);
+      },
+      tryTick() {
+        let that = this;
+        try {
+          that.tick()
+        } catch (err) {
+          that.reportErr("play", '' + err.message)
+        }
+      },
+      playerReadied(player) {
+        this.isReady = true
+        let video = player.tech_ && player.tech_.el_
+        if (video) {
+          video.addEventListener('enterpictureinpicture', this.onEnterPiP)
+          video.addEventListener('leavepictureinpicture', this.onLeavePiP)
+        }
+      },
+      onPlayerError(event) {
+        console.error("Video error:", event)
+      },
+      onPlayerTimeupdate(player) {
+        let curTimes = player.currentTime();
+        this.curTimes = curTimes || 0
+        if (this.media.isFinish) {
+          return;
+        }
+      },
+      safePlay(player) {
+        if (!player) return
+        try {
+          const p = player.play()
+          if (p && p.catch) p.catch(() => {})
+        } catch (e) {}
+      },
+      setposition(position) {
+        if (position > this.media.duration) position = this.media.duration;
+        let player = this.$refs.videoPlayer.player;
+        player.currentTime(position);
+        this.safePlay(player)
+        if (this.media.isFinish) return;
+        if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
+          this.tick(true)
+        }
+      },
+      onPlayerPause(event) {
+        this.reportErr("play", 'pause');
+        this.stopTick()
+        this.onPlay = false
+      },
+      onPlayerEnded(event) {
+        this.reportErr("play", 'end');
+        this.tick(true)
+        this.playNext()
+      },
+      playNext() {
+        if(this.tpl.courseCode.indexOf('-')==-1){
+          return;
+        }
+        let currentIndex = this.list.findIndex(i => i.id === this.media.id)
+        let nextItem = null
+        for (let i = currentIndex + 1; i < this.list.length; i++) {
+          if (this.list[i].isFinish != 1) {
+            nextItem = this.list[i]
+            break
+          }
+        }
+        if (!nextItem) {
+          for (let i = 0; i < currentIndex; i++) {
+            if (this.list[i].isFinish != 1) {
+              nextItem = this.list[i]
+              break
+            }
+          }
+        }
+        if (nextItem) {
+          this.$message.success(`即将播放: ${nextItem.name}`)
+          setTimeout(() => {
+            this.loadMedia(nextItem)
+          }, 1000)
+        }
+      },
+      onClose() {
+        this.reportErr("play", 'close')
+        this.doPause()
+      },
+      doPause() {
+        this.stopTick()
+        this.onPlay = false
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.pause()
+      },
+      doPlay() {
+        this.onPlay = true
+        this.startTick();
+        if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
+        this.safePlay(this.$refs.videoPlayer.player)
+        this.tickNum = 0
+      },
+      onPlayerStart() {
+        this.reportErr("play", 'start');
+        this.startTick()
+        this.onPlay = true
+        if (!this.hasSeeked && this.media.position > 5 && this.media.position < this.media.duration - 10) {
+          this.hasSeeked = true
+          let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+          if (player && player.currentTime) {
+            player.currentTime(this.media.position)
+          }
+        }
+      },
+      toggleMute() {
+        this.isMuted = !this.isMuted
+        let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+        if (player && player.volume) {
+          player.muted(this.isMuted)
+        }
+      },
+      reportErr(action, msg) {
+        httpServer("course.report", { action, msg }, true)
+      },
+      getPlayerTime() {
+        if (this.hiddenStartTime) {
+          let elapsed = (Date.now() - this.hiddenStartTime) / 1000
+          let estimated = this.hiddenBasePosition + elapsed
+          let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+          if (myPlayer && myPlayer.currentTime) {
+            let actual = myPlayer.currentTime()
+            if (actual >= estimated - 0.5) {
+              this.hiddenStartTime = 0
+              return actual
+            }
+          }
+          return Math.min(estimated, this.media.duration || 1/0)
+        }
+        let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+        if (myPlayer && myPlayer.currentTime) {
+          return myPlayer.currentTime()
+        }
+        return 0
+      },
+      tick(force = false) {
+        let media = this.media;
+        this.tickNum++
+        if (this.media.isFinish) {
+          return;
+        }
+        let myPlayer = this.$refs.videoPlayer.player;
+        if (!myPlayer) return;
+        let curTimes = parseInt(this.getPlayerTime());
+        this.curTimes = curTimes || 0
+        if (curTimes < 4) {
+          return;
+        }
+        if (this.media.position > curTimes + 5 && !force) return;
+        let isFinish = force ? 1 : 0
+        if (curTimes >= media.duration) isFinish = 1;
+        if (!isFinish) {
+          if (!this.onPlay) return;
+        }
+        if (window.navigator.webdriver) {
+          this.reportErr("play", 'webdriver');
+          this.doPause()
+          return
+        }
+        let param = {
+          id: media.id,
+          position: curTimes,
+          isFinish
+        };
+        httpServer("course.tick", param, true).then(res => {
+          if (res.code == 200) {
+            let {
+              skip,
+              position,
+              pause,
+              closed
+            } = res.data
+            if (pause || closed) {
+              this.doPause();
+              if (closed) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
+              } else if (pause) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
+              }
+              return
+            }
+              if (!skip) {
+                let curTimes = parseInt(this.getPlayerTime())
+                if (position < curTimes + 5) {
+                  this.setposition(position)
+                }
+              }
+            Object.assign(this.media, res.data)
+            if( res.data.isFinish == 1){
+              this.playNext()
+            }
+          }
+        })
+      },
+      loadMedia(item) {
+        this.media = item;
+        httpServer('course.GetMedia', {
+          id: item.id
+        }).then(res => {
+          if (res.code != 200) return;
+          let {
+            mediaUrl,
+            id,
+            position,
+            marks,
+            duration
+          } = res.data || {};
+          this.mediaUrl = mediaUrl;
+          this.media.position = position;
+          this.media.duration = duration;
+          this.media.id = id;
+          this.curTimes = 0
+          this.onPlay = false
+          this.hasSeeked = false
+          this.hiddenStartTime = 0
+          this.stopTick()
+          this.options = {
+            ...this.options,
+            sources: [{
+              src: mediaUrl,
+              type: "application/x-mpegURL"
+            }],
+            playtimes: position || 1,
+            autoplay: true
+          }
+        });
+      }
+    },
+    created() {
+      document.addEventListener('visibilitychange', this.handleVisibilityChange)
+      let { uid, token, courseId, mediaId } = this.$route.query
+      this.courseId = +courseId || +this.$route.params.courseId
+      this.mediaId = mediaId || this.$route.query.mediaId || 0
+      if (uid) localStorage.uid = uid
+      if (token) localStorage.token = token
+      if (this.courseId) {
+        this.getCourse()
+      }
+    },
+    beforeDestroy() {
+      document.removeEventListener('visibilitychange', this.handleVisibilityChange)
+      this.exitPiP()
+      this.stopTick()
+      let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
+      if (player) {
+        let video = player.tech_ && player.tech_.el_
+        if (video) {
+          video.removeEventListener('enterpictureinpicture', this.onEnterPiP)
+          video.removeEventListener('leavepictureinpicture', this.onLeavePiP)
+        }
+      }
+      this.reportErr("play", 'destroy')
+    }
+  }
+</script>
+<style>
+  @import url("./play.css");
+
+  .pc-player-container {
+    min-height: 100vh;
+    background: #f0f2f5;
+    padding: 20px;
+    box-sizing: border-box;
+  }
+
+  .pc-player-wrapper {
+    max-width: 1440px;
+    margin: 0 auto;
+    display: flex;
+    gap: 20px;
+    height: calc(100vh - 40px);
+  }
+
+  .pc-player-left {
+    flex: 1;
+    min-width: 0;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .panel-toggle {
+    flex-shrink: 0;
+    width: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    color: #999;
+    font-size: 14px;
+    user-select: none;
+    transition: color 0.2s;
+  }
+
+  .panel-toggle:hover {
+    color: #409EFF;
+  }
+
+  .video-section {
+    background: #fff;
+    border-radius: 12px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    overflow: hidden;
+  }
+
+  .video-title {
+    padding: 16px 20px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #1a1a2e;
+    border-bottom: 1px solid #f0f0f0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .video-player-wrap {
+    width: 100%;
+    background: #000;
+    touch-action: none;
+  }
+
+  .video-player-wrap .video-js {
+    width: 100%;
+  }
+
+  .video-info-bar {
+    padding: 12px 20px;
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    border-top: 1px solid #f0f0f0;
+  }
+
+  .video-time {
+    font-size: 14px;
+    color: #666;
+    white-space: nowrap;
+    font-variant-numeric: tabular-nums;
+  }
+
+  .time-current {
+    color: #409EFF;
+    font-weight: 600;
+  }
+
+  .time-sep {
+    margin: 0 4px;
+    color: #ccc;
+  }
+
+  .video-progress {
+    flex: 1;
+    min-width: 100px;
+  }
+
+  .video-status {
+    flex-shrink: 0;
+  }
+
+  .pc-player-right {
+    width: 380px;
+    flex-shrink: 0;
+    background: #fff;
+    border-radius: 12px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  .playlist-header {
+    padding: 16px 20px 12px;
+    border-bottom: 1px solid #f0f0f0;
+    flex-shrink: 0;
+  }
+
+  .playlist-header-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 6px;
+  }
+
+  .playlist-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #1a1a2e;
+    margin-bottom: 4px;
+  }
+
+  .playlist-title i {
+    margin-right: 6px;
+    color: #409EFF;
+  }
+
+  .playlist-count {
+    font-size: 12px;
+    color: #999;
+  }
+
+  .finished-count {
+    color: #67C23A;
+  }
+
+  .playlist-body {
+    flex: 1;
+    overflow-y: auto;
+    padding: 8px 0;
+  }
+
+  .playlist-body::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  .playlist-body::-webkit-scrollbar-thumb {
+    background: #ddd;
+    border-radius: 3px;
+  }
+
+  .playlist-body::-webkit-scrollbar-track {
+    background: transparent;
+  }
+
+  .playlist-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 20px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    border-left: 3px solid transparent;
+    gap: 12px;
+  }
+
+  .playlist-item:hover {
+    background: #f5f7fa;
+  }
+
+  .playlist-item.is-active {
+    background: #ecf5ff;
+    border-left-color: #409EFF;
+  }
+
+  .playlist-item.is-finished {
+    opacity: 0.65;
+  }
+
+  .playlist-item.is-current-playing {
+    background: #ecf5ff;
+    border-left-color: #409EFF;
+  }
+
+  .item-index {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: #f0f2f5;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    font-weight: 600;
+    color: #666;
+    flex-shrink: 0;
+    transition: all 0.2s;
+  }
+
+  .item-index.active-index {
+    background: #409EFF;
+    color: #fff;
+  }
+
+  .finish-badge {
+    color: #67C23A;
+    font-size: 14px;
+  }
+
+  .item-info {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .item-name {
+    font-size: 14px;
+    color: #333;
+    font-weight: 500;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin-bottom: 2px;
+  }
+
+  .is-finished .item-name {
+    color: #999;
+    text-decoration: line-through;
+  }
+
+  .is-active .item-name {
+    color: #409EFF;
+  }
+
+  .item-meta {
+    display: flex;
+    gap: 12px;
+    font-size: 12px;
+    color: #999;
+  }
+
+  .item-progress {
+    color: #409EFF;
+  }
+
+  .is-finished .item-progress {
+    color: #67C23A;
+  }
+
+  .item-action {
+    flex-shrink: 0;
+  }
+
+  .playlist-empty {
+    text-align: center;
+    padding: 60px 20px;
+    color: #ccc;
+  }
+
+  .playlist-empty i {
+    font-size: 48px;
+    display: block;
+    margin-bottom: 12px;
+  }
+
+  .playlist-empty p {
+    font-size: 14px;
+    margin: 0;
+  }
+
+  .dialog-footer {
+  padding: 0 16px;
+  border-top: 1px solid #f0f0f0;
+  background: #fafafa;
+}
+.dialog-footer.pt30 {
+  padding: 8px 16px;
+}
+.media-footer {
+  display: flex;
+  align-items: center;
+  line-height: 1;
+}
+.media-time {
+  font-size: 14px;
+  color: #666;
+  white-space: nowrap;
+  font-variant-numeric: tabular-nums;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+.media-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.bicon {
+  font-size: 22px !important;
+  padding: 4px !important;
+}
+.volume-control {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+.vol-icon {
+  font-size: 12px;
+  font-weight: 600;
+  cursor: pointer;
+  color: #606266;
+  user-select: none;
+  line-height: 1;
+}
+.vol-icon:hover {
+  color: #409EFF;
+}
+.volume-slider {
+  width: 300px !important;
+}
+.volume-slider .el-slider__runway {
+  height: 4px;
+}
+.volume-slider .el-slider__bar {
+  height: 4px;
+}
+.volume-slider .el-slider__button {
+  width: 12px;
+  height: 12px;
+}
+.pip-control {
+  display: inline-flex;
+  align-items: center;
+  margin-left: 12px;
+}
+.pip-icon {
+  font-size: 12px;
+  font-weight: 600;
+  cursor: pointer;
+  color: #606266;
+  user-select: none;
+  line-height: 1;
+  padding: 4px 8px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  transition: all 0.2s;
+}
+.pip-icon:hover {
+  color: #409EFF;
+  border-color: #c6e2ff;
+  background: #ecf5ff;
+}
+.pip-icon.is-active {
+  color: #409EFF;
+  border-color: #409EFF;
+  background: #ecf5ff;
+}
+.media-select {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+.media-el-select {
+  width: 80px;
+}
+.media-el-select .el-input__inner {
+  height: 28px;
+  line-height: 28px;
+  font-size: 12px;
+}
+
+@media (max-width: 900px) {
+    .pc-player-wrapper {
+      flex-direction: column;
+      height: auto;
+    }
+
+    .pc-player-right {
+      width: 100%;
+      max-height: 400px;
+    }
+
+    .pc-player-container {
+      padding: 12px;
+    }
+  }
+</style>

+ 16 - 8
src/containers/center/setting/index.vue

@@ -3,8 +3,8 @@
     <div class="right-block-bd">
       <div class="m-account">
         <div class="account-tit" style="height:30px">
-          <a :class="{'current':show===1}" style="text-decoration: none;padding-bottom: 5px" @click="show=1" class="">基础资料</a>
-          <a :class="{'current':show===2}" style="text-decoration: none;padding-bottom: 5px" @click="show=2" class="">实名认证</a>
+         <a :class="{'current':show===1}" style="text-decoration: none;padding-bottom: 5px" @click="show=1" class="">基础资料</a>
+          <!-- <a :class="{'current':show===2}" style="text-decoration: none;padding-bottom: 5px" @click="show=2" class="">实名认证</a> -->
           <a :class="{'current':show===3}" style="text-decoration: none;padding-bottom: 5px" @click="show=3" class="">额外信息</a>
           <a :class="{'current':show===4}" style="text-decoration: none;padding-bottom: 5px" @click="show=4" class="">密码修改</a>
           <a :class="{'current':show===5}" style="text-decoration: none;padding-bottom: 5px" @click="show=5" class="">手机修改</a>
@@ -15,12 +15,12 @@
             <div class="m-img tc"></div>
 
             <el-row :gutter="40" class="mt20">
-              <el-col :span="6" class="col-normal tc">
+              <el-col :lg="6"  :xs="24" class="col-normal tc">
                 <img :src="userInfo.headImg" :onerror="errorImg" class="setting-img" @click="showImg(userInfo.headImg)">
                 <do-upload @onFinish="setHeadImg" placeholder="上传头像"> </do-upload>
               </el-col>
 
-              <el-col :span="6" class="col-normal tc">
+              <el-col :lg="6" :xs="24" class="col-normal tc">
                 <img :src="userInfo.graduateImg" :onerror="errorImg" class="setting-img" @click="showImg(userInfo.graduateImg)">
                 <do-upload @onFinish="setGraduateImg" placeholder="上传毕业证"> </do-upload>
               </el-col>
@@ -28,7 +28,7 @@
 
             <ul class="ul-form">
               <li class="txt-r">
-                <span class="l-t"></span>注:<i></i>只有头像、性别、毕业证可以修改
+                <span class="l-t"></span>注:<i></i>所有信息都非必须,你可以选择性上传。
               </li>
 
               <li>
@@ -38,6 +38,9 @@
               </li>
               <li class="ng-binding">
                 <span class="l-t">证件号:</span>{{userInfo.cardId||'未知'}}</li>
+
+              <li class="ng-binding">
+                <span class="l-t"> 账户余额:</span>¥{{userInfo.balance/100}}元</li>
               <li>
                 <span class="l-t"><i></i>性别:</span>
 
@@ -73,12 +76,12 @@
             <div class="m-img tc"></div>
 
             <el-row :gutter="40" class="mt20">
-              <el-col :span="6" class="col-normal tc">
+              <el-col :lg="6" :xs="24" class="col-normal tc">
                  <img :src="userInfo.cardImg" :onerror="errorImg" class="setting-img" @click="showImg(userInfo.cardImg)">
                 <do-upload @onFinish="setCardImg" placeholder="身份证正面"> </do-upload>
               </el-col>
 
-              <el-col :span="6" class="col-normal tc">
+              <el-col :lg="6" :xs="24"  class="col-normal tc">
                 <img :src="userInfo.cardBackImg" :onerror="errorImg" class="setting-img" @click="showImg(userInfo.cardBackImg)">
                 <do-upload @onFinish="setCardBackImg" placeholder="身份证背面"> </do-upload>
               </el-col>
@@ -132,7 +135,11 @@
             </el-form-item>
 
             <el-form-item label="最高学历" label-width='150px' class="mt30" prop="oldPass" >
-               <el-input placeholder="最高学历" v-model="userInfo.education " class="pass-input">
+              <el-select placeholder="选择学历" v-model="userInfo.education" class="pass-input" >
+                <el-option v-for="(item,index) in educationList" :value="item" :label="item" v-bind:key="index" />
+              </el-select>
+               <!-- <el-input placeholder="最高学历" v-model="userInfo.education " class="pass-input">
+                -->
                </el-input>
             </el-form-item>
 
@@ -242,6 +249,7 @@
         timer: false,
         count: 0,
         imgUrl:'',
+        educationList: window.baseConfig.educationList,
         passForm:{
           oldPass:'',
           newPass:'',

+ 96 - 0
src/containers/center/userCert/index.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="m-right-block fr mh576" style="position: relative;">
+    <div class="right-block-bd">
+      <div class="mb10 tc mt20">
+        <span class="p-ico2 worm-ico-o"></span>
+        注:<span class="txt-o"></span>考试通过一周内生成,若未看到,请联系管理员
+      </div>
+
+      <div  class="ng-scope">
+        <el-table v-loading="listLoading" :data="list" border fit highlight-current-row>
+          <el-table-column label="流水号" align="center"  prop="uuid"/>
+          <el-table-column label="岗位类别" prop="courseName" align="center"/>
+
+          <el-table-column label="所在企业" prop="company" align="center"/>
+
+          <el-table-column label="发布时间" prop="publishDate" align="center"/>
+
+          <el-table-column label="操作" fixed="right" align="center" width="100">
+            <template slot-scope="{row}">
+              <el-button  @click="printCert( row )" type="text" size="small">查看</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page="page"
+           :page-size="size"
+           layout="total, prev,pager, next"
+           :total="list.length">
+        </el-pagination>
+
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script>
+  import {httpServer} from "@/components/httpServer/httpServer.js";
+  import {formatTimeToStr} from "@/utils/date.js";
+ import { mapGetters } from "vuex";
+  export default {
+    name: "Index",
+    data() {
+      return {
+        page:1,
+        size:10,
+        total: 20,
+        info:{},
+        list: [],
+        printCertDialog:false,
+        listLoading: false
+      };
+    },
+    beforeMount() {
+      this.getData()
+    },
+    computed: {
+    	...mapGetters("user", ["userInfo"])
+    },
+    methods: {
+      getData() {
+        this.listLoading = true
+        httpServer('Course.GetSmCertificateList', {}).then( res=>{
+            if( res.code == 200){
+              this.list = res.data||[]
+            }
+            this.listLoading = false
+        })
+      },
+      printCert( row  ){
+        window.open(row.certUrl, '_blank')
+      },
+      handleCurrentChange: function(page) {
+        this.page = page
+        this.getData()
+      }
+    }
+  }
+</script>
+
+<style>
+  .previewDialog{
+      text-align: center;
+  }
+  .previewDialog.el-dialog {
+    .el-dialog__header {
+      display: none;
+    }
+    .dj-dialog-content {
+      padding: 0;
+      overflow: unset;
+    }
+  }
+</style>

+ 10 - 10
src/containers/center/userSeat/components/iPrintCert.vue

@@ -25,16 +25,16 @@
           <td class="m-attr">考试类别</td>
           <td colspan="7" class="tl">{{info.examType}}</td>
         </tr>
-        <tr>
-          <td class="m-attr">岗位名称</td>
-          <td colspan="7" class="tl">{{info.courseName}}</td>
-        </tr>
         <tr>
           <td class="m-attr">工作单位</td>
           <td colspan="7" class="tl">{{info.company}}</td>
         </tr>
         <tr>
-          <td class="m-attr">考试地点</td>
+          <td class="m-attr">实操考试地点</td>
+          <td colspan="7" class="tl">{{info.opAddress||''}}</td>
+        </tr>
+        <tr>
+          <td class="m-attr">理论考试地点</td>
           <td colspan="7" class="tl">{{info.address}}</td>
         </tr>
 
@@ -43,20 +43,20 @@
           <td class="m-attr" colspan="2" style="width: 15%">准考证号</td>
           <td class="m-attr" style="width: 13%">考试时长</td>
 
-          <td class="m-attr" colspan="3" style="width: 30%">考场号</td>
+          <td class="m-attr" colspan="3" style="width: 30%">理论考试时间</td>
           <td class="m-attr" style="width: 9%">座位号</td>
-          <td class="m-attr" colspan="2" style="width: 20%">考试时间</td>
+          <td class="m-attr" colspan="2" style="width: 20%">实操考试时间</td>
         </tr>
 
         <tr>
-          <td  class="tc m-attr">检测试验人员</td>
+          <td  class="tc m-attr">{{info.courseName}}</td>
           <td class="tc m-attr" colspan="2">{{info.uuid}}</td>
           <td class="tc">{{info.duration}}</td>
 
-          <td class="tc" colspan="3">{{info.examRoom}}</td>
+          <td class="tc" colspan="3">{{info.examTime}}</td>
           <td class="tc">{{info.seat}}</td>
 
-          <td class="tc" colspan="2">{{info.examTime}}</td>
+          <td class="tc" colspan="2">{{info.opExamTime}}</td>
         </tr>
 
         <tr>

+ 1 - 1
src/containers/center/userSeat/index.vue

@@ -84,7 +84,7 @@
         })
       },
       printCert( row  ){
-        document.title = '福建省建设行业从业人员岗位考核考试';
+        document.title = window.baseConfig.userSeatTitle;
         this.info = Object.assign(this.info, row);
         if( !this.userInfo.headImg ){
           this.$message.errorMsg("还未上传头像", 2)

+ 106 - 0
src/containers/certificate/fjCertificate.css

@@ -0,0 +1,106 @@
+.learning-time-panel-2 .ui-ipt {
+  width: 300px;
+  border: 0;
+  padding: 13px 8px;
+  margin-right: 10px;
+}
+.learning-time-panel-2 .ipt-1 {
+  width: 400px;
+}
+.learning-time-panel-2 .ui-btn {
+  width: 240px;
+  height: 40px;
+  line-height: 40px;
+  background-color: #0e67a6;
+  box-sizing: border-box;
+  vertical-align: middle;
+}
+.learning-time-panel-2 .ui-btn:hover {
+  background-color: #1075be;
+}
+.title-box .goto-sign {
+  float: right;
+  color: #3290d4;
+  cursor: pointer;
+  font-weight: normal;
+}
+.title-box .goto-sign:hover {
+  color: #0e67a6;
+  text-decoration: underline;
+}
+.title-box .tab-switch {
+  float: right;
+  margin-right: 50px;
+}
+.title-box .tab-item {
+  display: inline-block;
+  padding: 4px 24px;
+  font-size: 1.5em;
+  color: #666;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+}
+.title-box .tab-item.active {
+  color: #3290d4;
+  border-bottom-color: #3290d4;
+}
+.title-box .tab-item:hover {
+  color: #3290d4;
+}
+
+.cert-card {
+  margin-bottom: 20px;
+  border: 2px solid #c0c4cc !important;
+  border-radius: 8px;
+}
+
+.cert-card .el-card__body {
+  padding: 0 !important;
+}
+
+.cert-card-header {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.cert-card-body {
+  padding: 0;
+}
+
+.cert-row {
+  display: flex;
+  border-bottom: 1px solid #e8e8e8;
+}
+
+.cert-row:last-child {
+  border-bottom: none;
+}
+
+.cert-item {
+  display: flex;
+  flex: 1;
+  min-width: 0;
+}
+
+.cert-item:first-child {
+  border-right: 1px solid #e8e8e8;
+}
+
+.cert-label {
+  width: 100px;
+  min-width: 100px;
+  padding: 10px 12px;
+  background-color: #e6f2ff;
+  font-weight: bold;
+  color: #333;
+  font-size: 14px;
+  box-sizing: border-box;
+}
+
+.cert-value {
+  padding: 10px 12px;
+  font-size: 14px;
+  color: #333;
+  flex: 1;
+}

+ 130 - 0
src/containers/certificate/fjCertificate.vue

@@ -0,0 +1,130 @@
+<template>
+  <div>
+    <navbar></navbar>
+    <div class="p-wrap-full" style="width: 1200px;margin: 20px auto;">
+      <el-card>
+        <el-form :inline="true" :model="periodObj" class="search-form">
+          <el-form-item label="证件号">
+            <el-input v-model="periodObj.cardId" placeholder="请输入证件号" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="姓名">
+            <el-input v-model="periodObj.nickname" placeholder="请输入姓名" clearable></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="goVerify()">立即查询</el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
+    </div>
+    <div class="p-wrap-full" style="width: 1200px;margin: 20px auto;min-height: 500px">
+      <el-card class="cert-card" v-for="(item, index) in recodeList" :key="index">
+        <div slot="header" class="cert-card-header">
+          <span>证书【{{ index + 1 }}】</span>
+        </div>
+        <div class="cert-card-body">
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">姓名</span>
+              <span class="cert-value">{{ item.userName }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">身份证</span>
+              <span class="cert-value">{{ item.cardId }}</span>
+            </div>
+          </div>
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">证书编号</span>
+              <span class="cert-value">{{ item.certificateNum }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">岗位名称</span>
+              <span class="cert-value">{{ item.positionName }}</span>
+            </div>
+          </div>
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">证书状态</span>
+              <span class="cert-value">{{ item.certificateStatus }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">证书类型</span>
+              <span class="cert-value">{{ item.certificateType }}</span>
+            </div>
+          </div>
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">岗位编码</span>
+              <span class="cert-value">{{ item.careerCode }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">发证机关</span>
+              <span class="cert-value">{{ item.certificateOrgan }}</span>
+            </div>
+          </div>
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">发证时间</span>
+              <span class="cert-value">{{ item.startTime }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">生日</span>
+              <span class="cert-value">{{ item.birthday }}</span>
+            </div>
+          </div>
+
+          <div class="cert-row">
+            <div class="cert-item">
+              <span class="cert-label">工作单位</span>
+              <span class="cert-value">{{ item.workUnit }}</span>
+            </div>
+            <div class="cert-item">
+              <span class="cert-label">复核时间</span>
+              <span class="cert-value">{{ item.reviewTime }}</span>
+            </div>
+          </div>
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+
+<script>
+  import navbar from "@/components/nav/navbar.vue";
+  import {httpServer} from "@/components/httpServer/httpServer.js";
+  export default {
+    components: { navbar },
+    data(){
+      return {
+        activeTab: 1,
+        periodObj:{
+          nickname:'',
+          cardId:'',
+        },
+        recodeList:[] ,
+      }
+    },
+    methods: {
+      goVerify(){
+        let {nickname,cardId} = this.periodObj;
+        if( !nickname || cardId.length <15 || cardId.length> 18){
+          this.$message({message:'请输入大陆身份证(15-18位)'})
+          return
+        }
+        httpServer("User.ReLoadRzCode", {nickname,cardId}).then( res=>{
+          if( res.code == 200){
+            this.recodeList = res.data;
+          }
+        })
+      },
+      goSign(){
+        this.$router.push({name:'nd-sign'})
+      }
+    }
+  }
+</script>
+
+<style>
+  @import url("fjCertificate.css");
+</style>

+ 271 - 0
src/containers/certificate/index.css

@@ -0,0 +1,271 @@
+.cert-body {
+  width: 100%;
+  overflow-y: hidden;
+  max-width: 500px;
+  margin: 0px auto;
+  background-size: 100% auto;
+  background-color: #4eaceb;
+  font-family: "Arial";
+  color: #fff;
+  background-image: linear-gradient(#207ec8, #fff);
+}
+
+.box_title {
+  height: 3rem;
+  font-size: 1.6rem;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #fff;
+}
+
+.content_box_title{
+  color: #000;
+  background-color: #f3f8fc;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 57px;
+}
+.content_box_title span{
+  width: 55%;
+  text-align: center;
+}
+
+.sign-serach{
+  width: 360px;
+  margin-top: 1rem;
+  padding-top: 1rem;
+  margin: 1rem auto;
+  background-color: white;
+}
+
+.sign-serach button{
+  padding: 12px 64px;
+}
+
+.mess_style {
+  background-color: #f3f8fc;
+  padding: 1rem;
+  font-size: 1.1rem;
+  color: #999;
+  line-height: 1.5rem;
+  margin-top: 1.2rem;
+}
+
+
+
+.mess_title {
+  color: #000;
+  font-weight: 600;
+  margin-top: 1rem;
+}
+
+.mess_explain {
+  position: relative;
+  width: 6rem;
+  height: 3rem;
+  text-align: right;
+  float: right;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  background: #178ce8;
+  opacity: .7;
+  border-radius: 0 0 0 20px;
+}
+
+.mess_explain img {
+  width: 2rem;
+  margin-right: 8px;
+}
+
+.mess_text {
+  display: inline-block;
+  text-align: justify;
+  text-indent: 2rem;
+  margin-top: .8rem;
+  line-height: 2rem;
+}
+
+
+.qr-code {
+  cursor: pointer; /* 鼠标悬停显示手型,提示可点击 */
+  display: inline-block;
+}
+
+/* 验证码图片样式 */
+.qr-code img {
+  height: 40px; /* 与输入框高度保持一致 */
+  width: auto; /* 自动计算宽度,保持图片比例 */
+  vertical-align: middle; /* 垂直居中对齐 */
+  border-radius: 4px; /* 可选:添加轻微圆角,与输入框风格统一 */
+}
+
+
+.certificate-cards {
+  display: flex;
+  margin-top: 0rem;
+  flex-direction: column;
+  gap: 15px;
+  overflow-y: hidden;
+}
+
+.card {
+  background-color: #fff;
+  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.04);
+}
+
+.card-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin: 0;
+  color: #333;
+}
+
+.card-status {
+  padding: 2px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.card-status.valid {
+  background-color: #e6f7ed;
+  color: #00875a;
+}
+
+.card-status.expired {
+  background-color: #fff2e8;
+  color: #e53e3e;
+}
+
+/* 信息网格布局,适合手机屏幕 */
+.info-grid {
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 0; /* 移除间隙,让线条连贯 */
+}
+
+.info-item {
+  display: flex;
+  padding: 0;
+}
+
+/* 为第一列(标签列)添加背景色 */
+.certificate-cards .el-table_1_column_1 {
+  flex: 0 0 35%;
+  font-size: 14px;
+  color: #666;
+  background-color: #f5f7fa !important; /* 浅灰色背景 */
+  padding: 10px 8px;
+  border-bottom: 1px solid #e9e9e9;
+  font-weight: 500;
+}
+
+.info-value {
+  flex: 0 0 65%;
+  font-size: 14px;
+  color: #333;
+  padding: 10px 8px;
+  border-bottom: 1px solid #e7f1f9;
+  word-break: break-word;
+}
+
+/* 最后一项去除底部边框 */
+.info-grid :last-child .info-label,
+.info-grid :last-child .info-value {
+  border-bottom: none;
+}
+
+.remark {
+  margin-top: 12px;
+  padding: 8px;
+  background-color: #f5f7fa;
+  border-left: 3px solid #2f86eb;
+  color: #666;
+  font-size: 13px;
+  line-height: 1.5;
+}
+
+.cls-label{
+  background-color: #999;
+}
+/* 适配小屏手机 */
+@media (max-width: 360px) {
+  .main-title {
+    font-size: 15px;
+  }
+  .info-label, .info-value {
+    font-size: 13px;
+    padding: 8px 6px;
+  }
+}
+
+
+
+ /* 左上角后退按钮 */
+.back-button {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 40px;
+    height: 40px;
+    background-color: #1E88E5;
+    border: none;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: background-color 0.3s ease;
+}
+
+.back-button:hover {
+    background-color: #1565C0;
+}
+
+.back-button svg {
+    width: 30px;
+    height: 30px;
+    fill: white;
+}
+
+
+
+
+
+/* 卡片整体样式 */
+.custom-card {
+  margin: 10px auto;
+  --el-card-padding: 0; /* 清除卡片默认内边距 */
+}
+
+/* 卡片内容 flex 布局:左右分栏 */
+.card-content {
+  display: flex;
+  height: 50px; /* 固定卡片高度,可自行调整 */
+}
+
+/* 左侧序号区域:10% 宽度 */
+.left-section {
+  width: 50px;
+  background-color: #409eff; /* 主题蓝色 */
+  display: flex;
+  align-items: center; /* 垂直居中 */
+  justify-content: center; /* 水平居中 */
+  color: white;
+  font-size: 36px;
+  font-weight: bold;
+}
+.right-section{
+  width: 380px;
+  margin-left: 4px;
+}
+.right-section-item{
+  margin: 4px 10px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}

+ 194 - 0
src/containers/certificate/index.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="cert-body">
+
+    <div v-if="showTable" class="back-button" @click="showTable=false">
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+            <path d="M19 11H7.83l4.88-4.88c.39-.39.39-1.03 0-1.42-.39-.39-1.02-.39-1.41 0l-6.59 6.59c-.39.39-.39 1.02 0 1.41l6.59 6.59c.39.39 1.02.39 1.41 0 .39-.39.39-1.03 0-1.42L7.83 13H19c.55 0 1-.45 1-1s-.45-1-1-1z"/>
+        </svg>
+    </div>
+
+
+    <el-card style="border-radius: 10px; padding-top: 2rem;">
+      <div class="content_box_title">
+        <img
+          src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAOCAYAAABaSYBTAAAABHNCSVQICAgIfAhkiAAAANRJREFUWEftmO0NgyAYhOtOMIxr6By6RoeBnVTqR2rL6wlFOJPyy+QC3j0vIlAZY55a6/ohNHbd2S7tUWJXOcFaOyilXs++xq4zZPBx24CyA0T+GAHvZisKwK6zAf5aCtgBIn9MgL3rLArArrMAvu1PDBWYAbAIl8EcAoj00hkO4ZY2l+L9KcZARZR0CLekuXXvGBvufe/56xgx/U/B/QOeyxQK+DTcmME/Ty2h5lL3z50hCG5uc1ccxXNmCIa7mGunu4hOuIpwn08z6T2rvmS42mM7AlIFLwQXiU8MAAAAAElFTkSuQmCC" />
+        <span style="font-weight: 700;">{{showTable?'证书信息':'学时验证'}}</span>
+        <img
+          src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFcAAAAOCAYAAABaSYBTAAAABHNCSVQICAgIfAhkiAAAALRJREFUWEft2LENgCAQBVCZwtoZkBHcwtItnMTedSgI29CgmGhiRBS8K0w+7YX75AEFiCpzGGMa59yglBpjU7nrIZM7g6q/yLHVWkvv/dSuIzaPux4yuTMo+7/GpQwt2Zi/wYb1vsIFbNmNecQFbBns48kFbDlsEhew32BvcQH7HTaKC1ga2AsuYOlgT7iApYU9cAFLD7vhApYHdsftpZRz7Elqra3XT5qOqx4yuTO4+6f+ZhZW89Ai/bICGAAAAABJRU5ErkJggg==" />
+      </div>
+
+      <el-form class="sign-serach" :rules="rules" :inline="false" :model="searchInfo" ref="searchForm"
+              label-width="80px">
+        <el-form-item label="身份证号" prop="cardId">
+          <el-input placeholder="输入身份证" v-model="searchInfo.cardId" type="text" style="width:260px;"
+            clearable></el-input>
+        </el-form-item>
+        <el-form-item label="学员姓名" prop="nickname">
+          <el-input placeholder="输入姓名" v-model="searchInfo.nickname" type="text" style="width:260px;"
+            clearable></el-input>
+        </el-form-item>
+
+
+        <el-form-item label="校验码" prop="code">
+          <el-input placeholder="校验码" v-model="searchInfo.code" type="text" style="width:130px;" clearable></el-input>
+          <span class="qr-code ng-scope" @click="changeCode()">
+            <img alt="验证码" :src="codeShow" v-show="codeShow">
+          </span>
+        </el-form-item>
+
+        <el-form-item class="form-btn-group" label-width="0px">
+          <el-button size="medium" @click="resetForm">重 置</el-button>
+          <el-button size="medium" type="primary" @click="queryCertificate">查 询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <div v-if="certificates.length>0">
+        <div class="certificate-cards">
+          <h3 style="margin: 1rem;">验证结果</h3>
+          <div v-for="(item, index) in certificates" :key="index" class="card">
+            <el-card class="custom-card">
+              <div class="card-content">
+                <div class="left-section">
+                  <span class="serial-number">{{ index+1 }}</span>
+                </div>
+                <div class="right-section">
+                  <p class="right-section-item" style="font-size: 16px;">
+                    <span > {{item.nd}}年度  {{ item.courseName }} </span>
+                    <span > <strong style="color: green;">{{ item.totalXs/10 }}</strong> 学时 </span>
+                  </p>
+
+                  <p class="right-section-item" style="font-size: 12px;">
+                    <span> 年度:{{item.nd}}</span>
+                    <span style="color: green;margin-right: 20px;"> 合格</span>
+                  </p>
+                </div>
+              </div>
+            </el-card>
+          </div>
+        </div>
+      </div>
+
+      <div class="mess_style" style="margin-top: 40px;">
+        <div class="mess_explain">
+          <img
+            src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABHNCSVQICAgIfAhkiAAAATZJREFUSIm1lrtxwzAQRA8czTh36h5UgBKjEVfgBpyJylyDmwEVuhnHzp4DH6QT/hKlneFwCO7eLsATICcdAF5E9mbI630RkaOIiHNu7tWpFgcCOUpjANcZJYWCGvqE44FZLwtfrlpP3xZcaq1ZfVY2fYPzNGiUBzSEagrgHfgFvhocXzSyCRril2Tt3xrcuOTBDg51CPBtuNsO9zwbM72mgQqfgU9gN8CNdcNGzj+uLpxzPyLyMcq3juE0rTHe0NKq5v879z64EfgbTALApM9LT7AG0cQ/qP4xmiwip9323thHk4djEpGDdb0XTGMcJufcog/Zdr4Sr5lrttfkyYZbmNpm2xNrz6eX74TJTOovB8HloVeuwejptlZLfmbPtYbQ5OmRPR6uYIYWm6n/iymGudXsquJ/zhquFSAu+Z8AAAAASUVORK5CYII=" />
+          <span>说明</span>
+        </div>
+        <div style="margin-top: 3rem;">
+          <div class="mess_title">01、证书依据</div>
+          <div class="mess_text">
+            宁德市建筑业协会建设行业从业人员继续教育网络培训平台
+          </div>
+          <div class="mess_title">02、查询范围</div>
+          <div class="mess_text">
+            施工现场专业人员,企业安全生产培训,安全生产管理人员,建筑施工特种作业人员
+          </div>
+          <div class="mess_title">03、联系方式</div>
+          <div class="mess_text">
+            电话: 0593-2823165 邮箱:3436014603@qq.com
+          </div>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+<script>
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
+  import {curDate} from "@/utils/date.js";
+  export default {
+    data() {
+      return {
+        id:'',
+        codeShow: '',
+        showTable: false,
+        certificates: [
+        ],
+        searchInfo: {
+          cardId: '',
+          nickname: '',
+          code: '',
+          captchaId: '',
+        },
+        rules: {
+          code: [{
+              required: true,
+              message: '请输入校验码',
+              trigger: 'blur'
+            },
+            {
+              len: 5,
+              message: '校验码有误',
+              trigger: 'blur'
+            },
+          ],
+          nickname: [{
+            required: true,
+            message: '请输入姓名',
+            trigger: 'blur',
+            min: 2
+          }],
+          cardId: [{
+              required: true,
+              message: '请输入身份证号码',
+              trigger: 'blur'
+            },
+            {
+              validator: (rule, value, callback) => {
+                if (!value) {
+                  return callback(new Error('请输入身份证号码'));
+                }
+                const cleanValue = value.replace(/[\s()]/g, '').toUpperCase();
+                const mainlandReg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
+                const hkReg = /^(?:C(?:\d{8}|[A-HJ-NP-Z]\d{7})|[HM](?:\d{10}|\d{8}))$/;
+                if (mainlandReg.test(cleanValue) || hkReg.test(cleanValue)) {
+                  callback();
+                } else {
+                  callback(new Error('请输入正确的大陆身份证(18位)或香港身份证(如 A1234567)'));
+                }
+              },
+              trigger: 'blur'
+            }
+          ],
+        },
+      }
+    },
+    methods: {
+      resetForm() {
+        this.searchInfo.cardId = '';
+        this.searchInfo.nickname = '';
+
+      },
+      changeCode() {
+        httpServer("Auth.GetValidateCode", {}).then(res => {
+          if (res.code == 200) {
+            this.codeShow = res.data.codeData;
+            this.searchInfo.captchaId = res.data.captchaId;
+          } else {
+            this.codeShow = ''
+          }
+        })
+      },
+      queryCertificate() {
+        this.$refs.searchForm.validate(valid => {
+          if (!valid) this.changeCode();
+          let param = Object.assign({}, this.searchInfo);
+          this.certificates = []
+          httpServer("Auth.QueryStudyHistory", param).then(res => {
+            this.changeCode();
+            if (res.code != 200) return;
+            this.certificates = res.data||[]
+          })
+        });
+      }
+    },
+    created() {
+      this.changeCode();
+    }
+  }
+</script>
+<style>
+  @import url("./index.css");
+
+</style>

+ 44 - 14
src/containers/login/components/friendlink/friendlink.vue

@@ -2,45 +2,75 @@
   <div class="youlian_mian">
     <div class="friendship">
       <ul>
-        <a href="http://www.mohurd.gov.cn/" target="_blank" style="height: 69px;overflow: hidden;">
+        <li v-for="(item, index) in links">
+          <el-button :type="item.type" @click="openLink(index)" class="friendship-a">{{item.name}}<br/> {{item.name2}}</el-button>
+        </li>
+
+<!--        <el-button type="primary" @click="Window.open('http://110.83.229.16:8123/gaia/peelLogin.html')"></el-button>
+        <a href="http://rcgz.mohurd.gov.cn" target="_blank"  class="friendship-a">
           <li>
-            <img alt=""  src="/static/images/friendlink/zhujianbu.png" />
+            <img alt=""
+              src="/static/images/friendlink/congyeguanli.png" class="friendship-a-img"/>
           </li>
         </a>
-        <a href="http://rcgz.mohurd.gov.cn" target="_blank"  style="height: 69px;overflow: hidden;">
+
+        <a href="http://220.160.52.118:9084/portalWeb/webPortal/toIndexPage" target="_blank" class="friendship-a">
           <li>
-            <img alt=""
-              src="/static/images/friendlink/congyeguanli.png" />
+            <img alt="" src="/static/images/friendlink/fjjscyry.png"  class="friendship-a-img" />
           </li>
         </a>
 
-        <a href="http://220.160.52.164:9081/portalWeb/webPortal/toIndexPage" target="_blank" style="height: 69px;overflow: hidden;">
+        <a href="http://zjj.sm.gov.cn/" target="_blank" class="friendship-a">
           <li>
-            <img alt="" src="/static/images/friendlink/fjjscyry.png"  />
+            <img src="/static/images/friendlink/sm.png" class="friendship-a-img"/>
           </li>
         </a>
 
-        <a href="http://zjj.sm.gov.cn/" target="_blank" style="height: 69px;overflow: hidden;">
+        <a href="http://csjs.snjsrc.com/" target="_blank" class="friendship-a">
           <li>
-            <img src="/static/images/friendlink/sm.png" />
+            <img src="/static/images/friendlink/link5.png" class="friendship-a-img" />
           </li>
-        </a>
+        </a> -->
       </ul>
     </div>
   </div>
 </template>
+<script>
 
+export default {
+  data(){
+    return {
+      // 三明市建设从业人员综合服务平台
+      links:[
+        {name:'宁德市建筑业协会网', name2:"", type:'warning', link:'http://www.ndsjx.com'},
+        {name:'住房城乡建设行业人员培训管理信息系统', type:'success', link:'http://rcgz.mohurd.gov.cn'},
+        {name:'福建省建设从业人员综合服务平台',   name2:"", type:'danger', link:'http://220.160.52.118:9084/portalWeb/fwZhcx/toFwZhcxPage?code=zscx'},
+      ]
+    }
+  },
+  methods: {
+    openLink(index){
+      let item = this.links[index];
+      window.open( item.link, '_brank')
+    }
+  }
+}
+</script>
 <style scoped>
 .friendship {
   margin-top:20px;
-  height: 100px;
+  height: 70px;
 }
 .friendship ul {
   display: flex;
   justify-content: space-between;
 }
-.friendship ul li img {
-  /* width: 100%; */
-  width: 280px;
+.friendship-a {
+  width: 360px;
+  line-height: 20px;
+  font-weight: 900;
+  font-size: 16px;
+  overflow: hidden;
 }
+
 </style>

+ 3 - 3
src/containers/login/components/isLogin.vue

@@ -24,7 +24,7 @@
     <ul class="entry-box clear">
       <li>
         <a href="javascript:void(0)" @click="gotoPage('/center/market')">
-          <i class="p-ico i7"></i><span>在线课程</span></a>
+          <i class="p-ico i7"></i><span>继续教育</span></a>
       </li>
       <li><a href="javascript:void(0)" @click="gotoPage('/center/class/train')">
         <i class="p-ico i1"></i><span>我的学习</span></a>
@@ -32,8 +32,8 @@
       <li><a href="javascript:void(0)" @click="gotoPage('/center/sign')">
         <i class="p-ico i2"></i><span>学时证明</span></a>
       </li>
-      <li><a href="javascript:void(0)" @click="gotoPage('/center/myOrder')">
-        <i class="p-ico i3"></i><span>我的订单</span>
+      <li><a href="javascript:void(0)" @click="gotoPage('/nd-sign')">
+        <i class="p-ico i3"></i><span>学时验证</span>
         </a></li>
       <li><a href="javascript:void(0)" @click="gotoPage('/center/setting')">
         <i class="p-ico i6"></i><span>帐号设置</span></a>

+ 29 - 0
src/containers/login/components/learnTime/learnTime.css

@@ -73,3 +73,32 @@
 .learning-time-panel-2 .ui-btn:hover {
   background-color: #1075be;
 }
+.title-box .goto-sign {
+  float: right;
+  color: #3290d4;
+  cursor: pointer;
+  font-weight: normal;
+}
+.title-box .goto-sign:hover {
+  color: #0e67a6;
+  text-decoration: underline;
+}
+.title-box .tab-switch {
+  float: right;
+  margin-right: 50px;
+}
+.title-box .tab-item {
+  display: inline-block;
+  padding: 4px 24px;
+  font-size: 1.5em;
+  color: #666;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+}
+.title-box .tab-item.active {
+  color: #3290d4;
+  border-bottom-color: #3290d4;
+}
+.title-box .tab-item:hover {
+  color: #3290d4;
+}

+ 10 - 5
src/containers/login/components/learnTime/learnTime.vue

@@ -1,10 +1,11 @@
 <template>
   <div>
-    <div class="p-wrap-full mb20 ng-scope">
-      <div class="title-box mt5 clear">
-        <h2 class="title">查询证书</h2>
+    <div class="p-wrap-full mb20">
+      <div class="title-box mt5">
+          <h2 :class="['tab-item', {active: activeTab === 1}]" @click="activeTab = 1">证书查询</h2>
+          <h2 :class="['tab-item', {active: activeTab === 2}]" @click="goSign()">学时认证</h2>
       </div>
-      <div class="learning-time-panel-2 mt30">
+      <div v-show="activeTab === 1" class="learning-time-panel-2 mt30">
         <div class="panel-bd tc">
           <input type="text" v-model="periodObj.cardId"
               class="ui-ipt ipt-1 ng-pristine ng-valid"
@@ -23,7 +24,7 @@
         </div>
       </div>
     </div>
-    <el-table v-if="recodeList.length>0" :data="recodeList" border fit highlight-current-row class="p-wrap-full mb20 ng-scope">
+    <el-table v-if="recodeList.length>0 && activeTab === 1" :data="recodeList" border fit highlight-current-row class="p-wrap-full mb20 ng-scope">
       <el-table-column label="姓名" prop="userName" width="90" align="center"> </el-table-column>
       <el-table-column label="身份证" prop="cardId" width="165" align="center"> </el-table-column>
       <el-table-column label="证书编号" prop="certificateNum" min-width="120" align="center" > </el-table-column>
@@ -46,6 +47,7 @@
   export default {
     data(){
       return {
+        activeTab: 1,
         periodObj:{
           nickname:'',
           cardId:'',
@@ -65,6 +67,9 @@
             this.recodeList = res.data;
           }
         })
+      },
+      goSign(){
+        this.$router.push({name:'nd-sign'})
       }
     }
   }

+ 8 - 2
src/containers/login/components/loginBase.css

@@ -76,6 +76,13 @@ input[type=radio] {
 .m-login {
   width: 290px;
   height: 350px;
+  float:right;
+}
+
+@media screen and   (max-width: 639px){
+  .m-login {
+    float: none;
+  }
 }
 
 .m-login .login-tips {
@@ -154,7 +161,7 @@ input[type=radio] {
   z-index: 9;
   background-color: #f9e2e4;
   border: 1px solid #ff914b;
-  border-radius: 5px;
+  /* border-radius: 5px; */
   color: #ff914b;
   padding: 2px 5px;
 }
@@ -301,7 +308,6 @@ input[type=radio] {
   color: #fff;
   font-size: 14px;
   line-height: 64px;
-  width: 150px;
 }
 .course-my .login-entry {
   margin-top: 15px;

+ 4 - 3
src/containers/login/components/unLogin.vue

@@ -1,7 +1,7 @@
 <template>
-  <div class="m-login opacity-bg fr">
+  <div class="m-login opacity-bg">
     <div ng-if="!sureUseLogin" class="ng-scope">
-      <div class="tit">学员/单位管理员登录</div>
+      <div class="tit">学员登录</div>
 
       <div  autocomplete="off" class="ng-pristine ng-invalid ng-invalid-required">
         <ul class="login-form login-form-1">
@@ -48,7 +48,8 @@
 
 
       <div class="mt10 ng-scope" style="font-size: 10px; text-align: center;">没有学员/单位管理员帐号?
-        <a @click="goRegist()" class="ao b">立即注册</a></div>
+        <a @click="goRegist()" class="ao b">立即注册</a>
+      </div>
     </div>
   </div>
 </template>

+ 39 - 1
src/containers/login/login.css

@@ -4,7 +4,6 @@ div {
     display: block;
 }
 
-
 .training-category {
   width: 880px;
   height: 350px;
@@ -93,3 +92,42 @@ div {
 /* .login-dialog, .el-dialog__header{
   display: none !important;
 } */
+.banner {
+    background: url(../../assets/images/banner_bg.jpg) no-repeat 0 0;
+    width: 910px;
+    height: 350px;
+    float: left;
+    position: relative;
+}
+
+.banner .category {
+    width: 290px;
+    height: 350px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: #000;
+    box-sizing: border-box;
+    padding: 15px 12px;
+}
+
+.banner .category .category-title {
+    text-align: center;
+    font-size: 18px;
+    font-weight: bold;
+    color: #fff;
+    margin-bottom: 10px;
+    line-height: 1.5;
+}
+
+.banner .category .btn-box {
+    width: 100%;
+}
+
+.banner .category .btn-box  {
+    display: inline-block;
+    vertical-align: middle;
+    margin: 2px 10px;
+    padding: 2px 2px;
+    line-height: 1.2;
+}

+ 52 - 59
src/containers/login/login.vue

@@ -1,67 +1,58 @@
 <template>
   <div class="ng-scope">
-    <navbar :user="userInfo" @openLoginDialog="openLoginDialog"></navbar>
-    <div class="banner-box">
-      <div class="p-wrap-full clear pt30">
-        <el-card class="training-category opacity-bg fl" style="border-radius: 20px;padding-top: -5px;" >
-          <h2 align="center" style="background-color: white; color: #000000;padding-top:5px;">文件通知</h2>
-          <el-table :data="postList"  highlight-current-row
-          :show-header="false" height="240px" >
-            <el-table-column min-width="600px">
-              <template slot-scope="{row, $index}">
-                <span @click="gotoDetail(row.postId)">{{(page*size)-size+$index+1}}、{{ row.title }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column  width="160px">
-              <template slot-scope="{row}">
-                <span>{{ row.publishTime  }}</span>
-              </template>
-            </el-table-column>
-          </el-table>
-          <el-pagination class="tc m-page"
-            @current-change="handlePageChange"
-            :current-page="page"
-            :page-size="size"
-            layout="total, prev, pager, next"
-            :total="total">
-          </el-pagination>
-
-        </el-card>
+    <div class="waphide">
+      <navbar :user="userInfo" @openLoginDialog="openLoginDialog"></navbar>
+    </div>
 
-        <div class="m-login opacity-bg fr">
-          <is-login v-if="loginOk" :user="userInfo" @logout="logout"></is-login>
-          <un-login v-else @login="login" :err="err"></un-login>
+    <div class="p-wrap-full clear">
+      <div class="banner">
+        <div class="category" style="height: 350px;overflow: hidden;">
+          <div class="category-title">培训类别</div>
+          <div class="category-tit clear">
+            <div class="btn-box" v-for="(item, index) in typeList" :key="index" v-show="item.isOpen">
+              <el-button @click="gotoCategory(item)" type="primary" style="width: 240px;font-size: 20px;">
+                {{ item.name }}
+              </el-button>
+            </div>
+          </div>
         </div>
       </div>
+      <div class="m-login opacity-bg">
+        <is-login v-if="loginOk" :user="userInfo" @logout="logout"></is-login>
+        <un-login v-else @login="login" :err="err"></un-login>
+      </div>
     </div>
 
-    <learn-time></learn-time>
-    <div class="p-wrap-full mb20 ng-scope">
+    <div class="p-wrap-full ng-scope waphide">
       <div class="title-box mt5 clear">
-        <h2 class="title">课程分类</h2>
+        <h2 class="title">文件通知</h2>
       </div>
-      <el-row :gutter="20" class="shadow p10 mt20" >
-        <el-col :span="+item.span" class="nopad"  v-for="(item, index) in typeList" :key="index">
-          <el-card shadow="hover" class="grid-content nopad "  >
-            <div class="mt10 typelist" @click="gotoCourse(item)">
-              <i class="p-ico"  :class="item.icon" style="color:#69c0ff"></i>
-              <p style="font-size: 13px;">{{ item.name }}</p>
-              <p class="mt10" style="font-size: 16px;" v-if="!item.isOpen">(暂未开放)</p>
-              <p class="mt10" style="font-size: 16px;" v-else>{{ item.content }}</p>
-            </div>
-          </el-card>
-        </el-col>
-      </el-row>
-    </div>
+      <el-card style="margin-top: 10px;">
+        <el-table :data="postList"  highlight-current-row
+        :show-header="false" height="240px" >
+          <el-table-column min-width="600px">
+            <template slot-scope="{row, $index}">
+              <span @click="gotoDetail(row.postId)">{{(page*size)-size+$index+1}}、{{ row.title }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column  width="160px">
+            <template slot-scope="{row}">
+              <span>{{ row.publishTime  }}</span>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-pagination class="tc m-page"
+          @current-change="handlePageChange"
+          :current-page="page"
+          :page-size="size"
+          layout="total, prev, pager, next"
+          :total="total">
+        </el-pagination>
 
-    <div class="right-nav">
-      <img class="right-img" src="../../assets/wxapp.jpg" alt="">
-      <p>使用小程序</p>
-      <p>学习更轻松</p>
+      </el-card>
     </div>
 
-
-    <div class="p-wrap-full ng-scope">
+    <div class="p-wrap-full ng-scope waphide">
       <div class="title-box mt5 clear">
         <h2 class="title">友情链接</h2>
       </div>
@@ -94,7 +85,6 @@ import { mapGetters,mapActions } from "vuex";
 
 export default {
   components: {
-    LearnTime,
     isLogin,
     unLogin,
     Friendlink,
@@ -112,7 +102,7 @@ export default {
       size: 5,
       total:0,
       loginDialog: false,
-      err: {},
+      err: {}
     };
   },
   computed: {
@@ -126,15 +116,14 @@ export default {
   //    }
   // },
   created(){
+    this.loadTypeList()
     this.getPostList()
     this.loginOk = false
     if( this.userInfo && this.userInfo.token && this.userInfo.uid){
       this.loadBaseInfo().then(res=>{
          if( !res || !res.uid ){
            this.loginOk = false
-           this.doLogout().then( res=>{
-             this.$router.push('/')
-           } )
+           this.doLogout()
          }else{
            this.loginOk = true
          }
@@ -142,9 +131,10 @@ export default {
     }
   },
   methods: {
-    ...mapActions("user", ["loadBaseInfo", "doLogout", "doLogin"]),
+    ...mapActions("user", ["loadBaseInfo", "loadTypeList", "doLogout", "doLogin"]),
     logout() {
       this.doLogout().then( res=>{
+        console.log("loginOk", res);
         this.loginOk = false;
       } )
     },
@@ -162,7 +152,7 @@ export default {
     },
     getPostList(){
       let {page,size} = this;
-      let param = {page,size,type:'操作指南'}
+      let param = {page,size}
       httpServer("course.getPostList", param).then( res=>{
         if( res.code == 200){
           this.postList = res.data.list
@@ -202,6 +192,9 @@ export default {
     openLoginDialog() {
       this.loginDialog = true
     },
+    gotoCategory(item) {
+      this.$router.push({path: '/center/market', query: {type: item.name}})
+    },
   },
 };
 </script>

+ 0 - 41
src/containers/login/loginData.js

@@ -1,41 +0,0 @@
-export default {
-  category: [
-
-      {
-          label: "建筑施工企业“三类人员”",
-          icon: "el-icon el-icon-setting",
-          name: "authority",
-          color: "#69c0ff",
-      },
-      {
-          label: "建筑施工企业特种作业人员",
-          icon: "el-icon el-icon-menu",
-          name: "menu",
-          color: "#69c0ff",
-      },
-      {
-          label: "专业技术人员",
-          icon: "el-icon el-icon-cpu",
-          name: "autoCode",
-          color: "#69c0ff",
-      },
-      {
-          label: "二级建造师",
-          icon: "el-icon el-icon-document-checked",
-          name: "formCreate",
-          color: "#69c0ff",
-      },
-      {
-          label: "施工现场专业人员-继续教育",
-          icon: "el-icon el-icon-user",
-          name: "about",
-          color: "#5cdbd3",
-      },
-      {
-        label: "园林技工",
-        icon: "el-icon el-icon-user",
-        name: "about",
-        color: "#5cdbd3",
-    }
-  ]
-}

+ 91 - 26
src/containers/register/register.vue

@@ -30,10 +30,17 @@
             <el-button class="auth-code" type="primary" v-else-if="!count" @click="getCode()">校验码</el-button>
             <el-button class="auth-code" type="primary" disabled v-else>{{count}} s</el-button>
 
-            <!-- <span class="formver ng-scope" v-if="phone.length==11 && code.length<4">
-                    <span class="p-ico ico-formver"></span>
-                    <span class="p-ico ico-worm"></span>请输入校验码
-                  </span> -->
+
+            <span class="formver ng-scope" style="font-size: 13px;color: green;" v-if="errCodeMsg=='校验成功'">
+              <span class="p-ico ico-formver"></span>
+              <span class="p-ico ico-passed ml20"></span>短信校验成功
+            </span>
+
+            <span class="formver ng-scope" style="font-size: 13px;" v-else-if="errCodeMsg">
+              <span class="p-ico ico-formver"></span>
+              <span class="p-ico ico-worm"></span>短信校验失败
+            </span>
+
           </li>
 
           <li class="mt10">
@@ -47,13 +54,15 @@
 
             <span class="formver ng-scope" v-if="!is_ok_card(cardId)">
               <span class="p-ico ico-formver"></span>
-              <span class="p-ico ico-worm"></span>请输入18位身份证号
+              <span class="p-ico ico-worm"></span>请输入合法的份证号
             </span>
           </li>
 
           <li class="mt10">
             <span class="title"><em class="fvx">*</em>学历</span>
-            <input type="text" name="education" v-model="education" class="ui-ipt iptform" placeholder="输入学历" clearable>
+            <el-select placeholder="选择学历" v-model="education" class=" iptform" >
+              <el-option v-for="(item,index) in educationList" :value="item" :label="item" v-bind:key="index" />
+            </el-select>
           </li>
 
 
@@ -136,10 +145,15 @@
           nickname: ''
         },
         count: 0,
+        errCount: 0,
         name: '',
+        isCodeOk: false,
+        errCodeMsg: "",
         passwordLevel: 'sick',
         password: '',
-
+        education:'',
+        company:'',
+        educationList: window.baseConfig.educationList,
         confirmPassword: '',
         phone: '',
         code: "",
@@ -150,6 +164,30 @@
       };
     },
     watch: {
+      code(code){
+        // const loadingInstance = this.$loading({
+        //   lock: true,
+        //   text: '用户注册中,请耐心等待...',
+        //   spinner: 'el-icon-loading',
+        //   background: 'rgba(240, 242, 245, 0.8)',
+        //   customClass: 'gray-bg-loading' // 自定义类名,避免全局污染
+        // })
+        // loadingInstance.close()
+        if(code.length != 5 || this.errCount>3) {
+          this.isCodeOk = false;
+          return;
+        }
+        if(this.isCodeOk) return;
+        let param = {phone:this.phone, code: this.code}
+        httpServer("Auth.isPhoneOk", param).then(res => {
+          this.isCodeOk = false;
+          this.errCount ++;
+          this.errCodeMsg = res.msg||'';
+          if( res.code != 200) return;
+          this.isCodeOk = true;
+          this.errCodeMsg = "校验成功"
+        })
+      },
       password(val) {
         if (val.length < 6) {
           this.passwordLevel = 'sick';
@@ -181,29 +219,36 @@
           cardId: this.cardId,
           password: md5(this.password)
         }
+        const loadingInstance = this.$loading({
+          lock: true,
+          text: '用户注册中,请耐心等待...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(240, 242, 245, 0.8)',
+          customClass: 'gray-bg-loading' // 自定义类名,避免全局污染
+        })
         httpServer("Auth.register", param).then(res => {
-          if (res.code == 200) {
-            this.$msgbox({
-              title: "注册成功",
-              message: "恭喜你-注册成功",
-              showCancelButton: false,
-              confirmButtonText: "回到登入页面",
-              beforeClose: (action, instance, done) => {
-                that.$router.push('/');
-                done();
-              }
-            })
-          } else {
-            this.err = res.msg;
-          }
-
+          console.log("loginOk");
+          loadingInstance.close();
+          if (res.code != 200) {
+             this.err = res.msg;
+             return;
+          };
+          this.$msgbox({
+            title: "注册成功",
+            message: "恭喜你-注册成功",
+            showCancelButton: false,
+            confirmButtonText: "回到登入页面",
+            beforeClose: (action, instance, done) => {
+              that.$router.push('/');
+              done();
+            }
+          })
         })
-
       },
       checkPhone() {
         let phone = this.phone;
         if (!/^1[3456789]\d{9}$/.test(phone)) {
-          this.$message.error("请填写正确的手机号");
+          this.$message.errorMsg("请填写正确的手机号", 2);
           return false;
         }
         return true
@@ -219,9 +264,17 @@
         if (!this.checkPhone()) return;
         let phone = this.phone
         let type ="register"
-
+        const loadingInstance = this.$loading({
+          lock: true,
+          text: '短信发送中,请耐心等待...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(240, 242, 245, 0.8)',
+          customClass: 'gray-bg-loading' // 自定义类名,避免全局污染
+        })
         httpServer("Auth.getCode", {phone,type}).then(res => {
+          loadingInstance.close()
           if (res.code == 200) {
+            this.errCount = 0
             this.$message.successMsg("发送成功", 1)
             this.startTimer()
           }
@@ -242,7 +295,7 @@
         }, 1000)
       },
       is_ok_card( cardId ){
-        if( !cardId ) return false;
+        if( !cardId ) return true;
         if( cardId.indexOf('H')>-1 || cardId.indexOf('W') >-1){
             return cardId.length==9 || cardId.length ==11;
         }
@@ -251,6 +304,7 @@
       },
       cansubmit() {
         if (this.isPhoneErr()) return false;
+        if(!this.isCodeOk) return false;
         if (!this.is_ok_card(this.cardId)) return false;
         if (!this.name) return false;
         if (!this.password || this.password != this.confirmPassword) return false;
@@ -264,3 +318,14 @@
     }
   }
 </script>
+<style>
+
+/* 文本用深灰色,图标同步 */
+.gray-bg-loading .el-loading-text {
+  color: #303133; /* 深灰色,与浅灰背景对比清晰 */
+  font-size: 16px;
+}
+.gray-bg-loading .el-loading-spinner .path {
+  stroke: #303133; /* 图标与文本同色,协调统一 */
+}
+</style>

+ 8 - 0
src/containers/reset/reset.vue

@@ -78,7 +78,15 @@ export default {
       } else {
         let {phone} = this.regForm;
         let type = "resetPass"
+        const loadingInstance = this.$loading({
+          lock: true,
+          text: '短信发送中,请耐心等待...',
+          spinner: 'el-icon-loading',
+          background: 'rgba(240, 242, 245, 0.8)',
+          customClass: 'gray-bg-loading' // 自定义类名,避免全局污染
+        })
         httpServer("Auth.getCode", {phone,type}).then(res => {
+          loadingInstance.close()
           if( res.code == 200){
             this.$message.successMsg("发送成功", 2);
             this.show = false

+ 10 - 8
src/containers/sign/index.css

@@ -5,6 +5,7 @@
     background-color:#4eaceb;
     margin:0;
     font-family:"Arial";
+    padding-bottom: 20px;
 }
 @media screen and  (min-width: 640px){
     .sign-body {
@@ -31,14 +32,15 @@
 }
 
 .sign-title-2{
-    width: 70%;
-    margin-left: auto;
-    margin-right: auto;
-    padding-top: 30px;
-    margin-bottom: 30px;
-    padding-bottom: 30px;
-    font-size: 20px;
-    display: block;
+  width: 100%;
+  font-size: 28px;
+  text-align: center;
+  /* margin: 30px auto; */
+  color: white;
+  padding-top: 30px;
+  padding-bottom: 30px;
+  display: block;
+  font-weight: bold;
 }
 
 .sign-content-box {

+ 3 - 2
src/containers/sign/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="sign-body">
-    <img class="sign-title-2" src="/static/sign/title.png" alt="继续教育信息验证结果">
+    <!-- <img class="sign-title-2" src="/static/sign/title.png" alt="继续教育信息验证结果"> -->
+    <span class="sign-title-2"> 课程学习信息验证结果</span>
     <div class="sign-content-box">
       <div class="sign-title">人员信息</div>
       <div class="sign-content">
@@ -15,7 +16,7 @@
       </div>
     </div>
     <div class="sign-content-box">
-      <div class="sign-title">选修课-课程
+      <div class="sign-title">课程
         <span style="font-size: 20px;color: black">(共</span>
         <span style="font-size: 20px;color: black">{{info.mediaList|totalXs}}</span>
         <span style="font-size: 20px;color: black">学时)</span>

+ 114 - 0
src/containers/weixin/index.css

@@ -0,0 +1,114 @@
+
+.weixin-body {
+    background-size: 100% auto;
+    background-color:#fff;
+    margin:0;
+    font-family:"Arial";
+    /* padding-bottom: 20px; */
+}
+@media screen and  (min-width: 640px){
+    .weixin-body {
+        font-size: 85.4px;
+    }
+}
+
+@media screen and  (min-width: 414px) and (max-width: 639px){
+    .weixin-body {
+        font-size: 55.2px;
+    }
+}
+
+@media screen and (min-width:375px) and (max-width:413px){
+    .weixin-body {
+        font-size: 50px;
+    }
+}
+
+@media screen and (min-width:320px) and (max-width:374px){
+    .weixin-body {
+        font-size: 42.7px;
+    }
+}
+
+.weixin-title-2{
+  width: 100%;
+  font-size: 28px;
+  text-align: center;
+  /* margin: 30px auto; */
+  color: white;
+  padding-top: 30px;
+  padding-bottom: 30px;
+  display: block;
+  font-weight: bold;
+}
+
+.weixin-content-box {
+    width: 96%;
+    background-color: white;
+    border-radius: .6rem;
+    margin-left: auto;
+    margin-right: auto;
+/*    padding: .9rem;
+    margin-bottom: 1.2rem; */
+}
+
+.weixin-title {
+    font-weight: bold;
+    font-size: 20px;
+    color: #439fff;
+    padding-top: 0.5rem;
+    text-align: center;
+    margin: 0 auto;
+}
+
+.weixin-content {
+    width: 100%;
+    margin-top: .6rem;
+    border-top: 1px solid #e6e6e6;
+}
+
+.weixin-course-name {
+    font-size: 1.1rem;
+    font-weight: bold;
+    color: #333;
+    margin-top: .6rem;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    margin-bottom: -0.3rem;
+}
+
+.weixin-ul-info {
+    font-size: .9rem;
+    margin-top: .6rem;
+    color: #333;
+}
+
+.weixin-info {
+    margin-top: .15rem;
+}
+
+.weixin-info-2 {
+    width: 40%;
+    float: left;
+}
+.weixin-info-3 {
+    width: 60%;
+    float: left;
+}
+
+.weixin-con {
+    font-size: .72rem;
+}
+
+.weixin-tips {
+    margin-left: auto;
+    margin-right: auto;
+    font-size: .72rem;
+    font-weight: bold;
+    color: white;
+    width: 80%;
+    margin-bottom: 1.2rem;
+}

+ 316 - 0
src/containers/weixin/index.vue

@@ -0,0 +1,316 @@
+<template>
+  <div class="weixin-body">
+
+    <div class="weixin-content-box">
+      <div class="weixin-title">{{media.name|| tpl.name}}</div>
+      <div class="weixin-content">
+        <video-player
+          id="myVideo1"
+          ref="videoPlayer"
+          :playsinline="true"
+          @pause="onPlayerPause($event)"
+          @timeupdate="onPlayerTimeupdate($event)"
+          @play="onPlayerStart($event)"
+          @ready="playerReadied"
+          @canplay="canpay($event)"
+          @ended="onPlayerEnded($event)"
+          :globalOptions="{controls:true}"
+          :options="options">
+        </video-player>
+        <div style="font-size: 16px;; margin: 10px auto;text-align: center;">
+          <span>{{curTimes|useTime}}</span>
+          <strong>/</strong>
+          <span>{{media.duration|useTime}}</span>
+          <span v-if="media.isFinish==1" style="color: green;">已完成</span>
+        </div>
+
+      </div>
+    </div>
+
+    <!-- <div  style="width: 100%; margin: 0px auto;"> -->
+    <div class="weixin-content-box" style="padding: 4px;">
+      <div class="weixin-content" v-for="item in list"  v-if="item.isFinish==0">
+        <div class="weixin-course-name">
+          <el-button @click="loadMedia(item)" type="text">{{item.name}} </el-button>
+        </div>
+        <ul class="weixin-ul-info clear">
+          <li class="weixin-info">
+            <span>学时:</span>
+            <span>{{item.xs/10}}</span>
+          </li>
+          <li class="weixin-info">
+            <span>学习进度:</span>
+            <span>{{(item.position*100/item.duration).toFixed(2)}}%</span>
+          </li>
+        </ul>
+      </div>
+    </div>
+
+  </div>
+</template>
+<script>
+  import { mapGetters,mapActions } from "vuex";
+  import {getPercent} from '@/utils/index.js'
+  import {
+    videoPlayer
+  } from 'vue-video-player';
+  // import 'video.js/dist/video-js.css'
+  import {
+    httpServer
+  } from "@/components/httpServer/httpServer.js";
+  export default {
+    data() {
+      return {
+        id: '',
+        media:{},
+        options:{
+          controls:true,
+          autoplay: true, // 如果true,浏览器准备好时开始回放。
+          muted: true, // 默认情况下将会消除任何音频。
+          loop: false, // 导致视频一结束就重新开始。
+          preload: "auto", // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
+          language: 'zh-CN',
+          aspectRatio: '4:3', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
+          fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
+          sources: [],
+          poster: '', // 你的封面地址
+          notSupportedMessage: '无法播放媒体源', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
+          playtimes: '',
+          controlBar: {
+            timeDivider: true,
+            durationDisplay: true,
+            remainingTimeDisplay: false,
+            fullscreenToggle: true  //全屏按钮
+          }
+        },
+        curTimes:0,
+        tpl:{},
+        info: {},
+        list: [],
+        timer: false,
+        tickNum: 0,
+        prevTime: 0,
+        isReady: false,
+        onPlay: false
+      };
+    },
+    components: {
+      videoPlayer
+    },
+    filters:{
+      useTime(val) {
+        val = parseInt(val)||0
+        let timestr = ""
+        let hour = parseInt(val / 3600);
+        let min = parseInt(val / 60 % 60);
+        let sec = parseInt(val % 60);
+        if (hour < 10) hour = "0" + hour;
+        if (min < 10) min = "0" + min;
+        if (sec < 10) sec = "0" + sec;
+        return hour + ":" + min + ":" + sec
+      }
+    },
+    methods: {
+       ...mapActions("user", ["loadBaseInfo"]),
+      getCourse() {
+        let param = { courseId: this.courseId }
+        httpServer("course.getCourse", param).then(res => {
+          if (res.code == 200) {
+            let {info, extra, list, tpl, extraXs, examList} = res.data;
+            this.info =  Object.assign( info, extra||{});
+            this.tpl = tpl||{};
+            this.list = list.map( (item)=>{
+              item.percent = getPercent(item)||0;
+              return item;
+            })
+            for( let i in this,list){
+              if( this.list[i].mediaId == this.userInfo.mediaId){
+                this.loadMedia( this.list[i])
+              }
+            }
+          }
+        })
+      },
+      startTick() {
+        let tick = this.tryTick;
+        if (this.timer) clearInterval(this.timer);
+        this.timer = setInterval(tick, 5 * 1000);
+      },
+      stopTick() {
+        if (this.timer) clearInterval(this.timer);
+      },
+      tryTick() {
+        let that = this;
+        try {
+          that.tick()
+        } catch (err) {
+          that.reportErr("play", '' + err.message)
+        }
+      },
+      playerReadied(audio) {
+        this.isReady = true
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.play()
+      },
+      onPlayerTimeupdate(player) {
+        let myPlayer = this.$refs.videoPlayer.player;
+        let curTimes = player.cache_.currentTime;
+        this.curTimes = curTimes || 0
+        if (this.media.isFinish) {
+          return;
+        }
+      },
+      canpay(player){
+        console.log("player", this.media.position )
+        if( this.media.position > this.curTimes){
+          player.currentTime(this.media.position+3);
+          setTimeout( ()=>{player.play()}, 100)
+
+        }
+      },
+      setposition(position) {
+        if (position > this.media.duration) position = this.media.duration;
+        let player = this.$refs.videoPlayer.player;
+        player.currentTime(position);
+        player.play()
+        if (this.media.isFinish) return;
+        if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
+          this.tick(true)
+        }
+      },
+      onPlayerPause(event) {
+        this.reportErr("play", 'pause');
+        this.stopTick()
+        this.onPlay = false
+      },
+      onPlayerEnded(event) {
+        this.reportErr("play", 'end');
+        this.tick(true)
+      },
+      onClose() {
+        this.reportErr("play", 'close')
+        this.doPause()
+        this.$emit("close")
+      },
+      doPause() {
+        // console.log("doPause")
+        this.stopTick()
+        this.onPlay = false
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.pause()
+      },
+      doPlay() {
+        console.log("doPlay")
+        this.onPlay = true
+        this.startTick();
+        if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
+        if (!this.dialog) return this.doPause();
+        let myPlayer = this.$refs.videoPlayer.player;
+        myPlayer && myPlayer.play()
+        this.tickNum = 0
+      },
+      onPlayerStart() {
+        console.log("onPlayerStart")
+        this.reportErr("play", 'start');
+        this.startTick();
+        this.onPlay = true
+      },
+      reportErr(action, msg) {
+        httpServer("course.report", {
+          action,
+          msg
+        })
+      },
+      tick(force = false) {
+        let media = this.media;
+        this.tickNum++
+        if (this.media.isFinish) {
+          console.log("finish")
+          return;
+        }
+        // 主动暂停
+        let myPlayer = this.$refs.videoPlayer.player;
+        let curTimes = parseInt(myPlayer.currentTime());
+        // 后退无心跳
+        if (this.media.position > curTimes && !force) return;
+        if (curTimes < 4) {
+          console.log("curTimes")
+          return;
+        }
+
+        let isFinish = force ? 1 : 0
+        if (curTimes >= media.duration) isFinish = 1;
+        //  拉到后面
+        if (!isFinish) {
+          if (!this.onPlay) return;
+        }
+        if(window.navigator.webdriver){
+          this.reportErr("play", 'webdriver');
+          this.doPause()
+          return
+        }
+        // 强制完成
+        let param = {
+          id: media.id,
+          position: curTimes,
+          isFinish
+        };
+        httpServer("course.tick", param, true).then(res => {
+          if (res.code == 200) {
+            let {
+              skip,
+              position,
+              pause,
+              closed
+            } = res.data
+            if (pause || closed) {
+              this.doPause();
+              this.$emit("close")
+              if (closed) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
+              } else if (pause) {
+                this.$message.errorMsg("禁止多处同时学习", 5);
+              }
+              return
+            }
+            if (!skip) this.setposition(position);
+            Object.assign(this.media, res.data)
+          }
+        })
+      },
+      loadMedia( item ) {
+        this.media = item;
+        httpServer('course.GetMedia', {id:item.id}).then( res => {
+          if( res.code != 200) return;
+          let {mediaUrl, id, position, marks,duration} = res.data||{};
+          this.mediaUrl = res.data.mediaUrl;
+          this.media.position = position;
+          this.media.duration = duration;
+          this.media.id = id;
+          this.options.sources = [{src:this.mediaUrl,type: "application/x-mpegURL"}];
+          this.options.playtimes = position||1;
+          this.options.autoplay  = position>0;
+          this.curTimes = 0
+          this.$refs.player && this.$refs.player.preload()
+        });
+      }
+    },
+    created() {
+      let {uid,token,courseId, mediaId} = this.$route.query;
+      this.userInfo = {uid, token, courseId, mediaId};
+      this.courseId = +courseId
+      if(uid) localStorage.uid = uid
+      if(token) localStorage.token = token
+      if (uid && token && courseId ) {
+        this.getCourse();
+      }
+      document.addEventListener('touchmove', function (event) {
+        console.log('touchmove')
+          event.preventDefault();
+      }, { passive: false });
+    }
+  }
+</script>
+<style>
+  @import url("./index.css");
+</style>

+ 1 - 0
src/main.js

@@ -7,6 +7,7 @@ import ElementUI from 'element-ui'
 import 'element-ui/lib/theme-chalk/index.css'
 import './assets/css/base.css'
 import './assets/css/main.css'
+import './assets/css/mobile.css'
 import { store } from '@/store/index'
 import md5 from 'js-md5';
 Vue.prototype.$md5 = md5;

+ 39 - 4
src/router/index.js

@@ -9,7 +9,7 @@ import main from '@/containers/main/main'
 import center from '@/containers/center/center'
 
 import Play from '@/containers/center/play/index.vue'
-
+import PlayMedia from '@/containers/center/play/play.vue'
 import Index from '@/containers/main/index/index'
 import Profile from '@/containers/main/profile/profile'
 
@@ -31,7 +31,9 @@ import CenterMyNotes from '@/containers/center/myNotes/index'
 import CenterMyOrder from '@/containers/center/myOrder/index'
 import CenterMessage from '@/containers/center/message/index'
 import CenterSign from '@/containers/center/sign/index'
+import PublicApply from '@/containers/apply/index'
 import PublicSign from '@/containers/sign/index'
+import PublicWeixin from '@/containers/weixin/index'
 
 import CenterMarket from '@/containers/center/market/index'
 import CenterTrainMarket from '@/containers/center/market/trainIndex'
@@ -41,6 +43,9 @@ import CenterExamScore from '@/containers/center/exam/score'
 import CenterExamDownload from '@/containers/center/exam/download'
 
 import CenterUserSeat from '@/containers/center/userSeat/index'
+import CenterUserCert from '@/containers/center/userCert/index'
+import PublicCertificate from '@/containers/certificate/index'
+import FjCertificate from '@/containers/certificate/fjCertificate'
 
 Vue.use(Router)
 
@@ -48,6 +53,9 @@ export default new Router({
   routes: [{
       path: '/',
       name: 'login',
+      meta:{
+        keepAlive: true,
+      },
       component: login,
     },
     {
@@ -55,11 +63,31 @@ export default new Router({
       name: 'register',
       component: Register,
     },
+    {
+      path: '/nd-sign',
+      name: 'nd-sign',
+      component: PublicCertificate,
+    },
+    {
+      path: '/fj-certificate',
+      name: 'fj-certificate',
+      component: FjCertificate,
+    },
     {
       path: '/sign',
       name: 'sign',
       component: PublicSign,
     },
+    {
+      path: '/apply',
+      name: 'apply',
+      component: PublicApply,
+    },
+    {
+      path: '/weixin',
+      name: 'weixin',
+      component: PublicWeixin,
+    },
     {
       path: '/news',
       name: 'news',
@@ -70,8 +98,11 @@ export default new Router({
       name: 'reset',
       component: Reset,
     },
-
-
+    {
+      path: '/playMedia/:courseId',
+      name: 'playMedia',
+      component: PlayMedia,
+    },
     {
       path: '/main',
       name: 'main',
@@ -80,7 +111,6 @@ export default new Router({
           path: 'index',
           component: Index,
         },
-
         {
           path: 'about',
           component: AboutMe,
@@ -133,6 +163,7 @@ export default new Router({
           name: 'play',
           component: Play,
         },
+
         {
           path: 'studyArchives',
           component: CenterStudyArchives,
@@ -184,6 +215,10 @@ export default new Router({
         {
           path: 'examDownload',
           component: CenterExamDownload,
+        },
+        {
+          path: 'userCert',
+          component: CenterUserCert
         }
       ],
     }

+ 3 - 2
src/setting.js

@@ -8,7 +8,8 @@ var examList  = {
   };
 
   module.exports = {
-    courseName: "检测试验人员",
-    noticeMsg:'提示:工程检测试验员新培人员需在5个工作日内完成40学时培训,三类、八大员、特种人员课程学习时间80天内完成',
+    courseName: "工程检测试验员",
+    noticeMsg:'提示:安全生产管理人员/建筑施工特种作业人员/企业安全生产培训 学习时间90天内完成',
+    noticeMsgTrain:'',
     examList
   }

+ 0 - 2
src/settings.js

@@ -1,6 +1,4 @@
 module.exports = {
-  title: '建设从业人员继续教育系统服务平台'
-  ,beian:'闽ICP备2121760号-1'
   ,exam:{
     username:'福建省爱数信息技术有限公司'
     ,bankName: '中国银行厦门市分行'

+ 12 - 1
src/store/module/user.js

@@ -13,15 +13,20 @@ export const user = {
     },
     notices: {},
     typeList: [],
+    promiseList: [],
     token: "",
   },
   mutations: {
     setUserInfo(state, userInfo) {
       state.userInfo = userInfo
+      console.log("setUserInfo", userInfo)
     },
     setNotice(state, courseId) {
       state.notices[courseId] = true;
     },
+    setPromiseList(state, list){
+      state.promiseList = list;
+    },
     setToken(state, token) {
       state.token = token
     },
@@ -42,6 +47,7 @@ export const user = {
       state.userInfo = { ...state.userInfo,
         ...userInfo
       }
+      console.log("ResetUserInfo", userInfo)
     }
   },
   actions: {
@@ -51,6 +57,7 @@ export const user = {
     }) {
       let res = await httpServer("User.baseInfo", {})
       if (res.code != 0 && res.code != 200) {
+        console.log("errLogout", res.code, res.msg)
         commit('LoginOut', {})
         return false
       }
@@ -68,6 +75,8 @@ export const user = {
       commit('setTypeList', res.data || [])
       return true
     },
+
+
     async saveBaseInfo({
       commit
     }, userInfo) {
@@ -88,7 +97,6 @@ export const user = {
     async doLogin({
       commit
     }, param) {
-      console.log("loginStart", res)
       let res = await httpServer("Auth.LoginStudy", param)
       if (res.code == 200) {
         commit('setUserInfo', res.data)
@@ -109,6 +117,9 @@ export const user = {
     typeList(state) {
       return state.typeList
     },
+    promiseList(state){
+      return state.promiseList
+    },
     token(state) {
       return state.token
     },

+ 897 - 0
src/tailwind.css

@@ -0,0 +1,897 @@
+/*
+! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  /* 1 */
+  border-width: 0;
+  /* 2 */
+  border-style: solid;
+  /* 2 */
+  border-color: #e5e7eb;
+  /* 2 */
+}
+
+::before,
+::after {
+  --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+*/
+
+html {
+  line-height: 1.5;
+  /* 1 */
+  -webkit-text-size-adjust: 100%;
+  /* 2 */
+  -moz-tab-size: 4;
+  /* 3 */
+  -o-tab-size: 4;
+     tab-size: 4;
+  /* 3 */
+  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  /* 4 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+  margin: 0;
+  /* 1 */
+  line-height: inherit;
+  /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+  height: 0;
+  /* 1 */
+  color: inherit;
+  /* 2 */
+  border-top-width: 1px;
+  /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+  -webkit-text-decoration: underline dotted;
+          text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-size: inherit;
+  font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+  color: inherit;
+  text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font family by default.
+2. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  /* 1 */
+  font-size: 1em;
+  /* 2 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+  font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+  text-indent: 0;
+  /* 1 */
+  border-color: inherit;
+  /* 2 */
+  border-collapse: collapse;
+  /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit;
+  /* 1 */
+  font-size: 100%;
+  /* 1 */
+  line-height: inherit;
+  /* 1 */
+  color: inherit;
+  /* 1 */
+  margin: 0;
+  /* 2 */
+  padding: 0;
+  /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+  text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+  -webkit-appearance: button;
+  /* 1 */
+  background-color: transparent;
+  /* 2 */
+  background-image: none;
+  /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+  outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+  box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+  vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+  -webkit-appearance: textfield;
+  /* 1 */
+  outline-offset: -2px;
+  /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button;
+  /* 1 */
+  font: inherit;
+  /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+  display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+  margin: 0;
+}
+
+fieldset {
+  margin: 0;
+  padding: 0;
+}
+
+legend {
+  padding: 0;
+}
+
+ol,
+ul,
+menu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+  resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+input::-moz-placeholder,
+textarea::-moz-placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+input::-ms-input-placeholder,
+textarea::-ms-input-placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+  opacity: 1;
+  /* 1 */
+  color: #9ca3af;
+  /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+  cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+  cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+   This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+  display: block;
+  /* 1 */
+  vertical-align: middle;
+  /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+  max-width: 100%;
+  height: auto;
+}
+
+/*
+Ensure the default browser behavior of the `hidden` attribute.
+*/
+
+[hidden] {
+  display: none;
+}
+
+*, ::before, ::after {
+  --tw-translate-x: 0;
+  --tw-translate-y: 0;
+  --tw-rotate: 0;
+  --tw-skew-x: 0;
+  --tw-skew-y: 0;
+  --tw-scale-x: 1;
+  --tw-scale-y: 1;
+  --tw-pan-x:  ;
+  --tw-pan-y:  ;
+  --tw-pinch-zoom:  ;
+  --tw-scroll-snap-strictness: proximity;
+  --tw-ordinal:  ;
+  --tw-slashed-zero:  ;
+  --tw-numeric-figure:  ;
+  --tw-numeric-spacing:  ;
+  --tw-numeric-fraction:  ;
+  --tw-ring-inset:  ;
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgb(59 130 246 / 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+  --tw-shadow: 0 0 #0000;
+  --tw-shadow-colored: 0 0 #0000;
+  --tw-blur:  ;
+  --tw-brightness:  ;
+  --tw-contrast:  ;
+  --tw-grayscale:  ;
+  --tw-hue-rotate:  ;
+  --tw-invert:  ;
+  --tw-saturate:  ;
+  --tw-sepia:  ;
+  --tw-drop-shadow:  ;
+  --tw-backdrop-blur:  ;
+  --tw-backdrop-brightness:  ;
+  --tw-backdrop-contrast:  ;
+  --tw-backdrop-grayscale:  ;
+  --tw-backdrop-hue-rotate:  ;
+  --tw-backdrop-invert:  ;
+  --tw-backdrop-opacity:  ;
+  --tw-backdrop-saturate:  ;
+  --tw-backdrop-sepia:  ;
+}
+
+.visible {
+  visibility: visible;
+}
+
+.fixed {
+  position: fixed;
+}
+
+.absolute {
+  position: absolute;
+}
+
+.relative {
+  position: relative;
+}
+
+.left-1 {
+  left: 0.25rem;
+}
+
+.top-1 {
+  top: 0.25rem;
+}
+
+.left-2 {
+  left: 0.5rem;
+}
+
+.top-2 {
+  top: 0.5rem;
+}
+.top-4 {
+  top: 1rem;
+}
+
+.right-2 {
+  right: 0.5rem;
+}
+
+.m-auto {
+  margin: auto;
+}
+
+.my-2 {
+  margin-top: 0.5rem;
+  margin-bottom: 0.5rem;
+}
+
+.my-4 {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+}
+
+.my-16 {
+  margin-top: 4rem;
+  margin-bottom: 4rem;
+}
+
+.my-12 {
+  margin-top: 3rem;
+  margin-bottom: 3rem;
+}
+
+.mr-6 {
+  margin-right: 1.5rem;
+}
+
+.mb-4 {
+  margin-bottom: 1rem;
+}
+
+.mr-2 {
+  margin-right: 0.5rem;
+}
+
+.mb-6 {
+  margin-bottom: 1.5rem;
+}
+
+.mt-4 {
+  margin-top: 1rem;
+}
+
+.inline-block {
+  display: inline-block;
+}
+
+.flex {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+}
+
+.h-72 {
+  height: 18rem;
+}
+
+.h-32 {
+  height: 8rem;
+}
+
+.h-56 {
+  height: 14rem;
+}
+
+.h-48 {
+  height: 12rem;
+}
+
+.w-full {
+  width: 100%;
+}
+
+.w-2\/4 {
+  width: 50%;
+}
+
+.w-32 {
+  width: 8rem;
+}
+
+.w-24 {
+  width: 6rem;
+}
+
+.w-auto {
+  width: auto;
+}
+
+.w-1\/2 {
+  width: 50%;
+}
+
+.flex-auto {
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+}
+
+.flex-none {
+  -webkit-box-flex: 0;
+      -ms-flex: none;
+          flex: none;
+}
+
+.transform {
+  -webkit-transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+          transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.cursor-pointer {
+  cursor: pointer;
+}
+
+.items-center {
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+}
+
+.justify-center {
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+}
+
+.justify-between {
+  -webkit-box-pack: justify;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+
+.overflow-hidden {
+  overflow: hidden;
+}
+
+.rounded-xl {
+  border-radius: 0.75rem;
+}
+
+.rounded {
+  border-radius: 0.25rem;
+}
+
+.border {
+  border-width: 1px;
+}
+
+.border-b {
+  border-bottom-width: 1px;
+}
+
+.border-gray-200 {
+  --tw-border-opacity: 1;
+  border-color: rgb(229 231 235 / var(--tw-border-opacity));
+}
+
+.bg-white {
+  --tw-bg-opacity: 1;
+  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
+}
+
+.bg-blue {
+  --tw-bg-opacity: 1;
+  background-color: rgb(56 118 218 / var(--tw-bg-opacity));
+}
+
+.object-cover {
+  -o-object-fit: cover;
+     object-fit: cover;
+}
+
+.p-4 {
+  padding: 1rem;
+}
+
+.p-2 {
+  padding: 0.5rem;
+}
+
+.px-6 {
+  padding-left: 1.5rem;
+  padding-right: 1.5rem;
+}
+
+.py-2 {
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+}
+.py-4 {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.px-4 {
+  padding-left: 1rem;
+  padding-right: 1rem;
+}
+
+.py-6 {
+  padding-top: 1.5rem;
+  padding-bottom: 1.5rem;
+}
+
+.pt-6 {
+  padding-top: 1.5rem;
+}
+
+.pb-2 {
+  padding-bottom: 0.5rem;
+}
+
+.pb-4 {
+  padding-bottom: 1rem;
+}
+
+.pb-6 {
+  padding-bottom: 1.5rem;
+}
+
+.pb-8 {
+  padding-bottom: 2rem;
+}
+
+.pr-2 {
+  padding-right: 0.5rem;
+}
+
+.pr-4 {
+  padding-right: 1rem;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-xl {
+  font-size: 1.25rem;
+  line-height: 1.75rem;
+}
+
+.text-base {
+  font-size: 1rem;
+  line-height: 1.5rem;
+}
+
+.text-xs {
+  font-size: 0.75rem;
+  line-height: 1rem;
+}
+
+.text-2xl {
+  font-size: 1.5rem;
+  line-height: 2rem;
+}
+
+.text-lg {
+  font-size: 1.125rem;
+  line-height: 1.75rem;
+}
+
+.text-sm {
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+}
+
+.leading-10 {
+  line-height: 2.5rem;
+}
+
+.text-blue {
+  --tw-text-opacity: 1;
+  color: rgb(56 118 218 / var(--tw-text-opacity));
+}
+
+.text-blue-dark {
+  --tw-text-opacity: 1;
+  color: rgb(18 96 221 / var(--tw-text-opacity));
+}
+
+.text-white {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.text-red-600 {
+  --tw-text-opacity: 1;
+  color: rgb(220 38 38 / var(--tw-text-opacity));
+}
+
+.text-yellow-600 {
+  --tw-text-opacity: 1;
+  color: rgb(202 138 4 / var(--tw-text-opacity));
+}
+
+.text-green-600 {
+  --tw-text-opacity: 1;
+  color: rgb(22 163 74 / var(--tw-text-opacity));
+}
+
+.text-green-700 {
+  --tw-text-opacity: 1;
+  color: rgb(21 128 61 / var(--tw-text-opacity));
+}
+
+.text-green-300 {
+  --tw-text-opacity: 1;
+  color: rgb(134 239 172 / var(--tw-text-opacity));
+}
+
+.text-yellow-300 {
+  --tw-text-opacity: 1;
+  color: rgb(253 224 71 / var(--tw-text-opacity));
+}
+
+.text-gray-300 {
+  --tw-text-opacity: 1;
+  color: rgb(209 213 219 / var(--tw-text-opacity));
+}
+
+.text-green-500 {
+  --tw-text-opacity: 1;
+  color: rgb(34 197 94 / var(--tw-text-opacity));
+}
+
+.text-red-300 {
+  --tw-text-opacity: 1;
+  color: rgb(252 165 165 / var(--tw-text-opacity));
+}
+
+.text-red-400 {
+  --tw-text-opacity: 1;
+  color: rgb(248 113 113 / var(--tw-text-opacity));
+}
+
+.shadow {
+  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+  -webkit-box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+          box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.blur {
+  --tw-blur: blur(8px);
+  -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+          filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.filter {
+  -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+          filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.transition {
+  -webkit-transition-property: color, background-color, border-color, fill, stroke, opacity, -webkit-text-decoration-color, -webkit-box-shadow, -webkit-transform, -webkit-filter, -webkit-backdrop-filter;
+  transition-property: color, background-color, border-color, fill, stroke, opacity, -webkit-text-decoration-color, -webkit-box-shadow, -webkit-transform, -webkit-filter, -webkit-backdrop-filter;
+  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-box-shadow, -webkit-transform, -webkit-filter, -webkit-backdrop-filter;
+  -webkit-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+          transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  -webkit-transition-duration: 150ms;
+          transition-duration: 150ms;
+}
+
+.hover\:text-blue-light:hover {
+  --tw-text-opacity: 1;
+  color: rgb(85 132 209 / var(--tw-text-opacity));
+}

+ 4 - 0
src/utils/date.js

@@ -21,6 +21,10 @@ export function curDate( ) {
     return d.toLocaleString();
 }
 
+export function curDatetime( ) {
+    var d = new Date().Format("yyyy-MM-dd hh:mm:ss");
+    return d.toLocaleString();
+}
 export function formatTimeToStr(times, pattern) {
     var d = new Date(times).Format("yyyy-MM-dd hh:mm:ss");
     if (pattern) {

+ 1 - 1
src/utils/index.js

@@ -100,7 +100,7 @@ export function parseTime(time, cFormat) {
 
 
 export function getPercent( item ){
-  if( item.isFinish ) return  100;
+  if( item.isFinish == 1 ) return  100;
   let percent = parseInt(item.position * 10000 / item.duration) / 100;
   if( percent >= 100) return 99.99;
   return percent

+ 0 - 2
src/utils/plugins.js

@@ -7,8 +7,6 @@ const preventReClick = Vue.directive('preventReClick', {
                 setTimeout(() => {
                     el.disabled = false
                 }, binding.value || 1000)
-            }else{
-              alert("点击太快")
             }
         })
     }

+ 8 - 0
static/config.js

@@ -0,0 +1,8 @@
+window.baseConfig = {
+    title: '宁德市建筑业协会建设行业从业人员继续教育网络培训平台',
+    userSeatTitle:'福建省建设行业从业人员岗位考核考试',
+    it:'福建鸿锵教育科技有限公司',
+    beian:'闽ICP备2021006867号-1',
+    contact:'0598-5157296',
+    educationList: ["博士研究生", "硕士研究生", "大学本科", "大学专科", "中专", "高中", "初中及以下", "其他"]
+}

TEMPAT SAMPAH
static/favicon.ico


TEMPAT SAMPAH
static/images/friendlink/link1.png


TEMPAT SAMPAH
static/images/friendlink/link5.png


TEMPAT SAMPAH
static/images/friendlink/三明市住房和城乡建设局.jpg


TEMPAT SAMPAH
static/images/friendlink/三明市住房和城乡建设行政审批系统.jpg


TEMPAT SAMPAH
static/images/friendlink/三明市建设人才服务中心.jpg


TEMPAT SAMPAH
static/images/friendlink/福建省建设从业人员综合服务平台.jpg


TEMPAT SAMPAH
static/images/header-bg-1.jpg


TEMPAT SAMPAH
static/images/header-bg.jpg.bak.jpg


TEMPAT SAMPAH
static/images/login-banner-1.jpg


TEMPAT SAMPAH
static/images/login-banner.jpg


TEMPAT SAMPAH
static/images/wxpay.jpg


+ 0 - 30
static/index.html

@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-  <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0">
-  <meta http-equiv="X-UA-Compatible" content="ie=edge">
-  <meta http-equiv="X-UA-Compatible" content="IE=9;IE=EDGE">
-  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
-  <title>学习登入</title>
-  <link href=./static/css/app.2ae1e83255747909b76174af1ef6c241.css rel=stylesheet>
-</head>
-
-<body style=margin:0px;>
-  <div id="img_float" style="z-index: 101; left: 134px; width: 242px; height: 141px; position: absolute; top: 577px; visibility: visible; cursor: pointer;">
-    <div align="right" onclick="document.getElementById('img_float').style.display='none'"
-      style="color:red;font-size:12px;margin-bottom:0px;text-decoration:none;position:relative">[关闭]</div>
-    <div align="center" id="wmdImg">
-      <a href="http://zjj.sm.gov.cn/pc/201808/t20180829_1195705.htm" target="_blank">
-        <img id="wmdImgId1" class="wmdImg" width="242" height="141" src="./images/W020180829573455831351.jpg">
-      </a>
-    </div>
-  </div>
-  <div id=app></div>
-  <script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script>
-  <script type=text/javascript src=./static/js/vendor.ed36f80b48754d8be4be.js></script>
-  <script type=text/javascript src=./static/js/app.94f85552b9823df5c616.js></script>
-</body>
-
-</html>

+ 1 - 1
sync.bat

@@ -1,2 +1,2 @@
-go run build.go -dir=study_sm
+go run build.go -dir=study_nd
 pause

+ 18 - 0
tailwind.config.js

@@ -0,0 +1,18 @@
+module.exports = {
+  content: [
+    './src/**/*.vue',
+    './public/**/*.html'
+  ],
+  theme: {
+    extend: {
+      colors: {
+        blue: {
+          light: '#5584d1',
+          DEFAULT: '#3876da',
+          dark: '#1260dd',
+        },
+      }
+    },
+  },
+  plugins: [],
+}