浏览代码

Merge branch 'master' of http://git.dayaedu.com/lex/classroom-instruments

liushengqiang 1 年之前
父节点
当前提交
9c7e8fb95e

+ 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.dgsa3bi7t4g"
-  }], {});
+  workbox.precacheAndRoute(
+    [
+      {
+        url: 'registerSW.js',
+        revision: '3ca0b8505b4bec776b69afdba2768812'
+      },
+      {
+        url: 'index.html',
+        revision: '0.dgsa3bi7t4g'
+      }
+    ],
+    {}
+  );
   workbox.cleanupOutdatedCaches();
-  workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
-    allowlist: [/^\/$/]
-  }));
-
-}));
+  workbox.registerRoute(
+    new workbox.NavigationRoute(workbox.createHandlerBoundToURL('index.html'), {
+      allowlist: [/^\/$/]
+    })
+  );
+});

+ 238 - 77
package-lock.json

@@ -11,8 +11,18 @@
       "dependencies": {
         "@tencentcloud/chat": "^3.1.1",
         "@tencentcloud/chat-uikit-vue": "^1.4.5",
+        "@tiptap/core": "^2.0.4",
+        "@tiptap/extension-document": "^2.0.4",
+        "@tiptap/extension-image": "^2.0.4",
+        "@tiptap/extension-mention": "^2.0.4",
+        "@tiptap/extension-paragraph": "^2.0.4",
+        "@tiptap/extension-placeholder": "^2.0.4",
+        "@tiptap/extension-text": "^2.0.4",
+        "@tiptap/suggestion": "^2.0.4",
+        "@tiptap/vue-3": "^2.0.4",
         "@vant/use": "^1.5.2",
         "@vicons/ionicons5": "^0.12.0",
+        "@vuepic/vue-datepicker": "^5.4.0",
         "@vueuse/core": "^10.2.0",
         "animate.css": "^4.1.1",
         "clean-deep": "^3.4.0",
@@ -42,6 +52,8 @@
         "vite-plugin-pwa": "^0.16.4",
         "vudio.js": "^1.0.3",
         "vue": "^3.3.4",
+        "vue-clipboard3": "^2.0.0",
+        "vue-i18n": "^9.2.2",
         "vue-qr": "^4.0.9",
         "vue-router": "^4.1.6",
         "vue3-lottie": "^2.7.0",
@@ -2270,60 +2282,60 @@
       "dev": true
     },
     "node_modules/@intlify/core-base": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.0-beta.32.tgz",
-      "integrity": "sha512-t9IB9Z65cx0fcpd9/oD8MrsMvGy1FOColOe5oodjo9E//Qm7P8kvSHnKymWjsAcJs0wR2Y6J9X8FRFmO6LNLNQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
       "dependencies": {
-        "@intlify/devtools-if": "9.2.0-beta.32",
-        "@intlify/message-compiler": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32",
-        "@intlify/vue-devtools": "9.2.0-beta.32"
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
       },
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/devtools-if": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.0-beta.32.tgz",
-      "integrity": "sha512-MvcxHrP7urM17wcna2vyqt8D8Pc5lyD3ps2BcmAr60UYifNKBQU7LmHfjTG7BZO4t0LyUWrWFJmm7fRVlju40g==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
       "dependencies": {
-        "@intlify/shared": "9.2.0-beta.32"
+        "@intlify/shared": "9.2.2"
       },
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/message-compiler": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.0-beta.32.tgz",
-      "integrity": "sha512-7OOHIhOmOM4nqe3KfEuE65xLwawwBGg/pCFByzayf0jARoHkeaA7v/eKsUJVJMEakeF2pNstx06AvfecijgPzg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
       "dependencies": {
-        "@intlify/shared": "9.2.0-beta.32",
+        "@intlify/shared": "9.2.2",
         "source-map": "0.6.1"
       },
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/shared": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.0-beta.32.tgz",
-      "integrity": "sha512-lVaHnKFNg16eWlfDLzDFLapurrf0WK7xLWEkz8DMYNIXJshRQOZCkH7sQaqtONPoOT0LqjyPo1+sV0Wq85/HRQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       }
     },
     "node_modules/@intlify/vue-devtools": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.32.tgz",
-      "integrity": "sha512-LZ+E8S+PBeKzV5zDrh/6kbPdEMu+oO6W7B9EmNhzBOKcF94oiJRTkFLymV6U2YefWu+JYEl2LGe7giXXt2jcwg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
       "dependencies": {
-        "@intlify/core-base": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
       },
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       }
     },
     "node_modules/@jridgewell/gen-mapping": {
@@ -2931,11 +2943,82 @@
         "vuex": "4.0.2"
       }
     },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@intlify/core-base": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.0-beta.32.tgz",
+      "integrity": "sha512-t9IB9Z65cx0fcpd9/oD8MrsMvGy1FOColOe5oodjo9E//Qm7P8kvSHnKymWjsAcJs0wR2Y6J9X8FRFmO6LNLNQ==",
+      "dependencies": {
+        "@intlify/devtools-if": "9.2.0-beta.32",
+        "@intlify/message-compiler": "9.2.0-beta.32",
+        "@intlify/shared": "9.2.0-beta.32",
+        "@intlify/vue-devtools": "9.2.0-beta.32"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@intlify/devtools-if": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.0-beta.32.tgz",
+      "integrity": "sha512-MvcxHrP7urM17wcna2vyqt8D8Pc5lyD3ps2BcmAr60UYifNKBQU7LmHfjTG7BZO4t0LyUWrWFJmm7fRVlju40g==",
+      "dependencies": {
+        "@intlify/shared": "9.2.0-beta.32"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@intlify/message-compiler": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.0-beta.32.tgz",
+      "integrity": "sha512-7OOHIhOmOM4nqe3KfEuE65xLwawwBGg/pCFByzayf0jARoHkeaA7v/eKsUJVJMEakeF2pNstx06AvfecijgPzg==",
+      "dependencies": {
+        "@intlify/shared": "9.2.0-beta.32",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@intlify/shared": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.0-beta.32.tgz",
+      "integrity": "sha512-lVaHnKFNg16eWlfDLzDFLapurrf0WK7xLWEkz8DMYNIXJshRQOZCkH7sQaqtONPoOT0LqjyPo1+sV0Wq85/HRQ==",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@intlify/vue-devtools": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.32.tgz",
+      "integrity": "sha512-LZ+E8S+PBeKzV5zDrh/6kbPdEMu+oO6W7B9EmNhzBOKcF94oiJRTkFLymV6U2YefWu+JYEl2LGe7giXXt2jcwg==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.0-beta.32",
+        "@intlify/shared": "9.2.0-beta.32"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@types/web-bluetooth": {
       "version": "0.0.14",
       "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
       "integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
     },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@vuepic/vue-datepicker": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-3.2.1.tgz",
+      "integrity": "sha512-+YP+Kvc9ZpMELftN4B243/ajgaIb6JVIR5CRoxZGIZX0cFe2tNhLYc8Cl40m12CHGlz44wbmQJGJuW9xtP05XA==",
+      "dependencies": {
+        "date-fns": "^2.28.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "vue": ">=3.2.0"
+      }
+    },
     "node_modules/@tencentcloud/chat-uikit-vue/node_modules/@vueuse/core": {
       "version": "8.9.4",
       "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-8.9.4.tgz",
@@ -2984,6 +3067,23 @@
         }
       }
     },
+    "node_modules/@tencentcloud/chat-uikit-vue/node_modules/vue-i18n": {
+      "version": "9.2.0-beta.32",
+      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.0-beta.32.tgz",
+      "integrity": "sha512-heUy1Aa/4DT2+ukoZkXMtDeU0o4pB5K6XxCypsmpoQ1QURAx1zyHqHB4mdjnLj3DDjllT921cV9cuKEWflQYGQ==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.0-beta.32",
+        "@intlify/shared": "9.2.0-beta.32",
+        "@intlify/vue-devtools": "9.2.0-beta.32",
+        "@vue/devtools-api": "^6.0.0-beta.13"
+      },
+      "engines": {
+        "node": ">= 12"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
     "node_modules/@tencentcloud/chat-uikit-vue/node_modules/vuex": {
       "version": "4.0.2",
       "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz",
@@ -3843,11 +3943,12 @@
       }
     },
     "node_modules/@vuepic/vue-datepicker": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-3.2.1.tgz",
-      "integrity": "sha512-+YP+Kvc9ZpMELftN4B243/ajgaIb6JVIR5CRoxZGIZX0cFe2tNhLYc8Cl40m12CHGlz44wbmQJGJuW9xtP05XA==",
+      "version": "5.4.0",
+      "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-5.4.0.tgz",
+      "integrity": "sha512-9f1ZqRDfak/UmBbD81BdqMDpUku2YphTwQXG8DF6hsrjIXsq5sX7BWJB6LhyVgvX9QFrSyFIp4fsHE3UFofZ7A==",
       "dependencies": {
-        "date-fns": "^2.28.0"
+        "date-fns": "^2.30.0",
+        "date-fns-tz": "^1.3.7"
       },
       "engines": {
         "node": ">=14"
@@ -4780,7 +4881,6 @@
       "version": "1.3.8",
       "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
       "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
-      "dev": true,
       "peerDependencies": {
         "date-fns": ">=2.0.0"
       }
@@ -11037,17 +11137,17 @@
       "dev": true
     },
     "node_modules/vue-i18n": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.0-beta.32.tgz",
-      "integrity": "sha512-heUy1Aa/4DT2+ukoZkXMtDeU0o4pB5K6XxCypsmpoQ1QURAx1zyHqHB4mdjnLj3DDjllT921cV9cuKEWflQYGQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
       "dependencies": {
-        "@intlify/core-base": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32",
-        "@intlify/vue-devtools": "9.2.0-beta.32",
-        "@vue/devtools-api": "^6.0.0-beta.13"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
       },
       "engines": {
-        "node": ">= 12"
+        "node": ">= 14"
       },
       "peerDependencies": {
         "vue": "^3.0.0"
@@ -13211,45 +13311,45 @@
       "dev": true
     },
     "@intlify/core-base": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.0-beta.32.tgz",
-      "integrity": "sha512-t9IB9Z65cx0fcpd9/oD8MrsMvGy1FOColOe5oodjo9E//Qm7P8kvSHnKymWjsAcJs0wR2Y6J9X8FRFmO6LNLNQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
       "requires": {
-        "@intlify/devtools-if": "9.2.0-beta.32",
-        "@intlify/message-compiler": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32",
-        "@intlify/vue-devtools": "9.2.0-beta.32"
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
       }
     },
     "@intlify/devtools-if": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.0-beta.32.tgz",
-      "integrity": "sha512-MvcxHrP7urM17wcna2vyqt8D8Pc5lyD3ps2BcmAr60UYifNKBQU7LmHfjTG7BZO4t0LyUWrWFJmm7fRVlju40g==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
       "requires": {
-        "@intlify/shared": "9.2.0-beta.32"
+        "@intlify/shared": "9.2.2"
       }
     },
     "@intlify/message-compiler": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.0-beta.32.tgz",
-      "integrity": "sha512-7OOHIhOmOM4nqe3KfEuE65xLwawwBGg/pCFByzayf0jARoHkeaA7v/eKsUJVJMEakeF2pNstx06AvfecijgPzg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
       "requires": {
-        "@intlify/shared": "9.2.0-beta.32",
+        "@intlify/shared": "9.2.2",
         "source-map": "0.6.1"
       }
     },
     "@intlify/shared": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.0-beta.32.tgz",
-      "integrity": "sha512-lVaHnKFNg16eWlfDLzDFLapurrf0WK7xLWEkz8DMYNIXJshRQOZCkH7sQaqtONPoOT0LqjyPo1+sV0Wq85/HRQ=="
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
     },
     "@intlify/vue-devtools": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.32.tgz",
-      "integrity": "sha512-LZ+E8S+PBeKzV5zDrh/6kbPdEMu+oO6W7B9EmNhzBOKcF94oiJRTkFLymV6U2YefWu+JYEl2LGe7giXXt2jcwg==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
       "requires": {
-        "@intlify/core-base": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
       }
     },
     "@jridgewell/gen-mapping": {
@@ -13730,11 +13830,61 @@
         "vuex": "4.0.2"
       },
       "dependencies": {
+        "@intlify/core-base": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.2.0-beta.32.tgz",
+          "integrity": "sha512-t9IB9Z65cx0fcpd9/oD8MrsMvGy1FOColOe5oodjo9E//Qm7P8kvSHnKymWjsAcJs0wR2Y6J9X8FRFmO6LNLNQ==",
+          "requires": {
+            "@intlify/devtools-if": "9.2.0-beta.32",
+            "@intlify/message-compiler": "9.2.0-beta.32",
+            "@intlify/shared": "9.2.0-beta.32",
+            "@intlify/vue-devtools": "9.2.0-beta.32"
+          }
+        },
+        "@intlify/devtools-if": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/@intlify/devtools-if/-/devtools-if-9.2.0-beta.32.tgz",
+          "integrity": "sha512-MvcxHrP7urM17wcna2vyqt8D8Pc5lyD3ps2BcmAr60UYifNKBQU7LmHfjTG7BZO4t0LyUWrWFJmm7fRVlju40g==",
+          "requires": {
+            "@intlify/shared": "9.2.0-beta.32"
+          }
+        },
+        "@intlify/message-compiler": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.2.0-beta.32.tgz",
+          "integrity": "sha512-7OOHIhOmOM4nqe3KfEuE65xLwawwBGg/pCFByzayf0jARoHkeaA7v/eKsUJVJMEakeF2pNstx06AvfecijgPzg==",
+          "requires": {
+            "@intlify/shared": "9.2.0-beta.32",
+            "source-map": "0.6.1"
+          }
+        },
+        "@intlify/shared": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.2.0-beta.32.tgz",
+          "integrity": "sha512-lVaHnKFNg16eWlfDLzDFLapurrf0WK7xLWEkz8DMYNIXJshRQOZCkH7sQaqtONPoOT0LqjyPo1+sV0Wq85/HRQ=="
+        },
+        "@intlify/vue-devtools": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/@intlify/vue-devtools/-/vue-devtools-9.2.0-beta.32.tgz",
+          "integrity": "sha512-LZ+E8S+PBeKzV5zDrh/6kbPdEMu+oO6W7B9EmNhzBOKcF94oiJRTkFLymV6U2YefWu+JYEl2LGe7giXXt2jcwg==",
+          "requires": {
+            "@intlify/core-base": "9.2.0-beta.32",
+            "@intlify/shared": "9.2.0-beta.32"
+          }
+        },
         "@types/web-bluetooth": {
           "version": "0.0.14",
           "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.14.tgz",
           "integrity": "sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A=="
         },
+        "@vuepic/vue-datepicker": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-3.2.1.tgz",
+          "integrity": "sha512-+YP+Kvc9ZpMELftN4B243/ajgaIb6JVIR5CRoxZGIZX0cFe2tNhLYc8Cl40m12CHGlz44wbmQJGJuW9xtP05XA==",
+          "requires": {
+            "date-fns": "^2.28.0"
+          }
+        },
         "@vueuse/core": {
           "version": "8.9.4",
           "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-8.9.4.tgz",
@@ -13759,6 +13909,17 @@
             "vue-demi": "*"
           }
         },
+        "vue-i18n": {
+          "version": "9.2.0-beta.32",
+          "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.0-beta.32.tgz",
+          "integrity": "sha512-heUy1Aa/4DT2+ukoZkXMtDeU0o4pB5K6XxCypsmpoQ1QURAx1zyHqHB4mdjnLj3DDjllT921cV9cuKEWflQYGQ==",
+          "requires": {
+            "@intlify/core-base": "9.2.0-beta.32",
+            "@intlify/shared": "9.2.0-beta.32",
+            "@intlify/vue-devtools": "9.2.0-beta.32",
+            "@vue/devtools-api": "^6.0.0-beta.13"
+          }
+        },
         "vuex": {
           "version": "4.0.2",
           "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz",
@@ -14441,11 +14602,12 @@
       }
     },
     "@vuepic/vue-datepicker": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-3.2.1.tgz",
-      "integrity": "sha512-+YP+Kvc9ZpMELftN4B243/ajgaIb6JVIR5CRoxZGIZX0cFe2tNhLYc8Cl40m12CHGlz44wbmQJGJuW9xtP05XA==",
+      "version": "5.4.0",
+      "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-5.4.0.tgz",
+      "integrity": "sha512-9f1ZqRDfak/UmBbD81BdqMDpUku2YphTwQXG8DF6hsrjIXsq5sX7BWJB6LhyVgvX9QFrSyFIp4fsHE3UFofZ7A==",
       "requires": {
-        "date-fns": "^2.28.0"
+        "date-fns": "^2.30.0",
+        "date-fns-tz": "^1.3.7"
       }
     },
     "@vueuse/core": {
@@ -15207,8 +15369,7 @@
     "date-fns-tz": {
       "version": "1.3.8",
       "resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
-      "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
-      "dev": true
+      "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ=="
     },
     "dayjs": {
       "version": "1.11.9",
@@ -20155,14 +20316,14 @@
       }
     },
     "vue-i18n": {
-      "version": "9.2.0-beta.32",
-      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.0-beta.32.tgz",
-      "integrity": "sha512-heUy1Aa/4DT2+ukoZkXMtDeU0o4pB5K6XxCypsmpoQ1QURAx1zyHqHB4mdjnLj3DDjllT921cV9cuKEWflQYGQ==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
       "requires": {
-        "@intlify/core-base": "9.2.0-beta.32",
-        "@intlify/shared": "9.2.0-beta.32",
-        "@intlify/vue-devtools": "9.2.0-beta.32",
-        "@vue/devtools-api": "^6.0.0-beta.13"
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
       }
     },
     "vue-qr": {

+ 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>
+    );
+  }
+});

二进制
src/components/timerMeter/images/add.png


二进制
src/components/timerMeter/images/minus.png


二进制
src/components/timerMeter/images/playing.png


二进制
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}
                 />