Browse Source

Merge branch 'hqyDev' of http://git.dayaedu.com/lex/classroom-instruments into jenkins

黄琪勇 11 months ago
parent
commit
48f3a44588
67 changed files with 1977 additions and 986 deletions
  1. 1 1
      .editorconfig
  2. 64 37
      package-lock.json
  3. 1 1
      package.json
  4. 22 0
      public/roll-call/css/index.css
  5. BIN
      public/roll-call/img/back.png
  6. 19 3
      public/roll-call/index.html
  7. 1 1
      public/version.json
  8. 16 2
      src/components/card-preview/index.module.less
  9. 30 0
      src/components/card-preview/index.tsx
  10. 2 2
      src/components/card-type/index.tsx
  11. BIN
      src/components/layout/images/gnyd.png
  12. 5 2
      src/components/layout/index.module.less
  13. 4 4
      src/components/layout/index.tsx
  14. 65 1
      src/components/layout/layoutTop.tsx
  15. 14 0
      src/hooks/useDrag/dragbom.jsx
  16. BIN
      src/hooks/useDrag/img/left.png
  17. BIN
      src/hooks/useDrag/img/right.png
  18. 18 0
      src/hooks/useDrag/index.module.less
  19. 153 0
      src/hooks/useDrag/index.ts
  20. 70 30
      src/views/attend-class/component/audio-pay.tsx
  21. 20 16
      src/views/attend-class/component/audio.module.less
  22. 6 2
      src/views/attend-class/component/rhythm-modal/index.tsx
  23. 7 1
      src/views/attend-class/component/tools/pen.module.less
  24. 32 4
      src/views/attend-class/component/tools/pen.tsx
  25. 231 106
      src/views/attend-class/component/video-play.tsx
  26. 19 14
      src/views/attend-class/component/video.module.less
  27. BIN
      src/views/attend-class/image/bg.png
  28. BIN
      src/views/attend-class/image/bottom_icon1.png
  29. BIN
      src/views/attend-class/image/bottom_icon2.png
  30. BIN
      src/views/attend-class/image/bottom_icon3.png
  31. BIN
      src/views/attend-class/image/bottom_icon4.png
  32. BIN
      src/views/attend-class/image/icon-pause.png
  33. BIN
      src/views/attend-class/image/icon-play.png
  34. BIN
      src/views/attend-class/image/icon-replay.png
  35. BIN
      src/views/attend-class/image/icon-speed.png
  36. BIN
      src/views/attend-class/image/left_hide_icon.png
  37. BIN
      src/views/attend-class/image/right_hide_icon.png
  38. BIN
      src/views/attend-class/image/right_icon1.png
  39. BIN
      src/views/attend-class/image/right_icon10.png
  40. BIN
      src/views/attend-class/image/right_icon11.png
  41. BIN
      src/views/attend-class/image/right_icon12.png
  42. BIN
      src/views/attend-class/image/right_icon2.png
  43. BIN
      src/views/attend-class/image/right_icon3.png
  44. BIN
      src/views/attend-class/image/right_icon4.png
  45. BIN
      src/views/attend-class/image/right_icon5.png
  46. BIN
      src/views/attend-class/image/right_icon7.png
  47. BIN
      src/views/attend-class/image/right_icon8.png
  48. BIN
      src/views/attend-class/image/right_icon9.png
  49. 124 226
      src/views/attend-class/index.module.less
  50. 497 198
      src/views/attend-class/index.tsx
  51. 60 51
      src/views/attend-class/model/chapter/index.module.less
  52. 86 71
      src/views/attend-class/model/chapter/index.tsx
  53. 5 5
      src/views/attend-class/model/select-class/index.module.less
  54. 87 120
      src/views/attend-class/model/source-list/index.module.less
  55. 49 56
      src/views/attend-class/model/source-list/index.tsx
  56. 5 0
      src/views/attend-class/model/train-type/index.tsx
  57. 6 2
      src/views/prepare-lessons/components/lesson-main/train/index.module.less
  58. 103 5
      src/views/prepare-lessons/components/lesson-main/train/index.tsx
  59. 6 1
      src/views/prepare-lessons/components/resource-main/components/select-music/index.module.less
  60. 45 3
      src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx
  61. 6 2
      src/views/prepare-lessons/components/resource-main/index.module.less
  62. 79 8
      src/views/prepare-lessons/components/resource-main/index.tsx
  63. 3 0
      src/views/prepare-lessons/model/select-music/index.tsx
  64. 9 1
      src/views/prepare-lessons/model/select-music/select-item/index.tsx
  65. 0 5
      src/views/prepare-lessons/model/select-resources/index.module.less
  66. 2 1
      src/views/prepare-lessons/model/select-resources/select-item/index.module.less
  67. 5 4
      src/views/prepare-lessons/model/select-resources/select-item/index.tsx

+ 1 - 1
.editorconfig

@@ -5,7 +5,7 @@ root = true
 charset = utf-8
 indent_style = space
 indent_size = 2
-end_of_line = lf
+end_of_line = CRLF
 insert_final_newline = true
 trim_trailing_whitespace = true
 

+ 64 - 37
package-lock.json

@@ -75,7 +75,7 @@
         "husky": "^8.0.0",
         "less": "^4.1.3",
         "lint-staged": "^13.2.2",
-        "naive-ui": "^2.34.4",
+        "naive-ui": "^2.38.1",
         "plop": "^3.1.2",
         "postcss-px-to-viewport": "^1.1.1",
         "prettier": "^2.8.7",
@@ -1626,8 +1626,9 @@
     },
     "node_modules/@css-render/vue3-ssr": {
       "version": "0.15.12",
+      "resolved": "https://registry.npmmirror.com/@css-render/vue3-ssr/-/vue3-ssr-0.15.12.tgz",
+      "integrity": "sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==",
       "dev": true,
-      "license": "MIT",
       "peerDependencies": {
         "vue": "^3.0.11"
       }
@@ -1913,8 +1914,9 @@
     },
     "node_modules/@juggle/resize-observer": {
       "version": "3.4.0",
-      "dev": true,
-      "license": "Apache-2.0"
+      "resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
+      "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
+      "dev": true
     },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
@@ -2346,9 +2348,10 @@
       "license": "MIT"
     },
     "node_modules/@types/katex": {
-      "version": "0.14.0",
-      "dev": true,
-      "license": "MIT"
+      "version": "0.16.7",
+      "resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.7.tgz",
+      "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
+      "dev": true
     },
     "node_modules/@types/liftoff": {
       "version": "4.0.0",
@@ -2360,14 +2363,16 @@
       }
     },
     "node_modules/@types/lodash": {
-      "version": "4.14.197",
-      "dev": true,
-      "license": "MIT"
+      "version": "4.17.0",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.0.tgz",
+      "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
+      "dev": true
     },
     "node_modules/@types/lodash-es": {
-      "version": "4.17.8",
+      "version": "4.17.12",
+      "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "@types/lodash": "*"
       }
@@ -4741,8 +4746,9 @@
     },
     "node_modules/evtd": {
       "version": "0.2.4",
-      "dev": true,
-      "license": "MIT"
+      "resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz",
+      "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==",
+      "dev": true
     },
     "node_modules/execa": {
       "version": "7.2.0",
@@ -7136,33 +7142,50 @@
       "license": "ISC"
     },
     "node_modules/naive-ui": {
-      "version": "2.34.4",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@css-render/plugin-bem": "^0.15.10",
-        "@css-render/vue3-ssr": "^0.15.10",
-        "@types/katex": "^0.14.0",
-        "@types/lodash": "^4.14.181",
-        "@types/lodash-es": "^4.17.6",
-        "async-validator": "^4.0.7",
-        "css-render": "^0.15.10",
-        "date-fns": "^2.28.0",
-        "date-fns-tz": "^1.3.3",
+      "version": "2.38.1",
+      "resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.38.1.tgz",
+      "integrity": "sha512-AnU1FQ7K/CbhguAX++V4kCFjk7h7RvWt4nvZPRjORMpq+fUIlzD+EcQ5Cv1VqDloNF8+eMv4Akc2Ogacc9S+5A==",
+      "dev": true,
+      "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.5.0",
+        "highlight.js": "^11.8.0",
         "lodash": "^4.17.21",
         "lodash-es": "^4.17.21",
-        "seemly": "^0.3.6",
+        "seemly": "^0.3.8",
         "treemate": "^0.3.11",
         "vdirs": "^0.1.8",
         "vooks": "^0.2.12",
-        "vueuc": "^0.4.51"
+        "vueuc": "^0.4.58"
       },
       "peerDependencies": {
         "vue": "^3.0.0"
       }
     },
+    "node_modules/naive-ui/node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "dev": true
+    },
+    "node_modules/naive-ui/node_modules/date-fns-tz": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-2.0.1.tgz",
+      "integrity": "sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==",
+      "dev": true,
+      "peerDependencies": {
+        "date-fns": "2.x"
+      }
+    },
     "node_modules/nanoid": {
       "version": "3.3.6",
       "funding": [
@@ -8623,9 +8646,10 @@
       "optional": true
     },
     "node_modules/seemly": {
-      "version": "0.3.6",
-      "dev": true,
-      "license": "MIT"
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/seemly/-/seemly-0.3.8.tgz",
+      "integrity": "sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==",
+      "dev": true
     },
     "node_modules/selecto": {
       "version": "1.26.0",
@@ -9646,8 +9670,9 @@
     },
     "node_modules/vdirs": {
       "version": "0.1.8",
+      "resolved": "https://registry.npmmirror.com/vdirs/-/vdirs-0.1.8.tgz",
+      "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "evtd": "^0.2.2"
       },
@@ -9832,8 +9857,9 @@
 >>>>>>> feature-3.20
     "node_modules/vooks": {
       "version": "0.2.12",
+      "resolved": "https://registry.npmmirror.com/vooks/-/vooks-0.2.12.tgz",
+      "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "evtd": "^0.2.2"
       },
@@ -10109,9 +10135,10 @@
       }
     },
     "node_modules/vueuc": {
-      "version": "0.4.51",
+      "version": "0.4.58",
+      "resolved": "https://registry.npmmirror.com/vueuc/-/vueuc-0.4.58.tgz",
+      "integrity": "sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
         "@css-render/vue3-ssr": "^0.15.10",
         "@juggle/resize-observer": "^3.3.1",

+ 1 - 1
package.json

@@ -85,7 +85,7 @@
     "husky": "^8.0.0",
     "less": "^4.1.3",
     "lint-staged": "^13.2.2",
-    "naive-ui": "^2.34.4",
+    "naive-ui": "^2.38.1",
     "plop": "^3.1.2",
     "postcss-px-to-viewport": "^1.1.1",
     "prettier": "^2.8.7",

+ 22 - 0
public/roll-call/css/index.css

@@ -65,6 +65,15 @@ a {
   width: 100%;
   text-align: center;
 }
+#menu.menuRight{
+  display: flex;
+  padding: 0 100px;
+  flex-direction: row-reverse;
+}
+#menu.menuLeft{
+  display: flex;
+  padding: 0 100px;
+}
 .bss {
   height: 100vh;
   width: 100%;
@@ -145,6 +154,19 @@ a {
 #table.disabled:hover {
   opacity: 1;
 }
+#backBtn {
+  width: 171px;
+  height: 69px;
+  background: url('../img/back.png') no-repeat center;
+  background-size: contain;
+  border: none;
+  cursor: pointer;
+  transition: opacity 0.2s ease;
+}
+#backBtn:hover {
+  opacity: 0.9;
+  transition: opacity 0.2s ease;
+}
 #sphere {
   width: 171px;
   height: 69px;

BIN
public/roll-call/img/back.png


+ 19 - 3
public/roll-call/index.html

@@ -29,11 +29,12 @@
   <div id="vueBoxs">
     <div class="bss">
       <div class="pageTitle"></div>
-      <div class="iconBack" @click="onBack()"></div>
-      <div id="menu">
+      <div v-if="platform!=='modal'"  class="iconBack" @click="onBack()"></div>
+      <div id="menu" :class="[platform==='modal'?(imagePos==='right'?'menuRight':'menuLeft'):'']">
         <button id="table" :class="tableIndex <= 1 ? 'disabled' : ''" v-show="!animationStatus"
           @click="start()"></button>
         <button id="sphere" v-show="animationStatus" @click="closes()"></button>
+        <button v-if="platform==='modal'" id="backBtn" @click="onBack()"></button>
         <!-- <button id="reset" style="margin-left:40px;" @click="resets()">照片墙</button> -->
         <!-- <button id="lists" @click="listShow = true">中奖名单</button> -->
       </div>
@@ -360,7 +361,10 @@
     data: {
       animationStatus: false,
       tableIndex: 0,
-      listShow: false
+      listShow: false,
+      platform: "",
+      imagePos: ""
+
     },
     methods: {
       keyDowns: function () {
@@ -417,6 +421,11 @@
           api: 'callBack',
           loading: false,
         }, '*');
+      },
+      handleUrlParmas(){
+        const params=getUrlParams(location.href)
+        params?.platform&&(this.platform = params?.platform)
+        params?.imagePos&&(this.imagePos = params?.imagePos)
       }
     },
     created: function () {
@@ -439,6 +448,13 @@
 
       // getStar();
       // this.ckPrice();
+      this.handleUrlParmas()
     }
   })
+  function getUrlParams(url) {
+    let urlStr = url.split('?')[1]
+    const urlSearchParams = new URLSearchParams(urlStr)
+    const result = Object.fromEntries(urlSearchParams.entries())
+    return result
+  }
 </script>

+ 1 - 1
public/version.json

@@ -1 +1 @@
-{"version":1713538570197}
+{"version":1713854848723}

+ 16 - 2
src/components/card-preview/index.module.less

@@ -18,7 +18,11 @@
     }
   }
 }
-
+.dragbom{
+  position: absolute;
+  left: 0;
+  bottom: 0;
+}
 .maxCard {
   width: 1200px;
 
@@ -41,4 +45,14 @@
   .instrumentGroup {
     height: 82vh;
   }
-}
+}
+.classCard{
+  width: 1200px;
+  .musicPreview {
+    height: 70vh;
+
+  }
+  .instrumentGroup {
+    height: 70vh;
+  }
+}

+ 30 - 0
src/components/card-preview/index.tsx

@@ -10,6 +10,9 @@ import InstruemntDetail from '/src/views/prepare-lessons/model/source-instrument
 import TheoryDetail from '/src/views/prepare-lessons/model/source-knowledge/detail';
 import MusicDetail from '/src/views/prepare-lessons/model/source-music/detail';
 import ListenModal from './listen-modal';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
 
 export default defineComponent({
   name: 'card-preview',
@@ -30,6 +33,11 @@ export default defineComponent({
     isDownload: {
       type: Boolean,
       default: false
+    },
+    /** 从哪里使用 */
+    from: {
+      type: String,
+      default: ''
     }
   },
   emit: ['update:show'],
@@ -51,9 +59,28 @@ export default defineComponent({
         item.value = props.item;
       }
     );
+    // 拖动
+    let cardPreviewBoxDragData: any;
+    let cardPreviewBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      cardPreviewBoxClass = 'cardPreviewBoxClass_drag';
+      cardPreviewBoxDragData = useDrag(
+        [
+          `${cardPreviewBoxClass}>.n-card-header`,
+          `${cardPreviewBoxClass} .bom_drag`
+        ],
+        cardPreviewBoxClass,
+        show,
+        users.info.id
+      );
+    }
     return () => (
       <>
         <NModal
+          style={
+            props.from === 'class' ? cardPreviewBoxDragData.styleDrag.value : {}
+          }
           v-model:show={show.value}
           onUpdate:show={() => {
             emit('update:show', show.value);
@@ -65,6 +92,8 @@ export default defineComponent({
           showIcon={false}
           class={[
             'modalTitle background',
+            cardPreviewBoxClass,
+            props.from === 'class' && styles.classCard,
             styles.cardPreview,
             item.value.type === 'PPT' && styles.maxCard,
             props.size === 'large' && styles.cardLarge
@@ -159,6 +188,7 @@ export default defineComponent({
             'MUSIC_WIKI',
             'LISTEN'
           ].includes(item.value.type) && <TheEmpty />}
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
       </>
     );

+ 2 - 2
src/components/card-type/index.tsx

@@ -405,13 +405,13 @@ export default defineComponent({
             ),
             footer: () => (
               <div class={styles.footer}>
-                <div class={styles.title}>
+                <div class={[styles.title, 'footerTitle']}>
                   <NImage
                     class={[styles.titleType]}
                     src={formatType(props.item.type)}
                     objectFit="cover"
                   />
-                  <span class={styles.titleContent}>
+                  <span class={[styles.titleContent, 'titleContent']}>
                     <TheNoticeBar
                       isAnimation={isAnimation.value}
                       text={props.item.title}

BIN
src/components/layout/images/gnyd.png


+ 5 - 2
src/components/layout/index.module.less

@@ -247,7 +247,10 @@
         flex-direction: row;
         align-items: center;
         cursor: pointer;
-
+        &.booxToolDisabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
         img {
           width: 100%;
           height: 100%;
@@ -586,4 +589,4 @@
 
 .popBox {
   z-index: 9999;
-}
+}

+ 4 - 4
src/components/layout/index.tsx

@@ -687,7 +687,7 @@ export default defineComponent({
                 <img src={setTimeIcon} alt="" />
                 计时器
               </div>
-              <div
+              {/* <div
                 class={[
                   styles.booxToolItem,
                   !startClassStatus.value && styles.booxToolDisabled
@@ -702,10 +702,10 @@ export default defineComponent({
                   alt=""
                 />
                 开始上课
-              </div>
+              </div> */}
             </div>
             <div>
-              <div
+              {/* <div
                 class={[
                   styles.booxToolItem,
                   !helpNoteStatus.value && styles.booxToolDisabled
@@ -718,7 +718,7 @@ export default defineComponent({
                 }}>
                 <img src={iconNote} alt="" />
                 功能引导
-              </div>
+              </div> */}
 
               <div
                 class={styles.booxToolItem}

+ 65 - 1
src/components/layout/layoutTop.tsx

@@ -4,7 +4,12 @@ import {
   onMounted,
   nextTick,
   onUnmounted,
+<<<<<<< HEAD
   reactive
+=======
+  reactive,
+  computed
+>>>>>>> e68448baeccfde14389272046acf029ff561a578
 } from 'vue';
 import styles from './index.module.less';
 import { NImage, NBadge, NPopover, NIcon, NModal, NTooltip } from 'naive-ui';
@@ -23,10 +28,11 @@ import inBack from './images/inBack.png';
 import submitBtn from './images/submitBtn.png';
 import sealing from './images/sealing.png';
 import boxBg from './images/boxBg.png';
-import { useRouter } from 'vue-router';
+import { useRouter, useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import opinionIcon from './images/opinionIcon.png';
 import inviteIcon from './images/invite_student_icon.png';
+import gnydIcon from './images/gnyd.png';
 import classHistoryIcon from './images/classHistoryIcon.png';
 import 'animate.css';
 import ForgotPassword from '/src/views/setting/modal/forgotPassword';
@@ -186,6 +192,30 @@ export default defineComponent({
       }
     };
     loadImg(imglist);
+
+    // 功能引导
+    const route = useRoute();
+    const helpNoteList = reactive({
+      baseListTab: ''
+    });
+    const helpNoteStatus = computed(() => {
+      const routePath = route.path;
+      const hidePath = [
+        '/classDetail',
+        '/classStudentDetail',
+        '/notation',
+        '/xiaoku-ai',
+        '/studentDetail',
+        '/classStudentRecode',
+        '/afterWorkDetail'
+      ];
+      // 单独判断个人信息页面[学校设置]有引导
+      if (route.path === '/setting') {
+        return helpNoteList.baseListTab === 'school' ? true : false;
+      } else {
+        return hidePath.includes(routePath) ? false : true;
+      }
+    });
     return () => (
       <>
         <div class={styles.layoutTop}>
@@ -204,6 +234,29 @@ export default defineComponent({
             {/* <NTooltip showArrow={false}>
               {{
                 trigger: () => (
+                  <div
+                    class={[
+                      styles.optons,
+                      !helpNoteStatus.value && styles.booxToolDisabled
+                    ]}
+                    onClick={() => {
+                      if (!helpNoteStatus.value) return;
+                      // 默认滚动到页面顶部,在显示指引
+                      document
+                        .querySelector('#WrapcoreViewWrap')
+                        ?.scrollTo(0, 0);
+                      console.log(route.name, 'guideInfo');
+                      eventGlobal.emit('teacher-guideInfo', route.name);
+                    }}>
+                    <NImage src={gnydIcon} previewDisabled></NImage>
+                  </div>
+                ),
+                default: '功能引导'
+              }}
+            </NTooltip>
+            <NTooltip showArrow={false}>
+              {{
+                trigger: () => (
                   <div class={styles.optons} onClick={showInviteQrcode}>
                     <NBadge dot={suggestionStatus.value} color={'#FF1036'}>
                       <NImage src={inviteIcon} previewDisabled></NImage>
@@ -212,7 +265,11 @@ export default defineComponent({
                 ),
                 default: '邀请学生'
               }}
+<<<<<<< HEAD
             </NTooltip>*/}
+=======
+            </NTooltip>
+>>>>>>> e68448baeccfde14389272046acf029ff561a578
             <NPopover
               width={380}
               class={styles.popoverClassModel}
@@ -430,8 +487,15 @@ export default defineComponent({
               }></SuggestionOption>
           </NModal>
 
+<<<<<<< HEAD
           {/* {state.addStudentVisible ? (
             <div v-model:show={state.addStudentVisible} class={['n-modal-mask', styles.popBox]}>
+=======
+          {state.addStudentVisible ? (
+            <div
+              v-model:show={state.addStudentVisible}
+              class={['n-modal-mask', styles.popBox]}>
+>>>>>>> e68448baeccfde14389272046acf029ff561a578
               <AddStudentModel
                 activeRow={state.activeRow}
                 onClose={() => {

+ 14 - 0
src/hooks/useDrag/dragbom.jsx

@@ -0,0 +1,14 @@
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+// 底部拖动区域
+export default defineComponent({
+  name: 'dragBom',
+  setup() {
+    return () => (
+      <div class={[styles.dragBom, 'bom_drag']}>
+        <div class={styles.box}></div>
+        <div class={styles.box}></div>
+      </div>
+    );
+  }
+});

BIN
src/hooks/useDrag/img/left.png


BIN
src/hooks/useDrag/img/right.png


+ 18 - 0
src/hooks/useDrag/index.module.less

@@ -0,0 +1,18 @@
+.dragBom {
+  width: 100%;
+  height: 42px;
+  display: flex;
+  justify-content: space-between;
+  border-radius: 0 0 16px 16px;
+  overflow: hidden;
+  .box {
+    width: 42px;
+    height: 100%;
+    background: url('./img/left.png') no-repeat;
+    background-size: 100% 100%;
+    &:last-child {
+      background: url('./img/right.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}

+ 153 - 0
src/hooks/useDrag/index.ts

@@ -0,0 +1,153 @@
+// 弹窗拖动
+import { ref, Ref, watch, nextTick, computed } from 'vue';
+
+type posType = {
+  top: number;
+  left: number;
+};
+
+/**
+ * @params classList  可拖动地方的class值,也为唯一值
+ * @params boxClass  容器class值必须为唯一值,这个class和useid拼接 作为缓存主键
+ * @params dragShow  弹窗是否显示
+ * @params userId    当前用户id
+ */
+export default function useDrag(
+  classList: string[],
+  boxClass: string,
+  dragShow: Ref<boolean>,
+  userId: string
+) {
+  const pos = ref<posType>({
+    top: -1, // -1 为初始值 代表没有缓存 默认居中
+    left: -1
+  });
+  const useIdDargClass = userId + boxClass;
+  watch(dragShow, () => {
+    if (dragShow.value) {
+      // 初始化pos值
+      initPos();
+      window.addEventListener('resize', refreshPos);
+      nextTick(() => {
+        const boxClassDom = document.querySelector(
+          `.${boxClass}`
+        ) as HTMLElement;
+        if (!boxClassDom) {
+          return;
+        }
+        classList.map((className: string) => {
+          const classDom = document.querySelector(
+            `.${className}`
+          ) as HTMLElement;
+          if (classDom) {
+            classDom.style.cursor = 'move';
+            drag(classDom, boxClassDom, pos);
+          }
+        });
+      });
+    } else {
+      window.removeEventListener('resize', refreshPos);
+      setCachePos(useIdDargClass, pos.value);
+    }
+  });
+  const styleDrag = computed(() => {
+    // 没有设置拖动的时候保持原本的
+    return pos.value.left === -1 && pos.value.top === -1
+      ? {}
+      : {
+          position: 'fixed',
+          left: `${pos.value.left}px`,
+          top: `${pos.value.top}px`
+        };
+  });
+  function initPos() {
+    const posCache = getCachePos(useIdDargClass);
+    // 有缓存 用缓存的值,没有缓存用默认
+    if (posCache) {
+      pos.value = posCache;
+    }
+  }
+  function refreshPos() {
+    const boxClassDom = document.querySelector(`.${boxClass}`) as HTMLElement;
+    if (!boxClassDom) return;
+    const parentElementRect = boxClassDom.getBoundingClientRect();
+    const clientWidth = document.documentElement.clientWidth;
+    const clientHeight = document.documentElement.clientHeight;
+    const { top, left } = pos.value;
+    const maxLeft = clientWidth - parentElementRect.width;
+    const maxTop = clientHeight - parentElementRect.height;
+    let moveX = left;
+    let moveY = top;
+    const minLeft = 0;
+    const minTop = 0;
+    moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+    moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+    pos.value = {
+      top: moveY,
+      left: moveX
+    };
+  }
+  return {
+    pos,
+    styleDrag
+  };
+}
+
+// 拖动
+function drag(el: HTMLElement, parentElement: HTMLElement, pos: Ref<posType>) {
+  function mousedown(e: MouseEvent) {
+    const parentElementRect = parentElement.getBoundingClientRect();
+    const downX = e.clientX;
+    const downY = e.clientY;
+    const clientWidth = document.documentElement.clientWidth;
+    const clientHeight = document.documentElement.clientHeight;
+    const maxLeft = clientWidth - parentElementRect.width;
+    const maxTop = clientHeight - parentElementRect.height;
+    const minLeft = 0;
+    const minTop = 0;
+    function onMousemove(e: MouseEvent) {
+      let moveX = parentElementRect.left + (e.clientX - downX);
+      let moveY = parentElementRect.top + (e.clientY - downY);
+      moveX = moveX < minLeft ? minLeft : moveX > maxLeft ? maxLeft : moveX;
+      moveY = moveY < minTop ? minTop : moveY > maxTop ? maxTop : moveY;
+      pos.value = {
+        top: moveY,
+        left: moveX
+      };
+    }
+    function onMouseup() {
+      document.removeEventListener('mousemove', onMousemove);
+      document.removeEventListener('mouseup', onMouseup);
+    }
+    document.addEventListener('mousemove', onMousemove);
+    document.addEventListener('mouseup', onMouseup);
+  }
+  el.addEventListener('mousedown', mousedown);
+}
+
+// 缓存
+const localStorageName = 'dragCachePos';
+function getCachePos(useIdDargClass: string): null | undefined | posType {
+  const localCachePos = localStorage.getItem(localStorageName);
+  if (localCachePos) {
+    try {
+      return JSON.parse(localCachePos)[useIdDargClass];
+    } catch {
+      return null;
+    }
+  }
+  return null;
+}
+function setCachePos(useIdDargClass: string, pos: posType) {
+  const localCachePos = localStorage.getItem(localStorageName);
+  let cachePosObj: Record<string, any> = {};
+  if (localCachePos) {
+    try {
+      cachePosObj = JSON.parse(localCachePos);
+    } catch {
+      //
+    }
+  }
+  cachePosObj[useIdDargClass] = pos;
+  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
+}

+ 70 - 30
src/views/attend-class/component/audio-pay.tsx

@@ -30,6 +30,10 @@ export default defineComponent({
     isEmtry: {
       type: Boolean,
       default: false
+    },
+    imagePos: {
+      type: String,
+      default: 'left'
     }
   },
   emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
@@ -219,21 +223,6 @@ export default defineComponent({
             e.stopPropagation();
             emit('reset');
           }}>
-          <div class={styles.actions}>
-            <div class={styles.actionWrap}>
-              <div class={styles.actionBtn} onClick={onToggleAudio}>
-                {audioForms.paused ? (
-                  <img class={styles.playIcon} src={iconplay} />
-                ) : (
-                  <img class={styles.playIcon} src={iconpause} />
-                )}
-              </div>
-              <button class={styles.iconReplay} onClick={onReplay}>
-                <img src={iconReplay} />
-              </button>
-            </div>
-          </div>
-
           <div class={styles.slider}>
             <NSlider
               value={audioForms.currentTimeNum}
@@ -247,21 +236,72 @@ export default defineComponent({
               }}
             />
           </div>
-
-          <div class={styles.actions}>
-            <div class={styles.time}>
-              <div
-                class="plyr__time plyr__time--current"
-                aria-label="Current time">
-                {audioForms.currentTime}
-              </div>
-              <span class={styles.line}>/</span>
-              <div
-                class="plyr__time plyr__time--duration"
-                aria-label="Duration">
-                {audioForms.duration}
-              </div>
-            </div>
+          <div class={styles.tools}>
+            {props.imagePos === 'right' ? (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.time}>
+                    <div
+                      class="plyr__time plyr__time--current"
+                      aria-label="Current time">
+                      {audioForms.currentTime}
+                    </div>
+                    <span class={styles.line}>/</span>
+                    <div
+                      class="plyr__time plyr__time--duration"
+                      aria-label="Duration">
+                      {audioForms.duration}
+                    </div>
+                  </div>
+                </div>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconReplay} />
+                    </button>
+                    <div class={styles.actionBtn} onClick={onToggleAudio}>
+                      {audioForms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+                  </div>
+                </div>
+              </>
+            ) : (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div class={styles.actionBtn} onClick={onToggleAudio}>
+                      {audioForms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconReplay} />
+                    </button>
+                  </div>
+                </div>
+                <div class={styles.actions}>
+                  <div class={styles.time}>
+                    <div
+                      class="plyr__time plyr__time--current"
+                      aria-label="Current time">
+                      {audioForms.currentTime}
+                    </div>
+                    <span class={styles.line}>/</span>
+                    <div
+                      class="plyr__time plyr__time--duration"
+                      aria-label="Duration">
+                      {audioForms.duration}
+                    </div>
+                  </div>
+                </div>
+              </>
+            )}
           </div>
         </div>
       </div>

+ 20 - 16
src/views/attend-class/component/audio.module.less

@@ -50,14 +50,15 @@
   left: 0;
   right: 0;
   width: 100%;
-  background-color: rgba(0, 0, 0, 0.7);
+  background: url('../image/../image/bg.png') no-repeat;
+  background-size: 100% 100%;
   // backdrop-filter: blur(26px);
-  height: 108px;
-  padding: 0 330px 0 40px !important;
+  height: 120px;
+  padding: 0 40px !important;
   transition: all 0.301s;
   display: flex;
-  align-items: center;
-
+  justify-content: center;
+  flex-direction: column;
   .time {
     display: flex;
     justify-content: space-between;
@@ -69,7 +70,8 @@
     line-height: 33px;
 
     &>div {
-      font-size: 24px !important;
+      font-size: 20px !important;
+      color: rgba(255,255,255,0.8);
     }
 
     .line {
@@ -84,7 +86,12 @@
     }
   }
 }
-
+.tools{
+  display: flex;
+  justify-content: space-between;
+  padding: 0 10px;
+  margin-top: 10px;
+}
 .actions {
   display: flex;
   justify-content: space-between;
@@ -100,10 +107,8 @@
   }
 
   .actionBtn {
-    display: flex;
-    width: 60px;
-    height: 60px;
-    padding: 4px 0;
+    width: 40px;
+    height: 40px;
     background: transparent;
     cursor: pointer;
 
@@ -116,11 +121,10 @@
 
   .iconReplay {
     width: 40px;
-    height: 39px;
+    height: 40px;
     background-color: transparent;
     cursor: pointer;
-    margin-left: 22px;
-    margin-right: 10px;
+    margin: 0 22px;
 
     &>img {
       width: 100%;
@@ -131,7 +135,7 @@
 
 .slider {
   width: 100%;
-  padding: 0 8px 0 12px;
+  padding-top: 6px;
 
   :global {
 
@@ -140,4 +144,4 @@
       transition: all .2s;
     }
   }
-}
+}

+ 6 - 2
src/views/attend-class/component/rhythm-modal/index.tsx

@@ -14,6 +14,10 @@ export default defineComponent({
     activeStatus: {
       type: Boolean,
       default: false
+    },
+    imagePos: {
+      type: String,
+      default: 'left'
     }
   },
   emits: ['setIframe'],
@@ -25,11 +29,11 @@ export default defineComponent({
       location.origin
     }/classroom-app/#/tempo-practice?v=${+new Date()}&platform=modal&dataJson=${
       props.item.dataJson
-    }&Authorization=${userStore.getToken}&win=pc`;
+    }&Authorization=${userStore.getToken}&win=pc&imagePos=${props.imagePos}`;
     if (/(localhost)/.test(location.host)) {
       src = `http://localhost:9002/#/tempo-practice?v=${+new Date()}&platform=modal&dataJson=${
         props.item.dataJson
-      }&Authorization=${userStore.getToken}&win=pc`;
+      }&Authorization=${userStore.getToken}&win=pc&imagePos=${props.imagePos}`;
     }
 
     watch(

+ 7 - 1
src/views/attend-class/component/tools/pen.module.less

@@ -4,7 +4,7 @@
   right: 0;
   bottom: 0;
   top: 0;
-  z-index: 501;
+  z-index: 2899;
 }
 
 .open {
@@ -85,8 +85,14 @@
 
 .removeVisiable {
   width: 432px;
+  position: relative;
 
   :global {
+    .bom_drag{
+      position: absolute;
+      bottom: 0;
+      left: 0;
+    }
     .n-card-header {
       font-size: max(22px, 16Px);
     }

+ 32 - 4
src/views/attend-class/component/tools/pen.tsx

@@ -1,8 +1,11 @@
-import { defineComponent, toRefs, ref, PropType, reactive } from 'vue';
+import { defineComponent, toRefs, ref, PropType, reactive, toRef } from 'vue';
 import styles from './pen.module.less';
 import { ToolType } from '../../index';
 import { NButton, NModal, NSpace } from 'naive-ui';
 import { iframeDislableKeyboard } from '/src/utils';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
 
 export default defineComponent({
   name: 'pen-page',
@@ -22,6 +25,14 @@ export default defineComponent({
     callStudents: {
       type: Array,
       default: () => []
+    },
+    imagePos: {
+      type: String,
+      default: 'left'
+    },
+    isDrag: {
+      type: Boolean,
+      default: false
     }
   },
   setup(props) {
@@ -42,9 +53,23 @@ export default defineComponent({
     let src = `${origin}/classroom-whiteboard?t=${+new Date()}`;
 
     if (props.type === 'call') {
-      src = `${origin}/roll-call/index.html?t=${+new Date()}`;
+      src = `${origin}/roll-call/index.html?t=${+new Date()}&platform=modal&imagePos=${
+        props.imagePos
+      }`;
+    }
+    // 拖动
+    let penBoxDragData: any;
+    let penBoxClass: string;
+    if (props.isDrag) {
+      const users = useUserStore();
+      penBoxClass = 'penBoxClass_drag';
+      penBoxDragData = useDrag(
+        [`${penBoxClass}>.n-card-header`, `${penBoxClass} .bom_drag`],
+        penBoxClass,
+        toRef(modal, 'status'),
+        users.info.id
+      );
     }
-
     return () => (
       <div
         class={[
@@ -108,12 +133,14 @@ export default defineComponent({
 
         {/* 布置作业 */}
         <NModal
+          style={props.isDrag ? penBoxDragData.styleDrag.value : {}}
+          z-index={3000}
           transformOrigin="center"
           v-model:show={modal.status}
           preset="card"
           // class={styles.attendClassModal}
           title={modal.title}
-          class={['modalTitle', styles.removeVisiable]}>
+          class={['modalTitle', styles.removeVisiable, penBoxClass]}>
           <div class={styles.studentRemove}>
             <p>{modal.content}</p>
             {/* <div class={styles.modelAttendContent}>
@@ -139,6 +166,7 @@ export default defineComponent({
                 确认
               </NButton>
             </NSpace>
+            {props.isDrag && <Dragbom></Dragbom>}
           </div>
         </NModal>
       </div>

+ 231 - 106
src/views/attend-class/component/video-play.tsx

@@ -37,6 +37,10 @@ export default defineComponent({
     isEmtry: {
       type: Boolean,
       default: false
+    },
+    imagePos: {
+      type: String,
+      default: 'left'
     }
   },
   emits: [
@@ -69,7 +73,6 @@ export default defineComponent({
     const videoRef = ref();
     const videoItem = ref();
     const videoID = ref('video' + Date.now() + Math.floor(Math.random() * 100));
-    const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100);
 
     // 对时间进行格式化
     const timeFormat = (num: number) => {
@@ -219,11 +222,6 @@ export default defineComponent({
       }); // player-container-id 为播放器容器 ID,必须与 html 中一致
 
       __init();
-
-      document.getElementById(speedBtnId)?.addEventListener('click', e => {
-        e.stopPropagation();
-        videoFroms.speedControl = !videoFroms.speedControl;
-      });
     });
     const stop = () => {
       videoItem.value.currentTime(0);
@@ -295,91 +293,6 @@ export default defineComponent({
             emit('close');
             emit('reset');
           }}>
-          <div class={styles.actions}>
-            <div class={styles.actionWrap}>
-              <div
-                class={styles.actionBtn}
-                onClick={() => {
-                  videoFroms.speedControl = false;
-                  onToggleVideo();
-                }}>
-                {videoFroms.paused ? (
-                  <img class={styles.playIcon} src={iconplay} />
-                ) : (
-                  <img class={styles.playIcon} src={iconpause} />
-                )}
-              </div>
-
-              <button class={styles.iconReplay} onClick={onReplay}>
-                <img src={iconLoop} />
-              </button>
-
-              <div class={styles.actionBtnSpeed} id={speedBtnId}>
-                <img src={iconSpeed} />
-
-                <div
-                  style={{
-                    display: videoFroms.speedControl ? 'block' : 'none'
-                  }}>
-                  <div
-                    class={styles.sliderPopup}
-                    onClick={(e: Event) => {
-                      e.stopPropagation();
-                    }}>
-                    <i
-                      class={styles.iconAdd}
-                      onClick={() => {
-                        if (videoFroms.defaultSpeed >= 1.5) {
-                          return;
-                        }
-
-                        if (videoItem.value) {
-                          videoFroms.defaultSpeed =
-                            (videoFroms.defaultSpeed * 10 + 1) / 10;
-                          videoItem.value.playbackRate(videoFroms.defaultSpeed);
-                        }
-                      }}></i>
-                    <NSlider
-                      value={videoFroms.defaultSpeed}
-                      step={0.1}
-                      max={1.5}
-                      min={0.5}
-                      vertical
-                      tooltip={false}
-                      onUpdate:value={(val: number) => {
-                        videoFroms.defaultSpeed = val;
-
-                        if (videoItem.value) {
-                          videoItem.value.playbackRate(videoFroms.defaultSpeed);
-                        }
-                      }}>
-                      {{
-                        thumb: () => (
-                          <div class={styles.sliderPoint}>
-                            {videoFroms.defaultSpeed}
-                            <span>x</span>
-                          </div>
-                        )
-                      }}
-                    </NSlider>
-                    <i
-                      class={[styles.iconCut]}
-                      onClick={() => {
-                        if (videoFroms.defaultSpeed <= 0.5) {
-                          return;
-                        }
-                        if (videoItem.value) {
-                          videoFroms.defaultSpeed =
-                            (videoFroms.defaultSpeed * 10 - 1) / 10;
-                          videoItem.value.playbackRate(videoFroms.defaultSpeed);
-                        }
-                      }}></i>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
           <div class={styles.slider}>
             <NSlider
               value={videoFroms.currentTimeNum}
@@ -394,23 +307,235 @@ export default defineComponent({
               }}
             />
           </div>
-
-          <div class={styles.actions}>
-            <div class={styles.actionWrap}>
-              <div class={styles.time}>
-                <div
-                  class="plyr__time plyr__time--current"
-                  aria-label="Current time">
-                  {videoFroms.currentTime}
+          <div class={styles.tools}>
+            {props.imagePos === 'right' ? (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div class={styles.time}>
+                      <div
+                        class="plyr__time plyr__time--current"
+                        aria-label="Current time">
+                        {videoFroms.currentTime}
+                      </div>
+                      <span class={styles.line}>/</span>
+                      <div
+                        class="plyr__time plyr__time--duration"
+                        aria-label="Duration">
+                        {videoFroms.duration}
+                      </div>
+                    </div>
+                  </div>
                 </div>
-                <span class={styles.line}>/</span>
-                <div
-                  class="plyr__time plyr__time--duration"
-                  aria-label="Duration">
-                  {videoFroms.duration}
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div
+                      class={styles.actionBtnSpeed}
+                      onClick={e => {
+                        e.stopPropagation();
+                        videoFroms.speedControl = !videoFroms.speedControl;
+                      }}>
+                      <img src={iconSpeed} />
+                      <div
+                        style={{
+                          display: videoFroms.speedControl ? 'block' : 'none'
+                        }}>
+                        <div
+                          class={styles.sliderPopup}
+                          onClick={(e: Event) => {
+                            e.stopPropagation();
+                          }}>
+                          <i
+                            class={styles.iconAdd}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed >= 1.5) {
+                                return;
+                              }
+
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                          <NSlider
+                            value={videoFroms.defaultSpeed}
+                            step={0.1}
+                            max={1.5}
+                            min={0.5}
+                            vertical
+                            tooltip={false}
+                            onUpdate:value={(val: number) => {
+                              videoFroms.defaultSpeed = val;
+
+                              if (videoItem.value) {
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}>
+                            {{
+                              thumb: () => (
+                                <div class={styles.sliderPoint}>
+                                  {videoFroms.defaultSpeed}
+                                  <span>x</span>
+                                </div>
+                              )
+                            }}
+                          </NSlider>
+                          <i
+                            class={[styles.iconCut]}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed <= 0.5) {
+                                return;
+                              }
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                        </div>
+                      </div>
+                    </div>
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconLoop} />
+                    </button>
+                    <div
+                      class={styles.actionBtn}
+                      onClick={() => {
+                        videoFroms.speedControl = false;
+                        onToggleVideo();
+                      }}>
+                      {videoFroms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+                  </div>
+                </div>
+              </>
+            ) : (
+              <>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div
+                      class={styles.actionBtn}
+                      onClick={() => {
+                        videoFroms.speedControl = false;
+                        onToggleVideo();
+                      }}>
+                      {videoFroms.paused ? (
+                        <img class={styles.playIcon} src={iconplay} />
+                      ) : (
+                        <img class={styles.playIcon} src={iconpause} />
+                      )}
+                    </div>
+
+                    <button class={styles.iconReplay} onClick={onReplay}>
+                      <img src={iconLoop} />
+                    </button>
+
+                    <div
+                      class={styles.actionBtnSpeed}
+                      onClick={e => {
+                        e.stopPropagation();
+                        videoFroms.speedControl = !videoFroms.speedControl;
+                      }}>
+                      <img src={iconSpeed} />
+
+                      <div
+                        style={{
+                          display: videoFroms.speedControl ? 'block' : 'none'
+                        }}>
+                        <div
+                          class={styles.sliderPopup}
+                          onClick={(e: Event) => {
+                            e.stopPropagation();
+                          }}>
+                          <i
+                            class={styles.iconAdd}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed >= 1.5) {
+                                return;
+                              }
+
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 + 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                          <NSlider
+                            value={videoFroms.defaultSpeed}
+                            step={0.1}
+                            max={1.5}
+                            min={0.5}
+                            vertical
+                            tooltip={false}
+                            onUpdate:value={(val: number) => {
+                              videoFroms.defaultSpeed = val;
+
+                              if (videoItem.value) {
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}>
+                            {{
+                              thumb: () => (
+                                <div class={styles.sliderPoint}>
+                                  {videoFroms.defaultSpeed}
+                                  <span>x</span>
+                                </div>
+                              )
+                            }}
+                          </NSlider>
+                          <i
+                            class={[styles.iconCut]}
+                            onClick={() => {
+                              if (videoFroms.defaultSpeed <= 0.5) {
+                                return;
+                              }
+                              if (videoItem.value) {
+                                videoFroms.defaultSpeed =
+                                  (videoFroms.defaultSpeed * 10 - 1) / 10;
+                                videoItem.value.playbackRate(
+                                  videoFroms.defaultSpeed
+                                );
+                              }
+                            }}></i>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div class={styles.actions}>
+                  <div class={styles.actionWrap}>
+                    <div class={styles.time}>
+                      <div
+                        class="plyr__time plyr__time--current"
+                        aria-label="Current time">
+                        {videoFroms.currentTime}
+                      </div>
+                      <span class={styles.line}>/</span>
+                      <div
+                        class="plyr__time plyr__time--duration"
+                        aria-label="Duration">
+                        {videoFroms.duration}
+                      </div>
+                    </div>
+                  </div>
                 </div>
-              </div>
-            </div>
+              </>
+            )}
           </div>
         </div>
       </div>

+ 19 - 14
src/views/attend-class/component/video.module.less

@@ -29,14 +29,15 @@
     left: 0;
     right: 0;
     width: 100%;
-    background-color: rgba(0, 0, 0, 0.7);
+    background: url('../image/../image/bg.png') no-repeat;
+    background-size: 100% 100%;
     // backdrop-filter: blur(26px);
-    height: 108px;
-    padding: 0 330px 0 40px !important;
-    display: flex;
-    align-items: center;
+    height: 120px;
+    padding: 0 40px !important;
     transition: all 0.301s;
-    z-index: 9;
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
 
     .time {
       display: flex;
@@ -49,7 +50,8 @@
       line-height: 33px;
 
       &>div {
-        font-size: 24px !important;
+        font-size: 20px !important;
+        color: rgba(255,255,255,0.8);
       }
 
       .line {
@@ -64,7 +66,12 @@
       }
     }
   }
-
+  .tools{
+    display: flex;
+    justify-content: space-between;
+    padding: 0 10px;
+    margin-top: 10px;
+  }
   .actions {
     display: flex;
     justify-content: space-between;
@@ -80,10 +87,8 @@
     }
 
     .actionBtn {
-      display: flex;
-      width: 60px;
-      height: 60px;
-      padding: 4px 0;
+      width: 40px;
+      height: 40px;
       background: transparent;
       cursor: pointer;
 
@@ -123,7 +128,7 @@
 
   .slider {
     width: 100%;
-    padding: 0 4px 0 16px;
+    padding-top: 6px;
 
     :global {
 
@@ -345,4 +350,4 @@
 //       }
 //     }
 //   }
-// }
+// }

BIN
src/views/attend-class/image/bg.png


BIN
src/views/attend-class/image/bottom_icon1.png


BIN
src/views/attend-class/image/bottom_icon2.png


BIN
src/views/attend-class/image/bottom_icon3.png


BIN
src/views/attend-class/image/bottom_icon4.png


BIN
src/views/attend-class/image/icon-pause.png


BIN
src/views/attend-class/image/icon-play.png


BIN
src/views/attend-class/image/icon-replay.png


BIN
src/views/attend-class/image/icon-speed.png


BIN
src/views/attend-class/image/left_hide_icon.png


BIN
src/views/attend-class/image/right_hide_icon.png


BIN
src/views/attend-class/image/right_icon1.png


BIN
src/views/attend-class/image/right_icon10.png


BIN
src/views/attend-class/image/right_icon11.png


BIN
src/views/attend-class/image/right_icon12.png


BIN
src/views/attend-class/image/right_icon2.png


BIN
src/views/attend-class/image/right_icon3.png


BIN
src/views/attend-class/image/right_icon4.png


BIN
src/views/attend-class/image/right_icon5.png


BIN
src/views/attend-class/image/right_icon7.png


BIN
src/views/attend-class/image/right_icon8.png


BIN
src/views/attend-class/image/right_icon9.png


+ 124 - 226
src/views/attend-class/index.module.less

@@ -23,8 +23,6 @@
   }
 }
 
-
-
 .coursewarePlay {
   position: relative;
   height: 100vh;
@@ -222,7 +220,6 @@
 }
 
 :global {
-
   .top-enter-active,
   .top-leave-active {
     transition: transform 0.5s;
@@ -268,65 +265,17 @@
 }
 
 .drawerContainer {
-  width: 360px !important;
-
-  :global {
-    .n-drawer-header {
-      background-color: #F5F6FA !important;
-    }
-  }
-
-
-  .cardContainer {
-    margin-bottom: 24px;
-  }
-
-  :global {
-    .n-drawer-body-content-wrapper {
-      padding: 8px 0px 0px !important;
-      text-align: center;
-
-      &>div {
-        // margin-bottom: 24px;
-      }
+  width: 860px;
+  :global{
+    .n-card-header{
+      background-color: #ffffff !important;
     }
-
-    .n-drawer-header {
+    .n-card__content{
       position: relative;
-      justify-content: center !important;
-      padding-top: 20px !important;
-      padding-bottom: 17px !important;
-      border-bottom: 0 !important;
-
-      .n-drawer-header__main {
-        position: relative;
-        z-index: 2;
-        font-size: max(18px, 13Px);
-        font-weight: 600;
-        color: #131415;
-        max-width: 220px;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-
-        // &::after {
-        //   position: absolute;
-        //   bottom: -4px;
-        //   left: 0;
-        //   z-index: -1;
-        //   content: ' ';
-        //   width: 100%;
-        //   display: inline-block;
-        //   height: 10px;
-        //   background: linear-gradient(90deg,
-        //       #77bbff 0%,
-        //       rgba(163, 231, 255, 0.22) 100%);
-        // }
-      }
-
-      .n-drawer-header__close {
+      .bom_drag{
         position: absolute;
-        right: 26px;
+        bottom: 0;
+        left: 0;
       }
     }
   }
@@ -365,7 +314,6 @@
     gap: 0 48px !important;
   }
 
-
   .btnItem {
     display: flex;
     align-items: center;
@@ -386,16 +334,13 @@
     p {
       padding-top: 6px;
       font-size: 18px;
-      color: #FFFFFF;
+      color: #ffffff;
       line-height: 25px;
       text-align: center;
     }
-
-
   }
 }
 
-
 .attendClassModal {
   width: 442px;
   border-radius: 16px;
@@ -426,8 +371,6 @@
     }
   }
 
-
-
   .modelAttendContent {
     font-size: max(18px, 16px);
     color: #777777;
@@ -467,7 +410,6 @@
   }
 }
 
-
 .toolboxImg {
   width: 83px;
   height: 83px;
@@ -566,7 +508,6 @@
   background-color: #fff;
 }
 
-
 .showModalTone {
   width: 500px;
 
@@ -580,16 +521,26 @@
       text-align: center;
 
       span {
-        color: #EA4132;
+        color: #ea4132;
       }
     }
   }
 }
-
+.dragbom{
+  position: absolute;
+  bottom: 0;
+  left: 0;
+}
 .removeVisiable {
   width: 432px;
+  position: relative;
 
   :global {
+    .bom_drag{
+      position: absolute;
+      bottom: 0;
+      left: 0;
+    }
     .n-card-header {
       font-size: max(22px, 16Px);
     }
@@ -605,7 +556,7 @@
       text-align: center;
 
       span {
-        color: #EA4132;
+        color: #ea4132;
       }
     }
   }
@@ -638,136 +589,125 @@
   }
 }
 
-.rightColumn {
+.rightColumn,
+.leftColumn {
   position: absolute;
-  right: 0;
-  top: 50%;
-  transform: translateY(-50%);
+  right: 14px;
+  bottom: 130px;
   background: rgba(0, 0, 0, 0.3);
   border-radius: 10px;
-  width: 76px;
+  width: 52px;
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 10px 0;
+  padding: 10px;
   transition: all 0.3s;
-
-  .rightItem {
-    width: 54px;
-    height: 54px;
-    margin-bottom: 12px;
+  .columnItemBox {
+    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+    padding: 5px 0;
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+  .columnItem {
+    padding: 5px;
     cursor: pointer;
     position: relative;
     display: flex;
     align-items: center;
     justify-content: center;
     z-index: 9;
-
     &.active,
     &:hover {
       background: RGBA(255, 255, 255, 0.3);
       border-radius: 8px;
       z-index: 9;
     }
-
-    &::after {
-      content: "";
-      position: absolute;
-      left: 50%;
-      bottom: -6px;
-      transform: translateX(-50%);
-      width: 44px;
-      height: 1px;
-      background: rgba(255, 255, 255, 0.2);
-    }
-
     img {
-      width: 34px;
-      height: 34px;
-      margin: auto;
+      width: 28px;
+      height: 28px;
       z-index: 9;
     }
-
-    .rightTips {
-      position: absolute;
-      right: 24px;
-      top: 50;
-      transform: translateX(-50%);
-      background: rgba(0, 0, 0, 0.7);
-      border-radius: 6px;
-      width: 88px;
-      height: 34px;
-      line-height: 34px;
-      text-align: center;
-      font-size: max(16px, 12Px);
-      font-family: PingFangSC, PingFang SC;
-      font-weight: 500;
-      color: #FFFFFF;
-      // transition: all 0.8s;
-      z-index: 1;
-      opacity: 0;
-    }
-
-    &:hover {
-      .rightTips {
-        opacity: 1;
-      }
-    }
-  }
-
-  .rightItem:last-child {
-    width: 54px;
-    height: 36px;
-
-    img {
-      width: 18px;
-      height: 18px;
-    }
-
-    &::after {
-      display: none;
-    }
   }
 
   .itemDisabled {
     opacity: 0.7;
     cursor: not-allowed;
   }
-
-  .itemHide {
-    display: none;
+}
+:global {
+  .columnItemTooltip.n-tooltip {
+    background: #ffffff !important;
+    box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1) !important;
+    border-radius: 20px !important;
+    padding: 0 !important;
+    margin-left: 22px !important;
+    &.rightColumnItemTooltip {
+      margin-right: 22px !important;
+    }
+    .tools {
+      padding: 10px 20px;
+      display: flex;
+      > div {
+        display: flex;
+        flex-direction: column;
+        margin-right: 16px;
+        cursor: pointer;
+        &.active,
+        &:hover {
+          opacity: 0.8;
+        }
+        &:last-child {
+          margin-right: 0;
+        }
+        img {
+          width: 56px;
+          height: 56px;
+        }
+        .tit {
+          margin-top: 4px;
+          text-align: center;
+          font-weight: 400;
+          font-size: max(6px, 12Px);
+          color: #383a3d;
+        }
+      }
+    }
   }
 }
-
-.rightColumnZ {
-  z-index: -1;
+.leftColumn {
+  left: 14px;
 }
-
 .rightColumnHide {
-  transform: translate(80px, -50%);
-
-  .rightTips {
-    display: none;
-  }
+  transform: translate(80px);
 }
-
-.rightHideIcon {
-  width: 30px;
+.leftColumnHide {
+  transform: translate(-80px);
+}
+.rightHideIcon,
+.leftHideIcon {
+  width: 28px;
   height: 60px;
   position: absolute;
   right: 0;
-  top: 50%;
-  transform: translate(60px, -50%);
+  bottom: 300px;
+  transform: translate(60px);
   z-index: 10;
   cursor: pointer;
   // transition: all 0.5s;
 }
-
+.leftHideIcon {
+  left: 0;
+  transform: translate(-60px);
+}
 .rightIconShow {
   animation: rightIn 0.3s ease 0.3s;
   animation-fill-mode: forwards;
 }
-
+.leftIconShow {
+  animation: leftIn 0.3s ease 0.3s;
+  animation-fill-mode: forwards;
+}
 @keyframes rightIn {
   0% {
     transform: translate(60px, -50%);
@@ -777,81 +717,40 @@
     transform: translate(0, -50%);
   }
 }
+@keyframes leftIn {
+  0% {
+    transform: translate(-60px, -50%);
+  }
+
+  100% {
+    transform: translate(0, -50%);
+  }
+}
 
 .instrumentContainer {
-  background: #FFFFFF;
+  background: #ffffff;
   padding: 68px;
   height: 100%;
 }
 
-.bottomColumn {
-  position: fixed;
-  right: 40px;
-  bottom: 26px;
-  display: flex;
-  z-index: 2;
-
-  .bottomItem {
-    width: 54px;
-    height: 54px;
-    background: rgba(0, 0, 0, 0.3);
-    border-radius: 10px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    margin-right: 16px;
-    position: relative;
-    cursor: pointer;
-
-    .bottomTips {
-      position: absolute;
-      left: 50%;
-      top: -40px;
-      transform: translateX(-50%);
-      opacity: 0;
-      background: rgba(0, 0, 0, 0.7);
-      border-radius: 6px;
-      width: 88px;
-      height: 34px;
-      line-height: 34px;
-      text-align: center;
-      font-size: max(16px, 12Px);
-      font-family: PingFangSC, PingFang SC;
-      font-weight: 500;
-      color: #FFFFFF;
-    }
-
-    &.active,
-    &:hover {
-      background: rgba(0, 0, 0, 0.5);
-
-      .bottomTips {
-        opacity: 1;
+:global{
+  .selResourBoxClass_drag{
+    .select-resource{
+      padding-bottom: 0;
+      height: 50vh;
+      .list_container{
+        --listContainerHeight:50vh;
+        .selresources_item_Wrap{
+          width: calc(100% / 3) !important;
+          padding-bottom: calc(100% / 3 * 0.73333) !important;
+        }
       }
     }
-
-    img {
-      width: 40px;
-      height: 40px;
-    }
-  }
-
-  .bottomItem:last-child {
-    margin-right: 0;
   }
-
-  .itemDisabled {
-    opacity: 0.7;
-    cursor: not-allowed;
-  }
-
-
 }
-
 .selectMusicModal {
   position: relative;
-  width: 1352px;
-
+  width: 1050px;
   :global {
     .n-card-header {
       position: absolute;
@@ -871,7 +770,7 @@
 }
 
 .workVisiable {
-  width: 1258px;
+  width: 1200px;
 }
 
 .workContainer {
@@ -880,13 +779,12 @@
 
   .workTrain {
     flex: 1;
-    height: 75vh;
+    height: 55vh;
 
-    &>div {
+    & > div {
       padding-top: 15px;
     }
   }
-
   :global {
     .train-container {
       // max-height: calc(var(--window-page-lesson-height) - 135px) !important;
@@ -900,7 +798,7 @@
 
   .resourceMain {
     flex: 0 0 360px;
-    height: 75vh;
+    height: 55vh;
     box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
   }
-}
+}

+ 497 - 198
src/views/attend-class/index.tsx

@@ -7,7 +7,8 @@ import {
   Transition,
   computed,
   nextTick,
-  watch
+  watch,
+  toRef
 } from 'vue';
 import styles from './index.module.less';
 import 'plyr/dist/plyr.css';
@@ -67,13 +68,16 @@ import rightIconMetronome from './image/right_icon5.png';
 import rightIconTuner from './image/right_icon6.png';
 import rightIconTimer from './image/right_icon7.png';
 import rightIconCall from './image/right_icon10.png';
-import rightIconPackUp from './image/right_icon8.png';
+import rightIconPackUp from './image/right_icon11.png';
+import leftIconPackUp from './image/right_icon8.png';
 import rightIconMusic from './image/right_icon9.png';
 import bottomIconSwitch from './image/bottom_icon1.png';
 import bottomIconResource from './image/bottom_icon2.png';
 import bottomIconPre from './image/bottom_icon3.png';
 import bottomIconNext from './image/bottom_icon4.png';
+import rightIconTool from './image/right_icon12.png';
 import rightHideIcon from './image/right_hide_icon.png';
+import leftHideIcon from './image/left_hide_icon.png';
 import SelectResources from '../prepare-lessons/model/select-resources';
 import { getStudentAfterWork, getStudentList } from '../studentList/api';
 import TheNoticeBar from '/src/components/TheNoticeBar';
@@ -90,6 +94,8 @@ import ResourceMain from '../prepare-lessons/components/resource-main';
 import { useResizeObserver } from '@vueuse/core';
 import { storage } from '/src/utils/storage';
 import { ACCESS_TOKEN_ADMIN } from '/src/store/mutation-types';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
 
 export type ToolType = 'init' | 'pen' | 'whiteboard' | 'call';
 export type ToolItem = {
@@ -193,6 +199,7 @@ export default defineComponent({
       selectClassStatus: false, // 选择课件
       homeworkStatus: true, // 布置作业完成时
       removeVisiable: false,
+      nextEndShow: false,
       removeTitle: '',
       removeContent: '',
       removeCourseStatus: false, // 是否布置作业
@@ -351,7 +358,16 @@ export default defineComponent({
       getDetail();
       getLessonCoursewareDetail();
       if (data.type === 'preview') {
-        rightList.splice(6, 1);
+        // 预览隐藏点名和布置作业
+        const hideListIds = [2, 10];
+        let index = rightList.length - 1;
+        while (index >= 0) {
+          if (hideListIds.includes(rightList[index].id)) {
+            console.log(index);
+            rightList.splice(index, 1);
+          }
+          index--;
+        }
       }
       rollCallStudentList();
     });
@@ -399,7 +415,6 @@ export default defineComponent({
         }, 200);
       }
     );
-
     const formatParentId = (id: any, list: any, ids = [] as any) => {
       for (const item of list) {
         if (item.knowledgeList && item.knowledgeList.length > 0) {
@@ -814,74 +829,76 @@ export default defineComponent({
           handleSwipeChange(popupData.activeIndex + 1);
           return;
         }
+        data.nextEndShow = true;
+      }
+    };
+    // 当前课件结束之后选择下一个课件
+    function handleNextEnd() {
+      // 获取当前是哪个章节
+      let detailIndex = popupData.chapterDetails.findIndex(
+        (item: any) => item.id == data.lessonCoursewareDetailId
+      );
+      const detailItem =
+        popupData.chapterDetails[detailIndex]?.knowledgeList || [];
+      let lessonIndex = detailItem.findIndex(
+        (item: any) => item.id == data.detailId
+      );
 
-        // 获取当前是哪个章节
-        let detailIndex = popupData.chapterDetails.findIndex(
-          (item: any) => item.id == data.lessonCoursewareDetailId
-        );
-        const detailItem =
-          popupData.chapterDetails[detailIndex]?.knowledgeList || [];
-        let lessonIndex = detailItem.findIndex(
-          (item: any) => item.id == data.detailId
-        );
-
-        let lessonStatus = false; // 当前章节下面是否有内容
-        let lessonCoursewareDetailId = '';
-        let coursewareDetailKnowledgeId = '';
-        while (lessonIndex < detailItem.length - 1) {
-          lessonIndex++;
-          if (lessonIndex >= 0) {
-            if (detailItem[lessonIndex].coursewareNum > 0) {
-              lessonStatus = true;
-              lessonCoursewareDetailId =
-                detailItem[lessonIndex].lessonCoursewareDetailId;
-              coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
-            }
-          }
-          if (lessonStatus) {
-            break;
+      let lessonStatus = false; // 当前章节下面是否有内容
+      let lessonCoursewareDetailId = '';
+      let coursewareDetailKnowledgeId = '';
+      while (lessonIndex < detailItem.length - 1) {
+        lessonIndex++;
+        if (lessonIndex >= 0) {
+          if (detailItem[lessonIndex].coursewareNum > 0) {
+            lessonStatus = true;
+            lessonCoursewareDetailId =
+              detailItem[lessonIndex].lessonCoursewareDetailId;
+            coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
           }
         }
-        // 判断当前章节下面课程是否有内容,否则往下一个章节走
         if (lessonStatus) {
-          popupData.courseId = coursewareDetailKnowledgeId;
-          data.selectClassStatus = true;
-          return;
+          break;
         }
+      }
+      // 判断当前章节下面课程是否有内容,否则往下一个章节走
+      if (lessonStatus) {
+        popupData.courseId = coursewareDetailKnowledgeId;
+        data.selectClassStatus = true;
+        return;
+      }
 
-        let nextLessonStatus = false;
-        while (detailIndex <= popupData.chapterDetails.length - 1) {
-          detailIndex++;
-          const tempDetail =
-            popupData.chapterDetails[detailIndex]?.knowledgeList || [];
-          let tempLessonLength = 0;
-          while (tempLessonLength <= tempDetail.length - 1) {
-            if (tempDetail[tempLessonLength].coursewareNum > 0) {
-              nextLessonStatus = true;
-              lessonCoursewareDetailId =
-                tempDetail[tempLessonLength].lessonCoursewareDetailId;
-              coursewareDetailKnowledgeId = tempDetail[tempLessonLength].id;
-            }
-            tempLessonLength++;
-            if (nextLessonStatus) {
-              break;
-            }
+      let nextLessonStatus = false;
+      while (detailIndex <= popupData.chapterDetails.length - 1) {
+        detailIndex++;
+        const tempDetail =
+          popupData.chapterDetails[detailIndex]?.knowledgeList || [];
+        let tempLessonLength = 0;
+        while (tempLessonLength <= tempDetail.length - 1) {
+          if (tempDetail[tempLessonLength].coursewareNum > 0) {
+            nextLessonStatus = true;
+            lessonCoursewareDetailId =
+              tempDetail[tempLessonLength].lessonCoursewareDetailId;
+            coursewareDetailKnowledgeId = tempDetail[tempLessonLength].id;
           }
-
+          tempLessonLength++;
           if (nextLessonStatus) {
             break;
           }
         }
 
-        // 判断当前章节下面课程是否有内容,否则往上一个章节走
         if (nextLessonStatus) {
-          popupData.courseId = coursewareDetailKnowledgeId;
-          data.selectClassStatus = true;
-          return;
+          break;
         }
       }
-    };
 
+      // 判断当前章节下面课程是否有内容,否则往上一个章节走
+      if (nextLessonStatus) {
+        popupData.courseId = coursewareDetailKnowledgeId;
+        data.selectClassStatus = true;
+        return;
+      }
+    }
     /** 弹窗关闭 */
     const handleClosePopup = () => {
       const item = data.itemList[popupData.activeIndex];
@@ -894,11 +911,11 @@ export default defineComponent({
       }
     };
     document.body.addEventListener('keyup', (e: KeyboardEvent) => {
-      if (e.code === 'ArrowLeft') {
+      if (e.code === 'ArrowUp') {
         // if (popupData.activeIndex === 0) return;
         setModalOpen();
         handlePreAndNext('up');
-      } else if (e.code === 'ArrowRight') {
+      } else if (e.code === 'ArrowDown') {
         // if (popupData.activeIndex === data.itemList.length - 1) return;
         setModalOpen();
         handlePreAndNext('down');
@@ -1166,36 +1183,30 @@ export default defineComponent({
     // 右侧菜单栏
     const rightList = reactive([
       {
-        name: '曲目资源',
-        name2: '曲目资源',
-        icon: rightIconMusic,
-        id: 9
+        name: '上一张',
+        icon: bottomIconPre,
+        id: 11
       },
       {
-        name: '批注',
-        icon: rightIconPostil,
-        id: 3
+        name: '下一张',
+        icon: bottomIconNext,
+        id: 12
       },
       {
-        name: '白板',
-        icon: rightIconWhiteboard,
-        id: 4
+        name: '切换章节',
+        icon: bottomIconSwitch,
+        id: 13
       },
       {
-        name: '节拍器',
-        icon: rightIconMetronome,
-        id: 5
+        name: '资源列表',
+        icon: bottomIconResource,
+        id: 14
       },
       {
-        name: '计时器',
-        icon: rightIconTimer,
-        id: 7
+        name: '曲目资源',
+        icon: rightIconMusic,
+        id: 9
       },
-      // {
-      //   name: '调音器',
-      //   icon: rightIconTuner,
-      //   id: 6
-      // },
       {
         name: '点名',
         icon: rightIconCall,
@@ -1207,6 +1218,11 @@ export default defineComponent({
         id: 2
       },
       {
+        name: '工具箱',
+        icon: rightIconTool,
+        id: 15
+      },
+      {
         name: '结束课程',
         name2: '结束预览',
         icon: rightIconEnd,
@@ -1214,40 +1230,55 @@ export default defineComponent({
       },
       {
         name: '收起',
-        icon: rightIconPackUp,
+        icon: leftIconPackUp,
         id: 8
       }
     ]);
-
-    // 底部菜单栏
-    const bottomList = reactive([
+    const tooltipList = [
       {
-        name: '切换章节',
-        icon: bottomIconSwitch,
-        id: 1
+        name: '节拍器',
+        icon: rightIconMetronome,
+        id: 5
       },
       {
-        name: '资源列表',
-        icon: bottomIconResource,
-        id: 2
+        name: '计时器',
+        icon: rightIconTimer,
+        id: 7
       },
       {
-        name: '上一张',
-        icon: bottomIconPre,
+        name: '批注',
+        icon: rightIconPostil,
         id: 3
       },
       {
-        name: '下一张',
-        icon: bottomIconNext,
+        name: '白板',
+        icon: rightIconWhiteboard,
         id: 4
       }
-    ]);
+      // {
+      //   name: '调音器',
+      //   icon: rightIconTuner,
+      //   id: 6
+      // }
+    ];
     // 默认收起菜单
-    const rightColumnShow = ref(false);
-
+    const columnShow = ref(true);
+    // 菜单位置
+    const columnPos = ref<'left' | 'right'>('left');
+    watch(columnPos, () => {
+      for (let i = 0; i < data.itemList.length; i++) {
+        const activeItem = data.itemList[i];
+        if (['RHYTHM', 'MUSIC'].includes(activeItem.type)) {
+          activeItem.iframeRef?.contentWindow?.postMessage(
+            { api: 'imagePos', data: columnPos.value },
+            '*'
+          );
+        }
+      }
+    });
     // 右边栏操作
     const operateRightBtn = async (id: number) => {
-      if (id !== 8) {
+      if (![8, 11, 12, 13, 14].includes(id)) {
         handleStop(false);
       }
 
@@ -1328,7 +1359,7 @@ export default defineComponent({
           startShowModal('setTimeIcon');
           break;
         case 8:
-          rightColumnShow.value = false;
+          columnShow.value = false;
           break;
         case 9:
           // 选择曲目时需要暂停所有播放
@@ -1347,6 +1378,23 @@ export default defineComponent({
             return;
           }
           break;
+        case 11:
+          if (!isUpArrow.value) return;
+          handlePreAndNext('up');
+          break;
+        case 12:
+          if (!isDownArrow.value) return;
+          handlePreAndNext('down');
+          break;
+        case 13:
+          popupData.chapterOpen = true;
+          break;
+        case 14:
+          popupData.open = true;
+          nextTick(() => {
+            scrollResourceSection();
+          });
+          break;
         default:
           break;
       }
@@ -1378,31 +1426,6 @@ export default defineComponent({
       }
     };
 
-    // 底部悬浮按钮操作
-    const operateBottomBtn = (id: number) => {
-      switch (id) {
-        case 1:
-          popupData.chapterOpen = true;
-          break;
-        case 2:
-          popupData.open = true;
-          nextTick(() => {
-            scrollResourceSection();
-          });
-          break;
-        case 3:
-          if (!isUpArrow.value) return;
-          handlePreAndNext('up');
-          break;
-        case 4:
-          if (!isDownArrow.value) return;
-          handlePreAndNext('down');
-          break;
-        default:
-          break;
-      }
-    };
-
     // 滚动到某个元素的位置
     const scrollResourceSection = () => {
       const drawerCardItemRefs =
@@ -1426,6 +1449,112 @@ export default defineComponent({
       }
     };
 
+    /* 弹窗加拖动 */
+    // 选择课件弹窗
+    const selCourBoxClass = 'selCourBoxClass_drag';
+    const selCourDragData = useDrag(
+      [`${selCourBoxClass}>.n-card-header`, `${selCourBoxClass} .bom_drag`],
+      selCourBoxClass,
+      toRef(data, 'selectClassStatus'),
+      users.info.id
+    );
+    // 选择资源弹窗
+    const selResourBoxClass = 'selResourBoxClass_drag';
+    const selResourDragData = useDrag(
+      [
+        `${selResourBoxClass} .select-resource>.n-tabs>.n-tabs-nav--top.n-tabs-nav`,
+        `${selResourBoxClass} .bom_drag`
+      ],
+      selResourBoxClass,
+      toRef(data, 'selectResourceStatus'),
+      users.info.id
+    );
+    // 结束预览  结束课程确认弹窗
+    const removeResourBoxClass = 'removeResourBoxClass_drag';
+    const removeResourDragData = useDrag(
+      [
+        `${removeResourBoxClass}>.n-card-header`,
+        `${removeResourBoxClass} .bom_drag`
+      ],
+      removeResourBoxClass,
+      toRef(data, 'removeVisiable'),
+      users.info.id
+    );
+    // 章节next弹窗
+    const nextEndBoxClass = 'nextEndBoxClass_drag';
+    const nextEndBoxDragData = useDrag(
+      [`${nextEndBoxClass}>.n-card-header`, `${nextEndBoxClass} .bom_drag`],
+      nextEndBoxClass,
+      toRef(data, 'nextEndShow'),
+      users.info.id
+    );
+    // 章节切换弹窗
+    const chapterConBoxClass = 'chapterConBoxClass_drag';
+    const chapterConBoxDragData = useDrag(
+      [
+        `${chapterConBoxClass}>.n-card-header`,
+        `${chapterConBoxClass} .bom_drag`
+      ],
+      chapterConBoxClass,
+      toRef(popupData, 'chapterOpen'),
+      users.info.id
+    );
+    // 资源列表弹窗
+    const resourcesConBoxClass = 'resourcesConBoxClass_drag';
+    const resourcesConDragData = useDrag(
+      [
+        `${resourcesConBoxClass}>.n-card-header`,
+        `${resourcesConBoxClass} .bom_drag`
+      ],
+      resourcesConBoxClass,
+      toRef(popupData, 'open'),
+      users.info.id
+    );
+    // 计时器
+    const timerMeterConBoxClass = 'timerMeterConBoxClass_drag';
+    const timerMeterConDragData = useDrag(
+      [
+        `${timerMeterConBoxClass}>.n-card-header`,
+        `${timerMeterConBoxClass} .bom_drag`
+      ],
+      timerMeterConBoxClass,
+      showModalTime,
+      users.info.id
+    );
+    // 节拍器
+    const metronomeConBoxClass = 'metronomeConBoxClass_drag';
+    const metronomeConBoxDragData = useDrag(
+      [
+        `${metronomeConBoxClass}>.n-card-header`,
+        `${metronomeConBoxClass} .bom_drag`
+      ],
+      metronomeConBoxClass,
+      showModalBeat,
+      users.info.id
+    );
+    // 布置作业弹窗
+    const modelTrainStatusConBoxClass = 'modelTrainStatusConBoxClass_drag';
+    const modelTrainStatusConBoxDragData = useDrag(
+      [
+        `${modelTrainStatusConBoxClass}>.n-card-header`,
+        `${modelTrainStatusConBoxClass} .bom_drag`
+      ],
+      modelTrainStatusConBoxClass,
+      toRef(data, 'modelTrainStatus'),
+      users.info.id
+    );
+    // 布置作业课后作业
+    const modelTrainHomeWordConBoxClass =
+      'modelTrainHomeWordConBoxClassBoxClass_drag';
+    const modelTrainHomeWordConBoxDragData = useDrag(
+      [
+        `${modelTrainHomeWordConBoxClass}>.n-card-header`,
+        `${modelTrainHomeWordConBoxClass} .bom_drag`
+      ],
+      modelTrainHomeWordConBoxClass,
+      toRef(data, 'modelAttendStatus'),
+      users.info.id
+    );
     return () => (
       <div id="playContent" class={[styles.playContent, 'wrap']}>
         <div
@@ -1457,6 +1586,7 @@ export default defineComponent({
                   }
                   class={styles.itemDiv}>
                   <VideoPlay
+                    imagePos={columnPos.value}
                     ref={(el: any) => (data.videoItemRef = el)}
                     item={activeVideoItem.value}
                     showModel={activeData.model}
@@ -1586,6 +1716,7 @@ export default defineComponent({
                         <img src={m.content} />
                       ) : m.type === 'SONG' ? (
                         <AudioPay
+                          imagePos={columnPos.value}
                           item={m}
                           activeStatus={popupData.activeIndex === mIndex}
                           ref={(v: any) => (data.audioRefs[mIndex] = v)}
@@ -1629,6 +1760,7 @@ export default defineComponent({
                         <RhythmModal
                           item={m}
                           activeStatus={popupData.activeIndex === mIndex}
+                          imagePos={columnPos.value}
                           onSetIframe={(el: any) => {
                             m.iframeRef = el;
                           }}
@@ -1690,74 +1822,133 @@ export default defineComponent({
         <div
           class={[
             styles.rightColumn,
-            !rightColumnShow.value ? styles.rightColumnHide : '',
-            studyData.type !== 'init' &&
-            (studyData.penShow || studyData.whiteboardShow)
-              ? styles.rightColumnZ
-              : ''
+            columnShow.value && columnPos.value === 'right'
+              ? ''
+              : styles.rightColumnHide
           ]}>
-          {rightList.map((item: any, index: number) => (
-            <div
-              class={[
-                styles.rightItem,
-                (item.id === 2 && data.preStudentNum <= 0) ||
-                (item.id === 10 && studyData.callStudentList.length <= 0)
-                  ? styles.itemDisabled
-                  : '',
-                item.id === 10 && !data.classId ? styles.itemHide : ''
-              ]}
-              onClick={() => operateRightBtn(item.id)}>
-              <NTooltip showArrow={false} placement="left">
-                {{
-                  trigger: () => <img src={item.icon} />,
-                  default: (
-                    <>
-                      {index === rightList.length - 2 && data.type === 'preview'
-                        ? item.name2
-                        : item.name}
-                    </>
-                  )
-                }}
-              </NTooltip>
+          {rightList.map((item: any) => (
+            <div class={styles.columnItemBox}>
+              <div
+                class={[
+                  styles.columnItem,
+                  (item.id === 2 && data.preStudentNum <= 0) ||
+                  (item.id === 10 && studyData.callStudentList.length <= 0) ||
+                  (item.id === 11 && !isUpArrow.value) ||
+                  (item.id === 12 && !isDownArrow.value)
+                    ? styles.itemDisabled
+                    : ''
+                ]}
+                onClick={() => operateRightBtn(item.id)}>
+                <NTooltip
+                  showArrow={false}
+                  placement="left"
+                  class={[
+                    item.id === 15
+                      ? 'columnItemTooltip rightColumnItemTooltip'
+                      : ''
+                  ]}>
+                  {{
+                    trigger: () => (
+                      <img src={item.id === 8 ? rightIconPackUp : item.icon} />
+                    ),
+                    default: () =>
+                      item.id === 15 ? (
+                        <div class="tools">
+                          {tooltipList.map(i => (
+                            <div onClick={() => operateRightBtn(i.id)}>
+                              <img src={i.icon} />
+                              <div class="tit">{i.name}</div>
+                            </div>
+                          ))}
+                        </div>
+                      ) : item.id === 1 && data.type === 'preview' ? (
+                        item.name2
+                      ) : (
+                        item.name
+                      )
+                  }}
+                </NTooltip>
+              </div>
             </div>
           ))}
         </div>
-        {!rightColumnShow.value && (
-          <img
-            class={[
-              styles.rightHideIcon,
-              !rightColumnShow.value ? styles.rightIconShow : ''
-            ]}
-            src={rightHideIcon}
-            onClick={() => (rightColumnShow.value = true)}
-          />
-        )}
-        {/* 右下角悬浮按钮 */}
-        <div class={styles.bottomColumn}>
-          {bottomList.map((item: any, index: number) => (
-            <div
-              class={[
-                styles.bottomItem,
-                (item.id === 3 && !isUpArrow.value) ||
-                (item.id === 4 && !isDownArrow.value)
-                  ? styles.itemDisabled
-                  : ''
-              ]}
-              onClick={() => operateBottomBtn(item.id)}>
-              <NTooltip showArrow={false} placement="top">
-                {{
-                  trigger: () => <img src={item.icon} />,
-                  default: <>{item.name}</>
-                }}
-              </NTooltip>
-              {/* <img src={item.icon} />
-              <div class={styles.bottomTips}>{item.name}</div> */}
+        <img
+          class={[
+            styles.rightHideIcon,
+            !(columnShow.value && columnPos.value === 'right')
+              ? styles.rightIconShow
+              : ''
+          ]}
+          src={rightHideIcon}
+          onClick={() => {
+            columnPos.value = 'right';
+            columnShow.value = true;
+          }}
+        />
+        {/* 左边操作栏 */}
+        <div
+          class={[
+            styles.leftColumn,
+            columnShow.value && columnPos.value === 'left'
+              ? ''
+              : styles.leftColumnHide
+          ]}>
+          {rightList.map((item: any) => (
+            <div class={styles.columnItemBox}>
+              <div
+                class={[
+                  styles.columnItem,
+                  (item.id === 2 && data.preStudentNum <= 0) ||
+                  (item.id === 10 && studyData.callStudentList.length <= 0) ||
+                  (item.id === 11 && !isUpArrow.value) ||
+                  (item.id === 12 && !isDownArrow.value)
+                    ? styles.itemDisabled
+                    : ''
+                ]}
+                onClick={() => operateRightBtn(item.id)}>
+                <NTooltip
+                  showArrow={false}
+                  placement="right"
+                  class={[item.id === 15 ? 'columnItemTooltip' : '']}>
+                  {{
+                    trigger: () => <img src={item.icon} />,
+                    default: () =>
+                      item.id === 15 ? (
+                        <div class="tools">
+                          {tooltipList.map(i => (
+                            <div onClick={() => operateRightBtn(i.id)}>
+                              <img src={i.icon} />
+                              <div class="tit">{i.name}</div>
+                            </div>
+                          ))}
+                        </div>
+                      ) : item.id === 1 && data.type === 'preview' ? (
+                        item.name2
+                      ) : (
+                        item.name
+                      )
+                  }}
+                </NTooltip>
+              </div>
             </div>
           ))}
         </div>
+        <img
+          class={[
+            styles.leftHideIcon,
+            !(columnShow.value && columnPos.value === 'left')
+              ? styles.leftIconShow
+              : ''
+          ]}
+          src={leftHideIcon}
+          onClick={() => {
+            columnPos.value = 'left';
+            columnShow.value = true;
+          }}
+        />
 
         {/* 显示列表 */}
-        <NDrawer
+        {/* <NDrawer
           v-model:show={popupData.open}
           class={[styles.drawerContainer, styles.drawerContainerSource]}
           onAfterLeave={handleClosePopup}
@@ -1783,10 +1974,32 @@ export default defineComponent({
               )
             }}
           </NDrawerContent>
-        </NDrawer>
-
-        {/* 显示列表 */}
-        <NDrawer
+        </NDrawer> */}
+        <NModal
+          transformOrigin="center"
+          v-model:show={popupData.open}
+          preset="card"
+          class={[
+            'modalTitle background',
+            styles.drawerContainer,
+            resourcesConBoxClass
+          ]}
+          style={resourcesConDragData.styleDrag.value}
+          title={activeName.value || '资源列表'}>
+          <SourceList
+            teacherChapterName={data.teacherChapterName}
+            knowledgePointList={data.knowledgePointList}
+            courseActiveIndex={popupData.courseActiveIndex}
+            activeItem={data.itemList[popupData.activeIndex]}
+            onConfirm={(item: any) => {
+              popupData.open = false;
+              toggleMaterial(item.id);
+            }}
+          />
+          <Dragbom></Dragbom>
+        </NModal>
+        {/* 章节切换 */}
+        {/* <NDrawer
           v-model:show={popupData.chapterOpen}
           class={styles.drawerContainer}
           onAfterLeave={handleClosePopup}
@@ -1806,11 +2019,34 @@ export default defineComponent({
               }}
             />
           </NDrawerContent>
-        </NDrawer>
-
+        </NDrawer> */}
+        <NModal
+          transformOrigin="center"
+          v-model:show={popupData.chapterOpen}
+          preset="card"
+          class={[
+            'modalTitle background',
+            styles.drawerContainer,
+            chapterConBoxClass
+          ]}
+          style={chapterConBoxDragData.styleDrag.value}
+          title={'切换章节'}>
+          <Chapter
+            treeList={popupData.chapterDetails}
+            itemActive={data.detailId as any}
+            onHandleSelect={async (val: any) => {
+              // itemActive: child.id,
+              // itemName: child.name
+              popupData.courseId = val.itemActive;
+              data.selectClassStatus = true;
+            }}
+          />
+          <Dragbom></Dragbom>
+        </NModal>
         {/* 批注 */}
         {studyData.penShow && (
           <Pen
+            isDrag={true}
             show={studyData.type === 'pen'}
             type={studyData.type}
             close={() => closeStudyTool()}
@@ -1819,6 +2055,7 @@ export default defineComponent({
 
         {studyData.whiteboardShow && (
           <Pen
+            isDrag={true}
             show={studyData.type === 'whiteboard'}
             type={studyData.type}
             close={() => closeStudyTool()}
@@ -1827,6 +2064,8 @@ export default defineComponent({
 
         {studyData.callShow && (
           <Pen
+            isDrag={true}
+            imagePos={columnPos.value}
             callStudents={studyData.callStudentList}
             show={studyData.type === 'call'}
             type={studyData.type}
@@ -1842,8 +2081,10 @@ export default defineComponent({
           class={[
             'modalTitle background',
             // styles.attendClassModal,
-            styles.selectClassModal
+            styles.selectClassModal,
+            selCourBoxClass
           ]}
+          style={selCourDragData.styleDrag.value}
           title={'选择课件'}>
           <SelectClass
             classId={data.classId}
@@ -1874,6 +2115,7 @@ export default defineComponent({
               popupData.chapterLoading = false;
             }}
           />
+          <Dragbom></Dragbom>
         </NModal>
 
         {/* 布置作业 */}
@@ -1882,7 +2124,12 @@ export default defineComponent({
           v-model:show={data.modelAttendStatus}
           preset="card"
           title={'课后作业'}
-          class={['modalTitle', styles.removeVisiable]}>
+          style={modelTrainHomeWordConBoxDragData.styleDrag.value}
+          class={[
+            'modalTitle',
+            styles.removeVisiable,
+            modelTrainHomeWordConBoxClass
+          ]}>
           <div class={styles.studentRemove}>
             <p>{data.modalAttendMessage}</p>
             <NSpace class={styles.btnGroupModal} justify="center">
@@ -1909,6 +2156,7 @@ export default defineComponent({
               </NButton>
             </NSpace>
           </div>
+          <Dragbom class={styles.dragbom}></Dragbom>
         </NModal>
 
         {/* 训练设置 */}
@@ -1930,11 +2178,17 @@ export default defineComponent({
         <NModal
           v-model:show={data.modelTrainStatus}
           preset="card"
-          class={['modalTitle background', styles.workVisiable]}
+          class={[
+            'modalTitle background',
+            styles.workVisiable,
+            modelTrainStatusConBoxClass
+          ]}
+          style={modelTrainStatusConBoxDragData.styleDrag.value}
           title={'布置作业'}>
           <div id="model-homework-height" class={styles.workContainer}>
             <div class={styles.workTrain}>
               <Train
+                from={'class'}
                 cardType="homeworkRecord"
                 lessonPreTraining={{
                   title: data.lessonPreTrainingId
@@ -1952,18 +2206,22 @@ export default defineComponent({
               />
             </div>
             <div class={styles.resourceMain}>
-              <ResourceMain cardType="homerowk-record" />
+              <ResourceMain from={'class'} cardType="homerowk-record" />
             </div>
           </div>
+          <Dragbom class={styles.dragbom}></Dragbom>
         </NModal>
 
         <NModal
           transformOrigin="center"
-          class={['modalTitle background']}
+          class={['modalTitle background', metronomeConBoxClass]}
           title={'节拍器'}
           preset="card"
           v-model:show={showModalBeat.value}
-          style={{ width: '687px' }}>
+          style={{
+            width: '687px',
+            ...metronomeConBoxDragData.styleDrag.value
+          }}>
           <div class={styles.modeWrap}>
             <iframe
               src={`${vaildUrl()}/metronome/?id=${new Date().getTime()}`}
@@ -1974,6 +2232,7 @@ export default defineComponent({
                 iframeDislableKeyboard(val.target);
               }}
               height={'650px'}></iframe>
+            <Dragbom class={styles.dragbom}></Dragbom>
           </div>
         </NModal>
 
@@ -1991,27 +2250,34 @@ export default defineComponent({
         <NModal
           transformOrigin="center"
           v-model:show={showModalTime.value}
-          class={['modalTitle background']}
+          class={['modalTitle background', timerMeterConBoxClass]}
           title={'计时器'}
           preset="card"
-          style={{ width: px2vw(772) }}>
+          style={{
+            width: px2vw(772),
+            ...timerMeterConDragData.styleDrag.value
+          }}>
           <div>
             <TimerMeter></TimerMeter>
+            <Dragbom class={styles.dragbom}></Dragbom>
           </div>
         </NModal>
 
         <NModal
           v-model:show={data.selectResourceStatus}
-          class={['modalTitle', styles.selectMusicModal]}
+          class={['modalTitle', styles.selectMusicModal, selResourBoxClass]}
+          style={selResourDragData.styleDrag.value}
           preset="card"
           title={'选择资源'}>
           <SelectResources from="class" />
+          <Dragbom></Dragbom>
         </NModal>
         <NModal
           transformOrigin="center"
           v-model:show={data.removeVisiable}
           preset="card"
-          class={['modalTitle', styles.removeVisiable]}
+          class={['modalTitle', styles.removeVisiable, removeResourBoxClass]}
+          style={removeResourDragData.styleDrag.value}
           title={data.removeTitle}>
           <div class={styles.studentRemove}>
             <p>{data.removeContent}</p>
@@ -2078,6 +2344,39 @@ export default defineComponent({
                 {data.removeCourseStatus ? '布置作业' : '确定'}
               </NButton>
             </NSpace>
+            <Dragbom></Dragbom>
+          </div>
+        </NModal>
+        <NModal
+          style={nextEndBoxDragData.styleDrag.value}
+          z-index={3000}
+          transformOrigin="center"
+          v-model:show={data.nextEndShow}
+          preset="card"
+          title={'提示'}
+          class={['modalTitle', styles.removeVisiable, nextEndBoxClass]}>
+          <div class={styles.studentRemove}>
+            <p>当前课件已结束,是否进入下一章节</p>
+            <NSpace class={styles.btnGroupModal} justify="center">
+              <NButton
+                type="default"
+                round
+                onClick={() => {
+                  data.nextEndShow = false;
+                }}>
+                取消
+              </NButton>
+              <NButton
+                type="primary"
+                round
+                onClick={() => {
+                  data.nextEndShow = false;
+                  handleNextEnd();
+                }}>
+                确认
+              </NButton>
+            </NSpace>
+            <Dragbom></Dragbom>
           </div>
         </NModal>
       </div>

+ 60 - 51
src/views/attend-class/model/chapter/index.module.less

@@ -1,48 +1,67 @@
-.scrollBar {
-  margin: 0 20px;
-  width: calc(100% - 40px);
+.chapterBox {
+  border-top: 1px solid #F0F0F0;
+  height: 46vh;
 }
-
-.treeParent {
-  transition: height 1s ease-in-out;
+.scrollBar{
+  width: 100%;
+  height: calc(100% - 42px);
+}
+.treeBox{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  :global{
+    .n-scrollbar-content{
+      padding: 0 30px;
+    }
+  }
+  .listSectionLeft{
+    padding-top: 10px;
+    width: 40%;
+    background: #F3F5F6;
+    height: 100%;
+    border-radius: 0 0 0 16px;
+  }
+  .listSectionRight{
+    padding-top: 10px;
+    width: 60%;
+    height: 100%;
+    box-shadow: 0px 12px 18px 0px rgba(217,221,223,0.45);
+  }
+}
+.treeParentSelect {
+  background-color: #fff;
+  border-radius: 10px;
 }
 
 .treeChild {
-  line-height: 54px;
+  line-height: 46px;
 }
 
 .treeItem {
-  display: flex;
-  align-items: center;
-  line-height: 54px;
+  line-height: 46px;
   border-radius: 10px;
-  padding: 0 5px;
   cursor: pointer;
   border-radius: 10px;
-  font-size: max(17px, 12Px);
-  margin-bottom: 8px;
+  font-size: max(18px, 12Px);
+  font-weight: 600;
 
   &.childItem:hover {
-
     // background: #E8F4FF;
-
-    .title {
+    .titleName {
       color: #198CFE;
     }
   }
 
   .title {
-    padding-left: 8px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    max-width: 280px !important;
-    color: rgba(0, 0, 0, .5);
+    padding-left: 20px;
+    width: 100%;
+    color: #777777;
     display: flex;
     align-items: center;
 
     .dir {
-      flex-shrink: 1;
+      flex-shrink: 0;
       display: inline-block;
       width: 16px;
       height: 18px;
@@ -50,7 +69,12 @@
       background-size: contain;
       margin-right: 6px;
     }
-
+    .name{
+      flex-grow: 1;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
     &.titleSelect {
       color: #198CFE;
       // font-weight: bold;
@@ -62,38 +86,23 @@
     }
   }
 
-  .arrow {
-    display: inline-block;
-    width: 14px;
-    height: 15px;
-    background: url('./images/arrow-default.png') no-repeat center;
-    background-size: contain;
-
-    &.arrowSelect {
-      background: url('./images/arrow-active.png') no-repeat center;
-      background-size: contain;
-    }
-  }
-
-  .childArrow {
-    width: 12px;
-  }
-
   &.childItem {
-    padding-left: 30px;
-    font-size: max(15px, 12Px);
-
-    .title {
-      color: #131415;
+    font-size: max(16px, 12Px);
+    .titleName {
+      width: 100%;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      padding-left: 0;
+      color: #333333;
+      font-weight: 600;
     }
   }
 
   &.childSelect {
-    background: #E8F4FF;
-
-    .title {
-      color: #198CFE;
+    .titleName {
+      color: #1677FF;
       // font-weight: bold;
     }
   }
-}
+}

+ 86 - 71
src/views/attend-class/model/chapter/index.tsx

@@ -1,4 +1,12 @@
-import { defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
+import {
+  computed,
+  defineComponent,
+  onMounted,
+  reactive,
+  toRefs,
+  watch,
+  ComputedRef
+} from 'vue';
 import styles from './index.module.less';
 import { NScrollbar, useMessage } from 'naive-ui';
 
@@ -63,83 +71,90 @@ export default defineComponent({
         });
       }
     });
+    const activeknowledgeList = computed<any | undefined>(() => {
+      return treeList.value.find((item: any) => {
+        return item.selected;
+      });
+    });
     return () => (
-      <NScrollbar class={styles.scrollBar}>
-        <div class={[styles.listSection]}>
-          {treeList.value.map((item: any, index: number) => (
-            <div class={styles.treeParent} key={'parent' + index}>
-              <div
-                class={[styles.treeItem, styles.parentItem]}
-                onClick={() => {
-                  treeList.value.forEach((child: any) => {
-                    if (item.id !== child.id) {
-                      child.selected = false;
-                    }
-                  });
-                  item.selected = item.selected ? false : true;
-                }}>
-                {item.knowledgeList && item.knowledgeList.length > 0 && (
-                  <span
-                    class={[
-                      styles.arrow,
-                      item.selected ? styles.arrowSelect : ''
-                    ]}></span>
-                )}
-                <p
-                  class={[
-                    styles.title,
-                    item.selected ? styles.titleSelect : ''
-                  ]}>
-                  <span
-                    class={[
-                      styles.dir,
-                      item.selected ? styles.dirSelect : ''
-                    ]}></span>
-                  {item.name}
-                </p>
-              </div>
-
-              {item.selected &&
-                item.knowledgeList &&
-                item.knowledgeList.map((child: any, j: number) => (
+      <div class={styles.chapterBox}>
+        <div class={styles.treeBox}>
+          <div class={[styles.listSectionLeft]}>
+            <NScrollbar class={styles.scrollBar}>
+              {treeList.value.map((item: any, index: number) => (
+                <div
+                  class={[item.selected ? styles.treeParentSelect : '']}
+                  key={'parent' + index}>
                   <div
-                    key={'child' + j}
-                    class={[
-                      styles.treeItem,
-                      styles.childItem,
-                      styles.animation,
-                      itemActive.value === child.id ? styles.childSelect : ''
-                    ]}
+                    class={[styles.treeItem, styles.parentItem]}
                     onClick={() => {
-                      // 判断是否选择的同一个课件
-                      if (itemActive.value == child.id) {
-                        return;
-                      }
-                      if (child.coursewareNum <= 0) {
-                        message.error('该章节暂无课件');
-                        return;
-                      }
-                      emit('handleSelect', {
-                        itemActive: child.id,
-                        itemName: child.name
+                      treeList.value.forEach((child: any) => {
+                        if (item.id !== child.id) {
+                          child.selected = false;
+                        }
                       });
-                      // emit('handleSelect', {});
-                      // prepareStore.setSelectKey(child.id);
-                      // prepareStore.setLessonCoursewareId(
-                      //   child.lessonCoursewareId
-                      // );
-                      // prepareStore.setLessonCoursewareDetailId(
-                      //   child.lessonCoursewareDetailId
-                      // );
+                      item.selected = true;
                     }}>
-                    <span class={styles.childArrow}></span>
-                    <p class={styles.title}>{child.name}</p>
+                    <p
+                      class={[
+                        styles.title,
+                        item.selected ? styles.titleSelect : ''
+                      ]}>
+                      <span
+                        class={[
+                          styles.dir,
+                          item.selected ? styles.dirSelect : ''
+                        ]}></span>
+                      <div class={styles.name}>{item.name}</div>
+                    </p>
                   </div>
-                ))}
-            </div>
-          ))}
+                </div>
+              ))}
+            </NScrollbar>
+          </div>
+          <div class={styles.listSectionRight}>
+            <NScrollbar class={styles.scrollBar}>
+              {activeknowledgeList.value &&
+                activeknowledgeList.value.knowledgeList &&
+                activeknowledgeList.value.knowledgeList.map(
+                  (child: any, j: number) => (
+                    <div
+                      key={'child' + j}
+                      class={[
+                        styles.treeItem,
+                        styles.childItem,
+                        itemActive.value === child.id ? styles.childSelect : ''
+                      ]}
+                      onClick={() => {
+                        // 判断是否选择的同一个课件
+                        if (itemActive.value == child.id) {
+                          return;
+                        }
+                        if (child.coursewareNum <= 0) {
+                          message.error('该章节暂无课件');
+                          return;
+                        }
+                        emit('handleSelect', {
+                          itemActive: child.id,
+                          itemName: child.name
+                        });
+                        // emit('handleSelect', {});
+                        // prepareStore.setSelectKey(child.id);
+                        // prepareStore.setLessonCoursewareId(
+                        //   child.lessonCoursewareId
+                        // );
+                        // prepareStore.setLessonCoursewareDetailId(
+                        //   child.lessonCoursewareDetailId
+                        // );
+                      }}>
+                      <div class={styles.titleName}>{child.name}</div>
+                    </div>
+                  )
+                )}
+            </NScrollbar>
+          </div>
         </div>
-      </NScrollbar>
+      </div>
     );
   }
 });

+ 5 - 5
src/views/attend-class/model/select-class/index.module.less

@@ -1,10 +1,10 @@
 .selectClass {
-  padding: 12px 0 27px;
+  padding: 12px 0 0;
 }
 
 .selectClassScroll {
-  min-height: 60vh;
-  max-height: 60vh;
+  min-height: 46vh;
+  max-height: 46vh;
 
 }
 
@@ -24,7 +24,7 @@
   &.listEmpty {
     display: flex;
     align-items: center;
-    min-height: 60vh;
+    min-height: 46vh;
   }
 
 
@@ -35,4 +35,4 @@
       padding: 0 10px;
     }
   }
-}
+}

+ 87 - 120
src/views/attend-class/model/source-list/index.module.less

@@ -1,128 +1,95 @@
-.listSection {
-  padding: 17px 20px 20px;
+.sourcelistBox {
+  border-top: 1px solid #f0f0f0;
+  height: 46vh;
 }
-
-.className {
+.treeBox{
+  width: 100%;
+  height: 100%;
   display: flex;
-  align-items: center;
-  padding-bottom: 12px;
-  border-bottom: 1px solid #F0F0F0;
-  margin-bottom: 10px;
-
-  .classNameIcon {
-    display: inline-block;
-    flex-shrink: 0;
-    background: url('../../image/icon-class-name.png') center no-repeat;
-    background-size: contain;
-    margin-right: 5px;
-    width: 13px;
-    height: 18px;
-  }
-
-  .classNameContent {
-    font-weight: 600;
-    font-size: max(15px, 13Px);
-    color: #777777;
-    line-height: 21px;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-}
-
-.treeParent {
-  transition: height 1s ease-in-out;
-  padding: 8px 13px 0;
-
-  .cardContainer {
-    margin-bottom: 12px;
-    padding-top: 12px;
-    margin-right: 30px;
-    margin-left: 18px;
-  }
-
-  &.parentSelect {
-    background: #F5F6FA;
-    border-radius: 8px;
-    padding-bottom: 12px;
-  }
-
-
-  :global {
-    .card-section-container {
-      width: 100%;
-      height: 180px;
-    }
-  }
-}
-
-.treeChild {
-  line-height: 54px;
-}
-
-.treeItem {
-  display: flex;
-  align-items: center;
-  line-height: 36px;
-  border-radius: 10px;
-  padding: 0 5px;
-  cursor: pointer;
-  border-radius: 10px;
-  font-size: max(17px, 12Px);
-
-  // &:hover {
-  //   background: #F5F6FA;
-  // }
-
-
-  .title {
-    padding-left: 8px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    max-width: 280px !important;
-    color: rgba(0, 0, 0, .5);
-    display: flex;
-    align-items: center;
-
-    &.titleSelect {
-      color: #1677FF;
-      font-weight: bold;
+  .listSectionLeft{
+    width: 30%;
+    border-right: 1px solid  #F0F0F0;
+    .leftscrollBar{
+      :global{
+        .n-scrollbar-container{
+          height: calc(100% - 42px);
+        }
+        .n-scrollbar-content{
+          padding: 6px 32px 0 18px;
+        }
+      }
+      .className {
+        display: flex;
+        align-items: center;
+        padding: 14px 20px 14px 38px;
+        line-height: 22px;
+        margin-bottom: 4px;
+        .classNameIcon {
+          flex-shrink: 0;
+          background: url('../../image/icon-class-name.png') center no-repeat;
+          background-size: contain;
+          margin-right: 6px;
+          width: 14px;
+          height: 18px;
+        }
+        .classNameContent {
+          font-weight: 500;
+          font-size: max(18px, 13Px);
+          color: #333333;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
     }
-  }
-
-  .arrow {
-    display: inline-block;
-    width: 14px;
-    height: 15px;
-    background: url('../../image/arrow-default.png') no-repeat center;
-    background-size: contain;
-
-    &.arrowSelect {
-      background: url('../../image/arrow-active.png') no-repeat center;
-      background-size: contain;
+    .treeParent{
+      padding: 14px 20px 14px 38px;
+      line-height: 22px;
+      margin-bottom: 4px;
+      font-size: max(18px, 13Px);
+      color: #898A8A;
+      cursor: pointer;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      &.parentSelect{
+        background: #F5F6FA;
+        border-radius: 10px;
+        color: #1677FF;
+        font-weight: 600;
+      }
     }
   }
-
-  .childArrow {
-    width: 12px;
-  }
-
-  &.childItem {
-    padding-left: 30px;
-    font-size: max(15px, 12Px);
-
-    .title {
-      color: #131415;
+  .listSectionRight{
+    width: 70%;
+    padding-top: 20px;
+    .rightscrollBar{
+      :global{
+        .n-scrollbar-content{
+          padding: 0 14px;
+          display: flex;
+          flex-wrap: wrap;
+        }
+      }
     }
-  }
-
-  &.childSelect {
-    background: #F5F6FA;
-
-    .title {
-      color: var(--n-color);
-      font-weight: bold;
+    .cardContainer{
+      width: 33.33%;
+      height: 112px;
+      padding: 0 6px;
+      :global{
+        .card-section-container{
+          width: 100% !important;
+          height: 100% !important;
+          .n-card__footer{
+            .footerTitle{
+              overflow: hidden;
+              .titleContent{
+                max-width: calc(100% - 13Px);
+              }
+            }
+          }
+        }
+      }
     }
   }
-}
+}

+ 49 - 56
src/views/attend-class/model/source-list/index.tsx

@@ -1,4 +1,4 @@
-import { NCollapse, NCollapseItem } from 'naive-ui';
+import { NCollapse, NCollapseItem, NScrollbar } from 'naive-ui';
 import { computed, defineComponent, ref, watch } from 'vue';
 import CardType from '/src/components/card-type';
 import styles from './index.module.less';
@@ -47,64 +47,57 @@ export default defineComponent({
       return index;
     });
     return () => (
-      <div class={[styles.listSection]} id="drawerCardRef">
-        {props.teacherChapterName && (
-          <div class={styles.className}>
-            <i class={styles.classNameIcon}></i>
-            <p class={styles.classNameContent}>{props.teacherChapterName}</p>
-          </div>
-        )}
-
-        {props.knowledgePointList.map((item: any, index: number) => (
-          <div
-            class={[
-              styles.treeParent,
-              parentIndex.value === index && styles.parentSelect
-            ]}
-            key={'parent' + index}>
-            <div
-              class={[styles.treeItem, styles.parentItem]}
-              onClick={() => {
-                if (parentIndex.value === index) {
-                  parentIndex.value = -1;
-                } else {
-                  parentIndex.value = index;
-                }
-              }}>
-              <span
-                class={[
-                  styles.arrow,
-                  parentIndex.value === index ? styles.arrowSelect : ''
-                ]}></span>
-              <p
-                class={[
-                  styles.title,
-                  parentIndex.value === index ? styles.titleSelect : ''
-                ]}>
-                {item.title}
-              </p>
-            </div>
-
-            {parentIndex.value === index &&
-              item.list &&
-              item.list.map((child: any, j: number) => (
-                <div class={[styles.cardContainer, 'drawerCardItemRef']}>
-                  <CardType
-                    item={child}
-                    isActive={
-                      childIndex.value === j &&
-                      parentIndex.value === props.courseActiveIndex
-                    }
-                    isCollect={false}
-                    isShowCollect={false}
-                    onClick={() => {
-                      emit('confirm', child);
-                    }}
-                  />
+      <div class={styles.sourcelistBox}>
+        <div class={styles.treeBox}>
+          <div class={[styles.listSectionLeft]}>
+            <NScrollbar class={styles.leftscrollBar}>
+              {props.teacherChapterName && (
+                <div class={styles.className}>
+                  <i class={styles.classNameIcon}></i>
+                  <p class={styles.classNameContent}>
+                    {props.teacherChapterName}
+                  </p>
+                </div>
+              )}
+              {props.knowledgePointList.map((item: any, index: number) => (
+                <div
+                  class={[
+                    styles.treeParent,
+                    parentIndex.value === index && styles.parentSelect
+                  ]}
+                  key={'parent' + index}
+                  onClick={() => {
+                    parentIndex.value = index;
+                  }}>
+                  {item.title}
                 </div>
               ))}
+            </NScrollbar>
+          </div>
+          <div class={[styles.listSectionRight]}>
+            <NScrollbar class={styles.rightscrollBar}>
+              {(props.knowledgePointList[parentIndex.value] as any)?.list &&
+                (props.knowledgePointList[parentIndex.value] as any).list.map(
+                  (child: any, j: number) => (
+                    <div class={[styles.cardContainer, 'drawerCardItemRef']}>
+                      <CardType
+                        item={child}
+                        isActive={
+                          childIndex.value === j &&
+                          parentIndex.value === props.courseActiveIndex
+                        }
+                        isCollect={false}
+                        isShowCollect={false}
+                        onClick={() => {
+                          emit('confirm', child);
+                        }}
+                      />
+                    </div>
+                  )
+                )}
+            </NScrollbar>
           </div>
-        ))}
+        </div>
       </div>
     );
   }

+ 5 - 0
src/views/attend-class/model/train-type/index.tsx

@@ -66,6 +66,10 @@ export default defineComponent({
     isCLassWork: {
       type: Boolean,
       default: false
+    },
+    from: {
+      type: String,
+      default: ''
     }
   },
   emits: ['click', 'delete', 'edit', 'offShelf'],
@@ -410,6 +414,7 @@ export default defineComponent({
         </NModal>
 
         <CardPreview
+          from={props.from}
           v-model:show={previewShow.value}
           item={preivewItem.value}
           isDownload={isDownload.value}

+ 6 - 2
src/views/prepare-lessons/components/lesson-main/train/index.module.less

@@ -57,7 +57,11 @@
     }
   }
 }
-
+.dragbom{
+  position: absolute;
+  bottom: 0;
+  left: 0;
+}
 .tipsContainer {
   display: flex;
   align-items: center;
@@ -331,4 +335,4 @@
       }
     }
   }
-}
+}

+ 103 - 5
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -5,7 +5,8 @@ import {
   watch,
   ref,
   PropType,
-  onUnmounted
+  onUnmounted,
+  toRef
 } from 'vue';
 import styles from './index.module.less';
 import {
@@ -40,6 +41,9 @@ import iconTips from '../../../images/icon-tips.png';
 import { typeFormat } from '../../resource-main/components/select-music';
 import TheMessageDialog from '/src/components/TheMessageDialog';
 import { useResizeObserver } from '@vueuse/core';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
 export default defineComponent({
   name: 'courseware-modal',
   props: {
@@ -65,6 +69,11 @@ export default defineComponent({
     courseScheduleId: {
       type: String,
       default: ''
+    },
+    from: {
+      // 来自哪里
+      type: String,
+      default: ''
     }
   },
   emits: ['change'],
@@ -239,6 +248,68 @@ export default defineComponent({
       forms.trainList = [];
       prepareStore.setTrainList([]);
     });
+    // 弹窗拖动
+    // 作业设置
+    let workSetingBoxDragData: any;
+    let workSetingBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workSetingBoxClass = 'workSetingBoxClass_drag';
+      workSetingBoxDragData = useDrag(
+        [
+          `${workSetingBoxClass}>.n-card-header`,
+          `${workSetingBoxClass} .bom_drag`
+        ],
+        workSetingBoxClass,
+        toRef(forms, 'editStatus'),
+        users.info.id
+      );
+    }
+    // 清空
+    let workClearBoxDragData: any;
+    let workClearBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workClearBoxClass = 'workClearBoxClass_drag';
+      workClearBoxDragData = useDrag(
+        [
+          `${workClearBoxClass}>.n-card-header`,
+          `${workClearBoxClass} .bom_drag`
+        ],
+        workClearBoxClass,
+        toRef(forms, 'removeVisiable1'),
+        users.info.id
+      );
+    }
+    // 清空
+    let workSaveBoxDragData: any;
+    let workSaveBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workSaveBoxClass = 'workSaveBoxClass_drag';
+      workSaveBoxDragData = useDrag(
+        [`${workSaveBoxClass}>.n-card-header`, `${workSaveBoxClass} .bom_drag`],
+        workSaveBoxClass,
+        toRef(forms, 'preSaveVisiable'),
+        users.info.id
+      );
+    }
+    // 立即布置
+    let workArrangeImmediatelyBoxDragData: any;
+    let workArrangeImmediatelyBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workArrangeImmediatelyBoxClass = 'workArrangeImmediatelyBoxClass_drag';
+      workArrangeImmediatelyBoxDragData = useDrag(
+        [
+          `${workArrangeImmediatelyBoxClass}>.n-card-header`,
+          `${workArrangeImmediatelyBoxClass} .bom_drag`
+        ],
+        workArrangeImmediatelyBoxClass,
+        toRef(forms, 'assignHomeworkStatus'),
+        users.info.id
+      );
+    }
     return () => (
       <div class={styles.coursewareModal}>
         <div class={styles.btnGroup}>
@@ -386,6 +457,7 @@ export default defineComponent({
                         return (
                           <div data-id={item.musicId} class={styles.itemBlock}>
                             <TrainType
+                              from={props.from}
                               item={item}
                               isDelete
                               type="prepare"
@@ -451,8 +523,15 @@ export default defineComponent({
 
         {/* 编辑 */}
         <NModal
+          style={
+            props.from === 'class' ? workSetingBoxDragData.styleDrag.value : {}
+          }
           v-model:show={forms.editStatus}
-          class={['modalTitle background', styles.trainEditModal]}
+          class={[
+            'modalTitle background',
+            styles.trainEditModal,
+            workSetingBoxClass
+          ]}
           preset="card"
           title="作业设置">
           <TrainUpdate
@@ -483,14 +562,24 @@ export default defineComponent({
               prepareStore.setTrainList(forms.trainList);
             }}
           />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
 
         {/* 添加自定义教材 */}
         <NModal
+          style={
+            props.from === 'class'
+              ? workArrangeImmediatelyBoxDragData.styleDrag.value
+              : {}
+          }
           v-model:show={forms.assignHomeworkStatus}
           preset="card"
           showIcon={false}
-          class={['modalTitle background', styles.assignHomework]}
+          class={[
+            'modalTitle background',
+            styles.assignHomework,
+            workArrangeImmediatelyBoxClass
+          ]}
           title={'布置作业'}
           blockScroll={false}>
           <AssignHomework
@@ -512,14 +601,18 @@ export default defineComponent({
               }
             }}
           />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
 
         {/* {showGuide.value ? <Trainguide></Trainguide> : null} */}
 
         <NModal
+          style={
+            props.from === 'class' ? workClearBoxDragData.styleDrag.value : {}
+          }
           v-model:show={forms.removeVisiable1}
           preset="card"
-          class={['modalTitle', styles.removeVisiable1]}
+          class={['modalTitle', styles.removeVisiable1, workClearBoxClass]}
           title={'清空资源'}>
           <div class={styles.studentRemove}>
             <p>
@@ -546,12 +639,16 @@ export default defineComponent({
               </NButton>
             </NSpace>
           </div>
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
 
         <NModal
+          style={
+            props.from === 'class' ? workSaveBoxDragData.styleDrag.value : {}
+          }
           v-model:show={forms.preSaveVisiable}
           preset="card"
-          class={['modalTitle', styles.removeVisiable1]}
+          class={['modalTitle', styles.removeVisiable1, workSaveBoxClass]}
           title={'保存'}>
           <TheMessageDialog
             content="是否保存当前页面编辑内容?"
@@ -560,6 +657,7 @@ export default defineComponent({
             onClose={() => (forms.preSaveVisiable = false)}
             onConfirm={() => onPreSave()}
           />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
       </div>
     );

+ 6 - 1
src/views/prepare-lessons/components/resource-main/components/select-music/index.module.less

@@ -38,4 +38,9 @@
 
 .trainEditModal {
   width: 494px;
-}
+}
+.dragbom{
+  position: absolute;
+  bottom: 0;
+  left: 0;
+}

+ 45 - 3
src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx

@@ -1,4 +1,11 @@
-import { PropType, defineComponent, onMounted, reactive, watch } from 'vue';
+import {
+  PropType,
+  defineComponent,
+  onMounted,
+  reactive,
+  watch,
+  toRef
+} from 'vue';
 import ResourceSearchGroup from './resource-search-group';
 import { NModal, NScrollbar, NSpin } from 'naive-ui';
 import styles from './index.module.less';
@@ -13,6 +20,9 @@ import CardPreview from '/src/components/card-preview';
 import { evaluateDifficult } from '/src/utils/contants';
 import { eventGlobal } from '/src/utils';
 import { favorite, materialQueryPage } from '/src/views/natural-resources/api';
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
 
 const formatType = (type: string) => {
   if (type === 'sahreMusic') {
@@ -57,6 +67,11 @@ export default defineComponent({
     cardType: {
       type: String as PropType<'' | 'homerowk-record' | 'prepare'>,
       default: ''
+    },
+    from: {
+      // 来自哪里
+      type: String,
+      default: ''
     }
   },
   setup(props) {
@@ -217,6 +232,23 @@ export default defineComponent({
         onAdd(item);
       });
     });
+    // 弹窗拖动
+    // 作业设置
+    let workSetingBoxDragData: any;
+    let workSetingBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workSetingBoxClass = 'workSetingBoxClass_drag';
+      workSetingBoxDragData = useDrag(
+        [
+          `${workSetingBoxClass}>.n-card-header`,
+          `${workSetingBoxClass} .bom_drag`
+        ],
+        workSetingBoxClass,
+        toRef(state, 'editStatus'),
+        users.info.id
+      );
+    }
     return () => (
       <div>
         <ResourceSearchGroup
@@ -276,11 +308,20 @@ export default defineComponent({
         </NScrollbar>
 
         {/* 弹窗查看 */}
-        <CardPreview v-model:show={state.show} item={state.item} />
+        <CardPreview
+          from={props.from}
+          v-model:show={state.show}
+          item={state.item}
+        />
 
         <NModal
           v-model:show={state.editStatus}
-          class={['modalTitle background', styles.trainEditModal]}
+          style={workSetingBoxDragData.styleDrag.value}
+          class={[
+            'modalTitle background',
+            styles.trainEditModal,
+            workSetingBoxClass
+          ]}
           preset="card"
           title="作业设置">
           <TrainUpdate
@@ -306,6 +347,7 @@ export default defineComponent({
               eventGlobal.emit('onTrainAddItem', train);
             }}
           />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
       </div>
     );

+ 6 - 2
src/views/prepare-lessons/components/resource-main/index.module.less

@@ -78,7 +78,11 @@
   }
 
 }
-
+.dragbom{
+  position: absolute;
+  bottom: 0;
+  left: 0;
+}
 .selectMusicModal {
   position: relative;
   width: 1352px;
@@ -99,4 +103,4 @@
 
 .trainEditModal {
   width: 494px;
-}
+}

+ 79 - 8
src/views/prepare-lessons/components/resource-main/index.tsx

@@ -4,7 +4,8 @@ import {
   nextTick,
   onMounted,
   reactive,
-  ref
+  ref,
+  toRef
 } from 'vue';
 import styles from './index.module.less';
 import { NTabs, NTabPane, NModal } from 'naive-ui';
@@ -16,7 +17,9 @@ import ResourceItem from './components/resource-item';
 import TrainUpdate from '/src/views/attend-class/model/train-update';
 import requestOrigin from 'umi-request';
 import { eventGlobal } from '/src/utils';
-
+import useDrag from '@/hooks/useDrag';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { useUserStore } from '@/store/modules/users';
 export default defineComponent({
   name: 'resource-main',
   props: {
@@ -24,6 +27,11 @@ export default defineComponent({
     cardType: {
       type: String as PropType<'' | 'homerowk-record' | 'prepare'>,
       default: ''
+    },
+    from: {
+      // 来自哪里
+      type: String,
+      default: ''
     }
   },
   setup(props, { expose }) {
@@ -79,7 +87,40 @@ export default defineComponent({
     onMounted(() => {
       resetTabPosition();
     });
-
+    // 弹窗拖动
+    // 曲目资源
+    let selectResourceStatusAddBoxDragData: any;
+    let selectResourceStatusAddBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      selectResourceStatusAddBoxClass = 'selectResourceStatusAddBoxClass_drag';
+      selectResourceStatusAddBoxDragData = useDrag(
+        [
+          `${selectResourceStatusAddBoxClass} .select-resource>.n-tabs>.n-tabs-nav--top.n-tabs-nav`,
+          `${selectResourceStatusAddBoxClass} .bom_drag`
+        ],
+        selectResourceStatusAddBoxClass,
+        toRef(forms, 'selectMusicStatus'),
+        users.info.id
+      );
+    }
+    // 曲目资源 作业设置
+    let workSetingBoxDragData: any;
+    let workSetingBoxClass: string;
+    if (props.from === 'class') {
+      const users = useUserStore();
+      workSetingBoxClass = 'workSetingBoxClass_drag';
+      workSetingBoxDragData = useDrag(
+        [
+          `${workSetingBoxClass}>.n-card-header`,
+          `${workSetingBoxClass} .bom_drag`
+        ],
+        workSetingBoxClass,
+        toRef(forms, 'editStatus'),
+        users.info.id
+      );
+    }
+    //
     expose({
       resetTabPosition
     });
@@ -181,13 +222,22 @@ export default defineComponent({
               default: () => (
                 <>
                   <NTabPane name="myMusic" tab="我的曲目">
-                    <SelectMusic cardType={props.cardType} type="myMusic" />
+                    <SelectMusic
+                      from={props.from}
+                      cardType={props.cardType}
+                      type="myMusic"
+                    />
                   </NTabPane>
                   <NTabPane name="sahreMusic" tab="共享曲目">
-                    <SelectMusic cardType={props.cardType} type="sahreMusic" />
+                    <SelectMusic
+                      from={props.from}
+                      cardType={props.cardType}
+                      type="sahreMusic"
+                    />
                   </NTabPane>
                   <NTabPane name="collectMusic" tab="收藏曲目">
                     <SelectMusic
+                      from={props.from}
                       cardType={props.cardType}
                       type="collectMusic"
                     />
@@ -211,20 +261,40 @@ export default defineComponent({
         </NModal>
 
         <NModal
+          style={
+            props.from === 'class'
+              ? selectResourceStatusAddBoxDragData.styleDrag.value
+              : {}
+          }
           v-model:show={forms.selectMusicStatus}
           onUpdate:show={(val: any) => {
             if (!val) {
               prepareStore.setSelectMusicStatus(val);
             }
           }}
-          class={['modalTitle', styles.selectMusicModal]}
+          class={[
+            'modalTitle',
+            styles.selectMusicModal,
+            selectResourceStatusAddBoxClass
+          ]}
           preset="card"
           title={'选择曲目'}>
-          <SelectMusicModal onAdd={(item: any) => onAdd(item)} />
+          <SelectMusicModal
+            from={props.from}
+            onAdd={(item: any) => onAdd(item)}
+          />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
         <NModal
+          style={
+            props.from === 'class' ? workSetingBoxDragData.styleDrag.value : {}
+          }
           v-model:show={forms.editStatus}
-          class={['modalTitle background', styles.trainEditModal]}
+          class={[
+            'modalTitle background',
+            styles.trainEditModal,
+            workSetingBoxClass
+          ]}
           preset="card"
           title="作业设置">
           <TrainUpdate
@@ -244,6 +314,7 @@ export default defineComponent({
               eventGlobal.emit('onTrainAddItem', train);
             }}
           />
+          {props.from === 'class' && <Dragbom class={styles.dragbom}></Dragbom>}
         </NModal>
       </div>
     );

+ 3 - 0
src/views/prepare-lessons/model/select-music/index.tsx

@@ -51,18 +51,21 @@ export default defineComponent({
           }}>
           <NTabPane name="myResources" tab={'我的曲目'}>
             <SelectItem
+              from={props.from}
               type="myResources"
               onAdd={(item: any) => emit('add', item)}
             />
           </NTabPane>
           <NTabPane name="shareResources" tab={'共享曲目'}>
             <SelectItem
+              from={props.from}
               type="shareResources"
               onAdd={(item: any) => emit('add', item)}
             />
           </NTabPane>
           <NTabPane name="myCollect" tab="收藏曲目">
             <SelectItem
+              from={props.from}
               type="myCollect"
               onAdd={(item: any) => emit('add', item)}
             />

+ 9 - 1
src/views/prepare-lessons/model/select-music/select-item/index.tsx

@@ -26,6 +26,10 @@ export default defineComponent({
     type: {
       type: String,
       default: ''
+    },
+    from: {
+      type: String,
+      default: ''
     }
   },
   emits: ['add'],
@@ -217,7 +221,11 @@ export default defineComponent({
         </NScrollbar>
 
         {/* 弹窗查看 */}
-        <CardPreview v-model:show={state.show} item={state.item} />
+        <CardPreview
+          from={props.from}
+          v-model:show={state.show}
+          item={state.item}
+        />
       </div>
     );
   }

+ 0 - 5
src/views/prepare-lessons/model/select-resources/index.module.less

@@ -55,8 +55,3 @@
     }
   }
 }
-
-.listContainer {
-  margin-bottom: 20px;
-  max-height: 50vh;
-}

+ 2 - 1
src/views/prepare-lessons/model/select-resources/select-item/index.module.less

@@ -1,4 +1,5 @@
 .listContainer {
+  --listContainerHeight:85vh;
   margin: 0;
   // max-height: calc(80vh - var(--modal-lesson-search-height) - 50px - 30Px);
 
@@ -58,4 +59,4 @@
       }
     }
   }
-}
+}

+ 5 - 4
src/views/prepare-lessons/model/select-resources/select-item/index.tsx

@@ -187,9 +187,9 @@ export default defineComponent({
           )}
         </div>
         <NScrollbar
-          class={styles.listContainer}
+          class={[styles.listContainer, 'list_container']}
           style={{
-            'max-height': `calc(85vh - var(--modal-lesson-tab-height) - ${state.searchHeight} - 12px) `
+            'max-height': `calc(var(--listContainerHeight) - var(--modal-lesson-tab-height) - ${state.searchHeight}) `
           }}
           onScroll={(e: any) => {
             const clientHeight = e.target?.clientHeight;
@@ -208,7 +208,7 @@ export default defineComponent({
           <NSpin show={state.loading} size={'small'}>
             <div
               style={{
-                'min-height': `calc(85vh - var(--modal-lesson-tab-height) - ${state.searchHeight} - 12px)`
+                'min-height': `calc(var(--listContainerHeight) - var(--modal-lesson-tab-height) - ${state.searchHeight})`
               }}
               class={[
                 styles.listSection,
@@ -219,7 +219,7 @@ export default defineComponent({
               {state.tableList.length > 0 && (
                 <div class={styles.list}>
                   {state.tableList.map((item: any) => (
-                    <div class={styles.itemWrap}>
+                    <div class={[styles.itemWrap, 'selresources_item_Wrap']}>
                       <div class={styles.itemWrapBox}>
                         <CardType
                           isShowAdd={props.from === 'class' ? false : true}
@@ -250,6 +250,7 @@ export default defineComponent({
         <CardPreview
           size={props.from === 'class' ? 'large' : 'default'}
           v-model:show={state.show}
+          from={props.from}
           item={state.item}
         />
       </div>