mo 1 год назад
Родитель
Сommit
80ba59fb90

+ 4 - 3
components.d.ts

@@ -5,11 +5,12 @@
 // Read more: https://github.com/vuejs/core/pull/3399
 import '@vue/runtime-core'
 
-export {}
+export {};
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
-    RouterLink: typeof import('vue-router')['RouterLink']
-    RouterView: typeof import('vue-router')['RouterView']
+    Flipper: typeof import('./src/components/timerMeter/modals/flipper.vue')['default'];
+    RouterLink: typeof import('vue-router')['RouterLink'];
+    RouterView: typeof import('vue-router')['RouterView'];
   }
 }

+ 43 - 34
dev-dist/sw.js

@@ -20,23 +20,21 @@ if (!self.define) {
   let nextDefineUri;
 
   const singleRequire = (uri, parentUri) => {
-    uri = new URL(uri + ".js", parentUri).href;
-    return registry[uri] || (
-      
-        new Promise(resolve => {
-          if ("document" in self) {
-            const script = document.createElement("script");
-            script.src = uri;
-            script.onload = resolve;
-            document.head.appendChild(script);
-          } else {
-            nextDefineUri = uri;
-            importScripts(uri);
-            resolve();
-          }
-        })
-      
-      .then(() => {
+    uri = new URL(uri + '.js', parentUri).href;
+    return (
+      registry[uri] ||
+      new Promise(resolve => {
+        if ('document' in self) {
+          const script = document.createElement('script');
+          script.src = uri;
+          script.onload = resolve;
+          document.head.appendChild(script);
+        } else {
+          nextDefineUri = uri;
+          importScripts(uri);
+          resolve();
+        }
+      }).then(() => {
         let promise = registry[uri];
         if (!promise) {
           throw new Error(`Module ${uri} didn’t register its module`);
@@ -47,7 +45,10 @@ if (!self.define) {
   };
 
   self.define = (depsNames, factory) => {
-    const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
+    const uri =
+      nextDefineUri ||
+      ('document' in self ? document.currentScript.src : '') ||
+      location.href;
     if (registry[uri]) {
       // Module is already loading or loaded.
       return;
@@ -59,15 +60,16 @@ if (!self.define) {
       exports,
       require
     };
-    registry[uri] = Promise.all(depsNames.map(
-      depName => specialDeps[depName] || require(depName)
-    )).then(deps => {
+    registry[uri] = Promise.all(
+      depsNames.map(depName => specialDeps[depName] || require(depName))
+    ).then(deps => {
       factory(...deps);
       return exports;
     });
   };
 }
-define(['./workbox-ab7aa862'], (function (workbox) { 'use strict';
+define(['./workbox-ab7aa862'], function (workbox) {
+  'use strict';
 
   self.skipWaiting();
   workbox.clientsClaim();
@@ -77,16 +79,23 @@ define(['./workbox-ab7aa862'], (function (workbox) { 'use strict';
    * requests for URLs in the manifest.
    * See https://goo.gl/S9QRab
    */
-  workbox.precacheAndRoute([{
-    "url": "registerSW.js",
-    "revision": "3ca0b8505b4bec776b69afdba2768812"
-  }, {
-    "url": "index.html",
-    "revision": "0.6daji1r5o5g"
-  }], {});
+  workbox.precacheAndRoute(
+    [
+      {
+        url: 'registerSW.js',
+        revision: '3ca0b8505b4bec776b69afdba2768812'
+      },
+      {
+        url: 'index.html',
+        revision: '0.01fpdbnlohg'
+      }
+    ],
+    {}
+  );
   workbox.cleanupOutdatedCaches();
-  workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
-    allowlist: [/^\/$/]
-  }));
-
-}));
+  workbox.registerRoute(
+    new workbox.NavigationRoute(workbox.createHandlerBoundToURL('index.html'), {
+      allowlist: [/^\/$/]
+    })
+  );
+});

+ 8 - 4
src/components/TheQrCode/index.tsx

@@ -174,8 +174,9 @@ export default defineComponent({
       this.render(bgImg, logoImg);
     },
     async render(img: any, logoImg: any, gifBgSrc?: any) {
-      console.log(img, logoImg, gifBgSrc);
-      new AwesomeQR({
+      console.log(img, logoImg, gifBgSrc, 'render====>code');
+
+      const obj = {
         gifBackground: gifBgSrc,
         text: this.text,
         size: this.size,
@@ -185,7 +186,6 @@ export default defineComponent({
         backgroundColor: this.backgroundColor,
         backgroundImage: img,
         backgroundDimming: this.backgroundDimming,
-        logoImage: logoImg ? logoImg + '?' + new Date().getTime() : undefined,
         logoScale: this.logoScale,
         logoBackgroundColor: this.logoBackgroundColor,
         correctLevel: this.correctLevel,
@@ -195,7 +195,11 @@ export default defineComponent({
         dotScale: this.dotScale,
         autoColor: toBoolean(this.autoColor),
         components: this.components
-      })
+      } as any
+      if (logoImg) {
+        obj.logoImg = logoImg + '?' + new Date().getTime()
+      }
+      new AwesomeQR(obj)
         .draw()
         .then((dataUri: any) => {
           console.log('🚀 ~ dataUri:', dataUri);

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

@@ -416,4 +416,9 @@
 
 .imChatModal {
   border-radius: 20px;
+}
+
+.modeWrap {
+  overflow: hidden;
+  border-radius: 16px;
 }

+ 13 - 16
src/components/layout/index.tsx

@@ -12,6 +12,7 @@ import beatImage from './images/beatImage.png';
 import toneImage from './images/toneImage.png';
 import setTimeImage from './images/setTimeImage.png';
 import dragingBoxIcon from './images/dragingBoxIcon.png';
+import TimerMeter from '../timerMeter';
 import { useRoute } from 'vue-router';
 export default defineComponent({
   name: 'layoutView',
@@ -214,15 +215,15 @@ export default defineComponent({
           </div>
         </NPopover>
 
-        <NModal v-model:show={showModalBeat.value}>
+        <NModal
+         class={['modalTitle background']}
+          title={'节拍器'}
+          preset="card"
+          v-model:show={showModalBeat.value}  style={{ width: '687px' }}>
           <div
-            onClick={() => {
-              showModalBeat.value = false;
-            }}>
-            <NImage
-              src={beatImage}
-              previewDisabled
-              class={styles.beatImage}></NImage>
+            class={styles.modeWrap}
+           >
+              <iframe src="https://test.lexiaoya.cn/metronome/"  scrolling='no'  frameborder="0" width='100%'  height={'650px'} ></iframe>
           </div>
         </NModal>
         <NModal v-model:show={showModalTone.value}>
@@ -236,15 +237,11 @@ export default defineComponent({
               class={styles.beatImage}></NImage>
           </div>
         </NModal>
-        <NModal v-model:show={showModalTime.value}>
+        <NModal v-model:show={showModalTime.value}   class={['modalTitle background']}
+          title={'计时器'}  preset="card" style={{ width: '772px' }}>
           <div
-            onClick={() => {
-              showModalTime.value = false;
-            }}>
-            <NImage
-              src={setTimeImage}
-              previewDisabled
-              class={styles.setTimeImage}></NImage>
+           >
+          <TimerMeter></TimerMeter>
           </div>
         </NModal>
       </div>

+ 286 - 0
src/components/timerMeter/components/countdown.tsx

@@ -0,0 +1,286 @@
+import { defineComponent, ref, watch, nextTick, onMounted,computed } 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'
+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'
+
+      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()
+      nextTick(() => {
+        count.value = 0
+        init()
+      })
+
+    }
+    onMounted(() => {
+      nextTick(() => {
+        init()
+        // run()
+      })
+
+    })
+   const addSecondTimer = (num:number)=>{
+
+    nextTick(()=>{
+      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)=>{
+    nextTick(()=>{
+      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 = ()=>{
+    nextTick(()=>{
+      console.log(mine.value, count.value)
+      const lastStr = getSecond(count.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=()=>{
+    nextTick(()=>{
+      console.log(mine.value)
+      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,(lastVal:number,val:number)=>{
+  //   console.log('lastVal',lastVal,'val',val)
+  //   mine.value = Math.floor(val / 60)
+  //   second.value = Math.floor(val % 60)
+  //   const lastStr = getSecond(lastVal)
+  //   const str = getSecond(val)
+  //   for (let i = 0; i < flipObjs.value.length; i++) {
+  //     if (lastStr[i] === str[i]) {
+  //       continue
+  //     }
+  //     flipObjs.value[i].value.flipUp(lastStr[i], str[i])
+  //   }
+  // })
+    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}>
+                  <Flipper ref={flipperMinute1} />
+                  <Flipper ref={flipperMinute2} />
+                  <div  class={styles.chioseWrap}>
+                    <img src={add} class={styles.add} alt="" onClick={()=>addSecondTimer(60)}/>
+                    <NInputNumber class={styles.countInput} min={0} max={59} show-button={false} onUpdate:value={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}>
+                  <Flipper ref={flipperSecond1} />
+                  <Flipper ref={flipperSecond2} />
+                  <div  class={styles.chioseWrap}>
+                    <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}  onUpdate:value={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: () => <>暂停</>,
+            icon: () => <NImage previewDisabled class={styles.palyIcon} src={suspend}></NImage>
+          }}>
+
+          </NButton> : <NButton round type="primary" icon-placement="right" onClick={() => startTimer()} v-slots={{
+            default: () => <>开始</>,
+            icon: () => <NImage previewDisabled class={styles.palyIcon} src={playIcon}></NImage>
+          }}>
+          </NButton>}
+
+        </NSpace>
+      </div>
+    );
+  }
+});

+ 195 - 0
src/components/timerMeter/components/positive.tsx

@@ -0,0 +1,195 @@
+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()
+      nextTick(() => {
+        count.value = 0
+        init()
+      })
+
+    }
+    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}>
+                  <Flipper ref={flipperMinute1} />
+                  <Flipper ref={flipperMinute2} />
+                </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}>
+                  <Flipper ref={flipperSecond1} />
+                  <Flipper ref={flipperSecond2} />
+                </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: () => <>暂停</>,
+            icon: () => <NImage previewDisabled class={styles.palyIcon} src={suspend}></NImage>
+          }}>
+
+          </NButton> : <NButton round type="primary" icon-placement="right" onClick={() => startTimer()} v-slots={{
+            default: () => <>开始</>,
+            icon: () => <NImage previewDisabled class={styles.palyIcon} src={playIcon}></NImage>
+          }}>
+          </NButton>}
+
+        </NSpace>
+      </div>
+    );
+  }
+});

BIN
src/components/timerMeter/images/add.png


BIN
src/components/timerMeter/images/minus.png


BIN
src/components/timerMeter/images/playing.png


BIN
src/components/timerMeter/images/suspend.png


+ 182 - 0
src/components/timerMeter/index.module.less

@@ -0,0 +1,182 @@
+.timerWrap {
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .timerTop {
+    width: 276px;
+    height: 41px;
+    background: #F5F6FA;
+    border-radius: 8px;
+    margin-top: 24px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 24px;
+
+    .timerTopPane {
+      width: 50%;
+      text-align: center;
+      line-height: 41px;
+      cursor: pointer;
+
+    }
+
+    .timerTopPaneActive {
+      background: #198CFE;
+      border-radius: 8px;
+      color: #fff;
+    }
+  }
+}
+
+
+// 计时器
+.timerItemInfo {
+  width: 692px;
+
+  background: #A6D1FF;
+  box-shadow: 0px 9px 0px 0px #CBD6DF;
+  border-radius: 46px;
+  border: 13px solid #EEF7FF;
+  // margin-bottom: 40px;
+  padding: 10px;
+
+  .timerItemInset {
+    border-radius: 28px 28px 38px 38px;
+    background: #D8ECFE;
+  }
+
+  .timerItemInfoTop {
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+
+    .dot {
+      width: 10px;
+      height: 10px;
+      background: #131415;
+      border-radius: 28px;
+      margin-bottom: 15px;
+      margin: 7px 70px;
+    }
+
+    .dotTop {
+
+      width: 10px;
+      height: 70px;
+    }
+
+    .timerItemTopCore {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+
+      h4 {
+        font-size: 22px;
+        font-weight: 600;
+        color: #7CAEE1;
+        line-height: 70px;
+      }
+    }
+
+
+  }
+}
+
+.nowTimerWrap {
+  margin-top: 20px;
+  width: 100%;
+  text-align: center;
+  font-size: 24px;
+
+  font-weight: 400;
+  color: rgba(19, 20, 21, .5);
+  line-height: 33px;
+  padding-bottom: 20px;
+}
+
+.btnGroupModal {
+  padding: 40px 0 32px;
+
+  :global {
+    .n-button {
+      height: 47px;
+      min-width: 156px;
+    }
+  }
+}
+
+.palyIcon {
+  width: 11px;
+  height: 14px;
+}
+
+.countInput {
+  // width: 66px;
+  // height: 33px;
+  // background: #F5F6FA;
+  // border-radius: 17px;
+  margin: 0 16px;
+
+  :global {
+    .n-input__border {
+      display: none;
+    }
+
+    .n-input__state-border {
+      display: none;
+    }
+
+    .n-input {
+      width: 66px;
+      height: 33px;
+      border-radius: 17px;
+      overflow: hidden;
+
+      .n-input-wrapper {
+        text-align: center;
+        background: #F5F6FA;
+        padding: 0;
+      }
+    }
+  }
+}
+
+.dotBtm {
+  width: 100%;
+
+  height: 16px;
+}
+
+.chioseWrap {
+  margin: 14px 0 26px;
+  width: 174px;
+  line-height: 45px;
+  height: 45px;
+  background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 100%);
+  box-shadow: 2px 2px 0px 0px #A2CAEE;
+  border-radius: 23px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+
+  .add {
+    width: 16px;
+    height: 16px;
+    cursor: pointer;
+  }
+
+
+
+  .minus {
+    width: 16px;
+    height: 4px;
+    cursor: pointer;
+  }
+}

+ 29 - 0
src/components/timerMeter/index.tsx

@@ -0,0 +1,29 @@
+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('countdown');  //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>
+    );
+  }
+});

+ 291 - 0
src/components/timerMeter/modals/flipper.vue

@@ -0,0 +1,291 @@
+
+<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
+      }
+      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: 90px;
+  height: 178px;
+  line-height: 178px;
+  /* border: solid 1px #000; */
+  border-radius: 10px;
+  background: #fff;
+  font-size: 128px;
+  color: #131415;
+  box-shadow: 4px 4px 0px 0px #A2CAEE;
+  text-align: center;
+  font-family: 'DINA';
+}
+
+/* @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(160px) 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 -2px 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(160px) 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(160px) rotateX(0deg);
+  }
+
+  100% {
+    transform: perspective(160px) rotateX(-180deg);
+  }
+}
+
+@keyframes backFlipDown {
+  0% {
+    transform: perspective(160px) rotateX(180deg);
+  }
+
+  100% {
+    transform: perspective(160px) rotateX(0deg);
+  }
+}
+
+@keyframes frontFlipUp {
+  0% {
+    transform: perspective(160px) rotateX(0deg);
+  }
+
+  100% {
+    transform: perspective(160px) rotateX(180deg);
+  }
+}
+
+@keyframes backFlipUp {
+  0% {
+    transform: perspective(160px) rotateX(-180deg);
+  }
+
+  100% {
+    transform: perspective(160px) 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>

+ 1 - 1
src/styles/index.less

@@ -304,4 +304,4 @@ body {
 
 .no-move {
   transition: transform 0s;
-}
+}

+ 12 - 0
src/utils/index.ts

@@ -288,6 +288,18 @@ export const getSecondRPM = (second: number, type?: string) => {
   }
 };
 
+// 秒转分
+export const getSecond = (second: number, type?: string) => {
+  if (isNaN(second)) return '0000';
+  const mm = Math.floor(second / 60)
+    .toString()
+    .padStart(2, '0');
+  const dd = Math.floor(second % 60)
+    .toString()
+    .padStart(2, '0');
+  return `${mm}${dd}`
+};
+
 /** 滚动到表单填写错误的地方 */
 export function scrollToErrorForm() {
   const isError =

+ 30 - 3
src/views/home/index.tsx

@@ -47,6 +47,7 @@ import { classGroupList, courseSchedulePage } from './api';
 import TheEmpty from '/src/components/TheEmpty';
 import { setTabsCaches } from '/src/hooks/use-async';
 import HomeGuide from '/src/custom-plugins/guide-page/home-guide';
+import TimerMeter from '/src/components/timerMeter';
 export const formatDateToDay = () => {
   const hours = dayjs().hour();
   if (hours < 12) {
@@ -65,6 +66,9 @@ export default defineComponent({
     const message = useMessage();
     const router = useRouter();
     const userStore = useUserStore();
+    const showModalBeat = ref(false);
+    const showModalTone = ref(false);
+    const showModalTime = ref(false);
     const forms = reactive({
       applyClassItem: {} as any, // 选择的内容
       applyStatus: false,
@@ -531,8 +535,8 @@ export default defineComponent({
               </div>
             </div>
             <img src={iconTo} class={styles.iconTo} />
-            <div class={styles.toolFunction} id="home-3">
-              <div class={[styles.toolItem, styles.item1]}>
+            <div class={styles.toolFunction} id="home-3" >
+              <div class={[styles.toolItem, styles.item1]} onClick={()=>{showModalBeat.value = true}}>
                 <img src={t1} />
                 <p class={styles.toolMemo}>提升效率,练习好节奏</p>
                 <NButton class={styles.btn1}>节拍器</NButton>
@@ -542,7 +546,7 @@ export default defineComponent({
                 <p class={styles.toolMemo}>精准调音,一劳永逸</p>
                 <NButton class={styles.btn2}>调音器</NButton>
               </div>
-              <div class={[styles.toolItem, styles.item3]}>
+              <div class={[styles.toolItem, styles.item3]} onClick={()=>{showModalTime.value = true}}>
                 <img src={t3} />
                 <p class={styles.toolMemo}>创造时间,集中注意力</p>
                 <NButton class={styles.btn3}>计时器</NButton>
@@ -688,8 +692,31 @@ export default defineComponent({
             onClose={() => (forms.useStatus = false)}
           />
         </NModal>
+
+        <NModal
+         class={['modalTitle background']}
+          title={'节拍器'}
+          preset="card"
+          v-model:show={showModalBeat.value}  style={{ width: '687px' }}>
+          <div
+            class={styles.modeWrap}
+           >
+              <iframe src="https://test.lexiaoya.cn/metronome/"  scrolling='no'  frameborder="0" width='100%'  height={'650px'} ></iframe>
+          </div>
+        </NModal>
+
+        <NModal v-model:show={showModalTime.value}   class={['modalTitle background']}
+          title={'计时器'}  preset="card" style={{ width: '772px' }}>
+          <div
+           >
+          <TimerMeter></TimerMeter>
+          </div>
+        </NModal>
+
         {forms.showGuide ? <HomeGuide></HomeGuide> : null}
       </div>
+
+
     );
   }
 });

+ 3 - 0
src/views/setting/index.module.less

@@ -164,6 +164,7 @@
         &.n-input--disabled {
           background-color: #f5f6fa;
           color: rgba(149, 149, 152, 1);
+
           .n-input__input-el {
             background-color: #F5F6FA;
             color: rgba(0, 0, 0, 0.4);
@@ -243,6 +244,7 @@
       }
     }
   }
+
   .sendMsg {
     min-width: 108Px;
     height: 50Px;
@@ -253,6 +255,7 @@
     height: 24Px;
     cursor: pointer;
   }
+
   .submitBtm {
     width: 45%;
     height: 46Px;

+ 10 - 2
src/views/setting/modal/addteacherModel.tsx

@@ -50,9 +50,13 @@ export default defineComponent({
       const queryStr = `tenantId=${userStore.info.schoolInfos?.[0]?.tenantId}&schoolId=${userStore.info.schoolInfos?.[0]?.id}&schoolName=${userStore.info.schoolInfos?.[0]?.name}`;
       const url =
         `${location.origin}/classroom-app/#/teaher-register?` + queryStr;
-      console.log(url);
+
       return url;
     };
+
+    const queryStr = `tenantId=${userStore.info.schoolInfos?.[0]?.tenantId}&schoolId=${userStore.info.schoolInfos?.[0]?.id}&schoolName=${userStore.info.schoolInfos?.[0]?.name}`;
+    const url =
+      `${location.origin}/classroom-app/#/teaher-register?` + queryStr;
     const imgs = reactive({
       saveLoading: false,
       image: null as any,
@@ -68,6 +72,7 @@ export default defineComponent({
         saveImg();
       } else {
         const container: any = document.getElementById(`preview-container`);
+
         html2canvas(container, {
           allowTaint: true,
           useCORS: true,
@@ -75,10 +80,13 @@ export default defineComponent({
         })
           .then(async canvas => {
             const url = canvas.toDataURL('image/png');
+            console.log(url, 'url===>')
             imgs.image = url;
+
             saveImg();
           })
           .catch(() => {
+            console.log('生成图片失败url===>')
             imgs.saveLoading = false;
           });
       }
@@ -152,7 +160,7 @@ export default defineComponent({
               </p>
               <div class={styles.codewrap}>
                 <img src={codewrap} class={styles.codewrapBg} alt="" />
-                <TheQrCode margin={0} text={registerUrl()} size={119} />
+                <TheQrCode margin={0} text={url} size={119} />
               </div>
               <div class={styles.codewrapSubmit}>
                 <NImage previewDisabled src={btnBg}></NImage>

+ 1 - 2
src/views/studentList/modals/addStudentModel.tsx

@@ -64,6 +64,7 @@ export default defineComponent({
         saveImg();
       } else {
         const container: any = document.getElementById(`preview-container`);
+        console.log(container, 'container=====>')
         html2canvas(container, {
           allowTaint: true,
           useCORS: true,
@@ -149,8 +150,6 @@ export default defineComponent({
                 <img src={codewrap} class={styles.codewrapBg} alt="" />
                 <TheQrCode
                   margin={0}
-                  // logoSrc={logo}
-                  class={styles.codewrapImg}
                   text={url.value}
                   size={119}
                 />