Browse Source

首次提交

zhengnaiwen_citu 4 months ago
parent
commit
7e2f548973
46 changed files with 1374 additions and 142 deletions
  1. 7 0
      .env.development
  2. 10 0
      .env.production
  3. 13 3
      package.json
  4. 280 19
      pnpm-lock.yaml
  5. 9 2
      public/index.html
  6. BIN
      public/models/age_gender_model-shard1
  7. 0 0
      public/models/age_gender_model-weights_manifest.json
  8. BIN
      public/models/face_expression_model-shard1
  9. 0 0
      public/models/face_expression_model-weights_manifest.json
  10. BIN
      public/models/face_landmark_68_model-shard1
  11. 0 0
      public/models/face_landmark_68_model-weights_manifest.json
  12. BIN
      public/models/face_landmark_68_tiny_model-shard1
  13. 0 0
      public/models/face_landmark_68_tiny_model-weights_manifest.json
  14. BIN
      public/models/face_recognition_model-shard1
  15. 0 0
      public/models/face_recognition_model-shard2
  16. 0 0
      public/models/face_recognition_model-weights_manifest.json
  17. BIN
      public/models/mtcnn_model-shard1
  18. 0 0
      public/models/mtcnn_model-weights_manifest.json
  19. BIN
      public/models/ssd_mobilenetv1_model-shard1
  20. 0 0
      public/models/ssd_mobilenetv1_model-shard2
  21. 0 0
      public/models/ssd_mobilenetv1_model-weights_manifest.json
  22. BIN
      public/models/tiny_face_detector_model-shard1
  23. 0 0
      public/models/tiny_face_detector_model-weights_manifest.json
  24. 18 23
      src/App.vue
  25. 5 0
      src/api/glass.js
  26. BIN
      src/assets/face/oval.jpg
  27. BIN
      src/assets/face/square.jpg
  28. BIN
      src/assets/face/圆形脸.jpg
  29. BIN
      src/assets/face/心形脸.jpg
  30. BIN
      src/assets/face/梨形脸.jpg
  31. BIN
      src/assets/face/钻石脸.jpg
  32. BIN
      src/assets/face/长形脸.jpg
  33. BIN
      src/assets/logo.png
  34. 1 0
      src/assets/logo.svg
  35. 0 60
      src/components/HelloWorld.vue
  36. 7 1
      src/main.js
  37. 23 0
      src/plugins/vuetify.js
  38. 1 10
      src/router/index.js
  39. 119 0
      src/utils/request.js
  40. 0 5
      src/views/AboutView.vue
  41. 112 0
      src/views/Home.vue
  42. 0 18
      src/views/HomeView.vue
  43. 430 0
      src/views/components/MCamera.vue
  44. 239 0
      src/views/components/MCover.vue
  45. 77 0
      src/views/components/MFeature.vue
  46. 23 1
      vue.config.js

+ 7 - 0
.env.development

@@ -0,0 +1,7 @@
+# just a flag
+NODE_ENV = 'development'
+
+VUE_APP_MODE = 'development'
+
+VUE_APP_BASE_API = 'http://192.168.3.58:5500'
+

+ 10 - 0
.env.production

@@ -0,0 +1,10 @@
+# just a flag
+NODE_ENV = 'production'
+
+
+VUE_APP_MODE = 'production'
+# base api
+
+VUE_APP_BASE_API = 'http://115.128.14.14/op/base'
+
+

+ 13 - 3
package.json

@@ -3,14 +3,20 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
+    "dev": "vue-cli-service serve --mode development",
+    "build": "vue-cli-service build --mode production",
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@mdi/font": "5.9.55",
+    "axios": "^1.7.9",
     "core-js": "^3.8.3",
+    "qs": "^6.14.0",
+    "roboto-fontface": "*",
     "vue": "^2.6.14",
+    "vue-js-modal": "^2.0.1",
     "vue-router": "^3.5.1",
+    "vuetify": "^2.6.0",
     "vuex": "^3.6.2"
   },
   "devDependencies": {
@@ -29,6 +35,10 @@
     "eslint-plugin-vue": "^8.0.3",
     "less": "^4.0.0",
     "less-loader": "^8.0.0",
-    "vue-template-compiler": "^2.6.14"
+    "sass": "~1.32.0",
+    "sass-loader": "^10.0.0",
+    "vue-cli-plugin-vuetify": "~2.5.8",
+    "vue-template-compiler": "^2.6.14",
+    "vuetify-loader": "^1.7.0"
   }
 }

+ 280 - 19
pnpm-lock.yaml

@@ -8,15 +8,33 @@ importers:
 
   .:
     dependencies:
+      '@mdi/font':
+        specifier: 5.9.55
+        version: 5.9.55
+      axios:
+        specifier: ^1.7.9
+        version: 1.7.9
       core-js:
         specifier: ^3.8.3
         version: 3.39.0
+      qs:
+        specifier: ^6.14.0
+        version: 6.14.0
+      roboto-fontface:
+        specifier: '*'
+        version: 0.10.0
       vue:
         specifier: ^2.6.14
         version: 2.7.16
+      vue-js-modal:
+        specifier: ^2.0.1
+        version: 2.0.1(vue@2.7.16)
       vue-router:
         specifier: ^3.5.1
         version: 3.6.5(vue@2.7.16)
+      vuetify:
+        specifier: ^2.6.0
+        version: 2.7.2(vue@2.7.16)
       vuex:
         specifier: ^3.6.2
         version: 3.6.2(vue@2.7.16)
@@ -29,22 +47,22 @@ importers:
         version: 7.25.9(@babel/core@7.26.0)(eslint@7.32.0)
       '@vue/cli-plugin-babel':
         specifier: ~5.0.0
-        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(core-js@3.39.0)(vue@2.7.16)
+        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(core-js@3.39.0)(vue@2.7.16)
       '@vue/cli-plugin-eslint':
         specifier: ~5.0.0
-        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint@7.32.0)
+        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint@7.32.0)
       '@vue/cli-plugin-router':
         specifier: ~5.0.0
-        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
+        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
       '@vue/cli-plugin-vuex':
         specifier: ~5.0.0
-        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
+        version: 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
       '@vue/cli-service':
         specifier: ~5.0.0
-        version: 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+        version: 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
       '@vue/eslint-config-standard':
         specifier: ^6.1.0
-        version: 6.1.0(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint-plugin-import@2.31.0(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(webpack@5.97.1)
+        version: 6.1.0(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint-plugin-import@2.31.0(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(webpack@5.97.1)
       eslint:
         specifier: ^7.32.0
         version: 7.32.0
@@ -66,9 +84,21 @@ importers:
       less-loader:
         specifier: ^8.0.0
         version: 8.1.1(less@4.2.1)(webpack@5.97.1)
+      sass:
+        specifier: ~1.32.0
+        version: 1.32.13
+      sass-loader:
+        specifier: ^10.0.0
+        version: 10.5.2(sass@1.32.13)(webpack@5.97.1)
+      vue-cli-plugin-vuetify:
+        specifier: ~2.5.8
+        version: 2.5.8(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue@2.7.16)(vuetify-loader@1.9.2(vue@2.7.16)(vuetify@2.7.2(vue@2.7.16))(webpack@5.97.1))(webpack@5.97.1)
       vue-template-compiler:
         specifier: ^2.6.14
         version: 2.7.16
+      vuetify-loader:
+        specifier: ^1.7.0
+        version: 1.9.2(vue@2.7.16)(vuetify@2.7.2(vue@2.7.16))(webpack@5.97.1)
 
 packages:
 
@@ -662,6 +692,9 @@ packages:
   '@leichtgewicht/ip-codec@2.0.5':
     resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
 
+  '@mdi/font@5.9.55':
+    resolution: {integrity: sha512-jswRF6q3eq8NWpWiqct6q+6Fg/I7nUhrxYJfiEM8JJpap0wVJLQdbKtyS65GdlK7S7Ytnx3TTi/bmw+tBhkGmg==}
+
   '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
     resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
 
@@ -1151,6 +1184,9 @@ packages:
   async@2.6.4:
     resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
 
+  asynckit@0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
   at-least-node@1.0.0:
     resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
     engines: {node: '>= 4.0.0'}
@@ -1166,6 +1202,9 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
+  axios@1.7.9:
+    resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
+
   babel-loader@8.4.1:
     resolution: {integrity: sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==}
     engines: {node: '>= 8.9'}
@@ -1257,6 +1296,9 @@ packages:
     resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
     engines: {node: '>= 0.4'}
 
+  callsite@1.0.0:
+    resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==}
+
   callsites@3.1.0:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
@@ -1356,6 +1398,10 @@ packages:
   colorette@2.0.20:
     resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
 
+  combined-stream@1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+
   commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
@@ -1723,6 +1769,9 @@ packages:
       supports-color:
         optional: true
 
+  decache@4.6.2:
+    resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==}
+
   deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
 
@@ -1749,6 +1798,10 @@ packages:
     resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
     engines: {node: '>= 0.4'}
 
+  delayed-stream@1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+
   depd@1.1.2:
     resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
     engines: {node: '>= 0.6'}
@@ -2129,6 +2182,12 @@ packages:
     resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
     engines: {node: ^10.12.0 || >=12.0.0}
 
+  file-loader@6.2.0:
+    resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      webpack: ^4.0.0 || ^5.0.0
+
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
@@ -2171,6 +2230,10 @@ packages:
   for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
 
+  form-data@4.0.1:
+    resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+    engines: {node: '>= 6'}
+
   forwarded@0.2.0:
     resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
     engines: {node: '>= 0.6'}
@@ -2992,6 +3055,12 @@ packages:
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
+  null-loader@4.0.1:
+    resolution: {integrity: sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      webpack: ^4.0.0 || ^5.0.0
+
   object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
@@ -3405,6 +3474,9 @@ packages:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
 
+  proxy-from-env@1.1.0:
+    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
   prr@1.0.1:
     resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
 
@@ -3422,6 +3494,10 @@ packages:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
 
+  qs@6.14.0:
+    resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
+    engines: {node: '>=0.6'}
+
   queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
 
@@ -3455,6 +3531,10 @@ packages:
     resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
     engines: {node: '>=8.10.0'}
 
+  rechoir@0.6.2:
+    resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
+    engines: {node: '>= 0.10'}
+
   reflect.getprototypeof@1.0.9:
     resolution: {integrity: sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==}
     engines: {node: '>= 0.4'}
@@ -3509,6 +3589,9 @@ packages:
   requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
 
+  resize-observer-polyfill@1.5.1:
+    resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -3543,6 +3626,9 @@ packages:
     deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
+  roboto-fontface@0.10.0:
+    resolution: {integrity: sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==}
+
   run-parallel@1.2.0:
     resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
 
@@ -3567,6 +3653,27 @@ packages:
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
 
+  sass-loader@10.5.2:
+    resolution: {integrity: sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      fibers: '>= 3.1.0'
+      node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+      sass: ^1.3.0
+      webpack: ^4.36.0 || ^5.0.0
+    peerDependenciesMeta:
+      fibers:
+        optional: true
+      node-sass:
+        optional: true
+      sass:
+        optional: true
+
+  sass@1.32.13:
+    resolution: {integrity: sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==}
+    engines: {node: '>=8.9.0'}
+    hasBin: true
+
   sax@1.4.1:
     resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
 
@@ -3655,6 +3762,11 @@ packages:
     resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
     engines: {node: '>= 0.4'}
 
+  shelljs@0.8.5:
+    resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
+    engines: {node: '>=4'}
+    hasBin: true
+
   side-channel-list@1.0.0:
     resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
     engines: {node: '>= 0.4'}
@@ -3998,6 +4110,19 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
+  vue-cli-plugin-vuetify@2.5.8:
+    resolution: {integrity: sha512-uqi0/URJETJBbWlQHD1l0pnY7JN8Ytu+AL1fw50HFlGByPa8/xx+mq19GkFXA9FcwFT01IqEc/TkxMPugchomg==}
+    peerDependencies:
+      sass-loader: '*'
+      vue: '*'
+      vuetify-loader: '*'
+      webpack: ^4.0.0 || ^5.0.0
+    peerDependenciesMeta:
+      sass-loader:
+        optional: true
+      vuetify-loader:
+        optional: true
+
   vue-eslint-parser@8.3.0:
     resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -4007,6 +4132,11 @@ packages:
   vue-hot-reload-api@2.3.4:
     resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==}
 
+  vue-js-modal@2.0.1:
+    resolution: {integrity: sha512-5FUwsH2zoxRKX4a7wkFAqX0eITCcIMunJDEfIxzHs2bHw9o20+Iqm+uQvBcg1jkzyo1+tVgThR/7NGU8djbD8Q==}
+    peerDependencies:
+      vue: ^2.6.11
+
   vue-loader@15.11.1:
     resolution: {integrity: sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==}
     peerDependencies:
@@ -4056,6 +4186,28 @@ packages:
     resolution: {integrity: sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==}
     deprecated: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.
 
+  vuetify-loader@1.9.2:
+    resolution: {integrity: sha512-8PP2w7aAs/rjA+Izec6qY7sHVb75MNrGQrDOTZJ5IEnvl+NiFhVpU2iWdRDZ3eMS842cWxSWStvkr+KJJKy+Iw==}
+    peerDependencies:
+      gm: ^1.23.0
+      pug: ^2.0.0 || ^3.0.0
+      sharp: '*'
+      vue: ^2.7.2
+      vuetify: ^1.3.0 || ^2.0.0
+      webpack: ^4.0.0 || ^5.0.0
+    peerDependenciesMeta:
+      gm:
+        optional: true
+      pug:
+        optional: true
+      sharp:
+        optional: true
+
+  vuetify@2.7.2:
+    resolution: {integrity: sha512-qr04ww7uzAPQbpk751x4fSdjsJ+zREzjQ/rBlcQGuWS6MIMFMXcXcwvp4+/tnGsULZxPMWfQ0kmZmg5Yc/XzgQ==}
+    peerDependencies:
+      vue: ^2.6.4
+
   vuex@3.6.2:
     resolution: {integrity: sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==}
     peerDependencies:
@@ -5005,6 +5157,8 @@ snapshots:
 
   '@leichtgewicht/ip-codec@2.0.5': {}
 
+  '@mdi/font@5.9.55': {}
+
   '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
     dependencies:
       eslint-scope: 5.1.1
@@ -5288,11 +5442,11 @@ snapshots:
 
   '@vue/cli-overlay@5.0.8': {}
 
-  '@vue/cli-plugin-babel@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(core-js@3.39.0)(vue@2.7.16)':
+  '@vue/cli-plugin-babel@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(core-js@3.39.0)(vue@2.7.16)':
     dependencies:
       '@babel/core': 7.26.0
       '@vue/babel-preset-app': 5.0.8(@babel/core@7.26.0)(core-js@3.39.0)(vue@2.7.16)
-      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
       '@vue/cli-shared-utils': 5.0.8
       babel-loader: 8.4.1(@babel/core@7.26.0)(webpack@5.97.1)
       thread-loader: 3.0.4(webpack@5.97.1)
@@ -5307,9 +5461,9 @@ snapshots:
       - vue
       - webpack-cli
 
-  '@vue/cli-plugin-eslint@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint@7.32.0)':
+  '@vue/cli-plugin-eslint@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint@7.32.0)':
     dependencies:
-      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
       '@vue/cli-shared-utils': 5.0.8
       eslint: 7.32.0
       eslint-webpack-plugin: 3.2.0(eslint@7.32.0)(webpack@5.97.1)
@@ -5323,26 +5477,26 @@ snapshots:
       - uglify-js
       - webpack-cli
 
-  '@vue/cli-plugin-router@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))':
+  '@vue/cli-plugin-router@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))':
     dependencies:
-      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
       '@vue/cli-shared-utils': 5.0.8
     transitivePeerDependencies:
       - encoding
 
-  '@vue/cli-plugin-vuex@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))':
+  '@vue/cli-plugin-vuex@5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))':
     dependencies:
-      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
 
-  '@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)':
+  '@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)':
     dependencies:
       '@babel/helper-compilation-targets': 7.25.9
       '@soda/friendly-errors-webpack-plugin': 1.8.1(webpack@5.97.1)
       '@soda/get-current-script': 1.0.2
       '@types/minimist': 1.2.5
       '@vue/cli-overlay': 5.0.8
-      '@vue/cli-plugin-router': 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
-      '@vue/cli-plugin-vuex': 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
+      '@vue/cli-plugin-router': 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
+      '@vue/cli-plugin-vuex': 5.0.8(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))
       '@vue/cli-shared-utils': 5.0.8
       '@vue/component-compiler-utils': 3.3.0(lodash@4.17.21)
       '@vue/vue-loader-v15': vue-loader@15.11.1(@vue/compiler-sfc@3.5.13)(css-loader@6.11.0(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(webpack@5.97.1)
@@ -5393,6 +5547,7 @@ snapshots:
       whatwg-fetch: 3.6.20
     optionalDependencies:
       less-loader: 8.1.1(less@4.2.1)(webpack@5.97.1)
+      sass-loader: 10.5.2(sass@1.32.13)(webpack@5.97.1)
       vue-template-compiler: 2.7.16
       webpack-sources: 3.2.3
     transitivePeerDependencies:
@@ -5587,7 +5742,7 @@ snapshots:
       - walrus
       - whiskers
 
-  '@vue/eslint-config-standard@6.1.0(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint-plugin-import@2.31.0(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(webpack@5.97.1)':
+  '@vue/eslint-config-standard@6.1.0(@vue/cli-service@5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3))(eslint-plugin-import@2.31.0(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint-plugin-vue@8.7.1(eslint@7.32.0))(eslint@7.32.0)(webpack@5.97.1)':
     dependencies:
       eslint: 7.32.0
       eslint-config-standard: 16.0.3(eslint-plugin-import@2.31.0(eslint@7.32.0))(eslint-plugin-node@11.1.0(eslint@7.32.0))(eslint-plugin-promise@5.2.0(eslint@7.32.0))(eslint@7.32.0)
@@ -5598,7 +5753,7 @@ snapshots:
       eslint-plugin-promise: 5.2.0(eslint@7.32.0)
       eslint-plugin-vue: 8.7.1(eslint@7.32.0)
     optionalDependencies:
-      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
+      '@vue/cli-service': 5.0.8(@vue/compiler-sfc@3.5.13)(less-loader@8.1.1(less@4.2.1)(webpack@5.97.1))(lodash@4.17.21)(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue-template-compiler@2.7.16)(vue@2.7.16)(webpack-sources@3.2.3)
     transitivePeerDependencies:
       - supports-color
       - webpack
@@ -5825,6 +5980,8 @@ snapshots:
     dependencies:
       lodash: 4.17.21
 
+  asynckit@0.4.0: {}
+
   at-least-node@1.0.0: {}
 
   autoprefixer@10.4.20(postcss@8.4.49):
@@ -5841,6 +5998,14 @@ snapshots:
     dependencies:
       possible-typed-array-names: 1.0.0
 
+  axios@1.7.9:
+    dependencies:
+      follow-redirects: 1.15.9(debug@4.4.0)
+      form-data: 4.0.1
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+
   babel-loader@8.4.1(@babel/core@7.26.0)(webpack@5.97.1):
     dependencies:
       '@babel/core': 7.26.0
@@ -5962,6 +6127,8 @@ snapshots:
       call-bind-apply-helpers: 1.0.1
       get-intrinsic: 1.2.6
 
+  callsite@1.0.0: {}
+
   callsites@3.1.0: {}
 
   camel-case@4.1.2:
@@ -6073,6 +6240,10 @@ snapshots:
 
   colorette@2.0.20: {}
 
+  combined-stream@1.0.8:
+    dependencies:
+      delayed-stream: 1.0.0
+
   commander@2.20.3: {}
 
   commander@7.2.0: {}
@@ -6297,6 +6468,10 @@ snapshots:
     dependencies:
       ms: 2.1.3
 
+  decache@4.6.2:
+    dependencies:
+      callsite: 1.0.0
+
   deep-is@0.1.4: {}
 
   deepmerge@1.5.2: {}
@@ -6323,6 +6498,8 @@ snapshots:
       has-property-descriptors: 1.0.2
       object-keys: 1.1.1
 
+  delayed-stream@1.0.0: {}
+
   depd@1.1.2: {}
 
   depd@2.0.0: {}
@@ -6841,6 +7018,12 @@ snapshots:
     dependencies:
       flat-cache: 3.2.0
 
+  file-loader@6.2.0(webpack@5.97.1):
+    dependencies:
+      loader-utils: 2.0.4
+      schema-utils: 3.3.0
+      webpack: 5.97.1
+
   fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -6888,6 +7071,12 @@ snapshots:
     dependencies:
       is-callable: 1.2.7
 
+  form-data@4.0.1:
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+
   forwarded@0.2.0: {}
 
   fraction.js@4.3.7: {}
@@ -7641,6 +7830,12 @@ snapshots:
     dependencies:
       boolbase: 1.0.0
 
+  null-loader@4.0.1(webpack@5.97.1):
+    dependencies:
+      loader-utils: 2.0.4
+      schema-utils: 3.3.0
+      webpack: 5.97.1
+
   object-assign@4.1.1: {}
 
   object-inspect@1.13.3: {}
@@ -8044,6 +8239,8 @@ snapshots:
       forwarded: 0.2.0
       ipaddr.js: 1.9.1
 
+  proxy-from-env@1.1.0: {}
+
   prr@1.0.1:
     optional: true
 
@@ -8060,6 +8257,10 @@ snapshots:
     dependencies:
       side-channel: 1.1.0
 
+  qs@6.14.0:
+    dependencies:
+      side-channel: 1.1.0
+
   queue-microtask@1.2.3: {}
 
   randombytes@2.1.0:
@@ -8108,6 +8309,10 @@ snapshots:
     dependencies:
       picomatch: 2.3.1
 
+  rechoir@0.6.2:
+    dependencies:
+      resolve: 1.22.10
+
   reflect.getprototypeof@1.0.9:
     dependencies:
       call-bind: 1.0.8
@@ -8171,6 +8376,8 @@ snapshots:
 
   requires-port@1.0.0: {}
 
+  resize-observer-polyfill@1.5.1: {}
+
   resolve-from@4.0.0: {}
 
   resolve@1.22.10:
@@ -8203,6 +8410,8 @@ snapshots:
     dependencies:
       glob: 7.2.3
 
+  roboto-fontface@0.10.0: {}
+
   run-parallel@1.2.0:
     dependencies:
       queue-microtask: 1.2.3
@@ -8232,6 +8441,21 @@ snapshots:
 
   safer-buffer@2.1.2: {}
 
+  sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1):
+    dependencies:
+      klona: 2.0.6
+      loader-utils: 2.0.4
+      neo-async: 2.6.2
+      schema-utils: 3.3.0
+      semver: 7.6.3
+      webpack: 5.97.1
+    optionalDependencies:
+      sass: 1.32.13
+
+  sass@1.32.13:
+    dependencies:
+      chokidar: 3.6.0
+
   sax@1.4.1:
     optional: true
 
@@ -8348,6 +8572,12 @@ snapshots:
 
   shell-quote@1.8.2: {}
 
+  shelljs@0.8.5:
+    dependencies:
+      glob: 7.2.3
+      interpret: 1.4.0
+      rechoir: 0.6.2
+
   side-channel-list@1.0.0:
     dependencies:
       es-errors: 1.3.0
@@ -8715,6 +8945,17 @@ snapshots:
 
   vary@1.1.2: {}
 
+  vue-cli-plugin-vuetify@2.5.8(sass-loader@10.5.2(sass@1.32.13)(webpack@5.97.1))(vue@2.7.16)(vuetify-loader@1.9.2(vue@2.7.16)(vuetify@2.7.2(vue@2.7.16))(webpack@5.97.1))(webpack@5.97.1):
+    dependencies:
+      null-loader: 4.0.1(webpack@5.97.1)
+      semver: 7.6.3
+      shelljs: 0.8.5
+      vue: 2.7.16
+      webpack: 5.97.1
+    optionalDependencies:
+      sass-loader: 10.5.2(sass@1.32.13)(webpack@5.97.1)
+      vuetify-loader: 1.9.2(vue@2.7.16)(vuetify@2.7.2(vue@2.7.16))(webpack@5.97.1)
+
   vue-eslint-parser@8.3.0(eslint@7.32.0):
     dependencies:
       debug: 4.4.0
@@ -8730,6 +8971,11 @@ snapshots:
 
   vue-hot-reload-api@2.3.4: {}
 
+  vue-js-modal@2.0.1(vue@2.7.16):
+    dependencies:
+      resize-observer-polyfill: 1.5.1
+      vue: 2.7.16
+
   vue-loader@15.11.1(@vue/compiler-sfc@3.5.13)(css-loader@6.11.0(webpack@5.97.1))(lodash@4.17.21)(vue-template-compiler@2.7.16)(webpack@5.97.1):
     dependencies:
       '@vue/component-compiler-utils': 3.3.0(lodash@4.17.21)
@@ -8828,6 +9074,21 @@ snapshots:
       '@vue/compiler-sfc': 2.7.16
       csstype: 3.1.3
 
+  vuetify-loader@1.9.2(vue@2.7.16)(vuetify@2.7.2(vue@2.7.16))(webpack@5.97.1):
+    dependencies:
+      acorn: 8.14.0
+      acorn-walk: 8.3.4
+      decache: 4.6.2
+      file-loader: 6.2.0(webpack@5.97.1)
+      loader-utils: 2.0.4
+      vue: 2.7.16
+      vuetify: 2.7.2(vue@2.7.16)
+      webpack: 5.97.1
+
+  vuetify@2.7.2(vue@2.7.16):
+    dependencies:
+      vue: 2.7.16
+
   vuex@3.6.2(vue@2.7.16):
     dependencies:
       vue: 2.7.16

+ 9 - 2
public/index.html

@@ -1,12 +1,12 @@
 <!DOCTYPE html>
-<html lang="">
+<html lang="en">
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%= htmlWebpackPlugin.options.title %></title>
-  </head>
+</head>
   <body>
     <noscript>
       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
@@ -14,4 +14,11 @@
     <div id="app"></div>
     <!-- built files will be auto injected -->
   </body>
+  <style>
+    html, body {
+      height: 100%;
+      width: 100%;
+      overflow: hidden;
+    }
+  </style>
 </html>

BIN
public/models/age_gender_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/age_gender_model-weights_manifest.json


BIN
public/models/face_expression_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/face_expression_model-weights_manifest.json


BIN
public/models/face_landmark_68_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/face_landmark_68_model-weights_manifest.json


BIN
public/models/face_landmark_68_tiny_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/face_landmark_68_tiny_model-weights_manifest.json


BIN
public/models/face_recognition_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/face_recognition_model-shard2


File diff suppressed because it is too large
+ 0 - 0
public/models/face_recognition_model-weights_manifest.json


BIN
public/models/mtcnn_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/mtcnn_model-weights_manifest.json


BIN
public/models/ssd_mobilenetv1_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/ssd_mobilenetv1_model-shard2


File diff suppressed because it is too large
+ 0 - 0
public/models/ssd_mobilenetv1_model-weights_manifest.json


BIN
public/models/tiny_face_detector_model-shard1


File diff suppressed because it is too large
+ 0 - 0
public/models/tiny_face_detector_model-weights_manifest.json


+ 18 - 23
src/App.vue

@@ -1,32 +1,27 @@
 <template>
-  <div id="app">
-    <nav>
-      <router-link to="/">Home</router-link> |
-      <router-link to="/about">About</router-link>
-    </nav>
+  <v-app>
     <router-view/>
-  </div>
+  </v-app>
 </template>
 
-<style lang="less">
-#app {
-  font-family: Avenir, Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  text-align: center;
-  color: #2c3e50;
-}
+<script>
 
-nav {
-  padding: 30px;
+export default {
+  name: 'App',
 
-  a {
-    font-weight: bold;
-    color: #2c3e50;
+  data: () => ({
+    //
+  })
+}
+</script>
 
-    &.router-link-exact-active {
-      color: #42b983;
-    }
-  }
+<style>
+* {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+.text--indigo {
+  color: #3F51B5;
 }
 </style>

+ 5 - 0
src/api/glass.js

@@ -0,0 +1,5 @@
+import http from '@/utils/request'
+
+export function uploadImg (data) {
+  return http.upload('/upload', data)
+}

BIN
src/assets/face/oval.jpg


BIN
src/assets/face/square.jpg


BIN
src/assets/face/圆形脸.jpg


BIN
src/assets/face/心形脸.jpg


BIN
src/assets/face/梨形脸.jpg


BIN
src/assets/face/钻石脸.jpg


BIN
src/assets/face/长形脸.jpg


BIN
src/assets/logo.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

+ 0 - 60
src/components/HelloWorld.vue

@@ -1,60 +0,0 @@
-<template>
-  <div class="hello">
-    <h1>{{ msg }}</h1>
-    <p>
-      For a guide and recipes on how to configure / customize this project,<br>
-      check out the
-      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
-    </p>
-    <h3>Installed CLI Plugins</h3>
-    <ul>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
-    </ul>
-    <h3>Essential Links</h3>
-    <ul>
-      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
-      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
-      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
-      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
-      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
-    </ul>
-    <h3>Ecosystem</h3>
-    <ul>
-      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
-      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
-      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
-      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
-      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'HelloWorld',
-  props: {
-    msg: String
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped lang="less">
-h3 {
-  margin: 40px 0 0;
-}
-ul {
-  list-style-type: none;
-  padding: 0;
-}
-li {
-  display: inline-block;
-  margin: 0 10px;
-}
-a {
-  color: #42b983;
-}
-</style>

+ 7 - 1
src/main.js

@@ -2,11 +2,17 @@ import Vue from 'vue'
 import App from './App.vue'
 import router from './router'
 import store from './store'
+import VModal from 'vue-js-modal'
+import vuetify from './plugins/vuetify'
+import 'vue-js-modal/dist/styles.css'
+import 'roboto-fontface/css/roboto/roboto-fontface.css'
+import '@mdi/font/css/materialdesignicons.css'
 
 Vue.config.productionTip = false
-
+Vue.use(VModal, { dialog: true, dialogComponentName: 'VueDialog' })
 new Vue({
   router,
   store,
+  vuetify,
   render: h => h(App)
 }).$mount('#app')

+ 23 - 0
src/plugins/vuetify.js

@@ -0,0 +1,23 @@
+import Vue from 'vue'
+import Vuetify from 'vuetify/lib/framework'
+
+Vue.use(Vuetify)
+
+export default new Vuetify({
+  theme: {
+    options: {
+      customProperties: true
+    },
+    themes: {
+      light: {
+        primary: '#007BFF',
+        secondary: '#424242',
+        accent: '#82B1FF',
+        error: '#FF5252',
+        info: '#2196F3',
+        success: '#4CAF50',
+        warning: '#FFC107'
+      }
+    }
+  }
+})

+ 1 - 10
src/router/index.js

@@ -1,6 +1,5 @@
 import Vue from 'vue'
 import VueRouter from 'vue-router'
-import HomeView from '../views/HomeView.vue'
 
 Vue.use(VueRouter)
 
@@ -8,15 +7,7 @@ const routes = [
   {
     path: '/',
     name: 'home',
-    component: HomeView
-  },
-  {
-    path: '/about',
-    name: 'about',
-    // route level code-splitting
-    // this generates a separate chunk (about.[hash].js) for this route
-    // which is lazy-loaded when the route is visited.
-    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
+    component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue')
   }
 ]
 

+ 119 - 0
src/utils/request.js

@@ -0,0 +1,119 @@
+import axios from 'axios'
+// import { getToken } from '@/utils/auth'
+// import { blobToJson } from '@/utils'
+import qs from 'qs'
+// import Vue from 'vue'
+// create an axios instance
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API,
+  // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 120000 // request timeout
+})
+
+// request interceptor
+// 发送请求拦截器
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+    // if (getToken()) {
+    //   config.headers.token = getToken()
+    // }
+    // config.headers.token = getToken()
+    return config
+  },
+  error => {
+    // do something with request error
+    // console.error(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+// 请求返回之后的拦截器
+service.interceptors.response.use(
+  async response => {
+    const res = response.data
+
+    // console.log(response)
+
+    // if (response.request.responseType === 'blob') {
+    //   // 返回的文件流当报错时转化成json
+    //   if (response.headers['content-type'] === 'application/json') {
+    //     try {
+    //       const result = await blobToJson(res)
+    //       return Promise.reject(result.msg)
+    //     } catch (error) {
+    //       return Promise.reject(error)
+    //     }
+    //   }
+    //   const name = response.headers['content-disposition']
+    //   // console.log(name)
+    //   return {
+    //     data: res,
+    //     name: name ? decodeURI(name.replace('attachment;filename=', '')) : '未命名'
+    //   }
+    // }
+
+    // if (res.code !== 20000) {
+    //   if (res.data && Object.keys(res.data).length) {
+    //     return Promise.reject(res)
+    //   }
+    //   return Promise.reject(res.msg.error || res.msg)
+    // }
+    return res
+  },
+  error => {
+    // console.error(error)
+    // error = error.message || error
+    return Promise.reject(error)
+  }
+)
+
+// 请求方法
+const http = {
+  post (url, params) {
+    return service.post(url, params, {
+      transformRequest: [(params) => {
+        return JSON.stringify(params)
+      }],
+      headers: {
+        'Content-Type': 'application/json'
+      }
+    })
+  },
+  get (url, params) {
+    return service.get(url, {
+      params: params,
+      paramsSerializer: (params) => {
+        return qs.stringify(params)
+      }
+    })
+  },
+  formData (url, params) {
+    return service.post(url, params, {
+      timeout: 600000,
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded'
+      }
+    })
+  },
+  upload (url, params) {
+    return service.post(url, params, {
+      timeout: 600000,
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    })
+  },
+  download (url, params) {
+    return service.post(url, params, {
+      timeout: 10000,
+      headers: {
+        'Content-Type': 'application/json'
+      },
+      responseType: 'blob'
+    })
+  }
+}
+export default http

+ 0 - 5
src/views/AboutView.vue

@@ -1,5 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>

+ 112 - 0
src/views/Home.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="home">
+    <section v-if="tab === 0">
+      <MCover @to="tab = 1">
+        <template #center="{ active, target }">
+          <MCamera v-if="active" @reject="$event => handleReject($event, target)" @photo="handleGet" @close="target(false)"></MCamera>
+        </template>
+      </MCover>
+    </section>
+    <section v-if="tab === 1">
+      <MFeature :src="imgBase64" :items="face" @reTake="tab = 0"></MFeature>
+    </section>
+    <modal name="VueDialog" height="150" width="300">
+      <div class="model">
+        <div class="model-title">提示</div>
+        <div class="model-content">{{ error }}</div>
+        <div class="model-btn">
+          <button class="btn" @click="$modal.hide('VueDialog')">关闭</button>
+        </div>
+      </div>
+    </modal>
+  </div>
+</template>
+
+<script>
+import MCover from './components/MCover'
+import MCamera from './components/MCamera'
+import MFeature from './components/MFeature'
+export default {
+  name: 'home-view',
+  components: {
+    MCover,
+    MCamera,
+    MFeature
+  },
+  data () {
+    return {
+      imgBase64: '',
+      tab: 0,
+      error: ''
+    }
+  },
+  methods: {
+    handleReject (error, target) {
+      this.tab = 0
+      if (error) {
+        target && target(false)
+        this.error = error
+        this.$modal.show('VueDialog')
+      }
+    },
+    handleGet (data, imgBase64) {
+      this.imgBase64 = imgBase64
+      this.face = data
+      this.tab = 1
+    }
+  }
+}
+</script>
+
+<style scoped lang="less">
+section {
+  height: 100vh;
+  width: 100vw;
+}
+.home {
+  height: 100vh;
+  width: 100vw;
+  overflow: hidden;
+}
+.model{
+  padding: 20px;
+  &-title {
+    font-size: 24px;
+    font-weight: bolder;
+    margin-bottom: 12px;
+  }
+  &-content {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 12px;
+    &-link {
+      color: #141e8c;
+      cursor: pointer;
+    }
+  }
+  &-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .btn {
+      -webkit-tap-highlight-color: transparent;
+      width: 100px;
+      height: 40px;
+      border-radius: 20px;
+      background: #eee;
+      color: #666;
+      border: none;
+      outline: none;
+      cursor: pointer;
+    }
+    ._primary {
+      background: #141e8c;
+      color: #fff;
+    }
+    .default {
+      background: #eee;
+      color: #666;
+    }
+  }
+}
+</style>

+ 0 - 18
src/views/HomeView.vue

@@ -1,18 +0,0 @@
-<template>
-  <div class="home">
-    <img alt="Vue logo" src="../assets/logo.png">
-    <HelloWorld msg="Welcome to Your Vue.js App"/>
-  </div>
-</template>
-
-<script>
-// @ is an alias to /src
-import HelloWorld from '@/components/HelloWorld.vue'
-
-export default {
-  name: 'HomeView',
-  components: {
-    HelloWorld
-  }
-}
-</script>

+ 430 - 0
src/views/components/MCamera.vue

@@ -0,0 +1,430 @@
+<template>
+  <div class="faceRecognition">
+    <img :src="src" alt="" v-show="false" ref="img">
+    <video
+      v-show="false"
+      ref="video"
+      autoplay
+      playsinline
+    ></video>
+    <div class="box d-flex align-center justify-center" ref="box">
+      <canvas ref="canvas"></canvas>
+      <template v-if="ready">
+        <div class="box-tool d-flex align-center justify-center">
+          <div class="item" @click="handleTake">
+            <v-icon color="#000" size="36">mdi mdi-camera</v-icon>
+          </div>
+        </div>
+        <div class="close" @click="$emit('close')">
+          <v-icon color="#000" size="36">mdi mdi-close-circle</v-icon>
+        </div>
+      </template>
+
+      <div v-if="showImg" class="imgBox d-flex align-center justify-center" @click="showImg = false">
+        <!-- <div class="faceItem" v-for="(detection, index) in detections" :key="index" :style="`left:${detection.x}px;top:${detection.y}px;font-size:14px;color: blue`">{{ index }}</div> -->
+        <img :src="src" alt="" :width="size.width" :height="size.height">
+      </div>
+    </div>
+
+    <v-overlay :value="loading" opacity="0">
+      <div class="loader">
+        <svg viewBox="0 0 80 80">
+          <rect height="64" width="64" y="8" x="8"></rect>
+        </svg>
+      </div>
+    </v-overlay>
+  </div>
+</template>
+
+<script>
+// import * as faceapi from 'face-api.js'
+import { uploadImg } from '@/api/glass'
+export default {
+  name: 'm-camera',
+  data () {
+    return {
+      videoStream: null,
+      animationId: null,
+      src: '',
+      loading: true,
+      ready: false,
+      faceShape: '',
+      showImg: false,
+      detections: [],
+      size: {
+        width: 480,
+        height: 480
+      }
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.resizeCanvas()
+      // this.loadModels()
+      this.startCamera()
+    })
+  },
+  methods: {
+    oGetUserMedia (constraints) {
+      const getUserMedia = navigator.mediaDevices?.getUserMedia ||
+                          navigator.getUserMedia ||
+                          navigator.webkitGetUserMedia ||
+                          navigator.mozGetUserMedia ||
+                          navigator.msGetUserMedia ||
+                          navigator.oGetUserMedia
+
+      return new Promise((resolve, reject) => {
+        if (!getUserMedia) {
+          return reject(new Error('getUserMedia is not supported.'))
+        }
+        getUserMedia.call(navigator.mediaDevices || navigator, constraints)
+          .then(resolve)
+          .catch(reject)
+      })
+    },
+    async startCamera () {
+      this.loading = true
+      this.ready = false
+      try {
+        this.videoStream = await this.oGetUserMedia({
+          video: { facingMode: 'user' }
+        })
+
+        const video = this.$refs.video
+        this.$refs.video.srcObject = this.videoStream
+        video.onloadedmetadata = () => {
+          video.play()
+          this.drawToCanvas()
+          this.loading = false
+          this.ready = true
+        }
+      } catch (error) {
+        this.$emit('reject', '相机打开失败')
+      }
+    },
+    resizeCanvas () {
+      const canvas = this.$refs.canvas
+      canvas.width = this.size.width
+      canvas.height = this.size.height
+      // canvas.width = this.$refs.box.clientWidth
+      // canvas.height = this.$refs.box.clientHeight
+    },
+    drawToCanvas () {
+      const canvas = this.$refs.canvas
+      const context = canvas.getContext('2d')
+      const video = this.$refs.video
+
+      // 将视频画面绘制到canvas上
+      const drawFrame = () => {
+        context.clearRect(0, 0, canvas.width, canvas.height)
+
+        // 获取视频宽高比
+        const videoAspectRatio = video.videoWidth / video.videoHeight
+        const canvasAspectRatio = canvas.width / canvas.height
+        let drawWidth, drawHeight, drawX, drawY
+        // 判断对比率
+        if (videoAspectRatio > canvasAspectRatio) {
+          drawWidth = canvas.height * videoAspectRatio
+          drawHeight = canvas.height
+          drawX = (canvas.width - drawWidth) / 2
+          drawY = 0
+        } else {
+          drawHeight = canvas.width * videoAspectRatio
+          drawWidth = canvas.width
+          drawX = 0
+          drawY = (canvas.height - drawHeight) / 2
+        }
+        // 保存当前画布状态
+        context.save()
+
+        // 水平翻转画布
+        context.scale(-1, 1)
+        // 在翻转的画布上绘制视频,调整绘制位置
+        context.drawImage(video, -drawX - drawWidth, drawY, drawWidth, drawHeight)
+
+        // 恢复画布状态
+        context.restore()
+
+        this.animationId = requestAnimationFrame(drawFrame)
+      }
+
+      // 开始动画循环
+      drawFrame()
+    },
+    async handleTake () {
+      this.loading = true
+      const canvas = this.$refs.canvas
+      // 使用 toDataURL 方法获取 Base64 图像数据
+      const img = canvas.toDataURL('image/png') // 可以选择其他格式如 'image/jpeg'
+      try {
+        this.src = img
+        this.showImg = true
+        const response = await fetch(img)
+        const blob = await response.blob()
+        const file = new File([blob], 'canvasImage.png', { type: 'image/png' })
+        const query = new FormData()
+        query.append('file', file)
+        const { data, error } = await uploadImg(query)
+        if (error) {
+          console.log('错误')
+          return
+        }
+        this.$emit('photo', data, img)
+      } catch (error) {
+        console.log(132, error)
+      }
+      // setTimeout(async () => {
+      //   const face = await this.detectFaceShape(img)
+      //   this.loading = false
+      //   if (!face) {
+      //     window.alert('请拍摄清晰的人脸照片')
+      //     // return
+      //   }
+      //   // this.showImg = false
+      //   // this.$emit('photo', face, img)
+      //   // this.$modal.show('face')
+      //   // window.alert(face)
+      // }, 3000)
+    }
+    // handleReTake () {
+    //   this.src = ''
+    //   this.showImg = false
+    //   this.$modal.hide('face')
+    // }
+  },
+  beforeDestroy () {
+    // 停止视频流和动画
+    if (this.videoStream) {
+      this.videoStream.getTracks().forEach(track => track.stop())
+    }
+    if (this.animationId) {
+      cancelAnimationFrame(this.animationId)
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.faceRecognition {
+  font-size: 14px;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  // background: #000;
+}
+
+.box {
+  margin: 0 auto;
+  max-width: 600px;
+  position: relative;
+  height: 100%;
+  // &-tool {
+  //   position: fixed;
+  //   width: calc(100% - 600px);
+  //   height: 100%;
+  //   right: 0;
+  //   top: 0;
+  //   font-size: 16px;
+  // }
+  &-tool {
+    position: absolute;
+    height: 150px;
+    width: 100%;
+    bottom: 0;
+    left: 0;
+    font-size: 16px;
+    // background: rgba(0, 0, 0, .5);
+    .item {
+      padding: 10px;
+      border: 1px solid #999;
+      border-radius: 90px;
+      cursor: pointer;
+    }
+  }
+  .close {
+    position: absolute;
+    cursor: pointer;
+    right: 10px;
+    top: 10px;
+    width: 40px;
+    height: 40px;
+    text-align: center;
+    line-height: 40px;
+  }
+  .imgBox {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+}
+
+.faceItem {
+  position: absolute;
+  width: 5px;
+  height: 5px;
+  z-index: 999;
+  background: greenyellow;
+}
+
+/* From Uiverse.io by mobinkakei */
+.loader {
+  --path: #FFF;
+  --dot: #141e8c;
+  --duration: 3s;
+  width: 44px;
+  height: 44px;
+  position: relative;
+}
+
+.loader:before {
+  content: "";
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  position: absolute;
+  display: block;
+  background: var(--dot);
+  top: 37px;
+  left: 19px;
+  transform: translate(-18px, -18px);
+  animation: dotRect var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
+    infinite;
+}
+
+.loader svg {
+  display: block;
+  width: 100%;
+  height: 100%;
+}
+
+.loader svg rect,
+.loader svg polygon,
+.loader svg circle {
+  fill: none;
+  stroke: var(--path);
+  stroke-width: 10px;
+  stroke-linejoin: round;
+  stroke-linecap: round;
+}
+
+.loader svg polygon {
+  stroke-dasharray: 145 76 145 76;
+  stroke-dashoffset: 0;
+  animation: pathTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
+    infinite;
+}
+
+.loader svg rect {
+  stroke-dasharray: 192 64 192 64;
+  stroke-dashoffset: 0;
+  animation: pathRect 3s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
+}
+
+.loader svg circle {
+  stroke-dasharray: 150 50 150 50;
+  stroke-dashoffset: 75;
+  animation: pathCircle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
+    infinite;
+}
+
+.loader.triangle {
+  width: 48px;
+}
+
+.loader.triangle:before {
+  left: 21px;
+  transform: translate(-10px, -18px);
+  animation: dotTriangle var(--duration) cubic-bezier(0.785, 0.135, 0.15, 0.86)
+    infinite;
+}
+
+@keyframes pathTriangle {
+  33% {
+    stroke-dashoffset: 74;
+  }
+
+  66% {
+    stroke-dashoffset: 147;
+  }
+
+  100% {
+    stroke-dashoffset: 221;
+  }
+}
+
+@keyframes dotTriangle {
+  33% {
+    transform: translate(0, 0);
+  }
+
+  66% {
+    transform: translate(10px, -18px);
+  }
+
+  100% {
+    transform: translate(-10px, -18px);
+  }
+}
+
+@keyframes pathRect {
+  25% {
+    stroke-dashoffset: 64;
+  }
+
+  50% {
+    stroke-dashoffset: 128;
+  }
+
+  75% {
+    stroke-dashoffset: 192;
+  }
+
+  100% {
+    stroke-dashoffset: 256;
+  }
+}
+
+@keyframes dotRect {
+  25% {
+    transform: translate(0, 0);
+  }
+
+  50% {
+    transform: translate(18px, -18px);
+  }
+
+  75% {
+    transform: translate(0, -36px);
+  }
+
+  100% {
+    transform: translate(-18px, -18px);
+  }
+}
+
+@keyframes pathCircle {
+  25% {
+    stroke-dashoffset: 125;
+  }
+
+  50% {
+    stroke-dashoffset: 175;
+  }
+
+  75% {
+    stroke-dashoffset: 225;
+  }
+
+  100% {
+    stroke-dashoffset: 275;
+  }
+}
+
+.loader {
+  display: inline-block;
+  margin: 0 16px;
+}
+
+</style>

+ 239 - 0
src/views/components/MCover.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="home">
+    <div class="logo">
+      <img src="@/assets/logo.png" alt="">
+      <div class="name">蔡司视界镜架试戴体验</div>
+    </div>
+    <div class="home-cover" :class="{ active: active }">
+      <!-- <div class="name">蔡司视界镜架试戴体验</div> -->
+      <div class="d-flex flex-column justify-center align-center full" v-if="!active" @click="handleAlert">
+        <v-icon size="64" color="#141e8c">mdi-camera</v-icon>
+        点击开始体验
+      </div>
+      <slot name="center" :target="handleTarget" :active="active"></slot>
+    </div>
+    <modal name="example" :maxWidth="600" height="auto" width="80%" :adaptive="true">
+      <div class="model">
+        <div class="model-title">温馨提示</div>
+        <div class="model-content">即将授权卡尔蔡司光学(广州)有限公司获取脸部生物特征数据,用于脸型检测服务</div>
+        <div class="model-content">
+          本人已阅读并充分理解
+          <span class="model-content-link" @click="$modal.show('service')">《用户服务协议》</span>
+          条款。本人自愿授权提供个人信息给蔡司用于上述目的,同意蔡司公司及其境内外关联公司为前述目的收集、使用、处理、存储本人的个人信息。
+        </div>
+        <div class="model-btn">
+          <button class="btn _primary" @click="handleAgree">同意</button>
+        </div>
+      </div>
+    </modal>
+    <modal name="service" height="auto" :maxWidth="600" width="80%" :adaptive="true">
+      <div class="model" style="max-height: 90vh; overflow-y: auto;">
+        <div class="model-content">亲爱的朋友:</div>
+        <div class="model-content">卡尔蔡司光学(广州)有限公司(“蔡司”)尊重所有用户的个人隐私权。我们将遵守中华人民共和国有关个人信息保护的法律法规规定及其不时更新(如有)(包括但不限于《中华人民共和国网络安全法》),合法的收集、使用、处理、存储您的个人信息。</div>
+        <div class="model-content">所有收集到的有关您的个人信息将用于蔡司的系统体验功能(“目的”),个人信息包括但不限于拍摄、储存您的人脸信息的情况等。您的个人信息仅限于蔡司及其境内外关联公司为上述目的而使用。未经您的允许,以上数据使用方均不会透露给其它任何第三方。但为了公司系统研发的需要,我们将适当地使用您的个人信息来实现我们的目的。您的意愿将得到尊重,您可以随时通知我们您不希望我们继续使用这些信息</div>
+        <div class="model-content">您的个人信息将储存在一个受保护的安全环境中,防止未经授权的访问及获取。如果我们认为这些信息有助于我们实现前述目的,在尊重您意愿的前提下,我们将在我们认为必要的时间范围内保存您的个人信息</div>
+        <div class="model-content">如您的个人信息因内部管理需求,须转移蔡司于境外的关联公司,我们将对传输及转移采取严格的保密措施。</div>
+        <div class="model-content">您有权查看我们所保留的有关您的个人信息并且进行必要的更正,您还有权要求我们停止使用这些信息。我们将切实努力尊重您的个人意愿。但是,特定的法规,尤其与安全问题或者财务有关的法规(例如,在涉及金融服务时),可能会限制更改您已提供的信息</div>
+        <div class="model-btn">
+          <button class="btn" @click="$modal.hide('service')">关闭</button>
+        </div>
+      </div>
+    </modal>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'm-cover',
+  data () {
+    return {
+      active: false
+    }
+  },
+  methods: {
+    handleAgree () {
+      this.active = true
+      this.$modal.hide('example')
+      // setTimeout(() => {
+      //   this.$emit('to')
+      // }, 200)
+    },
+    handleTarget (bool) {
+      this.active = bool
+    },
+    handleAlert () {
+      if (this.active) {
+        return
+      }
+      this.$modal.show('example')
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.home {
+  @delay: 0.5s;
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background: #000;
+  .logo {
+    position: absolute;
+    left: 30px;
+    top: 30px;
+    z-index: 99;
+    display: flex;
+    align-items: center;
+    img {
+      width: 80px;
+      height: auto;
+    }
+    .name {
+      color: #141e8c;
+      font-size: 32px;
+      font-weight: bolder;
+      margin-left: 30px;
+      text-shadow: 1px 1px 0 white;
+    }
+
+  }
+  // &.active {
+  //   &::before {
+  //     left: 100%;
+  //   }
+  //   &::after {
+  //     left: -100%;
+  //   }
+  // }
+  &::before {
+    content: '';
+    position: absolute;
+    transition: @delay;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: #141e8c;
+    clip-path: polygon(70% 0, 100% 0, 100% 100%, 30% 100%);
+  }
+  &::after {
+    content: '';
+    position: absolute;
+    transition: @delay;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: #FFF;
+    clip-path: polygon(0 0, 70% 0, 30% 100%, 0 100%);
+  }
+  &-cover {
+    @width: 200px;
+    cursor: pointer;
+    position: absolute;
+    overflow: hidden;
+    box-shadow: 0px 0px 19px 8px rgba(0, 0, 0, 0.2);
+    z-index: 9;
+    // display: flex;
+    // flex-direction: column;
+    // align-items: center;
+    // justify-content: center;
+    border-radius: @width;
+    width: @width;
+    height: @width;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    background: #FFF;
+    background-blend-mode: multiply;
+    transition: .5s ease-in-out;
+    .full {
+      width: 100%;
+      height: 100%;
+    }
+    &.active {
+      cursor: default;
+      width: 80%;
+      height: 80%;
+      border-radius: 10px;
+    }
+  }
+}
+.chooseMethod {
+  height: 100%;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  // background: #aaa;
+  &-item {
+    -webkit-tap-highlight-color: transparent;
+    width: 200px;
+    height: 200px;
+    border-radius: 10px;
+    background: rgba(20, 30, 140, 1);
+    // background: #141e8c;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    margin: 0 20px;
+    cursor: pointer;
+    color: #FFF;
+    .fa {
+      font-size: 40px;
+      color: #FFF;
+      margin-bottom: 12px;
+    }
+  }
+}
+.model{
+  padding: 20px;
+  &-title {
+    font-size: 24px;
+    font-weight: bolder;
+    margin-bottom: 12px;
+  }
+  &-content {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 12px;
+    &-link {
+      color: #141e8c;
+      cursor: pointer;
+    }
+  }
+  &-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20px;
+    .btn {
+      -webkit-tap-highlight-color: transparent;
+      width: 100px;
+      height: 40px;
+      border-radius: 20px;
+      background: #eee;
+      color: #666;
+      border: none;
+      outline: none;
+      cursor: pointer;
+    }
+    ._primary {
+      background: #141e8c;
+      color: #fff;
+    }
+    .default {
+      background: #eee;
+      color: #666;
+    }
+  }
+}
+@media screen and (max-width: 600px) {
+  .chooseMethod {
+    flex-direction: column;
+    justify-content: space-evenly;
+  }
+}
+</style>

+ 77 - 0
src/views/components/MFeature.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="pa-3 main d-flex align-center flex-column justify-center">
+    <div class="text-center text-h5 text--indigo mb-5">检测结果</div>
+    <div class="d-flex">
+      <div class="text-center" v-for="face in items" :key="face.name">
+        <img :src="require(`@/assets/face/${face.name}.jpg`)" alt="" width="300">
+        <div class="text-h5 text--indigo py-5">
+          {{ faceType[face.name] }}
+          {{ face.similarity.toFixed(2) }}%
+        </div>
+      </div>
+    </div>
+
+    <div class="d-flex mt-3">
+      <button class="btn mr-3 _primary" @click="handlePutOn">眼镜试戴</button>
+      <button class="btn" @click="handleReTake">重拍</button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'm-feature',
+  props: {
+    src: {
+      type: String,
+      default: ''
+    },
+    items: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data () {
+    return {
+      faceType: {
+        square: '方型脸',
+        oval: '鹅蛋脸'
+      }
+    }
+  },
+  methods: {
+    handlePutOn () {},
+    handleReTake () {
+      this.$emit('reTake')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.main {
+  width: 100%;
+  height: 100%;
+  font-size: 14px;
+  .content {
+    margin: 0 auto;
+    max-width: 600px;
+    height: 100%;
+  }
+}
+.btn {
+  -webkit-tap-highlight-color: transparent;
+  width: 100px;
+  height: 40px;
+  border-radius: 20px;
+  background: #eee;
+  color: #666;
+  border: none;
+  outline: none;
+  cursor: pointer;
+}
+._primary {
+  background: #141e8c;
+  color: #fff;
+}
+</style>

+ 23 - 1
vue.config.js

@@ -1,4 +1,26 @@
 const { defineConfig } = require('@vue/cli-service')
 module.exports = defineConfig({
-  transpileDependencies: true
+  publicPath: '/',
+  assetsDir: 'static',
+  productionSourceMap: process.env.NODE_ENV !== 'production',
+  devServer: {
+    open: true,
+    // host: 'localhost',
+    port: 3005,
+    hot: true
+    // https: false,
+    // proxy: {
+    //   '/api': {
+    //     target: process.env.VUE_APP_BASE_API,
+    //     secure: false, // 是否支持 https,默认 false
+    //     changeOrigin: true, // 是否支持跨域
+    //     pathRewrite: {
+    //       '^/api': ''
+    //     }
+    //   }
+    // }
+  },
+  transpileDependencies: [
+    'vuetify'
+  ]
 })

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