黄琪勇 11 months ago
parent
commit
1b8df940c4

File diff suppressed because it is too large
+ 1 - 9415
package-lock.json


+ 45 - 0
src/hooks/useDrag/dragbom.tsx

@@ -0,0 +1,45 @@
+import { defineComponent, computed, reactive, onMounted } from 'vue';
+import styles from './index.module.less';
+// 底部拖动区域
+export default defineComponent({
+  name: 'dragBom',
+  emits: ["guideDone"],
+	props: {
+		/** 是否显示引导 */
+		showGuide: {
+			type: Boolean,
+			default: false,
+		},
+	},
+  setup(props, { emit }) {
+    const data = reactive({
+      guidePos: "bottom" as "bottom" | "top",
+    });
+
+    const initGuidePos = () => {
+      const pageHeight = document.documentElement.clientHeight || document.body.clientHeight;
+      const guideHeight = document.querySelector('.bom_guide')?.getBoundingClientRect().height || 0;
+      const dragTop = document.querySelector('.bom_drag')?.getBoundingClientRect()?.top || 0;
+      data.guidePos = pageHeight - dragTop < guideHeight ? "top" : "bottom";
+    }
+    onMounted(() => {
+      initGuidePos();
+    });
+    return () => (
+      <>
+        <div class={[styles.dragBom, 'bom_drag']}>
+          <div class={styles.box}></div>
+          <div class={[styles.box, styles.right]}></div>
+        </div>
+        {
+          props.showGuide && 
+          <div class={[styles.guide, data.guidePos === "top" && styles.guideTop, 'bom_guide']}>
+            <div class={styles.guideBg}></div>
+            <div class={styles.guideDone} onClick={() => emit("guideDone")}></div>
+          </div>          
+        }
+
+      </>
+    );
+  }
+});

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


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


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


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


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


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

@@ -0,0 +1,64 @@
+.dragBom {
+  width: 100%;
+  height: 42px;
+  display: flex;
+  justify-content: space-between;
+  border-radius: 0 0 26px 26px;
+  overflow: hidden;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  .box {
+    width: 42px;
+    height: 100%;
+    background: url('./img/left.png') no-repeat;
+    background-size: 100% 100%;
+    &.right {
+      background: url('./img/right.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}
+.guide {
+  position: absolute;
+  left: 0;
+  top: calc(100% - 10px);
+  &::before {
+    content: "";
+    display: block;
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 9;
+    width: 100vw;
+    height: 100vh;
+    background: rgba(0,0,0,0.2);
+  }
+  .guideBg {
+    position: relative;
+    z-index: 99;
+    width: 200px;
+    height: 102px;
+    background: url('./img/modalDragBg.png') no-repeat;
+    background-size: 100% 100%;
+  }
+  .guideDone {
+    position: absolute;
+    z-index: 99;
+    left: 34.6%;
+    top: 72.2%;
+    width: 50px;
+    height: 20px;
+    background: url('./img/modalDragDone.png') no-repeat;
+    background-size: 100% 100%;
+    cursor: pointer;
+  }
+  &.guideTop {
+    top: initial;
+    bottom: 2px;
+    .guideBg {
+      background: url('./img/modalDragBg2.png') no-repeat;
+      background-size: 100% 100%;
+    }
+  }
+}

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

@@ -0,0 +1,158 @@
+// 弹窗拖动
+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;
+          console.log(classDom)
+          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`,
+          transform: 'initial',
+          transformOrigin: 'initial',
+          margin: 'initial',
+          transition: 'initial'
+        };
+  });
+  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));
+}

BIN
src/views/tempo-practice/images/setting.png


+ 5 - 2
src/views/tempo-practice/index.module.less

@@ -272,7 +272,10 @@
     width: 90px;
     height: 39px;
   }
-
+  .setting{
+    width: 74px;
+    height: 39px;
+  }
   .speedChange {
     width: 110px;
     height: 39px;
@@ -359,4 +362,4 @@
       padding: 0 9px;
     }
   }
-}
+}

+ 75 - 12
src/views/tempo-practice/index.tsx

@@ -4,7 +4,8 @@ import {
   onUnmounted,
   reactive,
   ref,
-  watch
+  watch,
+  toRef
 } from 'vue';
 import styles from './index.module.less';
 import { postMessage } from '@/helpers/native-message';
@@ -16,6 +17,7 @@ import iconPause from './images/icon-pause.png';
 import beat from './images/btn-2.png';
 import tempo from './images/btn-3.png';
 import randDom from './images/btn-1.png';
+import setImg from './images/setting.png';
 import iconPlus from './images/icon-plus.png';
 import iconAdd from './images/icon-add.png';
 import { getImage } from './images/music';
@@ -33,6 +35,8 @@ import { handleStartTick, hendleEndTick } from './tick';
 import { handleStartBeat, hendleEndBeat } from './beat-tick';
 import { browser } from '@/helpers/utils';
 import { useRoute } from 'vue-router';
+import useDrag from '@/hooks/useDrag';
+import { state as stateData } from '@/state';
 
 export default defineComponent({
   name: 'tempo-practice',
@@ -76,7 +80,8 @@ export default defineComponent({
         { text: '190', value: 190, color: '#060606' },
         { text: '200', value: 200, color: '#060606' }
       ],
-      dataJson: {} as any
+      dataJson: {} as any,
+      playPos: (route.query.imagePos || 'left') as 'left' | 'right' // 数字课堂老师端上课 镜像字段
     });
     // 返回
     const goback = () => {
@@ -164,6 +169,13 @@ export default defineComponent({
       if (ev.data.api === 'resetPlay') {
         resetSetting();
       }
+      if (ev.data.api === 'imagePos') {
+        if (ev.data.data === 'right') {
+          state.playPos = 'right';
+        } else {
+          state.playPos = 'left';
+        }
+      }
     };
 
     const resetSetting = () => {
@@ -187,7 +199,6 @@ export default defineComponent({
         //
       }
     };
-
     // watch(
     //   () => props.show,
     //   val => {
@@ -223,6 +234,17 @@ export default defineComponent({
     expose({
       resetSetting
     });
+    let settingBoxDragData: any;
+    let settingBoxClass: string;
+    if (state.platform === 'modal') {
+      settingBoxClass = 'settingBoxClass_drag';
+      settingBoxDragData = useDrag(
+        [`${settingBoxClass} .iconTitBoxMove`, `${settingBoxClass} .bom_drag`],
+        settingBoxClass,
+        toRef(state, 'settingStatus'),
+        stateData.user.data.id
+      );
+    }
     return () => (
       <div
         onClick={() => {
@@ -251,7 +273,7 @@ export default defineComponent({
           <div class={styles.title}>
             <img src={icon_title} />
           </div>
-          {state.modeType !== 'courseware' && (
+          {state.modeType !== 'courseware' && state.platform !== 'modal' ? (
             <div
               class={styles.back}
               style={{ cursor: 'pointer' }}
@@ -261,6 +283,8 @@ export default defineComponent({
               }}>
               <img src={icon_setting} />
             </div>
+          ) : (
+            <div></div>
           )}
         </div>
 
@@ -326,13 +350,26 @@ export default defineComponent({
             e.stopPropagation();
           }}>
           {/* 播放 */}
-          <div class={styles.play} onClick={handlePlay}>
-            {setting.playState === 'pause' ? (
-              <img src={iconPause} />
-            ) : (
-              <img src={iconPlay} />
-            )}
-          </div>
+          {state.playPos === 'left' && (
+            <div class={styles.play} onClick={handlePlay}>
+              {setting.playState === 'pause' ? (
+                <img src={iconPause} />
+              ) : (
+                <img src={iconPlay} />
+              )}
+            </div>
+          )}
+          {/* 老师端来的时候的设置按钮 */}
+          {state.platform === 'modal' && state.playPos === 'right' && (
+            <div
+              class={styles.setting}
+              onClick={() => {
+                handleStop();
+                state.settingStatus = true;
+              }}>
+              <img src={setImg} />
+            </div>
+          )}
           {/* 播放类型 */}
           <div class={styles.playType} onClick={handlePlayType}>
             {setting.playType === 'beat' ? (
@@ -412,9 +449,35 @@ export default defineComponent({
               }}
             />
           </div>
+          {/* 播放 */}
+          {state.playPos === 'right' && (
+            <div class={styles.play} onClick={handlePlay}>
+              {setting.playState === 'pause' ? (
+                <img src={iconPause} />
+              ) : (
+                <img src={iconPlay} />
+              )}
+            </div>
+          )}
+          {/* 老师端来的时候的设置按钮 */}
+          {state.platform === 'modal' && state.playPos === 'left' && (
+            <div
+              class={styles.setting}
+              onClick={() => {
+                handleStop();
+                state.settingStatus = true;
+              }}>
+              <img src={setImg} />
+            </div>
+          )}
         </div>
 
-        <Popup v-model:show={state.settingStatus} class={styles.settingPopup}>
+        <Popup
+          style={
+            state.platform === 'modal' ? settingBoxDragData.styleDrag.value : {}
+          }
+          v-model:show={state.settingStatus}
+          class={[styles.settingPopup, settingBoxClass]}>
           <SettingModal
             dataJson={state.dataJson}
             onClose={() => (state.settingStatus = false)}

+ 20 - 13
src/views/tempo-practice/setting-modal/index.module.less

@@ -35,18 +35,24 @@
     height: 34px;
     background: url('../images/icon-set-title.png') no-repeat center center / contain;
   }
-
-  .iconClose {
+  .iconTitBox{
     position: absolute;
-    right: 13px;
-    top: 13px;
-    z-index: 9;
-    display: inline-block;
-    width: 31px;
-    height: 32px;
-    background: url('../images/icon-close.png') no-repeat center center;
-    background-size: 96%;
-    cursor: pointer;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 40px;
+    .iconClose {
+      position: absolute;
+      right: 13px;
+      top: 13px;
+      z-index: 9;
+      display: inline-block;
+      width: 31px;
+      height: 32px;
+      background: url('../images/icon-close.png') no-repeat center center;
+      background-size: 96%;
+      cursor: pointer;
+    }
   }
 }
 
@@ -132,7 +138,7 @@
 }
 
 .btnGroup {
-  position: fixed;
+  position: absolute;
   bottom: 0;
   left: 0;
   right: 0;
@@ -145,6 +151,7 @@
   border-radius: 0 0 26px 26px;
 
   .btnSubmit {
+    z-index: 1;
     width: 135px;
     height: 45px;
     line-height: 45px;
@@ -152,4 +159,4 @@
     background: url('../images/btn-5.png') no-repeat center center / contain;
     border: none;
   }
-}
+}

+ 15 - 11
src/views/tempo-practice/setting-modal/index.tsx

@@ -15,6 +15,7 @@ import { hendleEndTick } from '../tick';
 import { hendleEndBeat } from '../beat-tick';
 import deepClone from '@/helpers/deep-clone';
 import { useRoute } from 'vue-router';
+import Dragbom from '@/hooks/useDrag/dragbom';
 
 export default defineComponent({
   emits: ['close'],
@@ -95,17 +96,19 @@ export default defineComponent({
       <div
         class={[styles.settingContainer, state.win === 'pc' ? styles.pcS : '']}>
         <div class={styles.title}></div>
-        <i
-          class={styles.iconClose}
-          onClick={() => {
-            emit('close');
-            setTimeout(() => {
-              state.element = JSON.parse(JSON.stringify(setting.element));
-              state.beat = JSON.parse(JSON.stringify(setting.beat)); //state.beat;
-              state.barLine = JSON.parse(JSON.stringify(setting.barLine)); // state.barLine;
-              state.tempo = JSON.parse(JSON.stringify(setting.tempo)); // state.tempo;
-            }, 300);
-          }}></i>
+        <div class={[styles.iconTitBox,'iconTitBoxMove']}>
+          <i
+            class={styles.iconClose}
+            onClick={() => {
+              emit('close');
+              setTimeout(() => {
+                state.element = JSON.parse(JSON.stringify(setting.element));
+                state.beat = JSON.parse(JSON.stringify(setting.beat)); //state.beat;
+                state.barLine = JSON.parse(JSON.stringify(setting.barLine)); // state.barLine;
+                state.tempo = JSON.parse(JSON.stringify(setting.tempo)); // state.tempo;
+              }, 300);
+            }}></i>
+        </div>
 
         <div class={styles.settingContent}>
           <div class={styles.settingParams}>
@@ -172,6 +175,7 @@ export default defineComponent({
         </div>
         <div class={styles.btnGroup}>
           <Button class={styles.btnSubmit} onClick={onSubmit}></Button>
+          {route.query.platform === 'modal' && <Dragbom></Dragbom>}
         </div>
       </div>
     );

Some files were not shown because too many files changed in this diff