Pārlūkot izejas kodu

Merge branch 'dev' of http://git.dayaedu.com/huangqiyong/classroom

黄琪勇 1 gadu atpakaļ
vecāks
revīzija
8a9ecb042e
70 mainītis faili ar 1013 papildinājumiem un 223 dzēšanām
  1. 4 0
      .env
  2. 6 0
      .env.development
  3. 6 0
      .env.production
  4. 0 0
      dist/css/496.e7f18f59.css
  5. 0 0
      dist/css/793.3bfc4ef0.css
  6. 0 0
      dist/css/app.02f6d1b5.css
  7. 0 0
      dist/css/app.3383937a.css
  8. BIN
      dist/img/bg.fab2a508.png
  9. BIN
      dist/img/mac1.9a91a426.png
  10. BIN
      dist/img/mac2.bda00939.png
  11. BIN
      dist/img/mac3.eff620bf.png
  12. BIN
      dist/img/mac3_1.da06d347.png
  13. BIN
      dist/img/mac4.2c3e2ff9.png
  14. BIN
      dist/img/mac5.4bfe6ee3.png
  15. BIN
      dist/img/mac6.5adee654.png
  16. BIN
      dist/img/win1.0baf3b5b.png
  17. BIN
      dist/img/win2.58c9cdfe.png
  18. BIN
      dist/img/win3.a5d3e545.png
  19. BIN
      dist/img/win4.d1a4a85c.png
  20. BIN
      dist/img/win5.5230f16c.png
  21. BIN
      dist/img/win6.1ff80fb5.png
  22. BIN
      dist/img/win7.cc03b1d8.png
  23. BIN
      dist/img/win8.837c0b40.png
  24. 4 0
      dist/index.html
  25. 0 0
      dist/js/25.a69ca377.js
  26. 0 0
      dist/js/496.f98c97d7.js
  27. 0 0
      dist/js/715.8c3b9268.js
  28. 0 0
      dist/js/74.740d259f.js
  29. 0 0
      dist/js/793.f17126f7.js
  30. 0 0
      dist/js/994.59104bb0.js
  31. 0 0
      dist/js/app.d35674f7.js
  32. 0 0
      dist/js/chunk-vendors.f67b6673.js
  33. 1 0
      package.json
  34. 7 0
      src/api/user.api.ts
  35. 4 1
      src/assets/index.scss
  36. 0 1
      src/assets/normalize.css
  37. 9 0
      src/config/index.ts
  38. BIN
      src/hooks/useSecureAnth/img/bg.png
  39. BIN
      src/hooks/useSecureAnth/img/btn.png
  40. BIN
      src/hooks/useSecureAnth/img/mac1.png
  41. BIN
      src/hooks/useSecureAnth/img/mac2.png
  42. BIN
      src/hooks/useSecureAnth/img/mac3.png
  43. BIN
      src/hooks/useSecureAnth/img/mac3_1.png
  44. BIN
      src/hooks/useSecureAnth/img/mac4.png
  45. BIN
      src/hooks/useSecureAnth/img/mac5.png
  46. BIN
      src/hooks/useSecureAnth/img/mac6.png
  47. BIN
      src/hooks/useSecureAnth/img/star.png
  48. BIN
      src/hooks/useSecureAnth/img/win1.png
  49. BIN
      src/hooks/useSecureAnth/img/win2.png
  50. BIN
      src/hooks/useSecureAnth/img/win3.png
  51. BIN
      src/hooks/useSecureAnth/img/win4.png
  52. BIN
      src/hooks/useSecureAnth/img/win5.png
  53. BIN
      src/hooks/useSecureAnth/img/win6.png
  54. BIN
      src/hooks/useSecureAnth/img/win7.png
  55. BIN
      src/hooks/useSecureAnth/img/win8.png
  56. BIN
      src/hooks/useSecureAnth/img/xiazai.png
  57. 30 0
      src/hooks/useSecureAnth/index.ts
  58. 427 0
      src/hooks/useSecureAnth/secureAnth.vue
  59. BIN
      src/img/coursewarePlay/ts1.png
  60. BIN
      src/img/coursewarePlay/ts2.png
  61. 13 0
      src/libs/axios.ts
  62. 1 1
      src/plugin/loadingBar/loadingBar.vue
  63. 1 1
      src/plugin/modalFrame/modalFrame.scss
  64. 16 2
      src/views/coursewarePlay/components/pen/pen.vue
  65. 3 3
      src/views/coursewarePlay/components/playRecordTime/playRecordTime.vue
  66. 204 110
      src/views/coursewarePlay/coursewarePlay.vue
  67. 113 102
      src/views/coursewarePlay/videoPlay/videoPlay.vue
  68. 17 1
      src/viewsframe/login/login.vue
  69. 1 1
      tsconfig.json
  70. 146 0
      yarn.lock

+ 4 - 0
.env

@@ -1,3 +1,7 @@
 # 公共环境变量
 
 VUE_APP_TITLE="乐教通"
+#安全证书
+VUE_APP_MAC_GYM_SECUREANTH="https://oss.dayaedu.com/https-ssl/gym/安全证书.p12"  #密码  dayaedu.com
+VUE_APP_MAC_GYT_SECUREANTH="https://oss.dayaedu.com/https-ssl/gyt/安全证书.p12"  #密码  lexiaoya.cn
+VUE_APP_WIN_SECUREANTH="https://oss.dayaedu.com/https-ssl/安全证书.pfx"

+ 6 - 0
.env.development

@@ -6,3 +6,9 @@ VUE_APP_URL_GYT = "/gyt"
 
 # 标注画板地址
 VUE_APP_WHITEBOARD = "https://test.lexiaoya.cn/whiteboard-noCollab"
+
+#管乐团 云教练
+VUE_APP_TEACH_GYT = "https://test.lexiaoya.cn/orchestra-music-score/"
+
+#管乐迷 云教练
+VUE_APP_TEACH_GYM ="https://mantest.dayaedu.com/accompany/"

+ 6 - 0
.env.production

@@ -6,3 +6,9 @@ VUE_APP_URL_GYT = "https://online.lexiaoya.cn"
 
 # 标注画板地址
 VUE_APP_WHITEBOARD = "https://online.lexiaoya.cn/whiteboard-noCollab"
+
+#管乐团 云教练
+VUE_APP_TEACH_GYT = "https://online.lexiaoya.cn/orchestra-music-score/"
+
+#管乐迷 云教练
+VUE_APP_TEACH_GYM ="https://online.dayaedu.com/accompany/"

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/css/496.e7f18f59.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/css/793.3bfc4ef0.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/css/app.02f6d1b5.css


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/css/app.3383937a.css


BIN
dist/img/bg.fab2a508.png


BIN
dist/img/mac1.9a91a426.png


BIN
dist/img/mac2.bda00939.png


BIN
dist/img/mac3.eff620bf.png


BIN
dist/img/mac3_1.da06d347.png


BIN
dist/img/mac4.2c3e2ff9.png


BIN
dist/img/mac5.4bfe6ee3.png


BIN
dist/img/mac6.5adee654.png


BIN
dist/img/win1.0baf3b5b.png


BIN
dist/img/win2.58c9cdfe.png


BIN
dist/img/win3.a5d3e545.png


BIN
dist/img/win4.d1a4a85c.png


BIN
dist/img/win5.5230f16c.png


BIN
dist/img/win6.1ff80fb5.png


BIN
dist/img/win7.cc03b1d8.png


BIN
dist/img/win8.837c0b40.png


+ 4 - 0
dist/index.html

@@ -1,6 +1,10 @@
 <!doctype html><html lang=""><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="renderer" content="webkit"/><meta name="force-rendering" content="webkit"/><link rel="icon" href="/favicon.ico"/><title>乐教通</title><script>if (!!window.ActiveXObject || "ActiveXObject" in window) {
             window.location.href = "./ieIncompatible/index.html"
+<<<<<<< HEAD
          }</script><script defer="defer" src="/js/chunk-vendors.8dce6083.js"></script><script defer="defer" src="/js/app.2b1b1581.js"></script><link href="/css/app.02f6d1b5.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but classroom doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><style>.firstLoading {
+=======
+         }</script><script defer="defer" src="/js/chunk-vendors.f67b6673.js"></script><script defer="defer" src="/js/app.d35674f7.js"></script><link href="/css/app.3383937a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but classroom doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"><style>.firstLoading {
+>>>>>>> f3661d409fe0d941518210203fb613cf506e0401
                position: fixed;
                left: 50%;
                top: 50%;

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/25.a69ca377.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/496.f98c97d7.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/715.8c3b9268.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/74.740d259f.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/793.f17126f7.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/994.59104bb0.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/app.d35674f7.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
dist/js/chunk-vendors.f67b6673.js


+ 1 - 0
package.json

@@ -16,6 +16,7 @@
       "element-plus": "^2.6.1",
       "js-cookie": "^3.0.5",
       "md5": "^2.3.0",
+      "naive-ui": "^2.38.1",
       "nprogress": "^0.2.0",
       "pinia": "^2.0.35",
       "qrcode.vue": "^3.4.1",

+ 7 - 0
src/api/user.api.ts

@@ -46,6 +46,13 @@ export const logout_gym = () => {
       }
    })
 }
+// 安全证书
+export const mutualTLSQuery_gym = () => {
+   return httpAxios_gym.axioseRquest({
+      method: "get",
+      url: "/api-auth/open/mutualTLS/query?isLogin=true" // 后面跟参数来区分是不是登录511
+   })
+}
 
 /** 管乐团*/
 

+ 4 - 1
src/assets/index.scss

@@ -7,7 +7,10 @@ body,
    width: 100%;
    height: 100%;
 }
-
+body {
+   font-size: 16px; // 防止rem时候body上面的字体太小,如果没有设置字体大小,可以应用16px
+   line-height: 1; // 清除naiveui行高
+}
 //<input type="number"> 去掉右侧上下按钮
 .inputNumNone {
    input::-webkit-outer-spin-button,

+ 0 - 1
src/assets/normalize.css

@@ -15,7 +15,6 @@ body {
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-family: PingFang SC, Helvetica Neue, Helvetica, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
-   font-size: 16px;
 }
 
 a:focus,

+ 9 - 0
src/config/index.ts

@@ -4,3 +4,12 @@ export const URL_API_GYM = process.env.VUE_APP_URL_GYM as string
 
 // 画板地址
 export const URL_WHITEBOARD = process.env.VUE_APP_WHITEBOARD as string
+
+// 安全证书地址
+export const URL_MAC_GYM_SECUREANTH = process.env.VUE_APP_MAC_GYM_SECUREANTH as string
+export const URL_MAC_GYT_SECUREANTH = process.env.VUE_APP_MAC_GYT_SECUREANTH as string
+export const URL_WIN_SECUREANTH = process.env.VUE_APP_WIN_SECUREANTH as string
+
+// 云教练地址
+export const URL_TEACH_GYT = process.env.VUE_APP_TEACH_GYT as string
+export const URL_TEACH_GYM = process.env.VUE_APP_TEACH_GYM as string

BIN
src/hooks/useSecureAnth/img/bg.png


BIN
src/hooks/useSecureAnth/img/btn.png


BIN
src/hooks/useSecureAnth/img/mac1.png


BIN
src/hooks/useSecureAnth/img/mac2.png


BIN
src/hooks/useSecureAnth/img/mac3.png


BIN
src/hooks/useSecureAnth/img/mac3_1.png


BIN
src/hooks/useSecureAnth/img/mac4.png


BIN
src/hooks/useSecureAnth/img/mac5.png


BIN
src/hooks/useSecureAnth/img/mac6.png


BIN
src/hooks/useSecureAnth/img/star.png


BIN
src/hooks/useSecureAnth/img/win1.png


BIN
src/hooks/useSecureAnth/img/win2.png


BIN
src/hooks/useSecureAnth/img/win3.png


BIN
src/hooks/useSecureAnth/img/win4.png


BIN
src/hooks/useSecureAnth/img/win5.png


BIN
src/hooks/useSecureAnth/img/win6.png


BIN
src/hooks/useSecureAnth/img/win7.png


BIN
src/hooks/useSecureAnth/img/win8.png


BIN
src/hooks/useSecureAnth/img/xiazai.png


+ 30 - 0
src/hooks/useSecureAnth/index.ts

@@ -0,0 +1,30 @@
+/*
+   安全证书
+ */
+
+import modalFrame from "@/plugin/modalFrame"
+import secureAnth from "./secureAnth.vue"
+
+/**
+ * @param obj
+ *  text :文字
+ *  btnShow:控制确认按钮显示
+ *  headImg:头部图片
+ */
+type objType = {
+   onCancel?: (...nargs: any[]) => void
+   onOk?: (...nargs: any[]) => void
+}
+export default (obj?: objType) => {
+   // 只弹窗一次
+   document.querySelector(".h-modalFrame.useSecureAnth") ||
+      modalFrame({
+         template: secureAnth,
+         width: 920,
+         height: 860,
+         btnShow: [],
+         className: "useSecureAnth",
+         onCancel: obj?.onCancel,
+         onOk: obj?.onCancel
+      })
+}

+ 427 - 0
src/hooks/useSecureAnth/secureAnth.vue

@@ -0,0 +1,427 @@
+<!--
+* @FileDescription: 安全证书
+* @Author: 黄琪勇
+* @Date:2024-04-09 14:01:56
+-->
+<template>
+   <div class="secureAnth">
+      <img class="star" src="./img/star.png" />
+      <ElScrollbar ref="elScrollbarDom" class="elScrollbar">
+         <template v-if="isMac">
+            <div class="stepCon" v-if="stepNum === 1">
+               <div class="titleBox">
+                  <div class="stepNum">01</div>
+                  <div class="titleCon">点击下方【下载证书】按钮,下载数据安全证书安装包</div>
+               </div>
+               <div class="contentBox">
+                  <img @click="handleDownload" class="xiazaiImg" src="./img/xiazai.png" />
+               </div>
+               <div class="titleBox">
+                  <div class="stepNum">02</div>
+                  <div class="titleCon">
+                     双击
+                     <span class="colorOne">《安全证书.p12》</span>
+                     ,输入钥匙串密码,点击
+                     <span class="colorOne">【修改钥匙串】</span>
+                     <span class="colorThree">(若无此步骤则忽略)</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="mac1Img" src="./img/mac1.png" />
+                  <img class="mac2Img" src="./img/mac2.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 2">
+               <div class="titleBox">
+                  <div class="stepNum">03</div>
+                  <div class="titleCon">
+                     输入证书密码:
+                     <span class="colorTwo">{{ userStoreHook.roles === "GYM" ? "dayaedu.com" : "lexiaoya.cn" }}</span>
+                     ,点击
+                     <span class="colorOne">【好】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="mac3Img" :src="userStoreHook.roles === 'GYM' ? require('./img/mac3_1.png') : require('./img/mac3.png')" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 3">
+               <div class="titleBox alignTop">
+                  <div class="stepNum">04</div>
+                  <div class="titleCon">
+                     <span class="colorTwo">重启浏览器</span>
+                     (在电脑屏幕左上方选择当前浏览器并点击
+                     <span class="colorOne">【退出】</span>
+                     ),再重新打开乐教通网址
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="mac4Img" src="./img/mac4.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 4">
+               <div class="titleBox">
+                  <div class="stepNum">05</div>
+                  <div class="titleCon">
+                     在【选择证书】弹窗中点击
+                     <span class="colorOne">【确定】</span>
+                     按钮
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="mac5Img" src="./img/mac5.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 5">
+               <div class="titleBox">
+                  <div class="stepNum">06</div>
+                  <div class="titleCon">
+                     输入您的电脑密码,点击
+                     <span class="colorOne">【始终允许】</span>
+                     <span class="colorThree">(若无此步骤则忽略)</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="mac6Img" src="./img/mac6.png" />
+               </div>
+               <div class="titleBox">
+                  <div class="stepNum">07</div>
+                  <div class="titleCon">证书安装完成,开始使用乐教通吧!</div>
+               </div>
+            </div>
+         </template>
+         <template v-else>
+            <div class="stepCon" v-if="stepNum === 1">
+               <div class="titleBox">
+                  <div class="stepNum">01</div>
+                  <div class="titleCon">点击下方【下载证书】按钮,下载数据安全证书安装包</div>
+               </div>
+               <div class="contentBox">
+                  <img @click="handleDownload" class="xiazaiImg" src="./img/xiazai.png" />
+               </div>
+               <div class="titleBox">
+                  <div class="stepNum">02</div>
+                  <div class="titleCon">
+                     双击
+                     <span class="colorOne">《安全证书.pfx》</span>
+                     ,出现弹窗后点击
+                     <span class="colorOne">【下一步】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win1Img" src="./img/win1.png" />
+                  <img class="win2Img" src="./img/win2.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 2">
+               <div class="titleBox">
+                  <div class="stepNum">03</div>
+                  <div class="titleCon">
+                     点击
+                     <span class="colorOne">【下一步】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win3Img" src="./img/win3.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 3">
+               <div class="titleBox">
+                  <div class="stepNum">04</div>
+                  <div class="titleCon">
+                     点击
+                     <span class="colorOne">【下一步】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win3Img" src="./img/win4.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 4">
+               <div class="titleBox">
+                  <div class="stepNum">05</div>
+                  <div class="titleCon">
+                     点击
+                     <span class="colorOne">【下一步】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win3Img" src="./img/win5.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 5">
+               <div class="titleBox">
+                  <div class="stepNum">06</div>
+                  <div class="titleCon">
+                     点击
+                     <span class="colorOne">【下一步】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win3Img" src="./img/win6.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 6">
+               <div class="titleBox">
+                  <div class="stepNum">07</div>
+                  <div class="titleCon">
+                     点击
+                     <span class="colorOne">【确定】</span>
+                  </div>
+               </div>
+               <div class="contentBox">
+                  <img class="win7Img" src="./img/win7.png" />
+               </div>
+            </div>
+            <div class="stepCon" v-if="stepNum === 7">
+               <div class="titleBox mb">
+                  <div class="stepNum">08</div>
+                  <div class="titleCon">
+                     <span class="colorTwo">重启浏览器</span>
+                     ,打开乐教通网址
+                  </div>
+               </div>
+               <div class="titleBox">
+                  <div class="stepNum">09</div>
+                  <div class="titleCon">在【选择证书】弹窗中点击【确定】按钮,证书安装完成,开始使用乐教通吧!</div>
+               </div>
+               <div class="contentBox">
+                  <img class="win8Img" src="./img/win8.png" />
+               </div>
+            </div>
+         </template>
+      </ElScrollbar>
+      <div class="stepBtn">
+         <div class="btn" v-if="stepNum > 1" @click="handleStep(-1)">上一步</div>
+         <div class="btn" v-if="stepNum <= maxStep" @click="handleStep(1)">{{ stepNum < maxStep ? "下一步" : "完成" }}</div>
+      </div>
+   </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue"
+import { ElScrollbar } from "element-plus"
+import { URL_MAC_GYM_SECUREANTH, URL_MAC_GYT_SECUREANTH, URL_WIN_SECUREANTH } from "@/config"
+import userStore from "@/store/modules/user"
+
+const userStoreHook = userStore()
+const emits = defineEmits<{
+   (e: "onCancel"): void
+}>()
+
+const isMac = (function () {
+   return /macintosh|mac os x/i.test(navigator.userAgent)
+})()
+
+const maxStep = isMac ? 5 : 7
+const stepNum = ref(1)
+const elScrollbarDom = ref<InstanceType<typeof ElScrollbar>>()
+
+// 下载证书
+function handleDownload() {
+   if (isMac) {
+      window.open(userStoreHook.roles === "GYM" ? URL_MAC_GYM_SECUREANTH : URL_MAC_GYT_SECUREANTH)
+   } else {
+      window.open(URL_WIN_SECUREANTH)
+   }
+}
+// 步骤
+function handleStep(num: -1 | 1) {
+   if (num === 1 && stepNum.value === maxStep) {
+      emits("onCancel")
+      return
+   }
+   if (num === -1) {
+      stepNum.value > 1 && (stepNum.value += num)
+   } else {
+      stepNum.value < maxStep && (stepNum.value += num)
+   }
+   elScrollbarDom.value?.setScrollTop(0)
+}
+</script>
+
+<style lang="scss" scoped>
+.secureAnth {
+   width: 100%;
+   height: 100%;
+   padding: 210px 36px 0 50px;
+   position: relative;
+   > :deep(.elScrollbar) {
+      width: 100%;
+      height: 546px;
+      background: linear-gradient(180deg, rgba(255, 255, 255, 0.7) 0%, rgba(255, 255, 255, 0.7) 36%, rgba(255, 234, 224, 0.7) 98%, #fff9ed 100%);
+      border-radius: 23px;
+      border: 1px solid #ffffff;
+      .el-scrollbar__wrap {
+         overflow-x: hidden;
+      }
+   }
+   .star {
+      position: absolute;
+      top: 214px;
+      right: 23px;
+      z-index: 10;
+      width: 22px;
+      height: 22px;
+   }
+   .stepCon {
+      padding: 24px 34px 16px;
+      .titleBox {
+         display: flex;
+         align-items: center;
+         &.alignTop {
+            align-items: initial;
+         }
+         &.mb {
+            margin-bottom: 34px;
+         }
+         .stepNum {
+            width: 34px;
+            height: 34px;
+            background: #ffdac2;
+            border: 1px solid #ffffff;
+            border-radius: 50%;
+            font-weight: 600;
+            font-size: 17px;
+            color: #131415;
+            text-align: center;
+            line-height: 34px;
+         }
+         .titleCon {
+            margin-left: 14px;
+            font-weight: 600;
+            font-size: 18px;
+            color: #000000;
+            line-height: 30px;
+            .colorOne {
+               color: #df6117;
+               flex-shrink: 0;
+            }
+            .colorTwo {
+               color: #e80000;
+               flex-shrink: 0;
+            }
+            .colorThree {
+               color: #777777;
+               flex-shrink: 0;
+            }
+         }
+      }
+      .contentBox {
+         display: flex;
+         overflow: hidden;
+         margin-bottom: 18px;
+         &:last-child {
+            margin-bottom: 0;
+         }
+         .xiazaiImg {
+            margin-top: 22px;
+            margin-left: 43px;
+            width: 179px;
+            height: 47px;
+            cursor: pointer;
+            &:hover {
+               opacity: $opacity-hover;
+            }
+         }
+         .win1Img {
+            margin: 14px 0 0 48px;
+            width: 90px;
+            height: 98px;
+         }
+         .win2Img {
+            margin: 14px 0 0 50px;
+            width: 519px;
+            height: 512px;
+         }
+         .win3Img {
+            margin: 14px 0 0 47px;
+            width: 415px;
+            height: 427px;
+         }
+         .win7Img {
+            margin: 14px 0 0 47px;
+            width: 336px;
+            height: 375px;
+         }
+         .win8Img {
+            margin: 14px 0 0 47px;
+            width: 680px;
+            height: 388px;
+         }
+         .mac1Img {
+            margin: 14px 0 0 48px;
+            width: 90px;
+            height: 98px;
+         }
+         .mac2Img {
+            margin: 14px 0 0 33px;
+            width: 299px;
+            height: 335px;
+         }
+         .mac3Img {
+            margin: 14px 0 0 48px;
+            width: 663px;
+            height: 269px;
+         }
+         .mac4Img {
+            margin: 14px 0 0 48px;
+            width: 357px;
+            height: 420px;
+         }
+         .mac5Img {
+            margin: 14px 0 0 48px;
+            width: 681px;
+            height: 381px;
+         }
+         .mac6Img {
+            margin: 14px 0 0 48px;
+            width: 681px;
+            height: 310px;
+         }
+      }
+   }
+   .stepBtn {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      margin-top: 25px;
+      .btn {
+         width: 179px;
+         height: 47px;
+         background: url("./img/btn.png") no-repeat;
+         background-size: 100% 100%;
+         cursor: pointer;
+         font-family: AlimamaFangYuanTiVF, AlimamaFangYuanTiVF;
+         font-weight: bold;
+         font-size: 20px;
+         color: #ffffff;
+         text-align: center;
+         line-height: 47px;
+         user-select: none;
+         &:hover {
+            opacity: $opacity-hover;
+         }
+         &:last-child {
+            margin-left: 17px;
+         }
+      }
+   }
+}
+</style>
+<style lang="scss">
+.h-modalFrame.useSecureAnth {
+   /* prettier-ignore */
+   --modalFrameTitHeight: 0PX;
+   .modalFrameBox {
+      box-shadow: initial;
+      border-radius: initial;
+      position: relative;
+      background: url("./img/bg.png") no-repeat;
+      background-size: 100% 100%;
+      .modalFrameTitle {
+         display: none;
+      }
+   }
+}
+</style>

BIN
src/img/coursewarePlay/ts1.png


BIN
src/img/coursewarePlay/ts2.png


+ 13 - 0
src/libs/axios.ts

@@ -4,6 +4,7 @@ import Nprogress from "@/plugin/nprogress"
 import { ElMessage } from "element-plus"
 import { TokenInvalidFlag, CODE401, CODE_ERR_CANCELED } from "@/libs/auth"
 import userStore from "@/store/modules/user"
+import useSecureAnth from "@/hooks/useSecureAnth"
 
 //重写 axios 传参
 interface AxiosConfigType extends AxiosRequestConfig {
@@ -74,7 +75,19 @@ class HttpAsynAxios {
             //         case 401:
             //     }
             // }
+            if (error.response?.status === 511) {
+               // isLogin=true 带有这个是登录时候的判断弹窗验证证书  其他的走正常弹窗
+               if (!/isLogin=true/.test(error.config?.url)) {
+                  useSecureAnth()
+               }
+               return Promise.reject({
+                  code: 511,
+                  message: "请下载安装安全数据证书",
+                  data: null
+               })
+            }
             const rejectData: apiResDataType =
+               //取消接口返回自己的 CODE_ERR_CANCELED
                error.code === CODE_ERR_CANCELED
                   ? {
                        code: error.code,

+ 1 - 1
src/plugin/loadingBar/loadingBar.vue

@@ -70,7 +70,7 @@ body.h-loadingBarBody {
    position: fixed;
    top: 0;
    left: 0;
-   z-index: 1239527;
+   z-index: 12345678;
    cursor: wait;
    width: 100%;
    height: 100%;

+ 1 - 1
src/plugin/modalFrame/modalFrame.scss

@@ -53,7 +53,7 @@
    left: 0;
    width: 100%;
    height: 100%;
-   z-index: 2000;
+   z-index: 1234567;
    background-color: $overlay-color-lighter;
    .animationBox {
       width: 100%;

+ 16 - 2
src/views/coursewarePlay/components/pen/pen.vue

@@ -6,7 +6,7 @@
 <template>
    <el-dialog modal-class="penModalClass" class="penElDialog" :class="{ isWhite: isWhite }" v-bind="$attrs" :fullscreen="true" :show-close="false">
       <iframe class="penIframe" frameborder="0" :src="URL_WHITEBOARD"></iframe>
-      <div class="closeSvg" @click="close">
+      <div class="closeSvg" @click="handleClose">
          <svg width="22px" height="20px" viewBox="0 0 22 20">
             <path
                transform="translate(-1.000000, -2.000000)"
@@ -20,10 +20,24 @@
 
 <script setup lang="ts">
 import { URL_WHITEBOARD } from "@/config/index"
-defineProps<{
+import useDialogConfirm from "@/hooks/useDialogConfirm"
+
+const props = defineProps<{
    isWhite?: boolean
    close: () => void
 }>()
+
+function handleClose() {
+   useDialogConfirm({
+      headImg: require(`@/img/coursewarePlay/${props.isWhite ? "ts2" : "ts1"}.png`),
+      text: `请确认是否退出${props.isWhite ? "白板" : "批注"}?`,
+      btnShow: [true, true],
+      onOk(vm: any) {
+         props.close()
+         vm.remove()
+      }
+   })
+}
 </script>
 <style lang="scss">
 .penElDialog.el-dialog {

+ 3 - 3
src/views/coursewarePlay/components/playRecordTime/playRecordTime.vue

@@ -6,7 +6,7 @@
 <template>
    <div class="playRecordTime">
       <div class="drop"></div>
-      <div class="time">{{ `${formatTime(playTime)}/${formatTime(props.coursewareTotalTIme)}` }}</div>
+      <div class="time">{{ `${formatTime(playTime)}/${formatTime(props.coursewareTotalTime)}` }}</div>
    </div>
 </template>
 
@@ -16,7 +16,7 @@ import { getCoursewarePlayTime_gyt, coursewarePlayTime_gyt } from "@/api/coursew
 import { httpAjax } from "@/plugin/httpAjax"
 import { ref, onUnmounted } from "vue"
 const props = defineProps<{
-   coursewareTotalTIme: number
+   coursewareTotalTime: number
    modeId: string
 }>()
 
@@ -37,7 +37,7 @@ function getCoursewarePlayTime() {
 }
 _time = setInterval(() => {
    // 播放时间大于总时间
-   if (playTime.value >= props.coursewareTotalTIme) {
+   if (playTime.value >= props.coursewareTotalTime) {
       _time && clearInterval(_time)
       timeRecord > 0 && handleCoursewarePlayTime(props.modeId, timeRecord)
       return

+ 204 - 110
src/views/coursewarePlay/coursewarePlay.vue

@@ -4,68 +4,78 @@
 * @Date:2024-04-03 17:31:41
 -->
 <template>
-   <div class="coursewarePlay">
-      <videoPlay ref="videoPlayDom" @ready="handleVideoReady" @ended="handleChangeCourseware(1)" :listen-win-events="true">
-         <div class="leftTools posTools">
-            <div v-if="activeCoursewareIndex > 0" class="posBtn" @click="handleChangeCourseware(-1)">
-               <img src="@/img/coursewarePlay/shang.png" />
-               <div>上一个</div>
-            </div>
-            <div v-if="activeCoursewareIndex < flattenCoursewareList.length - 1" class="posBtn" @click="handleChangeCourseware(1)">
-               <img src="@/img/coursewarePlay/xia.png" />
-               <div>下一个</div>
-            </div>
+   <div class="coursewarePlay" :class="{ hideController: !isShowController }">
+      <div class="coursewarePlayCon" @mousemove="handleMousemove" @click="handleClick">
+         <videoPlay
+            v-show="fileType === 'VIDEO'"
+            ref="videoPlayDom"
+            @ended="handleChangeCourseware(1)"
+            :disableEvents="true"
+            :isShowController="isShowController"
+         />
+         <div class="imgPlayBox" v-if="fileType === 'IMG'">
+            <ElImage :hide-on-click-modal="true" fit="contain" :src="activeCourseware?.content" class="imgPlay" />
          </div>
-         <div class="rightTools posTools">
-            <div
-               class="posBtn"
-               @click="
-                  () => {
-                     videoPlayDom?.pauseVideo()
-                     whitePenShow = true
-                  }
-               "
-            >
-               <img src="@/img/coursewarePlay/baiban.png" />
-               <div>白板</div>
-            </div>
-            <div
-               class="posBtn"
-               @click="
-                  () => {
-                     videoPlayDom?.pauseVideo()
-                     penShow = true
-                  }
-               "
-            >
-               <img src="@/img/coursewarePlay/pizhu.png" />
-               <div>批注</div>
-            </div>
-            <div class="posBtn" @click="drawerShow = true">
-               <img src="@/img/coursewarePlay/zhishidian.png" />
-               <div>知识点</div>
-            </div>
-            <div class="posBtn" @click="handleCoursewareEnd">
-               <img src="@/img/coursewarePlay/jieshu.png" />
-               <div>结束</div>
-            </div>
+         <div class="songPlayBox" v-if="fileType === 'SONG'">
+            <iframe class="songIframe" @mousemove="handleMousemove" :src="songPlaySrc" frameborder="0"></iframe>
          </div>
-         <div class="topTools">
-            <div class="leftMenu">
-               <img @click="handleGoBack" class="backImg" src="@/img/coursewarePlay/back.png" />
-               <playRecordTime
-                  v-if="route.query.modeId && coursewareTotalTIme && !(userStoreHook.roles === 'GYM')"
-                  :modeId="route.query.modeId as string"
-                  :coursewareTotalTIme="coursewareTotalTIme"
-               />
-            </div>
-            <div class="midMenu">{{ activeCourseware?.parentData.name || "" }}</div>
-            <div class="rightMenu"></div>
+      </div>
+      <div class="leftTools posTools">
+         <div v-if="activeCoursewareIndex > 0" class="posBtn" @click="handleChangeCourseware(-1)">
+            <img src="@/img/coursewarePlay/shang.png" />
+            <div>上一个</div>
          </div>
-         <div class="activeName">
-            <div>{{ activeCourseware?.name || "" }}</div>
+         <div v-if="activeCoursewareIndex < flattenCoursewareList.length - 1" class="posBtn" @click="handleChangeCourseware(1)">
+            <img src="@/img/coursewarePlay/xia.png" />
+            <div>下一个</div>
          </div>
-      </videoPlay>
+      </div>
+      <div class="rightTools posTools">
+         <div
+            class="posBtn"
+            @click="
+               () => {
+                  handleVideoPause()
+                  whitePenShow = true
+               }
+            "
+         >
+            <img src="@/img/coursewarePlay/baiban.png" />
+            <div>白板</div>
+         </div>
+         <div
+            class="posBtn"
+            @click="
+               () => {
+                  handleVideoPause()
+                  penShow = true
+               }
+            "
+         >
+            <img src="@/img/coursewarePlay/pizhu.png" />
+            <div>批注</div>
+         </div>
+         <div class="posBtn" @click="drawerShow = true">
+            <img src="@/img/coursewarePlay/zhishidian.png" />
+            <div>知识点</div>
+         </div>
+         <div class="posBtn" @click="handleCoursewareEnd">
+            <img src="@/img/coursewarePlay/jieshu.png" />
+            <div>结束</div>
+         </div>
+      </div>
+      <div class="topTools">
+         <div class="leftMenu">
+            <img @click="handleGoBack" class="backImg" src="@/img/coursewarePlay/back.png" />
+            <playRecordTime
+               v-if="route.query.modeId && coursewareTotalTime && !(userStoreHook.roles === 'GYM')"
+               :modeId="route.query.modeId as string"
+               :coursewareTotalTime="coursewareTotalTime"
+            />
+         </div>
+         <div class="midMenu">{{ activeCourseware?.parentData.name || "" }}</div>
+         <div class="rightMenu"></div>
+      </div>
       <el-drawer class="elDrawer" v-model="drawerShow" :show-close="false">
          <template #header="{ close }">
             <img class="directory" src="@/img/coursewarePlay/kcml.png" />
@@ -103,21 +113,23 @@ import { checkWebCourse_gyt } from "@/api/coursewarePlay.api"
 import { httpAjaxErrMsg, httpAjaxLoadingErrMsg } from "@/plugin/httpAjax"
 import userStore from "@/store/modules/user"
 import { useRoute } from "vue-router"
-import { shallowRef, ref, computed, watchEffect, onUnmounted, onMounted } from "vue"
+import { shallowRef, ref, computed, onUnmounted, onMounted, watch, nextTick } from "vue"
 import { ElMessageBox } from "element-plus"
 import courseCollapse from "./components/courseCollapse"
 import pen from "./components/pen"
 import playRecordTime from "./components/playRecordTime"
 import useDialogConfirm from "@/hooks/useDialogConfirm"
 import { getRecentCourseSchedule_gym } from "@/api/homePage.api"
+import { getToken } from "@/libs/auth"
+import { URL_TEACH_GYT, URL_TEACH_GYM } from "@/config"
 
+const route = useRoute()
+const userStoreHook = userStore()
 // 批注
 const penShow = ref(false)
 // 白板
 const whitePenShow = ref(false)
-
-const route = useRoute()
-const userStoreHook = userStore()
+/* 获取资源 */
 const videoPlayDom = ref<InstanceType<typeof videoPlay>>()
 const coursewareList = shallowRef<any[]>([]) // 知识点
 const flattenCoursewareList = shallowRef<any[]>([]) // 扁平化coursewareList
@@ -125,20 +137,35 @@ const flattenCoursewareList = shallowRef<any[]>([]) // 扁平化coursewareList
 const activeCourseware = computed<undefined | Record<string, any>>(() => {
    return flattenCoursewareList.value[activeCoursewareIndex.value]
 })
+// 文件类型
+const fileType = computed<"VIDEO" | "IMG" | "SONG">(() => {
+   return activeCourseware.value?.type || activeCourseware.value?.typeCode
+})
+const songPlaySrc = computed<string>(() => {
+   if (fileType.value !== "SONG") {
+      return ""
+   }
+   return userStoreHook.roles === "GYM"
+      ? `${URL_TEACH_GYM}?Authorization=${getToken()}&platform=web&isOpenMetronome=0#/detail/${activeCourseware.value?.content}?part-index=0`
+      : `${URL_TEACH_GYT}?id=${activeCourseware.value?.content}&modelType=practice&modeType=json&Authorization=${getToken()}`
+})
 const activeCoursewareIndex = ref(0)
 const drawerShow = ref(false)
 // 课程总时间
-const coursewareTotalTIme = ref(0)
-
-watchEffect(() => {
-   activeCourseware.value && videoPlayDom.value?.playVideo({ src: activeCourseware.value.content })
-})
-onMounted(() => {
-   document.addEventListener("keydown", handleVideoKeydown)
-})
-onUnmounted(() => {
-   document.removeEventListener("keydown", handleVideoKeydown)
+const coursewareTotalTime = ref(0)
+// 监控播放
+watch(activeCourseware, () => {
+   handleVideoPause()
+   fileType.value === "VIDEO" &&
+      nextTick(() => {
+         handlePlayVideo({
+            src: activeCourseware.value?.content,
+            name: activeCourseware.value?.name
+         })
+      })
+   showController()
 })
+getCoursewareList()
 function getCoursewareList() {
    httpAjaxErrMsg(userStoreHook.roles === "GYM" ? getLessonCourseDetail_gym : getLessonCoursewareDetail_gyt, route.params.id as string).then(res => {
       if (res.code === 200) {
@@ -174,7 +201,6 @@ function getCoursewareList() {
       }
    })
 }
-
 let flattenCoursewareListData: any = [] // 临时扁平化数据
 function handlePointList(pointList: any[]) {
    coursewareList.value = filterPointList(pointList)
@@ -195,7 +221,7 @@ function filterPointList(pointList: any[], parentData?: { ids: string[]; name: s
             children: filterPointList(point.children, { ids: [...(parentData?.ids || []), point.id], name: point.name })
          })
       } else {
-         coursewareTotalTIme.value += point.totalMaterialTimeSecond
+         coursewareTotalTime.value += point.totalMaterialTimeSecond
          return Object.assign(point, {
             materialList: point.materialList.map((item: any) => {
                item.parentData = {
@@ -209,11 +235,6 @@ function filterPointList(pointList: any[], parentData?: { ids: string[]; name: s
       }
    })
 }
-
-function handleVideoReady() {
-   getCoursewareList()
-}
-
 function handleChangeCourseware(index: -1 | 1) {
    const newIndex = index + activeCoursewareIndex.value
    if (newIndex < 0 || newIndex > flattenCoursewareList.value.length - 1) {
@@ -226,14 +247,77 @@ function handleCourseClick(value: any) {
       return value.id === item.id
    })
 }
-function handleVideoKeydown(e: KeyboardEvent) {
+/* 播放器相关 */
+// 视频播放或者暂停
+function handleVideoPlay() {
+   videoPlayDom.value?.handlePlay()
+   showController()
+}
+// 视频快进快退
+function handleVideoSpeedCurrentTime(type: "fast" | "slow") {
+   videoPlayDom.value?.speedCurrentTime(type)
+   showController()
+}
+// 视频暂停
+function handleVideoPause() {
+   videoPlayDom.value?.pauseVideo()
+   showController()
+}
+// 播放视频
+function handlePlayVideo({ src, name }: { src: string; name: string }) {
+   videoPlayDom.value?.playVideo({
+      src,
+      name
+   })
+   showController()
+}
+/* 按键事件相关 */
+onMounted(() => {
+   document.addEventListener("keydown", handleKeydown)
+   showController()
+})
+onUnmounted(() => {
+   document.removeEventListener("keydown", handleKeydown)
+})
+function handleKeydown(e: KeyboardEvent) {
    const key = e.key
-   if (key === "ArrowDown") {
+   if (key === " ") {
+      // 视频类型的时候才触发
+      fileType.value === "VIDEO" && handleVideoPlay()
+   } else if (key === "ArrowLeft") {
+      // 视频类型的时候才触发
+      fileType.value === "VIDEO" && handleVideoSpeedCurrentTime("slow")
+   } else if (key === "ArrowRight") {
+      // 视频类型的时候才触发
+      fileType.value === "VIDEO" && handleVideoSpeedCurrentTime("fast")
+   } else if (key === "ArrowDown") {
       handleChangeCourseware(1)
    } else if (key === "ArrowUp") {
       handleChangeCourseware(-1)
    }
 }
+function handleMousemove() {
+   showController()
+}
+function handleClick() {
+   fileType.value === "VIDEO" && isShowController.value && handleVideoPlay()
+   showController()
+}
+// 是否显示控制器
+const isShowController = ref(true)
+let _showTimer: any
+function showController() {
+   isShowController.value = true
+   _showTimer && clearTimeout(_showTimer)
+   _showTimer = setTimeout(hideController, 3000)
+}
+function hideController() {
+   if (fileType.value === "VIDEO" && videoPlayDom.value?.playType === "pause") {
+      return
+   }
+   isShowController.value = false
+}
+/* 结束课程 */
 function handleGoBack() {
    window.open("about:blank", "_self")
    window.close()
@@ -286,17 +370,45 @@ function handleCoursewareEnd() {
    width: 100%;
    height: 100%;
    position: relative;
-   .activeName {
-      transition: all 0.5s;
-      position: absolute;
-      right: 30px;
-      bottom: 15px;
-      font-weight: 500;
-      font-size: 20px;
-      color: #ffffff;
-      display: flex;
-      align-items: flex-end;
-      padding-bottom: 13px;
+   overflow: hidden;
+   &.hideController {
+      .leftTools {
+         opacity: 0;
+         transform: translate(-100%, -50%);
+      }
+      .rightTools {
+         opacity: 0;
+         transform: translate(100%, -50%);
+      }
+      .topTools {
+         opacity: 0;
+         transform: translateY(-100%);
+      }
+   }
+   .coursewarePlayCon {
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      .imgPlayBox {
+         width: 100%;
+         height: 100%;
+         display: flex;
+         justify-content: center;
+         align-items: center;
+         .imgPlay {
+            width: 84%;
+            height: 100%;
+         }
+      }
+      .songPlayBox {
+         width: 100%;
+         height: 100%;
+         .songIframe {
+            display: block;
+            width: 100%;
+            height: 100%;
+         }
+      }
    }
    .topTools {
       position: absolute;
@@ -362,24 +474,6 @@ function handleCoursewareEnd() {
          }
       }
    }
-   &:deep(.videoPlay.isHideController) {
-      .leftTools {
-         opacity: 0;
-         transform: translate(-100%, -50%);
-      }
-      .rightTools {
-         opacity: 0;
-         transform: translate(100%, -50%);
-      }
-      .topTools {
-         opacity: 0;
-         transform: translateY(-100%);
-      }
-      .activeName {
-         opacity: 0;
-         transform: translateY(100%);
-      }
-   }
    &:deep(.elDrawer.el-drawer) {
       width: 346px !important;
       .el-drawer__header {

+ 113 - 102
src/views/coursewarePlay/videoPlay/videoPlay.vue

@@ -5,26 +5,27 @@
 -->
 <template>
    <div
-      @keydown="props.listenWinEvents || handleVideoKeydown"
+      @keydown="handleVideoKeydown"
       @mousemove="handleVideoMousemove"
+      @click="handleVideoClick"
       class="videoPlay"
       :class="{ isHideController: !isShowController }"
       tabindex="-1"
    >
-      <video class="videoPlayBox" @click="handlePlay" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
+      <video class="videoPlayBox" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
       <div class="videoController" @click.stop>
          <div class="timeController">{{ `${formatTime(timeController.currentTime)} / ${formatTime(timeController.duration)}` }}</div>
-         <el-slider
+         <n-slider
             class="sliderController"
-            @change="handleTimeChange"
-            @mousedown="handleSilderMousedown"
-            v-model="timeController.currentTimeSilder"
+            :keyboard="false"
+            :value="timeController.currentTimeSilder"
+            :tooltip="isShowController"
+            @update:value="handleSilderChange"
+            :on-dragend="handleTimeChange"
             :max="timeController.duration"
-            :format-tooltip="
-               (value:number) => {
+            :format-tooltip="(value:number) => {
                   return formatTime(value)
-               }
-            "
+            }"
          />
          <div class="playController">
             <div class="leftPlayController">
@@ -43,48 +44,55 @@
                >
                   <div class="sliderSpeedCon">
                      <img @click="handlePalySpeed(playController.speedStep)" src="./img/jia.png" />
-                     <el-slider
-                        tooltip-class="sliderSpeedTitTooltip"
+                     <n-slider
                         class="sliderSpeed"
-                        @change="handlePalySpeedChange"
-                        v-model="playController.palySpeed"
+                        :tooltip="false"
+                        :keyboard="false"
+                        :value="playController.palySpeed"
+                        @update:value="handlePalySpeedChange"
                         vertical
                         :step="playController.speedStep"
                         :max="playController.maxSpeed"
                         :min="playController.minSpeed"
-                        :format-tooltip="(num:number) => {
-                           return num.toFixed(1)
-                        }"
-                     />
+                     >
+                        <template #thumb>
+                           <div class="thumb">{{ playController.palySpeed.toFixed(1) + "X" }}</div>
+                        </template>
+                     </n-slider>
                      <img @click="handlePalySpeed(-playController.speedStep)" src="./img/jian.png" />
                   </div>
                </el-popover>
             </div>
-            <div class="rightPlayController"></div>
+            <div class="rightPlayController">
+               <div class="videoName">
+                  <div>{{ videoName || "" }}</div>
+               </div>
+            </div>
          </div>
       </div>
-      <slot></slot>
    </div>
 </template>
 
 <script setup lang="ts">
 import TCPlayer from "tcplayer.js"
 import "tcplayer.js/dist/tcplayer.min.css"
-import { onMounted, onUnmounted, ref, reactive, watch } from "vue"
+import { onMounted, onUnmounted, ref, reactive, watch, toRef, watchEffect } from "vue"
 import { UUID } from "@/libs/tools"
 import { formatTime } from "./tools"
-import { addClass, removeClass, setStyle } from "@/libs/tools"
+import { NSlider } from "naive-ui"
 
 const props = defineProps<{
-   listenWinEvents?: boolean
+   disableEvents?: boolean
+   isShowController?: boolean
 }>()
 const emits = defineEmits<{
    (e: "ready"): void //播放器初始化完成
    (e: "ended"): void //播放结束
 }>()
-
 const videoId = "video" + UUID()
 let playerVm: Record<string, any>
+let isReady = false // 是否初始化播放器播放器
+const videoName = ref("")
 
 /* 时间控制器 */
 const timeController = reactive({
@@ -97,32 +105,6 @@ const timeController = reactive({
 const btnSpendDom = ref()
 const popoverSpendDom = ref()
 let _popoverSpendTime: any
-let _sliderSpeedTitTime: any
-const body = document.querySelector("body")
-const name = "_sliderSpeedTit"
-function handleSliderSpeedTitShow() {
-   const sliderSpeedTitTooltipDom = document.querySelector(".sliderSpeedTitTooltip.el-popper")
-   sliderSpeedTitTooltipDom?.setAttribute("data-popper-placement", "top")
-   setStyle(
-      document.querySelector(".sliderSpeedTitTooltip.el-popper .el-popper__arrow") as any,
-      {
-         transform: `translate(15px, 0)`
-      } as any
-   )
-   setStyle(
-      sliderSpeedTitTooltipDom as any,
-      {
-         "z-index": "3000",
-         inset: "auto auto 0px 0px",
-         transform: `translate(172px, ${-156 - (playController.palySpeed - 0.5) * 10 * 15}px)`
-      } as any
-   )
-   addClass(body, name)
-   clearTimeout(_sliderSpeedTitTime)
-   _sliderSpeedTitTime = setTimeout(() => {
-      removeClass(body, name)
-   }, 800)
-}
 // 定时隐藏
 function handlePopoverTimeHide() {
    _popoverSpendTime && clearTimeout(_popoverSpendTime)
@@ -154,17 +136,14 @@ watch(
 )
 /* 是否显示控制器 */
 const isShowController = ref(true)
+watchEffect(() => {
+   isShowController.value = props.isShowController
+})
 let _showTimer: any
 onMounted(() => {
    initVideo()
-   if (props.listenWinEvents) {
-      document.addEventListener("keydown", handleVideoKeydown)
-   }
 })
 onUnmounted(() => {
-   if (props.listenWinEvents) {
-      document.removeEventListener("keydown", handleVideoKeydown)
-   }
    playerVm?.dispose()
 })
 /**
@@ -173,12 +152,13 @@ onUnmounted(() => {
 function initVideo() {
    playerVm = TCPlayer(videoId, {
       controls: false,
-      autoplay: true,
+      //autoplay: true,  // 自动播放前1秒暂停不了,改为loadedmetadata调用播放 实现自动播放
       loop: false
    })
    // 初始化完成
    playerVm.ready(() => {
       console.log("播放器初始化完成")
+      isReady = true
       emits("ready")
    })
    // 开始加载数据时
@@ -186,6 +166,10 @@ function initVideo() {
       // 重新设置播放倍速  因为切换视频播放倍速会重置
       playerVm.playbackRate(playController.palySpeed)
    })
+   playerVm.on("loadedmetadata", () => {
+      console.log("loadedmetadata")
+      playerVm.play()
+   })
    //总时长变化时候
    playerVm.on("durationchange", () => {
       //duration和currentTime 时间向上取整Math.ceil
@@ -213,18 +197,35 @@ function initVideo() {
 /**
  * 播放  需要在ready之后调用
  */
-function playVideo({ src }: { src: string }) {
+// 接口请求可能在播放之前 所以这里等待播放器初始化
+let _time: any
+function playVideo({ src, name }: { src: string; name: string }) {
+   videoName.value = name
+   _time && clearInterval(_time)
+   if (isReady) {
+      handlePlayVideo(src)
+   } else {
+      _time = setInterval(() => {
+         if (isReady) {
+            clearInterval(_time)
+            handlePlayVideo(src)
+         }
+      }, 60)
+   }
+}
+function handlePlayVideo(src: string) {
    playerVm?.src(src)
    showController()
 }
 
 /* 时间控制器 */
-function handleTimeChange(value: number | number[]) {
-   playerVm.currentTime(value || 0)
+function handleTimeChange(value?: number) {
+   playerVm.currentTime(value || timeController.currentTimeSilder)
    timeController.isDrag = false
 }
-function handleSilderMousedown() {
+function handleSilderChange(value: number) {
    timeController.isDrag = true
+   timeController.currentTimeSilder = value
 }
 // 快进或者快退
 function speedCurrentTime(type: "fast" | "slow") {
@@ -241,11 +242,14 @@ function pauseVideo() {
    playerVm.pause()
    showController()
 }
+// 循环播放
 function handleLoop() {
    playController.loop ? playerVm.loop(false) : playerVm.loop(true)
    playController.loop = playerVm.loop()
 }
-function handlePalySpeedChange(value: number | number[]) {
+// 播放速度
+function handlePalySpeedChange(value: number) {
+   playController.palySpeed = value
    playerVm.playbackRate(value)
 }
 function handlePalySpeed(value: number) {
@@ -253,12 +257,14 @@ function handlePalySpeed(value: number) {
    if (palySpeed > playController.maxSpeed || palySpeed < playController.minSpeed) {
       return
    }
-   playController.palySpeed = palySpeed
    handlePalySpeedChange(palySpeed)
-   handleSliderSpeedTitShow()
 }
-/* 是否显示控制器 */
+function handleVideoClick() {
+   if (props.disableEvents) return
+   handlePlay()
+}
 function handleVideoKeydown(e: KeyboardEvent) {
+   if (props.disableEvents) return
    const key = e.key
    if (key === " ") {
       handlePlay()
@@ -269,9 +275,12 @@ function handleVideoKeydown(e: KeyboardEvent) {
    }
 }
 function handleVideoMousemove() {
+   if (props.disableEvents) return
    showController()
 }
+/* 是否显示控制器 */
 function showController() {
+   if (props.disableEvents) return
    isShowController.value = true
    _showTimer && clearTimeout(_showTimer)
    _showTimer = setTimeout(tryHideController, 3000)
@@ -283,17 +292,13 @@ function tryHideController() {
 }
 defineExpose({
    playVideo,
-   pauseVideo
+   pauseVideo,
+   handlePlay,
+   speedCurrentTime,
+   playType: toRef(playController, "type")
 })
 </script>
 
-<style lang="scss">
-body._sliderSpeedTit {
-   .sliderSpeedTitTooltip {
-      display: initial !important;
-   }
-}
-</style>
 <style lang="scss" scoped>
 .videoPlay {
    width: 100%;
@@ -325,27 +330,21 @@ body._sliderSpeedTit {
       background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
       color: #fff;
       transition: all 0.5s;
+      &:hover {
+         cursor: initial;
+         opacity: initial !important;
+         transform: initial !important;
+      }
       .timeController {
          font-weight: 500;
          font-size: 20px;
          color: #ffffff;
          line-height: 30px;
       }
-      & > :deep(.sliderController.el-slider) {
-         --el-slider-button-wrapper-offset: -12px;
-         --el-slider-button-wrapper-size: 28px;
-         --el-slider-button-size: 20px;
-         --el-slider-height: 4px;
-         --el-slider-border-radius: 2px;
-         --el-slider-main-bg-color: #ff8057;
-         --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
-         height: 28px;
-         .el-slider__button {
-            border: none;
-            &:hover {
-               transform: scale(1.1);
-            }
-         }
+      & > :deep(.sliderController.n-slider) {
+         --n-rail-color: #c9c9cb !important;
+         --n-fill-color: #ff8057 !important;
+         --n-fill-color-hover: #ff8057 !important;
       }
       .playController {
          display: flex;
@@ -384,26 +383,38 @@ body._sliderSpeedTit {
                      width: 30px;
                      height: 31px;
                   }
-                  .sliderSpeed.el-slider {
+                  .sliderSpeed.n-slider {
                      flex-grow: 1;
-                     padding: 10px 0;
-                     --el-slider-button-wrapper-offset: -9px;
-                     --el-slider-button-wrapper-size: 24px;
-                     --el-slider-button-size: 16px;
-                     --el-slider-height: 6px;
-                     --el-slider-border-radius: 4px;
-                     --el-slider-main-bg-color: #ff8057;
-                     --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
-                     .el-slider__button {
-                        border: none;
-                        &:hover {
-                           transform: scale(1.1);
-                        }
+                     padding: 6px 0;
+                     --n-rail-width-vertical: 5px !important;
+                     --n-rail-color: #c9c9cb !important;
+                     --n-fill-color: #ff8057 !important;
+                     --n-fill-color-hover: #ff8057 !important;
+                     .thumb {
+                        height: 22px;
+                        padding: 0 6px;
+                        background: #ffffff;
+                        box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
+                        border-radius: 11px;
+                        text-align: center;
+                        line-height: 22px;
+                        font-weight: 500;
+                        font-size: 15px;
+                        color: #ff8057;
                      }
                   }
                }
             }
          }
+         .rightPlayController {
+            display: flex;
+            align-items: center;
+            .videoName {
+               font-weight: 500;
+               font-size: 20px;
+               color: #ffffff;
+            }
+         }
       }
    }
 }

+ 17 - 1
src/viewsframe/login/login.vue

@@ -39,6 +39,9 @@ import QrcodeVue from "qrcode.vue"
 import { ref, computed } from "vue"
 import userStore from "@/store/modules/user"
 import { useRouter } from "vue-router"
+import useSecureAnth from "@/hooks/useSecureAnth"
+import { mutualTLSQuery_gym } from "@/api/user.api"
+import { httpAjaxLoading } from "@/plugin/httpAjax"
 
 const userStoreHook = userStore()
 const router = useRouter()
@@ -66,7 +69,20 @@ function handleQrcodeStatus() {
          if (status === "FINISH") {
             // 登录成功
             userStoreHook.login(res.data).then(() => {
-               router.push({ path: "/" })
+               // 目前管乐迷才有安全证书
+               res.data.appKey === "GYM"
+                  ? httpAjaxLoading(mutualTLSQuery_gym).then(res => {
+                       if (res.code === 511) {
+                          useSecureAnth({
+                             onCancel() {
+                                router.push({ path: "/" })
+                             }
+                          })
+                       } else {
+                          router.push({ path: "/" })
+                       }
+                    })
+                  : router.push({ path: "/" })
             })
             return
          }

+ 1 - 1
tsconfig.json

@@ -12,7 +12,7 @@
       "useDefineForClassFields": true,
       "sourceMap": true,
       "baseUrl": ".",
-      "types": ["webpack-env", "element-plus/global"],
+      "types": ["webpack-env", "element-plus/global", "naive-ui/volar"],
       "paths": {
          "@/*": ["src/*"]
       },

+ 146 - 0
yarn.lock

@@ -966,6 +966,13 @@
   dependencies:
     regenerator-runtime "^0.14.0"
 
+"@babel/runtime@^7.21.0":
+  version "7.24.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd"
+  integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==
+  dependencies:
+    regenerator-runtime "^0.14.0"
+
 "@babel/template@^7.22.5":
   version "7.22.5"
   resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz"
@@ -1000,6 +1007,16 @@
     "@babel/helper-validator-identifier" "^7.22.5"
     to-fast-properties "^2.0.0"
 
+"@css-render/plugin-bem@^0.15.12":
+  version "0.15.12"
+  resolved "https://registry.yarnpkg.com/@css-render/plugin-bem/-/plugin-bem-0.15.12.tgz#cd88e46a388e4786436bd622414da0aa6019af3b"
+  integrity sha512-Lq2jSOZn+wYQtsyaFj6QRz2EzAnd3iW5fZeHO1WSXQdVYwvwGX0ZiH3X2JQgtgYLT1yeGtrwrqJdNdMEUD2xTw==
+
+"@css-render/vue3-ssr@^0.15.10", "@css-render/vue3-ssr@^0.15.12":
+  version "0.15.12"
+  resolved "https://registry.yarnpkg.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.12.tgz#798d8dffadecd2bf8c80cbaab64e9df10be5626e"
+  integrity sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==
+
 "@ctrl/tinycolor@^3.4.1":
   version "3.6.0"
   resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz"
@@ -1015,6 +1032,11 @@
   resolved "https://registry.yarnpkg.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz#1f635ad5fdd5c85ed936481525570e82b5a8307a"
   integrity sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==
 
+"@emotion/hash@~0.8.0":
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+  integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
 "@eslint-community/eslint-utils@^4.2.0":
   version "4.4.0"
   resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
@@ -1128,6 +1150,11 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
+"@juggle/resize-observer@^3.3.1":
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
+  integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
+
 "@leichtgewicht/ip-codec@^2.0.1":
   version "2.0.4"
   resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz"
@@ -1306,6 +1333,11 @@
   resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz"
   integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
 
+"@types/katex@^0.16.2":
+  version "0.16.7"
+  resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
+  integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
+
 "@types/lodash-es@^4.17.6":
   version "4.17.8"
   resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.8.tgz"
@@ -1313,11 +1345,23 @@
   dependencies:
     "@types/lodash" "*"
 
+"@types/lodash-es@^4.17.9":
+  version "4.17.12"
+  resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b"
+  integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
+  dependencies:
+    "@types/lodash" "*"
+
 "@types/lodash@*", "@types/lodash@^4.14.182":
   version "4.14.197"
   resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz"
   integrity sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==
 
+"@types/lodash@^4.14.198":
+  version "4.17.0"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3"
+  integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==
+
 "@types/md5@^2.3.2":
   version "2.3.2"
   resolved "https://registry.npmjs.org/@types/md5/-/md5-2.3.2.tgz"
@@ -2903,6 +2947,14 @@ css-minimizer-webpack-plugin@^3.0.2:
     serialize-javascript "^6.0.0"
     source-map "^0.6.1"
 
+css-render@^0.15.10, css-render@^0.15.12:
+  version "0.15.12"
+  resolved "https://registry.yarnpkg.com/css-render/-/css-render-0.15.12.tgz#76be94066897bd3231a9b9412971ffc258ada66e"
+  integrity sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==
+  dependencies:
+    "@emotion/hash" "~0.8.0"
+    csstype "~3.0.5"
+
 css-select@^4.1.3:
   version "4.3.0"
   resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz"
@@ -2993,6 +3045,28 @@ csstype@^3.1.1:
   resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz"
   integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
 
+csstype@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
+  integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+csstype@~3.0.5:
+  version "3.0.11"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33"
+  integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==
+
+date-fns-tz@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-2.0.1.tgz#0a9b2099031c0d74120b45de9fd23192e48ea495"
+  integrity sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==
+
+date-fns@^2.30.0:
+  version "2.30.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
+  integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
+  dependencies:
+    "@babel/runtime" "^7.21.0"
+
 dayjs@^1.11.3:
   version "1.11.9"
   resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz"
@@ -3513,6 +3587,11 @@ events@^3.2.0:
   resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
   integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
 
+evtd@^0.2.2, evtd@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/evtd/-/evtd-0.2.4.tgz#0aac39ba44d6926e6668948ac27618e0795b9d07"
+  integrity sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==
+
 execa@^0.8.0:
   version "0.8.0"
   resolved "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz"
@@ -3967,6 +4046,11 @@ highlight.js@^10.7.1:
   resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz"
   integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
 
+highlight.js@^11.8.0:
+  version "11.9.0"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
+  integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
+
 hosted-git-info@^2.1.4:
   version "2.8.9"
   resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz"
@@ -4792,6 +4876,31 @@ mz@^2.4.0:
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
 
+naive-ui@^2.38.1:
+  version "2.38.1"
+  resolved "https://registry.yarnpkg.com/naive-ui/-/naive-ui-2.38.1.tgz#d64e2af0b161215a61b5b31038a538102da3e05f"
+  integrity sha512-AnU1FQ7K/CbhguAX++V4kCFjk7h7RvWt4nvZPRjORMpq+fUIlzD+EcQ5Cv1VqDloNF8+eMv4Akc2Ogacc9S+5A==
+  dependencies:
+    "@css-render/plugin-bem" "^0.15.12"
+    "@css-render/vue3-ssr" "^0.15.12"
+    "@types/katex" "^0.16.2"
+    "@types/lodash" "^4.14.198"
+    "@types/lodash-es" "^4.17.9"
+    async-validator "^4.2.5"
+    css-render "^0.15.12"
+    csstype "^3.1.3"
+    date-fns "^2.30.0"
+    date-fns-tz "^2.0.0"
+    evtd "^0.2.4"
+    highlight.js "^11.8.0"
+    lodash "^4.17.21"
+    lodash-es "^4.17.21"
+    seemly "^0.3.8"
+    treemate "^0.3.11"
+    vdirs "^0.1.8"
+    vooks "^0.2.12"
+    vueuc "^0.4.58"
+
 nanoid@^3.3.6:
   version "3.3.6"
   resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz"
@@ -5854,6 +5963,11 @@ screenfull@^6.0.2:
   resolved "https://registry.npmjs.org/screenfull/-/screenfull-6.0.2.tgz"
   integrity sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==
 
+seemly@^0.3.6, seemly@^0.3.8:
+  version "0.3.8"
+  resolved "https://registry.yarnpkg.com/seemly/-/seemly-0.3.8.tgz#42879d8375d73126a04dc16b1bf92a773d2e5974"
+  integrity sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==
+
 select-hose@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz"
@@ -6379,6 +6493,11 @@ tr46@~0.0.3:
   resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
   integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
 
+treemate@^0.3.11:
+  version "0.3.11"
+  resolved "https://registry.yarnpkg.com/treemate/-/treemate-0.3.11.tgz#7d52f8f69ab9ce326f8d139e0a3d1ffb25e48222"
+  integrity sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==
+
 ts-loader@^9.2.5:
   version "9.4.4"
   resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz"
@@ -6532,6 +6651,13 @@ vary@~1.1.2:
   resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
 
+vdirs@^0.1.4, vdirs@^0.1.8:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/vdirs/-/vdirs-0.1.8.tgz#a103bc43baca738f8dea912a7e9737154a19dbc2"
+  integrity sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==
+  dependencies:
+    evtd "^0.2.2"
+
 videojs-font@2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-2.1.0.tgz#a25930a67f6c9cfbf2bb88dacb8c6b451f093379"
@@ -6551,6 +6677,13 @@ videojs-vtt.js@0.12.4:
   dependencies:
     global "^4.3.1"
 
+vooks@^0.2.12, vooks@^0.2.4:
+  version "0.2.12"
+  resolved "https://registry.yarnpkg.com/vooks/-/vooks-0.2.12.tgz#2b6e23330b77bac81c7f7a344c4ca3e9f4f6c373"
+  integrity sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==
+  dependencies:
+    evtd "^0.2.2"
+
 vue-demi@*, vue-demi@>=0.14.5:
   version "0.14.5"
   resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz"
@@ -6631,6 +6764,19 @@ vue@^3.2.13:
     "@vue/server-renderer" "3.3.4"
     "@vue/shared" "3.3.4"
 
+vueuc@^0.4.58:
+  version "0.4.58"
+  resolved "https://registry.yarnpkg.com/vueuc/-/vueuc-0.4.58.tgz#03ee2ea6febf360ca9cbe490841fce91742eea12"
+  integrity sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==
+  dependencies:
+    "@css-render/vue3-ssr" "^0.15.10"
+    "@juggle/resize-observer" "^3.3.1"
+    css-render "^0.15.10"
+    evtd "^0.2.4"
+    seemly "^0.3.6"
+    vdirs "^0.1.4"
+    vooks "^0.2.4"
+
 watchpack@^2.4.0:
   version "2.4.0"
   resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels