Browse Source

Merge branch 'iteration-20260411' into jenkins-test

lex-wxl 1 day ago
parent
commit
4a4c43f3e3
100 changed files with 8243 additions and 8215 deletions
  1. 18 47
      src/App.tsx
  2. 21 25
      src/components/Application/Application.tsx
  3. 3 3
      src/components/Application/index.ts
  4. 1 1
      src/components/CBreadcrumb/index.tsx
  5. 84 84
      src/components/CDatePicker/index.tsx
  6. 54 54
      src/components/CSelect/index.tsx
  7. 831 831
      src/components/Metronome/Metronome.vue
  8. 2 2
      src/components/Metronome/index.ts
  9. 167 167
      src/components/Metronome/useMetronome.ts
  10. 349 349
      src/components/TheAuth/index.tsx
  11. 31 23
      src/components/TheEmpty/index.tsx
  12. 96 96
      src/components/TheNoticeBar/index.tsx
  13. 223 224
      src/components/TheQrCode/index.tsx
  14. 7 4
      src/components/TheSearch/index.tsx
  15. 67 67
      src/components/TheTipDialog/index.tsx
  16. 3 6
      src/components/VipPurchaseModal/index.tsx
  17. 3 1
      src/components/card-preview/music-modal/index.tsx
  18. 112 112
      src/components/card-type/audio-player/index.tsx
  19. 57 57
      src/components/card-type/video-player/index.tsx
  20. 2 2
      src/components/layout/guide-section/driver.ts
  21. 30 52
      src/components/layout/layoutTop.tsx
  22. 59 59
      src/components/layout/modals/api.ts
  23. 42 42
      src/components/layout/modals/placeholderTone.tsx
  24. 3 3
      src/components/layout/modals/silderItem.tsx
  25. 0 1
      src/components/layout/modals/suggestion-list.tsx
  26. 28 28
      src/components/searchInput/index.tsx
  27. 479 479
      src/components/timerMeter/TimerMeter.vue
  28. 2 2
      src/components/timerMeter/index.ts
  29. 386 386
      src/components/timerMeterOld/components/countdown.tsx
  30. 217 217
      src/components/timerMeterOld/components/positive.tsx
  31. 51 29
      src/components/timerMeterOld/index.tsx
  32. 296 298
      src/components/timerMeterOld/modals/flipper.vue
  33. 10 10
      src/components/upload-file/api.ts
  34. 172 172
      src/components/upload-file/copper.tsx
  35. 9 9
      src/custom-plugins/guide-page/images/index.ts
  36. 28 28
      src/enums/breakpointEnum.ts
  37. 34 34
      src/enums/httpEnum.ts
  38. 45 45
      src/enums/pageEnum.ts
  39. 16 16
      src/helpers/deep-clone.ts
  40. 116 116
      src/helpers/native-message.ts
  41. 406 406
      src/helpers/toolsValidate.ts
  42. 2 2
      src/helpers/utils.ts
  43. 47 47
      src/hooks/core/useTimeout.ts
  44. 91 91
      src/hooks/event/useBreakpoint.ts
  45. 63 63
      src/hooks/event/useEventListener.ts
  46. 40 36
      src/hooks/event/useWindowSizeFn.ts
  47. 18 18
      src/hooks/setting/useDesignSetting.ts
  48. 85 85
      src/hooks/use-async.ts
  49. 95 93
      src/hooks/useBattery.ts
  50. 23 23
      src/hooks/useDomWidth.ts
  51. 4 4
      src/hooks/useDrag/index.ts
  52. 30 30
      src/hooks/useOnline.ts
  53. 59 55
      src/hooks/useTime.ts
  54. 124 124
      src/hooks/web/useECharts.ts
  55. 70 70
      src/hooks/web/usePage.ts
  56. 59 59
      src/hooks/web/usePermission.ts
  57. 2 0
      src/main.ts
  58. 1 1
      src/plugins/index.ts
  59. 139 139
      src/plugins/naive.ts
  60. 40 40
      src/plugins/version.ts
  61. 10 8
      src/router/router-guards.ts
  62. 21 23
      src/screen-tips/index.tsx
  63. 32 32
      src/settings/designSetting.ts
  64. 10 10
      src/store/index.ts
  65. 40 40
      src/store/modules/designSetting.ts
  66. 268 268
      src/store/modules/prepareLessons.ts
  67. 31 0
      src/store/modules/vipModal.ts
  68. 5 5
      src/store/mutation-types.ts
  69. 29 29
      src/utils/cipher.ts
  70. 6 6
      src/utils/contants.ts
  71. 164 163
      src/utils/dateFormat.ts
  72. 41 44
      src/utils/index.ts
  73. 125 125
      src/utils/is/index.ts
  74. 55 55
      src/utils/lib/echarts.ts
  75. 18 18
      src/utils/rem.ts
  76. 135 135
      src/utils/request.ts
  77. 32 30
      src/utils/searchArray.ts
  78. 105 105
      src/utils/searchs.ts
  79. 134 134
      src/utils/storage.ts
  80. 10 10
      src/views/aboutUs/api.ts
  81. 71 71
      src/views/aboutUs/index.tsx
  82. 155 155
      src/views/attend-class/component/audio-pay copy.tsx
  83. 3 2
      src/views/attend-class/component/audio-pay.tsx
  84. 70 70
      src/views/attend-class/component/pptList.tsx
  85. 1 3
      src/views/attend-class/component/rhythm-modal/index.tsx
  86. 174 174
      src/views/attend-class/component/video-play copy.tsx
  87. 34 31
      src/views/attend-class/component/video-play.tsx
  88. 5 8
      src/views/attend-class/index.tsx
  89. 164 164
      src/views/attend-class/model/chapter/index.tsx
  90. 44 34
      src/views/attend-class/model/train-settings/index.tsx
  91. 1 1
      src/views/classList/components/classStudent.tsx
  92. 108 108
      src/views/classList/components/classStudentRecode.tsx
  93. 1 1
      src/views/classList/components/testRecode.tsx
  94. 109 109
      src/views/classList/contants.ts
  95. 2 1
      src/views/classList/index.tsx
  96. 7 5
      src/views/classList/modals/classTrainingDetails.tsx
  97. 162 162
      src/views/classList/modals/resetStudent.tsx
  98. 127 127
      src/views/classList/modals/resetSubject.tsx
  99. 139 139
      src/views/classList/modals/updateSubject.tsx
  100. 43 43
      src/views/content-information/api.ts

+ 18 - 47
src/App.tsx

@@ -1,45 +1,20 @@
-import {
-  computed,
-  defineComponent,
-  nextTick,
-  onMounted,
-  onUnmounted,
-  ref
-} from 'vue';
+import { computed, defineComponent, onMounted, onUnmounted } from 'vue';
 import { NConfigProvider, zhCN, dateZhCN, NModal } from 'naive-ui';
 import { AppProvider } from './components/Application';
 import { RouterView } from 'vue-router';
 import setting from './settings/designSetting';
-import { lighten, eventGlobal } from './utils';
+import { lighten } from './utils';
 import { useRegisterSW } from 'virtual:pwa-register/vue';
 import { useUserStore } from './store/modules/users';
+import { useVipModalState } from './store/modules/vipModal';
 import UpdateTips from './update-tips';
 import VipPurchaseModal from './components/VipPurchaseModal';
 
 export default defineComponent({
   name: 'App',
   setup() {
-    // VIP购买弹窗状态
-    const showVipModal = ref(false);
-    const hasClose = ref(false); // 是否允许关闭弹窗
-    const modalKey = ref(0); // 用于强制重新创建弹窗组件
-
-    // 监听VIP购买弹窗事件(必须在setup开头同步注册,否则路由守卫emit时监听还未准备好)
-    eventGlobal.on('show-vip-purchase', (val?: boolean) => {
-      // 更新 key 强制重新创建组件(解决手动删除DOM后状态残留问题)
-      modalKey.value++;
-      hasClose.value = val || false;
-      // 使用 nextTick 确保新组件创建完成后再显示
-      nextTick(() => {
-        showVipModal.value = true;
-      });
-    });
-
-    // 监听重置VIP弹窗事件(退出登录时重置状态)
-    eventGlobal.on('reset-vip-modal', () => {
-      showVipModal.value = false;
-      hasClose.value = false;
-    });
+    // 使用 VIP 弹窗状态(事件监听已在 main.ts 提前注册)
+    const { showVipModal, hasClose, modalKey } = useVipModalState();
 
     // const { needRefresh, offlineReady, updateServiceWorker } = useRegisterSW({
     //   onRegistered(r: any) {
@@ -118,17 +93,17 @@ export default defineComponent({
       window.addEventListener('message', handleOpen);
 
       // 主动检测会员状态(仅登录用户)
-      const userStore = useUserStore();
-      if (userStore.getToken) {
-        const membershipEndTime = userStore.getUserInfo?.membershipEndTime;
-        const isExpired =
-          !membershipEndTime || new Date(membershipEndTime) < new Date();
-        if (isExpired) {
-          nextTick(() => {
-            showVipModal.value = true;
-          });
-        }
-      }
+      // const userStore = useUserStore();
+      // if (userStore.getToken) {
+      //   const membershipEndTime = userStore.getUserInfo?.membershipEndTime;
+      //   const isExpired =
+      //     !membershipEndTime || new Date(membershipEndTime) < new Date();
+      //   if (isExpired) {
+      //     nextTick(() => {
+      //       showVipModal.value = true;
+      //     });
+      //   }
+      // }
 
       // 禁用右键菜单
       document.addEventListener('contextmenu', function (event) {
@@ -149,8 +124,6 @@ export default defineComponent({
     onUnmounted(() => {
       // window.removeEventListener('resize', resize);
       window.removeEventListener('message', handleOpen);
-      eventGlobal.off('show-vip-purchase');
-      eventGlobal.off('reset-vip-modal');
     });
 
     return () => (
@@ -158,8 +131,7 @@ export default defineComponent({
         <NConfigProvider
           locale={zhCN}
           themeOverrides={getThemeOverrides.value}
-          dateLocale={dateZhCN}
-        >
+          dateLocale={dateZhCN}>
           <AppProvider>
             <RouterView />
           </AppProvider>
@@ -174,8 +146,7 @@ export default defineComponent({
             preset="card"
             title="VIP会员续费"
             class="modalTitle background vipWidth"
-            v-model:show={showVipModal.value}
-          >
+            v-model:show={showVipModal.value}>
             <VipPurchaseModal
               hasCancel={hasClose.value}
               onClose={() => {

+ 21 - 25
src/components/Application/Application.tsx

@@ -1,25 +1,21 @@
-import { defineComponent } from 'vue';
-import {
-  NDialogProvider,
-  NNotificationProvider,
-  NMessageProvider
-} from 'naive-ui';
-
-export default defineComponent({
-  name: 'application-page',
-  setup(props, { slots }) {
-
-
-    return () => (
-      <NDialogProvider>
-        <NNotificationProvider>
-          <NMessageProvider max={1}>
-            {slots.default && slots.default()}
-          </NMessageProvider>
-        </NNotificationProvider>
-      </NDialogProvider>
-    );
-
-
-  }
-});
+import { defineComponent } from 'vue';
+import {
+  NDialogProvider,
+  NNotificationProvider,
+  NMessageProvider
+} from 'naive-ui';
+
+export default defineComponent({
+  name: 'application-page',
+  setup(props, { slots }) {
+    return () => (
+      <NDialogProvider>
+        <NNotificationProvider>
+          <NMessageProvider max={1}>
+            {slots.default && slots.default()}
+          </NMessageProvider>
+        </NNotificationProvider>
+      </NDialogProvider>
+    );
+  }
+});

+ 3 - 3
src/components/Application/index.ts

@@ -1,3 +1,3 @@
-import AppProvider from './Application';
-
-export { AppProvider };
+import AppProvider from './Application';
+
+export { AppProvider };

+ 1 - 1
src/components/CBreadcrumb/index.tsx

@@ -37,7 +37,7 @@ export default defineComponent({
             class={styles.icon_back}
             onClick={() => {
               emit('back');
-              router.go(-1)
+              router.go(-1);
             }}
           />
           <NBreadcrumb separator="">

+ 84 - 84
src/components/CDatePicker/index.tsx

@@ -1,84 +1,84 @@
-import { defineComponent, ref, watch } from 'vue';
-import styles from './index.module.less';
-import { NIcon, NImage, NDatePicker } from 'naive-ui';
-import dateIcons from './images/dateIcon.png';
-import disDate from './images/disDate.png';
-export default defineComponent({
-  name: 'CDatePicker',
-  props: {
-    type: {
-      type: String,
-      default: 'daterange'
-    },
-    separator: {
-      type: String,
-      default: '-'
-    },
-    timerValue: {
-      type: Array,
-      default: [] as any
-    },
-    // 
-    placeholder: {
-      type: String,
-      default: '选择日期时间'
-    },
-    startPlaceholder: {
-      type: String,
-      default: '结束日期时间'
-    },
-    endPlaceholder: {
-      type: String,
-      default: '开始日期时间'
-    }
-  },
-  setup(props, { emit, attrs }) {
-    const isFocus = ref(false);
-    const timer = ref(null as any);
-    const updateTimer = () => {
-      console.log('更新日期', timer.value);
-      emit('update:value', timer.value);
-    };
-    timer.value =
-      props.timerValue && props.timerValue.length > 0 ? props.timerValue : null;
-    watch(
-      () => props.timerValue,
-      (value: any) => {
-        timer.value = value && value.length ? value : null;
-      }
-    );
-    return () => (
-      <>
-        <div class={styles.CdataWrap}>
-          <NImage
-            previewDisabled
-            class={styles.dateIcons}
-            src={isFocus.value ? dateIcons : disDate}></NImage>
-          <NDatePicker
-            {...attrs}
-            class={styles.CDatePicker}
-            v-model:value={timer.value}
-            separator={props.separator}
-            type={props.type as any}
-            onUpdate:value={updateTimer}
-            placeholder={props.placeholder}
-            startPlaceholder={props.startPlaceholder}
-            endPlaceholder={props.endPlaceholder}
-            onFocus={() => {
-              isFocus.value = true;
-            }}
-            onBlur={() => {
-              isFocus.value = false;
-            }}
-            v-slots={{
-              'date-icon': () => (
-                <>
-                  <span></span>
-                </>
-              )
-            }}></NDatePicker>
-        </div>
-      </>
-    );
-  }
-});
+import { defineComponent, ref, watch } from 'vue';
+import styles from './index.module.less';
+import { NIcon, NImage, NDatePicker } from 'naive-ui';
+import dateIcons from './images/dateIcon.png';
+import disDate from './images/disDate.png';
+export default defineComponent({
+  name: 'CDatePicker',
+  props: {
+    type: {
+      type: String,
+      default: 'daterange'
+    },
+    separator: {
+      type: String,
+      default: '-'
+    },
+    timerValue: {
+      type: Array,
+      default: [] as any
+    },
+    //
+    placeholder: {
+      type: String,
+      default: '选择日期时间'
+    },
+    startPlaceholder: {
+      type: String,
+      default: '结束日期时间'
+    },
+    endPlaceholder: {
+      type: String,
+      default: '开始日期时间'
+    }
+  },
+  setup(props, { emit, attrs }) {
+    const isFocus = ref(false);
+    const timer = ref(null as any);
+    const updateTimer = () => {
+      console.log('更新日期', timer.value);
+      emit('update:value', timer.value);
+    };
+    timer.value =
+      props.timerValue && props.timerValue.length > 0 ? props.timerValue : null;
+    watch(
+      () => props.timerValue,
+      (value: any) => {
+        timer.value = value && value.length ? value : null;
+      }
+    );
+    return () => (
+      <>
+        <div class={styles.CdataWrap}>
+          <NImage
+            previewDisabled
+            class={styles.dateIcons}
+            src={isFocus.value ? dateIcons : disDate}></NImage>
+          <NDatePicker
+            {...attrs}
+            class={styles.CDatePicker}
+            v-model:value={timer.value}
+            separator={props.separator}
+            type={props.type as any}
+            onUpdate:value={updateTimer}
+            placeholder={props.placeholder}
+            startPlaceholder={props.startPlaceholder}
+            endPlaceholder={props.endPlaceholder}
+            onFocus={() => {
+              isFocus.value = true;
+            }}
+            onBlur={() => {
+              isFocus.value = false;
+            }}
+            v-slots={{
+              'date-icon': () => (
+                <>
+                  <span></span>
+                </>
+              )
+            }}></NDatePicker>
+        </div>
+      </>
+    );
+  }
+});

+ 54 - 54
src/components/CSelect/index.tsx

@@ -1,54 +1,54 @@
-import { defineComponent, ref } from 'vue';
-import styles from './index.module.less';
-import { NIcon, NImage, NDatePicker, NSelect } from 'naive-ui';
-
-import activeArrow from './images/activeArrow.png';
-import arrow from './images/arrow.png';
-export default defineComponent({
-  props: {
-    inline: {
-      type: Boolean,
-      default: false
-    }
-  },
-  name: 'CSelect',
-  setup(props, { emit, attrs }) {
-    const timer = ref(null);
-    const updateTimer = () => {
-      console.log('更新日期', timer.value);
-      emit('update:value', timer.value);
-    };
-    const isFocus = ref(false);
-    return () => (
-      <>
-        <div
-          class={[
-            styles.CSelectWrap,
-            props.inline ? styles.CSelectInitWrap : null
-          ]}>
-          <NSelect
-            show-checkmark={false}
-            {...attrs}
-            onUpdate:show={(flag: boolean) => {
-              isFocus.value = flag;
-            }}
-            // v-slots={{
-            //   arrow: () =>
-            //     isFocus.value ? (
-            //       <NImage
-            //         class={styles.arrow}
-            //         previewDisabled
-            //         src={activeArrow}></NImage>
-            //     ) : (
-            //       <NImage
-            //         class={styles.arrow}
-            //         previewDisabled
-            //         src={arrow}></NImage>
-            //     )
-            // }}
-            ></NSelect>
-        </div>
-      </>
-    );
-  }
-});
+import { defineComponent, ref } from 'vue';
+import styles from './index.module.less';
+import { NIcon, NImage, NDatePicker, NSelect } from 'naive-ui';
+
+import activeArrow from './images/activeArrow.png';
+import arrow from './images/arrow.png';
+export default defineComponent({
+  props: {
+    inline: {
+      type: Boolean,
+      default: false
+    }
+  },
+  name: 'CSelect',
+  setup(props, { emit, attrs }) {
+    const timer = ref(null);
+    const updateTimer = () => {
+      console.log('更新日期', timer.value);
+      emit('update:value', timer.value);
+    };
+    const isFocus = ref(false);
+    return () => (
+      <>
+        <div
+          class={[
+            styles.CSelectWrap,
+            props.inline ? styles.CSelectInitWrap : null
+          ]}>
+          <NSelect
+            show-checkmark={false}
+            {...attrs}
+            onUpdate:show={(flag: boolean) => {
+              isFocus.value = flag;
+            }}
+            // v-slots={{
+            //   arrow: () =>
+            //     isFocus.value ? (
+            //       <NImage
+            //         class={styles.arrow}
+            //         previewDisabled
+            //         src={activeArrow}></NImage>
+            //     ) : (
+            //       <NImage
+            //         class={styles.arrow}
+            //         previewDisabled
+            //         src={arrow}></NImage>
+            //     )
+            // }}
+          ></NSelect>
+        </div>
+      </>
+    );
+  }
+});

+ 831 - 831
src/components/Metronome/Metronome.vue

@@ -1,831 +1,831 @@
-<template>
-  <div class="Metronome">
-    <div class="MetronomeBox">
-      <div class="headTools">
-        <img
-          src="./imgs/minMet.png"
-          @click="
-            () => {
-              emits('windowMet');
-            }
-          "
-        />
-        <img
-          src="./imgs/closeMet.png"
-          @click="
-            () => {
-              emits('closeMet');
-            }
-          "
-        />
-      </div>
-      <div class="beatWrap">
-        <div
-          class="dot"
-          ref="dotDom"
-          @mousedown="handleDotMousedown"
-          @touchstart="handleDotTouchstart"
-        ></div>
-        <div class="round"></div>
-        <canvas
-          ref="mycanvasDom"
-          class="mycanvas"
-          width="252"
-          height="252"
-          @click="handleEventCanvas"
-        ></canvas>
-        <div class="beatCon">
-          <img
-            class="optImg"
-            src="./imgs/jia.png"
-            @click="
-              () => {
-                if (speedNum < 200) {
-                  speedNum++;
-                }
-              }
-            "
-          />
-          <div class="optMid">
-            <img class="optImg" src="./imgs/yin.png" />
-            <n-input-number
-              :disabled="true"
-              placeholder=""
-              :value="speedNum"
-              :show-button="false"
-              @update:value="(num:number)=>{
-                if(num){
-                  if(num<=50){
-                    speedNum=50
-                  }else if(num>=200){
-                    speedNum=200
-                  }else{
-                    speedNum=num
-                  }
-                }
-              }"
-            />
-          </div>
-          <img
-            class="optImg"
-            src="./imgs/jian.png"
-            @click="
-              () => {
-                if (speedNum > 50) {
-                  speedNum--;
-                }
-              }
-            "
-          />
-        </div>
-      </div>
-      <div class="selectCon">
-        <n-select
-          v-model:value="beatVal"
-          :options="beatValOpt"
-          :show-checkmark="false"
-          :show-arrow="false"
-          :placement="'top'"
-          :render-tag="
-            ({ option }:any) => {
-              return h('div',{class:'beatName'},[h('div',option.labelHide),h('div','拍')])
-            }
-          "
-          :render-label="
-            (option:any) => {
-              return h('div', option.labelHide);
-            }
-          "
-        ></n-select>
-        <n-select
-          class="beatSymbolSel"
-          v-model:value="beatSymbol"
-          :options="beatSymbolOpt"
-          :show-checkmark="false"
-          :show-arrow="false"
-          :placement="'top'"
-          :render-tag="
-            ({ option }:any) => {
-              return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
-            }
-          "
-          :render-label="
-            (option:any) => {
-              return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
-            }
-          "
-        ></n-select>
-      </div>
-      <div class="sliderList">
-        <img src="./imgs/sound.png" alt="" />
-        <NSlider v-model:value="volumeNum" :step="1" :tooltip="false">
-          <template #thumb>
-            <img class="thumbDot" src="./imgs/dotBtn.png" alt="" />
-          </template>
-        </NSlider>
-        <span class="sliderText">{{ volumeNum }}</span>
-      </div>
-      <div v-if="playState === 'pause'" class="playBtn" @click="startPlay">
-        <span>播放</span>
-        <img src="./imgs/playtBtn.png" />
-      </div>
-      <div v-else class="playBtn" @click="pausePlay">
-        <span>暂停</span>
-        <img class="pauseImg" src="./imgs/pauseBtn.png" />
-      </div>
-    </div>
-    <Dragbom></Dragbom>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted, h, watch } from 'vue';
-import { NInputNumber, NSelect, NSlider } from 'naive-ui';
-import useMetronome from './useMetronome';
-import Dragbom from '@/hooks/useDrag/dragbom';
-import { getCachePos, setCachePos } from './useMetronome';
-import { useUserStore } from '@/store/modules/users';
-
-const users = useUserStore();
-const emits = defineEmits<{
-  (e: 'windowMet'): void;
-  (e: 'closeMet'): void;
-  (e: 'playStateChange', state: 'play' | 'pause'): void;
-}>();
-
-/* dom */
-const mycanvasDom = ref<HTMLCanvasElement>();
-const dotDom = ref<HTMLElement>();
-/* select */
-const beatVal = ref('1');
-const beatValOpt = [
-  {
-    label: '',
-    labelHide: '0',
-    value: '0',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '1',
-    value: '1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '2',
-    value: '1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '3',
-    value: '1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '4',
-    value: '1-1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '5',
-    value: '1-1-1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '6',
-    value: '1-1-1-1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '7',
-    value: '1-1-1-1-1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '8',
-    value: '1-1-1-1-1-1-1-1',
-    class: 'beatValOptItem'
-  },
-  {
-    label: '',
-    labelHide: '9',
-    value: '1-1-1-1-1-1-1-1-1',
-    class: 'beatValOptItem'
-  }
-];
-const beatSymbol = ref('1');
-const beatSymbolOpt = [
-  {
-    label: '',
-    labelHide: '1',
-    value: '1',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '2',
-    value: '0.5-0.5',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '3',
-    value: '0.3333333-0.3333333-0.3333333',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '4',
-    value: '0.25-0.25-0.25-0.25',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '5',
-    value: '0.6666666-0.3333333',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '6',
-    value: '0.75-0.25',
-    class: 'beatSymbolOptItem'
-  },
-  {
-    label: '',
-    labelHide: '7',
-    value: '0.5-0.25-0.25',
-    class: 'beatSymbolOptItem'
-  }
-];
-const { volumeNum, playState, speedNum, startPlay, pausePlay } = useMetronome(
-  beatVal,
-  beatSymbol
-);
-
-onMounted(() => {
-  const cachePos = getCachePos(users.info.id);
-  if (cachePos) {
-    beatVal.value = cachePos.beatVal;
-    beatSymbol.value = cachePos.beatSymbol;
-    volumeNum.value = cachePos.volumeNum;
-    speedNum.value = cachePos.speedNum;
-  }
-  /*  n-input-number 有bug  input-props设置maxlength不上去 */
-  const iptDoms = document.querySelector(
-    '.Metronome .MetronomeBox .optMid .n-input__input-el'
-  );
-  iptDoms?.setAttribute('maxlength', '3');
-  getCircleBar(speedToScalc(speedNum.value));
-});
-
-onUnmounted(() => {
-  setCachePos(users.info.id, {
-    beatVal: beatVal.value,
-    beatSymbol: beatSymbol.value,
-    volumeNum: volumeNum.value,
-    speedNum: speedNum.value
-  });
-});
-
-watch(playState, () => {
-  emits('playStateChange', playState.value);
-});
-watch(speedNum, () => {
-  if (playState.value === 'play') {
-    pausePlay();
-  }
-  getCircleBar(speedToScalc(speedNum.value));
-});
-watch([beatVal, beatSymbol], () => {
-  if (playState.value === 'play') {
-    pausePlay();
-  }
-});
-function speedToScalc(speed: number) {
-  return ((speed - 50) / 150) * 100;
-}
-function scalcToSpeed(scalc: number) {
-  return Math.round((scalc * 150) / 100 + 50);
-}
-const handleEventCanvas = (e: MouseEvent | TouchEvent) => {
-  const circle = mycanvasDom.value!.getBoundingClientRect();
-  const centerX = circle.left + circle.width / 2;
-  const centerY = circle.top + circle.height / 2;
-  const event = isTouchEvent(e) ? e.touches[0] : e;
-  const angle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
-  const percentage = (angle * (180 / Math.PI) + 180) / 360;
-  const scalc = Math.round(percentage * 100) - 25;
-  speedNum.value = scalcToSpeed(scalc < 0 ? 100 + scalc : scalc);
-};
-function handleDotMousedown() {
-  function onMouseup() {
-    document.removeEventListener('mousemove', onMousemove);
-    document.removeEventListener('mouseup', onMouseup);
-  }
-  document.addEventListener('mousemove', onMousemove);
-  document.addEventListener('mouseup', onMouseup);
-}
-function handleDotTouchstart() {
-  function onTouchend() {
-    document.removeEventListener('touchmove', onTouchmove);
-    document.removeEventListener('touchend', onTouchend);
-  }
-  document.addEventListener('touchmove', onTouchmove);
-  document.addEventListener('touchend', onTouchend);
-}
-function onMousemove(e: MouseEvent) {
-  handleEventCanvas(e);
-}
-function onTouchmove(e: TouchEvent) {
-  handleEventCanvas(e);
-}
-// 根据百分比画圆
-function getCircleBar(steps: number) {
-  const radius = 121; //半径
-  const colorList = ['#51DBFF', '#009FFF'];
-  const mycanvas = mycanvasDom.value!;
-  const ctx = mycanvas.getContext('2d')!;
-  // 每次进来清空画布
-  ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
-  //找到画布的中心点
-  const canvasX = mycanvas.width / 2;
-  const canvasY = mycanvas.height / 2;
-  //进度条是100%,所以要把一圈360度分成100份
-  const progress = (Math.PI * 2) / 100;
-  const ao = -Math.PI / 2 + steps * progress;
-  const x1 = radius + Math.cos(ao) * radius;
-  const y1 = radius + Math.sin(ao) * radius;
-  // 这里开始算坐标
-  dotDom.value!.style.left = x1 - 7 + 'Px';
-  dotDom.value!.style.top = y1 - 7 + 'Px';
-  // 绘制渐变色
-  const gradientDirection = {
-    x1: 0,
-    y1: 0,
-    x2: radius * 2, // 半径*2
-    y2: 0
-  };
-  gradientDirection.y2 = radius * 2;
-  const gradient = ctx.createLinearGradient(
-    gradientDirection.x1,
-    gradientDirection.y1,
-    gradientDirection.x2,
-    gradientDirection.y2
-  );
-  colorList.map((color, index) => {
-    gradient.addColorStop(index, color);
-  });
-  ctx.strokeStyle = gradient;
-  ctx.lineWidth = 10;
-  ctx.save();
-  ctx.beginPath();
-  ctx.arc(
-    canvasX,
-    canvasY,
-    radius,
-    -Math.PI / 2,
-    -Math.PI / 2 + steps * progress,
-    false
-  );
-  ctx.stroke();
-  ctx.closePath();
-  ctx.restore();
-}
-function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
-  return window.TouchEvent && e instanceof window.TouchEvent;
-}
-defineExpose({
-  startPlay,
-  pausePlay,
-  speedNum,
-  beatSymbol
-});
-</script>
-
-<style lang="less" scoped>
-.Metronome {
-  width: 565Px;
-  height: 554Px;
-  background: url('./imgs/bg.png') no-repeat;
-  background-size: 100% 100%;
-  padding: 26Px 26Px 0;
-  &:deep(.bom_drag) {
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    height: 76Px;
-    padding: 0 28Px;
-    & > div {
-      width: 40Px;
-      height: 40Px;
-      border-radius: 0 0 18Px 0;
-      &:first-child {
-        border-radius: 0 0 0 18Px;
-      }
-    }
-  }
-  .MetronomeBox {
-    width: 100%;
-    position: relative;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    z-index: 2;
-  }
-  .headTools {
-    position: absolute;
-    right: 20Px;
-    top: 18Px;
-    & > img {
-      margin-left: 18Px;
-      width: 24Px;
-      height: 24Px;
-      cursor: pointer;
-    }
-  }
-  .beatWrap {
-    margin-top: 30Px;
-    width: 252Px;
-    height: 252Px;
-    position: relative;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    .dot {
-      position: absolute;
-      width: 24Px;
-      height: 24Px;
-      background: url('./imgs/dot.png') no-repeat;
-      background-size: 100% 100%;
-      cursor: pointer;
-      z-index: 3;
-    }
-    .round {
-      position: absolute;
-      left: 0;
-      top: 0;
-      width: 100%;
-      height: 100%;
-      border-radius: 50%;
-      border: 10Px solid #d1d1d1;
-      z-index: 1;
-    }
-    .mycanvas {
-      position: absolute;
-      left: 0;
-      top: 0;
-      z-index: 2;
-    }
-    .beatCon {
-      border-radius: 50%;
-      z-index: 4;
-      width: 200Px;
-      height: 200Px;
-      background: url('./imgs/yuan.png') no-repeat;
-      background-size: 100% 100%;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      .optMid {
-        margin: 5Px 0;
-        display: flex;
-        align-items: center;
-        :deep(.n-input-number) {
-          width: 74Px;
-          height: 52Px;
-          margin-left: 4Px;
-          .n-input {
-            height: 100%;
-            --n-height: 100% !important;
-            --n-color-focus: initial !important;
-            --n-color: initial !important;
-            --n-padding-left: initial !important;
-            --n-padding-right: initial !important;
-            --n-color-disabled: initial !important;
-            --n-caret-color: #3a3939 !important;
-            .n-input__border,
-            .n-input__state-border {
-              display: none;
-            }
-            .n-input__input-el {
-              text-align: center;
-              font-family: DINA !important;
-              font-weight: bold !important;
-              font-size: 45Px !important;
-              color: #000000 !important;
-            }
-            &.n-input--disabled {
-              cursor: initial;
-              .n-input__input-el {
-                cursor: initial;
-              }
-            }
-          }
-        }
-      }
-      .optImg {
-        cursor: pointer;
-        width: 35Px;
-        height: 35Px;
-      }
-    }
-  }
-  .selectCon {
-    display: flex;
-    margin-top: 22Px;
-    :deep(.n-select) {
-      width: 130Px;
-      .n-base-selection--selected {
-        --n-height: 48Px !important;
-        --n-border-radius: 24Px !important;
-        .n-base-selection__border,
-        .n-base-selection__state-border {
-          display: none;
-        }
-        .n-base-selection-input {
-          padding: 0;
-          .n-base-selection-input__content {
-            .beatName {
-              display: flex;
-              justify-content: center;
-              align-items: center;
-              padding-top: 2Px;
-              & > div:first-child {
-                font-weight: 500;
-                font-size: 30Px;
-                color: #333333;
-              }
-              & > div:last-child {
-                margin-left: 2Px;
-                font-weight: 500;
-                font-size: 14Px;
-                color: #333333;
-                margin-top: 4Px;
-              }
-            }
-          }
-        }
-      }
-    }
-    :deep(.beatSymbolSel.n-select) {
-      margin-left: 15Px;
-      .n-base-selection-input__content {
-        display: flex;
-        justify-content: center;
-        .beatSymbolImg {
-          width: 44Px;
-          height: 46Px;
-          &.beatSymbolImg1 {
-            background: url('./imgs/b1.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg2 {
-            background: url('./imgs/b2.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg3 {
-            background: url('./imgs/b3.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg4 {
-            background: url('./imgs/b4.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg5 {
-            background: url('./imgs/b5.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg6 {
-            background: url('./imgs/b6.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg7 {
-            background: url('./imgs/b7.png') no-repeat;
-            background-size: 100% 100%;
-          }
-        }
-      }
-    }
-  }
-  .sliderList {
-    margin-top: 24Px;
-    display: flex;
-    align-items: center;
-    & > img {
-      width: 18Px;
-      height: 18Px;
-      margin-right: 16Px;
-    }
-    .sliderText {
-      width: 32Px;
-      font-weight: 500;
-      font-size: 16Px;
-      color: #1cacf1;
-      margin-left: 16Px;
-    }
-    :deep(.n-slider-rail) {
-      height: 6Px;
-      line-height: 6Px;
-      background: #ffffff;
-      border-radius: 4Px;
-      width: 194Px;
-      .n-slider-rail__fill {
-        background: linear-gradient(90deg, #63daff 0%, #1798ff 100%);
-        border-radius: 4Px;
-      }
-    }
-    .thumbDot {
-      width: 21Px;
-      height: 21Px;
-    }
-  }
-  .playBtn {
-    cursor: pointer;
-    margin-top: 20Px;
-    width: 272Px;
-    height: 54Px;
-    background: #00acff;
-    border-radius: 27Px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    font-weight: 600;
-    font-size: 18Px;
-    color: #ffffff;
-    & > img {
-      margin-left: 14Px;
-      width: 13Px;
-      height: 14Px;
-      &.pauseImg {
-        width: 12Px;
-        height: 13Px;
-      }
-    }
-  }
-}
-</style>
-<style lang="less">
-.n-base-select-menu.n-select-menu {
-  .beatValOptItem {
-    padding: 0 9Px !important;
-    display: flex;
-    justify-content: center;
-    &.n-base-select-option::before {
-      transition: initial;
-    }
-    &.n-base-select-option--selected {
-      .n-base-select-option__content {
-        color: #47a7fe;
-      }
-    }
-    &.n-base-select-option--pending {
-      .n-base-select-option__content {
-        color: #ffffff;
-      }
-      &::before {
-        background: #47a7fe !important;
-        border-radius: 6Px !important;
-        left: 9Px;
-        right: 9Px;
-      }
-    }
-    .n-base-select-option__content {
-      font-weight: 500;
-      font-size: 18Px;
-      color: #777777;
-    }
-  }
-  .beatSymbolOptItem {
-    display: flex;
-    justify-content: center;
-    &.n-base-select-option::before {
-      transition: initial;
-    }
-    &.n-base-select-option--selected {
-      .n-base-select-option__content {
-        .beatSymbolImg {
-          &.beatSymbolImg1 {
-            background: url('./imgs/a1.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg2 {
-            background: url('./imgs/a2.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg3 {
-            background: url('./imgs/a3.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg4 {
-            background: url('./imgs/a4.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg5 {
-            background: url('./imgs/a5.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg6 {
-            background: url('./imgs/a6.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg7 {
-            background: url('./imgs/a7.png') no-repeat;
-            background-size: 100% 100%;
-          }
-        }
-      }
-    }
-    &.n-base-select-option--pending {
-      .n-base-select-option__content {
-        .beatSymbolImg {
-          &.beatSymbolImg1 {
-            background: url('./imgs/c1.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg2 {
-            background: url('./imgs/c2.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg3 {
-            background: url('./imgs/c3.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg4 {
-            background: url('./imgs/c4.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg5 {
-            background: url('./imgs/c5.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg6 {
-            background: url('./imgs/c6.png') no-repeat;
-            background-size: 100% 100%;
-          }
-          &.beatSymbolImg7 {
-            background: url('./imgs/c7.png') no-repeat;
-            background-size: 100% 100%;
-          }
-        }
-      }
-      &::before {
-        background: #47a7fe !important;
-        border-radius: 6Px !important;
-        left: 9Px;
-        right: 9Px;
-      }
-    }
-    .beatSymbolImg {
-      width: 44Px;
-      height: 46Px;
-      &.beatSymbolImg1 {
-        background: url('./imgs/b1.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg2 {
-        background: url('./imgs/b2.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg3 {
-        background: url('./imgs/b3.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg4 {
-        background: url('./imgs/b4.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg5 {
-        background: url('./imgs/b5.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg6 {
-        background: url('./imgs/b6.png') no-repeat;
-        background-size: 100% 100%;
-      }
-      &.beatSymbolImg7 {
-        background: url('./imgs/b7.png') no-repeat;
-        background-size: 100% 100%;
-      }
-    }
-  }
-}
-.metronomeConBoxClass_drag
-  .v-binder-follower-container
-  .n-base-select-menu.n-select-menu {
-  --n-border-radius: 10Px !important;
-  .n-scrollbar-rail.n-scrollbar-rail--vertical {
-    right: 3Px;
-    --n-scrollbar-width: 3Px !important;
-    opacity: 0.4;
-  }
-}
-</style>
+<template>
+  <div class="Metronome">
+    <div class="MetronomeBox">
+      <div class="headTools">
+        <img
+          src="./imgs/minMet.png"
+          @click="
+            () => {
+              emits('windowMet');
+            }
+          "
+        />
+        <img
+          src="./imgs/closeMet.png"
+          @click="
+            () => {
+              emits('closeMet');
+            }
+          "
+        />
+      </div>
+      <div class="beatWrap">
+        <div
+          class="dot"
+          ref="dotDom"
+          @mousedown="handleDotMousedown"
+          @touchstart="handleDotTouchstart"
+        ></div>
+        <div class="round"></div>
+        <canvas
+          ref="mycanvasDom"
+          class="mycanvas"
+          width="252"
+          height="252"
+          @click="handleEventCanvas"
+        ></canvas>
+        <div class="beatCon">
+          <img
+            class="optImg"
+            src="./imgs/jia.png"
+            @click="
+              () => {
+                if (speedNum < 200) {
+                  speedNum++;
+                }
+              }
+            "
+          />
+          <div class="optMid">
+            <img class="optImg" src="./imgs/yin.png" />
+            <n-input-number
+              :disabled="true"
+              placeholder=""
+              :value="speedNum"
+              :show-button="false"
+              @update:value="(num:number)=>{
+                if(num){
+                  if(num<=50){
+                    speedNum=50
+                  }else if(num>=200){
+                    speedNum=200
+                  }else{
+                    speedNum=num
+                  }
+                }
+              }"
+            />
+          </div>
+          <img
+            class="optImg"
+            src="./imgs/jian.png"
+            @click="
+              () => {
+                if (speedNum > 50) {
+                  speedNum--;
+                }
+              }
+            "
+          />
+        </div>
+      </div>
+      <div class="selectCon">
+        <n-select
+          v-model:value="beatVal"
+          :options="beatValOpt"
+          :show-checkmark="false"
+          :show-arrow="false"
+          :placement="'top'"
+          :render-tag="
+            ({ option }:any) => {
+              return h('div',{class:'beatName'},[h('div',option.labelHide),h('div','拍')])
+            }
+          "
+          :render-label="
+            (option:any) => {
+              return h('div', option.labelHide);
+            }
+          "
+        ></n-select>
+        <n-select
+          class="beatSymbolSel"
+          v-model:value="beatSymbol"
+          :options="beatSymbolOpt"
+          :show-checkmark="false"
+          :show-arrow="false"
+          :placement="'top'"
+          :render-tag="
+            ({ option }:any) => {
+              return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
+            }
+          "
+          :render-label="
+            (option:any) => {
+              return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
+            }
+          "
+        ></n-select>
+      </div>
+      <div class="sliderList">
+        <img src="./imgs/sound.png" alt="" />
+        <NSlider v-model:value="volumeNum" :step="1" :tooltip="false">
+          <template #thumb>
+            <img class="thumbDot" src="./imgs/dotBtn.png" alt="" />
+          </template>
+        </NSlider>
+        <span class="sliderText">{{ volumeNum }}</span>
+      </div>
+      <div v-if="playState === 'pause'" class="playBtn" @click="startPlay">
+        <span>播放</span>
+        <img src="./imgs/playtBtn.png" />
+      </div>
+      <div v-else class="playBtn" @click="pausePlay">
+        <span>暂停</span>
+        <img class="pauseImg" src="./imgs/pauseBtn.png" />
+      </div>
+    </div>
+    <Dragbom></Dragbom>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted, onUnmounted, h, watch } from 'vue';
+import { NInputNumber, NSelect, NSlider } from 'naive-ui';
+import useMetronome from './useMetronome';
+import Dragbom from '@/hooks/useDrag/dragbom';
+import { getCachePos, setCachePos } from './useMetronome';
+import { useUserStore } from '@/store/modules/users';
+
+const users = useUserStore();
+const emits = defineEmits<{
+  (e: 'windowMet'): void;
+  (e: 'closeMet'): void;
+  (e: 'playStateChange', state: 'play' | 'pause'): void;
+}>();
+
+/* dom */
+const mycanvasDom = ref<HTMLCanvasElement>();
+const dotDom = ref<HTMLElement>();
+/* select */
+const beatVal = ref('1');
+const beatValOpt = [
+  {
+    label: '',
+    labelHide: '0',
+    value: '0',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '1',
+    value: '1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '2',
+    value: '1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '3',
+    value: '1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '4',
+    value: '1-1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '5',
+    value: '1-1-1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '6',
+    value: '1-1-1-1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '7',
+    value: '1-1-1-1-1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '8',
+    value: '1-1-1-1-1-1-1-1',
+    class: 'beatValOptItem'
+  },
+  {
+    label: '',
+    labelHide: '9',
+    value: '1-1-1-1-1-1-1-1-1',
+    class: 'beatValOptItem'
+  }
+];
+const beatSymbol = ref('1');
+const beatSymbolOpt = [
+  {
+    label: '',
+    labelHide: '1',
+    value: '1',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '2',
+    value: '0.5-0.5',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '3',
+    value: '0.3333333-0.3333333-0.3333333',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '4',
+    value: '0.25-0.25-0.25-0.25',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '5',
+    value: '0.6666666-0.3333333',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '6',
+    value: '0.75-0.25',
+    class: 'beatSymbolOptItem'
+  },
+  {
+    label: '',
+    labelHide: '7',
+    value: '0.5-0.25-0.25',
+    class: 'beatSymbolOptItem'
+  }
+];
+const { volumeNum, playState, speedNum, startPlay, pausePlay } = useMetronome(
+  beatVal,
+  beatSymbol
+);
+
+onMounted(() => {
+  const cachePos = getCachePos(users.info.id);
+  if (cachePos) {
+    beatVal.value = cachePos.beatVal;
+    beatSymbol.value = cachePos.beatSymbol;
+    volumeNum.value = cachePos.volumeNum;
+    speedNum.value = cachePos.speedNum;
+  }
+  /*  n-input-number 有bug  input-props设置maxlength不上去 */
+  const iptDoms = document.querySelector(
+    '.Metronome .MetronomeBox .optMid .n-input__input-el'
+  );
+  iptDoms?.setAttribute('maxlength', '3');
+  getCircleBar(speedToScalc(speedNum.value));
+});
+
+onUnmounted(() => {
+  setCachePos(users.info.id, {
+    beatVal: beatVal.value,
+    beatSymbol: beatSymbol.value,
+    volumeNum: volumeNum.value,
+    speedNum: speedNum.value
+  });
+});
+
+watch(playState, () => {
+  emits('playStateChange', playState.value);
+});
+watch(speedNum, () => {
+  if (playState.value === 'play') {
+    pausePlay();
+  }
+  getCircleBar(speedToScalc(speedNum.value));
+});
+watch([beatVal, beatSymbol], () => {
+  if (playState.value === 'play') {
+    pausePlay();
+  }
+});
+function speedToScalc(speed: number) {
+  return ((speed - 50) / 150) * 100;
+}
+function scalcToSpeed(scalc: number) {
+  return Math.round((scalc * 150) / 100 + 50);
+}
+const handleEventCanvas = (e: MouseEvent | TouchEvent) => {
+  const circle = mycanvasDom.value!.getBoundingClientRect();
+  const centerX = circle.left + circle.width / 2;
+  const centerY = circle.top + circle.height / 2;
+  const event = isTouchEvent(e) ? e.touches[0] : e;
+  const angle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
+  const percentage = (angle * (180 / Math.PI) + 180) / 360;
+  const scalc = Math.round(percentage * 100) - 25;
+  speedNum.value = scalcToSpeed(scalc < 0 ? 100 + scalc : scalc);
+};
+function handleDotMousedown() {
+  function onMouseup() {
+    document.removeEventListener('mousemove', onMousemove);
+    document.removeEventListener('mouseup', onMouseup);
+  }
+  document.addEventListener('mousemove', onMousemove);
+  document.addEventListener('mouseup', onMouseup);
+}
+function handleDotTouchstart() {
+  function onTouchend() {
+    document.removeEventListener('touchmove', onTouchmove);
+    document.removeEventListener('touchend', onTouchend);
+  }
+  document.addEventListener('touchmove', onTouchmove);
+  document.addEventListener('touchend', onTouchend);
+}
+function onMousemove(e: MouseEvent) {
+  handleEventCanvas(e);
+}
+function onTouchmove(e: TouchEvent) {
+  handleEventCanvas(e);
+}
+// 根据百分比画圆
+function getCircleBar(steps: number) {
+  const radius = 121; //半径
+  const colorList = ['#51DBFF', '#009FFF'];
+  const mycanvas = mycanvasDom.value!;
+  const ctx = mycanvas.getContext('2d')!;
+  // 每次进来清空画布
+  ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
+  //找到画布的中心点
+  const canvasX = mycanvas.width / 2;
+  const canvasY = mycanvas.height / 2;
+  //进度条是100%,所以要把一圈360度分成100份
+  const progress = (Math.PI * 2) / 100;
+  const ao = -Math.PI / 2 + steps * progress;
+  const x1 = radius + Math.cos(ao) * radius;
+  const y1 = radius + Math.sin(ao) * radius;
+  // 这里开始算坐标
+  dotDom.value!.style.left = x1 - 7 + 'Px';
+  dotDom.value!.style.top = y1 - 7 + 'Px';
+  // 绘制渐变色
+  const gradientDirection = {
+    x1: 0,
+    y1: 0,
+    x2: radius * 2, // 半径*2
+    y2: 0
+  };
+  gradientDirection.y2 = radius * 2;
+  const gradient = ctx.createLinearGradient(
+    gradientDirection.x1,
+    gradientDirection.y1,
+    gradientDirection.x2,
+    gradientDirection.y2
+  );
+  colorList.map((color, index) => {
+    gradient.addColorStop(index, color);
+  });
+  ctx.strokeStyle = gradient;
+  ctx.lineWidth = 10;
+  ctx.save();
+  ctx.beginPath();
+  ctx.arc(
+    canvasX,
+    canvasY,
+    radius,
+    -Math.PI / 2,
+    -Math.PI / 2 + steps * progress,
+    false
+  );
+  ctx.stroke();
+  ctx.closePath();
+  ctx.restore();
+}
+function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
+  return window.TouchEvent && e instanceof window.TouchEvent;
+}
+defineExpose({
+  startPlay,
+  pausePlay,
+  speedNum,
+  beatSymbol
+});
+</script>
+
+<style lang="less" scoped>
+.Metronome {
+  width: 565px;
+  height: 554px;
+  background: url('./imgs/bg.png') no-repeat;
+  background-size: 100% 100%;
+  padding: 26px 26px 0;
+  &:deep(.bom_drag) {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 76px;
+    padding: 0 28px;
+    & > div {
+      width: 40px;
+      height: 40px;
+      border-radius: 0 0 18px 0;
+      &:first-child {
+        border-radius: 0 0 0 18px;
+      }
+    }
+  }
+  .MetronomeBox {
+    width: 100%;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    z-index: 2;
+  }
+  .headTools {
+    position: absolute;
+    right: 20px;
+    top: 18px;
+    & > img {
+      margin-left: 18px;
+      width: 24px;
+      height: 24px;
+      cursor: pointer;
+    }
+  }
+  .beatWrap {
+    margin-top: 30px;
+    width: 252px;
+    height: 252px;
+    position: relative;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .dot {
+      position: absolute;
+      width: 24px;
+      height: 24px;
+      background: url('./imgs/dot.png') no-repeat;
+      background-size: 100% 100%;
+      cursor: pointer;
+      z-index: 3;
+    }
+    .round {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 100%;
+      height: 100%;
+      border-radius: 50%;
+      border: 10px solid #d1d1d1;
+      z-index: 1;
+    }
+    .mycanvas {
+      position: absolute;
+      left: 0;
+      top: 0;
+      z-index: 2;
+    }
+    .beatCon {
+      border-radius: 50%;
+      z-index: 4;
+      width: 200px;
+      height: 200px;
+      background: url('./imgs/yuan.png') no-repeat;
+      background-size: 100% 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      .optMid {
+        margin: 5px 0;
+        display: flex;
+        align-items: center;
+        :deep(.n-input-number) {
+          width: 74px;
+          height: 52px;
+          margin-left: 4px;
+          .n-input {
+            height: 100%;
+            --n-height: 100% !important;
+            --n-color-focus: initial !important;
+            --n-color: initial !important;
+            --n-padding-left: initial !important;
+            --n-padding-right: initial !important;
+            --n-color-disabled: initial !important;
+            --n-caret-color: #3a3939 !important;
+            .n-input__border,
+            .n-input__state-border {
+              display: none;
+            }
+            .n-input__input-el {
+              text-align: center;
+              font-family: DINA !important;
+              font-weight: bold !important;
+              font-size: 45px !important;
+              color: #000000 !important;
+            }
+            &.n-input--disabled {
+              cursor: initial;
+              .n-input__input-el {
+                cursor: initial;
+              }
+            }
+          }
+        }
+      }
+      .optImg {
+        cursor: pointer;
+        width: 35px;
+        height: 35px;
+      }
+    }
+  }
+  .selectCon {
+    display: flex;
+    margin-top: 22px;
+    :deep(.n-select) {
+      width: 130px;
+      .n-base-selection--selected {
+        --n-height: 48px !important;
+        --n-border-radius: 24px !important;
+        .n-base-selection__border,
+        .n-base-selection__state-border {
+          display: none;
+        }
+        .n-base-selection-input {
+          padding: 0;
+          .n-base-selection-input__content {
+            .beatName {
+              display: flex;
+              justify-content: center;
+              align-items: center;
+              padding-top: 2px;
+              & > div:first-child {
+                font-weight: 500;
+                font-size: 30px;
+                color: #333333;
+              }
+              & > div:last-child {
+                margin-left: 2px;
+                font-weight: 500;
+                font-size: 14px;
+                color: #333333;
+                margin-top: 4px;
+              }
+            }
+          }
+        }
+      }
+    }
+    :deep(.beatSymbolSel.n-select) {
+      margin-left: 15px;
+      .n-base-selection-input__content {
+        display: flex;
+        justify-content: center;
+        .beatSymbolImg {
+          width: 44px;
+          height: 46px;
+          &.beatSymbolImg1 {
+            background: url('./imgs/b1.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg2 {
+            background: url('./imgs/b2.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg3 {
+            background: url('./imgs/b3.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg4 {
+            background: url('./imgs/b4.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg5 {
+            background: url('./imgs/b5.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg6 {
+            background: url('./imgs/b6.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg7 {
+            background: url('./imgs/b7.png') no-repeat;
+            background-size: 100% 100%;
+          }
+        }
+      }
+    }
+  }
+  .sliderList {
+    margin-top: 24px;
+    display: flex;
+    align-items: center;
+    & > img {
+      width: 18px;
+      height: 18px;
+      margin-right: 16px;
+    }
+    .sliderText {
+      width: 32px;
+      font-weight: 500;
+      font-size: 16px;
+      color: #1cacf1;
+      margin-left: 16px;
+    }
+    :deep(.n-slider-rail) {
+      height: 6px;
+      line-height: 6px;
+      background: #ffffff;
+      border-radius: 4px;
+      width: 194px;
+      .n-slider-rail__fill {
+        background: linear-gradient(90deg, #63daff 0%, #1798ff 100%);
+        border-radius: 4px;
+      }
+    }
+    .thumbDot {
+      width: 21px;
+      height: 21px;
+    }
+  }
+  .playBtn {
+    cursor: pointer;
+    margin-top: 20px;
+    width: 272px;
+    height: 54px;
+    background: #00acff;
+    border-radius: 27px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-weight: 600;
+    font-size: 18px;
+    color: #ffffff;
+    & > img {
+      margin-left: 14px;
+      width: 13px;
+      height: 14px;
+      &.pauseImg {
+        width: 12px;
+        height: 13px;
+      }
+    }
+  }
+}
+</style>
+<style lang="less">
+.n-base-select-menu.n-select-menu {
+  .beatValOptItem {
+    padding: 0 9px !important;
+    display: flex;
+    justify-content: center;
+    &.n-base-select-option::before {
+      transition: initial;
+    }
+    &.n-base-select-option--selected {
+      .n-base-select-option__content {
+        color: #47a7fe;
+      }
+    }
+    &.n-base-select-option--pending {
+      .n-base-select-option__content {
+        color: #ffffff;
+      }
+      &::before {
+        background: #47a7fe !important;
+        border-radius: 6px !important;
+        left: 9px;
+        right: 9px;
+      }
+    }
+    .n-base-select-option__content {
+      font-weight: 500;
+      font-size: 18px;
+      color: #777777;
+    }
+  }
+  .beatSymbolOptItem {
+    display: flex;
+    justify-content: center;
+    &.n-base-select-option::before {
+      transition: initial;
+    }
+    &.n-base-select-option--selected {
+      .n-base-select-option__content {
+        .beatSymbolImg {
+          &.beatSymbolImg1 {
+            background: url('./imgs/a1.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg2 {
+            background: url('./imgs/a2.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg3 {
+            background: url('./imgs/a3.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg4 {
+            background: url('./imgs/a4.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg5 {
+            background: url('./imgs/a5.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg6 {
+            background: url('./imgs/a6.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg7 {
+            background: url('./imgs/a7.png') no-repeat;
+            background-size: 100% 100%;
+          }
+        }
+      }
+    }
+    &.n-base-select-option--pending {
+      .n-base-select-option__content {
+        .beatSymbolImg {
+          &.beatSymbolImg1 {
+            background: url('./imgs/c1.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg2 {
+            background: url('./imgs/c2.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg3 {
+            background: url('./imgs/c3.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg4 {
+            background: url('./imgs/c4.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg5 {
+            background: url('./imgs/c5.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg6 {
+            background: url('./imgs/c6.png') no-repeat;
+            background-size: 100% 100%;
+          }
+          &.beatSymbolImg7 {
+            background: url('./imgs/c7.png') no-repeat;
+            background-size: 100% 100%;
+          }
+        }
+      }
+      &::before {
+        background: #47a7fe !important;
+        border-radius: 6px !important;
+        left: 9px;
+        right: 9px;
+      }
+    }
+    .beatSymbolImg {
+      width: 44px;
+      height: 46px;
+      &.beatSymbolImg1 {
+        background: url('./imgs/b1.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg2 {
+        background: url('./imgs/b2.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg3 {
+        background: url('./imgs/b3.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg4 {
+        background: url('./imgs/b4.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg5 {
+        background: url('./imgs/b5.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg6 {
+        background: url('./imgs/b6.png') no-repeat;
+        background-size: 100% 100%;
+      }
+      &.beatSymbolImg7 {
+        background: url('./imgs/b7.png') no-repeat;
+        background-size: 100% 100%;
+      }
+    }
+  }
+}
+.metronomeConBoxClass_drag
+  .v-binder-follower-container
+  .n-base-select-menu.n-select-menu {
+  --n-border-radius: 10px !important;
+  .n-scrollbar-rail.n-scrollbar-rail--vertical {
+    right: 3px;
+    --n-scrollbar-width: 3px !important;
+    opacity: 0.4;
+  }
+}
+</style>

+ 2 - 2
src/components/Metronome/index.ts

@@ -1,2 +1,2 @@
-import MetronomeBox from './MetronomeBox.vue';
-export default MetronomeBox;
+import MetronomeBox from './MetronomeBox.vue';
+export default MetronomeBox;

+ 167 - 167
src/components/Metronome/useMetronome.ts

@@ -1,167 +1,167 @@
-import { ref, Ref, watch, onUnmounted, computed, onMounted } from 'vue';
-import tickWav from './audio/tick.wav';
-import tockWav from './audio/tock.wav';
-
-/*  播放相关 */
-export default function useMetronome(
-  beatVal: Ref<string>,
-  beatSymbol: Ref<string>
-) {
-  /* 音量 */
-  const volumeNum = ref(100);
-  watch(volumeNum, () => {
-    changeVolume(volumeNum.value / 100);
-  });
-  /* 播放状态 */
-  const playState = ref<'play' | 'pause'>('pause');
-  /* 速度 */
-  const speedNum = ref(90);
-  /* 音频hooks */
-  const { start, stop, changeVolume } = useHandleAudio([tickWav, tockWav]);
-  onUnmounted(() => {
-    pausePlay();
-  });
-  // 开始播放
-  async function startPlay() {
-    (await start(computeTimeArr.value, {
-      volume: volumeNum.value / 100,
-      playbackRate: speedNum.value / 60
-    })) && (playState.value = 'play');
-  }
-  //暂停播放
-  function pausePlay() {
-    stop();
-    playState.value = 'pause';
-  }
-  const computeTimeArr = computed(() => {
-    if (beatSymbol.value === '1') {
-      return beatVal.value.split('-');
-    }
-    return beatSymbol.value.split('-');
-  });
-
-  return {
-    volumeNum,
-    playState,
-    speedNum,
-    startPlay,
-    pausePlay
-  };
-}
-
-import Crunker from 'crunker';
-function useHandleAudio(files: [File | Blob | string, File | Blob | string]) {
-  const crunker = new Crunker();
-  async function handleBatetimeToAudio(
-    files: [File | Blob | string, File | Blob | string],
-    timeArr: string[],
-    playbackRate: number
-  ) {
-    try {
-      const buffersArr = await crunker.fetchAudio(...files);
-      const tickAudioBuff = buffersArr[0];
-      const tockAudioBuff = buffersArr[1];
-      let mergeAudio: AudioBuffer | undefined;
-      /* 处理音频合并 */
-      timeArr.map((time, index) => {
-        const timeNum = Number(time);
-        let nowBuff =
-          index === 0 && timeNum !== 0 ? tickAudioBuff : tockAudioBuff;
-        /*  当速度过快时候 响的时候大于整个拍子时候 对响进行裁剪  当间隔小于响的时候也进行裁剪 */
-        if (
-          1 / playbackRate - nowBuff.duration * timeArr.length <= 0 ||
-          (timeNum || 1) / playbackRate - nowBuff.duration <= 0
-        ) {
-          nowBuff = crunker.sliceAudio(
-            nowBuff,
-            0,
-            nowBuff.duration / playbackRate,
-            0,
-            0.12
-          );
-        }
-        mergeAudio
-          ? (mergeAudio = crunker.concatAudio([mergeAudio, nowBuff]))
-          : (mergeAudio = nowBuff);
-        mergeAudio = crunker.padAudio(
-          mergeAudio,
-          mergeAudio.duration - 0.01, // 预留0.01的安全距离 crunker这里有bug duration时长精确度不够
-          (timeNum || 1) / playbackRate - nowBuff.duration
-        );
-      });
-      return mergeAudio;
-    } catch (err) {
-      console.log(err);
-      return undefined;
-    }
-  }
-
-  const audioCtx = crunker.context;
-  let audioSourceNode: AudioBufferSourceNode | null;
-  let audioGainNode: GainNode | null;
-  async function start(
-    timeArr: string[],
-    opt: { volume: number; playbackRate: number }
-  ) {
-    const buffer = await handleBatetimeToAudio(
-      files,
-      timeArr,
-      opt.playbackRate
-    );
-    if (buffer) {
-      audioSourceNode = audioCtx.createBufferSource();
-      audioSourceNode.buffer = buffer;
-      audioGainNode = audioCtx.createGain();
-      audioSourceNode.connect(audioGainNode);
-      audioGainNode.connect(audioCtx.destination);
-      audioGainNode.gain.value = opt.volume;
-      audioSourceNode.loop = true;
-      audioSourceNode.start();
-      return true;
-    } else {
-      return false;
-    }
-  }
-  function stop() {
-    audioSourceNode?.stop();
-    audioSourceNode = null;
-    audioGainNode = null;
-  }
-  function changeVolume(volume: number) {
-    audioGainNode && (audioGainNode.gain.value = volume);
-  }
-  return {
-    start,
-    stop,
-    changeVolume
-  };
-}
-
-// 缓存
-const localStorageName = 'metronomePos';
-export function getCachePos(
-  useId: string
-): null | undefined | Record<string, any> {
-  const localCachePos = localStorage.getItem(localStorageName);
-  if (localCachePos) {
-    try {
-      return JSON.parse(localCachePos)[useId + localStorageName];
-    } catch {
-      return null;
-    }
-  }
-  return null;
-}
-export function setCachePos(useId: string, pos: Record<string, any>) {
-  const localCachePos = localStorage.getItem(localStorageName);
-  let cachePosObj: Record<string, any> = {};
-  if (localCachePos) {
-    try {
-      cachePosObj = JSON.parse(localCachePos);
-    } catch {
-      //
-    }
-  }
-  cachePosObj[useId + localStorageName] = pos;
-  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
-}
+import { ref, Ref, watch, onUnmounted, computed, onMounted } from 'vue';
+import tickWav from './audio/tick.wav';
+import tockWav from './audio/tock.wav';
+
+/*  播放相关 */
+export default function useMetronome(
+  beatVal: Ref<string>,
+  beatSymbol: Ref<string>
+) {
+  /* 音量 */
+  const volumeNum = ref(100);
+  watch(volumeNum, () => {
+    changeVolume(volumeNum.value / 100);
+  });
+  /* 播放状态 */
+  const playState = ref<'play' | 'pause'>('pause');
+  /* 速度 */
+  const speedNum = ref(90);
+  /* 音频hooks */
+  const { start, stop, changeVolume } = useHandleAudio([tickWav, tockWav]);
+  onUnmounted(() => {
+    pausePlay();
+  });
+  // 开始播放
+  async function startPlay() {
+    (await start(computeTimeArr.value, {
+      volume: volumeNum.value / 100,
+      playbackRate: speedNum.value / 60
+    })) && (playState.value = 'play');
+  }
+  //暂停播放
+  function pausePlay() {
+    stop();
+    playState.value = 'pause';
+  }
+  const computeTimeArr = computed(() => {
+    if (beatSymbol.value === '1') {
+      return beatVal.value.split('-');
+    }
+    return beatSymbol.value.split('-');
+  });
+
+  return {
+    volumeNum,
+    playState,
+    speedNum,
+    startPlay,
+    pausePlay
+  };
+}
+
+import Crunker from 'crunker';
+function useHandleAudio(files: [File | Blob | string, File | Blob | string]) {
+  const crunker = new Crunker();
+  async function handleBatetimeToAudio(
+    files: [File | Blob | string, File | Blob | string],
+    timeArr: string[],
+    playbackRate: number
+  ) {
+    try {
+      const buffersArr = await crunker.fetchAudio(...files);
+      const tickAudioBuff = buffersArr[0];
+      const tockAudioBuff = buffersArr[1];
+      let mergeAudio: AudioBuffer | undefined;
+      /* 处理音频合并 */
+      timeArr.map((time, index) => {
+        const timeNum = Number(time);
+        let nowBuff =
+          index === 0 && timeNum !== 0 ? tickAudioBuff : tockAudioBuff;
+        /*  当速度过快时候 响的时候大于整个拍子时候 对响进行裁剪  当间隔小于响的时候也进行裁剪 */
+        if (
+          1 / playbackRate - nowBuff.duration * timeArr.length <= 0 ||
+          (timeNum || 1) / playbackRate - nowBuff.duration <= 0
+        ) {
+          nowBuff = crunker.sliceAudio(
+            nowBuff,
+            0,
+            nowBuff.duration / playbackRate,
+            0,
+            0.12
+          );
+        }
+        mergeAudio
+          ? (mergeAudio = crunker.concatAudio([mergeAudio, nowBuff]))
+          : (mergeAudio = nowBuff);
+        mergeAudio = crunker.padAudio(
+          mergeAudio,
+          mergeAudio.duration - 0.01, // 预留0.01的安全距离 crunker这里有bug duration时长精确度不够
+          (timeNum || 1) / playbackRate - nowBuff.duration
+        );
+      });
+      return mergeAudio;
+    } catch (err) {
+      console.log(err);
+      return undefined;
+    }
+  }
+
+  const audioCtx = crunker.context;
+  let audioSourceNode: AudioBufferSourceNode | null;
+  let audioGainNode: GainNode | null;
+  async function start(
+    timeArr: string[],
+    opt: { volume: number; playbackRate: number }
+  ) {
+    const buffer = await handleBatetimeToAudio(
+      files,
+      timeArr,
+      opt.playbackRate
+    );
+    if (buffer) {
+      audioSourceNode = audioCtx.createBufferSource();
+      audioSourceNode.buffer = buffer;
+      audioGainNode = audioCtx.createGain();
+      audioSourceNode.connect(audioGainNode);
+      audioGainNode.connect(audioCtx.destination);
+      audioGainNode.gain.value = opt.volume;
+      audioSourceNode.loop = true;
+      audioSourceNode.start();
+      return true;
+    } else {
+      return false;
+    }
+  }
+  function stop() {
+    audioSourceNode?.stop();
+    audioSourceNode = null;
+    audioGainNode = null;
+  }
+  function changeVolume(volume: number) {
+    audioGainNode && (audioGainNode.gain.value = volume);
+  }
+  return {
+    start,
+    stop,
+    changeVolume
+  };
+}
+
+// 缓存
+const localStorageName = 'metronomePos';
+export function getCachePos(
+  useId: string
+): null | undefined | Record<string, any> {
+  const localCachePos = localStorage.getItem(localStorageName);
+  if (localCachePos) {
+    try {
+      return JSON.parse(localCachePos)[useId + localStorageName];
+    } catch {
+      return null;
+    }
+  }
+  return null;
+}
+export function setCachePos(useId: string, pos: Record<string, any>) {
+  const localCachePos = localStorage.getItem(localStorageName);
+  let cachePosObj: Record<string, any> = {};
+  if (localCachePos) {
+    try {
+      cachePosObj = JSON.parse(localCachePos);
+    } catch {
+      //
+    }
+  }
+  cachePosObj[useId + localStorageName] = pos;
+  localStorage.setItem(localStorageName, JSON.stringify(cachePosObj));
+}

+ 349 - 349
src/components/TheAuth/index.tsx

@@ -1,349 +1,349 @@
-import { defineComponent, reactive, ref } from 'vue';
-import styles from './index.module.less';
-import { NButton, NScrollbar } from 'naive-ui';
-import w1 from './images/window/1.png';
-import w2 from './images/window/2.png';
-import w3 from './images/window/3.png';
-import w4 from './images/window/4.png';
-import w5 from './images/window/5.png';
-import w6 from './images/window/6.png';
-import w7 from './images/window/7.png';
-import w8 from './images/window/8.png';
-
-import m1 from './images/mac/1.png';
-import m2 from './images/mac/2.png';
-import m3 from './images/mac/3.png';
-import m4 from './images/mac/4.png';
-import m5 from './images/mac/5.png';
-import m6 from './images/mac/6.png';
-import { browser } from '/src/helpers/utils';
-
-export default defineComponent({
-  name: 'the-auth',
-  emits: ['close'],
-  setup(props, { emit }) {
-    const scrollbarRef = ref();
-    const state = reactive({
-      step: 1,
-      maxStep: browser().ios ? 5 : 7
-    });
-    // 下载证书
-    const onDownload = () => {
-      // const agent = navigator.userAgent.toLowerCase();
-      const isMac = (function () {
-        return /macintosh|mac os x/i.test(navigator.userAgent);
-      })();
-      if (isMac) {
-        window.open(
-          `https://oss.dayaedu.com/https-ssl/安全证书.p12?v=${new Date().getTime()}`
-        );
-      } else {
-        window.open('https://oss.dayaedu.com/https-ssl/安全证书.pfx');
-      }
-      // emit('close');
-    };
-    return () => (
-      <div class={styles.theAuth}>
-        <div class={styles.theTitle}></div>
-        {/* <i class={styles.iconClose} onClick={() => emit('close')}></i> */}
-        <div class={styles.authContent}>
-          <div class={styles.steps}>
-            <NScrollbar ref={scrollbarRef}>
-              {browser().ios ? (
-                <>
-                  {state.step === 1 && (
-                    <>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>01</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            点击下方【下载证书】按钮,下载数据安全证书安装包
-                          </p>
-
-                          <div>
-                            <div
-                              class={styles.downloadCert}
-                              onClick={onDownload}></div>
-                          </div>
-                        </div>
-                      </div>
-
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>02</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            双击<span>《安全证书.p12》</span>
-                            ,输入钥匙串密码,点击
-                            <span>【修改钥匙串】</span>
-                            <span
-                              style={{ 'font-weight': 400, color: '#777777' }}>
-                              (若无此步骤则忽略)
-                            </span>
-                          </p>
-                          <div class={[styles.imgs, styles.imgs2]}>
-                            <img src={m1} class={styles.m1} />
-                            <img src={m6} class={styles.m6} />
-                          </div>
-                        </div>
-                      </div>
-                    </>
-                  )}
-
-                  {state.step === 2 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>03</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          输入证书密码:
-                          <span
-                            class={styles.red}
-                            style={{ 'text-decoration': 'underline' }}>
-                            colexiu.com
-                          </span>
-                          ,点击
-                          <span>【好】</span>
-                        </p>
-
-                        <div class={styles.imgs}>
-                          <img src={m2} class={styles.m2} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-
-                  {state.step === 3 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>04</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          <span class={styles.red}>重启浏览器</span>
-                          (在电脑屏幕左上方选择当前浏览器并点击
-                          <span>【退出】</span>),再重新打开音乐数字课堂网址
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={m3} class={styles.m3} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-
-                  {state.step === 4 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>05</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          在【选择证书】弹窗中点击<span>【确定】</span>按钮
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={m4} class={styles.m4} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-
-                  {state.step === 5 && (
-                    <>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>06</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            输入您的电脑密码,点击<span>【始终允许】</span>
-                            <span
-                              style={{ 'font-weight': 400, color: '#777777' }}>
-                              (若无此步骤则忽略)
-                            </span>
-                          </p>
-                          <div class={styles.imgs}>
-                            <img src={m5} class={styles.m5} />
-                          </div>
-                        </div>
-                      </div>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>07</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            证书安装完成,开始使用音乐数字课堂吧!
-                          </p>
-                        </div>
-                      </div>
-                    </>
-                  )}
-                </>
-              ) : (
-                <>
-                  {state.step === 1 && (
-                    <>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>01</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            点击下方【下载证书】按钮,下载数据安全证书安装包
-                          </p>
-                          <div>
-                            <div
-                              class={styles.downloadCert}
-                              onClick={onDownload}></div>
-                          </div>
-                        </div>
-                      </div>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>02</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            双击<span>《安全证书.pfx》</span>,出现弹窗后点击
-                            <span>【下一步】</span>
-                          </p>
-                          <div class={[styles.imgs, styles.imgs2]}>
-                            <img src={w1} class={styles.w1} />
-                            <img src={w2} class={styles.w2} />
-                          </div>
-                        </div>
-                      </div>
-                    </>
-                  )}
-
-                  {state.step === 2 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>03</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          点击<span>【下一步】</span>
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={w3} class={styles.w3} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-
-                  {state.step === 3 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>04</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          点击<span>【下一步】</span>
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={w4} class={styles.w4} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-                  {state.step === 4 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>05</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          点击<span>【下一步】</span>
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={w8} class={styles.w8} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-                  {state.step === 5 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>06</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          点击<span>【完成】</span>
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={w5} class={styles.w5} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-                  {state.step === 6 && (
-                    <div class={styles.step}>
-                      <div class={styles.stepNum}>07</div>
-                      <div class={styles.stepContent}>
-                        <p class={styles.txt}>
-                          点击<span>【确定】</span>
-                        </p>
-                        <div class={styles.imgs}>
-                          <img src={w6} class={styles.w6} />
-                        </div>
-                      </div>
-                    </div>
-                  )}
-                  {state.step === 7 && (
-                    <>
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>08</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            <span class={styles.red}>重启浏览器</span>
-                            ,打开音乐数字课堂网址
-                          </p>
-                        </div>
-                      </div>
-
-                      <div class={styles.step}>
-                        <div class={styles.stepNum}>09</div>
-                        <div class={styles.stepContent}>
-                          <p class={styles.txt}>
-                            在【选择证书】弹窗中点击<span>【确定】</span>
-                            按钮,证书安装完成,开始使用音乐数字课堂吧!
-                            {/* (若浏览器没有弹出该弹窗请
-                        <span class={styles.red}>重启电脑</span>
-                        后重试) */}
-                          </p>
-                          <img src={w7} class={styles.w7} />
-                        </div>
-                      </div>
-                    </>
-                  )}
-
-                  {/* <div class={styles.step}>
-                    <div class={styles.stepNum}>05</div>
-                    <div class={styles.stepContent}>
-                      <p class={styles.txt}>
-                        证书安装完成,开始使用音乐数字课堂吧。
-                      </p>
-                    </div>
-                  </div> */}
-                </>
-              )}
-            </NScrollbar>
-          </div>
-
-          <div class={styles.btnGroup}>
-            {/* <NButton round type="primary" onClick={onDownload}>
-              下载证书
-            </NButton> */}
-            {state.step > 1 && (
-              <div
-                class={[styles.btn, styles.btnUp]}
-                style={{
-                  cursor: state.step <= 1 ? 'not-allowed' : 'pointer'
-                }}
-                onClick={() => {
-                  if (state.step <= 1) return;
-                  state.step -= 1;
-                  scrollbarRef.value.scrollTo({ top: 0, left: 0 });
-                }}></div>
-            )}
-
-            <div
-              class={[
-                styles.btn,
-                styles.btnDown,
-                state.step === state.maxStep && styles.btnDone
-              ]}
-              onClick={() => {
-                if (state.step >= state.maxStep) {
-                  emit('close');
-                } else {
-                  state.step += 1;
-                }
-
-                scrollbarRef.value.scrollTo({ top: 0, left: 0 });
-              }}></div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import { NButton, NScrollbar } from 'naive-ui';
+import w1 from './images/window/1.png';
+import w2 from './images/window/2.png';
+import w3 from './images/window/3.png';
+import w4 from './images/window/4.png';
+import w5 from './images/window/5.png';
+import w6 from './images/window/6.png';
+import w7 from './images/window/7.png';
+import w8 from './images/window/8.png';
+
+import m1 from './images/mac/1.png';
+import m2 from './images/mac/2.png';
+import m3 from './images/mac/3.png';
+import m4 from './images/mac/4.png';
+import m5 from './images/mac/5.png';
+import m6 from './images/mac/6.png';
+import { browser } from '/src/helpers/utils';
+
+export default defineComponent({
+  name: 'the-auth',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const scrollbarRef = ref();
+    const state = reactive({
+      step: 1,
+      maxStep: browser().ios ? 5 : 7
+    });
+    // 下载证书
+    const onDownload = () => {
+      // const agent = navigator.userAgent.toLowerCase();
+      const isMac = (function () {
+        return /macintosh|mac os x/i.test(navigator.userAgent);
+      })();
+      if (isMac) {
+        window.open(
+          `https://oss.dayaedu.com/https-ssl/安全证书.p12?v=${new Date().getTime()}`
+        );
+      } else {
+        window.open('https://oss.dayaedu.com/https-ssl/安全证书.pfx');
+      }
+      // emit('close');
+    };
+    return () => (
+      <div class={styles.theAuth}>
+        <div class={styles.theTitle}></div>
+        {/* <i class={styles.iconClose} onClick={() => emit('close')}></i> */}
+        <div class={styles.authContent}>
+          <div class={styles.steps}>
+            <NScrollbar ref={scrollbarRef}>
+              {browser().ios ? (
+                <>
+                  {state.step === 1 && (
+                    <>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>01</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            点击下方【下载证书】按钮,下载数据安全证书安装包
+                          </p>
+
+                          <div>
+                            <div
+                              class={styles.downloadCert}
+                              onClick={onDownload}></div>
+                          </div>
+                        </div>
+                      </div>
+
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>02</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            双击<span>《安全证书.p12》</span>
+                            ,输入钥匙串密码,点击
+                            <span>【修改钥匙串】</span>
+                            <span
+                              style={{ 'font-weight': 400, color: '#777777' }}>
+                              (若无此步骤则忽略)
+                            </span>
+                          </p>
+                          <div class={[styles.imgs, styles.imgs2]}>
+                            <img src={m1} class={styles.m1} />
+                            <img src={m6} class={styles.m6} />
+                          </div>
+                        </div>
+                      </div>
+                    </>
+                  )}
+
+                  {state.step === 2 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>03</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          输入证书密码:
+                          <span
+                            class={styles.red}
+                            style={{ 'text-decoration': 'underline' }}>
+                            colexiu.com
+                          </span>
+                          ,点击
+                          <span>【好】</span>
+                        </p>
+
+                        <div class={styles.imgs}>
+                          <img src={m2} class={styles.m2} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+
+                  {state.step === 3 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>04</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          <span class={styles.red}>重启浏览器</span>
+                          (在电脑屏幕左上方选择当前浏览器并点击
+                          <span>【退出】</span>),再重新打开音乐数字课堂网址
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={m3} class={styles.m3} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+
+                  {state.step === 4 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>05</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          在【选择证书】弹窗中点击<span>【确定】</span>按钮
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={m4} class={styles.m4} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+
+                  {state.step === 5 && (
+                    <>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>06</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            输入您的电脑密码,点击<span>【始终允许】</span>
+                            <span
+                              style={{ 'font-weight': 400, color: '#777777' }}>
+                              (若无此步骤则忽略)
+                            </span>
+                          </p>
+                          <div class={styles.imgs}>
+                            <img src={m5} class={styles.m5} />
+                          </div>
+                        </div>
+                      </div>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>07</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            证书安装完成,开始使用音乐数字课堂吧!
+                          </p>
+                        </div>
+                      </div>
+                    </>
+                  )}
+                </>
+              ) : (
+                <>
+                  {state.step === 1 && (
+                    <>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>01</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            点击下方【下载证书】按钮,下载数据安全证书安装包
+                          </p>
+                          <div>
+                            <div
+                              class={styles.downloadCert}
+                              onClick={onDownload}></div>
+                          </div>
+                        </div>
+                      </div>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>02</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            双击<span>《安全证书.pfx》</span>,出现弹窗后点击
+                            <span>【下一步】</span>
+                          </p>
+                          <div class={[styles.imgs, styles.imgs2]}>
+                            <img src={w1} class={styles.w1} />
+                            <img src={w2} class={styles.w2} />
+                          </div>
+                        </div>
+                      </div>
+                    </>
+                  )}
+
+                  {state.step === 2 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>03</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          点击<span>【下一步】</span>
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={w3} class={styles.w3} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+
+                  {state.step === 3 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>04</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          点击<span>【下一步】</span>
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={w4} class={styles.w4} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+                  {state.step === 4 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>05</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          点击<span>【下一步】</span>
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={w8} class={styles.w8} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+                  {state.step === 5 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>06</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          点击<span>【完成】</span>
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={w5} class={styles.w5} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+                  {state.step === 6 && (
+                    <div class={styles.step}>
+                      <div class={styles.stepNum}>07</div>
+                      <div class={styles.stepContent}>
+                        <p class={styles.txt}>
+                          点击<span>【确定】</span>
+                        </p>
+                        <div class={styles.imgs}>
+                          <img src={w6} class={styles.w6} />
+                        </div>
+                      </div>
+                    </div>
+                  )}
+                  {state.step === 7 && (
+                    <>
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>08</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            <span class={styles.red}>重启浏览器</span>
+                            ,打开音乐数字课堂网址
+                          </p>
+                        </div>
+                      </div>
+
+                      <div class={styles.step}>
+                        <div class={styles.stepNum}>09</div>
+                        <div class={styles.stepContent}>
+                          <p class={styles.txt}>
+                            在【选择证书】弹窗中点击<span>【确定】</span>
+                            按钮,证书安装完成,开始使用音乐数字课堂吧!
+                            {/* (若浏览器没有弹出该弹窗请
+                        <span class={styles.red}>重启电脑</span>
+                        后重试) */}
+                          </p>
+                          <img src={w7} class={styles.w7} />
+                        </div>
+                      </div>
+                    </>
+                  )}
+
+                  {/* <div class={styles.step}>
+                    <div class={styles.stepNum}>05</div>
+                    <div class={styles.stepContent}>
+                      <p class={styles.txt}>
+                        证书安装完成,开始使用音乐数字课堂吧。
+                      </p>
+                    </div>
+                  </div> */}
+                </>
+              )}
+            </NScrollbar>
+          </div>
+
+          <div class={styles.btnGroup}>
+            {/* <NButton round type="primary" onClick={onDownload}>
+              下载证书
+            </NButton> */}
+            {state.step > 1 && (
+              <div
+                class={[styles.btn, styles.btnUp]}
+                style={{
+                  cursor: state.step <= 1 ? 'not-allowed' : 'pointer'
+                }}
+                onClick={() => {
+                  if (state.step <= 1) return;
+                  state.step -= 1;
+                  scrollbarRef.value.scrollTo({ top: 0, left: 0 });
+                }}></div>
+            )}
+
+            <div
+              class={[
+                styles.btn,
+                styles.btnDown,
+                state.step === state.maxStep && styles.btnDone
+              ]}
+              onClick={() => {
+                if (state.step >= state.maxStep) {
+                  emit('close');
+                } else {
+                  state.step += 1;
+                }
+
+                scrollbarRef.value.scrollTo({ top: 0, left: 0 });
+              }}></div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 31 - 23
src/components/TheEmpty/index.tsx

@@ -1,23 +1,31 @@
-import { defineComponent } from 'vue';
-import styles from './index.module.less';
-import { NEmpty, NImage } from 'naive-ui';
-import emptyImg from './images/nomore.png'
-
-export default defineComponent({
-  name: 'the-empty',
-  props: {
-    description: {
-      type: String,
-      default: '暂无数据'
-    }
-  },
-  setup(props) {
-    return () => (
-      <div class={styles.theEmtpy}>
-        <NEmpty description={props.description} v-slots={{
-          icon: () => <NImage class={styles.emptyImg} previewDisabled src={emptyImg}></NImage>
-        }} />
-      </div>
-    );
-  }
-});
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import { NEmpty, NImage } from 'naive-ui';
+import emptyImg from './images/nomore.png';
+
+export default defineComponent({
+  name: 'the-empty',
+  props: {
+    description: {
+      type: String,
+      default: '暂无数据'
+    }
+  },
+  setup(props) {
+    return () => (
+      <div class={styles.theEmtpy}>
+        <NEmpty
+          description={props.description}
+          v-slots={{
+            icon: () => (
+              <NImage
+                class={styles.emptyImg}
+                previewDisabled
+                src={emptyImg}></NImage>
+            )
+          }}
+        />
+      </div>
+    );
+  }
+});

+ 96 - 96
src/components/TheNoticeBar/index.tsx

@@ -1,96 +1,96 @@
-import { defineComponent, reactive, ref, watch } from 'vue';
-import styles from './index.module.less';
-
-const refAnimation = (callback: any) => {
-  requestAnimationFrame(() => {
-    requestAnimationFrame(() => {
-      callback();
-    });
-  });
-};
-
-export default defineComponent({
-  name: 'TheNoticeBar',
-  props: {
-    text: {
-      type: String,
-      default: ''
-    },
-    isAnimation: {
-      type: Boolean,
-      default: false
-    },
-    time: {
-      type: Number,
-      default: 5
-    }
-  },
-  setup(props) {
-    const wrapRef = ref();
-    const contentRef = ref();
-    const notiData = reactive({
-      isActive: false,
-      wrapWidth: 0,
-      contentWidth: 0,
-      contentStyle: {
-        transitionDuration: '0s',
-        transform: 'translateX(0px)'
-      },
-      time: null as any
-    });
-    const init = () => {
-      if (notiData.isActive || !contentRef.value || !wrapRef.value) return;
-      notiData.isActive = true;
-      notiData.contentWidth = contentRef.value.getBoundingClientRect().width;
-      notiData.wrapWidth = wrapRef.value.getBoundingClientRect().width;
-      startAnimate();
-    };
-    const startAnimate = () => {
-      if (notiData.contentWidth <= notiData.wrapWidth || !notiData.isActive) {
-        notiData.contentStyle.transitionDuration = '0s';
-        notiData.contentStyle.transform = 'translateX(0px)';
-        return;
-      }
-
-      notiData.contentStyle.transitionDuration = props.time + 's';
-      notiData.contentStyle.transform = 'translateX(-100%)';
-
-      notiData.time = setTimeout(() => {
-        notiData.contentStyle.transitionDuration = '0s';
-        notiData.contentStyle.transform = `translateX(${notiData.wrapWidth}px)`;
-        refAnimation(startAnimate);
-      }, 5 * 1000);
-    };
-    const stopAnimate = () => {
-      clearTimeout(notiData.time);
-      notiData.isActive = false;
-      notiData.contentStyle.transitionDuration = '0s';
-      notiData.contentStyle.transform = 'translateX(0px)';
-      notiData.time = null;
-    };
-    watch(
-      () => props.isAnimation,
-      val => {
-        if (val) {
-          refAnimation(init);
-        } else {
-          refAnimation(stopAnimate);
-        }
-      }
-    );
-    return () => (
-      <div
-        ref={wrapRef}
-        class={[styles.wrap, props.isAnimation ? styles.isAnitaion : '']}
-        onMouseenter={() => !props.isAnimation && init()}
-        onMouseleave={() => !props.isAnimation && stopAnimate()}>
-        <div
-          ref={contentRef}
-          style={notiData.contentStyle}
-          class={styles.notice}>
-          {props.text}
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, reactive, ref, watch } from 'vue';
+import styles from './index.module.less';
+
+const refAnimation = (callback: any) => {
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      callback();
+    });
+  });
+};
+
+export default defineComponent({
+  name: 'TheNoticeBar',
+  props: {
+    text: {
+      type: String,
+      default: ''
+    },
+    isAnimation: {
+      type: Boolean,
+      default: false
+    },
+    time: {
+      type: Number,
+      default: 5
+    }
+  },
+  setup(props) {
+    const wrapRef = ref();
+    const contentRef = ref();
+    const notiData = reactive({
+      isActive: false,
+      wrapWidth: 0,
+      contentWidth: 0,
+      contentStyle: {
+        transitionDuration: '0s',
+        transform: 'translateX(0px)'
+      },
+      time: null as any
+    });
+    const init = () => {
+      if (notiData.isActive || !contentRef.value || !wrapRef.value) return;
+      notiData.isActive = true;
+      notiData.contentWidth = contentRef.value.getBoundingClientRect().width;
+      notiData.wrapWidth = wrapRef.value.getBoundingClientRect().width;
+      startAnimate();
+    };
+    const startAnimate = () => {
+      if (notiData.contentWidth <= notiData.wrapWidth || !notiData.isActive) {
+        notiData.contentStyle.transitionDuration = '0s';
+        notiData.contentStyle.transform = 'translateX(0px)';
+        return;
+      }
+
+      notiData.contentStyle.transitionDuration = props.time + 's';
+      notiData.contentStyle.transform = 'translateX(-100%)';
+
+      notiData.time = setTimeout(() => {
+        notiData.contentStyle.transitionDuration = '0s';
+        notiData.contentStyle.transform = `translateX(${notiData.wrapWidth}px)`;
+        refAnimation(startAnimate);
+      }, 5 * 1000);
+    };
+    const stopAnimate = () => {
+      clearTimeout(notiData.time);
+      notiData.isActive = false;
+      notiData.contentStyle.transitionDuration = '0s';
+      notiData.contentStyle.transform = 'translateX(0px)';
+      notiData.time = null;
+    };
+    watch(
+      () => props.isAnimation,
+      val => {
+        if (val) {
+          refAnimation(init);
+        } else {
+          refAnimation(stopAnimate);
+        }
+      }
+    );
+    return () => (
+      <div
+        ref={wrapRef}
+        class={[styles.wrap, props.isAnimation ? styles.isAnitaion : '']}
+        onMouseenter={() => !props.isAnimation && init()}
+        onMouseleave={() => !props.isAnimation && stopAnimate()}>
+        <div
+          ref={contentRef}
+          style={notiData.contentStyle}
+          class={styles.notice}>
+          {props.text}
+        </div>
+      </div>
+    );
+  }
+});

+ 223 - 224
src/components/TheQrCode/index.tsx

@@ -1,224 +1,223 @@
-import { defineComponent } from 'vue';
-import { AwesomeQR } from 'vue-qr/src/lib/awesome-qr';
-import { px2vw } from '@/utils/index';
-function toBoolean(val: any): boolean {
-  if (val === '') return val;
-  return val === 'true' || val == '1';
-}
-function readAsArrayBuffer(url: any) {
-  return new Promise(resolve => {
-    const xhr = new XMLHttpRequest();
-    xhr.responseType = 'blob'; //设定返回数据类型为Blob
-    xhr.onload = function () {
-      const reader = new FileReader();
-      reader.onloadend = function () {
-        resolve(reader.result);
-      };
-      reader.readAsArrayBuffer(xhr.response); //xhr.response就是一个Blob,用FileReader读取
-    };
-    xhr.open('GET', url);
-    xhr.send();
-  });
-}
-
-export default defineComponent({
-  name: 'TheQrCode',
-  props: {
-    text: {
-      type: String,
-      required: true
-    },
-    qid: {
-      type: String
-    },
-    correctLevel: {
-      type: Number,
-      default: 0
-    },
-    size: {
-      type: Number,
-      default: 220
-    },
-    margin: {
-      type: Number,
-      default: 20
-    },
-    colorDark: {
-      type: String,
-      default: '#000000'
-    },
-    colorLight: {
-      type: String,
-      default: '#FFFFFF'
-    },
-    bgSrc: {
-      type: String,
-      default: undefined
-    },
-    background: {
-      type: String,
-      default: 'rgba(0,0,0,0)'
-    },
-    backgroundDimming: {
-      type: String,
-      default: 'rgba(0,0,0,0)'
-    },
-    logoSrc: {
-      type: String,
-      default: undefined
-    },
-    logoBackgroundColor: {
-      type: String,
-      default: 'rgba(255,255,255,1)'
-    },
-    gifBgSrc: {
-      type: String,
-      default: undefined
-    },
-    logoScale: {
-      type: Number,
-      default: 0.2
-    },
-    logoMargin: {
-      type: Number,
-      default: 0
-    },
-    logoCornerRadius: {
-      type: Number,
-      default: 8
-    },
-    whiteMargin: {
-      type: [Boolean, String],
-      default: true
-    },
-    dotScale: {
-      type: Number,
-      default: 1
-    },
-    autoColor: {
-      type: [Boolean, String],
-      default: true
-    },
-    binarize: {
-      type: [Boolean, String],
-      default: false
-    },
-    binarizeThreshold: {
-      type: Number,
-      default: 128
-    },
-    callback: {
-      type: Function,
-      default: function () {
-        return undefined;
-      }
-    },
-    bindElement: {
-      type: Boolean,
-      default: true
-    },
-    backgroundColor: {
-      type: String,
-      default: '#FFFFFF'
-    },
-    components: {
-      default: function () {
-        return {
-          data: {
-            scale: 1
-          },
-          timing: {
-            scale: 1,
-            protectors: false
-          },
-          alignment: {
-            scale: 1,
-            protectors: false
-          },
-          cornerAlignment: {
-            scale: 1,
-            protectors: true
-          }
-        };
-      }
-    }
-  },
-  data() {
-    return {
-      imgUrl: '' as any
-    };
-  },
-  watch: {
-    $props: {
-      deep: true,
-      handler() {
-        this.main();
-      }
-    }
-  },
-  mounted() {
-    this.main();
-  },
-  methods: {
-    async main() {
-      // const that = this;
-      if (this.gifBgSrc) {
-        const gifImg = await readAsArrayBuffer(this.gifBgSrc);
-        const logoImg = this.logoSrc;
-
-        this.render(undefined, logoImg, gifImg);
-        return;
-      }
-      const bgImg = this.bgSrc;
-      const logoImg = this.logoSrc;
-      this.render(bgImg, logoImg);
-    },
-    async render(img: any, logoImg: any, gifBgSrc?: any) {
-
-      const obj = {
-        gifBackground: gifBgSrc,
-        text: this.text,
-        size: this.size,
-        margin: this.margin,
-        colorDark: this.colorDark,
-        colorLight: this.colorLight,
-        backgroundColor: this.backgroundColor,
-        backgroundImage: img,
-        backgroundDimming: this.backgroundDimming,
-        logoScale: this.logoScale,
-        logoBackgroundColor: this.logoBackgroundColor,
-        correctLevel: this.correctLevel,
-        logoMargin: this.logoMargin,
-        logoCornerRadius: this.logoCornerRadius,
-        whiteMargin: toBoolean(this.whiteMargin),
-        dotScale: this.dotScale,
-        autoColor: toBoolean(this.autoColor),
-        components: this.components
-      } as any;
-      if (logoImg) {
-        obj.logoImage = logoImg + '?' + new Date().getTime();
-      }
-      new AwesomeQR(obj).draw().then((dataUri: any) => {
-        this.imgUrl = dataUri;
-        this.callback && this.callback(dataUri, this.qid);
-      });
-    }
-  },
-  render() {
-    return (
-      <>
-        {this.bindElement && this.imgUrl && (
-          <img
-            style={{
-              width: px2vw(this.size),
-              height: px2vw(this.size),
-              display: 'inline-block',
-              zIndex: 1000
-            }}
-            src={this.imgUrl}
-          />
-        )}
-      </>
-    );
-  }
-});
+import { defineComponent } from 'vue';
+import { AwesomeQR } from 'vue-qr/src/lib/awesome-qr';
+import { px2vw } from '@/utils/index';
+function toBoolean(val: any): boolean {
+  if (val === '') return val;
+  return val === 'true' || val == '1';
+}
+function readAsArrayBuffer(url: any) {
+  return new Promise(resolve => {
+    const xhr = new XMLHttpRequest();
+    xhr.responseType = 'blob'; //设定返回数据类型为Blob
+    xhr.onload = function () {
+      const reader = new FileReader();
+      reader.onloadend = function () {
+        resolve(reader.result);
+      };
+      reader.readAsArrayBuffer(xhr.response); //xhr.response就是一个Blob,用FileReader读取
+    };
+    xhr.open('GET', url);
+    xhr.send();
+  });
+}
+
+export default defineComponent({
+  name: 'TheQrCode',
+  props: {
+    text: {
+      type: String,
+      required: true
+    },
+    qid: {
+      type: String
+    },
+    correctLevel: {
+      type: Number,
+      default: 0
+    },
+    size: {
+      type: Number,
+      default: 220
+    },
+    margin: {
+      type: Number,
+      default: 20
+    },
+    colorDark: {
+      type: String,
+      default: '#000000'
+    },
+    colorLight: {
+      type: String,
+      default: '#FFFFFF'
+    },
+    bgSrc: {
+      type: String,
+      default: undefined
+    },
+    background: {
+      type: String,
+      default: 'rgba(0,0,0,0)'
+    },
+    backgroundDimming: {
+      type: String,
+      default: 'rgba(0,0,0,0)'
+    },
+    logoSrc: {
+      type: String,
+      default: undefined
+    },
+    logoBackgroundColor: {
+      type: String,
+      default: 'rgba(255,255,255,1)'
+    },
+    gifBgSrc: {
+      type: String,
+      default: undefined
+    },
+    logoScale: {
+      type: Number,
+      default: 0.2
+    },
+    logoMargin: {
+      type: Number,
+      default: 0
+    },
+    logoCornerRadius: {
+      type: Number,
+      default: 8
+    },
+    whiteMargin: {
+      type: [Boolean, String],
+      default: true
+    },
+    dotScale: {
+      type: Number,
+      default: 1
+    },
+    autoColor: {
+      type: [Boolean, String],
+      default: true
+    },
+    binarize: {
+      type: [Boolean, String],
+      default: false
+    },
+    binarizeThreshold: {
+      type: Number,
+      default: 128
+    },
+    callback: {
+      type: Function,
+      default: function () {
+        return undefined;
+      }
+    },
+    bindElement: {
+      type: Boolean,
+      default: true
+    },
+    backgroundColor: {
+      type: String,
+      default: '#FFFFFF'
+    },
+    components: {
+      default: function () {
+        return {
+          data: {
+            scale: 1
+          },
+          timing: {
+            scale: 1,
+            protectors: false
+          },
+          alignment: {
+            scale: 1,
+            protectors: false
+          },
+          cornerAlignment: {
+            scale: 1,
+            protectors: true
+          }
+        };
+      }
+    }
+  },
+  data() {
+    return {
+      imgUrl: '' as any
+    };
+  },
+  watch: {
+    $props: {
+      deep: true,
+      handler() {
+        this.main();
+      }
+    }
+  },
+  mounted() {
+    this.main();
+  },
+  methods: {
+    async main() {
+      // const that = this;
+      if (this.gifBgSrc) {
+        const gifImg = await readAsArrayBuffer(this.gifBgSrc);
+        const logoImg = this.logoSrc;
+
+        this.render(undefined, logoImg, gifImg);
+        return;
+      }
+      const bgImg = this.bgSrc;
+      const logoImg = this.logoSrc;
+      this.render(bgImg, logoImg);
+    },
+    async render(img: any, logoImg: any, gifBgSrc?: any) {
+      const obj = {
+        gifBackground: gifBgSrc,
+        text: this.text,
+        size: this.size,
+        margin: this.margin,
+        colorDark: this.colorDark,
+        colorLight: this.colorLight,
+        backgroundColor: this.backgroundColor,
+        backgroundImage: img,
+        backgroundDimming: this.backgroundDimming,
+        logoScale: this.logoScale,
+        logoBackgroundColor: this.logoBackgroundColor,
+        correctLevel: this.correctLevel,
+        logoMargin: this.logoMargin,
+        logoCornerRadius: this.logoCornerRadius,
+        whiteMargin: toBoolean(this.whiteMargin),
+        dotScale: this.dotScale,
+        autoColor: toBoolean(this.autoColor),
+        components: this.components
+      } as any;
+      if (logoImg) {
+        obj.logoImage = logoImg + '?' + new Date().getTime();
+      }
+      new AwesomeQR(obj).draw().then((dataUri: any) => {
+        this.imgUrl = dataUri;
+        this.callback && this.callback(dataUri, this.qid);
+      });
+    }
+  },
+  render() {
+    return (
+      <>
+        {this.bindElement && this.imgUrl && (
+          <img
+            style={{
+              width: px2vw(this.size),
+              height: px2vw(this.size),
+              display: 'inline-block',
+              zIndex: 1000
+            }}
+            src={this.imgUrl}
+          />
+        )}
+      </>
+    );
+  }
+});

+ 7 - 4
src/components/TheSearch/index.tsx

@@ -31,9 +31,12 @@ export default defineComponent({
       value: props.value || ''
     });
 
-    watch(() => props.value, () => {
-      searchData.value = props.value
-    })
+    watch(
+      () => props.value,
+      () => {
+        searchData.value = props.value;
+      }
+    );
     return () => (
       <NInput
         class={[
@@ -46,7 +49,7 @@ export default defineComponent({
         clearable
         v-model:value={searchData.value}
         onUpdate:value={(val: string) => {
-          emit("update:value", val)
+          emit('update:value', val);
         }}
         onClear={() => emit('search', '')}
         onKeyup={(e: KeyboardEvent) => {

+ 67 - 67
src/components/TheTipDialog/index.tsx

@@ -1,67 +1,67 @@
-import { NButton, NScrollbar, NModal } from 'naive-ui';
-import { defineComponent } from 'vue';
-import styles from './index.module.less';
-
-export default defineComponent({
-  name: 'the-tip-dialog',
-  props: {
-    show: Boolean,
-    title: {
-      type: String,
-      default: '提示'
-    },
-    cancelButtonText: {
-      type: String,
-      default: '取消'
-    },
-    confirmButtonText: {
-      type: String,
-      default: '确定'
-    },
-    cancelBtn: {
-      type: Boolean,
-      default: true
-    },
-    confirmBtn: {
-      type: Boolean,
-      default: true
-    },
-    content: {
-      type: String,
-      default: ''
-    }
-  },
-  emits: ['close', 'confirm'],
-  setup(props, { emit }) {
-    return () => (
-      <NModal
-        class={['modalTitle', styles.theTipDialog]}
-        on-close={() => {
-          emit('close');
-        }}
-        show={props.show}
-        preset="card"
-        title={props.title}>
-        <div class={styles.tipCon}>
-          <div class={styles.tipBox}>
-            <NScrollbar>
-              <div class={styles.tip} v-html={props.content}></div>
-            </NScrollbar>
-          </div>
-          <div class={styles.tipBtnBox}>
-            {props.cancelBtn && (
-              <NButton round onClick={() => emit('close')}>
-                {props.cancelButtonText}
-              </NButton>
-            )}
-            {props.confirmBtn && (
-              <NButton type="primary" round onClick={() => emit('confirm')}>
-                {props.confirmButtonText}
-              </NButton>
-            )}
-          </div>
-        </div>
-      </NModal>
-    );
-  }
-});
+import { NButton, NScrollbar, NModal } from 'naive-ui';
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'the-tip-dialog',
+  props: {
+    show: Boolean,
+    title: {
+      type: String,
+      default: '提示'
+    },
+    cancelButtonText: {
+      type: String,
+      default: '取消'
+    },
+    confirmButtonText: {
+      type: String,
+      default: '确定'
+    },
+    cancelBtn: {
+      type: Boolean,
+      default: true
+    },
+    confirmBtn: {
+      type: Boolean,
+      default: true
+    },
+    content: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    return () => (
+      <NModal
+        class={['modalTitle', styles.theTipDialog]}
+        on-close={() => {
+          emit('close');
+        }}
+        show={props.show}
+        preset="card"
+        title={props.title}>
+        <div class={styles.tipCon}>
+          <div class={styles.tipBox}>
+            <NScrollbar>
+              <div class={styles.tip} v-html={props.content}></div>
+            </NScrollbar>
+          </div>
+          <div class={styles.tipBtnBox}>
+            {props.cancelBtn && (
+              <NButton round onClick={() => emit('close')}>
+                {props.cancelButtonText}
+              </NButton>
+            )}
+            {props.confirmBtn && (
+              <NButton type="primary" round onClick={() => emit('confirm')}>
+                {props.confirmButtonText}
+              </NButton>
+            )}
+          </div>
+        </div>
+      </NModal>
+    );
+  }
+});

+ 3 - 6
src/components/VipPurchaseModal/index.tsx

@@ -311,8 +311,7 @@ export default defineComponent({
                       styles.packageItem,
                       selectedPackageId.value === pkg.id ? styles.selected : ''
                     ]}
-                    onClick={() => (selectedPackageId.value = pkg.id)}
-                  >
+                    onClick={() => (selectedPackageId.value = pkg.id)}>
                     <div class={styles.packageName}>{pkg.name}</div>
                     <div class={styles.freeText}>{pkg.freeText || ''}</div>
                     <div class={styles.packagePrice}>
@@ -332,8 +331,7 @@ export default defineComponent({
                   type="primary"
                   loading={loading.value}
                   disabled={loading.value || hasClicked.value}
-                  onClick={handlePurchase}
-                >
+                  onClick={handlePurchase}>
                   续费 ¥{currentPackage.value?.price}
                 </NButton>
               </div>
@@ -376,8 +374,7 @@ export default defineComponent({
                       size="large"
                       type="primary"
                       loading={loading.value}
-                      onClick={handleRetry}
-                    >
+                      onClick={handleRetry}>
                       重新支付
                     </NButton>
                   ]

+ 3 - 1
src/components/card-preview/music-modal/index.tsx

@@ -23,7 +23,9 @@ export default defineComponent({
     const isLoaded = ref(false);
     let src = `${vaildMusicScoreUrl()}/instrument?v=${+new Date()}&modelType=practise&id=${
       props.item.content
-    }&Authorization=${userStore.getToken}&platform=pc&zoom=0.8&showWebGuide=false`;
+    }&Authorization=${
+      userStore.getToken
+    }&platform=pc&zoom=0.8&showWebGuide=false`;
     if (props.item.instrumentId) {
       src += '&instrumentId=' + props.item.instrumentId;
     }

+ 112 - 112
src/components/card-type/audio-player/index.tsx

@@ -1,112 +1,112 @@
-import { defineComponent, reactive } from 'vue';
-import styles from './index.module.less';
-import { NImage } from 'naive-ui';
-// import Vudio from 'vudio.js';
-
-export default defineComponent({
-  name: 'audio-player',
-  props: {
-    cover: {
-      type: String,
-      default: 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png'
-    },
-    content: {
-      type: String,
-      default: ''
-    },
-    previewDisabled: {
-      type: Boolean,
-      default: true
-    }
-  },
-  setup(props) {
-    // const canvasEle: any = ref();
-    // const audioEle: any = ref();
-
-    // const vudio: any = ref();
-    const audioForm = reactive({
-      status: false
-    });
-
-    // 鼠标移上时
-    // const onMouseover = (e: MouseEvent) => {
-    //   console.log(e, 'onMouseover');
-    //   audioForm.status = true;
-
-    //   nextTick(() => {
-    //     // onInit(audioEle.value, canvasEle.value);
-
-    //     console.log(audioEle.value, 'onMouseout');
-
-    //     setTimeout(() => {
-    //       // console.log(audioEle.value, 'value');
-    //       // audioEle.value.pause();
-    //       // audioEle.value.load();
-    //       // audioEle.value.currentTime = 0;
-    //       // audioEle.value.muted = true;
-    //       // audioEle.value.play();
-
-    //     }, 10);
-    //   });
-    // };
-
-    // // 鼠标移开时
-    // const onMouseout = (e: MouseEvent) => {
-    //   console.log(e, 'mouseover');
-    //   audioForm.status = false;
-    // };
-
-    // const onInit = (audio: undefined, canvas: undefined) => {
-    //   if (!vudio.value) {
-    //     vudio.value = new Vudio(audio, canvas, {
-    //       effect: 'waveform',
-    //       accuracy: 256,
-    //       width: 1024,
-    //       height: 600,
-    //       waveform: {
-    //         maxHeight: 200,
-    //         color: [
-    //           [0, '#44D1FF'],
-    //           [0.5, '#44D1FF'],
-    //           [0.5, '#198CFE'],
-    //           [1, '#198CFE']
-    //         ],
-    //         prettify: false
-    //       }
-    //     });
-    //     vudio.value.dance();
-    //   }
-    // };
-    return () => (
-      <div
-        class={[
-          styles.audioContainer,
-          audioForm.status ? styles.imgAnimated : ''
-        ]}>
-        <NImage
-          class={[styles.cover, audioForm.status ? styles.imgHover : '']}
-          lazy
-          previewDisabled={true}
-          objectFit="cover"
-          src={props.cover}
-        />
-
-        {/* <div
-          class={[
-            styles.previewAudio,
-            audioForm.status ? styles.previewAudioHover : ''
-          ]}>
-          <audio
-            controls="true"
-            muted="true"
-            autoplay
-            preload="auto"
-            ref={(el: any) => (audioEle.value = el)}
-            crossorigin="anonymous"
-            src={props.content + '?time=1'}></audio>
-          <canvas ref={(el: any) => (canvasEle.value = el)}></canvas>
-        </div> */}
-      </div>
-    );
-  }
-});
+import { defineComponent, reactive } from 'vue';
+import styles from './index.module.less';
+import { NImage } from 'naive-ui';
+// import Vudio from 'vudio.js';
+
+export default defineComponent({
+  name: 'audio-player',
+  props: {
+    cover: {
+      type: String,
+      default: 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png'
+    },
+    content: {
+      type: String,
+      default: ''
+    },
+    previewDisabled: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props) {
+    // const canvasEle: any = ref();
+    // const audioEle: any = ref();
+
+    // const vudio: any = ref();
+    const audioForm = reactive({
+      status: false
+    });
+
+    // 鼠标移上时
+    // const onMouseover = (e: MouseEvent) => {
+    //   console.log(e, 'onMouseover');
+    //   audioForm.status = true;
+
+    //   nextTick(() => {
+    //     // onInit(audioEle.value, canvasEle.value);
+
+    //     console.log(audioEle.value, 'onMouseout');
+
+    //     setTimeout(() => {
+    //       // console.log(audioEle.value, 'value');
+    //       // audioEle.value.pause();
+    //       // audioEle.value.load();
+    //       // audioEle.value.currentTime = 0;
+    //       // audioEle.value.muted = true;
+    //       // audioEle.value.play();
+
+    //     }, 10);
+    //   });
+    // };
+
+    // // 鼠标移开时
+    // const onMouseout = (e: MouseEvent) => {
+    //   console.log(e, 'mouseover');
+    //   audioForm.status = false;
+    // };
+
+    // const onInit = (audio: undefined, canvas: undefined) => {
+    //   if (!vudio.value) {
+    //     vudio.value = new Vudio(audio, canvas, {
+    //       effect: 'waveform',
+    //       accuracy: 256,
+    //       width: 1024,
+    //       height: 600,
+    //       waveform: {
+    //         maxHeight: 200,
+    //         color: [
+    //           [0, '#44D1FF'],
+    //           [0.5, '#44D1FF'],
+    //           [0.5, '#198CFE'],
+    //           [1, '#198CFE']
+    //         ],
+    //         prettify: false
+    //       }
+    //     });
+    //     vudio.value.dance();
+    //   }
+    // };
+    return () => (
+      <div
+        class={[
+          styles.audioContainer,
+          audioForm.status ? styles.imgAnimated : ''
+        ]}>
+        <NImage
+          class={[styles.cover, audioForm.status ? styles.imgHover : '']}
+          lazy
+          previewDisabled={true}
+          objectFit="cover"
+          src={props.cover}
+        />
+
+        {/* <div
+          class={[
+            styles.previewAudio,
+            audioForm.status ? styles.previewAudioHover : ''
+          ]}>
+          <audio
+            controls="true"
+            muted="true"
+            autoplay
+            preload="auto"
+            ref={(el: any) => (audioEle.value = el)}
+            crossorigin="anonymous"
+            src={props.content + '?time=1'}></audio>
+          <canvas ref={(el: any) => (canvasEle.value = el)}></canvas>
+        </div> */}
+      </div>
+    );
+  }
+});

+ 57 - 57
src/components/card-type/video-player/index.tsx

@@ -1,57 +1,57 @@
-import { defineComponent, reactive } from 'vue';
-import styles from './index.module.less';
-// import 'plyr/dist/plyr.css';
-// import Plyr from 'plyr';
-import { NImage } from 'naive-ui';
-
-export default defineComponent({
-  name: 'audio-player',
-  props: {
-    cover: {
-      type: String,
-      default: ''
-    },
-    content: {
-      type: String,
-      default: ''
-    },
-    previewDisabled: {
-      type: Boolean,
-      default: true
-    }
-  },
-  setup(props) {
-    const audioForm = reactive({
-      status: false
-    });
-
-    return () => (
-      <div
-        class={[
-          styles.audioContainer,
-          audioForm.status ? styles.imgAnimated : ''
-        ]}>
-        <NImage
-          class={[styles.cover, audioForm.status ? styles.imgHover : '']}
-          lazy
-          previewDisabled={true}
-          objectFit="cover"
-          src={props.cover}
-        />
-
-        {/* <div
-          class={[
-            styles.previewAudio,
-            audioForm.status ? styles.previewAudioHover : ''
-          ]}>
-          <video
-            style={{ width: '100%', height: '100%' }}
-            src={props.content}
-            ref={videoRef}
-            muted
-            playsinline="false"></video>
-        </div> */}
-      </div>
-    );
-  }
-});
+import { defineComponent, reactive } from 'vue';
+import styles from './index.module.less';
+// import 'plyr/dist/plyr.css';
+// import Plyr from 'plyr';
+import { NImage } from 'naive-ui';
+
+export default defineComponent({
+  name: 'audio-player',
+  props: {
+    cover: {
+      type: String,
+      default: ''
+    },
+    content: {
+      type: String,
+      default: ''
+    },
+    previewDisabled: {
+      type: Boolean,
+      default: true
+    }
+  },
+  setup(props) {
+    const audioForm = reactive({
+      status: false
+    });
+
+    return () => (
+      <div
+        class={[
+          styles.audioContainer,
+          audioForm.status ? styles.imgAnimated : ''
+        ]}>
+        <NImage
+          class={[styles.cover, audioForm.status ? styles.imgHover : '']}
+          lazy
+          previewDisabled={true}
+          objectFit="cover"
+          src={props.cover}
+        />
+
+        {/* <div
+          class={[
+            styles.previewAudio,
+            audioForm.status ? styles.previewAudioHover : ''
+          ]}>
+          <video
+            style={{ width: '100%', height: '100%' }}
+            src={props.content}
+            ref={videoRef}
+            muted
+            playsinline="false"></video>
+        </div> */}
+      </div>
+    );
+  }
+});

+ 2 - 2
src/components/layout/guide-section/driver.ts

@@ -56,11 +56,11 @@ export const GuideDriver = defineComponent({
               options.config.stagePadding = 0;
             },
             onCloseClick: () => {
-              onDriverClose()
+              onDriverClose();
             },
             onNextClick: () => {
               onDriverClose();
-            },
+            }
           }
         }
       ]

+ 30 - 52
src/components/layout/layoutTop.tsx

@@ -197,8 +197,7 @@ export default defineComponent({
           <NImage
             src={schoolIcon}
             class={styles.schoolIcon}
-            previewDisabled
-          ></NImage>
+            previewDisabled></NImage>
           <p>
             {(info.value?.schoolInfos && info.value?.schoolInfos[0].name) || ''}
           </p>
@@ -222,8 +221,7 @@ export default defineComponent({
                     // eventGlobal.emit('teacher-guideInfo', route.name);
 
                     guideSectionRef.value?.onToggle();
-                  }}
-                >
+                  }}>
                   <NImage src={gnydIcon} previewDisabled></NImage>
                 </div>
               ),
@@ -262,8 +260,7 @@ export default defineComponent({
                   }}
                 </NTooltip>
               )
-            }}
-          >
+            }}>
             <ClassModal
               onConfirm={() => {
                 classRecordStatus.value = false;
@@ -296,16 +293,14 @@ export default defineComponent({
                       noReadCount.value > 0 ? '' : styles.messageBadgeNo
                     ]}
                     {...{ id: 'home-3' }}
-                    color={'#FF1036'}
-                  >
+                    color={'#FF1036'}>
                     <NImage
                       class={[
                         styles.messageIcon,
                         noReadCount.value > 0 ? styles.animation : ''
                       ]}
                       preview-disabled
-                      src={messageIcon}
-                    ></NImage>
+                      src={messageIcon}></NImage>
                   </NBadge>
                 ),
                 default: '聊天'
@@ -330,31 +325,28 @@ export default defineComponent({
                   <NImage
                     preview-disabled
                     class={styles.teacherIcon}
-                    src={info.value.avatar ? info.value.avatar : teacherIcon}
-                  ></NImage>
+                    src={
+                      info.value.avatar ? info.value.avatar : teacherIcon
+                    }></NImage>
                   <NIcon
                     class={
                       showHeadFlag.value ? styles.rotueLeft : styles.rotueRight
-                    }
-                  >
+                    }>
                     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                       <path
                         d="M7.38 21.01c.49.49 1.28.49 1.77 0l8.31-8.31a.996.996 0 0 0 0-1.41L9.15 2.98c-.49-.49-1.28-.49-1.77 0s-.49 1.28 0 1.77L14.62 12l-7.25 7.25c-.48.48-.48 1.28.01 1.76z"
-                        fill="currentColor"
-                      ></path>
+                        fill="currentColor"></path>
                     </svg>
                   </NIcon>
                 </div>
               )
-            }}
-          >
+            }}>
             <div class={styles.propWrap}>
               <div class={styles.teacherInfo}>
                 <NImage
                   class={styles.teacherIcon}
                   src={info.value.avatar ? info.value.avatar : teacherIcon}
-                  previewDisabled
-                ></NImage>
+                  previewDisabled></NImage>
                 <div class={styles.userInfos}>
                   <div class={styles.nameWrap}>
                     <NTooltip class={styles.nameTool}>
@@ -384,13 +376,11 @@ export default defineComponent({
               <div class={styles.propWrapList}>
                 <div
                   class={styles.propWrapItem}
-                  onClick={() => oncheckEditStatus(gotoPerson)}
-                >
+                  onClick={() => oncheckEditStatus(gotoPerson)}>
                   <NImage
                     class={styles.smallIcon}
                     src={personIcon}
-                    previewDisabled
-                  ></NImage>
+                    previewDisabled></NImage>
                   <p class={styles.smallTitle}>个人信息</p>
                 </div>
                 {info.value.isSuperAdmin ||
@@ -399,13 +389,11 @@ export default defineComponent({
                     class={styles.propWrapItem}
                     onClick={() => {
                       oncheckEditStatus(gotoSchool);
-                    }}
-                  >
+                    }}>
                     <NImage
                       class={styles.smallIcon}
                       src={schoolDot}
-                      previewDisabled
-                    ></NImage>
+                      previewDisabled></NImage>
                     <p class={styles.smallTitle}>学校信息</p>
                   </div>
                 ) : null}
@@ -414,37 +402,31 @@ export default defineComponent({
                   <NImage
                     class={styles.smallIcon}
                     src={clockIcon}
-                    previewDisabled
-                  ></NImage>
+                    previewDisabled></NImage>
                   <p class={styles.smallTitle}>修改密码</p>
                 </div>
 
                 <div
                   class={styles.propWrapItem}
-                  onClick={() => oncheckEditStatus(aboutUs)}
-                >
+                  onClick={() => oncheckEditStatus(aboutUs)}>
                   <NImage
                     class={styles.smallIcon}
                     src={iconAboutus}
-                    previewDisabled
-                  ></NImage>
+                    previewDisabled></NImage>
                   <p class={styles.smallTitle}>关于我们</p>
                 </div>
                 <div
                   class={[styles.propWrapItem, styles.vipHighlight]}
-                  onClick={() => onOpenVip()}
-                >
+                  onClick={() => onOpenVip()}>
                   <svg
                     class={styles.smallIcon}
                     style={{ marginTop: '4px' }}
                     xmlns="http://www.w3.org/2000/svg"
                     xmlns:xlink="http://www.w3.org/1999/xlink"
-                    viewBox="0 0 24 24"
-                  >
+                    viewBox="0 0 24 24">
                     <path
                       d="M18.5 11.5c.92 0 1.75.26 2.49.69V5c0-1.1-.89-2-1.99-2H5c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h8.55c-.02-.17-.05-.33-.05-.5c0-2.76 2.24-5 5-5zm-5.61-1.45c-.56.28-1.23.28-1.79 0l-5.61-2.8a.893.893 0 0 1-.49-.8c0-.66.7-1.1 1.29-.8L12 8.5l5.71-2.85a.89.89 0 0 1 1.29.8c0 .34-.19.65-.49.8l-5.62 2.8zM18.5 13c-1.93 0-3.5 1.57-3.5 3.5s1.57 3.5 3.5 3.5s3.5-1.57 3.5-3.5s-1.57-3.5-3.5-3.5zm2 3.5c0 .28-.22.5-.5.5h-3c-.28 0-.5-.22-.5-.5s.22-.5.5-.5h3c.28 0 .5.22.5.5z"
-                      fill="currentColor"
-                    ></path>
+                      fill="currentColor"></path>
                   </svg>
                   <p class={styles.smallTitle}>会员续费</p>
                 </div>
@@ -457,14 +439,12 @@ export default defineComponent({
                   // .then(() => {
                   //   window.location.reload();
                   // });
-                }}
-              >
+                }}>
                 <div class={styles.propWrapItem}>
                   <NImage
                     class={styles.smallIcon}
                     src={closeIcon}
-                    previewDisabled
-                  ></NImage>
+                    previewDisabled></NImage>
                   <p class={styles.smallTitle}>退出登录</p>
                 </div>
               </div>
@@ -478,8 +458,7 @@ export default defineComponent({
           v-model:show={showWord.value}
           preset="dialog"
           showIcon={false}
-          title="修改密码"
-        >
+          title="修改密码">
           <ForgotPassword
             phone={info.value.phone}
             onClose={() => {
@@ -494,8 +473,7 @@ export default defineComponent({
           showIcon={false}
           class={showImGroupLoading.value ? styles.hideModal : ''}
           {...{ id: 'imGroupDiv' }}
-          displayDirective="show"
-        >
+          displayDirective="show">
           <ImGroup />
         </NModal>
 
@@ -504,12 +482,12 @@ export default defineComponent({
           class={['modalTitle', 'background', styles.suggestWrap]}
           v-model:show={showSuggestionViseble.value}
           display-directive="show"
-          showIcon={false}
-        >
+          showIcon={false}>
           <SuggestionOption
             ref={suggestionOptionRef}
-            onClose={() => (showSuggestionViseble.value = false)}
-          ></SuggestionOption>
+            onClose={() =>
+              (showSuggestionViseble.value = false)
+            }></SuggestionOption>
         </NModal>
 
         {/* 操作手册 */}

+ 59 - 59
src/components/layout/modals/api.ts

@@ -1,59 +1,59 @@
-import request from '@/utils/request';
-/**
- * 新增意见
- */
-export const addSuggestion = (params: any) => {
-  return request.post('/edu-app/sysSuggestion/save', {
-    data: params
-    // requestType: 'form'
-  });
-};
-
-/**
- * 获取意见类型
- */
-
-export const getSuggestionList = (params: any) => {
-  return request.post('/edu-app/sysSuggestionType/page', {
-    data: params
-    // requestType: 'form'
-  });
-};
-
-/**
- * 获取系统参数
- */
-export const sysParamConfigPage = (params: any) => {
-  return request.post('/edu-app/sysParamConfig/page', {
-    data: params
-    // requestType: 'form'
-  });
-};
-
-/**
- * @description: 意见反馈类型
- */
-export const getSysSuggestionTypeList = (params: object) => {
-  return request.post('/edu-app/sysSuggestionType/page', {
-    data: params
-  });
-};
-
-/**
- * @description: 意见反馈列表
- */
-export const getSysSuggestionList = (params: object) => {
-  return request.post('/edu-app/sysSuggestion/page', {
-    data: params
-  });
-};
-
-/**
- * @description: 意见反馈意见已读
- */
-export const batchSetRead = (params: object) => {
-  return request.get('/edu-app/sysMessage/batchSetRead', {
-    data: params,
-    params
-  });
-};
+import request from '@/utils/request';
+/**
+ * 新增意见
+ */
+export const addSuggestion = (params: any) => {
+  return request.post('/edu-app/sysSuggestion/save', {
+    data: params
+    // requestType: 'form'
+  });
+};
+
+/**
+ * 获取意见类型
+ */
+
+export const getSuggestionList = (params: any) => {
+  return request.post('/edu-app/sysSuggestionType/page', {
+    data: params
+    // requestType: 'form'
+  });
+};
+
+/**
+ * 获取系统参数
+ */
+export const sysParamConfigPage = (params: any) => {
+  return request.post('/edu-app/sysParamConfig/page', {
+    data: params
+    // requestType: 'form'
+  });
+};
+
+/**
+ * @description: 意见反馈类型
+ */
+export const getSysSuggestionTypeList = (params: object) => {
+  return request.post('/edu-app/sysSuggestionType/page', {
+    data: params
+  });
+};
+
+/**
+ * @description: 意见反馈列表
+ */
+export const getSysSuggestionList = (params: object) => {
+  return request.post('/edu-app/sysSuggestion/page', {
+    data: params
+  });
+};
+
+/**
+ * @description: 意见反馈意见已读
+ */
+export const batchSetRead = (params: object) => {
+  return request.get('/edu-app/sysMessage/batchSetRead', {
+    data: params,
+    params
+  });
+};

+ 42 - 42
src/components/layout/modals/placeholderTone.tsx

@@ -1,42 +1,42 @@
-import { defineComponent, ref, watch } from 'vue';
-import styles from './holder.module.less';
-import { NButton, NImage, NSpace } from 'naive-ui';
-import radiusIcon from '../images/radiusIcon.png';
-import moveTop from '@/views/login/images/moveTopBg.png';
-import dingPng from '@/views/login/images/ding.png';
-import closeAble from '@/views/login/images/closeAble.png';
-export default defineComponent({
-  props: ['item', 'message'],
-  emits: ['close'],
-  name: 'placeholderTone',
-  setup(props, { emit }) {
-    return () => (
-      <>
-        <div class={styles.downMove}>
-          <img src={dingPng} class={styles.dingPng} alt="" />
-          <img src={moveTop} class={styles.downMoveBg} alt="" />
-          {/* <img src={closeAble} class={styles.closeAble} onClick={() => {
-            emit('close')
-          }} alt="" /> */}
-          <h2>温馨提示</h2>
-          <p>{props.message || '调音器功能暂未开放,敬请期待!'}</p>
-          {/* <NButton>确定</NButton> */}
-          <NSpace style={{ padding: '25px 0 0 0' }} justify="center">
-            <NButton
-              {...{
-                id: 'submitBtn'
-              }}
-              class={styles.submitAppBtn}
-              round
-              type="primary"
-              onClick={() => {
-                emit('close');
-              }}>
-              我知道了
-            </NButton>
-          </NSpace>
-        </div>
-      </>
-    );
-  }
-});
+import { defineComponent, ref, watch } from 'vue';
+import styles from './holder.module.less';
+import { NButton, NImage, NSpace } from 'naive-ui';
+import radiusIcon from '../images/radiusIcon.png';
+import moveTop from '@/views/login/images/moveTopBg.png';
+import dingPng from '@/views/login/images/ding.png';
+import closeAble from '@/views/login/images/closeAble.png';
+export default defineComponent({
+  props: ['item', 'message'],
+  emits: ['close'],
+  name: 'placeholderTone',
+  setup(props, { emit }) {
+    return () => (
+      <>
+        <div class={styles.downMove}>
+          <img src={dingPng} class={styles.dingPng} alt="" />
+          <img src={moveTop} class={styles.downMoveBg} alt="" />
+          {/* <img src={closeAble} class={styles.closeAble} onClick={() => {
+            emit('close')
+          }} alt="" /> */}
+          <h2>温馨提示</h2>
+          <p>{props.message || '调音器功能暂未开放,敬请期待!'}</p>
+          {/* <NButton>确定</NButton> */}
+          <NSpace style={{ padding: '25px 0 0 0' }} justify="center">
+            <NButton
+              {...{
+                id: 'submitBtn'
+              }}
+              class={styles.submitAppBtn}
+              round
+              type="primary"
+              onClick={() => {
+                emit('close');
+              }}>
+              我知道了
+            </NButton>
+          </NSpace>
+        </div>
+      </>
+    );
+  }
+});

+ 3 - 3
src/components/layout/modals/silderItem.tsx

@@ -2,7 +2,7 @@ import { defineComponent, ref, watch } from 'vue';
 import styles from '../index.module.less';
 import { NImage } from 'naive-ui';
 import radiusIcon from '../images/radiusIcon.png';
-import radiusIcon1 from '../images/radiusIcon-1.png'
+import radiusIcon1 from '../images/radiusIcon-1.png';
 export default defineComponent({
   emits: ['checkNavBar'],
   props: ['item'],
@@ -40,8 +40,8 @@ export default defineComponent({
         <p>{myItem.value.name}</p>
         {myItem.value.isActive ? (
           <img src={radiusIcon} class={styles.radiusIcon} />
-          // <img src={radiusIcon1} class={styles.radiusIcon1} />
-        ) : null}
+        ) : // <img src={radiusIcon1} class={styles.radiusIcon1} />
+        null}
       </div>
     );
   }

+ 0 - 1
src/components/layout/modals/suggestion-list.tsx

@@ -250,4 +250,3 @@ export default defineComponent({
     );
   }
 });
-

+ 28 - 28
src/components/searchInput/index.tsx

@@ -1,28 +1,28 @@
-import { defineComponent } from 'vue';
-import styles from './index.module.less';
-import { NInput } from 'naive-ui';
-
-export default defineComponent({
-  name: 'student-studentList',
-  props: ['searchWord'],
-  emits: ['changeValue', 'keyup', 'clear'],
-  setup(props, { emit, attrs }) {
-    return () => (
-      <div>
-        <NInput
-          {...attrs}
-          clearable
-          class={styles.searchInput}
-          v-slots={{
-            prefix: () => <span class={'icon-search-input'}></span>
-          }}
-          value={props.searchWord}
-          onInput={(str: string) => {
-            emit('changeValue', str);
-          }}
-          onClear={() => emit('clear')}
-          onKeyup={(e: KeyboardEvent) => emit('keyup', e)}></NInput>
-      </div>
-    );
-  }
-});
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import { NInput } from 'naive-ui';
+
+export default defineComponent({
+  name: 'student-studentList',
+  props: ['searchWord'],
+  emits: ['changeValue', 'keyup', 'clear'],
+  setup(props, { emit, attrs }) {
+    return () => (
+      <div>
+        <NInput
+          {...attrs}
+          clearable
+          class={styles.searchInput}
+          v-slots={{
+            prefix: () => <span class={'icon-search-input'}></span>
+          }}
+          value={props.searchWord}
+          onInput={(str: string) => {
+            emit('changeValue', str);
+          }}
+          onClear={() => emit('clear')}
+          onKeyup={(e: KeyboardEvent) => emit('keyup', e)}></NInput>
+      </div>
+    );
+  }
+});

+ 479 - 479
src/components/timerMeter/TimerMeter.vue

@@ -1,479 +1,479 @@
-<template>
-  <div class="timerMeter">
-    <div class="timeCon">
-      <div class="timeBox">
-        <div class="timeInput mmTimeIpt">
-          <div
-            class="timeInputBtn"
-            v-if="timeType === 'countdown'"
-            :class="{ palyDisabled: playState === 'play' }"
-          >
-            <div @click="handleTimeNum(600)">
-              <img src="./img/upBtn.png" />
-            </div>
-            <div @click="handleTimeNum(60)">
-              <img src="./img/upBtn.png" />
-            </div>
-          </div>
-          <div class="timeInputBox">
-            <n-input-number
-              :disabled="true"
-              placeholder=""
-              :value="mmValue"
-              :format="(value: number | null)=>{
-                return value?(value<10?`0${value}`:value+''):'00'
-              }"
-              @update:value="(num:number)=>{
-                if(num){
-                  if(num<0){
-                    num=0
-                  }else if(num>59){
-                    num=59
-                  }
-                  timeNum=num * 60 + ssValue
-                }
-              }"
-              :show-button="false"
-            />
-          </div>
-          <div
-            class="timeInputBtn"
-            v-if="timeType === 'countdown'"
-            :class="{ palyDisabled: playState === 'play' }"
-          >
-            <div @click="handleTimeNum(-600)">
-              <img src="./img/downBtn.png" />
-            </div>
-            <div @click="handleTimeNum(-60)">
-              <img src="./img/downBtn.png" />
-            </div>
-          </div>
-        </div>
-        <img class="midBg" src="./img/midBg.png" />
-        <div class="timeInput ssTimeIpt">
-          <div
-            class="timeInputBtn"
-            v-if="timeType === 'countdown'"
-            :class="{ palyDisabled: playState === 'play' }"
-          >
-            <div @click="handleTimeNum(10)">
-              <img src="./img/upBtn.png" />
-            </div>
-            <div @click="handleTimeNum(1)">
-              <img src="./img/upBtn.png" />
-            </div>
-          </div>
-          <div class="timeInputBox">
-            <n-input-number
-              :disabled="true"
-              placeholder=""
-              :value="ssValue"
-              :format="(value: number | null)=>{
-                return value?(value<10?`0${value}`:value+''):'00'
-              }"
-              @update:value="(num:number)=>{
-                if(num){
-                  if(num<0){
-                    num=0
-                  }else if(num>59){
-                    num=59
-                  }
-                  timeNum=mmValue * 60 + num
-                }
-              }"
-              :show-button="false"
-            />
-          </div>
-          <div
-            class="timeInputBtn"
-            v-if="timeType === 'countdown'"
-            :class="{ palyDisabled: playState === 'play' }"
-          >
-            <div @click="handleTimeNum(-10)">
-              <img src="./img/downBtn.png" />
-            </div>
-            <div @click="handleTimeNum(-1)">
-              <img src="./img/downBtn.png" />
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="timeTools">
-        <div class="timeType">
-          <div class="timeTypeBox">
-            <div
-              class="timeTypeName"
-              :class="{ timeTypeActive: timeType === 'countdown' }"
-              @click="handleChangeTimeType('countdown')"
-            >
-              倒计时
-            </div>
-            <div
-              class="timeTypeName"
-              :class="{ timeTypeActive: timeType === 'countup' }"
-              @click="handleChangeTimeType('countup')"
-            >
-              正计时
-            </div>
-          </div>
-        </div>
-        <div class="playState">
-          <img
-            v-if="playState === 'play'"
-            @click="handlePause"
-            src="./img/play.png"
-          />
-          <img v-else @click="handlePlay" src="./img/pause.png" />
-        </div>
-        <div class="resetting">
-          <img @click="handleInitTime" src="./img/resetting.png" />
-        </div>
-      </div>
-    </div>
-    <div class="timeBomCon">
-      <div
-        v-if="timeType === 'countdown'"
-        class="timeDownBom"
-        :class="{ palyDisabled: playState === 'play' }"
-      >
-        <div @click="handleTimeNum(300)">5分</div>
-        <div @click="handleTimeNum(60)">1分</div>
-        <div @click="handleTimeNum(30)">30秒</div>
-        <div @click="handleTimeNum(5)">5秒</div>
-      </div>
-      <img v-else class="timeUpBom" src="./img/timeUpBom.png" />
-      <Dragbom />
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, watch, onUnmounted, onMounted } from 'vue';
-import { NInputNumber } from 'naive-ui';
-import soundWav from './timer.mp3';
-import Dragbom from '@/hooks/useDrag/dragbom';
-
-const soundVIdeo = new Audio(soundWav);
-
-onMounted(() => {
-  /*  n-input-number 有bug  input-props设置maxlength不上去 */
-  const iptDoms = document.querySelectorAll(
-    '.timerMeter .timeInput .timeInputBox .n-input__input-el'
-  );
-  iptDoms.forEach(item => {
-    item.setAttribute('maxlength', '2');
-  });
-});
-onUnmounted(() => {
-  clearInterval(_time);
-  soundVIdeo.pause();
-});
-
-// 计时类型
-const timeType = ref<'countdown' | 'countup'>('countdown');
-function handleChangeTimeType(type: 'countdown' | 'countup') {
-  if (timeType.value === type) {
-    return;
-  }
-  timeType.value = type;
-  handleInitTime();
-}
-// 清空状态
-function handleInitTime() {
-  handlePause();
-  timeNum.value = 0;
-  mmValue.value = 0;
-  ssValue.value = 0;
-}
-// 播放状态
-const playState = ref<'pause' | 'play'>('pause');
-//统计时间
-const timeNum = ref(0);
-const mmValue = ref(0);
-const ssValue = ref(0);
-watch(timeNum, () => {
-  mmValue.value = Math.floor(timeNum.value / 60);
-  ssValue.value = timeNum.value % 60;
-});
-function handleTimeNum(num: number) {
-  if (playState.value === 'play') return;
-  let timeNumNow = timeNum.value + num;
-  if (timeNumNow >= 3599) {
-    timeNumNow = 3599;
-  }
-  if (timeNumNow <= 0) {
-    timeNumNow = 0;
-  }
-  timeNum.value = timeNumNow;
-}
-// 开始
-function handlePlay() {
-  if (timeType.value === 'countdown') {
-    if (timeNum.value <= 0) {
-      return;
-    }
-    playState.value = 'play';
-    handleCountdownStart();
-  } else {
-    if (timeNum.value >= 3599) {
-      return;
-    }
-    playState.value = 'play';
-    handleCountUpStart();
-  }
-}
-// 暂停
-function handlePause() {
-  clearInterval(_time);
-  playState.value = 'pause';
-  soundVIdeo.pause();
-}
-
-let _time: NodeJS.Timer;
-// 倒计时
-function handleCountdownStart() {
-  // 由于时间结束之后还会有尾音,所以开始时候可能音乐在播放,所以这里先暂停
-  soundVIdeo.pause();
-  soundVIdeo.currentTime = 0;
-  if (timeNum.value <= 3) {
-    soundVIdeo.currentTime = 3 - timeNum.value;
-    soundVIdeo.play();
-  }
-  _time = setInterval(() => {
-    timeNum.value--;
-    if (timeNum.value === 3) {
-      soundVIdeo.play();
-    }
-    if (timeNum.value <= 0) {
-      // 结束的时候音乐不暂停
-      clearInterval(_time);
-      playState.value = 'pause';
-    }
-  }, 1000);
-}
-// 正计时
-function handleCountUpStart() {
-  _time = setInterval(() => {
-    timeNum.value++;
-    if (timeNum.value >= 3599) {
-      handlePause();
-    }
-  }, 1000);
-}
-</script>
-
-<style lang="less" scoped>
-.timerMeter {
-  width: 620px;
-  height: 316px;
-  background: url('./img/bg.png') no-repeat;
-  background-size: 100% 100%;
-  padding: 46px 25px 0 24px;
-  .timeCon {
-    display: flex;
-    justify-content: space-between;
-    .timeBox {
-      width: 432px;
-      height: 190px;
-      background: #3a3939;
-      box-shadow: 0px 3px 0px 0px rgba(255, 255, 255, 0.46),
-        0px -1px 3px 0px rgba(255, 255, 255, 0.65),
-        inset 0px 3px 8px 0px rgba(0, 0, 0, 0.5);
-      border-radius: 53px;
-      padding: 20px 46px 20px 42px;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      .midBg {
-        width: 10px;
-        height: 36px;
-      }
-      .timeInput {
-        display: flex;
-        flex-direction: column;
-        align-items: center;
-        width: 132px;
-        height: 100%;
-        position: relative;
-        &.mmTimeIpt,
-        &.ssTimeIpt {
-          &::before {
-            content: 'M';
-            position: absolute;
-            font-weight: bold;
-            font-size: 18px;
-            color: #ffffff;
-            top: 9px;
-            right: -16px;
-          }
-          &.ssTimeIpt::before {
-            right: -10px;
-            content: 'S' !important;
-          }
-        }
-        .timeInputBox {
-          flex-grow: 1;
-          overflow: hidden;
-          :deep(.n-input-number) {
-            height: 100%;
-            .n-input {
-              height: 100%;
-              --n-height: 100% !important;
-              --n-color-focus: initial !important;
-              --n-color: initial !important;
-              --n-padding-left: initial !important;
-              --n-padding-right: initial !important;
-              --n-color-disabled: initial !important;
-              --n-caret-color: #ffffff !important;
-              .n-input__border,
-              .n-input__state-border {
-                display: none;
-              }
-              .n-input__input-el {
-                text-align: center;
-                font-family: DINA !important;
-                font-weight: bold !important;
-                font-size: 136px !important;
-                color: #ffffff !important;
-                &::selection {
-                  background: #555252;
-                }
-              }
-              &.n-input--disabled {
-                cursor: initial;
-                .n-input__input-el {
-                  cursor: initial;
-                }
-              }
-            }
-          }
-        }
-        .timeInputBtn {
-          width: 100%;
-          flex-shrink: 0;
-          display: flex;
-          justify-content: space-between;
-          &.palyDisabled > div {
-            cursor: initial;
-            & > img {
-              opacity: 0.4;
-            }
-          }
-
-          & > div {
-            display: flex;
-            align-items: center;
-            height: 20px;
-            width: 50%;
-            cursor: pointer;
-            &:first-child {
-              flex-direction: row-reverse;
-              & > img {
-                margin: 0 22px 0 0;
-              }
-            }
-            & > img {
-              margin-left: 20px;
-              width: 12px;
-              height: 8px;
-            }
-          }
-        }
-      }
-    }
-    .timeTools {
-      flex-shrink: 0;
-      min-width: 138px;
-      .timeType {
-        width: 100%;
-        height: 40px;
-        background: rgba(229, 229, 299, 0.42);
-        border-radius: 40px;
-        padding: 6px;
-        .timeTypeBox {
-          width: 100%;
-          height: 100%;
-          background: #e3e3e3;
-          border-radius: 14px;
-          display: flex;
-          .timeTypeName {
-            width: 50%;
-            height: 100%;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            font-weight: 600;
-            font-size: 14px;
-            color: #7c7c7c;
-            border-radius: 14px;
-            cursor: pointer;
-            &.timeTypeActive {
-              color: #fff;
-              background: linear-gradient(90deg, #999999 0%, #7c7c7c 100%);
-            }
-          }
-        }
-      }
-      .playState,
-      .resetting {
-        margin-top: 12px;
-        width: 100%;
-        display: flex;
-        justify-content: center;
-        & > img {
-          cursor: pointer;
-          width: 78px;
-          height: 59px;
-        }
-      }
-    }
-  }
-  .timeBomCon {
-    width: 432px;
-    height: calc(100% - 190px);
-    display: flex;
-    justify-content: center;
-    position: relative;
-    .timeDownBom {
-      width: 100%;
-      height: 56px;
-      display: flex;
-      justify-content: space-between;
-      padding: 18px 30px 0;
-      & > div {
-        width: 85px;
-        height: 38px;
-        background: url('./img/timeNum.png') no-repeat;
-        background-size: 100% 100%;
-        font-weight: 600;
-        font-size: 18px;
-        color: #7c7c7c;
-        line-height: 38px;
-        text-align: center;
-        cursor: pointer;
-        z-index: 2;
-      }
-      &.palyDisabled > div {
-        opacity: 0.4;
-        cursor: initial;
-      }
-    }
-    .timeUpBom {
-      margin-top: 25px;
-      width: 332px;
-      height: 21px;
-    }
-    :global(.timerMeter .timeBomCon .bom_drag) {
-      position: absolute;
-      left: -24px;
-      bottom: 0;
-      width: 620px;
-      height: 40px;
-      z-index: 1;
-    }
-    :global(.timerMeter .timeBomCon .bom_drag > div) {
-      display: none;
-    }
-  }
-}
-</style>
+<template>
+  <div class="timerMeter">
+    <div class="timeCon">
+      <div class="timeBox">
+        <div class="timeInput mmTimeIpt">
+          <div
+            class="timeInputBtn"
+            v-if="timeType === 'countdown'"
+            :class="{ palyDisabled: playState === 'play' }"
+          >
+            <div @click="handleTimeNum(600)">
+              <img src="./img/upBtn.png" />
+            </div>
+            <div @click="handleTimeNum(60)">
+              <img src="./img/upBtn.png" />
+            </div>
+          </div>
+          <div class="timeInputBox">
+            <n-input-number
+              :disabled="true"
+              placeholder=""
+              :value="mmValue"
+              :format="(value: number | null)=>{
+                return value?(value<10?`0${value}`:value+''):'00'
+              }"
+              @update:value="(num:number)=>{
+                if(num){
+                  if(num<0){
+                    num=0
+                  }else if(num>59){
+                    num=59
+                  }
+                  timeNum=num * 60 + ssValue
+                }
+              }"
+              :show-button="false"
+            />
+          </div>
+          <div
+            class="timeInputBtn"
+            v-if="timeType === 'countdown'"
+            :class="{ palyDisabled: playState === 'play' }"
+          >
+            <div @click="handleTimeNum(-600)">
+              <img src="./img/downBtn.png" />
+            </div>
+            <div @click="handleTimeNum(-60)">
+              <img src="./img/downBtn.png" />
+            </div>
+          </div>
+        </div>
+        <img class="midBg" src="./img/midBg.png" />
+        <div class="timeInput ssTimeIpt">
+          <div
+            class="timeInputBtn"
+            v-if="timeType === 'countdown'"
+            :class="{ palyDisabled: playState === 'play' }"
+          >
+            <div @click="handleTimeNum(10)">
+              <img src="./img/upBtn.png" />
+            </div>
+            <div @click="handleTimeNum(1)">
+              <img src="./img/upBtn.png" />
+            </div>
+          </div>
+          <div class="timeInputBox">
+            <n-input-number
+              :disabled="true"
+              placeholder=""
+              :value="ssValue"
+              :format="(value: number | null)=>{
+                return value?(value<10?`0${value}`:value+''):'00'
+              }"
+              @update:value="(num:number)=>{
+                if(num){
+                  if(num<0){
+                    num=0
+                  }else if(num>59){
+                    num=59
+                  }
+                  timeNum=mmValue * 60 + num
+                }
+              }"
+              :show-button="false"
+            />
+          </div>
+          <div
+            class="timeInputBtn"
+            v-if="timeType === 'countdown'"
+            :class="{ palyDisabled: playState === 'play' }"
+          >
+            <div @click="handleTimeNum(-10)">
+              <img src="./img/downBtn.png" />
+            </div>
+            <div @click="handleTimeNum(-1)">
+              <img src="./img/downBtn.png" />
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="timeTools">
+        <div class="timeType">
+          <div class="timeTypeBox">
+            <div
+              class="timeTypeName"
+              :class="{ timeTypeActive: timeType === 'countdown' }"
+              @click="handleChangeTimeType('countdown')"
+            >
+              倒计时
+            </div>
+            <div
+              class="timeTypeName"
+              :class="{ timeTypeActive: timeType === 'countup' }"
+              @click="handleChangeTimeType('countup')"
+            >
+              正计时
+            </div>
+          </div>
+        </div>
+        <div class="playState">
+          <img
+            v-if="playState === 'play'"
+            @click="handlePause"
+            src="./img/play.png"
+          />
+          <img v-else @click="handlePlay" src="./img/pause.png" />
+        </div>
+        <div class="resetting">
+          <img @click="handleInitTime" src="./img/resetting.png" />
+        </div>
+      </div>
+    </div>
+    <div class="timeBomCon">
+      <div
+        v-if="timeType === 'countdown'"
+        class="timeDownBom"
+        :class="{ palyDisabled: playState === 'play' }"
+      >
+        <div @click="handleTimeNum(300)">5分</div>
+        <div @click="handleTimeNum(60)">1分</div>
+        <div @click="handleTimeNum(30)">30秒</div>
+        <div @click="handleTimeNum(5)">5秒</div>
+      </div>
+      <img v-else class="timeUpBom" src="./img/timeUpBom.png" />
+      <Dragbom />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, onUnmounted, onMounted } from 'vue';
+import { NInputNumber } from 'naive-ui';
+import soundWav from './timer.mp3';
+import Dragbom from '@/hooks/useDrag/dragbom';
+
+const soundVIdeo = new Audio(soundWav);
+
+onMounted(() => {
+  /*  n-input-number 有bug  input-props设置maxlength不上去 */
+  const iptDoms = document.querySelectorAll(
+    '.timerMeter .timeInput .timeInputBox .n-input__input-el'
+  );
+  iptDoms.forEach(item => {
+    item.setAttribute('maxlength', '2');
+  });
+});
+onUnmounted(() => {
+  clearInterval(_time);
+  soundVIdeo.pause();
+});
+
+// 计时类型
+const timeType = ref<'countdown' | 'countup'>('countdown');
+function handleChangeTimeType(type: 'countdown' | 'countup') {
+  if (timeType.value === type) {
+    return;
+  }
+  timeType.value = type;
+  handleInitTime();
+}
+// 清空状态
+function handleInitTime() {
+  handlePause();
+  timeNum.value = 0;
+  mmValue.value = 0;
+  ssValue.value = 0;
+}
+// 播放状态
+const playState = ref<'pause' | 'play'>('pause');
+//统计时间
+const timeNum = ref(0);
+const mmValue = ref(0);
+const ssValue = ref(0);
+watch(timeNum, () => {
+  mmValue.value = Math.floor(timeNum.value / 60);
+  ssValue.value = timeNum.value % 60;
+});
+function handleTimeNum(num: number) {
+  if (playState.value === 'play') return;
+  let timeNumNow = timeNum.value + num;
+  if (timeNumNow >= 3599) {
+    timeNumNow = 3599;
+  }
+  if (timeNumNow <= 0) {
+    timeNumNow = 0;
+  }
+  timeNum.value = timeNumNow;
+}
+// 开始
+function handlePlay() {
+  if (timeType.value === 'countdown') {
+    if (timeNum.value <= 0) {
+      return;
+    }
+    playState.value = 'play';
+    handleCountdownStart();
+  } else {
+    if (timeNum.value >= 3599) {
+      return;
+    }
+    playState.value = 'play';
+    handleCountUpStart();
+  }
+}
+// 暂停
+function handlePause() {
+  clearInterval(_time);
+  playState.value = 'pause';
+  soundVIdeo.pause();
+}
+
+let _time: NodeJS.Timer;
+// 倒计时
+function handleCountdownStart() {
+  // 由于时间结束之后还会有尾音,所以开始时候可能音乐在播放,所以这里先暂停
+  soundVIdeo.pause();
+  soundVIdeo.currentTime = 0;
+  if (timeNum.value <= 3) {
+    soundVIdeo.currentTime = 3 - timeNum.value;
+    soundVIdeo.play();
+  }
+  _time = setInterval(() => {
+    timeNum.value--;
+    if (timeNum.value === 3) {
+      soundVIdeo.play();
+    }
+    if (timeNum.value <= 0) {
+      // 结束的时候音乐不暂停
+      clearInterval(_time);
+      playState.value = 'pause';
+    }
+  }, 1000);
+}
+// 正计时
+function handleCountUpStart() {
+  _time = setInterval(() => {
+    timeNum.value++;
+    if (timeNum.value >= 3599) {
+      handlePause();
+    }
+  }, 1000);
+}
+</script>
+
+<style lang="less" scoped>
+.timerMeter {
+  width: 620px;
+  height: 316px;
+  background: url('./img/bg.png') no-repeat;
+  background-size: 100% 100%;
+  padding: 46px 25px 0 24px;
+  .timeCon {
+    display: flex;
+    justify-content: space-between;
+    .timeBox {
+      width: 432px;
+      height: 190px;
+      background: #3a3939;
+      box-shadow: 0px 3px 0px 0px rgba(255, 255, 255, 0.46),
+        0px -1px 3px 0px rgba(255, 255, 255, 0.65),
+        inset 0px 3px 8px 0px rgba(0, 0, 0, 0.5);
+      border-radius: 53px;
+      padding: 20px 46px 20px 42px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .midBg {
+        width: 10px;
+        height: 36px;
+      }
+      .timeInput {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        width: 132px;
+        height: 100%;
+        position: relative;
+        &.mmTimeIpt,
+        &.ssTimeIpt {
+          &::before {
+            content: 'M';
+            position: absolute;
+            font-weight: bold;
+            font-size: 18px;
+            color: #ffffff;
+            top: 9px;
+            right: -16px;
+          }
+          &.ssTimeIpt::before {
+            right: -10px;
+            content: 'S' !important;
+          }
+        }
+        .timeInputBox {
+          flex-grow: 1;
+          overflow: hidden;
+          :deep(.n-input-number) {
+            height: 100%;
+            .n-input {
+              height: 100%;
+              --n-height: 100% !important;
+              --n-color-focus: initial !important;
+              --n-color: initial !important;
+              --n-padding-left: initial !important;
+              --n-padding-right: initial !important;
+              --n-color-disabled: initial !important;
+              --n-caret-color: #ffffff !important;
+              .n-input__border,
+              .n-input__state-border {
+                display: none;
+              }
+              .n-input__input-el {
+                text-align: center;
+                font-family: DINA !important;
+                font-weight: bold !important;
+                font-size: 136px !important;
+                color: #ffffff !important;
+                &::selection {
+                  background: #555252;
+                }
+              }
+              &.n-input--disabled {
+                cursor: initial;
+                .n-input__input-el {
+                  cursor: initial;
+                }
+              }
+            }
+          }
+        }
+        .timeInputBtn {
+          width: 100%;
+          flex-shrink: 0;
+          display: flex;
+          justify-content: space-between;
+          &.palyDisabled > div {
+            cursor: initial;
+            & > img {
+              opacity: 0.4;
+            }
+          }
+
+          & > div {
+            display: flex;
+            align-items: center;
+            height: 20px;
+            width: 50%;
+            cursor: pointer;
+            &:first-child {
+              flex-direction: row-reverse;
+              & > img {
+                margin: 0 22px 0 0;
+              }
+            }
+            & > img {
+              margin-left: 20px;
+              width: 12px;
+              height: 8px;
+            }
+          }
+        }
+      }
+    }
+    .timeTools {
+      flex-shrink: 0;
+      min-width: 138px;
+      .timeType {
+        width: 100%;
+        height: 40px;
+        background: rgba(229, 229, 299, 0.42);
+        border-radius: 40px;
+        padding: 6px;
+        .timeTypeBox {
+          width: 100%;
+          height: 100%;
+          background: #e3e3e3;
+          border-radius: 14px;
+          display: flex;
+          .timeTypeName {
+            width: 50%;
+            height: 100%;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-weight: 600;
+            font-size: 14px;
+            color: #7c7c7c;
+            border-radius: 14px;
+            cursor: pointer;
+            &.timeTypeActive {
+              color: #fff;
+              background: linear-gradient(90deg, #999999 0%, #7c7c7c 100%);
+            }
+          }
+        }
+      }
+      .playState,
+      .resetting {
+        margin-top: 12px;
+        width: 100%;
+        display: flex;
+        justify-content: center;
+        & > img {
+          cursor: pointer;
+          width: 78px;
+          height: 59px;
+        }
+      }
+    }
+  }
+  .timeBomCon {
+    width: 432px;
+    height: calc(100% - 190px);
+    display: flex;
+    justify-content: center;
+    position: relative;
+    .timeDownBom {
+      width: 100%;
+      height: 56px;
+      display: flex;
+      justify-content: space-between;
+      padding: 18px 30px 0;
+      & > div {
+        width: 85px;
+        height: 38px;
+        background: url('./img/timeNum.png') no-repeat;
+        background-size: 100% 100%;
+        font-weight: 600;
+        font-size: 18px;
+        color: #7c7c7c;
+        line-height: 38px;
+        text-align: center;
+        cursor: pointer;
+        z-index: 2;
+      }
+      &.palyDisabled > div {
+        opacity: 0.4;
+        cursor: initial;
+      }
+    }
+    .timeUpBom {
+      margin-top: 25px;
+      width: 332px;
+      height: 21px;
+    }
+    :global(.timerMeter .timeBomCon .bom_drag) {
+      position: absolute;
+      left: -24px;
+      bottom: 0;
+      width: 620px;
+      height: 40px;
+      z-index: 1;
+    }
+    :global(.timerMeter .timeBomCon .bom_drag > div) {
+      display: none;
+    }
+  }
+}
+</style>

+ 2 - 2
src/components/timerMeter/index.ts

@@ -1,2 +1,2 @@
-import TimerMeter from './TimerMeter.vue';
-export default TimerMeter;
+import TimerMeter from './TimerMeter.vue';
+export default TimerMeter;

+ 386 - 386
src/components/timerMeterOld/components/countdown.tsx

@@ -1,386 +1,386 @@
-import {
-  defineComponent,
-  ref,
-  watch,
-  nextTick,
-  onMounted,
-  computed,
-  onUnmounted
-} from 'vue';
-import styles from '../index.module.less';
-import {
-  NTabs,
-  NTabPane,
-  NSpace,
-  NButton,
-  NImage,
-  NInput,
-  NInputNumber
-} from 'naive-ui';
-import { useRoute } from 'vue-router';
-import Flipper from '../modals/flipper.vue';
-import { stringify } from 'crypto-js/enc-utf8';
-import dayjs from 'dayjs';
-import playIcon from '../images/playing.png';
-import suspend from '../images/suspend.png';
-import add from '../images/add.png';
-import minus from '../images/minus.png';
-import { getSecond } from '@/utils/index';
-import soundWav from '../timer.wav';
-export default defineComponent({
-  name: 'timer-countdown',
-  setup() {
-    const activeTab = ref('positive'); //countdown
-    const route = useRoute();
-    // const flipperHour1 = ref()
-    // const flipperHour2 = ref()
-    const flipperMinute1 = ref();
-    const flipperMinute2 = ref();
-    const flipperSecond1 = ref();
-    const flipperSecond2 = ref();
-    const timer = ref(null as any);
-    const nowTimer = ref(null) as any;
-    const nowDate = ref(new Date()) as any;
-    nowTimer.value = setInterval(() => {
-      nowDate.value = new Date();
-    }, 1000);
-    const count = ref(0);
-    const mine = ref(0);
-    const second = ref(0);
-    const isPlaying = ref(false);
-    // flipperHour1, flipperHour2,
-    const flipObjs = ref([
-      flipperMinute1,
-      flipperMinute2,
-      flipperSecond1,
-      flipperSecond2
-    ]) as any;
-    const init = () => {
-      const now = new Date();
-      const nowTimeStr = '0000';
-      mine.value = 0;
-      second.value = 0;
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        flipObjs.value[i].value.setFront(nowTimeStr[i]);
-      }
-    };
-    const soundVIdeo = new Audio(soundWav);
-    const formatDate = (date: Date, dateFormat: string) => {
-      /* 单独格式化年份,根据y的字符数量输出年份
-     * 例如:yyyy => 2019
-            yy => 19
-            y => 9
-     */
-      if (/(y+)/.test(dateFormat)) {
-        dateFormat = dateFormat.replace(
-          RegExp.$1,
-          (date.getFullYear() + '').substr(4 - RegExp.$1.length)
-        );
-      }
-      // 格式化月、日、时、分、秒
-      const o = {
-        'm+': date.getMonth() + 1,
-        'd+': date.getDate(),
-        'h+': date.getHours(),
-        'i+': date.getMinutes(),
-        's+': date.getSeconds()
-      } as any;
-      for (const k in o) {
-        if (new RegExp(`(${k})`).test(dateFormat)) {
-          // 取出对应的值
-          const str = o[k] + '';
-          /* 根据设置的格式,输出对应的字符
-           * 例如: 早上8时,hh => 08,h => 8
-           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
-           * 例如: 下午15时,hh => 15, h => 15
-           */
-          dateFormat = dateFormat.replace(
-            RegExp.$1,
-            RegExp.$1.length === 1 ? str : padLeftZero(str)
-          );
-        }
-      }
-      return dateFormat;
-    };
-    const padLeftZero = (str: string) => {
-      return ('00' + str).substr(str.length);
-    };
-
-    const run = () => {
-      timer.value = setInterval(() => {
-        // 获取当前时间
-        const now = new Date();
-        const nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'iiss');
-        const nextTimeStr = formatDate(now, 'iiss');
-        console.log(nowTimeStr, nextTimeStr);
-        for (let i = 0; i < flipObjs.value.length; i++) {
-          if (nowTimeStr[i] === nextTimeStr[i]) {
-            continue;
-          }
-          flipObjs.value[i].value.flipDown(nowTimeStr[i], nextTimeStr[i]);
-        }
-      }, 1000);
-    };
-    const startTimer = () => {
-      isPlaying.value = true;
-      timer.value = setInterval(() => {
-        // 获取当前时间
-        const lastStr = getSecond(count.value);
-        if (count.value == 4) {
-          soundVIdeo.play();
-        }
-        if (count.value <= 0) {
-          onReset();
-          return;
-        }
-        count.value--;
-
-        const str = getSecond(count.value);
-        for (let i = 0; i < flipObjs.value.length; i++) {
-          if (lastStr[i] === str[i]) {
-            continue;
-          }
-          flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
-        }
-      }, 1000);
-    };
-
-    const suspendNum = () => {
-      setTimeout(() => {
-        isPlaying.value = false;
-        soundVIdeo.currentTime = 0;
-        soundVIdeo.pause();
-        if (timer.value) {
-          clearInterval(timer.value);
-          timer.value = null;
-        }
-      }, 600);
-    };
-    const onReset = () => {
-      if (isPlaying.value) {
-        suspendNum();
-        count.value = 0;
-        soundVIdeo.currentTime = 0;
-        soundVIdeo.pause();
-        setTimeout(() => {
-          init();
-        }, 600);
-      } else {
-        // isPlaying.value = false
-        count.value = 0;
-        init();
-      }
-    };
-    onMounted(() => {
-      nextTick(() => {
-        init();
-        // run()
-      });
-    });
-    onUnmounted(() => {
-      soundVIdeo.pause();
-    });
-    const addSecondTimer = (num: number) => {
-      const lastStr = getSecond(count.value);
-      count.value += num;
-      count.value > 3599 ? (count.value = 3599) : count.value;
-      const str = getSecond(count.value);
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        if (lastStr[i] === str[i]) {
-          continue;
-        }
-        flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
-      }
-      mine.value = Math.floor(count.value / 60);
-      second.value = Math.floor(count.value % 60);
-    };
-    const minusSecondTimer = (num: number) => {
-      const lastStr = getSecond(count.value);
-      count.value -= num;
-      count.value < 0 ? (count.value = 0) : count.value;
-      const str = getSecond(count.value);
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        if (lastStr[i] === str[i]) {
-          continue;
-        }
-        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
-      }
-      mine.value = Math.floor(count.value / 60);
-      second.value = Math.floor(count.value % 60);
-    };
-
-    const updateMin = (e: any) => {
-      let targetCount = parseInt(e.target.value);
-      if (Number.isNaN(targetCount)) {
-        targetCount = 0;
-      }
-      if (targetCount > 59) {
-        targetCount = 59;
-      }
-      mine.value = targetCount;
-      const lastStr = getSecond(count.value);
-      console.log(mine.value);
-      count.value = mine.value * 60 + second.value;
-
-      const str = getSecond(count.value);
-      console.log(str, lastStr);
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        if (lastStr[i] === str[i]) {
-          continue;
-        }
-        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
-      }
-    };
-    const updateSecond = (e: any) => {
-      let targetCount = parseInt(e.target.value);
-      if (Number.isNaN(targetCount)) {
-        targetCount = 0;
-      }
-      if (targetCount > 59) {
-        targetCount = 59;
-      }
-      second.value = targetCount;
-      const lastStr = getSecond(count.value);
-      count.value = mine.value * 60 + second.value;
-      const str = getSecond(count.value);
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        if (lastStr[i] === str[i]) {
-          continue;
-        }
-        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
-      }
-    };
-
-    watch(
-      () => count.value,
-      val => {
-        mine.value = Math.floor(val / 60);
-        second.value = Math.floor(val % 60);
-      }
-    );
-    return () => (
-      <div class={styles.timerItemWrap}>
-        <div class={styles.timerItemInfo}>
-          <div class={styles.timerItemInset}>
-            <div class={styles.timerItemInfoTop}>
-              <div class={styles.timerItemTopCore}>
-                <h4> 分</h4>
-
-                <div class={styles.FlipClock}>
-                  <div class={styles.numberWrap}>
-                    <Flipper ref={flipperMinute1} />
-                    <Flipper ref={flipperMinute2} />
-                  </div>
-
-                  <div
-                    class={[
-                      styles.chioseWrap,
-                      isPlaying.value ? styles.chioseHidden : ''
-                    ]}>
-                    <img
-                      src={add}
-                      class={styles.add}
-                      alt=""
-                      onClick={() => addSecondTimer(60)}
-                    />
-                    <NInputNumber
-                      class={styles.countInput}
-                      min={0}
-                      max={59}
-                      show-button={false}
-                      onInput={updateMin}
-                      v-model:value={mine.value}></NInputNumber>
-                    <img
-                      src={minus}
-                      class={styles.minus}
-                      alt=""
-                      onClick={() => minusSecondTimer(60)}
-                    />
-                  </div>
-                </div>
-              </div>
-              <div class={styles.timerItemTopCore}>
-                <div class={styles.dot}></div>
-                <div class={styles.dot}></div>
-                <h4 class={styles.dotBtm}></h4>
-              </div>
-              <div class={styles.timerItemTopCore}>
-                <h4> 秒 </h4>
-                <div class={styles.FlipClock}>
-                  <div class={styles.numberWrap}>
-                    <Flipper ref={flipperSecond1} />
-                    <Flipper ref={flipperSecond2} />
-                  </div>
-
-                  <div
-                    class={[
-                      styles.chioseWrap,
-                      isPlaying.value ? styles.chioseHidden : ''
-                    ]}>
-                    <img
-                      src={add}
-                      class={styles.add}
-                      alt=""
-                      onClick={() => addSecondTimer(1)}
-                    />
-                    <NInputNumber
-                      class={styles.countInput}
-                      min={0}
-                      max={59}
-                      show-button={false}
-                      v-model:value={second.value}
-                      onInput={updateSecond}></NInputNumber>
-                    <img
-                      src={minus}
-                      class={styles.minus}
-                      alt=""
-                      onClick={() => minusSecondTimer(1)}
-                    />
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <NSpace class={styles.btnGroupModal} justify="center">
-          <NButton round onClick={() => onReset()}>
-            重置
-          </NButton>
-          {isPlaying.value ? (
-            <NButton
-              round
-              type="primary"
-              icon-placement="right"
-              onClick={() => suspendNum()}
-              v-slots={{
-                default: () => <p class={styles.playText}>暂停</p>,
-                icon: () => (
-                  <NImage
-                    previewDisabled
-                    class={styles.palyIcon}
-                    src={suspend}></NImage>
-                )
-              }}></NButton>
-          ) : (
-            <NButton
-              round
-              type="primary"
-              disabled={count.value == 0}
-              icon-placement="right"
-              onClick={() => startTimer()}
-              v-slots={{
-                default: () => <p class={styles.playText}>开始</p>,
-                icon: () => (
-                  <NImage
-                    previewDisabled
-                    class={styles.palyIcon}
-                    src={playIcon}></NImage>
-                )
-              }}></NButton>
-          )}
-        </NSpace>
-      </div>
-    );
-  }
-});
+import {
+  defineComponent,
+  ref,
+  watch,
+  nextTick,
+  onMounted,
+  computed,
+  onUnmounted
+} from 'vue';
+import styles from '../index.module.less';
+import {
+  NTabs,
+  NTabPane,
+  NSpace,
+  NButton,
+  NImage,
+  NInput,
+  NInputNumber
+} from 'naive-ui';
+import { useRoute } from 'vue-router';
+import Flipper from '../modals/flipper.vue';
+import { stringify } from 'crypto-js/enc-utf8';
+import dayjs from 'dayjs';
+import playIcon from '../images/playing.png';
+import suspend from '../images/suspend.png';
+import add from '../images/add.png';
+import minus from '../images/minus.png';
+import { getSecond } from '@/utils/index';
+import soundWav from '../timer.wav';
+export default defineComponent({
+  name: 'timer-countdown',
+  setup() {
+    const activeTab = ref('positive'); //countdown
+    const route = useRoute();
+    // const flipperHour1 = ref()
+    // const flipperHour2 = ref()
+    const flipperMinute1 = ref();
+    const flipperMinute2 = ref();
+    const flipperSecond1 = ref();
+    const flipperSecond2 = ref();
+    const timer = ref(null as any);
+    const nowTimer = ref(null) as any;
+    const nowDate = ref(new Date()) as any;
+    nowTimer.value = setInterval(() => {
+      nowDate.value = new Date();
+    }, 1000);
+    const count = ref(0);
+    const mine = ref(0);
+    const second = ref(0);
+    const isPlaying = ref(false);
+    // flipperHour1, flipperHour2,
+    const flipObjs = ref([
+      flipperMinute1,
+      flipperMinute2,
+      flipperSecond1,
+      flipperSecond2
+    ]) as any;
+    const init = () => {
+      const now = new Date();
+      const nowTimeStr = '0000';
+      mine.value = 0;
+      second.value = 0;
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        flipObjs.value[i].value.setFront(nowTimeStr[i]);
+      }
+    };
+    const soundVIdeo = new Audio(soundWav);
+    const formatDate = (date: Date, dateFormat: string) => {
+      /* 单独格式化年份,根据y的字符数量输出年份
+     * 例如:yyyy => 2019
+            yy => 19
+            y => 9
+     */
+      if (/(y+)/.test(dateFormat)) {
+        dateFormat = dateFormat.replace(
+          RegExp.$1,
+          (date.getFullYear() + '').substr(4 - RegExp.$1.length)
+        );
+      }
+      // 格式化月、日、时、分、秒
+      const o = {
+        'm+': date.getMonth() + 1,
+        'd+': date.getDate(),
+        'h+': date.getHours(),
+        'i+': date.getMinutes(),
+        's+': date.getSeconds()
+      } as any;
+      for (const k in o) {
+        if (new RegExp(`(${k})`).test(dateFormat)) {
+          // 取出对应的值
+          const str = o[k] + '';
+          /* 根据设置的格式,输出对应的字符
+           * 例如: 早上8时,hh => 08,h => 8
+           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
+           * 例如: 下午15时,hh => 15, h => 15
+           */
+          dateFormat = dateFormat.replace(
+            RegExp.$1,
+            RegExp.$1.length === 1 ? str : padLeftZero(str)
+          );
+        }
+      }
+      return dateFormat;
+    };
+    const padLeftZero = (str: string) => {
+      return ('00' + str).substr(str.length);
+    };
+
+    const run = () => {
+      timer.value = setInterval(() => {
+        // 获取当前时间
+        const now = new Date();
+        const nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'iiss');
+        const nextTimeStr = formatDate(now, 'iiss');
+        console.log(nowTimeStr, nextTimeStr);
+        for (let i = 0; i < flipObjs.value.length; i++) {
+          if (nowTimeStr[i] === nextTimeStr[i]) {
+            continue;
+          }
+          flipObjs.value[i].value.flipDown(nowTimeStr[i], nextTimeStr[i]);
+        }
+      }, 1000);
+    };
+    const startTimer = () => {
+      isPlaying.value = true;
+      timer.value = setInterval(() => {
+        // 获取当前时间
+        const lastStr = getSecond(count.value);
+        if (count.value == 4) {
+          soundVIdeo.play();
+        }
+        if (count.value <= 0) {
+          onReset();
+          return;
+        }
+        count.value--;
+
+        const str = getSecond(count.value);
+        for (let i = 0; i < flipObjs.value.length; i++) {
+          if (lastStr[i] === str[i]) {
+            continue;
+          }
+          flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
+        }
+      }, 1000);
+    };
+
+    const suspendNum = () => {
+      setTimeout(() => {
+        isPlaying.value = false;
+        soundVIdeo.currentTime = 0;
+        soundVIdeo.pause();
+        if (timer.value) {
+          clearInterval(timer.value);
+          timer.value = null;
+        }
+      }, 600);
+    };
+    const onReset = () => {
+      if (isPlaying.value) {
+        suspendNum();
+        count.value = 0;
+        soundVIdeo.currentTime = 0;
+        soundVIdeo.pause();
+        setTimeout(() => {
+          init();
+        }, 600);
+      } else {
+        // isPlaying.value = false
+        count.value = 0;
+        init();
+      }
+    };
+    onMounted(() => {
+      nextTick(() => {
+        init();
+        // run()
+      });
+    });
+    onUnmounted(() => {
+      soundVIdeo.pause();
+    });
+    const addSecondTimer = (num: number) => {
+      const lastStr = getSecond(count.value);
+      count.value += num;
+      count.value > 3599 ? (count.value = 3599) : count.value;
+      const str = getSecond(count.value);
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        if (lastStr[i] === str[i]) {
+          continue;
+        }
+        flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
+      }
+      mine.value = Math.floor(count.value / 60);
+      second.value = Math.floor(count.value % 60);
+    };
+    const minusSecondTimer = (num: number) => {
+      const lastStr = getSecond(count.value);
+      count.value -= num;
+      count.value < 0 ? (count.value = 0) : count.value;
+      const str = getSecond(count.value);
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        if (lastStr[i] === str[i]) {
+          continue;
+        }
+        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
+      }
+      mine.value = Math.floor(count.value / 60);
+      second.value = Math.floor(count.value % 60);
+    };
+
+    const updateMin = (e: any) => {
+      let targetCount = parseInt(e.target.value);
+      if (Number.isNaN(targetCount)) {
+        targetCount = 0;
+      }
+      if (targetCount > 59) {
+        targetCount = 59;
+      }
+      mine.value = targetCount;
+      const lastStr = getSecond(count.value);
+      console.log(mine.value);
+      count.value = mine.value * 60 + second.value;
+
+      const str = getSecond(count.value);
+      console.log(str, lastStr);
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        if (lastStr[i] === str[i]) {
+          continue;
+        }
+        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
+      }
+    };
+    const updateSecond = (e: any) => {
+      let targetCount = parseInt(e.target.value);
+      if (Number.isNaN(targetCount)) {
+        targetCount = 0;
+      }
+      if (targetCount > 59) {
+        targetCount = 59;
+      }
+      second.value = targetCount;
+      const lastStr = getSecond(count.value);
+      count.value = mine.value * 60 + second.value;
+      const str = getSecond(count.value);
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        if (lastStr[i] === str[i]) {
+          continue;
+        }
+        flipObjs.value[i].value.flipUp(lastStr[i], str[i]);
+      }
+    };
+
+    watch(
+      () => count.value,
+      val => {
+        mine.value = Math.floor(val / 60);
+        second.value = Math.floor(val % 60);
+      }
+    );
+    return () => (
+      <div class={styles.timerItemWrap}>
+        <div class={styles.timerItemInfo}>
+          <div class={styles.timerItemInset}>
+            <div class={styles.timerItemInfoTop}>
+              <div class={styles.timerItemTopCore}>
+                <h4> 分</h4>
+
+                <div class={styles.FlipClock}>
+                  <div class={styles.numberWrap}>
+                    <Flipper ref={flipperMinute1} />
+                    <Flipper ref={flipperMinute2} />
+                  </div>
+
+                  <div
+                    class={[
+                      styles.chioseWrap,
+                      isPlaying.value ? styles.chioseHidden : ''
+                    ]}>
+                    <img
+                      src={add}
+                      class={styles.add}
+                      alt=""
+                      onClick={() => addSecondTimer(60)}
+                    />
+                    <NInputNumber
+                      class={styles.countInput}
+                      min={0}
+                      max={59}
+                      show-button={false}
+                      onInput={updateMin}
+                      v-model:value={mine.value}></NInputNumber>
+                    <img
+                      src={minus}
+                      class={styles.minus}
+                      alt=""
+                      onClick={() => minusSecondTimer(60)}
+                    />
+                  </div>
+                </div>
+              </div>
+              <div class={styles.timerItemTopCore}>
+                <div class={styles.dot}></div>
+                <div class={styles.dot}></div>
+                <h4 class={styles.dotBtm}></h4>
+              </div>
+              <div class={styles.timerItemTopCore}>
+                <h4> 秒 </h4>
+                <div class={styles.FlipClock}>
+                  <div class={styles.numberWrap}>
+                    <Flipper ref={flipperSecond1} />
+                    <Flipper ref={flipperSecond2} />
+                  </div>
+
+                  <div
+                    class={[
+                      styles.chioseWrap,
+                      isPlaying.value ? styles.chioseHidden : ''
+                    ]}>
+                    <img
+                      src={add}
+                      class={styles.add}
+                      alt=""
+                      onClick={() => addSecondTimer(1)}
+                    />
+                    <NInputNumber
+                      class={styles.countInput}
+                      min={0}
+                      max={59}
+                      show-button={false}
+                      v-model:value={second.value}
+                      onInput={updateSecond}></NInputNumber>
+                    <img
+                      src={minus}
+                      class={styles.minus}
+                      alt=""
+                      onClick={() => minusSecondTimer(1)}
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <NSpace class={styles.btnGroupModal} justify="center">
+          <NButton round onClick={() => onReset()}>
+            重置
+          </NButton>
+          {isPlaying.value ? (
+            <NButton
+              round
+              type="primary"
+              icon-placement="right"
+              onClick={() => suspendNum()}
+              v-slots={{
+                default: () => <p class={styles.playText}>暂停</p>,
+                icon: () => (
+                  <NImage
+                    previewDisabled
+                    class={styles.palyIcon}
+                    src={suspend}></NImage>
+                )
+              }}></NButton>
+          ) : (
+            <NButton
+              round
+              type="primary"
+              disabled={count.value == 0}
+              icon-placement="right"
+              onClick={() => startTimer()}
+              v-slots={{
+                default: () => <p class={styles.playText}>开始</p>,
+                icon: () => (
+                  <NImage
+                    previewDisabled
+                    class={styles.palyIcon}
+                    src={playIcon}></NImage>
+                )
+              }}></NButton>
+          )}
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 217 - 217
src/components/timerMeterOld/components/positive.tsx

@@ -1,217 +1,217 @@
-import { defineComponent, ref, watch, nextTick, onMounted } from 'vue';
-import styles from '../index.module.less';
-import { NTabs, NTabPane, NSpace, NButton, NImage } from 'naive-ui';
-import { useRoute } from 'vue-router';
-import Flipper from '../modals/flipper.vue';
-import { stringify } from 'crypto-js/enc-utf8';
-import dayjs from 'dayjs';
-import playIcon from '../images/playing.png';
-import suspend from '../images/suspend.png';
-import { getSecond } from '@/utils/index';
-export default defineComponent({
-  name: 'timer-positive',
-  setup() {
-    const activeTab = ref('positive'); //countdown
-    const route = useRoute();
-    // const flipperHour1 = ref()
-    // const flipperHour2 = ref()
-    const flipperMinute1 = ref();
-    const flipperMinute2 = ref();
-    const flipperSecond1 = ref();
-    const flipperSecond2 = ref();
-    const timer = ref(null as any);
-    const nowTimer = ref(null) as any;
-    const nowDate = ref(new Date()) as any;
-    nowTimer.value = setInterval(() => {
-      nowDate.value = new Date();
-    }, 1000);
-    const count = ref(0);
-    const isPlaying = ref(false);
-    // flipperHour1, flipperHour2,
-    const flipObjs = ref([
-      flipperMinute1,
-      flipperMinute2,
-      flipperSecond1,
-      flipperSecond2
-    ]) as any;
-    const init = () => {
-      const now = new Date();
-      const nowTimeStr = '0000';
-
-      for (let i = 0; i < flipObjs.value.length; i++) {
-        flipObjs.value[i].value.setFront(nowTimeStr[i]);
-      }
-    };
-
-    const formatDate = (date: Date, dateFormat: string) => {
-      /* 单独格式化年份,根据y的字符数量输出年份
-     * 例如:yyyy => 2019
-            yy => 19
-            y => 9
-     */
-      if (/(y+)/.test(dateFormat)) {
-        dateFormat = dateFormat.replace(
-          RegExp.$1,
-          (date.getFullYear() + '').substr(4 - RegExp.$1.length)
-        );
-      }
-      // 格式化月、日、时、分、秒
-      const o = {
-        'm+': date.getMonth() + 1,
-        'd+': date.getDate(),
-        'h+': date.getHours(),
-        'i+': date.getMinutes(),
-        's+': date.getSeconds()
-      } as any;
-      for (const k in o) {
-        if (new RegExp(`(${k})`).test(dateFormat)) {
-          // 取出对应的值
-          const str = o[k] + '';
-          /* 根据设置的格式,输出对应的字符
-           * 例如: 早上8时,hh => 08,h => 8
-           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
-           * 例如: 下午15时,hh => 15, h => 15
-           */
-          dateFormat = dateFormat.replace(
-            RegExp.$1,
-            RegExp.$1.length === 1 ? str : padLeftZero(str)
-          );
-        }
-      }
-      return dateFormat;
-    };
-    const padLeftZero = (str: string) => {
-      return ('00' + str).substr(str.length);
-    };
-
-    const run = () => {
-      timer.value = setInterval(() => {
-        // 获取当前时间
-        const now = new Date();
-        const nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'iiss');
-        const nextTimeStr = formatDate(now, 'iiss');
-        console.log(nowTimeStr, nextTimeStr);
-        for (let i = 0; i < flipObjs.value.length; i++) {
-          if (nowTimeStr[i] === nextTimeStr[i]) {
-            continue;
-          }
-          flipObjs.value[i].value.flipDown(nowTimeStr[i], nextTimeStr[i]);
-        }
-      }, 1000);
-    };
-    const startTimer = () => {
-      isPlaying.value = true;
-      timer.value = setInterval(() => {
-        // 获取当前时间
-        const lastStr = getSecond(count.value);
-        count.value++;
-
-        const str = getSecond(count.value);
-        for (let i = 0; i < flipObjs.value.length; i++) {
-          if (lastStr[i] === str[i]) {
-            continue;
-          }
-          flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
-        }
-      }, 1000);
-    };
-
-    const suspendNum = () => {
-      isPlaying.value = false;
-      if (timer.value) {
-        clearInterval(timer.value);
-        timer.value = null;
-      }
-    };
-    const onReset = () => {
-      suspendNum();
-      count.value = 0;
-      setTimeout(() => {
-        //   console.log('初始化')
-        init();
-      }, 600);
-    };
-    onMounted(() => {
-      nextTick(() => {
-        init();
-        // run()
-      });
-    });
-
-    return () => (
-      <div class={styles.timerItemWrap}>
-        <div class={styles.timerItemInfo}>
-          <div class={styles.timerItemInset}>
-            <div class={styles.timerItemInfoTop}>
-              <div class={styles.timerItemTopCore}>
-                <h4> 分</h4>
-
-                <div class={styles.FlipClock}>
-                  <div class={styles.numberWrap}>
-                    <Flipper ref={flipperMinute1} />
-                    <Flipper ref={flipperMinute2} />
-                  </div>
-                </div>
-              </div>
-              <div class={styles.timerItemTopCore}>
-                <h4 class={styles.dotTop}></h4>
-                <div class={styles.dot}></div>
-                <div class={styles.dot}></div>
-              </div>
-              <div class={styles.timerItemTopCore}>
-                <h4> 秒</h4>
-
-                <div class={styles.FlipClock}>
-                  <div class={styles.numberWrap}>
-                    <Flipper ref={flipperSecond1} />
-                    <Flipper ref={flipperSecond2} />
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div class={styles.nowTimerWrap}>
-              {dayjs(nowDate.value).format('YYYY年MM月DD日 HH:mm:ss')}
-            </div>
-          </div>
-        </div>
-
-        <NSpace class={styles.btnGroupModal} justify="center">
-          <NButton round onClick={() => onReset()}>
-            重置
-          </NButton>
-          {isPlaying.value ? (
-            <NButton
-              round
-              type="primary"
-              icon-placement="right"
-              onClick={() => suspendNum()}
-              v-slots={{
-                default: () => <p class={styles.playText}>暂停</p>,
-                icon: () => (
-                  <NImage
-                    previewDisabled
-                    class={styles.palyIcon}
-                    src={suspend}></NImage>
-                )
-              }}></NButton>
-          ) : (
-            <NButton
-              round
-              type="primary"
-              icon-placement="right"
-              onClick={() => startTimer()}
-              v-slots={{
-                default: () => <p class={styles.playText}>开始</p>,
-                icon: () => (
-                  <NImage
-                    previewDisabled
-                    class={styles.palyIcon}
-                    src={playIcon}></NImage>
-                )
-              }}></NButton>
-          )}
-        </NSpace>
-      </div>
-    );
-  }
-});
+import { defineComponent, ref, watch, nextTick, onMounted } from 'vue';
+import styles from '../index.module.less';
+import { NTabs, NTabPane, NSpace, NButton, NImage } from 'naive-ui';
+import { useRoute } from 'vue-router';
+import Flipper from '../modals/flipper.vue';
+import { stringify } from 'crypto-js/enc-utf8';
+import dayjs from 'dayjs';
+import playIcon from '../images/playing.png';
+import suspend from '../images/suspend.png';
+import { getSecond } from '@/utils/index';
+export default defineComponent({
+  name: 'timer-positive',
+  setup() {
+    const activeTab = ref('positive'); //countdown
+    const route = useRoute();
+    // const flipperHour1 = ref()
+    // const flipperHour2 = ref()
+    const flipperMinute1 = ref();
+    const flipperMinute2 = ref();
+    const flipperSecond1 = ref();
+    const flipperSecond2 = ref();
+    const timer = ref(null as any);
+    const nowTimer = ref(null) as any;
+    const nowDate = ref(new Date()) as any;
+    nowTimer.value = setInterval(() => {
+      nowDate.value = new Date();
+    }, 1000);
+    const count = ref(0);
+    const isPlaying = ref(false);
+    // flipperHour1, flipperHour2,
+    const flipObjs = ref([
+      flipperMinute1,
+      flipperMinute2,
+      flipperSecond1,
+      flipperSecond2
+    ]) as any;
+    const init = () => {
+      const now = new Date();
+      const nowTimeStr = '0000';
+
+      for (let i = 0; i < flipObjs.value.length; i++) {
+        flipObjs.value[i].value.setFront(nowTimeStr[i]);
+      }
+    };
+
+    const formatDate = (date: Date, dateFormat: string) => {
+      /* 单独格式化年份,根据y的字符数量输出年份
+     * 例如:yyyy => 2019
+            yy => 19
+            y => 9
+     */
+      if (/(y+)/.test(dateFormat)) {
+        dateFormat = dateFormat.replace(
+          RegExp.$1,
+          (date.getFullYear() + '').substr(4 - RegExp.$1.length)
+        );
+      }
+      // 格式化月、日、时、分、秒
+      const o = {
+        'm+': date.getMonth() + 1,
+        'd+': date.getDate(),
+        'h+': date.getHours(),
+        'i+': date.getMinutes(),
+        's+': date.getSeconds()
+      } as any;
+      for (const k in o) {
+        if (new RegExp(`(${k})`).test(dateFormat)) {
+          // 取出对应的值
+          const str = o[k] + '';
+          /* 根据设置的格式,输出对应的字符
+           * 例如: 早上8时,hh => 08,h => 8
+           * 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
+           * 例如: 下午15时,hh => 15, h => 15
+           */
+          dateFormat = dateFormat.replace(
+            RegExp.$1,
+            RegExp.$1.length === 1 ? str : padLeftZero(str)
+          );
+        }
+      }
+      return dateFormat;
+    };
+    const padLeftZero = (str: string) => {
+      return ('00' + str).substr(str.length);
+    };
+
+    const run = () => {
+      timer.value = setInterval(() => {
+        // 获取当前时间
+        const now = new Date();
+        const nowTimeStr = formatDate(new Date(now.getTime() - 1000), 'iiss');
+        const nextTimeStr = formatDate(now, 'iiss');
+        console.log(nowTimeStr, nextTimeStr);
+        for (let i = 0; i < flipObjs.value.length; i++) {
+          if (nowTimeStr[i] === nextTimeStr[i]) {
+            continue;
+          }
+          flipObjs.value[i].value.flipDown(nowTimeStr[i], nextTimeStr[i]);
+        }
+      }, 1000);
+    };
+    const startTimer = () => {
+      isPlaying.value = true;
+      timer.value = setInterval(() => {
+        // 获取当前时间
+        const lastStr = getSecond(count.value);
+        count.value++;
+
+        const str = getSecond(count.value);
+        for (let i = 0; i < flipObjs.value.length; i++) {
+          if (lastStr[i] === str[i]) {
+            continue;
+          }
+          flipObjs.value[i].value.flipDown(lastStr[i], str[i]);
+        }
+      }, 1000);
+    };
+
+    const suspendNum = () => {
+      isPlaying.value = false;
+      if (timer.value) {
+        clearInterval(timer.value);
+        timer.value = null;
+      }
+    };
+    const onReset = () => {
+      suspendNum();
+      count.value = 0;
+      setTimeout(() => {
+        //   console.log('初始化')
+        init();
+      }, 600);
+    };
+    onMounted(() => {
+      nextTick(() => {
+        init();
+        // run()
+      });
+    });
+
+    return () => (
+      <div class={styles.timerItemWrap}>
+        <div class={styles.timerItemInfo}>
+          <div class={styles.timerItemInset}>
+            <div class={styles.timerItemInfoTop}>
+              <div class={styles.timerItemTopCore}>
+                <h4> 分</h4>
+
+                <div class={styles.FlipClock}>
+                  <div class={styles.numberWrap}>
+                    <Flipper ref={flipperMinute1} />
+                    <Flipper ref={flipperMinute2} />
+                  </div>
+                </div>
+              </div>
+              <div class={styles.timerItemTopCore}>
+                <h4 class={styles.dotTop}></h4>
+                <div class={styles.dot}></div>
+                <div class={styles.dot}></div>
+              </div>
+              <div class={styles.timerItemTopCore}>
+                <h4> 秒</h4>
+
+                <div class={styles.FlipClock}>
+                  <div class={styles.numberWrap}>
+                    <Flipper ref={flipperSecond1} />
+                    <Flipper ref={flipperSecond2} />
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class={styles.nowTimerWrap}>
+              {dayjs(nowDate.value).format('YYYY年MM月DD日 HH:mm:ss')}
+            </div>
+          </div>
+        </div>
+
+        <NSpace class={styles.btnGroupModal} justify="center">
+          <NButton round onClick={() => onReset()}>
+            重置
+          </NButton>
+          {isPlaying.value ? (
+            <NButton
+              round
+              type="primary"
+              icon-placement="right"
+              onClick={() => suspendNum()}
+              v-slots={{
+                default: () => <p class={styles.playText}>暂停</p>,
+                icon: () => (
+                  <NImage
+                    previewDisabled
+                    class={styles.palyIcon}
+                    src={suspend}></NImage>
+                )
+              }}></NButton>
+          ) : (
+            <NButton
+              round
+              type="primary"
+              icon-placement="right"
+              onClick={() => startTimer()}
+              v-slots={{
+                default: () => <p class={styles.playText}>开始</p>,
+                icon: () => (
+                  <NImage
+                    previewDisabled
+                    class={styles.palyIcon}
+                    src={playIcon}></NImage>
+                )
+              }}></NButton>
+          )}
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 51 - 29
src/components/timerMeterOld/index.tsx

@@ -1,29 +1,51 @@
-import { defineComponent, ref, watch, nextTick, onMounted } from 'vue';
-import styles from './index.module.less';
-import { NTabs, NTabPane, NSpace, NButton } from 'naive-ui';
-import { useRoute } from 'vue-router';
-import Countdown from './components/countdown';
-import Positive from './components/positive';
-export default defineComponent({
-  name: 'data-module',
-  setup() {
-    const activeTab = ref('positive');  //countdown
-    const route = useRoute();
-    // onMounted(() => {
-    // })
-    const setActivePinia = (name: string) => {
-      activeTab.value = name
-    }
-    return () => (
-      <div>
-        <div class={styles.timerWrap}>
-          <div class={styles.timerTop}>
-            <div class={[styles.timerTopPane, activeTab.value == 'positive' ? styles.timerTopPaneActive : '']} onClick={() => { setActivePinia('positive') }}>正计时</div>
-            <div class={[styles.timerTopPane, activeTab.value == 'countdown' ? styles.timerTopPaneActive : '']} onClick={() => { setActivePinia('countdown') }}>倒计时</div>
-          </div>
-          {activeTab.value == 'positive' ? <Positive></Positive> : <Countdown></Countdown>}
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, ref, watch, nextTick, onMounted } from 'vue';
+import styles from './index.module.less';
+import { NTabs, NTabPane, NSpace, NButton } from 'naive-ui';
+import { useRoute } from 'vue-router';
+import Countdown from './components/countdown';
+import Positive from './components/positive';
+export default defineComponent({
+  name: 'data-module',
+  setup() {
+    const activeTab = ref('positive'); //countdown
+    const route = useRoute();
+    // onMounted(() => {
+    // })
+    const setActivePinia = (name: string) => {
+      activeTab.value = name;
+    };
+    return () => (
+      <div>
+        <div class={styles.timerWrap}>
+          <div class={styles.timerTop}>
+            <div
+              class={[
+                styles.timerTopPane,
+                activeTab.value == 'positive' ? styles.timerTopPaneActive : ''
+              ]}
+              onClick={() => {
+                setActivePinia('positive');
+              }}>
+              正计时
+            </div>
+            <div
+              class={[
+                styles.timerTopPane,
+                activeTab.value == 'countdown' ? styles.timerTopPaneActive : ''
+              ]}
+              onClick={() => {
+                setActivePinia('countdown');
+              }}>
+              倒计时
+            </div>
+          </div>
+          {activeTab.value == 'positive' ? (
+            <Positive></Positive>
+          ) : (
+            <Countdown></Countdown>
+          )}
+        </div>
+      </div>
+    );
+  }
+});

+ 296 - 298
src/components/timerMeterOld/modals/flipper.vue

@@ -1,298 +1,296 @@
-
-<template>
-  <div class="M-Flipper" :class="[flipType, {'go': isFlipping}]">
-    <div class="digital front" :class="_textClass(frontTextFromData)"></div>
-    <div class="digital back" :class="_textClass(backTextFromData)"></div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'FlipClock',
-  data() {
-    return {
-      isFlipping: false,
-      flipType: 'down',
-      frontTextFromData: 0,
-      backTextFromData: 1
-    }
-  },
-  props: {
-    // front paper text
-    // 前牌文字
-    frontText: {
-      type: [Number, String],
-      default: 0
-    },
-    // back paper text
-    // 后牌文字
-    backText: {
-      type: [Number, String],
-      default: 1
-    },
-    // flipping duration, please be consistent with the CSS animation-duration value.
-    // 翻牌动画时间,与CSS中设置的animation-duration保持一致
-    duration: {
-      type: Number,
-      default: 600
-    }
-  },
-  methods: {
-    _textClass(number) {
-      return 'number' + number
-    },
-    _flip(type, front, back) {
-      // 如果处于翻转中,则不执行
-      // if (this.isFlipping) {
-      //   return false
-      // }
-      console.log(type, front, back,'=====>')
-      this.frontTextFromData = front
-      this.backTextFromData = back
-      // 根据传递过来的type设置翻转方向
-      this.flipType = type
-      // 设置翻转状态为true
-      this.isFlipping = true
-      setTimeout(() => {
-        // 设置翻转状态为false
-        this.isFlipping = false
-        this.frontTextFromData = back
-      }, this.duration)
-    },
-    // 下翻牌
-    flipDown(front, back) {
-      this._flip('down', front, back)
-    },
-    // 上翻牌
-    flipUp(front, back) {
-      this._flip('up', front, back)
-    },
-    // 设置前牌文字
-    setFront(text) {
-        this.frontTextFromData = text
-    },
-    // 设置后牌文字
-    setBack(text) {
-        this.backTextFromData = text
-    }
-  },
-  created() {
-      this.frontTextFromData = this.frontText
-      this.backTextFromData = this.backText
-  }
-}
-</script>
-
-<style>
-.M-Flipper {
-  display: inline-block;
-  position: relative;
-  width: 75px;
-  height: 150px;
-  line-height: 150px;
-  /* border: solid 1px #000; */
-  background: #fff;
-  font-size: 128px;
-  color: #131415;
-  text-align: center;
-  font-family: 'DINA';
-
-}
-.M-Flipper:nth-of-type(2n){
-  text-align: left;
-  margin-left: 5px;
-}
-.M-Flipper:nth-of-type(odd){
-  text-align: right;
-  margin-right: 5px;
-}
-/* @media screen and (min-width: 375px) and (max-width: 768px){
-   .M-Flipper {
-       width: 35px;
-      font-size: 40px;
-   }
-}
-
-@media screen and (max-width: 320px){
-   .M-Flipper {
-       width: 25px;
-      font-size: 40px;
-   }
-}
-
-@media screen and (min-width: 320px) and (max-width: 375px){
-   .M-Flipper {
-      width: 27px;
-      font-size: 40px;
-   }
-} */
-
-.M-Flipper .digital:before,
-.M-Flipper .digital:after {
-  content: '';
-  position: absolute;
-  left: 0;
-  right: 0;
-  background: #fff;
-  overflow: hidden;
-  box-sizing: border-box;
-}
-
-.M-Flipper .digital:before {
-  top: 0;
-  bottom: 50%;
-  border-radius: 10px 10px 0 0;
-  /* border-bottom: solid 2px #fff; */
-}
-
-.M-Flipper .digital:after {
-  top: 50%;
-  bottom: 0;
-  border-radius: 0 0 10px 10px;
-  line-height: 0;
-}
-
-/*向下翻*/
-.M-Flipper.down .front:before {
-  z-index: 3;
-}
-
-.M-Flipper.down .back:after {
-  z-index: 2;
-  transform-origin: 50% 0%;
-  transform: perspective(300px) rotateX(180deg);
-}
-
-.M-Flipper.down .front:after,
-.M-Flipper.down .back:before {
-  z-index: 1;
-}
-
-.M-Flipper.down.go .front:before {
-  transform-origin: 50% 100%;
-  animation: frontFlipDown 0.6s ease-in-out both;
-  /* box-shadow: 0 0px 6px rgba(255, 255, 255, 0.3); */
-  backface-visibility: hidden;
-}
-
-.M-Flipper.down.go .back:after {
-  animation: backFlipDown 0.6s ease-in-out both;
-}
-
-/*向上翻*/
-.M-Flipper.up .front:after {
-  z-index: 3;
-}
-
-.M-Flipper.up .back:before {
-  z-index: 2;
-  transform-origin: 50% 100%;
-  transform: perspective(300px) rotateX(-180deg);
-}
-
-.M-Flipper.up .front:before,
-.M-Flipper.up .back:after {
-  z-index: 1;
-}
-
-.M-Flipper.up.go .front:after {
-  transform-origin: 50% 0;
-  animation: frontFlipUp 0.6s ease-in-out both;
-  box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
-  backface-visibility: hidden;
-}
-
-.M-Flipper.up.go .back:before {
-  animation: backFlipUp 0.6s ease-in-out both;
-}
-
-@keyframes frontFlipDown {
-  0% {
-    transform: perspective(300px) rotateX(0deg);
-  }
-
-  100% {
-    transform: perspective(300px) rotateX(-180deg);
-  }
-}
-
-@keyframes backFlipDown {
-  0% {
-    transform: perspective(300px) rotateX(180deg);
-  }
-
-  100% {
-    transform: perspective(300px) rotateX(0deg);
-  }
-}
-
-@keyframes frontFlipUp {
-  0% {
-    transform: perspective(300px) rotateX(0deg);
-  }
-
-  100% {
-    transform: perspective(300px) rotateX(180deg);
-  }
-}
-
-@keyframes backFlipUp {
-  0% {
-    transform: perspective(300px) rotateX(-180deg);
-  }
-
-  100% {
-    transform: perspective(300px) rotateX(0deg);
-  }
-}
-
-.M-Flipper .number0:before,
-.M-Flipper .number0:after {
-  content: '0';
-}
-
-.M-Flipper .number1:before,
-.M-Flipper .number1:after {
-  content: '1';
-}
-
-.M-Flipper .number2:before,
-.M-Flipper .number2:after {
-  content: '2';
-}
-
-.M-Flipper .number3:before,
-.M-Flipper .number3:after {
-  content: '3';
-}
-
-.M-Flipper .number4:before,
-.M-Flipper .number4:after {
-  content: '4';
-}
-
-.M-Flipper .number5:before,
-.M-Flipper .number5:after {
-  content: '5';
-}
-
-.M-Flipper .number6:before,
-.M-Flipper .number6:after {
-  content: '6';
-}
-
-.M-Flipper .number7:before,
-.M-Flipper .number7:after {
-  content: '7';
-}
-
-.M-Flipper .number8:before,
-.M-Flipper .number8:after {
-  content: '8';
-}
-
-.M-Flipper .number9:before,
-.M-Flipper .number9:after {
-  content: '9';
-}
-</style>
+<template>
+  <div class="M-Flipper" :class="[flipType, { go: isFlipping }]">
+    <div class="digital front" :class="_textClass(frontTextFromData)"></div>
+    <div class="digital back" :class="_textClass(backTextFromData)"></div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'FlipClock',
+  data() {
+    return {
+      isFlipping: false,
+      flipType: 'down',
+      frontTextFromData: 0,
+      backTextFromData: 1
+    };
+  },
+  props: {
+    // front paper text
+    // 前牌文字
+    frontText: {
+      type: [Number, String],
+      default: 0
+    },
+    // back paper text
+    // 后牌文字
+    backText: {
+      type: [Number, String],
+      default: 1
+    },
+    // flipping duration, please be consistent with the CSS animation-duration value.
+    // 翻牌动画时间,与CSS中设置的animation-duration保持一致
+    duration: {
+      type: Number,
+      default: 600
+    }
+  },
+  methods: {
+    _textClass(number) {
+      return 'number' + number;
+    },
+    _flip(type, front, back) {
+      // 如果处于翻转中,则不执行
+      // if (this.isFlipping) {
+      //   return false
+      // }
+      console.log(type, front, back, '=====>');
+      this.frontTextFromData = front;
+      this.backTextFromData = back;
+      // 根据传递过来的type设置翻转方向
+      this.flipType = type;
+      // 设置翻转状态为true
+      this.isFlipping = true;
+      setTimeout(() => {
+        // 设置翻转状态为false
+        this.isFlipping = false;
+        this.frontTextFromData = back;
+      }, this.duration);
+    },
+    // 下翻牌
+    flipDown(front, back) {
+      this._flip('down', front, back);
+    },
+    // 上翻牌
+    flipUp(front, back) {
+      this._flip('up', front, back);
+    },
+    // 设置前牌文字
+    setFront(text) {
+      this.frontTextFromData = text;
+    },
+    // 设置后牌文字
+    setBack(text) {
+      this.backTextFromData = text;
+    }
+  },
+  created() {
+    this.frontTextFromData = this.frontText;
+    this.backTextFromData = this.backText;
+  }
+};
+</script>
+
+<style>
+.M-Flipper {
+  display: inline-block;
+  position: relative;
+  width: 75px;
+  height: 150px;
+  line-height: 150px;
+  /* border: solid 1px #000; */
+  background: #fff;
+  font-size: 128px;
+  color: #131415;
+  text-align: center;
+  font-family: 'DINA';
+}
+.M-Flipper:nth-of-type(2n) {
+  text-align: left;
+  margin-left: 5px;
+}
+.M-Flipper:nth-of-type(odd) {
+  text-align: right;
+  margin-right: 5px;
+}
+/* @media screen and (min-width: 375px) and (max-width: 768px){
+   .M-Flipper {
+       width: 35px;
+      font-size: 40px;
+   }
+}
+
+@media screen and (max-width: 320px){
+   .M-Flipper {
+       width: 25px;
+      font-size: 40px;
+   }
+}
+
+@media screen and (min-width: 320px) and (max-width: 375px){
+   .M-Flipper {
+      width: 27px;
+      font-size: 40px;
+   }
+} */
+
+.M-Flipper .digital:before,
+.M-Flipper .digital:after {
+  content: '';
+  position: absolute;
+  left: 0;
+  right: 0;
+  background: #fff;
+  overflow: hidden;
+  box-sizing: border-box;
+}
+
+.M-Flipper .digital:before {
+  top: 0;
+  bottom: 50%;
+  border-radius: 10px 10px 0 0;
+  /* border-bottom: solid 2px #fff; */
+}
+
+.M-Flipper .digital:after {
+  top: 50%;
+  bottom: 0;
+  border-radius: 0 0 10px 10px;
+  line-height: 0;
+}
+
+/*向下翻*/
+.M-Flipper.down .front:before {
+  z-index: 3;
+}
+
+.M-Flipper.down .back:after {
+  z-index: 2;
+  transform-origin: 50% 0%;
+  transform: perspective(300px) rotateX(180deg);
+}
+
+.M-Flipper.down .front:after,
+.M-Flipper.down .back:before {
+  z-index: 1;
+}
+
+.M-Flipper.down.go .front:before {
+  transform-origin: 50% 100%;
+  animation: frontFlipDown 0.6s ease-in-out both;
+  /* box-shadow: 0 0px 6px rgba(255, 255, 255, 0.3); */
+  backface-visibility: hidden;
+}
+
+.M-Flipper.down.go .back:after {
+  animation: backFlipDown 0.6s ease-in-out both;
+}
+
+/*向上翻*/
+.M-Flipper.up .front:after {
+  z-index: 3;
+}
+
+.M-Flipper.up .back:before {
+  z-index: 2;
+  transform-origin: 50% 100%;
+  transform: perspective(300px) rotateX(-180deg);
+}
+
+.M-Flipper.up .front:before,
+.M-Flipper.up .back:after {
+  z-index: 1;
+}
+
+.M-Flipper.up.go .front:after {
+  transform-origin: 50% 0;
+  animation: frontFlipUp 0.6s ease-in-out both;
+  box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
+  backface-visibility: hidden;
+}
+
+.M-Flipper.up.go .back:before {
+  animation: backFlipUp 0.6s ease-in-out both;
+}
+
+@keyframes frontFlipDown {
+  0% {
+    transform: perspective(300px) rotateX(0deg);
+  }
+
+  100% {
+    transform: perspective(300px) rotateX(-180deg);
+  }
+}
+
+@keyframes backFlipDown {
+  0% {
+    transform: perspective(300px) rotateX(180deg);
+  }
+
+  100% {
+    transform: perspective(300px) rotateX(0deg);
+  }
+}
+
+@keyframes frontFlipUp {
+  0% {
+    transform: perspective(300px) rotateX(0deg);
+  }
+
+  100% {
+    transform: perspective(300px) rotateX(180deg);
+  }
+}
+
+@keyframes backFlipUp {
+  0% {
+    transform: perspective(300px) rotateX(-180deg);
+  }
+
+  100% {
+    transform: perspective(300px) rotateX(0deg);
+  }
+}
+
+.M-Flipper .number0:before,
+.M-Flipper .number0:after {
+  content: '0';
+}
+
+.M-Flipper .number1:before,
+.M-Flipper .number1:after {
+  content: '1';
+}
+
+.M-Flipper .number2:before,
+.M-Flipper .number2:after {
+  content: '2';
+}
+
+.M-Flipper .number3:before,
+.M-Flipper .number3:after {
+  content: '3';
+}
+
+.M-Flipper .number4:before,
+.M-Flipper .number4:after {
+  content: '4';
+}
+
+.M-Flipper .number5:before,
+.M-Flipper .number5:after {
+  content: '5';
+}
+
+.M-Flipper .number6:before,
+.M-Flipper .number6:after {
+  content: '6';
+}
+
+.M-Flipper .number7:before,
+.M-Flipper .number7:after {
+  content: '7';
+}
+
+.M-Flipper .number8:before,
+.M-Flipper .number8:after {
+  content: '8';
+}
+
+.M-Flipper .number9:before,
+.M-Flipper .number9:after {
+  content: '9';
+}
+</style>

+ 10 - 10
src/components/upload-file/api.ts

@@ -1,10 +1,10 @@
-import request from '@/utils/request';
-
-/**
- * @description: 获取key
- */
-export const policy = (params: object) => {
-  return request.post('/edu-app/open/getUploadSign', {
-    data: params
-  });
-};
+import request from '@/utils/request';
+
+/**
+ * @description: 获取key
+ */
+export const policy = (params: object) => {
+  return request.post('/edu-app/open/getUploadSign', {
+    data: params
+  });
+};

+ 172 - 172
src/components/upload-file/copper.tsx

@@ -1,172 +1,172 @@
-import { NButton, NGi, NGrid, NSpace } from 'naive-ui';
-import { defineComponent, nextTick, reactive, ref } from 'vue';
-import Cropper from 'cropperjs';
-import 'cropperjs/dist/cropper.css';
-
-export default defineComponent({
-  name: 'copper-image',
-  emits: ['close', 'cropperNo', 'cropperOk'],
-  setup(props, { emit, expose }) {
-    const state = reactive({
-      visible: false,
-      img: null,
-      confirmLoading: false,
-      options: {
-        img: '', //裁剪图片的地址
-        autoCrop: true, //是否默认生成截图框
-        autoCropWidth: 180, //默认生成截图框宽度
-        autoCropHeight: 180, //默认生成截图框高度
-        fixedBox: true, //是否固定截图框大小 不允许改变
-        full: false,
-        enlarge: 1, // 是否按照截图框比例输出 默认为1
-        previewsCircle: true, //预览图是否是原圆形
-        centerBox: true,
-        outputType: 'png',
-        title: '修改头像',
-        name: null // 文件名称
-      },
-      previews: {},
-      url: {
-        upload: '/sys/common/saveToImgByStr'
-      },
-      myCropper: null as any
-    });
-    const imgCropper = ref();
-
-    const edit = (record: any) => {
-      const { options } = state;
-      state.visible = true;
-      state.options = Object.assign({}, options, record);
-      nextTick(() => {
-        initImgCropper();
-      });
-    };
-
-    const initImgCropper = () => {
-      state.myCropper = new Cropper(imgCropper.value, {
-        viewMode: 1, //定义裁剪器的视图模式。如果将viewMode设置为0,则裁剪框可以延伸到画布外部,而值为1、2或3将限制裁剪框的大小为画布的大小。viewMode为2或3会将画布限制为容器。请注意,如果画布和容器的比例相同,则2和3之间没有差别。
-        dragMode: 'move', //定义的拖动模式裁剪器.canvas和容器一样,2和3没有区别。move:移动画布 crop:创建新的裁剪框(默认) none:什么也不做
-        //定义裁剪框的固定纵横比。默认情况下,裁剪框为自由比率。
-        aspectRatio: state.options.autoCropWidth / state.options.autoCropHeight,
-        initialAspectRatio: 1,
-        autoCropArea: 1, //定义0到1之间的fA编号。定义自动裁剪区域大小(百分比)。默认0.8
-        cropBoxMovable: true, //允许通过拖动移动裁剪框。默认true
-        cropBoxResizable: false, //以通过拖动来调整裁剪框的大小 默认true
-        background: true, //显示容器的网格背景
-        movable: true, //移动图像
-        modal: true,
-        preview: '.before'
-      });
-    };
-
-    const onOperation = (type: string) => {
-      switch (type) {
-        case 'left':
-          state.myCropper.rotate(90);
-          break;
-        case 'right':
-          state.myCropper.rotate(-90);
-          break;
-        case 'zoomIn':
-          state.myCropper.zoom(0.1);
-          break;
-        case 'zoomOut':
-          state.myCropper.zoom(-0.1);
-          break;
-      }
-    };
-
-    const onSubmit = () => {
-      state.confirmLoading = true;
-      state.myCropper
-        .getCroppedCanvas({
-          imageSmoothingQuality: 'high'
-        })
-        .toBlob((blob: any) => {
-          console.log(blob, '1212');
-          emit('cropperOk', blob);
-          state.confirmLoading = false;
-        });
-    };
-
-    expose({
-      edit
-    });
-    return () => (
-      <div>
-        <NGrid cols={2} xGap={24} style={{ paddingTop: '12px' }}>
-          <NGi>
-            <div style="width: 100%; height: 300px">
-              {state.options?.img && (
-                <img ref={imgCropper} src={state.options?.img} alt="" />
-              )}
-            </div>
-
-            <NSpace justify="center" style={{ paddingTop: '12px' }}>
-              <NButton
-                type="primary"
-                size="small"
-                onClick={() => onOperation('left')}>
-                逆时针旋转
-              </NButton>
-              <NButton
-                type="primary"
-                size="small"
-                onClick={() => onOperation('right')}>
-                顺时针旋转
-              </NButton>
-              <NButton
-                type="primary"
-                size="small"
-                onClick={() => onOperation('zoomIn')}>
-                放大
-              </NButton>
-              <NButton
-                type="primary"
-                size="small"
-                onClick={() => onOperation('zoomOut')}>
-                缩小
-              </NButton>
-            </NSpace>
-          </NGi>
-          <NGi>
-            {/* font-weight: 600; padding-bottom: 8px; display: inline-block; */}
-            <span
-              style={{
-                fontSize: '15px',
-                fontWeight: 600,
-                paddingBottom: '8px',
-                display: 'inline-block'
-              }}>
-              预览图片
-            </span>
-            <div
-              class="before"
-              style={{
-                width: state.options.autoCropWidth + 'px',
-                height: state.options.autoCropHeight + 'px',
-                overflow: 'hidden'
-              }}></div>
-          </NGi>
-        </NGrid>
-        <NSpace justify="end">
-          <NButton
-            type="default"
-            onClick={() => {
-              state.confirmLoading = false;
-              emit('close');
-              emit('cropperNo');
-            }}>
-            取消
-          </NButton>
-          <NButton
-            type="primary"
-            loading={state.confirmLoading}
-            onClick={onSubmit}>
-            确认
-          </NButton>
-        </NSpace>
-      </div>
-    );
-  }
-});
+import { NButton, NGi, NGrid, NSpace } from 'naive-ui';
+import { defineComponent, nextTick, reactive, ref } from 'vue';
+import Cropper from 'cropperjs';
+import 'cropperjs/dist/cropper.css';
+
+export default defineComponent({
+  name: 'copper-image',
+  emits: ['close', 'cropperNo', 'cropperOk'],
+  setup(props, { emit, expose }) {
+    const state = reactive({
+      visible: false,
+      img: null,
+      confirmLoading: false,
+      options: {
+        img: '', //裁剪图片的地址
+        autoCrop: true, //是否默认生成截图框
+        autoCropWidth: 180, //默认生成截图框宽度
+        autoCropHeight: 180, //默认生成截图框高度
+        fixedBox: true, //是否固定截图框大小 不允许改变
+        full: false,
+        enlarge: 1, // 是否按照截图框比例输出 默认为1
+        previewsCircle: true, //预览图是否是原圆形
+        centerBox: true,
+        outputType: 'png',
+        title: '修改头像',
+        name: null // 文件名称
+      },
+      previews: {},
+      url: {
+        upload: '/sys/common/saveToImgByStr'
+      },
+      myCropper: null as any
+    });
+    const imgCropper = ref();
+
+    const edit = (record: any) => {
+      const { options } = state;
+      state.visible = true;
+      state.options = Object.assign({}, options, record);
+      nextTick(() => {
+        initImgCropper();
+      });
+    };
+
+    const initImgCropper = () => {
+      state.myCropper = new Cropper(imgCropper.value, {
+        viewMode: 1, //定义裁剪器的视图模式。如果将viewMode设置为0,则裁剪框可以延伸到画布外部,而值为1、2或3将限制裁剪框的大小为画布的大小。viewMode为2或3会将画布限制为容器。请注意,如果画布和容器的比例相同,则2和3之间没有差别。
+        dragMode: 'move', //定义的拖动模式裁剪器.canvas和容器一样,2和3没有区别。move:移动画布 crop:创建新的裁剪框(默认) none:什么也不做
+        //定义裁剪框的固定纵横比。默认情况下,裁剪框为自由比率。
+        aspectRatio: state.options.autoCropWidth / state.options.autoCropHeight,
+        initialAspectRatio: 1,
+        autoCropArea: 1, //定义0到1之间的fA编号。定义自动裁剪区域大小(百分比)。默认0.8
+        cropBoxMovable: true, //允许通过拖动移动裁剪框。默认true
+        cropBoxResizable: false, //以通过拖动来调整裁剪框的大小 默认true
+        background: true, //显示容器的网格背景
+        movable: true, //移动图像
+        modal: true,
+        preview: '.before'
+      });
+    };
+
+    const onOperation = (type: string) => {
+      switch (type) {
+        case 'left':
+          state.myCropper.rotate(90);
+          break;
+        case 'right':
+          state.myCropper.rotate(-90);
+          break;
+        case 'zoomIn':
+          state.myCropper.zoom(0.1);
+          break;
+        case 'zoomOut':
+          state.myCropper.zoom(-0.1);
+          break;
+      }
+    };
+
+    const onSubmit = () => {
+      state.confirmLoading = true;
+      state.myCropper
+        .getCroppedCanvas({
+          imageSmoothingQuality: 'high'
+        })
+        .toBlob((blob: any) => {
+          console.log(blob, '1212');
+          emit('cropperOk', blob);
+          state.confirmLoading = false;
+        });
+    };
+
+    expose({
+      edit
+    });
+    return () => (
+      <div>
+        <NGrid cols={2} xGap={24} style={{ paddingTop: '12px' }}>
+          <NGi>
+            <div style="width: 100%; height: 300px">
+              {state.options?.img && (
+                <img ref={imgCropper} src={state.options?.img} alt="" />
+              )}
+            </div>
+
+            <NSpace justify="center" style={{ paddingTop: '12px' }}>
+              <NButton
+                type="primary"
+                size="small"
+                onClick={() => onOperation('left')}>
+                逆时针旋转
+              </NButton>
+              <NButton
+                type="primary"
+                size="small"
+                onClick={() => onOperation('right')}>
+                顺时针旋转
+              </NButton>
+              <NButton
+                type="primary"
+                size="small"
+                onClick={() => onOperation('zoomIn')}>
+                放大
+              </NButton>
+              <NButton
+                type="primary"
+                size="small"
+                onClick={() => onOperation('zoomOut')}>
+                缩小
+              </NButton>
+            </NSpace>
+          </NGi>
+          <NGi>
+            {/* font-weight: 600; padding-bottom: 8px; display: inline-block; */}
+            <span
+              style={{
+                fontSize: '15px',
+                fontWeight: 600,
+                paddingBottom: '8px',
+                display: 'inline-block'
+              }}>
+              预览图片
+            </span>
+            <div
+              class="before"
+              style={{
+                width: state.options.autoCropWidth + 'px',
+                height: state.options.autoCropHeight + 'px',
+                overflow: 'hidden'
+              }}></div>
+          </NGi>
+        </NGrid>
+        <NSpace justify="end">
+          <NButton
+            type="default"
+            onClick={() => {
+              state.confirmLoading = false;
+              emit('close');
+              emit('cropperNo');
+            }}>
+            取消
+          </NButton>
+          <NButton
+            type="primary"
+            loading={state.confirmLoading}
+            onClick={onSubmit}>
+            确认
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 9 - 9
src/custom-plugins/guide-page/images/index.ts

@@ -1,9 +1,9 @@
-const modules = import.meta.glob('../images/**', {
-  import: 'default',
-  eager: true
-});
-export const getImage = (name: string): string => {
-  // console.log("🚀 ~ modules", modules[`./${name}`])
-  const src: any = modules[`./${name}`] || '';
-  return src;
-};
+const modules = import.meta.glob('../images/**', {
+  import: 'default',
+  eager: true
+});
+export const getImage = (name: string): string => {
+  // console.log("🚀 ~ modules", modules[`./${name}`])
+  const src: any = modules[`./${name}`] || '';
+  return src;
+};

+ 28 - 28
src/enums/breakpointEnum.ts

@@ -1,28 +1,28 @@
-export enum sizeEnum {
-  XS = 'XS',
-  SM = 'SM',
-  MD = 'MD',
-  LG = 'LG',
-  XL = 'XL',
-  XXL = 'XXL'
-}
-
-export enum screenEnum {
-  XS = 480,
-  SM = 576,
-  MD = 768,
-  LG = 992,
-  XL = 1200,
-  XXL = 1600
-}
-
-const screenMap = new Map<sizeEnum, number>();
-
-screenMap.set(sizeEnum.XS, screenEnum.XS);
-screenMap.set(sizeEnum.SM, screenEnum.SM);
-screenMap.set(sizeEnum.MD, screenEnum.MD);
-screenMap.set(sizeEnum.LG, screenEnum.LG);
-screenMap.set(sizeEnum.XL, screenEnum.XL);
-screenMap.set(sizeEnum.XXL, screenEnum.XXL);
-
-export { screenMap };
+export enum sizeEnum {
+  XS = 'XS',
+  SM = 'SM',
+  MD = 'MD',
+  LG = 'LG',
+  XL = 'XL',
+  XXL = 'XXL'
+}
+
+export enum screenEnum {
+  XS = 480,
+  SM = 576,
+  MD = 768,
+  LG = 992,
+  XL = 1200,
+  XXL = 1600
+}
+
+const screenMap = new Map<sizeEnum, number>();
+
+screenMap.set(sizeEnum.XS, screenEnum.XS);
+screenMap.set(sizeEnum.SM, screenEnum.SM);
+screenMap.set(sizeEnum.MD, screenEnum.MD);
+screenMap.set(sizeEnum.LG, screenEnum.LG);
+screenMap.set(sizeEnum.XL, screenEnum.XL);
+screenMap.set(sizeEnum.XXL, screenEnum.XXL);
+
+export { screenMap };

+ 34 - 34
src/enums/httpEnum.ts

@@ -1,34 +1,34 @@
-/**
- * @description: 请求结果集
- */
-export enum ResultEnum {
-  SUCCESS = 200,
-  ERROR = -1,
-  TIMEOUT = 10042,
-  TYPE = 'success'
-}
-
-/**
- * @description: 请求方法
- */
-export enum RequestEnum {
-  GET = 'GET',
-  POST = 'POST',
-  PATCH = 'PATCH',
-  PUT = 'PUT',
-  DELETE = 'DELETE'
-}
-
-/**
- * @description:  常用的contentTyp类型
- */
-export enum ContentTypeEnum {
-  // json
-  JSON = 'application/json;charset=UTF-8',
-  // json
-  TEXT = 'text/plain;charset=UTF-8',
-  // form-data 一般配合qs
-  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
-  // form-data  上传
-  FORM_DATA = 'multipart/form-data;charset=UTF-8'
-}
+/**
+ * @description: 请求结果集
+ */
+export enum ResultEnum {
+  SUCCESS = 200,
+  ERROR = -1,
+  TIMEOUT = 10042,
+  TYPE = 'success'
+}
+
+/**
+ * @description: 请求方法
+ */
+export enum RequestEnum {
+  GET = 'GET',
+  POST = 'POST',
+  PATCH = 'PATCH',
+  PUT = 'PUT',
+  DELETE = 'DELETE'
+}
+
+/**
+ * @description:  常用的contentTyp类型
+ */
+export enum ContentTypeEnum {
+  // json
+  JSON = 'application/json;charset=UTF-8',
+  // json
+  TEXT = 'text/plain;charset=UTF-8',
+  // form-data 一般配合qs
+  FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
+  // form-data  上传
+  FORM_DATA = 'multipart/form-data;charset=UTF-8'
+}

+ 45 - 45
src/enums/pageEnum.ts

@@ -1,45 +1,45 @@
-export enum PageEnum {
-  // 登录
-  BASE_LOGIN = '/login',
-  BASE_LOGIN_NAME = 'Login',
-  //重定向
-  REDIRECT = '/redirect',
-  // REDIRECT_NAME = 'Redirect',
-  // 首页
-  BASE_HOME = '/home',
-  // //首页跳转默认路由
-  // BASE_HOME_REDIRECT = '/dashboard/console',
-  // // 错误
-  // ERROR_PAGE_NAME = 'ErrorPage'
-  SONG_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png',
-  // ppt封面
-  PPT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/12/1701931810284.png',
-  /** 听音练习 */
-  // LISTEN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
-  /** 节奏练习 */
-  RHYTHM_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
-  /** 乐理知识 */
-  THEORY_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093160d479afe.png',
-  /** 曲目 */
-  MUSIC_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009315eedcdeed.png',
-  /** 乐器 */
-  INSTRUMENT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093153448b2cd.png',
-  /** 音乐家 */
-  MUSICIAN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009316fbd65d39.png'
-}
-
-// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
-export const NaturalType: { [_: string]: string } = {
-  IMG: '图片',
-  VIDEO: '视频',
-  SONG: '音频',
-  MUSIC: '乐谱'
-};
-
-export enum NaturalTypeEnum {
-  IMG = 'IMG',
-  VIDEO = 'VIDEO',
-  MUSIC = 'MUSIC',
-  SONG = 'SONG',
-  PPT = 'PPT'
-}
+export enum PageEnum {
+  // 登录
+  BASE_LOGIN = '/login',
+  BASE_LOGIN_NAME = 'Login',
+  //重定向
+  REDIRECT = '/redirect',
+  // REDIRECT_NAME = 'Redirect',
+  // 首页
+  BASE_HOME = '/home',
+  // //首页跳转默认路由
+  // BASE_HOME_REDIRECT = '/dashboard/console',
+  // // 错误
+  // ERROR_PAGE_NAME = 'ErrorPage'
+  SONG_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1698420034679a22d3f7a.png',
+  // ppt封面
+  PPT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/12/1701931810284.png',
+  /** 听音练习 */
+  // LISTEN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
+  /** 节奏练习 */
+  RHYTHM_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/171013700931689a322a6.png',
+  /** 乐理知识 */
+  THEORY_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093160d479afe.png',
+  /** 曲目 */
+  MUSIC_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009315eedcdeed.png',
+  /** 乐器 */
+  INSTRUMENT_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/17101370093153448b2cd.png',
+  /** 音乐家 */
+  MUSICIAN_DEFAULT_COVER = 'https://oss.dayaedu.com/ktqy/1710137009316fbd65d39.png'
+}
+
+// LISTEN:听音,RHYTHM:节奏,THEORY:乐理知识,MUSIC:曲目 INSTRUMENT:乐器 MUSICIAN:音乐家)
+export const NaturalType: { [_: string]: string } = {
+  IMG: '图片',
+  VIDEO: '视频',
+  SONG: '音频',
+  MUSIC: '乐谱'
+};
+
+export enum NaturalTypeEnum {
+  IMG = 'IMG',
+  VIDEO = 'VIDEO',
+  MUSIC = 'MUSIC',
+  SONG = 'SONG',
+  PPT = 'PPT'
+}

+ 16 - 16
src/helpers/deep-clone.ts

@@ -1,16 +1,16 @@
-const deepClone = (obj: any) => {
-  if (obj === null) return null;
-  const clone = Object.assign({}, obj);
-  Object.keys(clone).forEach(
-    key =>
-      (clone[key] =
-        typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
-  );
-  if (Array.isArray(obj)) {
-    clone.length = obj.length;
-    return Array.from(clone);
-  }
-  return clone;
-};
-
-export default deepClone;
+const deepClone = (obj: any) => {
+  if (obj === null) return null;
+  const clone = Object.assign({}, obj);
+  Object.keys(clone).forEach(
+    key =>
+      (clone[key] =
+        typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
+  );
+  if (Array.isArray(obj)) {
+    clone.length = obj.length;
+    return Array.from(clone);
+  }
+  return clone;
+};
+
+export default deepClone;

+ 116 - 116
src/helpers/native-message.ts

@@ -1,116 +1,116 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { browser, getRandomKey } from '@/helpers/utils';
-
-export interface IPostMessage {
-  api: string;
-  content?: any;
-}
-
-/**
- * 劫持postMessage
- */
-
-const originalPostMessage = window.postMessage;
-
-window.postMessage = (message: IPostMessage) => {
-  // console.log('通过劫持', message)
-  originalPostMessage(message, '*');
-};
-
-/**
- *
- * 目前已支持API
- *
- * openWebView
- *
- */
-
-type CallBack = (evt?: IPostMessage) => void;
-
-// eslint-disable-next-line @typescript-eslint/no-empty-function
-const loop = () => {};
-
-const calls: { [key: string]: CallBack | CallBack[] } = {};
-
-const browserInfo = browser();
-
-if (browserInfo.isApp) {
-  window.addEventListener('message', evt => {
-    try {
-      console.log('app交互接受:', evt.data);
-      const data = evt.data
-        ? typeof evt.data === 'object'
-          ? evt.data
-          : JSON.parse(evt.data)
-        : {};
-      const uuid = data.content?.uuid || data.uuid;
-      console.log(uuid, data.content, 'uuid');
-      try {
-        if (data.content) {
-          data.content = JSON.parse(data.content);
-        }
-      } catch (error) {
-        //
-      }
-      if (data?.content?.uuid) {
-        // console.log('data', data)
-      }
-      if (!uuid) {
-        const keys = Object.keys(calls).filter(
-          key => key.indexOf(data.api) === 0
-        );
-        // console.log(keys, 'keys')
-        // console.log(data, 'data')
-        for (const key of keys) {
-          const callback = calls[key] || loop;
-          typeof callback === 'function' && callback(data);
-        }
-        return;
-      }
-      const callId = data.content?.uuid || data.uuid || data.api + data.uuid;
-      const callback = calls[callId] || loop;
-      // console.log(data, 'data')
-      typeof callback === 'function' && callback(data);
-    } catch (error) {
-      console.error('通信消息解析错误', error);
-    }
-  });
-}
-
-const instance: any =
-  (window as any).DAYA || (window as any).webkit?.messageHandlers?.DAYA;
-
-export const postMessage = (data: IPostMessage, callback?: CallBack) => {
-  if (browserInfo.isApp) {
-    const uuid = getRandomKey();
-    calls[uuid] = callback || loop;
-    data.content = data.content ? { ...data.content, uuid } : { uuid };
-    console.log('app交互发送:', data);
-    instance.postMessage(JSON.stringify(data));
-  }
-};
-
-export const listenerMessage = (api: string, callback: CallBack) => {
-  if (browserInfo.isApp) {
-    const uuid = api + getRandomKey();
-    calls[uuid] = callback || loop;
-  }
-};
-
-export const removeListenerMessage = (api: string, callback: CallBack) => {
-  if (browserInfo.isApp) {
-    const uuid = api;
-    if (Array.isArray(calls[uuid])) {
-      const indexOf = (calls[uuid] as CallBack[]).indexOf(callback);
-      (calls[uuid] as CallBack[]).splice(indexOf, 1);
-    }
-  }
-};
-
-export const promisefiyPostMessage = (
-  data: IPostMessage
-): Promise<IPostMessage | undefined> => {
-  return new Promise(resolve => {
-    postMessage(data, res => resolve(res));
-  });
-};
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { browser, getRandomKey } from '@/helpers/utils';
+
+export interface IPostMessage {
+  api: string;
+  content?: any;
+}
+
+/**
+ * 劫持postMessage
+ */
+
+const originalPostMessage = window.postMessage;
+
+window.postMessage = (message: IPostMessage) => {
+  // console.log('通过劫持', message)
+  originalPostMessage(message, '*');
+};
+
+/**
+ *
+ * 目前已支持API
+ *
+ * openWebView
+ *
+ */
+
+type CallBack = (evt?: IPostMessage) => void;
+
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const loop = () => {};
+
+const calls: { [key: string]: CallBack | CallBack[] } = {};
+
+const browserInfo = browser();
+
+if (browserInfo.isApp) {
+  window.addEventListener('message', evt => {
+    try {
+      console.log('app交互接受:', evt.data);
+      const data = evt.data
+        ? typeof evt.data === 'object'
+          ? evt.data
+          : JSON.parse(evt.data)
+        : {};
+      const uuid = data.content?.uuid || data.uuid;
+      console.log(uuid, data.content, 'uuid');
+      try {
+        if (data.content) {
+          data.content = JSON.parse(data.content);
+        }
+      } catch (error) {
+        //
+      }
+      if (data?.content?.uuid) {
+        // console.log('data', data)
+      }
+      if (!uuid) {
+        const keys = Object.keys(calls).filter(
+          key => key.indexOf(data.api) === 0
+        );
+        // console.log(keys, 'keys')
+        // console.log(data, 'data')
+        for (const key of keys) {
+          const callback = calls[key] || loop;
+          typeof callback === 'function' && callback(data);
+        }
+        return;
+      }
+      const callId = data.content?.uuid || data.uuid || data.api + data.uuid;
+      const callback = calls[callId] || loop;
+      // console.log(data, 'data')
+      typeof callback === 'function' && callback(data);
+    } catch (error) {
+      console.error('通信消息解析错误', error);
+    }
+  });
+}
+
+const instance: any =
+  (window as any).DAYA || (window as any).webkit?.messageHandlers?.DAYA;
+
+export const postMessage = (data: IPostMessage, callback?: CallBack) => {
+  if (browserInfo.isApp) {
+    const uuid = getRandomKey();
+    calls[uuid] = callback || loop;
+    data.content = data.content ? { ...data.content, uuid } : { uuid };
+    console.log('app交互发送:', data);
+    instance.postMessage(JSON.stringify(data));
+  }
+};
+
+export const listenerMessage = (api: string, callback: CallBack) => {
+  if (browserInfo.isApp) {
+    const uuid = api + getRandomKey();
+    calls[uuid] = callback || loop;
+  }
+};
+
+export const removeListenerMessage = (api: string, callback: CallBack) => {
+  if (browserInfo.isApp) {
+    const uuid = api;
+    if (Array.isArray(calls[uuid])) {
+      const indexOf = (calls[uuid] as CallBack[]).indexOf(callback);
+      (calls[uuid] as CallBack[]).splice(indexOf, 1);
+    }
+  }
+};
+
+export const promisefiyPostMessage = (
+  data: IPostMessage
+): Promise<IPostMessage | undefined> => {
+  return new Promise(resolve => {
+    postMessage(data, res => resolve(res));
+  });
+};

+ 406 - 406
src/helpers/toolsValidate.ts

@@ -1,406 +1,406 @@
-/* eslint-disable no-useless-escape */
-/**
- * 验证百分比(不可以小数)
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyNumberPercentage(val: string): string {
-  // 匹配空格
-  let v = val.replace(/(^\s*)|(\s*$)/g, '');
-  // 只能是数字和小数点,不能是其他输入
-  v = v.replace(/[^\d]/g, '');
-  // 不能以0开始
-  v = v.replace(/^0/g, '');
-  // 数字超过100,赋值成最大值100
-  v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
-  // 返回结果
-  return v;
-}
-
-/**
- * 验证百分比(可以小数)
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyNumberPercentageFloat(val: string): string {
-  let v = verifyNumberIntegerAndFloat(val);
-  // 数字超过100,赋值成最大值100
-  v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
-  // 超过100之后不给再输入值
-  v = v.replace(/^100\.$/, '100');
-  // 返回结果
-  return v;
-}
-
-/**
- * 小数或整数(不可以负数)
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyNumberIntegerAndFloat(val: string) {
-  // 匹配空格
-  let v = val.replace(/(^\s*)|(\s*$)/g, '');
-  // 只能是数字和小数点,不能是其他输入
-  v = v.replace(/[^\d.]/g, '');
-  // 以0开始只能输入一个
-  v = v.replace(/^0{2}$/g, '0');
-  // 保证第一位只能是数字,不能是点
-  v = v.replace(/^\./g, '');
-  // 小数只能出现1位
-  v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.');
-  // 小数点后面保留2位
-  v = v.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
-  // 返回结果
-  return v;
-}
-
-/**
- * 正整数验证
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifiyNumberInteger(val: string) {
-  // 匹配空格
-  let v = val.replace(/(^\s*)|(\s*$)/g, '');
-  // 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
-  v = v.replace(/[\.]*/g, '');
-  // 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
-  v = v.replace(/(^0[\d]*)$/g, '0');
-  // 首位是0,只能出现一次
-  v = v.replace(/^0\d$/g, '0');
-  // 只匹配数字
-  v = v.replace(/[^\d]/g, '');
-  // 返回结果
-  return v;
-}
-
-/**
- * 检测正整数
- * @param val 当前值字符串
- * @returns 返回boolean
- */
-export function checkNumberInteger(val: string) {
-  const rep = /[\.]/;
-  return !!rep.test(val);
-}
-
-/**
- * 去掉中文及空格
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyCnAndSpace(val: string) {
-  // 匹配中文与空格
-  let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
-  // 匹配空格
-  v = v.replace(/(^\s*)|(\s*$)/g, '');
-  // 返回结果
-  return v;
-}
-
-/**
- * 去掉英文及空格
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyEnAndSpace(val: string) {
-  // 匹配英文与空格
-  let v = val.replace(/[a-zA-Z]+/g, '');
-  // 匹配空格
-  v = v.replace(/(^\s*)|(\s*$)/g, '');
-  // 返回结果
-  return v;
-}
-
-/**
- * 禁止输入空格
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyAndSpace(val: string) {
-  // 匹配空格
-  const v = val.replace(/(^\s*)|(\s*$)/g, '');
-  // 返回结果
-  return v;
-}
-
-/**
- * 金额用 `,` 区分开
- * @param val 当前值字符串
- * @returns 返回处理后的字符串
- */
-export function verifyNumberComma(val: string) {
-  // 调用小数或整数(不可以负数)方法
-  let v: any = verifyNumberIntegerAndFloat(val);
-  // 字符串转成数组
-  v = v.toString().split('.');
-  // \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
-  v[0] = v[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
-  // 数组转字符串
-  v = v.join('.');
-  // 返回结果
-  return v;
-}
-
-/**
- * 匹配文字变色(搜索时)
- * @param val 当前值字符串
- * @param text 要处理的字符串值
- * @param color 搜索到时字体高亮颜色
- * @returns 返回处理后的字符串
- */
-export function verifyTextColor(val: string, text = '', color = 'red') {
-  // 返回内容,添加颜色
-  const v = text.replace(
-    new RegExp(val, 'gi'),
-    `<span style='color: ${color}'>${val}</span>`
-  );
-  // 返回结果
-  return v;
-}
-
-/**
- * 数字转中文大写
- * @param val 当前值字符串
- * @param unit 默认:仟佰拾亿仟佰拾万仟佰拾元角分
- * @returns 返回处理后的字符串
- */
-export function verifyNumberCnUppercase(
-  val: any,
-  unit = '仟佰拾亿仟佰拾万仟佰拾元角分',
-  v = ''
-) {
-  // 当前内容字符串添加 2个0,为什么??
-  val += '00';
-  // 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
-  const lookup = val.indexOf('.');
-  // substring:不包含结束下标内容,substr:包含结束下标内容
-  if (lookup >= 0) val = val.substring(0, lookup) + val.substr(lookup + 1, 2);
-  // 根据内容 val 的长度,截取返回对应大写
-  unit = unit.substr(unit.length - val.length);
-  // 循环截取拼接大写
-  for (let i = 0; i < val.length; i++) {
-    v += '零壹贰叁肆伍陆柒捌玖'.substr(val.substr(i, 1), 1) + unit.substr(i, 1);
-  }
-  // 正则处理
-  v = v
-    .replace(/零角零分$/, '整')
-    .replace(/零[仟佰拾]/g, '零')
-    .replace(/零{2,}/g, '零')
-    .replace(/零([亿|万])/g, '$1')
-    .replace(/零+元/, '元')
-    .replace(/亿零{0,3}万/, '亿')
-    .replace(/^元/, '零元');
-  // 返回结果
-  return v;
-}
-
-/**
- * 手机号码
- * @param val 当前值字符串
- * @returns 返回 true: 手机号码正确
- */
-export function verifyPhone(val: string) {
-  // false: 手机号码不正确
-  if (
-    !/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(
-      val
-    )
-  )
-    return false;
-  // true: 手机号码正确
-  else return true;
-}
-
-/**
- * 国内电话号码
- * @param val 当前值字符串
- * @returns 返回 true: 国内电话号码正确
- */
-export function verifyTelPhone(val: string) {
-  // false: 国内电话号码不正确
-  if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
-  // true: 国内电话号码正确
-  else return true;
-}
-
-/**
- * 登录账号 (字母开头,允许5-16字节,允许字母数字下划线)
- * @param val 当前值字符串
- * @returns 返回 true: 登录账号正确
- */
-export function verifyAccount(val: string) {
-  // false: 登录账号不正确
-  if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
-  // true: 登录账号正确
-  else return true;
-}
-
-/**
- * 密码 (以字母开头,长度在6~16之间,只能包含字母、数字和下划线)
- * @param val 当前值字符串
- * @returns 返回 true: 密码正确
- */
-export function verifyPassword(val: string) {
-  // false: 密码不正确
-  if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
-  // true: 密码正确
-  else return true;
-}
-
-/**
- * 强密码 (字母+数字+特殊字符,长度在6-16之间)
- * @param val 当前值字符串
- * @returns 返回 true: 强密码正确
- */
-export function verifyPasswordPowerful(val: string) {
-  // false: 强密码不正确
-  if (
-    !/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
-      val
-    )
-  )
-    return false;
-  // true: 强密码正确
-  else return true;
-}
-
-/**
- * 密码强度
- * @param val 当前值字符串
- * @description 弱:纯数字,纯字母,纯特殊字符
- * @description 中:字母+数字,字母+特殊字符,数字+特殊字符
- * @description 强:字母+数字+特殊字符
- * @returns 返回处理后的字符串:弱、中、强
- */
-export function verifyPasswordStrength(val: string) {
-  let v = '';
-  // 弱:纯数字,纯字母,纯特殊字符
-  if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱';
-  // 中:字母+数字,字母+特殊字符,数字+特殊字符
-  if (
-    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
-      val
-    )
-  )
-    v = '中';
-  // 强:字母+数字+特殊字符
-  if (
-    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
-      val
-    )
-  )
-    v = '强';
-  // 返回结果
-  return v;
-}
-
-/**
- * IP地址
- * @param val 当前值字符串
- * @returns 返回 true: IP地址正确
- */
-export function verifyIPAddress(val: string) {
-  // false: IP地址不正确
-  if (
-    !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
-      val
-    )
-  )
-    return false;
-  // true: IP地址正确
-  else return true;
-}
-
-/**
- * 邮箱
- * @param val 当前值字符串
- * @returns 返回 true: 邮箱正确
- */
-export function verifyEmail(val: string) {
-  // false: 邮箱不正确
-  const reg =
-    /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
-  if (!reg.test(val)) return false;
-  // true: 邮箱正确
-  else return true;
-}
-
-/**
- * 身份证
- * @param val 当前值字符串
- * @returns 返回 true: 身份证正确
- */
-export function verifyIdCard(val: string) {
-  // false: 身份证不正确
-  if (
-    !/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(
-      val
-    )
-  )
-    return false;
-  // true: 身份证正确
-  else return true;
-}
-
-/**
- * 姓名
- * @param val 当前值字符串
- * @returns 返回 true: 姓名正确
- */
-export function verifyFullName(val: string) {
-  // false: 姓名不正确
-  if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val))
-    return false;
-  // true: 姓名正确
-  else return true;
-}
-
-/**
- * 邮政编码
- * @param val 当前值字符串
- * @returns 返回 true: 邮政编码正确
- */
-export function verifyPostalCode(val: string) {
-  // false: 邮政编码不正确
-  if (!/^[1-9][0-9]{5}$/.test(val)) return false;
-  // true: 邮政编码正确
-  else return true;
-}
-
-/**
- * url 处理
- * @param val 当前值字符串
- * @returns 返回 true: url 正确
- */
-export function verifyUrl(val: string) {
-  // false: url不正确
-  // !/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
-  //   val
-  // )
-  if (
-    !/^(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?$/.test(
-      val
-    )
-  )
-    return false;
-  // true: url正确
-  else return true;
-}
-
-/**
- * 车牌号
- * @param val 当前值字符串
- * @returns 返回 true:车牌号正确
- */
-export function verifyCarNum(val: string) {
-  // false: 车牌号不正确
-  if (
-    !/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(
-      val
-    )
-  )
-    return false;
-  // true:车牌号正确
-  else return true;
-}
+/* eslint-disable no-useless-escape */
+/**
+ * 验证百分比(不可以小数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberPercentage(val: string): string {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '');
+  // 只能是数字和小数点,不能是其他输入
+  v = v.replace(/[^\d]/g, '');
+  // 不能以0开始
+  v = v.replace(/^0/g, '');
+  // 数字超过100,赋值成最大值100
+  v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 验证百分比(可以小数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberPercentageFloat(val: string): string {
+  let v = verifyNumberIntegerAndFloat(val);
+  // 数字超过100,赋值成最大值100
+  v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
+  // 超过100之后不给再输入值
+  v = v.replace(/^100\.$/, '100');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 小数或整数(不可以负数)
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberIntegerAndFloat(val: string) {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '');
+  // 只能是数字和小数点,不能是其他输入
+  v = v.replace(/[^\d.]/g, '');
+  // 以0开始只能输入一个
+  v = v.replace(/^0{2}$/g, '0');
+  // 保证第一位只能是数字,不能是点
+  v = v.replace(/^\./g, '');
+  // 小数只能出现1位
+  v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.');
+  // 小数点后面保留2位
+  v = v.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 正整数验证
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifiyNumberInteger(val: string) {
+  // 匹配空格
+  let v = val.replace(/(^\s*)|(\s*$)/g, '');
+  // 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
+  v = v.replace(/[\.]*/g, '');
+  // 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
+  v = v.replace(/(^0[\d]*)$/g, '0');
+  // 首位是0,只能出现一次
+  v = v.replace(/^0\d$/g, '0');
+  // 只匹配数字
+  v = v.replace(/[^\d]/g, '');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 检测正整数
+ * @param val 当前值字符串
+ * @returns 返回boolean
+ */
+export function checkNumberInteger(val: string) {
+  const rep = /[\.]/;
+  return !!rep.test(val);
+}
+
+/**
+ * 去掉中文及空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyCnAndSpace(val: string) {
+  // 匹配中文与空格
+  let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
+  // 匹配空格
+  v = v.replace(/(^\s*)|(\s*$)/g, '');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 去掉英文及空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyEnAndSpace(val: string) {
+  // 匹配英文与空格
+  let v = val.replace(/[a-zA-Z]+/g, '');
+  // 匹配空格
+  v = v.replace(/(^\s*)|(\s*$)/g, '');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 禁止输入空格
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyAndSpace(val: string) {
+  // 匹配空格
+  const v = val.replace(/(^\s*)|(\s*$)/g, '');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 金额用 `,` 区分开
+ * @param val 当前值字符串
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberComma(val: string) {
+  // 调用小数或整数(不可以负数)方法
+  let v: any = verifyNumberIntegerAndFloat(val);
+  // 字符串转成数组
+  v = v.toString().split('.');
+  // \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
+  v[0] = v[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
+  // 数组转字符串
+  v = v.join('.');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 匹配文字变色(搜索时)
+ * @param val 当前值字符串
+ * @param text 要处理的字符串值
+ * @param color 搜索到时字体高亮颜色
+ * @returns 返回处理后的字符串
+ */
+export function verifyTextColor(val: string, text = '', color = 'red') {
+  // 返回内容,添加颜色
+  const v = text.replace(
+    new RegExp(val, 'gi'),
+    `<span style='color: ${color}'>${val}</span>`
+  );
+  // 返回结果
+  return v;
+}
+
+/**
+ * 数字转中文大写
+ * @param val 当前值字符串
+ * @param unit 默认:仟佰拾亿仟佰拾万仟佰拾元角分
+ * @returns 返回处理后的字符串
+ */
+export function verifyNumberCnUppercase(
+  val: any,
+  unit = '仟佰拾亿仟佰拾万仟佰拾元角分',
+  v = ''
+) {
+  // 当前内容字符串添加 2个0,为什么??
+  val += '00';
+  // 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
+  const lookup = val.indexOf('.');
+  // substring:不包含结束下标内容,substr:包含结束下标内容
+  if (lookup >= 0) val = val.substring(0, lookup) + val.substr(lookup + 1, 2);
+  // 根据内容 val 的长度,截取返回对应大写
+  unit = unit.substr(unit.length - val.length);
+  // 循环截取拼接大写
+  for (let i = 0; i < val.length; i++) {
+    v += '零壹贰叁肆伍陆柒捌玖'.substr(val.substr(i, 1), 1) + unit.substr(i, 1);
+  }
+  // 正则处理
+  v = v
+    .replace(/零角零分$/, '整')
+    .replace(/零[仟佰拾]/g, '零')
+    .replace(/零{2,}/g, '零')
+    .replace(/零([亿|万])/g, '$1')
+    .replace(/零+元/, '元')
+    .replace(/亿零{0,3}万/, '亿')
+    .replace(/^元/, '零元');
+  // 返回结果
+  return v;
+}
+
+/**
+ * 手机号码
+ * @param val 当前值字符串
+ * @returns 返回 true: 手机号码正确
+ */
+export function verifyPhone(val: string) {
+  // false: 手机号码不正确
+  if (
+    !/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(
+      val
+    )
+  )
+    return false;
+  // true: 手机号码正确
+  else return true;
+}
+
+/**
+ * 国内电话号码
+ * @param val 当前值字符串
+ * @returns 返回 true: 国内电话号码正确
+ */
+export function verifyTelPhone(val: string) {
+  // false: 国内电话号码不正确
+  if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
+  // true: 国内电话号码正确
+  else return true;
+}
+
+/**
+ * 登录账号 (字母开头,允许5-16字节,允许字母数字下划线)
+ * @param val 当前值字符串
+ * @returns 返回 true: 登录账号正确
+ */
+export function verifyAccount(val: string) {
+  // false: 登录账号不正确
+  if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
+  // true: 登录账号正确
+  else return true;
+}
+
+/**
+ * 密码 (以字母开头,长度在6~16之间,只能包含字母、数字和下划线)
+ * @param val 当前值字符串
+ * @returns 返回 true: 密码正确
+ */
+export function verifyPassword(val: string) {
+  // false: 密码不正确
+  if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
+  // true: 密码正确
+  else return true;
+}
+
+/**
+ * 强密码 (字母+数字+特殊字符,长度在6-16之间)
+ * @param val 当前值字符串
+ * @returns 返回 true: 强密码正确
+ */
+export function verifyPasswordPowerful(val: string) {
+  // false: 强密码不正确
+  if (
+    !/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    return false;
+  // true: 强密码正确
+  else return true;
+}
+
+/**
+ * 密码强度
+ * @param val 当前值字符串
+ * @description 弱:纯数字,纯字母,纯特殊字符
+ * @description 中:字母+数字,字母+特殊字符,数字+特殊字符
+ * @description 强:字母+数字+特殊字符
+ * @returns 返回处理后的字符串:弱、中、强
+ */
+export function verifyPasswordStrength(val: string) {
+  let v = '';
+  // 弱:纯数字,纯字母,纯特殊字符
+  if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱';
+  // 中:字母+数字,字母+特殊字符,数字+特殊字符
+  if (
+    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    v = '中';
+  // 强:字母+数字+特殊字符
+  if (
+    /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(
+      val
+    )
+  )
+    v = '强';
+  // 返回结果
+  return v;
+}
+
+/**
+ * IP地址
+ * @param val 当前值字符串
+ * @returns 返回 true: IP地址正确
+ */
+export function verifyIPAddress(val: string) {
+  // false: IP地址不正确
+  if (
+    !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
+      val
+    )
+  )
+    return false;
+  // true: IP地址正确
+  else return true;
+}
+
+/**
+ * 邮箱
+ * @param val 当前值字符串
+ * @returns 返回 true: 邮箱正确
+ */
+export function verifyEmail(val: string) {
+  // false: 邮箱不正确
+  const reg =
+    /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+  if (!reg.test(val)) return false;
+  // true: 邮箱正确
+  else return true;
+}
+
+/**
+ * 身份证
+ * @param val 当前值字符串
+ * @returns 返回 true: 身份证正确
+ */
+export function verifyIdCard(val: string) {
+  // false: 身份证不正确
+  if (
+    !/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(
+      val
+    )
+  )
+    return false;
+  // true: 身份证正确
+  else return true;
+}
+
+/**
+ * 姓名
+ * @param val 当前值字符串
+ * @returns 返回 true: 姓名正确
+ */
+export function verifyFullName(val: string) {
+  // false: 姓名不正确
+  if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val))
+    return false;
+  // true: 姓名正确
+  else return true;
+}
+
+/**
+ * 邮政编码
+ * @param val 当前值字符串
+ * @returns 返回 true: 邮政编码正确
+ */
+export function verifyPostalCode(val: string) {
+  // false: 邮政编码不正确
+  if (!/^[1-9][0-9]{5}$/.test(val)) return false;
+  // true: 邮政编码正确
+  else return true;
+}
+
+/**
+ * url 处理
+ * @param val 当前值字符串
+ * @returns 返回 true: url 正确
+ */
+export function verifyUrl(val: string) {
+  // false: url不正确
+  // !/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
+  //   val
+  // )
+  if (
+    !/^(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?$/.test(
+      val
+    )
+  )
+    return false;
+  // true: url正确
+  else return true;
+}
+
+/**
+ * 车牌号
+ * @param val 当前值字符串
+ * @returns 返回 true:车牌号正确
+ */
+export function verifyCarNum(val: string) {
+  // false: 车牌号不正确
+  if (
+    !/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(
+      val
+    )
+  )
+    return false;
+  // true:车牌号正确
+  else return true;
+}

+ 2 - 2
src/helpers/utils.ts

@@ -58,5 +58,5 @@ export function checkPhone(phone: string) {
 }
 
 export const getHttpOrigin = () => {
-	return window.location.origin
-}
+  return window.location.origin;
+};

+ 47 - 47
src/hooks/core/useTimeout.ts

@@ -1,47 +1,47 @@
-import { ref, watch } from 'vue';
-import { Fn, tryOnUnmounted } from '@vueuse/core';
-import { isFunction } from '@/utils/is';
-
-export function useTimeoutFn(handle: any, wait: number, native = false) {
-  if (!isFunction(handle)) {
-    throw new Error('handle is not Function!');
-  }
-
-  const { readyRef, stop, start } = useTimeoutRef(wait);
-  if (native) {
-    handle();
-  } else {
-    watch(
-      readyRef,
-      (maturity) => {
-        maturity && handle();
-      },
-      { immediate: false }
-    );
-  }
-  return { readyRef, stop, start };
-}
-
-export function useTimeoutRef(wait: number) {
-  const readyRef = ref(false);
-
-  let timer: any;
-
-  function stop(): void {
-    readyRef.value = false;
-    timer && window.clearTimeout(timer);
-  }
-
-  function start(): void {
-    stop();
-    timer = setTimeout(() => {
-      readyRef.value = true;
-    }, wait);
-  }
-
-  start();
-
-  tryOnUnmounted(stop);
-
-  return { readyRef, stop, start };
-}
+import { ref, watch } from 'vue';
+import { Fn, tryOnUnmounted } from '@vueuse/core';
+import { isFunction } from '@/utils/is';
+
+export function useTimeoutFn(handle: any, wait: number, native = false) {
+  if (!isFunction(handle)) {
+    throw new Error('handle is not Function!');
+  }
+
+  const { readyRef, stop, start } = useTimeoutRef(wait);
+  if (native) {
+    handle();
+  } else {
+    watch(
+      readyRef,
+      maturity => {
+        maturity && handle();
+      },
+      { immediate: false }
+    );
+  }
+  return { readyRef, stop, start };
+}
+
+export function useTimeoutRef(wait: number) {
+  const readyRef = ref(false);
+
+  let timer: any;
+
+  function stop(): void {
+    readyRef.value = false;
+    timer && window.clearTimeout(timer);
+  }
+
+  function start(): void {
+    stop();
+    timer = setTimeout(() => {
+      readyRef.value = true;
+    }, wait);
+  }
+
+  start();
+
+  tryOnUnmounted(stop);
+
+  return { readyRef, stop, start };
+}

+ 91 - 91
src/hooks/event/useBreakpoint.ts

@@ -1,91 +1,91 @@
-import { ref, computed, ComputedRef, unref } from 'vue';
-import { useEventListener } from '@/hooks/event/useEventListener';
-import { screenMap, sizeEnum, screenEnum } from '@/enums/breakpointEnum';
-
-let globalScreenRef: ComputedRef<sizeEnum | undefined>;
-let globalWidthRef: ComputedRef<number>;
-let globalRealWidthRef: ComputedRef<number>;
-
-export interface CreateCallbackParams {
-  screen: ComputedRef<sizeEnum | undefined>;
-  width: ComputedRef<number>;
-  realWidth: ComputedRef<number>;
-  screenEnum: typeof screenEnum;
-  screenMap: Map<sizeEnum, number>;
-  sizeEnum: typeof sizeEnum;
-}
-
-export function useBreakpoint() {
-  return {
-    screenRef: computed(() => unref(globalScreenRef)),
-    widthRef: globalWidthRef,
-    screenEnum,
-    realWidthRef: globalRealWidthRef
-  };
-}
-
-// Just call it once
-export function createBreakpointListen(
-  fn?: (opt: CreateCallbackParams) => void
-) {
-  const screenRef = ref<sizeEnum>(sizeEnum.XL);
-  const realWidthRef = ref(window.innerWidth);
-
-  function getWindowWidth() {
-    const width = document.body.clientWidth;
-    const xs = screenMap.get(sizeEnum.XS)!;
-    const sm = screenMap.get(sizeEnum.SM)!;
-    const md = screenMap.get(sizeEnum.MD)!;
-    const lg = screenMap.get(sizeEnum.LG)!;
-    const xl = screenMap.get(sizeEnum.XL)!;
-    if (width < xs) {
-      screenRef.value = sizeEnum.XS;
-    } else if (width < sm) {
-      screenRef.value = sizeEnum.SM;
-    } else if (width < md) {
-      screenRef.value = sizeEnum.MD;
-    } else if (width < lg) {
-      screenRef.value = sizeEnum.LG;
-    } else if (width < xl) {
-      screenRef.value = sizeEnum.XL;
-    } else {
-      screenRef.value = sizeEnum.XXL;
-    }
-    realWidthRef.value = width;
-  }
-
-  useEventListener({
-    el: window,
-    name: 'resize',
-
-    listener: () => {
-      getWindowWidth();
-      resizeFn();
-    }
-    // wait: 100,
-  });
-
-  getWindowWidth();
-  globalScreenRef = computed(() => unref(screenRef));
-  globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!);
-  globalRealWidthRef = computed((): number => unref(realWidthRef));
-
-  function resizeFn() {
-    fn?.({
-      screen: globalScreenRef,
-      width: globalWidthRef,
-      realWidth: globalRealWidthRef,
-      screenEnum,
-      screenMap,
-      sizeEnum
-    });
-  }
-
-  resizeFn();
-  return {
-    screenRef: globalScreenRef,
-    screenEnum,
-    widthRef: globalWidthRef,
-    realWidthRef: globalRealWidthRef
-  };
-}
+import { ref, computed, ComputedRef, unref } from 'vue';
+import { useEventListener } from '@/hooks/event/useEventListener';
+import { screenMap, sizeEnum, screenEnum } from '@/enums/breakpointEnum';
+
+let globalScreenRef: ComputedRef<sizeEnum | undefined>;
+let globalWidthRef: ComputedRef<number>;
+let globalRealWidthRef: ComputedRef<number>;
+
+export interface CreateCallbackParams {
+  screen: ComputedRef<sizeEnum | undefined>;
+  width: ComputedRef<number>;
+  realWidth: ComputedRef<number>;
+  screenEnum: typeof screenEnum;
+  screenMap: Map<sizeEnum, number>;
+  sizeEnum: typeof sizeEnum;
+}
+
+export function useBreakpoint() {
+  return {
+    screenRef: computed(() => unref(globalScreenRef)),
+    widthRef: globalWidthRef,
+    screenEnum,
+    realWidthRef: globalRealWidthRef
+  };
+}
+
+// Just call it once
+export function createBreakpointListen(
+  fn?: (opt: CreateCallbackParams) => void
+) {
+  const screenRef = ref<sizeEnum>(sizeEnum.XL);
+  const realWidthRef = ref(window.innerWidth);
+
+  function getWindowWidth() {
+    const width = document.body.clientWidth;
+    const xs = screenMap.get(sizeEnum.XS)!;
+    const sm = screenMap.get(sizeEnum.SM)!;
+    const md = screenMap.get(sizeEnum.MD)!;
+    const lg = screenMap.get(sizeEnum.LG)!;
+    const xl = screenMap.get(sizeEnum.XL)!;
+    if (width < xs) {
+      screenRef.value = sizeEnum.XS;
+    } else if (width < sm) {
+      screenRef.value = sizeEnum.SM;
+    } else if (width < md) {
+      screenRef.value = sizeEnum.MD;
+    } else if (width < lg) {
+      screenRef.value = sizeEnum.LG;
+    } else if (width < xl) {
+      screenRef.value = sizeEnum.XL;
+    } else {
+      screenRef.value = sizeEnum.XXL;
+    }
+    realWidthRef.value = width;
+  }
+
+  useEventListener({
+    el: window,
+    name: 'resize',
+
+    listener: () => {
+      getWindowWidth();
+      resizeFn();
+    }
+    // wait: 100,
+  });
+
+  getWindowWidth();
+  globalScreenRef = computed(() => unref(screenRef));
+  globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!);
+  globalRealWidthRef = computed((): number => unref(realWidthRef));
+
+  function resizeFn() {
+    fn?.({
+      screen: globalScreenRef,
+      width: globalWidthRef,
+      realWidth: globalRealWidthRef,
+      screenEnum,
+      screenMap,
+      sizeEnum
+    });
+  }
+
+  resizeFn();
+  return {
+    screenRef: globalScreenRef,
+    screenEnum,
+    widthRef: globalWidthRef,
+    realWidthRef: globalRealWidthRef
+  };
+}

+ 63 - 63
src/hooks/event/useEventListener.ts

@@ -1,66 +1,66 @@
-import type { Ref } from 'vue';
-
-import { ref, watch, unref } from 'vue';
-import { useThrottleFn, useDebounceFn } from '@vueuse/core';
-
-export type RemoveEventFn = () => void;
-
-export interface UseEventParams {
-  el?: Element | Ref<Element | undefined> | Window | any;
-  name: string;
-  listener: EventListener;
-  options?: boolean | AddEventListenerOptions;
-  autoRemove?: boolean;
-  isDebounce?: boolean;
-  wait?: number;
-}
-
-export function useEventListener({
-  el = window,
-  name,
-  listener,
-  options,
-  autoRemove = true,
-  isDebounce = true,
-  wait = 80
-}: UseEventParams): { removeEvent: RemoveEventFn } {
-  /* eslint-disable-next-line */
+import type { Ref } from 'vue';
+
+import { ref, watch, unref } from 'vue';
+import { useThrottleFn, useDebounceFn } from '@vueuse/core';
+
+export type RemoveEventFn = () => void;
+
+export interface UseEventParams {
+  el?: Element | Ref<Element | undefined> | Window | any;
+  name: string;
+  listener: EventListener;
+  options?: boolean | AddEventListenerOptions;
+  autoRemove?: boolean;
+  isDebounce?: boolean;
+  wait?: number;
+}
+
+export function useEventListener({
+  el = window,
+  name,
+  listener,
+  options,
+  autoRemove = true,
+  isDebounce = true,
+  wait = 80
+}: UseEventParams): { removeEvent: RemoveEventFn } {
+  /* eslint-disable-next-line */
   let remove: RemoveEventFn = () => {};
-  const isAddRef = ref(false);
-
-  if (el) {
-    const element = ref<Element>(el);
-
-    // eslint-disable-next-line prettier/prettier
+  const isAddRef = ref(false);
+
+  if (el) {
+    const element = ref<Element>(el);
+
+    // eslint-disable-next-line prettier/prettier
     const handler = isDebounce
-      ? useDebounceFn(listener, wait)
-      : useThrottleFn(listener, wait);
-    const realHandler = wait ? handler : listener;
-    const removeEventListener = (e: Element) => {
-      isAddRef.value = true;
-      e.removeEventListener(name, realHandler, options);
-    };
-    // eslint-disable-next-line prettier/prettier
+      ? useDebounceFn(listener, wait)
+      : useThrottleFn(listener, wait);
+    const realHandler = wait ? handler : listener;
+    const removeEventListener = (e: Element) => {
+      isAddRef.value = true;
+      e.removeEventListener(name, realHandler, options);
+    };
+    // eslint-disable-next-line prettier/prettier
     const addEventListener = (e: Element) =>
-      e.addEventListener(name, realHandler, options);
-
-    const removeWatch = watch(
-      element,
-      (v, _ov, cleanUp) => {
-        if (v) {
-          !unref(isAddRef) && addEventListener(v as unknown as Element);
-          cleanUp(() => {
-            autoRemove && removeEventListener(v as unknown as Element);
-          });
-        }
-      },
-      { immediate: true }
-    );
-
-    remove = () => {
-      removeEventListener(element.value as unknown as Element);
-      removeWatch();
-    };
-  }
-  return { removeEvent: remove };
-}
+      e.addEventListener(name, realHandler, options);
+
+    const removeWatch = watch(
+      element,
+      (v, _ov, cleanUp) => {
+        if (v) {
+          !unref(isAddRef) && addEventListener(v as unknown as Element);
+          cleanUp(() => {
+            autoRemove && removeEventListener(v as unknown as Element);
+          });
+        }
+      },
+      { immediate: true }
+    );
+
+    remove = () => {
+      removeEventListener(element.value as unknown as Element);
+      removeWatch();
+    };
+  }
+  return { removeEvent: remove };
+}

+ 40 - 36
src/hooks/event/useWindowSizeFn.ts

@@ -1,36 +1,40 @@
-import { Fn, tryOnMounted, tryOnUnmounted } from '@vueuse/core';
-import { useDebounceFn } from '@vueuse/core';
-
-interface WindowSizeOptions {
-  once?: boolean;
-  immediate?: boolean;
-  listenerOptions?: AddEventListenerOptions | boolean;
-}
-
-export function useWindowSizeFn<T>(fn: any, wait = 150, options?: WindowSizeOptions) {
-  let handler = () => {
-    fn();
-  };
-  const handleSize = useDebounceFn(handler, wait);
-  handler = handleSize;
-
-  const start = () => {
-    if (options && options.immediate) {
-      handler();
-    }
-    window.addEventListener('resize', handler);
-  };
-
-  const stop = () => {
-    window.removeEventListener('resize', handler);
-  };
-
-  tryOnMounted(() => {
-    start();
-  });
-
-  tryOnUnmounted(() => {
-    stop();
-  });
-  return [start, stop];
-}
+import { Fn, tryOnMounted, tryOnUnmounted } from '@vueuse/core';
+import { useDebounceFn } from '@vueuse/core';
+
+interface WindowSizeOptions {
+  once?: boolean;
+  immediate?: boolean;
+  listenerOptions?: AddEventListenerOptions | boolean;
+}
+
+export function useWindowSizeFn<T>(
+  fn: any,
+  wait = 150,
+  options?: WindowSizeOptions
+) {
+  let handler = () => {
+    fn();
+  };
+  const handleSize = useDebounceFn(handler, wait);
+  handler = handleSize;
+
+  const start = () => {
+    if (options && options.immediate) {
+      handler();
+    }
+    window.addEventListener('resize', handler);
+  };
+
+  const stop = () => {
+    window.removeEventListener('resize', handler);
+  };
+
+  tryOnMounted(() => {
+    start();
+  });
+
+  tryOnUnmounted(() => {
+    stop();
+  });
+  return [start, stop];
+}

+ 18 - 18
src/hooks/setting/useDesignSetting.ts

@@ -1,18 +1,18 @@
-import { computed } from 'vue';
-import { useDesignSettingStore } from '@/store/modules/designSetting';
-
-export function useDesignSetting() {
-  const designStore = useDesignSettingStore();
-
-  const getDarkTheme = computed(() => designStore.darkTheme);
-
-  const getAppTheme = computed(() => designStore.appTheme);
-
-  const getAppThemeList = computed(() => designStore.appThemeList);
-
-  return {
-    getDarkTheme,
-    getAppTheme,
-    getAppThemeList
-  };
-}
+import { computed } from 'vue';
+import { useDesignSettingStore } from '@/store/modules/designSetting';
+
+export function useDesignSetting() {
+  const designStore = useDesignSettingStore();
+
+  const getDarkTheme = computed(() => designStore.darkTheme);
+
+  const getAppTheme = computed(() => designStore.appTheme);
+
+  const getAppThemeList = computed(() => designStore.appThemeList);
+
+  return {
+    getDarkTheme,
+    getAppTheme,
+    getAppThemeList
+  };
+}

+ 85 - 85
src/hooks/use-async.ts

@@ -1,85 +1,85 @@
-import { isReactive, isRef, onMounted } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { Searchs } from '@/utils/searchs';
-
-function setLoading(loading: any, val: any) {
-  if (loading != undefined && isRef(loading)) {
-    loading.value = val;
-  } else if (loading != undefined && isReactive(loading)) {
-    loading.loading = val;
-  }
-}
-
-export const useAsync = async (
-  func: Promise<any>,
-  loading: any
-): Promise<any> => {
-  setLoading(loading, true);
-
-  return await func.finally(() => setLoading(loading, false));
-};
-
-export const getTabsCache = (callBack: any) => {
-  const route = useRoute();
-  const searchs = new Searchs(route.path);
-
-  const active = searchs.get(route.path);
-  onMounted(() => {
-    callBack(active);
-  });
-};
-
-export const setTabsCaches = (current: any, key = 'current', routes: any) => {
-  const searchs = new Searchs(routes.path);
-  searchs.update({ [key]: current }, undefined, 'form');
-  const active = searchs.get(routes.path);
-  // console.log(active, 'setTabsCaches');
-};
-
-/**
- * 初始化缓存
- * @param {object} { key 默认form 关键字, saveKey 地址, current 对象, callBack 回调 }
- */
-export const initCache = (params: any) => {
-  const route = useRoute();
-  if (!params.current) {
-    return;
-  }
-  if (!params.key) {
-    params.key = 'form';
-  }
-  if (!params.saveKey) {
-    params.saveKey = route.path;
-  }
-  const searchs = new Searchs(params.saveKey);
-  const active: any = searchs.get(params.saveKey);
-  const model: any = params.current;
-  const tempActive: any = active[params.key];
-  for (const key in tempActive) {
-    if (Object.prototype.hasOwnProperty.call(model, key)) {
-      const item = tempActive[key];
-      model[key] = item;
-    }
-  }
-  searchs.update({ ...model }, undefined, params.key);
-
-  onMounted(() => {
-    params.callBack && params.callBack(model);
-  });
-};
-
-/**
- * 设置缓存
- * @param {object} { key 默认form 关键字, saveKey 默认当前路由地址, current 对象, callBack 回调 }
- */
-export const setCache = (params: any) => {
-  if (!params.current || !params.saveKey) {
-    return;
-  }
-  if (!params.key) {
-    params.key = 'form';
-  }
-
-  const searchs = new Searchs(params.saveKey);
-  searchs.update({ ...params.current }, undefined, params.key);
-};
+import { isReactive, isRef, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { Searchs } from '@/utils/searchs';
+
+function setLoading(loading: any, val: any) {
+  if (loading != undefined && isRef(loading)) {
+    loading.value = val;
+  } else if (loading != undefined && isReactive(loading)) {
+    loading.loading = val;
+  }
+}
+
+export const useAsync = async (
+  func: Promise<any>,
+  loading: any
+): Promise<any> => {
+  setLoading(loading, true);
+
+  return await func.finally(() => setLoading(loading, false));
+};
+
+export const getTabsCache = (callBack: any) => {
+  const route = useRoute();
+  const searchs = new Searchs(route.path);
+
+  const active = searchs.get(route.path);
+  onMounted(() => {
+    callBack(active);
+  });
+};
+
+export const setTabsCaches = (current: any, key = 'current', routes: any) => {
+  const searchs = new Searchs(routes.path);
+  searchs.update({ [key]: current }, undefined, 'form');
+  const active = searchs.get(routes.path);
+  // console.log(active, 'setTabsCaches');
+};
+
+/**
+ * 初始化缓存
+ * @param {object} { key 默认form 关键字, saveKey 地址, current 对象, callBack 回调 }
+ */
+export const initCache = (params: any) => {
+  const route = useRoute();
+  if (!params.current) {
+    return;
+  }
+  if (!params.key) {
+    params.key = 'form';
+  }
+  if (!params.saveKey) {
+    params.saveKey = route.path;
+  }
+  const searchs = new Searchs(params.saveKey);
+  const active: any = searchs.get(params.saveKey);
+  const model: any = params.current;
+  const tempActive: any = active[params.key];
+  for (const key in tempActive) {
+    if (Object.prototype.hasOwnProperty.call(model, key)) {
+      const item = tempActive[key];
+      model[key] = item;
+    }
+  }
+  searchs.update({ ...model }, undefined, params.key);
+
+  onMounted(() => {
+    params.callBack && params.callBack(model);
+  });
+};
+
+/**
+ * 设置缓存
+ * @param {object} { key 默认form 关键字, saveKey 默认当前路由地址, current 对象, callBack 回调 }
+ */
+export const setCache = (params: any) => {
+  if (!params.current || !params.saveKey) {
+    return;
+  }
+  if (!params.key) {
+    params.key = 'form';
+  }
+
+  const searchs = new Searchs(params.saveKey);
+  searchs.update({ ...params.current }, undefined, params.key);
+};

+ 95 - 93
src/hooks/useBattery.ts

@@ -1,93 +1,95 @@
-import { computed, onMounted, reactive, toRefs } from 'vue';
-
-interface Battery {
-  charging: boolean; // 当前电池是否正在充电
-  chargingTime: number; // 距离充电完毕还需多少秒,如果为0则充电完毕
-  dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
-  level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
-  [key: string]: any;
-}
-
-export const useBattery = () => {
-  const state = reactive({
-    battery: {
-      charging: false,
-      chargingTime: 0,
-      dischargingTime: 0,
-      level: 100,
-    } as any,
-  });
-
-  // 更新电池使用状态
-  const updateBattery = (target: any) => {
-    for (const key in state.battery) {
-      state.battery[key] = target[key];
-    }
-    state.battery.level = state.battery.level * 100;
-  };
-
-  // 计算电池剩余可用时间
-  const calcDischargingTime = computed(() => {
-    const hour = state.battery.dischargingTime / 3600;
-    const minute = (state.battery.dischargingTime / 60) % 60;
-    return `${~~hour}小时${~~minute}分钟`;
-  });
-
-  // 计算电池充满剩余时间
-  const calcChargingTime = computed(() => {
-    console.log(state.battery);
-    const hour = state.battery.chargingTime / 3600;
-    const minute = (state.battery.chargingTime / 60) % 60;
-    return `${~~hour}小时${~~minute}分钟`;
-  });
-
-  // 电池状态
-  const batteryStatus = computed(() => {
-    if (state.battery.charging && state.battery.level >= 100) {
-      return '已充满';
-    } else if (state.battery.charging) {
-      return '充电中';
-    } else {
-      return '已断开电源';
-    }
-  });
-
-  onMounted(async () => {
-    const BatteryManager: Battery = await (window.navigator as any).getBattery();
-    updateBattery(BatteryManager);
-
-    // 电池充电状态更新时被调用
-    BatteryManager.onchargingchange = ({ target }: any) => {
-      updateBattery(target);
-    };
-    // 电池充电时间更新时被调用
-    BatteryManager.onchargingtimechange = ({ target }: any) => {
-      updateBattery(target);
-    };
-    // 电池断开充电时间更新时被调用
-    BatteryManager.ondischargingtimechange = ({ target }: any) => {
-      updateBattery(target);
-    };
-    // 电池电量更新时被调用
-    BatteryManager.onlevelchange = ({ target }: any) => {
-      updateBattery(target);
-    };
-
-    // new Intl.DateTimeFormat('zh', {
-    //   year: 'numeric',
-    //   month: '2-digit',
-    //   day: '2-digit',
-    //   hour: '2-digit',
-    //   minute: '2-digit',
-    //   second: '2-digit',
-    //   hour12: false
-    // }).format(new Date())
-  });
-
-  return {
-    ...toRefs(state),
-    batteryStatus,
-    calcDischargingTime,
-    calcChargingTime,
-  };
-};
+import { computed, onMounted, reactive, toRefs } from 'vue';
+
+interface Battery {
+  charging: boolean; // 当前电池是否正在充电
+  chargingTime: number; // 距离充电完毕还需多少秒,如果为0则充电完毕
+  dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒
+  level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间
+  [key: string]: any;
+}
+
+export const useBattery = () => {
+  const state = reactive({
+    battery: {
+      charging: false,
+      chargingTime: 0,
+      dischargingTime: 0,
+      level: 100
+    } as any
+  });
+
+  // 更新电池使用状态
+  const updateBattery = (target: any) => {
+    for (const key in state.battery) {
+      state.battery[key] = target[key];
+    }
+    state.battery.level = state.battery.level * 100;
+  };
+
+  // 计算电池剩余可用时间
+  const calcDischargingTime = computed(() => {
+    const hour = state.battery.dischargingTime / 3600;
+    const minute = (state.battery.dischargingTime / 60) % 60;
+    return `${~~hour}小时${~~minute}分钟`;
+  });
+
+  // 计算电池充满剩余时间
+  const calcChargingTime = computed(() => {
+    console.log(state.battery);
+    const hour = state.battery.chargingTime / 3600;
+    const minute = (state.battery.chargingTime / 60) % 60;
+    return `${~~hour}小时${~~minute}分钟`;
+  });
+
+  // 电池状态
+  const batteryStatus = computed(() => {
+    if (state.battery.charging && state.battery.level >= 100) {
+      return '已充满';
+    } else if (state.battery.charging) {
+      return '充电中';
+    } else {
+      return '已断开电源';
+    }
+  });
+
+  onMounted(async () => {
+    const BatteryManager: Battery = await (
+      window.navigator as any
+    ).getBattery();
+    updateBattery(BatteryManager);
+
+    // 电池充电状态更新时被调用
+    BatteryManager.onchargingchange = ({ target }: any) => {
+      updateBattery(target);
+    };
+    // 电池充电时间更新时被调用
+    BatteryManager.onchargingtimechange = ({ target }: any) => {
+      updateBattery(target);
+    };
+    // 电池断开充电时间更新时被调用
+    BatteryManager.ondischargingtimechange = ({ target }: any) => {
+      updateBattery(target);
+    };
+    // 电池电量更新时被调用
+    BatteryManager.onlevelchange = ({ target }: any) => {
+      updateBattery(target);
+    };
+
+    // new Intl.DateTimeFormat('zh', {
+    //   year: 'numeric',
+    //   month: '2-digit',
+    //   day: '2-digit',
+    //   hour: '2-digit',
+    //   minute: '2-digit',
+    //   second: '2-digit',
+    //   hour12: false
+    // }).format(new Date())
+  });
+
+  return {
+    ...toRefs(state),
+    batteryStatus,
+    calcDischargingTime,
+    calcChargingTime
+  };
+};

+ 23 - 23
src/hooks/useDomWidth.ts

@@ -1,23 +1,23 @@
-import { ref, onMounted, onUnmounted } from 'vue';
-import { debounce } from 'lodash';
-
-/**
- * description: 获取页面宽度
- */
-
-export function useDomWidth() {
-  const domWidth = ref(window.innerWidth);
-
-  function resize() {
-    domWidth.value = document.body.clientWidth;
-  }
-
-  onMounted(() => {
-    window.addEventListener('resize', debounce(resize, 80));
-  });
-  onUnmounted(() => {
-    window.removeEventListener('resize', resize);
-  });
-
-  return domWidth;
-}
+import { ref, onMounted, onUnmounted } from 'vue';
+import { debounce } from 'lodash';
+
+/**
+ * description: 获取页面宽度
+ */
+
+export function useDomWidth() {
+  const domWidth = ref(window.innerWidth);
+
+  function resize() {
+    domWidth.value = document.body.clientWidth;
+  }
+
+  onMounted(() => {
+    window.addEventListener('resize', debounce(resize, 80));
+  });
+  onUnmounted(() => {
+    window.removeEventListener('resize', resize);
+  });
+
+  return domWidth;
+}

+ 4 - 4
src/hooks/useDrag/index.ts

@@ -55,10 +55,10 @@ export default function useDrag(
     return pos.value.left === -1 && pos.value.top === -1
       ? {}
       : {
-        position: 'fixed',
-        left: `${pos.value.left}px`,
-        top: `${pos.value.top}px`
-      };
+          position: 'fixed',
+          left: `${pos.value.left}px`,
+          top: `${pos.value.top}px`
+        };
   });
   function initPos() {
     const posCache = getCachePos(useIdDargClass);

+ 30 - 30
src/hooks/useOnline.ts

@@ -1,30 +1,30 @@
-import { ref, onMounted, onUnmounted } from 'vue';
-
-/**
- * @description 用户网络是否可用
- * */
-export function useOnline() {
-  const online = ref(true);
-
-  const showStatus = (val: any) => {
-    online.value = typeof val == 'boolean' ? val : val.target.online;
-  };
-
-  // 在页面加载后,设置正确的网络状态
-  navigator.onLine ? showStatus(true) : showStatus(false);
-
-  onMounted(() => {
-    // 开始监听网络状态的变化
-    window.addEventListener('online', showStatus);
-
-    window.addEventListener('offline', showStatus);
-  });
-  onUnmounted(() => {
-    // 移除监听网络状态的变化
-    window.removeEventListener('online', showStatus);
-
-    window.removeEventListener('offline', showStatus);
-  });
-
-  return { online };
-}
+import { ref, onMounted, onUnmounted } from 'vue';
+
+/**
+ * @description 用户网络是否可用
+ * */
+export function useOnline() {
+  const online = ref(true);
+
+  const showStatus = (val: any) => {
+    online.value = typeof val == 'boolean' ? val : val.target.online;
+  };
+
+  // 在页面加载后,设置正确的网络状态
+  navigator.onLine ? showStatus(true) : showStatus(false);
+
+  onMounted(() => {
+    // 开始监听网络状态的变化
+    window.addEventListener('online', showStatus);
+
+    window.addEventListener('offline', showStatus);
+  });
+  onUnmounted(() => {
+    // 移除监听网络状态的变化
+    window.removeEventListener('online', showStatus);
+
+    window.removeEventListener('offline', showStatus);
+  });
+
+  return { online };
+}

+ 59 - 55
src/hooks/useTime.ts

@@ -1,55 +1,59 @@
-import { ref, onMounted, onUnmounted } from 'vue'
-
-/**
- * @description 获取本地时间
- */
-export function useTime() {
-  let timer: any // 定时器
-  const year = ref(0) // 年份
-  const month = ref(0) // 月份
-  const week = ref('') // 星期几
-  const day = ref(0) // 天数
-  const hour = ref<number | string>(0) // 小时
-  const minute = ref<number | string>(0) // 分钟
-  const second = ref(0) // 秒
-
-  // 更新时间
-  const updateTime = () => {
-    const date = new Date()
-    year.value = date.getFullYear()
-    month.value = date.getMonth() + 1
-    week.value = '日一二三四五六'.charAt(date.getDay())
-    day.value = date.getDate()
-    hour.value =
-      (date.getHours() + '')?.padStart(2, '0') ||
-      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
-    minute.value =
-      (date.getMinutes() + '')?.padStart(2, '0') ||
-      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
-    second.value = date.getSeconds()
-  }
-
-  // 原生时间格式化
-  // new Intl.DateTimeFormat('zh', {
-  //     year: 'numeric',
-  //     month: '2-digit',
-  //     day: '2-digit',
-  //     hour: '2-digit',
-  //     minute: '2-digit',
-  //     second: '2-digit',
-  //     hour12: false
-  // }).format(new Date())
-
-  updateTime()
-
-  onMounted(() => {
-    clearInterval(timer)
-    timer = setInterval(() => updateTime(), 1000)
-  })
-
-  onUnmounted(() => {
-    clearInterval(timer)
-  })
-
-  return { month, day, hour, minute, second, week }
-}
+import { ref, onMounted, onUnmounted } from 'vue';
+
+/**
+ * @description 获取本地时间
+ */
+export function useTime() {
+  let timer: any; // 定时器
+  const year = ref(0); // 年份
+  const month = ref(0); // 月份
+  const week = ref(''); // 星期几
+  const day = ref(0); // 天数
+  const hour = ref<number | string>(0); // 小时
+  const minute = ref<number | string>(0); // 分钟
+  const second = ref(0); // 秒
+
+  // 更新时间
+  const updateTime = () => {
+    const date = new Date();
+    year.value = date.getFullYear();
+    month.value = date.getMonth() + 1;
+    week.value = '日一二三四五六'.charAt(date.getDay());
+    day.value = date.getDate();
+    hour.value =
+      (date.getHours() + '')?.padStart(2, '0') ||
+      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(
+        date.getHours()
+      );
+    minute.value =
+      (date.getMinutes() + '')?.padStart(2, '0') ||
+      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(
+        date.getMinutes()
+      );
+    second.value = date.getSeconds();
+  };
+
+  // 原生时间格式化
+  // new Intl.DateTimeFormat('zh', {
+  //     year: 'numeric',
+  //     month: '2-digit',
+  //     day: '2-digit',
+  //     hour: '2-digit',
+  //     minute: '2-digit',
+  //     second: '2-digit',
+  //     hour12: false
+  // }).format(new Date())
+
+  updateTime();
+
+  onMounted(() => {
+    clearInterval(timer);
+    timer = setInterval(() => updateTime(), 1000);
+  });
+
+  onUnmounted(() => {
+    clearInterval(timer);
+  });
+
+  return { month, day, hour, minute, second, week };
+}

+ 124 - 124
src/hooks/web/useECharts.ts

@@ -1,124 +1,124 @@
-import type { EChartsOption } from 'echarts';
-import type { Ref } from 'vue';
-
-import { useTimeoutFn } from '@/hooks/core/useTimeout';
-import { Fn, tryOnUnmounted } from '@vueuse/core';
-import { unref, nextTick, watch, computed, ref } from 'vue';
-import { useDebounceFn } from '@vueuse/core';
-import { useEventListener } from '@/hooks/event/useEventListener';
-
-import echarts from '@/utils/lib/echarts';
-
-import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
-import { useBreakpoint } from '@/hooks/event/useBreakpoint';
-export function useECharts(
-  elRef: Ref<HTMLDivElement>,
-  theme: 'light' | 'dark' | 'default' = 'default'
-) {
-  const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
-
-  const getDarkTheme = computed(() => {
-    const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
-    return theme === 'default' ? sysTheme : theme;
-  });
-
-  let chartInstance: echarts.ECharts | null = null;
-  let resizeFn: Fn = resize;
-  const cacheOptions = ref({});
-
-  // eslint-disable-next-line @typescript-eslint/no-empty-function
-  let removeResizeFn: Fn = () => {};
-  resizeFn = useDebounceFn(resize, 200);
-
-  const getOptions = computed((): EChartsOption => {
-    if (getDarkTheme.value !== 'dark') {
-      return cacheOptions.value;
-    }
-    return {
-      backgroundColor: 'transparent',
-      ...cacheOptions.value
-    };
-  });
-
-  function initCharts(t = theme) {
-    const el = unref(elRef);
-    if (!el || !unref(el)) {
-      return;
-    }
-
-    chartInstance = echarts.init(el, t);
-    const { removeEvent } = useEventListener({
-      el: window,
-      name: 'resize',
-      listener: resizeFn
-    });
-    removeResizeFn = removeEvent;
-    const { widthRef, screenEnum } = useBreakpoint();
-    if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
-      useTimeoutFn(() => {
-        resizeFn();
-      }, 30);
-    }
-  }
-
-  function setOptions(options: EChartsOption, clear = true) {
-    cacheOptions.value = options;
-    if (unref(elRef)?.offsetHeight === 0) {
-      useTimeoutFn(() => {
-        setOptions(unref(getOptions));
-      }, 30);
-      return;
-    }
-    nextTick(() => {
-      useTimeoutFn(() => {
-        if (!chartInstance) {
-          initCharts(getDarkTheme.value as 'default');
-
-          if (!chartInstance) return;
-        }
-        clear && chartInstance?.clear();
-
-        chartInstance?.setOption(unref(getOptions));
-      }, 30);
-    });
-  }
-
-  function resize() {
-    chartInstance?.resize();
-  }
-
-  watch(
-    () => getDarkTheme.value,
-    theme => {
-      if (chartInstance) {
-        chartInstance.dispose();
-        initCharts(theme as 'default');
-        setOptions(cacheOptions.value);
-      }
-    }
-  );
-
-  tryOnUnmounted(disposeInstance);
-
-  function getInstance(): echarts.ECharts | null {
-    if (!chartInstance) {
-      initCharts(getDarkTheme.value as 'default');
-    }
-    return chartInstance;
-  }
-
-  function disposeInstance() {
-    if (!chartInstance) return;
-    removeResizeFn();
-    chartInstance.dispose();
-    chartInstance = null;
-  }
-
-  return {
-    setOptions,
-    resize,
-    echarts,
-    getInstance,
-    disposeInstance
-  };
-}
+import type { EChartsOption } from 'echarts';
+import type { Ref } from 'vue';
+
+import { useTimeoutFn } from '@/hooks/core/useTimeout';
+import { Fn, tryOnUnmounted } from '@vueuse/core';
+import { unref, nextTick, watch, computed, ref } from 'vue';
+import { useDebounceFn } from '@vueuse/core';
+import { useEventListener } from '@/hooks/event/useEventListener';
+
+import echarts from '@/utils/lib/echarts';
+
+import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
+import { useBreakpoint } from '@/hooks/event/useBreakpoint';
+export function useECharts(
+  elRef: Ref<HTMLDivElement>,
+  theme: 'light' | 'dark' | 'default' = 'default'
+) {
+  const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
+
+  const getDarkTheme = computed(() => {
+    const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
+    return theme === 'default' ? sysTheme : theme;
+  });
+
+  let chartInstance: echarts.ECharts | null = null;
+  let resizeFn: Fn = resize;
+  const cacheOptions = ref({});
+
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  let removeResizeFn: Fn = () => {};
+  resizeFn = useDebounceFn(resize, 200);
+
+  const getOptions = computed((): EChartsOption => {
+    if (getDarkTheme.value !== 'dark') {
+      return cacheOptions.value;
+    }
+    return {
+      backgroundColor: 'transparent',
+      ...cacheOptions.value
+    };
+  });
+
+  function initCharts(t = theme) {
+    const el = unref(elRef);
+    if (!el || !unref(el)) {
+      return;
+    }
+
+    chartInstance = echarts.init(el, t);
+    const { removeEvent } = useEventListener({
+      el: window,
+      name: 'resize',
+      listener: resizeFn
+    });
+    removeResizeFn = removeEvent;
+    const { widthRef, screenEnum } = useBreakpoint();
+    if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
+      useTimeoutFn(() => {
+        resizeFn();
+      }, 30);
+    }
+  }
+
+  function setOptions(options: EChartsOption, clear = true) {
+    cacheOptions.value = options;
+    if (unref(elRef)?.offsetHeight === 0) {
+      useTimeoutFn(() => {
+        setOptions(unref(getOptions));
+      }, 30);
+      return;
+    }
+    nextTick(() => {
+      useTimeoutFn(() => {
+        if (!chartInstance) {
+          initCharts(getDarkTheme.value as 'default');
+
+          if (!chartInstance) return;
+        }
+        clear && chartInstance?.clear();
+
+        chartInstance?.setOption(unref(getOptions));
+      }, 30);
+    });
+  }
+
+  function resize() {
+    chartInstance?.resize();
+  }
+
+  watch(
+    () => getDarkTheme.value,
+    theme => {
+      if (chartInstance) {
+        chartInstance.dispose();
+        initCharts(theme as 'default');
+        setOptions(cacheOptions.value);
+      }
+    }
+  );
+
+  tryOnUnmounted(disposeInstance);
+
+  function getInstance(): echarts.ECharts | null {
+    if (!chartInstance) {
+      initCharts(getDarkTheme.value as 'default');
+    }
+    return chartInstance;
+  }
+
+  function disposeInstance() {
+    if (!chartInstance) return;
+    removeResizeFn();
+    chartInstance.dispose();
+    chartInstance = null;
+  }
+
+  return {
+    setOptions,
+    resize,
+    echarts,
+    getInstance,
+    disposeInstance
+  };
+}

+ 70 - 70
src/hooks/web/usePage.ts

@@ -1,70 +1,70 @@
-import type { RouteLocationRaw, Router } from 'vue-router';
-
-import { PageEnum } from '@/enums/pageEnum';
-// import { RedirectName } from '@/router/constant'
-
-import { useRouter } from 'vue-router';
-import { isString } from '@/utils/is';
-import { unref } from 'vue';
-
-export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & {
-  path: PageEnum;
-};
-
-function handleError(e: Error) {
-  console.error(e);
-}
-
-/**
- * 页面切换
- */
-export function useGo(_router?: Router) {
-  let router;
-  if (!_router) {
-    router = useRouter();
-  }
-  const { push, replace }: any = _router || router;
-
-  function go(
-    opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME,
-    isReplace = false
-  ) {
-    if (!opt) {
-      return;
-    }
-    if (isString(opt)) {
-      isReplace
-        ? replace(opt).catch(handleError)
-        : push(opt).catch(handleError);
-    } else {
-      const o = opt as RouteLocationRaw;
-      isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
-    }
-  }
-  return go;
-}
-
-/**
- * 重做当前页面
- */
-export const useRedo = (_router?: Router) => {
-  const { push, currentRoute } = _router || useRouter();
-  const { query, params = {}, name, fullPath } = unref(currentRoute.value);
-  function redo(): Promise<boolean> {
-    return new Promise(resolve => {
-      // if (name === RedirectName) {
-      //   resolve(false)
-      //   return
-      // }
-      // if (name && Object.keys(params).length > 0) {
-      //   params['_redirect_type'] = 'name'
-      //   params['path'] = String(name)
-      // } else {
-      //   params['_redirect_type'] = 'path'
-      //   params['path'] = fullPath
-      // }
-      // push({ name: RedirectName, params, query }).then(() => resolve(true))
-    });
-  }
-  return redo;
-};
+import type { RouteLocationRaw, Router } from 'vue-router';
+
+import { PageEnum } from '@/enums/pageEnum';
+// import { RedirectName } from '@/router/constant'
+
+import { useRouter } from 'vue-router';
+import { isString } from '@/utils/is';
+import { unref } from 'vue';
+
+export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & {
+  path: PageEnum;
+};
+
+function handleError(e: Error) {
+  console.error(e);
+}
+
+/**
+ * 页面切换
+ */
+export function useGo(_router?: Router) {
+  let router;
+  if (!_router) {
+    router = useRouter();
+  }
+  const { push, replace }: any = _router || router;
+
+  function go(
+    opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME,
+    isReplace = false
+  ) {
+    if (!opt) {
+      return;
+    }
+    if (isString(opt)) {
+      isReplace
+        ? replace(opt).catch(handleError)
+        : push(opt).catch(handleError);
+    } else {
+      const o = opt as RouteLocationRaw;
+      isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
+    }
+  }
+  return go;
+}
+
+/**
+ * 重做当前页面
+ */
+export const useRedo = (_router?: Router) => {
+  const { push, currentRoute } = _router || useRouter();
+  const { query, params = {}, name, fullPath } = unref(currentRoute.value);
+  function redo(): Promise<boolean> {
+    return new Promise(resolve => {
+      // if (name === RedirectName) {
+      //   resolve(false)
+      //   return
+      // }
+      // if (name && Object.keys(params).length > 0) {
+      //   params['_redirect_type'] = 'name'
+      //   params['path'] = String(name)
+      // } else {
+      //   params['_redirect_type'] = 'path'
+      //   params['path'] = fullPath
+      // }
+      // push({ name: RedirectName, params, query }).then(() => resolve(true))
+    });
+  }
+  return redo;
+};

+ 59 - 59
src/hooks/web/usePermission.ts

@@ -1,59 +1,59 @@
-// import { useUserStore } from '@/store/modules/user';
-
-export function usePermission() {
-  // const userStore = useUserStore();
-
-  /**
-   * 检查权限
-   * @param accesses
-   */
-  function _somePermissions(accesses: string[]) {
-    // return userStore.getPermissions.some((item: any) => {
-    //   const { value }: any = item;
-    //   return accesses.includes(value);
-    // });
-    return false;
-  }
-
-  /**
-   * 判断是否存在权限
-   * 可用于 v-if 显示逻辑
-   * */
-  function hasPermission(accesses: string[]): boolean {
-    if (!accesses || !accesses.length) return true;
-    return _somePermissions(accesses);
-  }
-
-  /**
-   * 是否包含指定的所有权限
-   * @param accesses
-   */
-  function hasEveryPermission(accesses: string[]): boolean {
-    // const permissionsList = userStore.getPermissions;
-    // if (Array.isArray(accesses)) {
-    //   return permissionsList.every((access: any) =>
-    //     accesses.includes(access.value)
-    //   );
-    // }
-    return false;
-    // throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
-  }
-
-  /**
-   * 是否包含其中某个权限
-   * @param accesses
-   * @param accessMap
-   */
-  function hasSomePermission(accesses: string[]): boolean {
-    // const permissionsList = userStore.getPermissions;
-    // if (Array.isArray(accesses)) {
-    //   return permissionsList.some((access: any) =>
-    //     accesses.includes(access.value)
-    //   );
-    // }
-    return false;
-    // throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
-  }
-
-  return { hasPermission, hasEveryPermission, hasSomePermission };
-}
+// import { useUserStore } from '@/store/modules/user';
+
+export function usePermission() {
+  // const userStore = useUserStore();
+
+  /**
+   * 检查权限
+   * @param accesses
+   */
+  function _somePermissions(accesses: string[]) {
+    // return userStore.getPermissions.some((item: any) => {
+    //   const { value }: any = item;
+    //   return accesses.includes(value);
+    // });
+    return false;
+  }
+
+  /**
+   * 判断是否存在权限
+   * 可用于 v-if 显示逻辑
+   * */
+  function hasPermission(accesses: string[]): boolean {
+    if (!accesses || !accesses.length) return true;
+    return _somePermissions(accesses);
+  }
+
+  /**
+   * 是否包含指定的所有权限
+   * @param accesses
+   */
+  function hasEveryPermission(accesses: string[]): boolean {
+    // const permissionsList = userStore.getPermissions;
+    // if (Array.isArray(accesses)) {
+    //   return permissionsList.every((access: any) =>
+    //     accesses.includes(access.value)
+    //   );
+    // }
+    return false;
+    // throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
+  }
+
+  /**
+   * 是否包含其中某个权限
+   * @param accesses
+   * @param accessMap
+   */
+  function hasSomePermission(accesses: string[]): boolean {
+    // const permissionsList = userStore.getPermissions;
+    // if (Array.isArray(accesses)) {
+    //   return permissionsList.some((access: any) =>
+    //     accesses.includes(access.value)
+    //   );
+    // }
+    return false;
+    // throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
+  }
+
+  return { hasPermission, hasEveryPermission, hasSomePermission };
+}

+ 2 - 0
src/main.ts

@@ -7,6 +7,8 @@ import { setupStore } from './store';
 import 'dayjs/locale/zh-cn';
 import './styles/index.less';
 import './utils/rem';
+// 提前导入 VIP 弹窗状态模块,确保事件监听器在路由守卫之前注册
+import './store/modules/vipModal';
 // import { storage } from './utils/storage';
 // import { ACCESS_TOKEN_ADMIN } from './store/mutation-types';
 import useErrorLog from './hooks/useErrorLog';

+ 1 - 1
src/plugins/index.ts

@@ -1 +1 @@
-export { setupNaive } from '@/plugins/naive';
+export { setupNaive } from '@/plugins/naive';

+ 139 - 139
src/plugins/naive.ts

@@ -1,139 +1,139 @@
-import type { App } from 'vue';
-import * as NaiveUI from 'naive-ui';
-import { computed } from 'vue';
-
-// import { MessageProviderProps } from 'naive-ui';
-import { lighten } from '@/utils';
-import setting from '@/settings/designSetting';
-
-// NaiveUI 全局方法注册
-const configProviderPropsRef = computed(() => ({
-  theme: undefined,
-  themeOverrides: {
-    common: {
-      primaryColor: setting.appTheme,
-      primaryColorHover: lighten(setting.appTheme, 6),
-      primaryColorPressed: lighten(setting.appTheme, 6)
-    },
-    LoadingBar: {
-      colorLoading: setting.appTheme
-    }
-  }
-}));
-
-// const messageProviderPropsRef = computed(
-//   () =>
-//     ({
-//       closable: false,
-//       containerStyle: 'undefined',
-//       duration: 3,
-//       keepAliveOnHover: false,
-//       max: 1,
-//       placement: 'top',
-//       to: 'body'
-//     } as MessageProviderProps)
-// );
-
-// const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
-//   ['message', 'dialog', 'notification', 'loadingBar'],
-//   {
-//     configProviderProps: configProviderPropsRef
-//   }
-// )
-
-// const w = window as any
-// window['$message'] = message
-// window['$dialog'] = dialog
-// window['$notification'] = notification
-// window['$loading'] = loadingBar
-
-const naive = NaiveUI.create({
-  components: [
-    NaiveUI.NMessageProvider,
-    NaiveUI.NDialogProvider,
-    NaiveUI.NConfigProvider,
-    NaiveUI.NInput,
-    NaiveUI.NButton,
-    NaiveUI.NForm,
-    NaiveUI.NFormItem,
-    NaiveUI.NCheckboxGroup,
-    NaiveUI.NCheckbox,
-    NaiveUI.NIcon,
-    NaiveUI.NLayout,
-    NaiveUI.NLayoutHeader,
-    NaiveUI.NLayoutContent,
-    NaiveUI.NLayoutFooter,
-    NaiveUI.NLayoutSider,
-    NaiveUI.NMenu,
-    NaiveUI.NBreadcrumb,
-    NaiveUI.NBreadcrumbItem,
-    NaiveUI.NDropdown,
-    NaiveUI.NSpace,
-    NaiveUI.NTooltip,
-    NaiveUI.NAvatar,
-    NaiveUI.NTabs,
-    NaiveUI.NTabPane,
-    NaiveUI.NCard,
-    NaiveUI.NRow,
-    NaiveUI.NCol,
-    NaiveUI.NDrawer,
-    NaiveUI.NDrawerContent,
-    NaiveUI.NDivider,
-    NaiveUI.NSwitch,
-    NaiveUI.NBadge,
-    NaiveUI.NAlert,
-    NaiveUI.NElement,
-    NaiveUI.NTag,
-    NaiveUI.NNotificationProvider,
-    NaiveUI.NProgress,
-    NaiveUI.NDatePicker,
-    NaiveUI.NGrid,
-    NaiveUI.NGridItem,
-    NaiveUI.NList,
-    NaiveUI.NListItem,
-    NaiveUI.NThing,
-    NaiveUI.NDataTable,
-    NaiveUI.NPopover,
-    NaiveUI.NPagination,
-    NaiveUI.NSelect,
-    NaiveUI.NRadioGroup,
-    NaiveUI.NRadio,
-    NaiveUI.NSteps,
-    NaiveUI.NStep,
-    NaiveUI.NInputGroup,
-    NaiveUI.NResult,
-    NaiveUI.NDescriptions,
-    NaiveUI.NDescriptionsItem,
-    NaiveUI.NTable,
-    NaiveUI.NInputNumber,
-    NaiveUI.NLoadingBarProvider,
-    NaiveUI.NModal,
-    NaiveUI.NUpload,
-    NaiveUI.NTree,
-    NaiveUI.NSpin,
-    NaiveUI.NTimePicker,
-    NaiveUI.NBackTop,
-    NaiveUI.NSkeleton
-  ]
-});
-
-const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
-  ['message', 'dialog', 'notification', 'loadingBar'],
-  {
-    configProviderProps: configProviderPropsRef
-    // messageProviderProps: messageProviderPropsRef
-  }
-);
-
-(window as any)['$message'] = message;
-(window as any)['$loadingBar'] = loadingBar;
-
-// const w = window as any
-// window['$message'] = message
-// window['$dialog'] = dialog
-// window['$notification'] = notification
-// window['$loading'] = loadingBar
-
-export function setupNaive(app: App<Element>) {
-  app.use(naive);
-}
+import type { App } from 'vue';
+import * as NaiveUI from 'naive-ui';
+import { computed } from 'vue';
+
+// import { MessageProviderProps } from 'naive-ui';
+import { lighten } from '@/utils';
+import setting from '@/settings/designSetting';
+
+// NaiveUI 全局方法注册
+const configProviderPropsRef = computed(() => ({
+  theme: undefined,
+  themeOverrides: {
+    common: {
+      primaryColor: setting.appTheme,
+      primaryColorHover: lighten(setting.appTheme, 6),
+      primaryColorPressed: lighten(setting.appTheme, 6)
+    },
+    LoadingBar: {
+      colorLoading: setting.appTheme
+    }
+  }
+}));
+
+// const messageProviderPropsRef = computed(
+//   () =>
+//     ({
+//       closable: false,
+//       containerStyle: 'undefined',
+//       duration: 3,
+//       keepAliveOnHover: false,
+//       max: 1,
+//       placement: 'top',
+//       to: 'body'
+//     } as MessageProviderProps)
+// );
+
+// const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
+//   ['message', 'dialog', 'notification', 'loadingBar'],
+//   {
+//     configProviderProps: configProviderPropsRef
+//   }
+// )
+
+// const w = window as any
+// window['$message'] = message
+// window['$dialog'] = dialog
+// window['$notification'] = notification
+// window['$loading'] = loadingBar
+
+const naive = NaiveUI.create({
+  components: [
+    NaiveUI.NMessageProvider,
+    NaiveUI.NDialogProvider,
+    NaiveUI.NConfigProvider,
+    NaiveUI.NInput,
+    NaiveUI.NButton,
+    NaiveUI.NForm,
+    NaiveUI.NFormItem,
+    NaiveUI.NCheckboxGroup,
+    NaiveUI.NCheckbox,
+    NaiveUI.NIcon,
+    NaiveUI.NLayout,
+    NaiveUI.NLayoutHeader,
+    NaiveUI.NLayoutContent,
+    NaiveUI.NLayoutFooter,
+    NaiveUI.NLayoutSider,
+    NaiveUI.NMenu,
+    NaiveUI.NBreadcrumb,
+    NaiveUI.NBreadcrumbItem,
+    NaiveUI.NDropdown,
+    NaiveUI.NSpace,
+    NaiveUI.NTooltip,
+    NaiveUI.NAvatar,
+    NaiveUI.NTabs,
+    NaiveUI.NTabPane,
+    NaiveUI.NCard,
+    NaiveUI.NRow,
+    NaiveUI.NCol,
+    NaiveUI.NDrawer,
+    NaiveUI.NDrawerContent,
+    NaiveUI.NDivider,
+    NaiveUI.NSwitch,
+    NaiveUI.NBadge,
+    NaiveUI.NAlert,
+    NaiveUI.NElement,
+    NaiveUI.NTag,
+    NaiveUI.NNotificationProvider,
+    NaiveUI.NProgress,
+    NaiveUI.NDatePicker,
+    NaiveUI.NGrid,
+    NaiveUI.NGridItem,
+    NaiveUI.NList,
+    NaiveUI.NListItem,
+    NaiveUI.NThing,
+    NaiveUI.NDataTable,
+    NaiveUI.NPopover,
+    NaiveUI.NPagination,
+    NaiveUI.NSelect,
+    NaiveUI.NRadioGroup,
+    NaiveUI.NRadio,
+    NaiveUI.NSteps,
+    NaiveUI.NStep,
+    NaiveUI.NInputGroup,
+    NaiveUI.NResult,
+    NaiveUI.NDescriptions,
+    NaiveUI.NDescriptionsItem,
+    NaiveUI.NTable,
+    NaiveUI.NInputNumber,
+    NaiveUI.NLoadingBarProvider,
+    NaiveUI.NModal,
+    NaiveUI.NUpload,
+    NaiveUI.NTree,
+    NaiveUI.NSpin,
+    NaiveUI.NTimePicker,
+    NaiveUI.NBackTop,
+    NaiveUI.NSkeleton
+  ]
+});
+
+const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
+  ['message', 'dialog', 'notification', 'loadingBar'],
+  {
+    configProviderProps: configProviderPropsRef
+    // messageProviderProps: messageProviderPropsRef
+  }
+);
+
+(window as any)['$message'] = message;
+(window as any)['$loadingBar'] = loadingBar;
+
+// const w = window as any
+// window['$message'] = message
+// window['$dialog'] = dialog
+// window['$notification'] = notification
+// window['$loading'] = loadingBar
+
+export function setupNaive(app: App<Element>) {
+  app.use(naive);
+}

+ 40 - 40
src/plugins/version.ts

@@ -1,40 +1,40 @@
-// versionUpdatePlugin.js
-import fs from 'fs';
-import path from 'path';
-
-const writeVersion = (versionFile: any, content: any) => {
-  // 写入文件
-  fs.writeFile(versionFile, content, err => {
-    if (err) throw err;
-  });
-};
-
-export default (options: any) => {
-  let config: any;
-  console.log('加载插件');
-  return {
-    name: 'version-update',
-    configResolved(resolvedConfig: any) {
-      // 存储最终解析的配置
-      config = resolvedConfig;
-    },
-
-    buildStart() {
-      // 生成版本信息文件路径
-      const file = config.publicDir + path.sep + 'version.json';
-      // 这里使用编译时间作为版本信息
-      const content = JSON.stringify({ version: options.version });
-
-      if (fs.existsSync(config.publicDir)) {
-        writeVersion(file, content);
-      } else {
-        fs.mkdir(config.publicDir, err => {
-          console.log(err, '引入报错了');
-          if (err) throw err;
-
-          writeVersion(file, content);
-        });
-      }
-    }
-  };
-};
+// versionUpdatePlugin.js
+import fs from 'fs';
+import path from 'path';
+
+const writeVersion = (versionFile: any, content: any) => {
+  // 写入文件
+  fs.writeFile(versionFile, content, err => {
+    if (err) throw err;
+  });
+};
+
+export default (options: any) => {
+  let config: any;
+  console.log('加载插件');
+  return {
+    name: 'version-update',
+    configResolved(resolvedConfig: any) {
+      // 存储最终解析的配置
+      config = resolvedConfig;
+    },
+
+    buildStart() {
+      // 生成版本信息文件路径
+      const file = config.publicDir + path.sep + 'version.json';
+      // 这里使用编译时间作为版本信息
+      const content = JSON.stringify({ version: options.version });
+
+      if (fs.existsSync(config.publicDir)) {
+        writeVersion(file, content);
+      } else {
+        fs.mkdir(config.publicDir, err => {
+          console.log(err, '引入报错了');
+          if (err) throw err;
+
+          writeVersion(file, content);
+        });
+      }
+    }
+  };
+};

+ 10 - 8
src/router/router-guards.ts

@@ -99,13 +99,15 @@ export function createRouterGuards(router: Router) {
 
     if (!userStore.getNickname) {
       await userStore.getInfo();
-      // 检测会员状态
-      const membershipEndTime = userStore.getUserInfo?.membershipEndTime;
-      const isExpired =
-        !membershipEndTime || new Date(membershipEndTime) < new Date();
-      if (isExpired) {
-        // 通过全局事件触发购买弹窗
-        eventGlobal.emit('show-vip-purchase');
+      // 检测会员状态(后台管理员预览模式不弹窗)
+      if (userAuth.authSource !== 'admin') {
+        const membershipEndTime = userStore.getUserInfo?.membershipEndTime;
+        const isExpired =
+          !membershipEndTime || new Date(membershipEndTime) < new Date();
+        if (isExpired) {
+          // 通过全局事件触发购买弹窗
+          eventGlobal.emit('show-vip-purchase');
+        }
       }
     }
 
@@ -118,7 +120,7 @@ export function createRouterGuards(router: Router) {
     // window.$loadingBar && window.$loadingBar.finish();
   });
 
-  router.afterEach((to, _, failure) => {
+  router.afterEach((_to, _, failure) => {
     if (isNavigationFailure(failure)) {
       console.log('failed navigation', failure);
     }

+ 21 - 23
src/screen-tips/index.tsx

@@ -1,23 +1,21 @@
-import { defineComponent } from 'vue';
-import styles from './index.module.less';
-import commentBg from './images/tip-bg.png';
-import commentTop from './images/tip-dang.png';
-
-export default defineComponent({
-  name: 'screen-tips',
-  setup() {
-
-
-    return () => (
-      <div class={styles.commonWork}>
-        <img src={commentTop} class={styles.dingPng} alt="" />
-        <img src={commentBg} class={styles.downMoveBg} alt="" />
-        <h2>温馨提示</h2>
-        
-        <div class={styles.header}>
-          为了更好的上课体验,请切换到<span>横屏模式</span>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import commentBg from './images/tip-bg.png';
+import commentTop from './images/tip-dang.png';
+
+export default defineComponent({
+  name: 'screen-tips',
+  setup() {
+    return () => (
+      <div class={styles.commonWork}>
+        <img src={commentTop} class={styles.dingPng} alt="" />
+        <img src={commentBg} class={styles.downMoveBg} alt="" />
+        <h2>温馨提示</h2>
+
+        <div class={styles.header}>
+          为了更好的上课体验,请切换到<span>横屏模式</span>
+        </div>
+      </div>
+    );
+  }
+});

+ 32 - 32
src/settings/designSetting.ts

@@ -1,32 +1,32 @@
-// app theme preset color
-export const appThemeList: string[] = [
-  '#2d8cf0',
-  '#0960bd',
-  '#0084f4',
-  '#009688',
-  '#536dfe',
-  '#ff5c93',
-  '#ee4f12',
-  '#0096c7',
-  '#9c27b0',
-  '#ff9800',
-  '#FF3D68',
-  '#00C1D4',
-  '#71EFA3',
-  '#171010',
-  '#78DEC7',
-  '#1768AC',
-  '#FB9300',
-  '#FC5404'
-];
-
-const setting = {
-  //深色主题
-  darkTheme: false,
-  //系统主题色
-  appTheme: '#198CFE',
-  //系统内置主题色列表
-  appThemeList
-};
-
-export default setting;
+// app theme preset color
+export const appThemeList: string[] = [
+  '#2d8cf0',
+  '#0960bd',
+  '#0084f4',
+  '#009688',
+  '#536dfe',
+  '#ff5c93',
+  '#ee4f12',
+  '#0096c7',
+  '#9c27b0',
+  '#ff9800',
+  '#FF3D68',
+  '#00C1D4',
+  '#71EFA3',
+  '#171010',
+  '#78DEC7',
+  '#1768AC',
+  '#FB9300',
+  '#FC5404'
+];
+
+const setting = {
+  //深色主题
+  darkTheme: false,
+  //系统主题色
+  appTheme: '#198CFE',
+  //系统内置主题色列表
+  appThemeList
+};
+
+export default setting;

+ 10 - 10
src/store/index.ts

@@ -1,10 +1,10 @@
-import type { App } from 'vue';
-import { createPinia } from 'pinia';
-
-const store = createPinia();
-
-export function setupStore(app: App<Element>) {
-  app.use(store);
-}
-
-export { store };
+import type { App } from 'vue';
+import { createPinia } from 'pinia';
+
+const store = createPinia();
+
+export function setupStore(app: App<Element>) {
+  app.use(store);
+}
+
+export { store };

+ 40 - 40
src/store/modules/designSetting.ts

@@ -1,40 +1,40 @@
-import { defineStore } from 'pinia';
-import { store } from '@/store';
-import designSetting from '@/settings/designSetting';
-
-const { darkTheme, appTheme, appThemeList } = designSetting;
-
-interface DesignSettingState {
-  //深色主题
-  darkTheme: boolean;
-  //系统风格
-  appTheme: string;
-  //系统内置风格
-  appThemeList: string[];
-}
-
-export const useDesignSettingStore = defineStore({
-  id: 'app-design-setting',
-  state: (): DesignSettingState => ({
-    darkTheme,
-    appTheme,
-    appThemeList
-  }),
-  getters: {
-    getDarkTheme(): boolean {
-      return this.darkTheme;
-    },
-    getAppTheme(): string {
-      return this.appTheme;
-    },
-    getAppThemeList(): string[] {
-      return this.appThemeList;
-    }
-  },
-  actions: {}
-});
-
-// Need to be used outside the setup
-export function useDesignSettingWithOut() {
-  return useDesignSettingStore(store);
-}
+import { defineStore } from 'pinia';
+import { store } from '@/store';
+import designSetting from '@/settings/designSetting';
+
+const { darkTheme, appTheme, appThemeList } = designSetting;
+
+interface DesignSettingState {
+  //深色主题
+  darkTheme: boolean;
+  //系统风格
+  appTheme: string;
+  //系统内置风格
+  appThemeList: string[];
+}
+
+export const useDesignSettingStore = defineStore({
+  id: 'app-design-setting',
+  state: (): DesignSettingState => ({
+    darkTheme,
+    appTheme,
+    appThemeList
+  }),
+  getters: {
+    getDarkTheme(): boolean {
+      return this.darkTheme;
+    },
+    getAppTheme(): string {
+      return this.appTheme;
+    },
+    getAppThemeList(): string[] {
+      return this.appThemeList;
+    }
+  },
+  actions: {}
+});
+
+// Need to be used outside the setup
+export function useDesignSettingWithOut() {
+  return useDesignSettingStore(store);
+}

+ 268 - 268
src/store/modules/prepareLessons.ts

@@ -1,268 +1,268 @@
-import { defineStore } from 'pinia';
-import { store } from '@/store';
-
-export const usePrepareStore = defineStore('prepare-lessons-store', {
-  state: () => ({
-    subjectId: null as any, // 基础声部
-    instrumentId: null as any, // 基础乐器
-    baseCourseware: {} as any, // 基础教学课件
-    selectKey: '', // 选的哪一节课
-    lessonCoursewareId: '', // 哪个教材分类
-    subjectList: [] as any, // 教材带的声部列表
-    instrumentList: [] as any, // 教材自带的乐器
-    lessonCoursewareDetailId: '', // 哪个教材详情
-    treeList: [] as any[], // 左边教学课件列表
-    coursewareList: [] as any[], // 课件信息
-    trainList: [] as any[], // 训练信息
-    tabType: 'courseware', // 备课 - 课件 | 训练 类型切换 'courseware' | 'train'
-    selectMusicStatus: false, // 乐谱状态
-    selectResourceStatus: false, // 资源状态
-    isAddResource: false, // 是否添加资源
-    isEditResource: false, // 是否编辑资源 - 课件编辑状态
-    iseditTrain: false, // 是否编辑训练,
-    isAddTrain: false, // 是否添加训练,
-    classGroupId: null as any, // 班级编号
-    beforeLoading: false, // 添加空白课件时,相关资源图片没有值时,默认选中共享资源
-  }),
-  getters: {
-    /** 获取资源状态 */
-    getSubjectId(): [string, number] {
-      return this.subjectId;
-    },
-    /** 获取乐器编号 */
-    getInstrumentId(): [string, number] {
-      return this.instrumentId;
-    },
-    /** 获取基础教学课件 */
-    getBaseCourseware(): any {
-      return this.baseCourseware;
-    },
-    /** 获取选择课的编号 */
-    getSelectKey(): string {
-      return this.selectKey;
-    },
-    /** 获取选择课的名称 */
-    getSelectName(): string {
-      let title = '';
-      for (let index = 0; index < this.getTreeList.length; index++) {
-        const element = this.getTreeList[index];
-        if (element.id === this.selectKey) {
-          title = element.name;
-        }
-
-        if (!title && element.knowledgeList.length > 0) {
-          element.knowledgeList?.forEach((item: any) => {
-            if (item.id === this.selectKey) {
-              title = item.name;
-            }
-          });
-        }
-      }
-      return title;
-    },
-    /** 获取教材编号 */
-    getLessonCoursewareId(): string {
-      return this.lessonCoursewareId;
-    },
-    /** 获取分类编号 */
-    getLessonCoursewareDetailId(): string {
-      return this.lessonCoursewareDetailId;
-    },
-    /** 获取树形控件 */
-    getTreeList(): any[] {
-      return this.treeList;
-    },
-    /** 获取课件列表 */
-    getCoursewareList(): any[] {
-      return this.coursewareList;
-    },
-    /** 获取训练列表 */
-    getTrainList(): any[] {
-      return this.trainList;
-    },
-    /** 获取课件类型 */
-    getTabType(): string {
-      return this.tabType;
-    },
-    /** 获取乐谱状态 */
-    getSelectMusicStatus(): boolean {
-      return this.selectMusicStatus;
-    },
-    /** 获取资源状态 */
-    getSelectResourceStatus(): boolean {
-      return this.selectResourceStatus;
-    },
-    /** 获取是否添加资源 */
-    getIsAddResource(): boolean {
-      return this.isAddResource;
-    },
-    /** 获取是否修改资源 */
-    getIsEditResource(): boolean {
-      return this.isEditResource;
-    },
-    /** 获取是否修改训练 */
-    getIsEditTrain(): boolean {
-      return this.iseditTrain;
-    },
-    /** 获取是否添加训练 */
-    getIsAddTrain(): boolean {
-      return this.isAddTrain;
-    },
-    /** 获取声部列表 */
-    getSubjectList(): any {
-      return this.subjectList;
-    },
-    /** 获取乐器列表 */
-    getInstrumentList(): any {
-      return this.instrumentList;
-    },
-    /** 获取乐器格式化列表 */
-    getFormatInstrumentList(): any {
-      const tempSubjectInstruments: any = [];
-      this.instrumentList.forEach((item: any) => {
-        item.value = item.id;
-        item.label = item.name;
-        if (item.instruments && item.instruments.length > 0) {
-          item.instruments.forEach((child: any) => {
-            child.label = child.name;
-            child.value = child.id;
-          });
-        }
-
-        const tempSi = {
-          value: item.id,
-          label: item.name,
-          id: item.id,
-          name: item.name,
-          instruments: [] as any
-        };
-        if (item.instruments) {
-          if (item.instruments.length == 1) {
-            tempSi.value = item.instruments[0].id;
-            tempSi.label = item.instruments[0].name;
-            tempSi.id = item.id;
-            tempSi.name = item.name;
-          } else if (item.instruments.length > 1) {
-            item.instruments.forEach((child: any) => {
-              child.label = child.name;
-              child.value = child.id;
-              tempSi.instruments.push({
-                label: child.name,
-                value: child.id,
-                id: child.id,
-                name: child.name
-              });
-            });
-          }
-        }
-        tempSubjectInstruments.push(tempSi);
-      });
-      return tempSubjectInstruments;
-    },
-    /** 获取所有乐器列表 */
-    getSingleInstrumentList(): any {
-      const temps: any = [];
-      this.instrumentList.forEach((item: any) => {
-        if (Array.isArray(item.instruments)) {
-          item.instruments.forEach((child: any) => {
-            temps.push(child);
-          });
-        }
-      });
-      return temps;
-    },
-    /** 获取班级编号 */
-    getClassGroupId(): string | number {
-      return this.classGroupId;
-    },
-    getBeforeLoading(): boolean {
-      return this.beforeLoading
-    }
-  },
-  actions: {
-    setBeforeLoading(val: boolean) {
-      this.beforeLoading = val
-    },
-    /** 设置基础声部 */
-    setSubjectId(subjectId: string | number) {
-      this.subjectId = subjectId;
-    },
-    /** 获取乐器编号 */
-    setInstrumentId(instrumentId: string | number) {
-      this.instrumentId = instrumentId;
-    },
-    /** 设置基础教学课件 */
-    setBaseCourseware(baseCourseware: any) {
-      this.baseCourseware = baseCourseware;
-    },
-    /** 设置课的编号 */
-    setSelectKey(key: string) {
-      this.selectKey = key;
-    },
-    /** 设置教材的编号 */
-    setLessonCoursewareId(id: string) {
-      this.lessonCoursewareId = id;
-    },
-    /** 设置分类的编号 */
-    setLessonCoursewareDetailId(id: string) {
-      this.lessonCoursewareDetailId = id;
-    },
-    /** 设置课的编号 */
-    setTreeList(list: any[]) {
-      this.treeList = list;
-    },
-    /** 设置课件列表 */
-    setCoursewareList(list: any[]) {
-      this.coursewareList = list;
-    },
-    /** 设置训练列表 */
-    setTrainList(list: any[]) {
-      this.trainList = list;
-    },
-    /** 设置tab类型 */
-    setTabType(type: string) {
-      this.tabType = type;
-    },
-    /** 设置乐谱状态 */
-    setSelectMusicStatus(status: boolean) {
-      this.selectMusicStatus = status;
-    },
-    /** 设置资源状态 */
-    setSelectResourceStatus(status: boolean) {
-      this.selectResourceStatus = status;
-    },
-    /** 设置资源状态 */
-    setIsAddResource(status: boolean) {
-      this.isAddResource = status;
-    },
-    /** 设置训练状态 */
-    setIsAddTrain(status: boolean) {
-      this.isAddTrain = status;
-    },
-    /** 设置资源状态 */
-    setIsEditResource(status: boolean) {
-      this.isEditResource = status;
-    },
-    /** 设置训练状态 */
-    setIsEditTrain(status: boolean) {
-      this.iseditTrain = status;
-    },
-    /** 设置声部列表 */
-    setSubjectList(subjects: any): any {
-      this.subjectList = subjects;
-    },
-    /** 设置乐器列表 */
-    setInstrumentList(instruments: any): any {
-      this.instrumentList = instruments;
-    },
-    /** 设置班级编号 */
-    setClassGroupId(id: string | number): any {
-      this.classGroupId = id;
-    }
-  }
-});
-
-// Need to be used outside the setup
-export function usePrepareLessonsStoreWidthOut() {
-  return usePrepareStore(store);
-}
+import { defineStore } from 'pinia';
+import { store } from '@/store';
+
+export const usePrepareStore = defineStore('prepare-lessons-store', {
+  state: () => ({
+    subjectId: null as any, // 基础声部
+    instrumentId: null as any, // 基础乐器
+    baseCourseware: {} as any, // 基础教学课件
+    selectKey: '', // 选的哪一节课
+    lessonCoursewareId: '', // 哪个教材分类
+    subjectList: [] as any, // 教材带的声部列表
+    instrumentList: [] as any, // 教材自带的乐器
+    lessonCoursewareDetailId: '', // 哪个教材详情
+    treeList: [] as any[], // 左边教学课件列表
+    coursewareList: [] as any[], // 课件信息
+    trainList: [] as any[], // 训练信息
+    tabType: 'courseware', // 备课 - 课件 | 训练 类型切换 'courseware' | 'train'
+    selectMusicStatus: false, // 乐谱状态
+    selectResourceStatus: false, // 资源状态
+    isAddResource: false, // 是否添加资源
+    isEditResource: false, // 是否编辑资源 - 课件编辑状态
+    iseditTrain: false, // 是否编辑训练,
+    isAddTrain: false, // 是否添加训练,
+    classGroupId: null as any, // 班级编号
+    beforeLoading: false // 添加空白课件时,相关资源图片没有值时,默认选中共享资源
+  }),
+  getters: {
+    /** 获取资源状态 */
+    getSubjectId(): [string, number] {
+      return this.subjectId;
+    },
+    /** 获取乐器编号 */
+    getInstrumentId(): [string, number] {
+      return this.instrumentId;
+    },
+    /** 获取基础教学课件 */
+    getBaseCourseware(): any {
+      return this.baseCourseware;
+    },
+    /** 获取选择课的编号 */
+    getSelectKey(): string {
+      return this.selectKey;
+    },
+    /** 获取选择课的名称 */
+    getSelectName(): string {
+      let title = '';
+      for (let index = 0; index < this.getTreeList.length; index++) {
+        const element = this.getTreeList[index];
+        if (element.id === this.selectKey) {
+          title = element.name;
+        }
+
+        if (!title && element.knowledgeList.length > 0) {
+          element.knowledgeList?.forEach((item: any) => {
+            if (item.id === this.selectKey) {
+              title = item.name;
+            }
+          });
+        }
+      }
+      return title;
+    },
+    /** 获取教材编号 */
+    getLessonCoursewareId(): string {
+      return this.lessonCoursewareId;
+    },
+    /** 获取分类编号 */
+    getLessonCoursewareDetailId(): string {
+      return this.lessonCoursewareDetailId;
+    },
+    /** 获取树形控件 */
+    getTreeList(): any[] {
+      return this.treeList;
+    },
+    /** 获取课件列表 */
+    getCoursewareList(): any[] {
+      return this.coursewareList;
+    },
+    /** 获取训练列表 */
+    getTrainList(): any[] {
+      return this.trainList;
+    },
+    /** 获取课件类型 */
+    getTabType(): string {
+      return this.tabType;
+    },
+    /** 获取乐谱状态 */
+    getSelectMusicStatus(): boolean {
+      return this.selectMusicStatus;
+    },
+    /** 获取资源状态 */
+    getSelectResourceStatus(): boolean {
+      return this.selectResourceStatus;
+    },
+    /** 获取是否添加资源 */
+    getIsAddResource(): boolean {
+      return this.isAddResource;
+    },
+    /** 获取是否修改资源 */
+    getIsEditResource(): boolean {
+      return this.isEditResource;
+    },
+    /** 获取是否修改训练 */
+    getIsEditTrain(): boolean {
+      return this.iseditTrain;
+    },
+    /** 获取是否添加训练 */
+    getIsAddTrain(): boolean {
+      return this.isAddTrain;
+    },
+    /** 获取声部列表 */
+    getSubjectList(): any {
+      return this.subjectList;
+    },
+    /** 获取乐器列表 */
+    getInstrumentList(): any {
+      return this.instrumentList;
+    },
+    /** 获取乐器格式化列表 */
+    getFormatInstrumentList(): any {
+      const tempSubjectInstruments: any = [];
+      this.instrumentList.forEach((item: any) => {
+        item.value = item.id;
+        item.label = item.name;
+        if (item.instruments && item.instruments.length > 0) {
+          item.instruments.forEach((child: any) => {
+            child.label = child.name;
+            child.value = child.id;
+          });
+        }
+
+        const tempSi = {
+          value: item.id,
+          label: item.name,
+          id: item.id,
+          name: item.name,
+          instruments: [] as any
+        };
+        if (item.instruments) {
+          if (item.instruments.length == 1) {
+            tempSi.value = item.instruments[0].id;
+            tempSi.label = item.instruments[0].name;
+            tempSi.id = item.id;
+            tempSi.name = item.name;
+          } else if (item.instruments.length > 1) {
+            item.instruments.forEach((child: any) => {
+              child.label = child.name;
+              child.value = child.id;
+              tempSi.instruments.push({
+                label: child.name,
+                value: child.id,
+                id: child.id,
+                name: child.name
+              });
+            });
+          }
+        }
+        tempSubjectInstruments.push(tempSi);
+      });
+      return tempSubjectInstruments;
+    },
+    /** 获取所有乐器列表 */
+    getSingleInstrumentList(): any {
+      const temps: any = [];
+      this.instrumentList.forEach((item: any) => {
+        if (Array.isArray(item.instruments)) {
+          item.instruments.forEach((child: any) => {
+            temps.push(child);
+          });
+        }
+      });
+      return temps;
+    },
+    /** 获取班级编号 */
+    getClassGroupId(): string | number {
+      return this.classGroupId;
+    },
+    getBeforeLoading(): boolean {
+      return this.beforeLoading;
+    }
+  },
+  actions: {
+    setBeforeLoading(val: boolean) {
+      this.beforeLoading = val;
+    },
+    /** 设置基础声部 */
+    setSubjectId(subjectId: string | number) {
+      this.subjectId = subjectId;
+    },
+    /** 获取乐器编号 */
+    setInstrumentId(instrumentId: string | number) {
+      this.instrumentId = instrumentId;
+    },
+    /** 设置基础教学课件 */
+    setBaseCourseware(baseCourseware: any) {
+      this.baseCourseware = baseCourseware;
+    },
+    /** 设置课的编号 */
+    setSelectKey(key: string) {
+      this.selectKey = key;
+    },
+    /** 设置教材的编号 */
+    setLessonCoursewareId(id: string) {
+      this.lessonCoursewareId = id;
+    },
+    /** 设置分类的编号 */
+    setLessonCoursewareDetailId(id: string) {
+      this.lessonCoursewareDetailId = id;
+    },
+    /** 设置课的编号 */
+    setTreeList(list: any[]) {
+      this.treeList = list;
+    },
+    /** 设置课件列表 */
+    setCoursewareList(list: any[]) {
+      this.coursewareList = list;
+    },
+    /** 设置训练列表 */
+    setTrainList(list: any[]) {
+      this.trainList = list;
+    },
+    /** 设置tab类型 */
+    setTabType(type: string) {
+      this.tabType = type;
+    },
+    /** 设置乐谱状态 */
+    setSelectMusicStatus(status: boolean) {
+      this.selectMusicStatus = status;
+    },
+    /** 设置资源状态 */
+    setSelectResourceStatus(status: boolean) {
+      this.selectResourceStatus = status;
+    },
+    /** 设置资源状态 */
+    setIsAddResource(status: boolean) {
+      this.isAddResource = status;
+    },
+    /** 设置训练状态 */
+    setIsAddTrain(status: boolean) {
+      this.isAddTrain = status;
+    },
+    /** 设置资源状态 */
+    setIsEditResource(status: boolean) {
+      this.isEditResource = status;
+    },
+    /** 设置训练状态 */
+    setIsEditTrain(status: boolean) {
+      this.iseditTrain = status;
+    },
+    /** 设置声部列表 */
+    setSubjectList(subjects: any): any {
+      this.subjectList = subjects;
+    },
+    /** 设置乐器列表 */
+    setInstrumentList(instruments: any): any {
+      this.instrumentList = instruments;
+    },
+    /** 设置班级编号 */
+    setClassGroupId(id: string | number): any {
+      this.classGroupId = id;
+    }
+  }
+});
+
+// Need to be used outside the setup
+export function usePrepareLessonsStoreWidthOut() {
+  return usePrepareStore(store);
+}

+ 31 - 0
src/store/modules/vipModal.ts

@@ -0,0 +1,31 @@
+import { ref } from 'vue';
+import { eventGlobal } from '@/utils';
+
+// VIP购买弹窗的响应式状态
+const showVipModal = ref(false);
+const hasClose = ref(false);
+const modalKey = ref(0);
+
+// 在应用初始化时立即注册事件监听器(必须在路由守卫之前)
+// 这样路由守卫 emit 事件时监听器已经准备好
+eventGlobal.on('show-vip-purchase', (val?: boolean) => {
+  // 更新 key 强制重新创建组件(解决手动删除DOM后状态残留问题)
+  modalKey.value++;
+  hasClose.value = val || false;
+  showVipModal.value = true;
+});
+
+// 监听重置VIP弹窗事件(退出登录时重置状态)
+eventGlobal.on('reset-vip-modal', () => {
+  showVipModal.value = false;
+  hasClose.value = false;
+});
+
+// 导出状态供 App.tsx 使用
+export function useVipModalState() {
+  return {
+    showVipModal,
+    hasClose,
+    modalKey
+  };
+}

+ 5 - 5
src/store/mutation-types.ts

@@ -1,5 +1,5 @@
-export const ACCESS_TOKEN = 'ACCESS-TOKEN-TEACHER'; // 用户token
-export const ACCESS_TOKEN_ADMIN = 'ACCESS-TOKEN-TEACHER--ADMIN'; // 用户token
-export const IM_TOKEN = 'IM-TOKEN'; //
-export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
-export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
+export const ACCESS_TOKEN = 'ACCESS-TOKEN-TEACHER'; // 用户token
+export const ACCESS_TOKEN_ADMIN = 'ACCESS-TOKEN-TEACHER--ADMIN'; // 用户token
+export const IM_TOKEN = 'IM-TOKEN'; //
+export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
+export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页

+ 29 - 29
src/utils/cipher.ts

@@ -1,29 +1,29 @@
-import { encrypt, decrypt } from 'crypto-js/aes';
-import { parse } from 'crypto-js/enc-utf8';
-import pkcs7 from 'crypto-js/pad-pkcs7';
-import ECB from 'crypto-js/mode-ecb';
-import UTF8 from 'crypto-js/enc-utf8';
-// 注意 key 和 iv 至少都需要 16 位
-const AES_KEY = '1111111111000000';
-const AES_IV = '0000001111111111';
-export class AesEncryption {
-  private key;
-  private iv;
-  constructor(key = AES_KEY, iv = AES_IV) {
-    this.key = parse(key);
-    this.iv = parse(iv);
-  }
-  get getOptions() {
-    return {
-      mode: ECB,
-      padding: pkcs7,
-      iv: this.iv
-    };
-  }
-  encryptByAES(text: string) {
-    return encrypt(text, this.key, this.getOptions).toString();
-  }
-  decryptByAES(text: string) {
-    return decrypt(text, this.key, this.getOptions).toString(UTF8);
-  }
-}
+import { encrypt, decrypt } from 'crypto-js/aes';
+import { parse } from 'crypto-js/enc-utf8';
+import pkcs7 from 'crypto-js/pad-pkcs7';
+import ECB from 'crypto-js/mode-ecb';
+import UTF8 from 'crypto-js/enc-utf8';
+// 注意 key 和 iv 至少都需要 16 位
+const AES_KEY = '1111111111000000';
+const AES_IV = '0000001111111111';
+export class AesEncryption {
+  private key;
+  private iv;
+  constructor(key = AES_KEY, iv = AES_IV) {
+    this.key = parse(key);
+    this.iv = parse(iv);
+  }
+  get getOptions() {
+    return {
+      mode: ECB,
+      padding: pkcs7,
+      iv: this.iv
+    };
+  }
+  encryptByAES(text: string) {
+    return encrypt(text, this.key, this.getOptions).toString();
+  }
+  decryptByAES(text: string) {
+    return decrypt(text, this.key, this.getOptions).toString(UTF8);
+  }
+}

+ 6 - 6
src/utils/contants.ts

@@ -26,16 +26,16 @@ export const resourceType = {
   MUSIC: '乐谱',
   IMG: '图片',
   SONG: '音频',
-  VIDEO: '视频',
+  VIDEO: '视频'
   // PPT: 'PPT'
 };
 
 export const resourceTypeLesson = {
-  IMG: "图片",
-  MUSIC: "乐谱",
-  SONG: "音频",
-  VIDEO: "视频"
-}
+  IMG: '图片',
+  MUSIC: '乐谱',
+  SONG: '音频',
+  VIDEO: '视频'
+};
 
 /**
  * @description: 评测难度

+ 164 - 163
src/utils/dateFormat.ts

@@ -1,163 +1,164 @@
-import dayjs from 'dayjs';
-import { get } from 'http';
-
-export function getNowDateAndMonday(time: number) {
-  let timestamp = time;
-  const serverDate = new Date(time);
-  if (serverDate.getDay() == 0) {
-    timestamp -= 7 * 24 * 60 * 60 * 1000;
-  }
-  const mondayTime =
-    timestamp - (serverDate.getDay() - 1) * 24 * 60 * 60 * 1000;
-
-  const mondayData = new Date(mondayTime).getTime();
-  //年
-  // const mondayY = mondayData.getFullYear();
-  // //月
-  // const mondayM =
-  //   mondayData.getMonth() + 1 < 10
-  //     ? '0' + (mondayData.getMonth() + 1)
-  //     : mondayData.getMonth() + 1;
-  // //日
-  // const mondayD =
-  //   mondayData.getDate() < 10
-  //     ? '0' + mondayData.getDate()
-  //     : mondayData.getDate();
-
-  // const str = mondayY + '-' + mondayM + '-' + mondayD;
-  return mondayData;
-}
-export function getNowDateAndSunday(time: number) {
-  const timestamp = time;
-  const serverDate = new Date(time);
-
-  let num = 7 - serverDate.getDay();
-  if (num == 7) {
-    num = 0;
-  }
-  const sundayTiem = timestamp + num * 24 * 60 * 60 * 1000;
-  const SundayData = new Date(sundayTiem).getTime();
-  //年
-  // const tomorrowY = SundayData.getFullYear(); //月
-  // const tomorrowM =
-  //   SundayData.getMonth() + 1 < 10
-  //     ? '0' + (SundayData.getMonth() + 1)
-  //     : SundayData.getMonth() + 1;
-  // //日
-  // const tomorrowD =
-  //   SundayData.getDate() < 10
-  //     ? '0' + SundayData.getDate()
-  //     : SundayData.getDate();
-  // const str = tomorrowY + '-' + tomorrowM + '-' + tomorrowD;
-
-  return SundayData;
-}
-
-export const getTimes = (
-  times: any,
-  keys: Array<string> = [],
-  format = 'YYYY-MM-DD'
-) => {
-  if (times && times.length) {
-    return format == 'YYYY-MM-DD'
-      ? {
-          [keys[0] || 'start']: dayjs(times[0]).isValid()
-            ? dayjs(times[0]).format(format) + ' 00:00:00'
-            : '',
-          [keys[1] || 'end']: dayjs(times[1]).isValid()
-            ? dayjs(times[1]).format(format) + ' 23:59:59'
-            : ''
-        }
-      : {
-          [keys[0] || 'start']: dayjs(times[0]).isValid()
-            ? dayjs(times[0]).format(format)
-            : '',
-          [keys[1] || 'end']: dayjs(times[1]).isValid()
-            ? dayjs(times[1]).format(format)
-            : ''
-        };
-  }
-  return {};
-};
-
-export function formatTime(time: number) {
-  const hours = Math.floor(time / 3600);
-
-  const minutes = Math.floor(Math.floor(time % 3600) / 60);
-
-  const seconds = Math.floor(time % 60);
-
-  const h = hours.toString().length === 1 ? `0${hours}` : hours;
-
-  const m = minutes.toString().length === 1 ? `0${minutes}` : minutes;
-
-  const s = seconds.toString().length === 1 ? `0${seconds}` : seconds;
-  let str = '';
-  if (hours > 0) {
-    str = `${h}小时${m}分钟${s}秒`;
-  } else if (minutes > 0) {
-    str = `${m}分钟${s}秒`;
-  } else {
-    str = `${s}秒`;
-  }
-  console.log();
-  return str;
-}
-
-export function getHours(time: number) {
-  const minutes = Math.floor(time / 60 / 60);
-  return minutes;
-}
-
-export function getLastMinutes(time: number) {
-  const minutes = Math.floor(time / 60);
-  return Math.floor(minutes % 60)
-}
-
-export function getMinutes(time: number) {
-  const minutes = Math.floor(time / 60);
-  return minutes;
-}
-
-export function getSecend(time: number) {
-  const seconds = Math.floor(time % 60);
-  return seconds;
-}
-
-export function getChatMinutes(time: number) {
-  const minutes = Math.floor(time / 60);
-  let s = 0
-  if(time % 60) {
-    s = Math.ceil(time % 60 / 60 * 100) / 100
-  }
-  return minutes + s;
-}
-
-
-// 秒转时分秒
-export function formateSeconds(endTime: string, pad = 2) {
-  let secondTime = parseInt(endTime) //将传入的秒的值转化为Number
-  let min = 0 // 初始化分
-  let h = 0 // 初始化小时
-  let result = ''
-  if (secondTime >= 60) {
-    //如果秒数大于等于60,将秒数转换成整数
-    min = parseInt(secondTime / 60 + '') //获取分钟,除以60取整数,得到整数分钟
-    secondTime = parseInt((secondTime % 60) + '') //获取秒数,秒数取佘,得到整数秒数
-    if (min >= 60) {
-      //如果分钟大于等于60,将分钟转换成小时
-      h = parseInt(min / 60 + '') //获取小时,获取分钟除以60,得到整数小时
-      min = parseInt((min % 60) + '') //获取小时后取佘的分,获取分钟除以60取佘的分
-    }
-  }
-  if (h) {
-    result = `${h.toString().padStart(pad, '0')}时${min.toString().padStart(pad, '0')}分${secondTime
-      .toString()
-      .padStart(pad, '0')}秒`
-  } else if (min) {
-    result = `${min.toString().padStart(pad, '0')}分${secondTime.toString().padStart(pad, '0')}秒`
-  } else {
-    result = `${secondTime.toString().padStart(pad, '0')}秒`
-  }
-  return result
-}
+import dayjs from 'dayjs';
+import { get } from 'http';
+
+export function getNowDateAndMonday(time: number) {
+  let timestamp = time;
+  const serverDate = new Date(time);
+  if (serverDate.getDay() == 0) {
+    timestamp -= 7 * 24 * 60 * 60 * 1000;
+  }
+  const mondayTime =
+    timestamp - (serverDate.getDay() - 1) * 24 * 60 * 60 * 1000;
+
+  const mondayData = new Date(mondayTime).getTime();
+  //年
+  // const mondayY = mondayData.getFullYear();
+  // //月
+  // const mondayM =
+  //   mondayData.getMonth() + 1 < 10
+  //     ? '0' + (mondayData.getMonth() + 1)
+  //     : mondayData.getMonth() + 1;
+  // //日
+  // const mondayD =
+  //   mondayData.getDate() < 10
+  //     ? '0' + mondayData.getDate()
+  //     : mondayData.getDate();
+
+  // const str = mondayY + '-' + mondayM + '-' + mondayD;
+  return mondayData;
+}
+export function getNowDateAndSunday(time: number) {
+  const timestamp = time;
+  const serverDate = new Date(time);
+
+  let num = 7 - serverDate.getDay();
+  if (num == 7) {
+    num = 0;
+  }
+  const sundayTiem = timestamp + num * 24 * 60 * 60 * 1000;
+  const SundayData = new Date(sundayTiem).getTime();
+  //年
+  // const tomorrowY = SundayData.getFullYear(); //月
+  // const tomorrowM =
+  //   SundayData.getMonth() + 1 < 10
+  //     ? '0' + (SundayData.getMonth() + 1)
+  //     : SundayData.getMonth() + 1;
+  // //日
+  // const tomorrowD =
+  //   SundayData.getDate() < 10
+  //     ? '0' + SundayData.getDate()
+  //     : SundayData.getDate();
+  // const str = tomorrowY + '-' + tomorrowM + '-' + tomorrowD;
+
+  return SundayData;
+}
+
+export const getTimes = (
+  times: any,
+  keys: Array<string> = [],
+  format = 'YYYY-MM-DD'
+) => {
+  if (times && times.length) {
+    return format == 'YYYY-MM-DD'
+      ? {
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format) + ' 00:00:00'
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format) + ' 23:59:59'
+            : ''
+        }
+      : {
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format)
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format)
+            : ''
+        };
+  }
+  return {};
+};
+
+export function formatTime(time: number) {
+  const hours = Math.floor(time / 3600);
+
+  const minutes = Math.floor(Math.floor(time % 3600) / 60);
+
+  const seconds = Math.floor(time % 60);
+
+  const h = hours.toString().length === 1 ? `0${hours}` : hours;
+
+  const m = minutes.toString().length === 1 ? `0${minutes}` : minutes;
+
+  const s = seconds.toString().length === 1 ? `0${seconds}` : seconds;
+  let str = '';
+  if (hours > 0) {
+    str = `${h}小时${m}分钟${s}秒`;
+  } else if (minutes > 0) {
+    str = `${m}分钟${s}秒`;
+  } else {
+    str = `${s}秒`;
+  }
+  console.log();
+  return str;
+}
+
+export function getHours(time: number) {
+  const minutes = Math.floor(time / 60 / 60);
+  return minutes;
+}
+
+export function getLastMinutes(time: number) {
+  const minutes = Math.floor(time / 60);
+  return Math.floor(minutes % 60);
+}
+
+export function getMinutes(time: number) {
+  const minutes = Math.floor(time / 60);
+  return minutes;
+}
+
+export function getSecend(time: number) {
+  const seconds = Math.floor(time % 60);
+  return seconds;
+}
+
+export function getChatMinutes(time: number) {
+  const minutes = Math.floor(time / 60);
+  let s = 0;
+  if (time % 60) {
+    s = Math.ceil(((time % 60) / 60) * 100) / 100;
+  }
+  return minutes + s;
+}
+
+// 秒转时分秒
+export function formateSeconds(endTime: string, pad = 2) {
+  let secondTime = parseInt(endTime); //将传入的秒的值转化为Number
+  let min = 0; // 初始化分
+  let h = 0; // 初始化小时
+  let result = '';
+  if (secondTime >= 60) {
+    //如果秒数大于等于60,将秒数转换成整数
+    min = parseInt(secondTime / 60 + ''); //获取分钟,除以60取整数,得到整数分钟
+    secondTime = parseInt((secondTime % 60) + ''); //获取秒数,秒数取佘,得到整数秒数
+    if (min >= 60) {
+      //如果分钟大于等于60,将分钟转换成小时
+      h = parseInt(min / 60 + ''); //获取小时,获取分钟除以60,得到整数小时
+      min = parseInt((min % 60) + ''); //获取小时后取佘的分,获取分钟除以60取佘的分
+    }
+  }
+  if (h) {
+    result = `${h.toString().padStart(pad, '0')}时${min
+      .toString()
+      .padStart(pad, '0')}分${secondTime.toString().padStart(pad, '0')}秒`;
+  } else if (min) {
+    result = `${min.toString().padStart(pad, '0')}分${secondTime
+      .toString()
+      .padStart(pad, '0')}秒`;
+  } else {
+    result = `${secondTime.toString().padStart(pad, '0')}秒`;
+  }
+  return result;
+}

+ 41 - 44
src/utils/index.ts

@@ -320,21 +320,21 @@ export const getTimes = (
   if (times && times.length) {
     return format == 'YYYY-MM-DD'
       ? {
-        [keys[0] || 'start']: dayjs(times[0]).isValid()
-          ? dayjs(times[0]).format(format) + ' 00:00:00'
-          : '',
-        [keys[1] || 'end']: dayjs(times[1]).isValid()
-          ? dayjs(times[1]).format(format) + ' 23:59:59'
-          : ''
-      }
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format) + ' 00:00:00'
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format) + ' 23:59:59'
+            : ''
+        }
       : {
-        [keys[0] || 'start']: dayjs(times[0]).isValid()
-          ? dayjs(times[0]).format(format)
-          : '',
-        [keys[1] || 'end']: dayjs(times[1]).isValid()
-          ? dayjs(times[1]).format(format)
-          : ''
-      };
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format)
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format)
+            : ''
+        };
   }
   return {};
 };
@@ -359,10 +359,10 @@ export const exitFullscreen = () => {
   document.exitFullscreen
     ? document.exitFullscreen()
     : document.mozCancelFullScreen
-      ? document.mozCancelFullScreen()
-      : document.webkitExitFullscreen
-        ? document.webkitExitFullscreen()
-        : '';
+    ? document.mozCancelFullScreen()
+    : document.webkitExitFullscreen
+    ? document.webkitExitFullscreen()
+    : '';
 };
 
 /** 检测链接格式 */
@@ -747,28 +747,26 @@ export const iframeDislableKeyboard = (iframeDom: any) => {
   }
 };
 
-
 // 后台打开老师端页面时初始化数据
 export const getAuthForAdmin = () => {
   const search = location.href.split('??');
   const userAuth = {
-    Authorization: "" as any,
-    authSource: ""
-  }
-  if(search[1]) {
+    Authorization: '' as any,
+    authSource: ''
+  };
+  if (search[1]) {
     const parse = search[1];
     const result = parseQuery(parse);
     if (result.Authorization) {
-      
       // storage.set(ACCESS_TOKEN_ADMIN, result.Authorization, ex);
       // sessionStorage.setItem('authLoadNum', '1');
       // sessionStorage.setItem('authSource', result.source?.toString() || '');
-      userAuth.Authorization = result.Authorization
-      userAuth.authSource = result.source?.toString() || ''
+      userAuth.Authorization = result.Authorization;
+      userAuth.authSource = result.source?.toString() || '';
     }
   }
 
-  return userAuth
+  return userAuth;
 
   // const authLoadNum = sessionStorage.getItem('authLoadNum');
   // const search = location.href.split('??');
@@ -787,38 +785,37 @@ export const getAuthForAdmin = () => {
   //   sessionStorage.removeItem('authLoadNum');
   //   storage.remove(ACCESS_TOKEN_ADMIN);
   // }
-}
-
+};
 
 export function convertToChineseNumeral(num: number) {
   if (num == 10) {
-    return '十'
+    return '十';
   } else if (num == 1) {
-    return '一'
+    return '一';
   }
-  const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
-  const units = ['', '十', '百', '千', '万']
-  let result = ''
-  let numStr = num.toString()
+  const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
+  const units = ['', '十', '百', '千', '万'];
+  let result = '';
+  const numStr = num.toString();
   for (let i = 0; i < numStr.length; i++) {
-    let digit = parseInt(numStr.charAt(i))
-    let unit = units[numStr.length - i - 1]
+    const digit = parseInt(numStr.charAt(i));
+    const unit = units[numStr.length - i - 1];
     if (digit === 0) {
       // 当前数字为0时不需要输出汉字,但需要考虑上一个数字是否为0,避免出现连续的零
       if (result.charAt(result.length - 1) !== '零') {
-        result += '零'
+        result += '零';
       }
     } else {
-      result += digits[digit] + unit
+      result += digits[digit] + unit;
     }
   }
   // 对于一些特殊的数字,如10、100等,需要在最前面加上“一”
   if (result.charAt(0) === '一') {
-    result = result.substr(1, result.length)
+    result = result.substr(1, result.length);
   } else if (result.charAt(0) === '百') {
-    result = '一' + result
+    result = '一' + result;
   } else if (result.charAt(0) === '千') {
-    result = '一' + result
+    result = '一' + result;
   }
-  return result
-}
+  return result;
+}

+ 125 - 125
src/utils/is/index.ts

@@ -1,125 +1,125 @@
-const toString = Object.prototype.toString;
-
-/**
- * @description: 判断值是否未某个类型
- */
-export function is(val: unknown, type: string) {
-  return toString.call(val) === `[object ${type}]`;
-}
-
-/**
- * @description:  是否为函数
- */
-export function isFunction(val: unknown) {
-  return is(val, 'Function') || is(val, 'AsyncFunction');
-}
-
-/**
- * @description: 是否已定义
- */
-export const isDef = <T = unknown>(val?: T): val is T => {
-  return typeof val !== 'undefined';
-};
-
-export const isUnDef = <T = unknown>(val?: T): val is T => {
-  return !isDef(val);
-};
-/**
- * @description: 是否为对象
- */
-export const isObject = (val: any): val is Record<any, any> => {
-  return val !== null && is(val, 'Object');
-};
-
-/**
- * @description:  是否为时间
- */
-export function isDate(val: unknown): val is Date {
-  return is(val, 'Date');
-}
-
-/**
- * @description:  是否为数值
- */
-export function isNumber(val: unknown): val is number {
-  return is(val, 'Number');
-}
-
-/**
- * @description:  是否为AsyncFunction
- */
-export function isAsyncFunction<T = any>(
-  val: unknown
-): val is () => Promise<T> {
-  return is(val, 'AsyncFunction');
-}
-
-/**
- * @description:  是否为promise
- */
-export function isPromise<T = any>(val: unknown): val is Promise<T> {
-  return (
-    is(val, 'Promise') &&
-    isObject(val) &&
-    isFunction(val.then) &&
-    isFunction(val.catch)
-  );
-}
-
-/**
- * @description:  是否为字符串
- */
-export function isString(val: unknown): val is string {
-  return is(val, 'String');
-}
-
-/**
- * @description:  是否为boolean类型
- */
-export function isBoolean(val: unknown): val is boolean {
-  return is(val, 'Boolean');
-}
-
-/**
- * @description:  是否为数组
- */
-export function isArray(val: any): val is Array<any> {
-  return val && Array.isArray(val);
-}
-
-/**
- * @description: 是否客户端
- */
-export const isClient = () => {
-  return typeof window !== 'undefined';
-};
-
-/**
- * @description: 是否为浏览器
- */
-export const isWindow = (val: any): val is Window => {
-  return typeof window !== 'undefined' && is(val, 'Window');
-};
-
-export const isElement = (val: unknown): val is Element => {
-  return isObject(val) && !!val.tagName;
-};
-
-export const isServer = typeof window === 'undefined';
-
-// 是否为图片节点
-export function isImageDom(o: Element) {
-  return o && ['IMAGE', 'IMG'].includes(o.tagName);
-}
-
-export function isNull(val: unknown): val is null {
-  return val === null;
-}
-
-export function isNullAndUnDef(val: unknown): val is null | undefined {
-  return isUnDef(val) && isNull(val);
-}
-
-export function isNullOrUnDef(val: unknown): val is null | undefined {
-  return isUnDef(val) || isNull(val);
-}
+const toString = Object.prototype.toString;
+
+/**
+ * @description: 判断值是否未某个类型
+ */
+export function is(val: unknown, type: string) {
+  return toString.call(val) === `[object ${type}]`;
+}
+
+/**
+ * @description:  是否为函数
+ */
+export function isFunction(val: unknown) {
+  return is(val, 'Function') || is(val, 'AsyncFunction');
+}
+
+/**
+ * @description: 是否已定义
+ */
+export const isDef = <T = unknown>(val?: T): val is T => {
+  return typeof val !== 'undefined';
+};
+
+export const isUnDef = <T = unknown>(val?: T): val is T => {
+  return !isDef(val);
+};
+/**
+ * @description: 是否为对象
+ */
+export const isObject = (val: any): val is Record<any, any> => {
+  return val !== null && is(val, 'Object');
+};
+
+/**
+ * @description:  是否为时间
+ */
+export function isDate(val: unknown): val is Date {
+  return is(val, 'Date');
+}
+
+/**
+ * @description:  是否为数值
+ */
+export function isNumber(val: unknown): val is number {
+  return is(val, 'Number');
+}
+
+/**
+ * @description:  是否为AsyncFunction
+ */
+export function isAsyncFunction<T = any>(
+  val: unknown
+): val is () => Promise<T> {
+  return is(val, 'AsyncFunction');
+}
+
+/**
+ * @description:  是否为promise
+ */
+export function isPromise<T = any>(val: unknown): val is Promise<T> {
+  return (
+    is(val, 'Promise') &&
+    isObject(val) &&
+    isFunction(val.then) &&
+    isFunction(val.catch)
+  );
+}
+
+/**
+ * @description:  是否为字符串
+ */
+export function isString(val: unknown): val is string {
+  return is(val, 'String');
+}
+
+/**
+ * @description:  是否为boolean类型
+ */
+export function isBoolean(val: unknown): val is boolean {
+  return is(val, 'Boolean');
+}
+
+/**
+ * @description:  是否为数组
+ */
+export function isArray(val: any): val is Array<any> {
+  return val && Array.isArray(val);
+}
+
+/**
+ * @description: 是否客户端
+ */
+export const isClient = () => {
+  return typeof window !== 'undefined';
+};
+
+/**
+ * @description: 是否为浏览器
+ */
+export const isWindow = (val: any): val is Window => {
+  return typeof window !== 'undefined' && is(val, 'Window');
+};
+
+export const isElement = (val: unknown): val is Element => {
+  return isObject(val) && !!val.tagName;
+};
+
+export const isServer = typeof window === 'undefined';
+
+// 是否为图片节点
+export function isImageDom(o: Element) {
+  return o && ['IMAGE', 'IMG'].includes(o.tagName);
+}
+
+export function isNull(val: unknown): val is null {
+  return val === null;
+}
+
+export function isNullAndUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) && isNull(val);
+}
+
+export function isNullOrUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) || isNull(val);
+}

+ 55 - 55
src/utils/lib/echarts.ts

@@ -1,55 +1,55 @@
-import * as echarts from 'echarts/core';
-
-import {
-  BarChart,
-  LineChart,
-  PieChart,
-  MapChart,
-  PictorialBarChart,
-  RadarChart,
-} from 'echarts/charts';
-
-import {
-  TitleComponent,
-  TooltipComponent,
-  GridComponent,
-  PolarComponent,
-  AriaComponent,
-  ParallelComponent,
-  LegendComponent,
-  RadarComponent,
-  ToolboxComponent,
-  DataZoomComponent,
-  VisualMapComponent,
-  TimelineComponent,
-  CalendarComponent,
-  GraphicComponent
-} from 'echarts/components';
-
-import { SVGRenderer } from 'echarts/renderers';
-
-echarts.use([
-  LegendComponent,
-  TitleComponent,
-  TooltipComponent,
-  GridComponent,
-  PolarComponent,
-  AriaComponent,
-  ParallelComponent,
-  BarChart,
-  LineChart,
-  PieChart,
-  MapChart,
-  RadarChart,
-  SVGRenderer,
-  PictorialBarChart,
-  RadarComponent,
-  ToolboxComponent,
-  DataZoomComponent,
-  VisualMapComponent,
-  TimelineComponent,
-  CalendarComponent,
-  GraphicComponent
-]);
-
-export default echarts;
+import * as echarts from 'echarts/core';
+
+import {
+  BarChart,
+  LineChart,
+  PieChart,
+  MapChart,
+  PictorialBarChart,
+  RadarChart
+} from 'echarts/charts';
+
+import {
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  PolarComponent,
+  AriaComponent,
+  ParallelComponent,
+  LegendComponent,
+  RadarComponent,
+  ToolboxComponent,
+  DataZoomComponent,
+  VisualMapComponent,
+  TimelineComponent,
+  CalendarComponent,
+  GraphicComponent
+} from 'echarts/components';
+
+import { SVGRenderer } from 'echarts/renderers';
+
+echarts.use([
+  LegendComponent,
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  PolarComponent,
+  AriaComponent,
+  ParallelComponent,
+  BarChart,
+  LineChart,
+  PieChart,
+  MapChart,
+  RadarChart,
+  SVGRenderer,
+  PictorialBarChart,
+  RadarComponent,
+  ToolboxComponent,
+  DataZoomComponent,
+  VisualMapComponent,
+  TimelineComponent,
+  CalendarComponent,
+  GraphicComponent
+]);
+
+export default echarts;

+ 18 - 18
src/utils/rem.ts

@@ -1,18 +1,18 @@
-const baseSize = 16;
-// 设置 rem 函数
-function setRem() {
-  // 当前页面屏幕分辨率相对于1920宽的缩放比例,可根据自己需要修改
-  let scale = document.documentElement.clientWidth / 1920;
-  // 下面这一行代码可以忽略,这是我另外加的,我加这行代码是为了屏幕宽度小于1280时就不继续等比缩放了
-  if (document.documentElement.clientWidth < 1280) scale = 1280 / 1920;
-  // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
-  document.documentElement.style.fontSize = `${
-    baseSize * Math.min(scale, 1)
-  }px`;
-}
-// 初始化
-setRem();
-// 改变窗口大小时重新设置 rem
-window.onresize = () => {
-  setRem();
-};
+const baseSize = 16;
+// 设置 rem 函数
+function setRem() {
+  // 当前页面屏幕分辨率相对于1920宽的缩放比例,可根据自己需要修改
+  let scale = document.documentElement.clientWidth / 1920;
+  // 下面这一行代码可以忽略,这是我另外加的,我加这行代码是为了屏幕宽度小于1280时就不继续等比缩放了
+  if (document.documentElement.clientWidth < 1280) scale = 1280 / 1920;
+  // 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2,可根据实际业务需求调整)
+  document.documentElement.style.fontSize = `${
+    baseSize * Math.min(scale, 1)
+  }px`;
+}
+// 初始化
+setRem();
+// 改变窗口大小时重新设置 rem
+window.onresize = () => {
+  setRem();
+};

+ 135 - 135
src/utils/request.ts

@@ -1,135 +1,135 @@
-import { extend } from 'umi-request';
-import cleanDeep from 'clean-deep';
-import { useUserStore } from '../store/modules/users';
-import router from '@/router';
-import { eventGlobal, getAuthForAdmin } from '.';
-import { storage } from './storage';
-import { ACCESS_TOKEN_ADMIN } from '../store/mutation-types';
-
-export interface SearchInitParams {
-  rows?: string | number;
-  page?: string | number;
-}
-
-let hideErrorMesage = false; // 是否显示错误信息
-
-const request = extend({
-  // requestType: 'form',
-  hideLoading: true, // 默认都不显示加载
-  timeout: 20000,
-  timeoutMessage: '请求超时'
-});
-
-request.interceptors.request.use(
-  (url, options: any) => {
-    hideErrorMesage = options.hideErrorMesage || false;
-    if (!options.hideLoading) {
-      window.$message.loading('加载中...');
-    }
-
-    const userStore = useUserStore();
-
-    let Authorization = userStore.getToken || '';
-    const authHeaders: any = {};
-    // if (
-    //   userStore.getUserInfo &&
-    //   userStore.getUserInfo.schoolInfos &&
-    //   userStore.getUserInfo.schoolInfos[0]?.id &&
-    //   options.data
-    // ) {
-    //   options.data['schoolId'] =
-    //     (userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id) ||
-    //     '';
-    // }
-    if (
-      userStore.getUserInfo &&
-      (userStore.getUserInfo.schoolInfos as any) &&
-      userStore.getUserInfo.schoolInfos[0]?.id
-    ) {
-      // console.log(
-      //   userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id,
-      //   ' userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id',
-      //   options
-      // );
-
-      options.headers['schoolId'] =
-        (userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id) ||
-        '';
-    }
-    if (
-      Authorization &&
-      !['/api-oauth/userlogin', '/api-auth/open/sendSms'].includes(url)
-    ) {
-      authHeaders.Authorization = Authorization;
-    }
-
-    return {
-      url,
-      options: {
-        ...options,
-        params: cleanDeep(options.params),
-        data: cleanDeep(options.data),
-        headers: {
-          ...options.headers,
-          ...authHeaders
-        }
-      }
-    };
-  },
-  { global: false }
-);
-
-request.interceptors.response.use(
-  async (res: any) => {
-    const userStore = useUserStore();
-    if (res.status > 299 || res.status < 200) {
-      const msg = '服务器错误,状态码' + res.status;
-      // 判断是否有资源需要证书,不提示错误信息
-      if (res.status === 511) {
-        eventGlobal.emit('auth-not-installed');
-      } else {
-        !hideErrorMesage && window.$message.error(msg);
-      }
-
-      throw new Error(msg);
-    }
-    const data = await res.clone().json();
-
-    if (
-      data.code === 401 ||
-      data.code === 4001 ||
-      data.code == 403 ||
-      data.code == 5000
-    ) {
-      userStore.logout(); // 删除登录 - 清除缓存
-      router.replace('/login');
-      location.reload();
-      return;
-    }
-    // if (
-    //   (((data.code < 200 && data.code != 100) ||
-    //     (data.code >= 300 && data.code != 100)) &&
-    //     data.code != 0 &&
-    //     data.code == 5200) ||
-    //   data.code == 5400 ||
-    //   (data.code >= 5000 && data.code < 6000) ||
-    //   data.code == -1
-    // ) {
-    //   const str = res.message || `请求失败code码为${data.code}`;
-    //   window.$message.error(str);
-    //   throw new Error(str);
-    // }
-
-    if (data.code !== 200 && data.errCode !== 0) {
-      const msg = data.msg || data.message || '处理失败,请重试';
-      if (!(data.code === 403 || data.code === 401)) {
-        window.$message.error(msg);
-      }
-      throw new Error(msg);
-    }
-    return res;
-  },
-  { global: false }
-);
-
-export default request;
+import { extend } from 'umi-request';
+import cleanDeep from 'clean-deep';
+import { useUserStore } from '../store/modules/users';
+import router from '@/router';
+import { eventGlobal, getAuthForAdmin } from '.';
+import { storage } from './storage';
+import { ACCESS_TOKEN_ADMIN } from '../store/mutation-types';
+
+export interface SearchInitParams {
+  rows?: string | number;
+  page?: string | number;
+}
+
+let hideErrorMesage = false; // 是否显示错误信息
+
+const request = extend({
+  // requestType: 'form',
+  hideLoading: true, // 默认都不显示加载
+  timeout: 20000,
+  timeoutMessage: '请求超时'
+});
+
+request.interceptors.request.use(
+  (url, options: any) => {
+    hideErrorMesage = options.hideErrorMesage || false;
+    if (!options.hideLoading) {
+      window.$message.loading('加载中...');
+    }
+
+    const userStore = useUserStore();
+
+    const Authorization = userStore.getToken || '';
+    const authHeaders: any = {};
+    // if (
+    //   userStore.getUserInfo &&
+    //   userStore.getUserInfo.schoolInfos &&
+    //   userStore.getUserInfo.schoolInfos[0]?.id &&
+    //   options.data
+    // ) {
+    //   options.data['schoolId'] =
+    //     (userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id) ||
+    //     '';
+    // }
+    if (
+      userStore.getUserInfo &&
+      (userStore.getUserInfo.schoolInfos as any) &&
+      userStore.getUserInfo.schoolInfos[0]?.id
+    ) {
+      // console.log(
+      //   userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id,
+      //   ' userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id',
+      //   options
+      // );
+
+      options.headers['schoolId'] =
+        (userStore.getUserInfo && userStore.getUserInfo.schoolInfos[0]?.id) ||
+        '';
+    }
+    if (
+      Authorization &&
+      !['/api-oauth/userlogin', '/api-auth/open/sendSms'].includes(url)
+    ) {
+      authHeaders.Authorization = Authorization;
+    }
+
+    return {
+      url,
+      options: {
+        ...options,
+        params: cleanDeep(options.params),
+        data: cleanDeep(options.data),
+        headers: {
+          ...options.headers,
+          ...authHeaders
+        }
+      }
+    };
+  },
+  { global: false }
+);
+
+request.interceptors.response.use(
+  async (res: any) => {
+    const userStore = useUserStore();
+    if (res.status > 299 || res.status < 200) {
+      const msg = '服务器错误,状态码' + res.status;
+      // 判断是否有资源需要证书,不提示错误信息
+      if (res.status === 511) {
+        eventGlobal.emit('auth-not-installed');
+      } else {
+        !hideErrorMesage && window.$message.error(msg);
+      }
+
+      throw new Error(msg);
+    }
+    const data = await res.clone().json();
+
+    if (
+      data.code === 401 ||
+      data.code === 4001 ||
+      data.code == 403 ||
+      data.code == 5000
+    ) {
+      userStore.logout(); // 删除登录 - 清除缓存
+      router.replace('/login');
+      location.reload();
+      return;
+    }
+    // if (
+    //   (((data.code < 200 && data.code != 100) ||
+    //     (data.code >= 300 && data.code != 100)) &&
+    //     data.code != 0 &&
+    //     data.code == 5200) ||
+    //   data.code == 5400 ||
+    //   (data.code >= 5000 && data.code < 6000) ||
+    //   data.code == -1
+    // ) {
+    //   const str = res.message || `请求失败code码为${data.code}`;
+    //   window.$message.error(str);
+    //   throw new Error(str);
+    // }
+
+    if (data.code !== 200 && data.errCode !== 0) {
+      const msg = data.msg || data.message || '处理失败,请重试';
+      if (!(data.code === 403 || data.code === 401)) {
+        window.$message.error(msg);
+      }
+      throw new Error(msg);
+    }
+    return res;
+  },
+  { global: false }
+);
+
+export default request;

+ 32 - 30
src/utils/searchArray.ts

@@ -1,30 +1,32 @@
-import * as constant from './contants';
-
-export function getValueForKey(obj: any) {
-  const arr: any[] = [];
-  for (const k in obj) {
-    arr.push({
-      label: obj[k],
-      value: k
-    });
-  }
-  return arr;
-}
-
-// 教材
-export const teachingArray = getValueForKey(constant.teaching);
-
-// 乐器
-export const instrumentArray = getValueForKey(constant.instrument);
-
-// 资源类型
-export const resourceTypeArray = getValueForKey(constant.resourceType);
-export const resourceTypeArray2 = getValueForKey(constant.resourceTypeLesson);
-
-// 训练状态
-export const trainingStatusArray = getValueForKey(constant.trainingStatus);
-/**
- * @description: 评测难度
- * 入门:BEGINNER/进阶:ADVANCED/大师:PERFORMER")
- */
-export const evaluateDifficultArray = getValueForKey(constant.evaluateDifficult)
+import * as constant from './contants';
+
+export function getValueForKey(obj: any) {
+  const arr: any[] = [];
+  for (const k in obj) {
+    arr.push({
+      label: obj[k],
+      value: k
+    });
+  }
+  return arr;
+}
+
+// 教材
+export const teachingArray = getValueForKey(constant.teaching);
+
+// 乐器
+export const instrumentArray = getValueForKey(constant.instrument);
+
+// 资源类型
+export const resourceTypeArray = getValueForKey(constant.resourceType);
+export const resourceTypeArray2 = getValueForKey(constant.resourceTypeLesson);
+
+// 训练状态
+export const trainingStatusArray = getValueForKey(constant.trainingStatus);
+/**
+ * @description: 评测难度
+ * 入门:BEGINNER/进阶:ADVANCED/大师:PERFORMER")
+ */
+export const evaluateDifficultArray = getValueForKey(
+  constant.evaluateDifficult
+);

+ 105 - 105
src/utils/searchs.ts

@@ -1,105 +1,105 @@
-/* eslint-disable no-empty */
-export class Searchs {
-  saveKey = 'searchs';
-
-  initSearch = {
-    form: {},
-    page: {}
-  };
-
-  searchs = {} as any;
-  key = '' as string;
-
-  constructor(key: string) {
-    this.key = key;
-    this.searchs = this.parse();
-  }
-
-  save() {
-    localStorage.setItem(this.saveKey, JSON.stringify(this.searchs));
-  }
-
-  parse() {
-    let json = { ...initSearch };
-    try {
-      const val = localStorage.getItem(this.saveKey) as string;
-      json = JSON.parse(val) || json;
-    } catch (error) {}
-    return json;
-  }
-
-  get(key: string) {
-    const k = key || this.key;
-    if (!this.searchs[k]) {
-      this.searchs[k] = { ...initSearch };
-    }
-    return this.searchs[k];
-  }
-
-  remove(type: 'page' | 'form') {
-    if (this.searchs && this.searchs[this.key]) {
-      type
-        ? delete this.searchs[this.key][type]
-        : delete this.searchs[this.key];
-      this.save();
-    }
-    return this.searchs;
-  }
-
-  getSearchs() {
-    return this.searchs;
-  }
-
-  removeByKey(key: string) {
-    console.log('真正的删', key);
-    delete this.searchs[key];
-    this.save();
-    return this.searchs;
-  }
-  removeAll() {
-    this.searchs = {};
-    localStorage.setItem(this.saveKey, JSON.stringify(this.searchs));
-    return this.searchs;
-  }
-  removeByRouter(path: string) {
-    this.searchs = this.parse();
-    for (const key in this.searchs) {
-      // console.log('清除的循环', key, this.searchs[key]?.bind)
-      if (path === key || path === this.searchs[key]?.bind) {
-        console.log('清除的页面', key);
-        this.removeByKey(key);
-      }
-    }
-  }
-
-  removeByOtherRouter(path: string) {
-    this.searchs = this.parse();
-    for (const key in this.searchs) {
-      if (path === key || path === this.searchs[key]?.bind) {
-      } else {
-        this.removeByKey(key);
-      }
-    }
-  }
-
-  update(data: any, key: string | any, type: 'page' | 'form' | 'bind' | '') {
-    this.searchs = this.parse();
-    const k = key || this.key;
-    if (!this.searchs[k]) {
-      this.searchs[k] = { ...initSearch };
-    }
-
-    if (type) {
-      this.searchs[k][type] = data;
-    } else {
-      this.searchs[k] = data;
-    }
-    this.save();
-    return this.searchs;
-  }
-}
-
-const initSearch = {
-  form: {},
-  page: {}
-};
+/* eslint-disable no-empty */
+export class Searchs {
+  saveKey = 'searchs';
+
+  initSearch = {
+    form: {},
+    page: {}
+  };
+
+  searchs = {} as any;
+  key = '' as string;
+
+  constructor(key: string) {
+    this.key = key;
+    this.searchs = this.parse();
+  }
+
+  save() {
+    localStorage.setItem(this.saveKey, JSON.stringify(this.searchs));
+  }
+
+  parse() {
+    let json = { ...initSearch };
+    try {
+      const val = localStorage.getItem(this.saveKey) as string;
+      json = JSON.parse(val) || json;
+    } catch (error) {}
+    return json;
+  }
+
+  get(key: string) {
+    const k = key || this.key;
+    if (!this.searchs[k]) {
+      this.searchs[k] = { ...initSearch };
+    }
+    return this.searchs[k];
+  }
+
+  remove(type: 'page' | 'form') {
+    if (this.searchs && this.searchs[this.key]) {
+      type
+        ? delete this.searchs[this.key][type]
+        : delete this.searchs[this.key];
+      this.save();
+    }
+    return this.searchs;
+  }
+
+  getSearchs() {
+    return this.searchs;
+  }
+
+  removeByKey(key: string) {
+    console.log('真正的删', key);
+    delete this.searchs[key];
+    this.save();
+    return this.searchs;
+  }
+  removeAll() {
+    this.searchs = {};
+    localStorage.setItem(this.saveKey, JSON.stringify(this.searchs));
+    return this.searchs;
+  }
+  removeByRouter(path: string) {
+    this.searchs = this.parse();
+    for (const key in this.searchs) {
+      // console.log('清除的循环', key, this.searchs[key]?.bind)
+      if (path === key || path === this.searchs[key]?.bind) {
+        console.log('清除的页面', key);
+        this.removeByKey(key);
+      }
+    }
+  }
+
+  removeByOtherRouter(path: string) {
+    this.searchs = this.parse();
+    for (const key in this.searchs) {
+      if (path === key || path === this.searchs[key]?.bind) {
+      } else {
+        this.removeByKey(key);
+      }
+    }
+  }
+
+  update(data: any, key: string | any, type: 'page' | 'form' | 'bind' | '') {
+    this.searchs = this.parse();
+    const k = key || this.key;
+    if (!this.searchs[k]) {
+      this.searchs[k] = { ...initSearch };
+    }
+
+    if (type) {
+      this.searchs[k][type] = data;
+    } else {
+      this.searchs[k] = data;
+    }
+    this.save();
+    return this.searchs;
+  }
+}
+
+const initSearch = {
+  form: {},
+  page: {}
+};

+ 134 - 134
src/utils/storage.ts

@@ -1,134 +1,134 @@
-// 默认缓存期限为7天
-const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
-
-/**
- * 创建本地缓存对象
- * @param {string=} prefixKey -
- * @param {Object} [storage=localStorage] - sessionStorage | localStorage
- */
-export const createStorage = ({
-  prefixKey = '',
-  storage = localStorage
-} = {}) => {
-  /**
-   * 本地缓存类
-   * @class Storage
-   */
-  const Storage = class {
-    private storage = storage;
-    private prefixKey?: string = prefixKey;
-
-    private getKey(key: string) {
-      return `${this.prefixKey}${key}`.toUpperCase();
-    }
-
-    /**
-     * @description 设置缓存
-     * @param {string} key 缓存键
-     * @param {*} value 缓存值
-     * @param expire
-     */
-    set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
-      const stringData = JSON.stringify({
-        value,
-        expire: expire !== null ? new Date().getTime() + expire * 1000 : null
-      });
-      this.storage.setItem(this.getKey(key), stringData);
-    }
-
-    /**
-     * 读取缓存
-     * @param {string} key 缓存键
-     * @param {*=} def 默认值
-     */
-    get(key: string, def: any = null) {
-      const item = this.storage.getItem(this.getKey(key));
-      if (item) {
-        try {
-          const data = JSON.parse(item);
-          const { value, expire } = data;
-          // 在有效期内直接返回
-          if (expire === null || expire >= Date.now()) {
-            return value;
-          }
-          this.remove(key);
-        } catch (e) {
-          return def;
-        }
-      }
-      return def;
-    }
-
-    /**
-     * 从缓存删除某项
-     * @param {string} key
-     */
-    remove(key: string) {
-      this.storage.removeItem(this.getKey(key));
-    }
-
-    /**
-     * 清空所有缓存
-     * @memberOf Cache
-     */
-    clear(): void {
-      this.storage.clear();
-    }
-
-    /**
-     * 设置cookie
-     * @param {string} name cookie 名称
-     * @param {*} value cookie 值
-     * @param {number=} expire 过期时间
-     * 如果过期时间未设置,默认关闭浏览器自动删除
-     * @example
-     */
-    setCookie(
-      name: string,
-      value: any,
-      expire: number | null = DEFAULT_CACHE_TIME
-    ) {
-      document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
-    }
-
-    /**
-     * 根据名字获取cookie值
-     * @param name
-     */
-    getCookie(name: string): string {
-      const cookieArr = document.cookie.split('; ');
-      for (let i = 0, length = cookieArr.length; i < length; i++) {
-        const kv = cookieArr[i].split('=');
-        if (kv[0] === this.getKey(name)) {
-          return kv[1];
-        }
-      }
-      return '';
-    }
-
-    /**
-     * 根据名字删除指定的cookie
-     * @param {string} key
-     */
-    removeCookie(key: string) {
-      this.setCookie(key, 1, -1);
-    }
-
-    /**
-     * 清空cookie,使所有cookie失效
-     */
-    clearCookie(): void {
-      const keys = document.cookie.match(/[^ =;]+(?==)/g);
-      if (keys) {
-        for (let i = keys.length; i--; ) {
-          document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
-        }
-      }
-    }
-  };
-  return new Storage();
-};
-
-export const storage = createStorage();
-
-export default Storage;
+// 默认缓存期限为7天
+const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
+
+/**
+ * 创建本地缓存对象
+ * @param {string=} prefixKey -
+ * @param {Object} [storage=localStorage] - sessionStorage | localStorage
+ */
+export const createStorage = ({
+  prefixKey = '',
+  storage = localStorage
+} = {}) => {
+  /**
+   * 本地缓存类
+   * @class Storage
+   */
+  const Storage = class {
+    private storage = storage;
+    private prefixKey?: string = prefixKey;
+
+    private getKey(key: string) {
+      return `${this.prefixKey}${key}`.toUpperCase();
+    }
+
+    /**
+     * @description 设置缓存
+     * @param {string} key 缓存键
+     * @param {*} value 缓存值
+     * @param expire
+     */
+    set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
+      const stringData = JSON.stringify({
+        value,
+        expire: expire !== null ? new Date().getTime() + expire * 1000 : null
+      });
+      this.storage.setItem(this.getKey(key), stringData);
+    }
+
+    /**
+     * 读取缓存
+     * @param {string} key 缓存键
+     * @param {*=} def 默认值
+     */
+    get(key: string, def: any = null) {
+      const item = this.storage.getItem(this.getKey(key));
+      if (item) {
+        try {
+          const data = JSON.parse(item);
+          const { value, expire } = data;
+          // 在有效期内直接返回
+          if (expire === null || expire >= Date.now()) {
+            return value;
+          }
+          this.remove(key);
+        } catch (e) {
+          return def;
+        }
+      }
+      return def;
+    }
+
+    /**
+     * 从缓存删除某项
+     * @param {string} key
+     */
+    remove(key: string) {
+      this.storage.removeItem(this.getKey(key));
+    }
+
+    /**
+     * 清空所有缓存
+     * @memberOf Cache
+     */
+    clear(): void {
+      this.storage.clear();
+    }
+
+    /**
+     * 设置cookie
+     * @param {string} name cookie 名称
+     * @param {*} value cookie 值
+     * @param {number=} expire 过期时间
+     * 如果过期时间未设置,默认关闭浏览器自动删除
+     * @example
+     */
+    setCookie(
+      name: string,
+      value: any,
+      expire: number | null = DEFAULT_CACHE_TIME
+    ) {
+      document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`;
+    }
+
+    /**
+     * 根据名字获取cookie值
+     * @param name
+     */
+    getCookie(name: string): string {
+      const cookieArr = document.cookie.split('; ');
+      for (let i = 0, length = cookieArr.length; i < length; i++) {
+        const kv = cookieArr[i].split('=');
+        if (kv[0] === this.getKey(name)) {
+          return kv[1];
+        }
+      }
+      return '';
+    }
+
+    /**
+     * 根据名字删除指定的cookie
+     * @param {string} key
+     */
+    removeCookie(key: string) {
+      this.setCookie(key, 1, -1);
+    }
+
+    /**
+     * 清空cookie,使所有cookie失效
+     */
+    clearCookie(): void {
+      const keys = document.cookie.match(/[^ =;]+(?==)/g);
+      if (keys) {
+        for (let i = keys.length; i--; ) {
+          document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString();
+        }
+      }
+    }
+  };
+  return new Storage();
+};
+
+export const storage = createStorage();
+
+export default Storage;

+ 10 - 10
src/views/aboutUs/api.ts

@@ -1,10 +1,10 @@
-import request from '@/utils/request';
-/**
- * 关于我们
- */
-export const api_getCustomerServiceContact = (params: any) => {
-  return request.get('/edu-app/sysSuggestion/getCustomerServiceContact', {
-    data: params
-    // requestType: 'form'
-  });
-};
+import request from '@/utils/request';
+/**
+ * 关于我们
+ */
+export const api_getCustomerServiceContact = (params: any) => {
+  return request.get('/edu-app/sysSuggestion/getCustomerServiceContact', {
+    data: params
+    // requestType: 'form'
+  });
+};

+ 71 - 71
src/views/aboutUs/index.tsx

@@ -1,71 +1,71 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from './index.module.less';
-import { NTabs, NTabPane, NImage } from 'naive-ui';
-import iconLogo from './images/icon-logo.png';
-import { api_getCustomerServiceContact } from './api';
-export default defineComponent({
-  name: 'aboutUs',
-  setup() {
-    const activeTab = ref('person' as any);
-    const state = reactive({
-      email: '',
-      phone: ''
-    });
-
-    const getAboutUsInfo = async () => {
-      try {
-        const { data } = await api_getCustomerServiceContact({});
-        state.email = data.email;
-        state.phone = data.phone;
-      } catch {
-        //
-      }
-    };
-
-    onMounted(() => {
-      getAboutUsInfo();
-    });
-
-    return () => (
-      <div class={styles.listWrap}>
-        <NTabs
-          class={styles.customTabs}
-          v-model:value={activeTab.value}
-          size="large"
-          // animated
-          pane-wrapper-style="margin: 0 -4px"
-          pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
-          <NTabPane name="person" tab="关于我们"></NTabPane>
-        </NTabs>
-
-        <div class={styles.content}>
-          <div class={styles.information}>
-            <div class={styles.logoContainer}>
-              <NImage src={iconLogo} class={styles.iconLogo} />
-              <p>版本号 1.1.4</p>
-            </div>
-
-            <div class={styles.infos}>
-              <div class={styles.info}>
-                <p class={styles.title}>客服电话:</p>
-                <p>{state.phone || '--'}</p>
-              </div>
-              <div class={styles.info}>
-                <p class={styles.title}>E-mail:</p>
-                <p>{state.email || '--'}</p>
-              </div>
-            </div>
-          </div>
-          <div class={styles.copyright}>
-            <a target="_blank" href="https://beian.miit.gov.cn/">
-              网站备案:鄂ICP备2021020787号-1
-            </a>
-            |增值电信业务经营许可证:鄂B2-20231246|教育移动互联网应用备案:教APP备4200212号|网络安全等级保护备案号:42010043158-24001{' '}
-            <br />
-            Copyright@2021-2024|酷乐秀 colexiu.com 版权所有
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import { NTabs, NTabPane, NImage } from 'naive-ui';
+import iconLogo from './images/icon-logo.png';
+import { api_getCustomerServiceContact } from './api';
+export default defineComponent({
+  name: 'aboutUs',
+  setup() {
+    const activeTab = ref('person' as any);
+    const state = reactive({
+      email: '',
+      phone: ''
+    });
+
+    const getAboutUsInfo = async () => {
+      try {
+        const { data } = await api_getCustomerServiceContact({});
+        state.email = data.email;
+        state.phone = data.phone;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getAboutUsInfo();
+    });
+
+    return () => (
+      <div class={styles.listWrap}>
+        <NTabs
+          class={styles.customTabs}
+          v-model:value={activeTab.value}
+          size="large"
+          // animated
+          pane-wrapper-style="margin: 0 -4px"
+          pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
+          <NTabPane name="person" tab="关于我们"></NTabPane>
+        </NTabs>
+
+        <div class={styles.content}>
+          <div class={styles.information}>
+            <div class={styles.logoContainer}>
+              <NImage src={iconLogo} class={styles.iconLogo} />
+              <p>版本号 1.1.4</p>
+            </div>
+
+            <div class={styles.infos}>
+              <div class={styles.info}>
+                <p class={styles.title}>客服电话:</p>
+                <p>{state.phone || '--'}</p>
+              </div>
+              <div class={styles.info}>
+                <p class={styles.title}>E-mail:</p>
+                <p>{state.email || '--'}</p>
+              </div>
+            </div>
+          </div>
+          <div class={styles.copyright}>
+            <a target="_blank" href="https://beian.miit.gov.cn/">
+              网站备案:鄂ICP备2021020787号-1
+            </a>
+            |增值电信业务经营许可证:鄂B2-20231246|教育移动互联网应用备案:教APP备4200212号|网络安全等级保护备案号:42010043158-24001{' '}
+            <br />
+            Copyright@2021-2024|酷乐秀 colexiu.com 版权所有
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 155 - 155
src/views/attend-class/component/audio-pay copy.tsx

@@ -1,155 +1,155 @@
-import { defineComponent, onMounted, toRefs, reactive } from 'vue';
-import styles from './audio.module.less';
-import WaveSurfer from 'wavesurfer.js';
-import iconplay from '../image/icon-pause.png';
-import iconpause from '../image/icon-play.png';
-import { NSlider } from 'naive-ui';
-
-export default defineComponent({
-  name: 'audio-play',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-        return {};
-      }
-    },
-    isEmtry: {
-      type: Boolean,
-      default: false
-    }
-  },
-  setup(props) {
-    const { item } = toRefs(props);
-    const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100);
-    const audioForms = reactive({
-      paused: true,
-      currentTimeNum: 0,
-      currentTime: '00:00',
-      duration: '00:00'
-    });
-    const audioDom = new Audio();
-    audioDom.controls = true;
-    audioDom.style.width = '100%';
-    audioDom.className = styles.audio;
-    document.querySelector(`#${audioId}`)?.appendChild(audioDom);
-
-    onMounted(() => {
-      const wavesurfer = WaveSurfer.create({
-        container: document.querySelector(`#${audioId}`) as HTMLElement,
-        waveColor: '#C5C5C5',
-        progressColor: '#02baff',
-        url: item.value.content + '?t=' + +new Date(),
-        cursorWidth: 0,
-        height: 160,
-        normalize: true,
-        // Set a bar width
-        barWidth: 6,
-        // Optionally, specify the spacing between bars
-        barGap: 12,
-        // And the bar radius
-        barRadius: 12,
-        autoScroll: true,
-        /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
-        autoCenter: true,
-        hideScrollbar: false,
-        media: audioDom
-      });
-
-      wavesurfer.once('interaction', () => {
-        // wavesurfer.play();
-      });
-      wavesurfer.once('ready', () => {
-        audioForms.paused = audioDom.paused;
-        audioForms.duration = timeFormat(Math.round(audioDom.duration));
-      });
-
-      wavesurfer.on('finish', () => {
-        audioForms.paused = true;
-      });
-    });
-
-    // 切换音频播放
-    const onToggleAudio = (e: MouseEvent) => {
-      e.stopPropagation();
-      if (audioDom.paused) {
-        audioDom.play();
-      } else {
-        audioDom.pause();
-      }
-
-      audioForms.paused = audioDom.paused;
-    };
-
-    // 播放时监听
-    audioDom.addEventListener('timeupdate', () => {
-      audioForms.currentTime = timeFormat(Math.round(audioDom.currentTime));
-      audioForms.currentTimeNum = audioDom.currentTime;
-    });
-
-    // 播放结束时
-
-    // 对时间进行格式化
-    const timeFormat = (num: number) => {
-      if (num > 0) {
-        const m = Math.floor(num / 60);
-        const s = num % 60;
-        return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
-      } else {
-        return '00:00';
-      }
-    };
-
-    return () => (
-      <div class={styles.audioWrap}>
-        <div class={styles.audioContainer}>
-          <div
-            id={audioId}
-            onClick={(e: MouseEvent) => {
-              e.stopPropagation();
-            }}></div>
-        </div>
-
-        <div
-          class={styles.controls}
-          onClick={(e: MouseEvent) => {
-            e.stopPropagation();
-          }}>
-          <div class={styles.actions}>
-            <div class={styles.actionWrap}>
-              <button class={styles.actionBtn} onClick={onToggleAudio}>
-                {audioForms.paused ? (
-                  <img class={styles.playIcon} src={iconplay} />
-                ) : (
-                  <img class={styles.playIcon} src={iconpause} />
-                )}
-              </button>
-            </div>
-            <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.slider}>
-            <NSlider
-              v-model:value={audioForms.currentTimeNum}
-              step={0.01}
-              max={audioDom.duration}
-              tooltip={false}
-            />
-          </div>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, toRefs, reactive } from 'vue';
+import styles from './audio.module.less';
+import WaveSurfer from 'wavesurfer.js';
+import iconplay from '../image/icon-pause.png';
+import iconpause from '../image/icon-play.png';
+import { NSlider } from 'naive-ui';
+
+export default defineComponent({
+  name: 'audio-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props) {
+    const { item } = toRefs(props);
+    const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100);
+    const audioForms = reactive({
+      paused: true,
+      currentTimeNum: 0,
+      currentTime: '00:00',
+      duration: '00:00'
+    });
+    const audioDom = new Audio();
+    audioDom.controls = true;
+    audioDom.style.width = '100%';
+    audioDom.className = styles.audio;
+    document.querySelector(`#${audioId}`)?.appendChild(audioDom);
+
+    onMounted(() => {
+      const wavesurfer = WaveSurfer.create({
+        container: document.querySelector(`#${audioId}`) as HTMLElement,
+        waveColor: '#C5C5C5',
+        progressColor: '#02baff',
+        url: item.value.content + '?t=' + +new Date(),
+        cursorWidth: 0,
+        height: 160,
+        normalize: true,
+        // Set a bar width
+        barWidth: 6,
+        // Optionally, specify the spacing between bars
+        barGap: 12,
+        // And the bar radius
+        barRadius: 12,
+        autoScroll: true,
+        /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
+        autoCenter: true,
+        hideScrollbar: false,
+        media: audioDom
+      });
+
+      wavesurfer.once('interaction', () => {
+        // wavesurfer.play();
+      });
+      wavesurfer.once('ready', () => {
+        audioForms.paused = audioDom.paused;
+        audioForms.duration = timeFormat(Math.round(audioDom.duration));
+      });
+
+      wavesurfer.on('finish', () => {
+        audioForms.paused = true;
+      });
+    });
+
+    // 切换音频播放
+    const onToggleAudio = (e: MouseEvent) => {
+      e.stopPropagation();
+      if (audioDom.paused) {
+        audioDom.play();
+      } else {
+        audioDom.pause();
+      }
+
+      audioForms.paused = audioDom.paused;
+    };
+
+    // 播放时监听
+    audioDom.addEventListener('timeupdate', () => {
+      audioForms.currentTime = timeFormat(Math.round(audioDom.currentTime));
+      audioForms.currentTimeNum = audioDom.currentTime;
+    });
+
+    // 播放结束时
+
+    // 对时间进行格式化
+    const timeFormat = (num: number) => {
+      if (num > 0) {
+        const m = Math.floor(num / 60);
+        const s = num % 60;
+        return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+      } else {
+        return '00:00';
+      }
+    };
+
+    return () => (
+      <div class={styles.audioWrap}>
+        <div class={styles.audioContainer}>
+          <div
+            id={audioId}
+            onClick={(e: MouseEvent) => {
+              e.stopPropagation();
+            }}></div>
+        </div>
+
+        <div
+          class={styles.controls}
+          onClick={(e: MouseEvent) => {
+            e.stopPropagation();
+          }}>
+          <div class={styles.actions}>
+            <div class={styles.actionWrap}>
+              <button class={styles.actionBtn} onClick={onToggleAudio}>
+                {audioForms.paused ? (
+                  <img class={styles.playIcon} src={iconplay} />
+                ) : (
+                  <img class={styles.playIcon} src={iconpause} />
+                )}
+              </button>
+            </div>
+            <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.slider}>
+            <NSlider
+              v-model:value={audioForms.currentTimeNum}
+              step={0.01}
+              max={audioDom.duration}
+              tooltip={false}
+            />
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 3 - 2
src/views/attend-class/component/audio-pay.tsx

@@ -43,7 +43,7 @@ export default defineComponent({
   },
   emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
   setup(props, { emit, expose }) {
-    const message = useMessage()
+    const message = useMessage();
     const audioForms = reactive({
       paused: true,
       speedInKbps: '',
@@ -538,7 +538,8 @@ export default defineComponent({
             }}
             onCanplay={() => {
               audioForms.isBuffering = false;
-              audioForms.bufferTimeout && clearTimeout(audioForms.bufferTimeout); // 清除超时检测
+              audioForms.bufferTimeout &&
+                clearTimeout(audioForms.bufferTimeout); // 清除超时检测
             }}
             onError={() => {
               audio.value?.pause();

+ 70 - 70
src/views/attend-class/component/pptList.tsx

@@ -1,70 +1,70 @@
-import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
-import styles from './pptList.module.less';
-import { vaildPPTUrl } from '/src/utils/urlUtils';
-import { useUserStore } from '/src/store/modules/users';
-import { useRoute } from 'vue-router';
-
-export default defineComponent({
-  name: 'pptList',
-  props: {
-    pptData: {
-      type: Object,
-      default: () => ({})
-    },
-    instrumentId: {
-      type: String,
-      default: ''
-    }
-  },
-  emits: ['initPPT', 'changeSlideIndex', 'init'],
-  setup(props, { emit, expose }) {
-    const route = useRoute();
-    const query = route.query;
-    const userStore = useUserStore();
-    const iframeRef = ref<HTMLIFrameElement>();
-    const src = `${vaildPPTUrl()}/#/pptScreen?id=${
-      props.pptData.id
-    }&instrumentId=${props.instrumentId}&Authorization=${
-      userStore.getToken
-    }&hideFullScreen=true&fromType=${
-      query.fromType === 'PLATFORM' ? 'PLATFORM' : 'TEACHER'
-    }&pptResourcesType=${
-      query.fromType === 'PLATFORM' ? 'PLATFORM' : 'TEACHER'
-    }`;
-    // 上一页下一页
-    function handleChangeSlide(type: 'prev' | 'next') {
-      iframeRef.value?.contentWindow?.postMessage(
-        { type: 'changePageSlide', content: type },
-        '*'
-      );
-    }
-    function handleMessage(event: any) {
-      const { type, content } = event.data || {};
-      console.log(content, 'message');
-      if (type === 'initPPT') {
-        emit('initPPT', content);
-      } else if (type === 'changeSlideIndex') {
-        emit('changeSlideIndex', content);
-      }
-    }
-    emit('init');
-    onMounted(() => {
-      window.addEventListener('message', handleMessage);
-    });
-    onUnmounted(() => {
-      window.removeEventListener('message', handleMessage);
-    });
-    expose({
-      handleChangeSlide
-    });
-    return () => (
-      <div class={styles.pptList}>
-        <iframe
-          ref={iframeRef}
-          class={[styles.container]}
-          frameborder="0"
-          src={src}></iframe>
-      </div>
-    );
-  }
-});
+import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
+import styles from './pptList.module.less';
+import { vaildPPTUrl } from '/src/utils/urlUtils';
+import { useUserStore } from '/src/store/modules/users';
+import { useRoute } from 'vue-router';
+
+export default defineComponent({
+  name: 'pptList',
+  props: {
+    pptData: {
+      type: Object,
+      default: () => ({})
+    },
+    instrumentId: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['initPPT', 'changeSlideIndex', 'init'],
+  setup(props, { emit, expose }) {
+    const route = useRoute();
+    const query = route.query;
+    const userStore = useUserStore();
+    const iframeRef = ref<HTMLIFrameElement>();
+    const src = `${vaildPPTUrl()}/#/pptScreen?id=${
+      props.pptData.id
+    }&instrumentId=${props.instrumentId}&Authorization=${
+      userStore.getToken
+    }&hideFullScreen=true&fromType=${
+      query.fromType === 'PLATFORM' ? 'PLATFORM' : 'TEACHER'
+    }&pptResourcesType=${
+      query.fromType === 'PLATFORM' ? 'PLATFORM' : 'TEACHER'
+    }`;
+    // 上一页下一页
+    function handleChangeSlide(type: 'prev' | 'next') {
+      iframeRef.value?.contentWindow?.postMessage(
+        { type: 'changePageSlide', content: type },
+        '*'
+      );
+    }
+    function handleMessage(event: any) {
+      const { type, content } = event.data || {};
+      console.log(content, 'message');
+      if (type === 'initPPT') {
+        emit('initPPT', content);
+      } else if (type === 'changeSlideIndex') {
+        emit('changeSlideIndex', content);
+      }
+    }
+    emit('init');
+    onMounted(() => {
+      window.addEventListener('message', handleMessage);
+    });
+    onUnmounted(() => {
+      window.removeEventListener('message', handleMessage);
+    });
+    expose({
+      handleChangeSlide
+    });
+    return () => (
+      <div class={styles.pptList}>
+        <iframe
+          ref={iframeRef}
+          class={[styles.container]}
+          frameborder="0"
+          src={src}></iframe>
+      </div>
+    );
+  }
+});

+ 1 - 3
src/views/attend-class/component/rhythm-modal/index.tsx

@@ -26,9 +26,7 @@ export default defineComponent({
     const userStore = useUserStore();
     const iframeRef = ref();
     const isLoaded = ref(false);
-    let src = `${
-      getHttpOrigin()
-    }/classroom-app/#/tempo-practice?v=${+new Date()}&platform=modal&dataJson=${
+    let src = `${getHttpOrigin()}/classroom-app/#/tempo-practice?v=${+new Date()}&platform=modal&dataJson=${
       props.item.dataJson
     }&Authorization=${userStore.getToken}&win=pc&imagePos=${props.imagePos}`;
     if (/(localhost)/.test(location.host)) {

+ 174 - 174
src/views/attend-class/component/video-play copy.tsx

@@ -1,174 +1,174 @@
-import { defineComponent, nextTick, onMounted, toRefs } from 'vue';
-import 'plyr/dist/plyr.css';
-import Plyr from 'plyr';
-import { ref } from 'vue';
-import styles from './video.module.less';
-import iconplay from '../image/icon-pause.png';
-import iconpause from '../image/icon-play.png';
-import iconReplay from '../image/icon-replay.png';
-
-export default defineComponent({
-  name: 'video-play',
-  props: {
-    item: {
-      type: Object,
-      default: () => {
-        return {};
-      }
-    },
-    isEmtry: {
-      type: Boolean,
-      default: false
-    }
-  },
-  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
-  setup(props, { emit, expose }) {
-    const { item, isEmtry } = toRefs(props);
-    const videoRef = ref();
-    const videoItem = ref<Plyr>();
-    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100);
-    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100);
-    const replayBtnId = 'replay' + Date.now() + Math.floor(Math.random() * 100);
-    const toggleHideControl = (isShow: false) => {
-      videoItem.value?.toggleControls(isShow);
-    };
-    const togglePlay = (e: Event) => {
-      e.stopPropagation();
-      videoItem.value?.togglePlay();
-    };
-    const toggleReplay = () => {
-      const replayBtn = document.getElementById(replayBtnId);
-      if (!replayBtn || !videoItem.value) return;
-      videoItem.value.restart();
-    };
-    const onDefault = () => {
-      document
-        .getElementById(controlID)
-        ?.addEventListener('click', (e: Event) => {
-          e.stopPropagation();
-          emit('reset');
-        });
-      document.getElementById(playBtnId)?.addEventListener('click', togglePlay);
-      document
-        .getElementById(replayBtnId)
-        ?.addEventListener('click', toggleReplay);
-    };
-
-    const changePlayBtn = (code: string) => {
-      const playBtn = document.getElementById(playBtnId);
-      if (!playBtn) return;
-      if (code == 'play') {
-        playBtn.classList.remove(styles.btnPause);
-        playBtn.classList.add(styles.btnPlay);
-      } else {
-        playBtn.classList.remove(styles.btnPlay);
-        playBtn.classList.add(styles.btnPause);
-      }
-    };
-    const controls = `
-            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
-                <div class="${styles.actions}">
-                    <div class="${styles.actionWrap}">
-                        <button id="${playBtnId}" class="${styles.actionBtn}">
-                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
-                            <img class="${styles.playIcon}" src="${iconplay}" />
-                            <img class="${styles.playIcon}" src="${iconpause}" />
-                        </button>
-                    </div>
-                    <div class="${styles.time}">
-                        <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div><span class="${styles.line}">/</span>
-                        <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
-                    </div>
-                </div>
-                <div class="${styles.slider}">
-                    <div class="plyr__progress">
-                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
-                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
-                        <span role="tooltip" class="plyr__tooltip">00:00</span>
-                    </div>
-
-                </div>
-                <div class="${styles.actions}" style="padding-right: 0;">
-                    <button id="${replayBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
-                        <img class="loop" src="${iconReplay}" />
-                    </button>
-                </div>
-            </div>`;
-
-    // <div class="${styles.actionWrap}">
-    //             <button id="${playBtnId}" class="${styles.actionBtn}">
-    //                 <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
-    //                 <img class="${styles.playIcon}" src="${iconplay}" />
-    //                 <img class="${styles.playIcon}" src="${iconpause}" />
-    //             </button>
-
-    //         </div>
-    //         <div>${item.value.name}</div>
-
-    onMounted(() => {
-      videoItem.value = new Plyr(videoRef.value, {
-        autoplay: false,
-        controls: controls,
-        autopause: true, // 一次只允许
-        ratio: '16:9', // 强制所有视频的纵横比
-        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
-        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
-        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
-      });
-      if (videoItem.value) {
-        videoItem.value.on('play', () => {
-          if (videoItem.value) {
-            videoItem.value.muted = false;
-            videoItem.value.volume = 1;
-          }
-
-          // console.log('开始播放', item.value)
-          if (
-            !item.value.autoPlay &&
-            !item.value.isprepare &&
-            videoItem.value
-          ) {
-            // 加载完成后,取消静音播放
-
-            console.log(videoItem.value);
-            videoItem.value.pause();
-          }
-          changePlayBtn('');
-          emit('togglePlay', videoItem.value?.paused);
-        });
-        videoItem.value.on('pause', () => {
-          changePlayBtn('play');
-          emit('togglePlay', videoItem.value?.paused);
-        });
-        videoItem.value.on('ended', () => {
-          emit('ended');
-          changePlayBtn('play');
-        });
-        videoItem.value.once('loadedmetadata', () => {
-          changePlayBtn('play');
-          if (item.value.autoPlay && videoItem.value) {
-            videoItem.value.play();
-          }
-          emit('loadedmetadata', videoItem.value);
-        });
-
-        nextTick(() => {
-          onDefault();
-        });
-      }
-    });
-    expose({
-      changePlayBtn,
-      toggleHideControl
-    });
-    return () => (
-      <div class={styles.videoWrap}>
-        <video
-          style={{ width: '100%', height: '100%' }}
-          src={isEmtry.value ? '' : item.value.content}
-          ref={videoRef}
-          playsinline="false"></video>
-      </div>
-    );
-  }
-});
+import { defineComponent, nextTick, onMounted, toRefs } from 'vue';
+import 'plyr/dist/plyr.css';
+import Plyr from 'plyr';
+import { ref } from 'vue';
+import styles from './video.module.less';
+import iconplay from '../image/icon-pause.png';
+import iconpause from '../image/icon-play.png';
+import iconReplay from '../image/icon-replay.png';
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    },
+    isEmtry: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
+  setup(props, { emit, expose }) {
+    const { item, isEmtry } = toRefs(props);
+    const videoRef = ref();
+    const videoItem = ref<Plyr>();
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100);
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100);
+    const replayBtnId = 'replay' + Date.now() + Math.floor(Math.random() * 100);
+    const toggleHideControl = (isShow: false) => {
+      videoItem.value?.toggleControls(isShow);
+    };
+    const togglePlay = (e: Event) => {
+      e.stopPropagation();
+      videoItem.value?.togglePlay();
+    };
+    const toggleReplay = () => {
+      const replayBtn = document.getElementById(replayBtnId);
+      if (!replayBtn || !videoItem.value) return;
+      videoItem.value.restart();
+    };
+    const onDefault = () => {
+      document
+        .getElementById(controlID)
+        ?.addEventListener('click', (e: Event) => {
+          e.stopPropagation();
+          emit('reset');
+        });
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay);
+      document
+        .getElementById(replayBtnId)
+        ?.addEventListener('click', toggleReplay);
+    };
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId);
+      if (!playBtn) return;
+      if (code == 'play') {
+        playBtn.classList.remove(styles.btnPause);
+        playBtn.classList.add(styles.btnPlay);
+      } else {
+        playBtn.classList.remove(styles.btnPlay);
+        playBtn.classList.add(styles.btnPause);
+      }
+    };
+    const controls = `
+            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
+                <div class="${styles.actions}">
+                    <div class="${styles.actionWrap}">
+                        <button id="${playBtnId}" class="${styles.actionBtn}">
+                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
+                            <img class="${styles.playIcon}" src="${iconplay}" />
+                            <img class="${styles.playIcon}" src="${iconpause}" />
+                        </button>
+                    </div>
+                    <div class="${styles.time}">
+                        <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div><span class="${styles.line}">/</span>
+                        <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                    </div>
+                </div>
+                <div class="${styles.slider}">
+                    <div class="plyr__progress">
+                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                        <span role="tooltip" class="plyr__tooltip">00:00</span>
+                    </div>
+
+                </div>
+                <div class="${styles.actions}" style="padding-right: 0;">
+                    <button id="${replayBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
+                        <img class="loop" src="${iconReplay}" />
+                    </button>
+                </div>
+            </div>`;
+
+    // <div class="${styles.actionWrap}">
+    //             <button id="${playBtnId}" class="${styles.actionBtn}">
+    //                 <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
+    //                 <img class="${styles.playIcon}" src="${iconplay}" />
+    //                 <img class="${styles.playIcon}" src="${iconpause}" />
+    //             </button>
+
+    //         </div>
+    //         <div>${item.value.name}</div>
+
+    onMounted(() => {
+      videoItem.value = new Plyr(videoRef.value, {
+        autoplay: false,
+        controls: controls,
+        autopause: true, // 一次只允许
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
+        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
+      });
+      if (videoItem.value) {
+        videoItem.value.on('play', () => {
+          if (videoItem.value) {
+            videoItem.value.muted = false;
+            videoItem.value.volume = 1;
+          }
+
+          // console.log('开始播放', item.value)
+          if (
+            !item.value.autoPlay &&
+            !item.value.isprepare &&
+            videoItem.value
+          ) {
+            // 加载完成后,取消静音播放
+
+            console.log(videoItem.value);
+            videoItem.value.pause();
+          }
+          changePlayBtn('');
+          emit('togglePlay', videoItem.value?.paused);
+        });
+        videoItem.value.on('pause', () => {
+          changePlayBtn('play');
+          emit('togglePlay', videoItem.value?.paused);
+        });
+        videoItem.value.on('ended', () => {
+          emit('ended');
+          changePlayBtn('play');
+        });
+        videoItem.value.once('loadedmetadata', () => {
+          changePlayBtn('play');
+          if (item.value.autoPlay && videoItem.value) {
+            videoItem.value.play();
+          }
+          emit('loadedmetadata', videoItem.value);
+        });
+
+        nextTick(() => {
+          onDefault();
+        });
+      }
+    });
+    expose({
+      changePlayBtn,
+      toggleHideControl
+    });
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          style={{ width: '100%', height: '100%' }}
+          src={isEmtry.value ? '' : item.value.content}
+          ref={videoRef}
+          playsinline="false"></video>
+      </div>
+    );
+  }
+});

+ 34 - 31
src/views/attend-class/component/video-play.tsx

@@ -199,7 +199,7 @@ export default defineComponent({
         });
 
         videoItem.value.on('waiting', () => {
-          console.log('waiting')
+          console.log('waiting');
 
           videoFroms.isBuffering = true;
 
@@ -237,7 +237,7 @@ export default defineComponent({
         });
 
         videoItem.value.on('canplay', (e: any) => {
-          console.log('canplay')
+          console.log('canplay');
 
           videoFroms.isBuffering = false;
           videoFroms.bufferTimeout && clearTimeout(videoFroms.bufferTimeout); // 清除超时检测
@@ -247,7 +247,6 @@ export default defineComponent({
           );
           videoFroms.durationNum = videoItem.value.duration();
           emit('canplay');
-
         });
 
         // 视频播放异常
@@ -523,11 +522,11 @@ export default defineComponent({
         const currentTime = videoItem.value.currentTime();
         videoItem.value.load();
         videoItem.value.currentTime(currentTime);
-        console.log('online')
+        console.log('online');
         pause();
       } else if (val.type === 'offline') {
         videoFroms.isOnline = false;
-        console.log('offline')
+        console.log('offline');
 
         // 去掉检测加载缓存
         videoFroms.isBuffering = false;
@@ -626,19 +625,21 @@ export default defineComponent({
             emit('reset');
           }}>
           <div class={styles.slider}>
-            {props.imagePos !== 'right' && <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}
+            {props.imagePos !== 'right' && (
+              <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>}
+            )}
             <NSlider
               value={videoFroms.currentTimeNum}
               step={0.01}
@@ -651,19 +652,21 @@ export default defineComponent({
                 videoFroms.currentTime = timeFormat(Math.round(val || 0));
               }}
             />
-            {props.imagePos === 'right' && <div class={[styles.time, styles.rightTime]}>
-                      <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>}
+            {props.imagePos === 'right' && (
+              <div class={[styles.time, styles.rightTime]}>
+                <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 class={styles.tools}>
             {props.imagePos === 'right' ? (
@@ -912,7 +915,7 @@ export default defineComponent({
                 </div>
                 <div class={styles.actions}>
                   <div class={styles.actionWrap}>
-                   <div class={styles.title}>{props.item.title}</div>
+                    <div class={styles.title}>{props.item.title}</div>
                     {/* <div class={styles.time}>
                       <div
                         class="plyr__time plyr__time--current"

+ 5 - 8
src/views/attend-class/index.tsx

@@ -27,13 +27,7 @@ import { Vue3Lottie } from 'vue3-lottie';
 import playLoadData from './datas/data.json';
 // import Moveable from 'moveable';
 import VideoPlay from './component/video-play';
-import {
-  useMessage,
-  NModal,
-  NSpace,
-  NButton,
-  NTooltip
-} from 'naive-ui';
+import { useMessage, NModal, NSpace, NButton, NTooltip } from 'naive-ui';
 // import CardType from '@/components/card-type';
 import Pen from './component/tools/pen';
 import AudioPay from './component/audio-pay';
@@ -1864,7 +1858,10 @@ export default defineComponent({
                           </Transition>
                         </>
                       ) : m.type === 'IMG' ? (
-                        <ImageModal src={m.content} activeStatus={popupData.activeIndex === mIndex} />
+                        <ImageModal
+                          src={m.content}
+                          activeStatus={popupData.activeIndex === mIndex}
+                        />
                       ) : m.type === 'SONG' ? (
                         <AudioPay
                           imagePos={columnPos.value}

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

@@ -1,164 +1,164 @@
-import {
-  computed,
-  defineComponent,
-  onMounted,
-  reactive,
-  toRef,
-  watch,
-  ComputedRef,
-  ref
-} from 'vue';
-
-import styles from './index.module.less';
-import { NScrollbar, useMessage } from 'naive-ui';
-import deepClone from '/src/helpers/deep-clone';
-
-export default defineComponent({
-  name: 'chapter-modal',
-  props: {
-    treeList: {
-      type: Array,
-      default: () => []
-    },
-    itemActive: {
-      type: String,
-      default: ''
-    }
-  },
-  emits: ['handleSelect'],
-  setup(props, { emit }) {
-    const message = useMessage();
-    const itemActive = toRef(props, 'itemActive');
-    const treeList = ref(deepClone(props.treeList));
-
-    const formatParentId = (id: any, list: any, ids = [] as any) => {
-      for (const item of list) {
-        if (item.knowledgeList && item.knowledgeList.length > 0) {
-          const cIds: any = formatParentId(id, item.knowledgeList, [
-            ...ids,
-            item.id
-          ]);
-          if (cIds.includes(id)) {
-            return cIds;
-          }
-        }
-        if (item.id === id) {
-          return [...ids, id];
-        }
-      }
-      return ids;
-    };
-
-    watch(
-      () => props.itemActive,
-      () => {
-        const ids = formatParentId(itemActive.value, treeList.value);
-        if (ids.length > 0) {
-          treeList.value.forEach((tree: any) => {
-            if (tree.id == ids[0]) {
-              tree.selected = true;
-            } else {
-              tree.selected = false;
-            }
-          });
-        }
-      }
-    );
-
-    onMounted(() => {
-      const ids = formatParentId(itemActive.value, treeList.value);
-      if (ids.length > 0) {
-        treeList.value.forEach((tree: any) => {
-          if (tree.id == ids[0]) {
-            tree.selected = true;
-          }
-        });
-      }
-    });
-    const activeknowledgeList = computed<any | undefined>(() => {
-      return treeList.value.find((item: any) => {
-        return item.selected;
-      });
-    });
-    return () => (
-      <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
-                    class={[styles.treeItem, styles.parentItem]}
-                    onClick={() => {
-                      treeList.value.forEach((child: any) => {
-                        if (item.id !== child.id) {
-                          child.selected = false;
-                        }
-                      });
-                      item.selected = true;
-                    }}>
-                    <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>
-              ))}
-            </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>
-      </div>
-    );
-  }
-});
+import {
+  computed,
+  defineComponent,
+  onMounted,
+  reactive,
+  toRef,
+  watch,
+  ComputedRef,
+  ref
+} from 'vue';
+
+import styles from './index.module.less';
+import { NScrollbar, useMessage } from 'naive-ui';
+import deepClone from '/src/helpers/deep-clone';
+
+export default defineComponent({
+  name: 'chapter-modal',
+  props: {
+    treeList: {
+      type: Array,
+      default: () => []
+    },
+    itemActive: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const itemActive = toRef(props, 'itemActive');
+    const treeList = ref(deepClone(props.treeList));
+
+    const formatParentId = (id: any, list: any, ids = [] as any) => {
+      for (const item of list) {
+        if (item.knowledgeList && item.knowledgeList.length > 0) {
+          const cIds: any = formatParentId(id, item.knowledgeList, [
+            ...ids,
+            item.id
+          ]);
+          if (cIds.includes(id)) {
+            return cIds;
+          }
+        }
+        if (item.id === id) {
+          return [...ids, id];
+        }
+      }
+      return ids;
+    };
+
+    watch(
+      () => props.itemActive,
+      () => {
+        const ids = formatParentId(itemActive.value, treeList.value);
+        if (ids.length > 0) {
+          treeList.value.forEach((tree: any) => {
+            if (tree.id == ids[0]) {
+              tree.selected = true;
+            } else {
+              tree.selected = false;
+            }
+          });
+        }
+      }
+    );
+
+    onMounted(() => {
+      const ids = formatParentId(itemActive.value, treeList.value);
+      if (ids.length > 0) {
+        treeList.value.forEach((tree: any) => {
+          if (tree.id == ids[0]) {
+            tree.selected = true;
+          }
+        });
+      }
+    });
+    const activeknowledgeList = computed<any | undefined>(() => {
+      return treeList.value.find((item: any) => {
+        return item.selected;
+      });
+    });
+    return () => (
+      <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
+                    class={[styles.treeItem, styles.parentItem]}
+                    onClick={() => {
+                      treeList.value.forEach((child: any) => {
+                        if (item.id !== child.id) {
+                          child.selected = false;
+                        }
+                      });
+                      item.selected = true;
+                    }}>
+                    <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>
+              ))}
+            </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>
+      </div>
+    );
+  }
+});

+ 44 - 34
src/views/attend-class/model/train-settings/index.tsx

@@ -21,7 +21,7 @@ import dayjs from 'dayjs';
 import TheEmpty from '/src/components/TheEmpty';
 import requestOrigin from 'umi-request';
 import { modalClickMask } from '/src/state';
-import TheTipDialog from "@/components/TheTipDialog"
+import TheTipDialog from '@/components/TheTipDialog';
 import router from '/src/router';
 import ResetSubjectList from '/src/views/classList/modals/resetSubjectList';
 
@@ -98,7 +98,9 @@ export default defineComponent({
       if (trainingType === 'EVALUATION') {
         tList = [
           `${evaluateDifficult[configJson.evaluateDifficult]}`,
-          `${configJson.practiceChapterBegin || 0}-${configJson.practiceChapterEnd || 0}小节`,
+          `${configJson.practiceChapterBegin || 0}-${
+            configJson.practiceChapterEnd || 0
+          }小节`,
           `速度${configJson.evaluateSpeed || 0}`,
           `${configJson.trainingTimes}分达标`
         ];
@@ -130,7 +132,7 @@ export default defineComponent({
           const childs = Array.from(system?.querySelectorAll('system')) || [];
           childs.forEach((child: any) => {
             system?.removeChild(child);
-          })
+          });
         });
         const parts = xmlParse.getElementsByTagName('part');
         firstMeasures = parts[0]?.getElementsByTagName('measure');
@@ -142,8 +144,11 @@ export default defineComponent({
       // 判断读取小节数
       if (xmlStatus == 'success') {
         // 标记xml是否是从0小节开始
-        item.firstMeasureIsZero = firstMeasures?.[0]?.getAttribute('number') === '0';
-        item.practiceChapterMax = item.firstMeasureIsZero ? firstMeasures.length - 1 : firstMeasures.length;
+        item.firstMeasureIsZero =
+          firstMeasures?.[0]?.getAttribute('number') === '0';
+        item.practiceChapterMax = item.firstMeasureIsZero
+          ? firstMeasures.length - 1
+          : firstMeasures.length;
       } else {
         item.practiceChapterMax = 0;
       }
@@ -156,14 +161,14 @@ export default defineComponent({
 
     const tipDialog = reactive({
       show: false,
-      msg:"",
-      confirmButtonText:"",
+      msg: '',
+      confirmButtonText: '',
       cancelBtn: false,
-      type: "CLASS" as "CLASS"|"PERSON"|"MUSIC"
-    })
-    function handleLessonAddErr(data:any){
-      const { type, errList } = data
-      tipDialog.type = type
+      type: 'CLASS' as 'CLASS' | 'PERSON' | 'MUSIC'
+    });
+    function handleLessonAddErr(data: any) {
+      const { type, errList } = data;
+      tipDialog.type = type;
       // if(type === "CLASS"){
       //   // 班级乐器没有设置
       //   tipDialog.cancelBtn = true
@@ -190,24 +195,30 @@ export default defineComponent({
           };
         });
         setClassSubjects.value = msgList;
-      } else if(type === "MUSIC"){
+      } else if (type === 'MUSIC') {
         // 曲目和当前选择学生的乐器对不上的时候
-        tipDialog.cancelBtn = false
-        tipDialog.confirmButtonText = "我知道了"
-        const msg = errList.map((item:any)=>{
-          return `<div>曲目<span style="color:#F44541">【${item.musicSheetName}】</span>不支持<span style="color:#F44541">【${item.instrumentName}】</span>练习,请更换曲目或取消<span style="color:#F44541">【${item.instrumentName}】</span>的学生${errList.length>1?";":""}</div>`
-        })
-        tipDialog.msg = msg.join("")
+        tipDialog.cancelBtn = false;
+        tipDialog.confirmButtonText = '我知道了';
+        const msg = errList.map((item: any) => {
+          return `<div>曲目<span style="color:#F44541">【${
+            item.musicSheetName
+          }】</span>不支持<span style="color:#F44541">【${
+            item.instrumentName
+          }】</span>练习,请更换曲目或取消<span style="color:#F44541">【${
+            item.instrumentName
+          }】</span>的学生${errList.length > 1 ? ';' : ''}</div>`;
+        });
+        tipDialog.msg = msg.join('');
       }
-      tipDialog.show = true
+      tipDialog.show = true;
     }
-    function handleTipConfirm(){
-      if(["CLASS", "PERSON"].includes(tipDialog.type)){
+    function handleTipConfirm() {
+      if (['CLASS', 'PERSON'].includes(tipDialog.type)) {
         router.push({
-          path: "/classList"
-        })
-      }else if(tipDialog.type === "MUSIC"){
-        tipDialog.show = false
+          path: '/classList'
+        });
+      } else if (tipDialog.type === 'MUSIC') {
+        tipDialog.show = false;
       }
     }
     const onSubmit = async () => {
@@ -237,14 +248,14 @@ export default defineComponent({
           classGroupId: props.classGroupId,
           courseScheduleId: props.courseScheduleId || null
         };
-        const lessonRes =  await lessonTrainingAdd(params);
-        if (lessonRes.code === 200){
-          if (lessonRes.data.status){
+        const lessonRes = await lessonTrainingAdd(params);
+        if (lessonRes.code === 200) {
+          if (lessonRes.data.status) {
             message.success('布置成功');
             emit('close');
             emit('confirm');
           } else {
-            handleLessonAddErr(lessonRes.data)
+            handleLessonAddErr(lessonRes.data);
           }
         }
       } catch {
@@ -402,13 +413,12 @@ export default defineComponent({
           show={tipDialog.show}
           content={tipDialog.msg}
           onClose={() => {
-            tipDialog.show = false
+            tipDialog.show = false;
           }}
           onConfirm={handleTipConfirm}
-          cancelButtonText={"暂不设置"}
+          cancelButtonText={'暂不设置'}
           cancelBtn={tipDialog.cancelBtn}
-          confirmButtonText={tipDialog.confirmButtonText}
-        ></TheTipDialog>
+          confirmButtonText={tipDialog.confirmButtonText}></TheTipDialog>
 
         <NModal
           maskClosable={modalClickMask}

+ 1 - 1
src/views/classList/components/classStudent.tsx

@@ -100,7 +100,7 @@ export default defineComponent({
     const getInfo = async () => {
       try {
         const { data } = await api_studentStat({
-          classGroupId: route.query.id as any,
+          classGroupId: route.query.id as any
           // ...state.searchForm
         });
         payForm.studentNum = data.studentNum || 0;

+ 108 - 108
src/views/classList/components/classStudentRecode.tsx

@@ -1,108 +1,108 @@
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from '../index.module.less';
-import {
-  NButton,
-  NDataTable,
-  NForm,
-  NFormItem,
-  NImage,
-  NSelect,
-  NSpace,
-  NTabPane,
-  NTabs
-} from 'naive-ui';
-import { getStudentDetail, getTrainingStudentList } from '../api';
-import { useRoute } from 'vue-router';
-import CBreadcrumb from '/src/components/CBreadcrumb';
-import defultHeade from '@/components/layout/images/teacherIcon.png';
-import femaleIcon from '@/views/setting/images/femaleIcon.png';
-import maleIcon from '@/views/setting/images/maleIcon.png';
-import PracticeData from '@/views/studentList/components/practiceData';
-import EvaluationRecords from '@/views/studentList/components/evaluationRecords';
-
-import dayjs from 'dayjs';
-export default defineComponent({
-  name: 'classStudentRecode',
-  setup(props, { emit }) {
-    const state = reactive({
-      studentInfo: { avatar: '', nickname: '', gender: null, subjectNames: '' }
-    });
-    const activeStudentTab = ref('textRcode');
-    const route = useRoute();
-    const routerList = ref([
-      { name: '班级管理', path: '/classList' },
-      { name: route.query.name, path: '/classDetail' },
-      { name: route.query.studentName, path: '/classStudentRecode' }
-    ] as any);
-
-    const getWorkInfo = async () => {
-      console.log(route.query.studentId);
-      try {
-        const res = await getStudentDetail({
-          id: route.query.studentId
-        });
-        state.studentInfo = { ...res.data };
-      } catch (e) {
-        console.log(e);
-      }
-    };
-    onMounted(() => {
-      getWorkInfo();
-    });
-
-    return () => (
-      <div>
-        <CBreadcrumb list={routerList.value}></CBreadcrumb>
-        <div class={styles.listWrap}>
-          <div class={styles.teacherList}>
-            <div class={styles.teacherHeader}>
-              <div class={styles.teacherHeaderBorder}>
-                <NImage
-                  class={styles.teacherHeaderImg}
-                  src={
-                    state.studentInfo.avatar
-                      ? state.studentInfo.avatar
-                      : defultHeade
-                  }
-                  previewDisabled></NImage>
-              </div>
-            </div>
-            <div class={styles.workafterInfo}>
-              <h4 class={styles.studentGender}>
-                {state.studentInfo.nickname}{' '}
-                <NImage
-                  src={
-                    state.studentInfo.gender ? maleIcon : femaleIcon
-                  }></NImage>
-              </h4>
-              <p>
-                {route.query.name}{' '}
-                {state.studentInfo.subjectNames
-                  ? '|' + state.studentInfo.subjectNames
-                  : ''}
-              </p>
-            </div>
-          </div>
-          <NTabs
-            class={styles.customTabs}
-            v-model:value={activeStudentTab.value}
-            size="large"
-            animated={false}
-            pane-wrapper-style="margin: 0 -4px"
-            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
-            <NTabPane name="textRcode" tab="练习记录">
-              <PracticeData
-                classGroupId={route.query.id as string}
-                studentId={route.query.studentId as string}></PracticeData>
-            </NTabPane>
-            <NTabPane name="evaluatingRcode" tab="评测记录">
-              <EvaluationRecords
-                classGroupId={route.query.id as string}
-                studentId={route.query.studentId as string}></EvaluationRecords>
-            </NTabPane>
-          </NTabs>
-        </div>
-      </div>
-    );
-  }
-});
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import {
+  NButton,
+  NDataTable,
+  NForm,
+  NFormItem,
+  NImage,
+  NSelect,
+  NSpace,
+  NTabPane,
+  NTabs
+} from 'naive-ui';
+import { getStudentDetail, getTrainingStudentList } from '../api';
+import { useRoute } from 'vue-router';
+import CBreadcrumb from '/src/components/CBreadcrumb';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import femaleIcon from '@/views/setting/images/femaleIcon.png';
+import maleIcon from '@/views/setting/images/maleIcon.png';
+import PracticeData from '@/views/studentList/components/practiceData';
+import EvaluationRecords from '@/views/studentList/components/evaluationRecords';
+
+import dayjs from 'dayjs';
+export default defineComponent({
+  name: 'classStudentRecode',
+  setup(props, { emit }) {
+    const state = reactive({
+      studentInfo: { avatar: '', nickname: '', gender: null, subjectNames: '' }
+    });
+    const activeStudentTab = ref('textRcode');
+    const route = useRoute();
+    const routerList = ref([
+      { name: '班级管理', path: '/classList' },
+      { name: route.query.name, path: '/classDetail' },
+      { name: route.query.studentName, path: '/classStudentRecode' }
+    ] as any);
+
+    const getWorkInfo = async () => {
+      console.log(route.query.studentId);
+      try {
+        const res = await getStudentDetail({
+          id: route.query.studentId
+        });
+        state.studentInfo = { ...res.data };
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    onMounted(() => {
+      getWorkInfo();
+    });
+
+    return () => (
+      <div>
+        <CBreadcrumb list={routerList.value}></CBreadcrumb>
+        <div class={styles.listWrap}>
+          <div class={styles.teacherList}>
+            <div class={styles.teacherHeader}>
+              <div class={styles.teacherHeaderBorder}>
+                <NImage
+                  class={styles.teacherHeaderImg}
+                  src={
+                    state.studentInfo.avatar
+                      ? state.studentInfo.avatar
+                      : defultHeade
+                  }
+                  previewDisabled></NImage>
+              </div>
+            </div>
+            <div class={styles.workafterInfo}>
+              <h4 class={styles.studentGender}>
+                {state.studentInfo.nickname}{' '}
+                <NImage
+                  src={
+                    state.studentInfo.gender ? maleIcon : femaleIcon
+                  }></NImage>
+              </h4>
+              <p>
+                {route.query.name}{' '}
+                {state.studentInfo.subjectNames
+                  ? '|' + state.studentInfo.subjectNames
+                  : ''}
+              </p>
+            </div>
+          </div>
+          <NTabs
+            class={styles.customTabs}
+            v-model:value={activeStudentTab.value}
+            size="large"
+            animated={false}
+            pane-wrapper-style="margin: 0 -4px"
+            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
+            <NTabPane name="textRcode" tab="练习记录">
+              <PracticeData
+                classGroupId={route.query.id as string}
+                studentId={route.query.studentId as string}></PracticeData>
+            </NTabPane>
+            <NTabPane name="evaluatingRcode" tab="评测记录">
+              <EvaluationRecords
+                classGroupId={route.query.id as string}
+                studentId={route.query.studentId as string}></EvaluationRecords>
+            </NTabPane>
+          </NTabs>
+        </div>
+      </div>
+    );
+  }
+});

+ 1 - 1
src/views/classList/components/testRecode.tsx

@@ -127,7 +127,7 @@ export default defineComponent({
           ...state.pagination,
           ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
         });
-        searchTimer.value = timer.value
+        searchTimer.value = timer.value;
 
         state.tableList = res.data.rows;
 

+ 109 - 109
src/views/classList/contants.ts

@@ -1,109 +1,109 @@
-import { useUserStore } from '/src/store/modules/users';
-export const threeYearSystem = [
-  { label: '全部年级', value: '' },
-  { label: '七年级', value: 7 },
-  { label: '八年级', value: 8 },
-  { label: '九年级', value: 9 }
-];
-export const foreYearSystem = [
-  { label: '全部年级', value: '' },
-  { label: '六年级', value: 6 },
-  { label: '七年级', value: 7 },
-  { label: '八年级', value: 8 },
-  { label: '九年级', value: 9 }
-];
-export const fiveYearSystem = [
-  { label: '全部年级', value: '' },
-  { label: '一年级', value: 1 },
-  { label: '二年级', value: 2 },
-  { label: '三年级', value: 3 },
-  { label: '四年级', value: 4 },
-  { label: '五年级', value: 5 }
-];
-export const sixYearSystem = [
-  { label: '全部年级', value: '' },
-  { label: '一年级', value: 1 },
-  { label: '二年级', value: 2 },
-  { label: '三年级', value: 3 },
-  { label: '四年级', value: 4 },
-  { label: '五年级', value: 5 },
-  { label: '六年级', value: 6 }
-];
-export const nineYearSystem = [
-  { label: '全部年级', value: '' },
-  { label: '一年级', value: 1 },
-  { label: '二年级', value: 2 },
-  { label: '三年级', value: 3 },
-  { label: '四年级', value: 4 },
-  { label: '五年级', value: 5 },
-  { label: '六年级', value: 6 },
-  { label: '七年级', value: 7 },
-  { label: '八年级', value: 8 },
-  { label: '九年级', value: 9 }
-];
-export const classArray = [
-  { label: '全部班级', value: '' },
-  { value: 1, label: '1班' },
-  { value: 2, label: '2班' },
-  { value: 3, label: '3班' },
-  { value: 4, label: '4班' },
-  { value: 5, label: '5班' },
-  { value: 6, label: '6班' },
-  { value: 7, label: '7班' },
-  { value: 8, label: '8班' },
-  { value: 9, label: '9班' },
-  { value: 10, label: '10班' },
-  { value: 11, label: '11班' },
-  { value: 12, label: '12班' },
-  { value: 13, label: '13班' },
-  { value: 14, label: '14班' },
-  { value: 15, label: '15班' },
-  { value: 16, label: '16班' },
-  { value: 17, label: '17班' },
-  { value: 18, label: '18班' },
-  { value: 19, label: '19班' },
-  { value: 20, label: '20班' },
-  { value: 21, label: '21班' },
-  { value: 22, label: '22班' },
-  { value: 23, label: '23班' },
-  { value: 24, label: '24班' },
-  { value: 25, label: '25班' },
-  { value: 26, label: '26班' },
-  { value: 27, label: '27班' },
-  { value: 28, label: '28班' },
-  { value: 29, label: '29班' },
-  { value: 30, label: '30班' },
-  { value: 31, label: '31班' },
-  { value: 32, label: '32班' },
-  { value: 33, label: '33班' },
-  { value: 34, label: '34班' },
-  { value: 35, label: '35班' },
-  { value: 36, label: '36班' },
-  { value: 37, label: '37班' },
-  { value: 38, label: '38班' },
-  { value: 39, label: '39班' },
-  { value: 40, label: '40班' }
-];
-
-export const getgradeNumList = () => {
-  const userInfo = useUserStore();
-  let gradeNumList = [];
-  if (userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'THREE_YEAR_SYSTEM') {
-    gradeNumList = threeYearSystem;
-  } else if (
-    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'FORE_YEAR_SYSTEM'
-  ) {
-    gradeNumList = foreYearSystem;
-  } else if (
-    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'FIVE_YEAR_SYSTEM'
-  ) {
-    gradeNumList = fiveYearSystem;
-  } else if (
-    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'SIX_YEAR_SYSTEM'
-  ) {
-    gradeNumList = sixYearSystem;
-  } else {
-    gradeNumList = nineYearSystem;
-  }
-  return gradeNumList;
-};
+import { useUserStore } from '/src/store/modules/users';
+export const threeYearSystem = [
+  { label: '全部年级', value: '' },
+  { label: '七年级', value: 7 },
+  { label: '八年级', value: 8 },
+  { label: '九年级', value: 9 }
+];
+export const foreYearSystem = [
+  { label: '全部年级', value: '' },
+  { label: '六年级', value: 6 },
+  { label: '七年级', value: 7 },
+  { label: '八年级', value: 8 },
+  { label: '九年级', value: 9 }
+];
+export const fiveYearSystem = [
+  { label: '全部年级', value: '' },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 }
+];
+export const sixYearSystem = [
+  { label: '全部年级', value: '' },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 },
+  { label: '六年级', value: 6 }
+];
+export const nineYearSystem = [
+  { label: '全部年级', value: '' },
+  { label: '一年级', value: 1 },
+  { label: '二年级', value: 2 },
+  { label: '三年级', value: 3 },
+  { label: '四年级', value: 4 },
+  { label: '五年级', value: 5 },
+  { label: '六年级', value: 6 },
+  { label: '七年级', value: 7 },
+  { label: '八年级', value: 8 },
+  { label: '九年级', value: 9 }
+];
+export const classArray = [
+  { label: '全部班级', value: '' },
+  { value: 1, label: '1班' },
+  { value: 2, label: '2班' },
+  { value: 3, label: '3班' },
+  { value: 4, label: '4班' },
+  { value: 5, label: '5班' },
+  { value: 6, label: '6班' },
+  { value: 7, label: '7班' },
+  { value: 8, label: '8班' },
+  { value: 9, label: '9班' },
+  { value: 10, label: '10班' },
+  { value: 11, label: '11班' },
+  { value: 12, label: '12班' },
+  { value: 13, label: '13班' },
+  { value: 14, label: '14班' },
+  { value: 15, label: '15班' },
+  { value: 16, label: '16班' },
+  { value: 17, label: '17班' },
+  { value: 18, label: '18班' },
+  { value: 19, label: '19班' },
+  { value: 20, label: '20班' },
+  { value: 21, label: '21班' },
+  { value: 22, label: '22班' },
+  { value: 23, label: '23班' },
+  { value: 24, label: '24班' },
+  { value: 25, label: '25班' },
+  { value: 26, label: '26班' },
+  { value: 27, label: '27班' },
+  { value: 28, label: '28班' },
+  { value: 29, label: '29班' },
+  { value: 30, label: '30班' },
+  { value: 31, label: '31班' },
+  { value: 32, label: '32班' },
+  { value: 33, label: '33班' },
+  { value: 34, label: '34班' },
+  { value: 35, label: '35班' },
+  { value: 36, label: '36班' },
+  { value: 37, label: '37班' },
+  { value: 38, label: '38班' },
+  { value: 39, label: '39班' },
+  { value: 40, label: '40班' }
+];
+
+export const getgradeNumList = () => {
+  const userInfo = useUserStore();
+  let gradeNumList = [];
+  if (userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'THREE_YEAR_SYSTEM') {
+    gradeNumList = threeYearSystem;
+  } else if (
+    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'FORE_YEAR_SYSTEM'
+  ) {
+    gradeNumList = foreYearSystem;
+  } else if (
+    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'FIVE_YEAR_SYSTEM'
+  ) {
+    gradeNumList = fiveYearSystem;
+  } else if (
+    userInfo.getUserInfo.schoolInfos[0]?.gradeYear == 'SIX_YEAR_SYSTEM'
+  ) {
+    gradeNumList = sixYearSystem;
+  } else {
+    gradeNumList = nineYearSystem;
+  }
+  return gradeNumList;
+};

+ 2 - 1
src/views/classList/index.tsx

@@ -718,7 +718,8 @@ export default defineComponent({
         {state.addStudentVisible ? (
           <div
             v-model:show={state.addStudentVisible}
-            class={['n-modal-mask', styles.popBox]} style={{ zIndex: '100001' }}>
+            class={['n-modal-mask', styles.popBox]}
+            style={{ zIndex: '100001' }}>
             <AddStudentModel
               activeRow={state.activeRow}
               onClose={() => {

+ 7 - 5
src/views/classList/modals/classTrainingDetails.tsx

@@ -52,7 +52,9 @@ export default defineComponent({
       if (trainingType === 'EVALUATION') {
         tList = [
           `${evaluateDifficult[configJson.evaluateDifficult]}`,
-          `${configJson.practiceChapterBegin || 0}-${configJson.practiceChapterEnd || 0}小节`,
+          `${configJson.practiceChapterBegin || 0}-${
+            configJson.practiceChapterEnd || 0
+          }小节`,
           `速度${configJson.evaluateSpeed || 0}`,
           `${configJson.trainingTimes}分达标`
         ];
@@ -131,14 +133,14 @@ export default defineComponent({
                   开始时间:
                   {teacherInfo.value.createTime
                     ? dayjs(new Date(teacherInfo.value.createTime)).format(
-                      'YYYY-MM-DD'
-                    )
+                        'YYYY-MM-DD'
+                      )
                     : '--'}{' '}
                   | 结束时间:
                   {teacherInfo.value.expireDate
                     ? dayjs(new Date(teacherInfo.value.expireDate)).format(
-                      'YYYY-MM-DD'
-                    )
+                        'YYYY-MM-DD'
+                      )
                     : '--'}
                 </p>
               </div>

+ 162 - 162
src/views/classList/modals/resetStudent.tsx

@@ -1,162 +1,162 @@
-import {
-  NButton,
-  NLegacyTransfer,
-  NSpace,
-  useMessage,
-  NPopselect,
-  NImage,
-  NDropdown
-} from 'naive-ui';
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from '../index.module.less';
-import smallArrow from '../images/smallArrow.png';
-import { getCLassStudent, classGroupList } from '../api';
-export default defineComponent({
-  props: {
-    activeRow: {
-      type: Object,
-      default: () => ({ id: '' })
-    }
-  },
-  name: 'resetStudent',
-  emits: ['close'],
-  setup(props, { emit }) {
-    const message = useMessage();
-    const data = reactive({
-      uploading: false
-    });
-    const options = ref([] as any);
-    const currentStudnetList = ref([] as any);
-    const chioseOptions = ref([] as any);
-    const formRef = ref();
-    const handleSubmit = async () => {
-      data.uploading = true;
-    };
-    const classList = ref([] as any);
-    console.log(props.activeRow, 'activeRow');
-    const targetClass = reactive({
-      name: '',
-      id: ''
-    });
-    //
-
-    /**
-     * 这里干3件事  1.获取当前班的学生
-     * 2.查询所有的班级列表  并且排查当前班级
-     * 3.默认选择第一个班级 并且查出此班的学生
-     */
-    const chioseStudnet = (val: any) => {
-      console.log(val);
-    };
-    const getAllClassList = async () => {
-      try {
-        const res = await classGroupList({ page: 1, rows: 9999 });
-        classList.value = res.data.rows.map((item: any) => {
-          return {
-            label: item.name,
-            key: item.id,
-            disabled: item.id == props.activeRow.id
-          };
-        });
-        if (classList.value[0].disabled) {
-          targetClass.name = classList.value[1].label;
-          targetClass.id = classList.value[1].id;
-        } else {
-          targetClass.name = classList.value[0].label;
-          targetClass.id = classList.value[0].id;
-        }
-
-        console.log(classList.value, ' classList.value');
-      } catch (e) {
-        console.log(e);
-      }
-    };
-    const getCLassStudentList = async (id: string | number) => {
-      return await getCLassStudent({
-        page: 1,
-        rows: 999,
-        classGroupId: id
-      });
-    };
-    const chioseClass = async (val: any) => {
-      classList.value.forEach((item: any) => {
-        if (item.key == val) {
-          targetClass.name = item.label;
-          targetClass.id = item.key;
-        }
-      });
-      console.log(targetClass);
-      const res = await getCLassStudentList(val);
-      chioseOptions.value = res.data.rows.map((item: any) => {
-        return item.id;
-      });
-      console.log(chioseOptions.value, 'chioseOptions.value');
-    };
-    onMounted(async () => {
-      console.log('onMounted');
-      getAllClassList();
-      const res = await getCLassStudentList(props.activeRow.id as string);
-      currentStudnetList.value = res.data.rows.map((item: any) => {
-        return {
-          label: item.nickname + '(' + item.id + ')',
-          value: item.id
-        };
-      });
-    });
-    return () => (
-      <div class={[styles.container, styles.resetStudentWrap]}>
-        <div class={styles.studentTransfer}>
-          <NDropdown
-            key="111"
-            v-model:value={targetClass.id}
-            options={classList.value}
-            onSelect={(value: any) => {
-              chioseClass(value);
-            }}
-            scrollable>
-            <NImage
-              class={styles.smallArrow}
-              src={smallArrow}
-              previewDisabled></NImage>
-          </NDropdown>
-          <NLegacyTransfer
-            source-title={props.activeRow.name}
-            target-title={targetClass.name}
-            size="large"
-            ref={formRef}
-            options={currentStudnetList.value}
-            source-filter-placeholder="请输入学生姓名"
-            target-filter-placeholder="请输入学生姓名"
-            v-model:value={chioseOptions.value}
-            virtual-scroll
-            onUpdate:value={(val: any) => {
-              chioseStudnet(val);
-            }}
-            filterable></NLegacyTransfer>
-          <div class={styles.studentTransferBottom}>
-            <div class={[styles.bottomLeft, styles.bottom]}>
-              <div class={styles.bottomWrap}>共0名学生</div>
-            </div>
-            <div class={[styles.bottomRight, styles.bottom]}>
-              <div class={styles.bottomWrap}>共0名学生</div>
-            </div>
-          </div>
-        </div>
-
-        <NSpace class={styles.btnGroup} justify="center">
-          <NButton round onClick={() => emit('close')}>
-            取消
-          </NButton>
-          <NButton
-            round
-            loading={data.uploading}
-            type="primary"
-            // onClick={() => handleSave()}
-          >
-            保存
-          </NButton>
-        </NSpace>
-      </div>
-    );
-  }
-});
+import {
+  NButton,
+  NLegacyTransfer,
+  NSpace,
+  useMessage,
+  NPopselect,
+  NImage,
+  NDropdown
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import smallArrow from '../images/smallArrow.png';
+import { getCLassStudent, classGroupList } from '../api';
+export default defineComponent({
+  props: {
+    activeRow: {
+      type: Object,
+      default: () => ({ id: '' })
+    }
+  },
+  name: 'resetStudent',
+  emits: ['close'],
+  setup(props, { emit }) {
+    const message = useMessage();
+    const data = reactive({
+      uploading: false
+    });
+    const options = ref([] as any);
+    const currentStudnetList = ref([] as any);
+    const chioseOptions = ref([] as any);
+    const formRef = ref();
+    const handleSubmit = async () => {
+      data.uploading = true;
+    };
+    const classList = ref([] as any);
+    console.log(props.activeRow, 'activeRow');
+    const targetClass = reactive({
+      name: '',
+      id: ''
+    });
+    //
+
+    /**
+     * 这里干3件事  1.获取当前班的学生
+     * 2.查询所有的班级列表  并且排查当前班级
+     * 3.默认选择第一个班级 并且查出此班的学生
+     */
+    const chioseStudnet = (val: any) => {
+      console.log(val);
+    };
+    const getAllClassList = async () => {
+      try {
+        const res = await classGroupList({ page: 1, rows: 9999 });
+        classList.value = res.data.rows.map((item: any) => {
+          return {
+            label: item.name,
+            key: item.id,
+            disabled: item.id == props.activeRow.id
+          };
+        });
+        if (classList.value[0].disabled) {
+          targetClass.name = classList.value[1].label;
+          targetClass.id = classList.value[1].id;
+        } else {
+          targetClass.name = classList.value[0].label;
+          targetClass.id = classList.value[0].id;
+        }
+
+        console.log(classList.value, ' classList.value');
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    const getCLassStudentList = async (id: string | number) => {
+      return await getCLassStudent({
+        page: 1,
+        rows: 999,
+        classGroupId: id
+      });
+    };
+    const chioseClass = async (val: any) => {
+      classList.value.forEach((item: any) => {
+        if (item.key == val) {
+          targetClass.name = item.label;
+          targetClass.id = item.key;
+        }
+      });
+      console.log(targetClass);
+      const res = await getCLassStudentList(val);
+      chioseOptions.value = res.data.rows.map((item: any) => {
+        return item.id;
+      });
+      console.log(chioseOptions.value, 'chioseOptions.value');
+    };
+    onMounted(async () => {
+      console.log('onMounted');
+      getAllClassList();
+      const res = await getCLassStudentList(props.activeRow.id as string);
+      currentStudnetList.value = res.data.rows.map((item: any) => {
+        return {
+          label: item.nickname + '(' + item.id + ')',
+          value: item.id
+        };
+      });
+    });
+    return () => (
+      <div class={[styles.container, styles.resetStudentWrap]}>
+        <div class={styles.studentTransfer}>
+          <NDropdown
+            key="111"
+            v-model:value={targetClass.id}
+            options={classList.value}
+            onSelect={(value: any) => {
+              chioseClass(value);
+            }}
+            scrollable>
+            <NImage
+              class={styles.smallArrow}
+              src={smallArrow}
+              previewDisabled></NImage>
+          </NDropdown>
+          <NLegacyTransfer
+            source-title={props.activeRow.name}
+            target-title={targetClass.name}
+            size="large"
+            ref={formRef}
+            options={currentStudnetList.value}
+            source-filter-placeholder="请输入学生姓名"
+            target-filter-placeholder="请输入学生姓名"
+            v-model:value={chioseOptions.value}
+            virtual-scroll
+            onUpdate:value={(val: any) => {
+              chioseStudnet(val);
+            }}
+            filterable></NLegacyTransfer>
+          <div class={styles.studentTransferBottom}>
+            <div class={[styles.bottomLeft, styles.bottom]}>
+              <div class={styles.bottomWrap}>共0名学生</div>
+            </div>
+            <div class={[styles.bottomRight, styles.bottom]}>
+              <div class={styles.bottomWrap}>共0名学生</div>
+            </div>
+          </div>
+        </div>
+
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            round
+            loading={data.uploading}
+            type="primary"
+            // onClick={() => handleSave()}
+          >
+            保存
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 127 - 127
src/views/classList/modals/resetSubject.tsx

@@ -1,127 +1,127 @@
-import {
-  NButton,
-  NSpace,
-  useMessage,
-  NForm,
-  NFormItem,
-  NCascader
-} from 'naive-ui';
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from '../index.module.less';
-import { getConfiguredSubjects, updateInstrument } from '../api';
-export default defineComponent({
-  props: {
-    activeRow: {
-      type: Object,
-      default: () => ({ id: '' })
-    },
-    gradeNumList: {
-      type: Array,
-      default: () => []
-    },
-    classArray: {
-      type: Array,
-      default: () => []
-    }
-  },
-  name: 'resetStudent',
-  emits: ['close', 'getList'],
-  setup(props, { emit }) {
-    const data = reactive({
-      uploading: false
-    });
-    const message = useMessage();
-    const foemsRef = ref();
-    const subjectList = ref([] as any);
-    const createClassForm = reactive({
-      currentGradeNum: null,
-      gradeYear: null,
-      currentClass: null,
-      instrumentId: null,
-      id: null
-    });
-    onMounted(() => {
-      createClassForm.currentGradeNum = props.activeRow.currentGradeNum;
-      createClassForm.gradeYear = props.activeRow.gradeYear;
-      createClassForm.currentClass = props.activeRow.currentClass;
-      createClassForm.instrumentId = props.activeRow.instrumentId;
-      createClassForm.id = props.activeRow.id;
-
-      getConfigSubject();
-    });
-    const submitForms = () => {
-      foemsRef.value.validate(async (error: any) => {
-        if (error) {
-          return;
-        }
-        data.uploading = true;
-        try {
-          await updateInstrument({ ...createClassForm });
-          message.success('修改成功');
-          emit('close');
-          emit('getList');
-          data.uploading = false;
-        } catch (e) {
-          console.log(e);
-        }
-        data.uploading = false;
-      });
-    };
-
-    const getConfigSubject = async () => {
-      try {
-        const { data } = await getConfiguredSubjects({
-          gradeYear: createClassForm.gradeYear,
-          currentGradeNum: createClassForm.currentGradeNum,
-          currentClass: createClassForm.currentClass
-        });
-        const temp = data || [];
-        subjectList.value = temp;
-      } catch {
-        //
-      }
-    };
-
-    return () => (
-      <div class={[styles.addClass]}>
-        <NForm label-placement="left" model={createClassForm} ref={foemsRef}>
-          <NFormItem
-            path="instrumentId"
-            rule={[
-              {
-                required: true,
-                message: '请选择乐器'
-              }
-            ]}>
-            <NCascader
-              placeholder="请选择乐器"
-              v-model:value={createClassForm.instrumentId}
-              options={subjectList.value}
-              checkStrategy="child"
-              showPath={false}
-              childrenField="instruments"
-              expandTrigger="hover"
-              labelField="name"
-              valueField="id"
-              clearable
-              filterable
-              style={{ width: '400px' }}
-            />
-          </NFormItem>
-        </NForm>
-        <NSpace class={styles.btnGroup} justify="center">
-          <NButton round onClick={() => emit('close')}>
-            取消
-          </NButton>
-          <NButton
-            round
-            loading={data.uploading}
-            onClick={() => submitForms()}
-            type="primary">
-            保存
-          </NButton>
-        </NSpace>
-      </div>
-    );
-  }
-});
+import {
+  NButton,
+  NSpace,
+  useMessage,
+  NForm,
+  NFormItem,
+  NCascader
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import { getConfiguredSubjects, updateInstrument } from '../api';
+export default defineComponent({
+  props: {
+    activeRow: {
+      type: Object,
+      default: () => ({ id: '' })
+    },
+    gradeNumList: {
+      type: Array,
+      default: () => []
+    },
+    classArray: {
+      type: Array,
+      default: () => []
+    }
+  },
+  name: 'resetStudent',
+  emits: ['close', 'getList'],
+  setup(props, { emit }) {
+    const data = reactive({
+      uploading: false
+    });
+    const message = useMessage();
+    const foemsRef = ref();
+    const subjectList = ref([] as any);
+    const createClassForm = reactive({
+      currentGradeNum: null,
+      gradeYear: null,
+      currentClass: null,
+      instrumentId: null,
+      id: null
+    });
+    onMounted(() => {
+      createClassForm.currentGradeNum = props.activeRow.currentGradeNum;
+      createClassForm.gradeYear = props.activeRow.gradeYear;
+      createClassForm.currentClass = props.activeRow.currentClass;
+      createClassForm.instrumentId = props.activeRow.instrumentId;
+      createClassForm.id = props.activeRow.id;
+
+      getConfigSubject();
+    });
+    const submitForms = () => {
+      foemsRef.value.validate(async (error: any) => {
+        if (error) {
+          return;
+        }
+        data.uploading = true;
+        try {
+          await updateInstrument({ ...createClassForm });
+          message.success('修改成功');
+          emit('close');
+          emit('getList');
+          data.uploading = false;
+        } catch (e) {
+          console.log(e);
+        }
+        data.uploading = false;
+      });
+    };
+
+    const getConfigSubject = async () => {
+      try {
+        const { data } = await getConfiguredSubjects({
+          gradeYear: createClassForm.gradeYear,
+          currentGradeNum: createClassForm.currentGradeNum,
+          currentClass: createClassForm.currentClass
+        });
+        const temp = data || [];
+        subjectList.value = temp;
+      } catch {
+        //
+      }
+    };
+
+    return () => (
+      <div class={[styles.addClass]}>
+        <NForm label-placement="left" model={createClassForm} ref={foemsRef}>
+          <NFormItem
+            path="instrumentId"
+            rule={[
+              {
+                required: true,
+                message: '请选择乐器'
+              }
+            ]}>
+            <NCascader
+              placeholder="请选择乐器"
+              v-model:value={createClassForm.instrumentId}
+              options={subjectList.value}
+              checkStrategy="child"
+              showPath={false}
+              childrenField="instruments"
+              expandTrigger="hover"
+              labelField="name"
+              valueField="id"
+              clearable
+              filterable
+              style={{ width: '400px' }}
+            />
+          </NFormItem>
+        </NForm>
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            round
+            loading={data.uploading}
+            onClick={() => submitForms()}
+            type="primary">
+            保存
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 139 - 139
src/views/classList/modals/updateSubject.tsx

@@ -1,139 +1,139 @@
-import {
-  NButton,
-  NSpace,
-  useMessage,
-  NForm,
-  NFormItem,
-  NCascader
-} from 'naive-ui';
-import { defineComponent, onMounted, reactive, ref } from 'vue';
-import styles from '../index.module.less';
-import CSelect from '/src/components/CSelect';
-import { getConfiguredSubjects, updateInstrument } from '../api';
-import { useCatchStore } from '/src/store/modules/catchData';
-export default defineComponent({
-  props: {
-    activeRow: {
-      type: Object,
-      default: () => ({ id: '' })
-    },
-    subjectList: {
-      type: Array,
-      default: () => []
-    }
-  },
-  name: 'resetStudent',
-  emits: ['close', 'getList', 'confirm'],
-  setup(props, { emit }) {
-    const catchStore = useCatchStore();
-    const data = reactive({
-      uploading: false
-    });
-    const message = useMessage();
-    const subjectList = ref([] as any);
-    const foemsRef = ref();
-    const createClassForm = reactive({
-      currentGradeNum: null,
-      currentClass: null,
-      gradeYear: null,
-      instrumentId: null,
-      id: null
-    });
-
-    const getConfigSubject = async () => {
-      try {
-        const { data } = await getConfiguredSubjects({
-          gradeYear: createClassForm.gradeYear,
-          currentGradeNum: createClassForm.currentGradeNum,
-          currentClass: createClassForm.currentClass
-        });
-        const temp = data || [];
-        subjectList.value = temp;
-      } catch {
-        //
-      }
-    };
-    onMounted(async () => {
-      // 获取教材分类列表
-      // await catchStore.getSubjects();
-      // subjectList.value = [
-      //   { id: null, name: '选择声部' },
-      //   ...catchStore.getSubjectList
-      // console.log(props.activeRow, ' props.activeRow');
-      // ];
-
-      createClassForm.gradeYear = props.activeRow.gradeYear;
-      createClassForm.currentGradeNum = props.activeRow.currentGradeNum;
-      createClassForm.currentClass = props.activeRow.currentClass;
-      createClassForm.instrumentId = props.activeRow.instrumentId;
-      createClassForm.id = props.activeRow.id;
-
-      getConfigSubject();
-    });
-    const submitForms = () => {
-      foemsRef.value.validate(async (error: any) => {
-        if (error) {
-          return;
-        }
-        data.uploading = true;
-        try {
-          await updateInstrument({ ...createClassForm });
-          message.success('修改成功');
-          emit('close');
-          emit('confirm', {
-            lastUseCoursewareId: props.activeRow.lessonCoursewareId,
-            unit: props.activeRow.lessonCoursewareKnowledgeDetailId,
-            instrumentId: createClassForm.instrumentId,
-            name: props.activeRow.name, // 班级名称
-            classGroupId: props.activeRow.id // 班级编号
-          });
-          emit('getList');
-        } catch (e) {
-          console.log(e);
-        }
-        data.uploading = false;
-      });
-    };
-    return () => (
-      <div class={[styles.addClass]}>
-        <NForm label-placement="left" model={createClassForm} ref={foemsRef}>
-          <NFormItem
-            path="instrumentId"
-            rule={[
-              {
-                required: true,
-                message: '请选择乐器'
-              }
-            ]}>
-            <NCascader
-              placeholder="请选择乐器"
-              v-model:value={createClassForm.instrumentId}
-              options={subjectList.value}
-              checkStrategy="child"
-              showPath={false}
-              childrenField="instruments"
-              expandTrigger="hover"
-              labelField="name"
-              valueField="id"
-              clearable
-              filterable
-              style={{ width: '400px' }}
-            />
-          </NFormItem>
-        </NForm>
-        <NSpace class={styles.btnGroup} justify="center">
-          <NButton round onClick={() => emit('close')}>
-            取消
-          </NButton>
-          <NButton
-            round
-            loading={data.uploading}
-            onClick={() => submitForms()}
-            type="primary">
-            确定
-          </NButton>
-        </NSpace>
-      </div>
-    );
-  }
-});
+import {
+  NButton,
+  NSpace,
+  useMessage,
+  NForm,
+  NFormItem,
+  NCascader
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '../index.module.less';
+import CSelect from '/src/components/CSelect';
+import { getConfiguredSubjects, updateInstrument } from '../api';
+import { useCatchStore } from '/src/store/modules/catchData';
+export default defineComponent({
+  props: {
+    activeRow: {
+      type: Object,
+      default: () => ({ id: '' })
+    },
+    subjectList: {
+      type: Array,
+      default: () => []
+    }
+  },
+  name: 'resetStudent',
+  emits: ['close', 'getList', 'confirm'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
+    const data = reactive({
+      uploading: false
+    });
+    const message = useMessage();
+    const subjectList = ref([] as any);
+    const foemsRef = ref();
+    const createClassForm = reactive({
+      currentGradeNum: null,
+      currentClass: null,
+      gradeYear: null,
+      instrumentId: null,
+      id: null
+    });
+
+    const getConfigSubject = async () => {
+      try {
+        const { data } = await getConfiguredSubjects({
+          gradeYear: createClassForm.gradeYear,
+          currentGradeNum: createClassForm.currentGradeNum,
+          currentClass: createClassForm.currentClass
+        });
+        const temp = data || [];
+        subjectList.value = temp;
+      } catch {
+        //
+      }
+    };
+    onMounted(async () => {
+      // 获取教材分类列表
+      // await catchStore.getSubjects();
+      // subjectList.value = [
+      //   { id: null, name: '选择声部' },
+      //   ...catchStore.getSubjectList
+      // console.log(props.activeRow, ' props.activeRow');
+      // ];
+
+      createClassForm.gradeYear = props.activeRow.gradeYear;
+      createClassForm.currentGradeNum = props.activeRow.currentGradeNum;
+      createClassForm.currentClass = props.activeRow.currentClass;
+      createClassForm.instrumentId = props.activeRow.instrumentId;
+      createClassForm.id = props.activeRow.id;
+
+      getConfigSubject();
+    });
+    const submitForms = () => {
+      foemsRef.value.validate(async (error: any) => {
+        if (error) {
+          return;
+        }
+        data.uploading = true;
+        try {
+          await updateInstrument({ ...createClassForm });
+          message.success('修改成功');
+          emit('close');
+          emit('confirm', {
+            lastUseCoursewareId: props.activeRow.lessonCoursewareId,
+            unit: props.activeRow.lessonCoursewareKnowledgeDetailId,
+            instrumentId: createClassForm.instrumentId,
+            name: props.activeRow.name, // 班级名称
+            classGroupId: props.activeRow.id // 班级编号
+          });
+          emit('getList');
+        } catch (e) {
+          console.log(e);
+        }
+        data.uploading = false;
+      });
+    };
+    return () => (
+      <div class={[styles.addClass]}>
+        <NForm label-placement="left" model={createClassForm} ref={foemsRef}>
+          <NFormItem
+            path="instrumentId"
+            rule={[
+              {
+                required: true,
+                message: '请选择乐器'
+              }
+            ]}>
+            <NCascader
+              placeholder="请选择乐器"
+              v-model:value={createClassForm.instrumentId}
+              options={subjectList.value}
+              checkStrategy="child"
+              showPath={false}
+              childrenField="instruments"
+              expandTrigger="hover"
+              labelField="name"
+              valueField="id"
+              clearable
+              filterable
+              style={{ width: '400px' }}
+            />
+          </NFormItem>
+        </NForm>
+        <NSpace class={styles.btnGroup} justify="center">
+          <NButton round onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            round
+            loading={data.uploading}
+            onClick={() => submitForms()}
+            type="primary">
+            确定
+          </NButton>
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 43 - 43
src/views/content-information/api.ts

@@ -1,43 +1,43 @@
-import request from '@/utils/request';
-
-/**
- * 分类列表
- */
-export const api_knowledgeWikiCategoryType_page = (params: any) => {
-  return request.post('/edu-app/knowledgeWikiCategoryType/page', {
-    data: params
-  });
-};
-
-/**
- * 分页列表
- */
-export const api_knowledgeWiki_page = (params: any) => {
-  return request.post('/edu-app/knowledgeWiki/page', {
-    data: params
-  });
-};
-
-/**
- * 详情
- */
-export const api_knowledgeWiki_detail = (params: any) => {
-  return request.get('/edu-app/knowledgeWiki/detail/' + params.id, {});
-};
-
-/**
- * 知识详情
- */
-export const api_lessonCoursewareDetail_listKnowledge = (params: any) => {
-  return request.post('/edu-app/lessonCoursewareDetail/listKnowledge', {
-    data: params
-  });
-};
-/**
- * 知识详情
- */
-export const api_lessonCoursewareKnowledgeDetail = (params: any) => {
-  return request.get(
-    '/edu-app/lessonCoursewareKnowledgeDetail/detail/' + params.id
-  );
-};
+import request from '@/utils/request';
+
+/**
+ * 分类列表
+ */
+export const api_knowledgeWikiCategoryType_page = (params: any) => {
+  return request.post('/edu-app/knowledgeWikiCategoryType/page', {
+    data: params
+  });
+};
+
+/**
+ * 分页列表
+ */
+export const api_knowledgeWiki_page = (params: any) => {
+  return request.post('/edu-app/knowledgeWiki/page', {
+    data: params
+  });
+};
+
+/**
+ * 详情
+ */
+export const api_knowledgeWiki_detail = (params: any) => {
+  return request.get('/edu-app/knowledgeWiki/detail/' + params.id, {});
+};
+
+/**
+ * 知识详情
+ */
+export const api_lessonCoursewareDetail_listKnowledge = (params: any) => {
+  return request.post('/edu-app/lessonCoursewareDetail/listKnowledge', {
+    data: params
+  });
+};
+/**
+ * 知识详情
+ */
+export const api_lessonCoursewareKnowledgeDetail = (params: any) => {
+  return request.get(
+    '/edu-app/lessonCoursewareKnowledgeDetail/detail/' + params.id
+  );
+};

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