瀏覽代碼

眼镜试戴

zhengnaiwen_citu 4 月之前
父節點
當前提交
312a38bb15

+ 2 - 1
.env.development

@@ -3,5 +3,6 @@ NODE_ENV = 'development'
 
 VUE_APP_MODE = 'development'
 
-VUE_APP_BASE_API = 'http://192.168.3.58:5500'
+# VUE_APP_BASE_API = 'http://192.168.3.58:5500'
+VUE_APP_BASE_API = 'https://company.citupro.com:18181/op/base'
 

+ 1 - 1
.env.production

@@ -5,6 +5,6 @@ NODE_ENV = 'production'
 VUE_APP_MODE = 'production'
 # base api
 
-VUE_APP_BASE_API = 'http://115.128.14.14/op/base'
+VUE_APP_BASE_API = 'https://company.citupro.com:18181/op/base'
 
 

+ 4 - 2
package.json

@@ -11,9 +11,11 @@
     "@mdi/font": "5.9.55",
     "axios": "^1.7.9",
     "core-js": "^3.8.3",
+    "lodash": "^4.17.21",
     "qs": "^6.14.0",
-    "roboto-fontface": "*",
+    "swiper": "4",
     "vue": "^2.6.14",
+    "vue-awesome-swiper": "3.1.3",
     "vue-js-modal": "^2.0.1",
     "vue-router": "^3.5.1",
     "vuetify": "^2.6.0",
@@ -32,7 +34,7 @@
     "eslint-plugin-import": "^2.25.3",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-promise": "^5.1.0",
-    "eslint-plugin-vue": "^8.0.3",
+    "eslint-plugin-vue": "^7.20.0",
     "less": "^4.0.0",
     "less-loader": "^8.0.0",
     "sass": "~1.32.0",

+ 74 - 70
pnpm-lock.yaml

@@ -17,15 +17,21 @@ importers:
       core-js:
         specifier: ^3.8.3
         version: 3.39.0
+      lodash:
+        specifier: ^4.17.21
+        version: 4.17.21
       qs:
         specifier: ^6.14.0
         version: 6.14.0
-      roboto-fontface:
-        specifier: '*'
-        version: 0.10.0
+      swiper:
+        specifier: '4'
+        version: 4.5.1
       vue:
         specifier: ^2.6.14
         version: 2.7.16
+      vue-awesome-swiper:
+        specifier: 3.1.3
+        version: 3.1.3
       vue-js-modal:
         specifier: ^2.0.1
         version: 2.0.1(vue@2.7.16)
@@ -62,7 +68,7 @@ importers:
         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)(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)
+        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@7.20.0(eslint@7.32.0))(eslint@7.32.0)(webpack@5.97.1)
       eslint:
         specifier: ^7.32.0
         version: 7.32.0
@@ -76,8 +82,8 @@ importers:
         specifier: ^5.1.0
         version: 5.2.0(eslint@7.32.0)
       eslint-plugin-vue:
-        specifier: ^8.0.3
-        version: 8.7.1(eslint@7.32.0)
+        specifier: ^7.20.0
+        version: 7.20.0(eslint@7.32.0)
       less:
         specifier: ^4.0.0
         version: 4.2.1
@@ -1839,6 +1845,9 @@ packages:
   dom-serializer@1.4.1:
     resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
 
+  dom7@2.1.5:
+    resolution: {integrity: sha512-xnhwVgyOh3eD++/XGtH+5qBwYTgCm0aW91GFgPJ3XG+jlsRLyJivnbP0QmUBFhI+Oaz9FV0s7cxgXHezwOEBYA==}
+
   domelementtype@2.3.0:
     resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
 
@@ -2035,9 +2044,9 @@ packages:
     peerDependencies:
       eslint: ^7.0.0
 
-  eslint-plugin-vue@8.7.1:
-    resolution: {integrity: sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+  eslint-plugin-vue@7.20.0:
+    resolution: {integrity: sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==}
+    engines: {node: '>=8.10'}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
 
@@ -2045,20 +2054,10 @@ packages:
     resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
     engines: {node: '>=8.0.0'}
 
-  eslint-scope@7.2.2:
-    resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
   eslint-utils@2.1.0:
     resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
     engines: {node: '>=6'}
 
-  eslint-utils@3.0.0:
-    resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
-    engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
-    peerDependencies:
-      eslint: '>=5'
-
   eslint-visitor-keys@1.3.0:
     resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
     engines: {node: '>=4'}
@@ -2067,10 +2066,6 @@ packages:
     resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
     engines: {node: '>=10'}
 
-  eslint-visitor-keys@3.4.3:
-    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
   eslint-webpack-plugin@3.2.0:
     resolution: {integrity: sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==}
     engines: {node: '>= 12.13.0'}
@@ -2084,14 +2079,14 @@ packages:
     deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
+  espree@6.2.1:
+    resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
+    engines: {node: '>=6.0.0'}
+
   espree@7.3.1:
     resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==}
     engines: {node: ^10.12.0 || >=12.0.0}
 
-  espree@9.6.1:
-    resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
   esprima@4.0.1:
     resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
     engines: {node: '>=4'}
@@ -3626,9 +3621,6 @@ 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==}
 
@@ -3834,6 +3826,12 @@ packages:
   sprintf-js@1.0.3:
     resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
 
+  ssr-window@1.0.1:
+    resolution: {integrity: sha512-dgFqB+f00LJTEgb6UXhx0h+SrG50LJvti2yMKMqAgzfUmUXZrLSv2fjULF7AWGwK25EXu8+smLR3jYsJQChPsg==}
+
+  ssr-window@2.0.0:
+    resolution: {integrity: sha512-NXzN+/HPObKAx191H3zKlYomE5WrVIkoCB5IaSdvKokxTpjBdWfr0RaP+1Z5KOfDT0ZVz+2tdtiBkhsEQ9p+0A==}
+
   ssri@8.0.1:
     resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
     engines: {node: '>= 8'}
@@ -3937,6 +3935,10 @@ packages:
     engines: {node: '>=10.13.0'}
     hasBin: true
 
+  swiper@4.5.1:
+    resolution: {integrity: sha512-se6I7PWWu950NAMXXT+ENtF/6SVb8mPyO+bTfNxbQBILSeLqsYp3Ndap+YOA0EczOIUlea274PKejT6gKZDseA==}
+    engines: {node: '>= 4.7.0'}
+
   table@6.9.0:
     resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
     engines: {node: '>=10.0.0'}
@@ -4110,6 +4112,10 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
+  vue-awesome-swiper@3.1.3:
+    resolution: {integrity: sha512-E7suzkyApO8vNZbgdEnjSmnpsmQZyRvSVXJ7sey3XYwKPOkLhH3+GnHroBw+5PZIQXvWBwdCeQsPG1xQ1r1Rhg==}
+    engines: {node: '>= 4.0.0', npm: '>= 3.0.0'}
+
   vue-cli-plugin-vuetify@2.5.8:
     resolution: {integrity: sha512-uqi0/URJETJBbWlQHD1l0pnY7JN8Ytu+AL1fw50HFlGByPa8/xx+mq19GkFXA9FcwFT01IqEc/TkxMPugchomg==}
     peerDependencies:
@@ -4123,11 +4129,11 @@ packages:
       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}
+  vue-eslint-parser@7.11.0:
+    resolution: {integrity: sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==}
+    engines: {node: '>=8.10'}
     peerDependencies:
-      eslint: '>=6.0.0'
+      eslint: '>=5.0.0'
 
   vue-hot-reload-api@2.3.4:
     resolution: {integrity: sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==}
@@ -5742,7 +5748,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)(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)':
+  '@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@7.20.0(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)
@@ -5751,7 +5757,7 @@ snapshots:
       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-plugin-vue: 7.20.0(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)(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:
@@ -5851,10 +5857,6 @@ snapshots:
     dependencies:
       acorn: 7.4.1
 
-  acorn-jsx@5.3.2(acorn@8.14.0):
-    dependencies:
-      acorn: 8.14.0
-
   acorn-walk@8.3.4:
     dependencies:
       acorn: 8.14.0
@@ -6534,6 +6536,10 @@ snapshots:
       domhandler: 4.3.1
       entities: 2.2.0
 
+  dom7@2.1.5:
+    dependencies:
+      ssr-window: 2.0.0
+
   domelementtype@2.3.0: {}
 
   domhandler@4.3.1:
@@ -6788,15 +6794,13 @@ snapshots:
     dependencies:
       eslint: 7.32.0
 
-  eslint-plugin-vue@8.7.1(eslint@7.32.0):
+  eslint-plugin-vue@7.20.0(eslint@7.32.0):
     dependencies:
       eslint: 7.32.0
-      eslint-utils: 3.0.0(eslint@7.32.0)
+      eslint-utils: 2.1.0
       natural-compare: 1.4.0
-      nth-check: 2.1.1
-      postcss-selector-parser: 6.1.2
-      semver: 7.6.3
-      vue-eslint-parser: 8.3.0(eslint@7.32.0)
+      semver: 6.3.1
+      vue-eslint-parser: 7.11.0(eslint@7.32.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -6805,26 +6809,14 @@ snapshots:
       esrecurse: 4.3.0
       estraverse: 4.3.0
 
-  eslint-scope@7.2.2:
-    dependencies:
-      esrecurse: 4.3.0
-      estraverse: 5.3.0
-
   eslint-utils@2.1.0:
     dependencies:
       eslint-visitor-keys: 1.3.0
 
-  eslint-utils@3.0.0(eslint@7.32.0):
-    dependencies:
-      eslint: 7.32.0
-      eslint-visitor-keys: 2.1.0
-
   eslint-visitor-keys@1.3.0: {}
 
   eslint-visitor-keys@2.1.0: {}
 
-  eslint-visitor-keys@3.4.3: {}
-
   eslint-webpack-plugin@3.2.0(eslint@7.32.0)(webpack@5.97.1):
     dependencies:
       '@types/eslint': 8.56.12
@@ -6880,17 +6872,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  espree@7.3.1:
+  espree@6.2.1:
     dependencies:
       acorn: 7.4.1
       acorn-jsx: 5.3.2(acorn@7.4.1)
       eslint-visitor-keys: 1.3.0
 
-  espree@9.6.1:
+  espree@7.3.1:
     dependencies:
-      acorn: 8.14.0
-      acorn-jsx: 5.3.2(acorn@8.14.0)
-      eslint-visitor-keys: 3.4.3
+      acorn: 7.4.1
+      acorn-jsx: 5.3.2(acorn@7.4.1)
+      eslint-visitor-keys: 1.3.0
 
   esprima@4.0.1: {}
 
@@ -8410,8 +8402,6 @@ snapshots:
     dependencies:
       glob: 7.2.3
 
-  roboto-fontface@0.10.0: {}
-
   run-parallel@1.2.0:
     dependencies:
       queue-microtask: 1.2.3
@@ -8674,6 +8664,10 @@ snapshots:
 
   sprintf-js@1.0.3: {}
 
+  ssr-window@1.0.1: {}
+
+  ssr-window@2.0.0: {}
+
   ssri@8.0.1:
     dependencies:
       minipass: 3.3.6
@@ -8778,6 +8772,11 @@ snapshots:
       picocolors: 1.1.1
       stable: 0.1.8
 
+  swiper@4.5.1:
+    dependencies:
+      dom7: 2.1.5
+      ssr-window: 1.0.1
+
   table@6.9.0:
     dependencies:
       ajv: 8.17.1
@@ -8945,6 +8944,11 @@ snapshots:
 
   vary@1.1.2: {}
 
+  vue-awesome-swiper@3.1.3:
+    dependencies:
+      object-assign: 4.1.1
+      swiper: 4.5.1
+
   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)
@@ -8956,16 +8960,16 @@ snapshots:
       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):
+  vue-eslint-parser@7.11.0(eslint@7.32.0):
     dependencies:
       debug: 4.4.0
       eslint: 7.32.0
-      eslint-scope: 7.2.2
-      eslint-visitor-keys: 3.4.3
-      espree: 9.6.1
+      eslint-scope: 5.1.1
+      eslint-visitor-keys: 1.3.0
+      espree: 6.2.1
       esquery: 1.6.0
       lodash: 4.17.21
-      semver: 7.6.3
+      semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
 

+ 12 - 0
public/index.html

@@ -20,5 +20,17 @@
       width: 100%;
       overflow: hidden;
     }
+    * {
+      user-select: none;
+      -webkit-user-select: none;  /* Safari */
+      -moz-user-select: none;    /* Firefox */
+      -ms-user-select: none;    /* IE 10 and IE 11 */
+    }
+    img {
+      -webkit-user-drag: none;  /* Safari 和 Chrome */
+      -moz-user-drag: none;     /* Firefox */
+      -o-user-drag: none;       /* Opera */
+      user-drag: none;          /* 非标准 */
+    }
   </style>
 </html>

+ 4 - 0
src/api/glass.js

@@ -3,3 +3,7 @@ import http from '@/utils/request'
 export function uploadImg (data) {
   return http.upload('/upload', data)
 }
+
+export function selectGlasses (data) {
+  return http.post('/select', data)
+}

+ 0 - 0
src/assets/face/长形脸.jpg → src/assets/face/long.jpg


+ 0 - 0
src/assets/face/圆形脸.jpg → src/assets/face/round.jpg


+ 2 - 1
src/main.js

@@ -5,10 +5,11 @@ 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.config.devtools = process.env.NODE_ENV === 'development' // 启用 DevTools
+
 Vue.use(VModal, { dialog: true, dialogComponentName: 'VueDialog' })
 new Vue({
   router,

+ 56 - 54
src/utils/program.js

@@ -1,31 +1,32 @@
 const program = [
-  // {
-  //   face: '圆形脸',
-  //   tedian: '脸部线条偏圆润/可爱/有亲和力',
-  //   detail:'https://minio.citupro.com/dev/static/glass/detail/circledetail.jpg',
-  //   quanxing: [
-  //     {
-  //       type: '猫眼型',
-  //       des: '精致优雅',
-  //       img: 'https://minio.citupro.com/dev/static/glass/catshape.png'
-  //     },
-  //     {
-  //       type: '多边形',
-  //       des: '时尚休闲',
-  //       img:'https://minio.citupro.com/dev/static/glass/poly.png'
-  //     },
-  //     {
-  //       type: '长方形',
-  //       des: '商务正式',
-  //       img: 'https://minio.citupro.com/dev/static/glass/rectangleshape.png',
-  //     }
-  //   ],
-  //   yuanze: {
-  //     title: '对比原则(平衡原则)',
-  //     des: ['推荐有棱角的圈形','平衡脸部比例,打造面部立体感'],
-  //   },
-  //   tips: '半框或纤细的镜架为首选,视觉上可拉长脸部线条不宜选择小而圆的框型',
-  // },
+  {
+    value: 'round',
+    face: '圆形脸',
+    characteristic: '脸部线条偏圆润/可爱/有亲和力',
+    detail: 'https://minio.citupro.com/dev/static/glass/detail/circledetail.jpg',
+    items: [
+      {
+        type: '猫眼型',
+        des: '精致优雅',
+        img: 'https://minio.citupro.com/dev/static/glass/catshape.png'
+      },
+      {
+        type: '多边形',
+        des: '时尚休闲',
+        img: 'https://minio.citupro.com/dev/static/glass/poly.png'
+      },
+      {
+        type: '长方形',
+        des: '商务正式',
+        img: 'https://minio.citupro.com/dev/static/glass/rectangleshape.png'
+      }
+    ],
+    principle: {
+      title: '对比原则(平衡原则)',
+      des: ['推荐有棱角的圈形', '平衡脸部比例,打造面部立体感']
+    },
+    tips: '半框或纤细的镜架为首选,视觉上可拉长脸部线条不宜选择小而圆的框型'
+  },
   {
     value: 'oval',
     face: '鹅蛋脸',
@@ -84,33 +85,34 @@ const program = [
     },
     tips: '不宜选择大而方的框型'
   },
-  // {
-  //   face: '长形脸',
-  //   tedian: '轮廓明显/脸部立体 / 有距离感',
-  //   detail:'https://minio.citupro.com/dev/static/glass/detail/rectangledetail.jpg',
-  //   quanxing: [
-  //     {
-  //       type: '不规则',
-  //       des: '时尚百搭',
-  //       img: 'https://minio.citupro.com/dev/static/glass/unregular.png'
-  //     },
-  //     {
-  //       type: '潘托斯',
-  //       des: '文艺休闲',
-  //       img: 'https://minio.citupro.com/dev/static/glass/pentosshape.png'
-  //     },
-  //     {
-  //       type: '圆形',
-  //       des: '复古潮流',
-  //       img: 'https://minio.citupro.com/dev/static/glass/circle.png',
-  //     }
-  //   ],
-  //   yuanze: {
-  //     title: '对比原则(平衡原则)',
-  //     des: ['推荐有圆润的圈形','平衡脸部棱角,柔化面部线条'],
-  //   },
-  //   tips: '有一定高度的全框镜架为首选,视觉上可缩短脸部长度',
-  // },
+  {
+    value: 'long',
+    face: '长形脸',
+    characteristic: '轮廓明显/脸部立体 / 有距离感',
+    detail: 'https://minio.citupro.com/dev/static/glass/detail/rectangledetail.jpg',
+    items: [
+      {
+        type: '不规则',
+        des: '时尚百搭',
+        img: 'https://minio.citupro.com/dev/static/glass/unregular.png'
+      },
+      {
+        type: '潘托斯',
+        des: '文艺休闲',
+        img: 'https://minio.citupro.com/dev/static/glass/pentosshape.png'
+      },
+      {
+        type: '圆形',
+        des: '复古潮流',
+        img: 'https://minio.citupro.com/dev/static/glass/circle.png'
+      }
+    ],
+    principle: {
+      title: '对比原则(平衡原则)',
+      des: ['推荐有圆润的圈形', '平衡脸部棱角,柔化面部线条']
+    },
+    tips: '有一定高度的全框镜架为首选,视觉上可缩短脸部长度'
+  },
   {
     value: 'heart',
     face: '心形脸',

+ 9 - 35
src/utils/request.js

@@ -8,7 +8,7 @@ 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
+  timeout: 10000 // request timeout
 })
 
 // request interceptor
@@ -32,41 +32,17 @@ service.interceptors.request.use(
 // response interceptor
 // 请求返回之后的拦截器
 service.interceptors.response.use(
-  async response => {
+  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)
-    // }
+    console.log(res)
     return res
   },
   error => {
-    // console.error(error)
-    // error = error.message || error
-    return Promise.reject(error.response.data)
+    console.log('error', error)
+    if (error?.response?.status === 400) {
+      return Promise.reject(error.response.data.message)
+    }
+    return Promise.reject(error.message)
   }
 )
 
@@ -92,7 +68,6 @@ const http = {
   },
   formData (url, params) {
     return service.post(url, params, {
-      timeout: 600000,
       headers: {
         'Content-Type': 'application/x-www-form-urlencoded'
       }
@@ -100,7 +75,6 @@ const http = {
   },
   upload (url, params) {
     return service.post(url, params, {
-      timeout: 600000,
       headers: {
         'Content-Type': 'multipart/form-data'
       }
@@ -108,7 +82,7 @@ const http = {
   },
   download (url, params) {
     return service.post(url, params, {
-      timeout: 10000,
+      // timeout: 10000,
       headers: {
         'Content-Type': 'application/json'
       },

+ 44 - 6
src/views/Home.vue

@@ -1,14 +1,35 @@
 <template>
   <div class="home">
     <section>
-      <MCover>
+      <MCover :width="width">
         <template #center="{ active, target }">
-          <MCamera v-if="active && tab === 0" @reject="$event => handleReject($event, target)" @error="handleError" @photo="handleGet" @close="target(false)"></MCamera>
-          <MFeature v-if="active && tab === 1" :src="imgBase64" :items="face" @reTake="tab = 0"></MFeature>
+          <template v-if="active">
+            <MCamera
+              v-if="tab === 0"
+              @reject="$event => handleReject($event, target)"
+              @error="handleError"
+              @photo="handleGet"
+              @close="target(false)">
+            </MCamera>
+            <MFeature
+              v-if="tab === 1"
+              :items="face"
+              @reTake="tab = 0; width = '480px'"
+              @click:try="handleTry"
+              @error="handleError"
+            ></MFeature>
+            <MTry
+              v-if="tab === 2"
+              :src="imgBase64"
+              :item="tryItem"
+              @feature="tab = 1; width = '80%'"
+              @retake="tab = 0; width = '480px'"
+            ></MTry>
+          </template>
         </template>
       </MCover>
     </section>
-    <modal name="VueDialog" height="150" width="300">
+    <modal name="VueDialog" height="200" width="350">
       <div class="model">
         <div class="model-title">提示</div>
         <div class="model-content">{{ error }}</div>
@@ -24,18 +45,28 @@
 import MCover from './components/MCover'
 import MCamera from './components/MCamera'
 import MFeature from './components/MFeature'
+import MTry from './components/MTry'
 export default {
   name: 'home-view',
   components: {
     MCover,
     MCamera,
-    MFeature
+    MFeature,
+    MTry
+  },
+  provide: {
+    size: {
+      width: 480,
+      height: 640
+    }
   },
   data () {
     return {
       imgBase64: '',
       tab: 0,
-      error: ''
+      width: '480px',
+      error: '',
+      tryItem: {}
     }
   },
   methods: {
@@ -44,6 +75,7 @@ export default {
       this.$modal.show('VueDialog')
     },
     handleReject (error, target) {
+      this.width = '480px'
       this.tab = 0
       target && target(false)
       if (error) {
@@ -54,7 +86,13 @@ export default {
     handleGet (data, imgBase64) {
       this.imgBase64 = imgBase64
       this.face = data
+      this.width = '80%'
       this.tab = 1
+    },
+    handleTry (item) {
+      this.tryItem = item
+      this.width = '480px'
+      this.tab = 2
     }
   }
 }

+ 21 - 18
src/views/components/MCamera.vue

@@ -26,14 +26,17 @@
       </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> -->
     <v-overlay :value="loading" opacity="0">
+      <v-progress-linear
+        color="white"
+        indeterminate
+        rounded
+        height="10"
+        style="width: 200px;"
+      ></v-progress-linear>
+
+    </v-overlay>
+    <v-overlay :value="checkLoading" opacity="0">
       <div class="cover">
         <div class="cover-move"></div>
       </div>
@@ -51,17 +54,15 @@ export default {
       videoStream: null,
       animationId: null,
       src: '',
-      loading: false,
+      checkLoading: false,
+      loading: true,
       ready: false,
       faceShape: '',
       showImg: false,
-      detections: [],
-      size: {
-        width: 480,
-        height: 480
-      }
+      detections: []
     }
   },
+  inject: ['size'],
   mounted () {
     this.$nextTick(() => {
       this.resizeCanvas()
@@ -111,8 +112,6 @@ export default {
       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
@@ -157,7 +156,7 @@ export default {
       drawFrame()
     },
     async handleTake () {
-      this.loading = true
+      this.checkLoading = true
       const canvas = this.$refs.canvas
       // 使用 toDataURL 方法获取 Base64 图像数据
       const img = canvas.toDataURL('image/png') // 可以选择其他格式如 'image/jpeg'
@@ -170,11 +169,13 @@ export default {
         const query = new FormData()
         query.append('file', file)
         const { data } = await uploadImg(query)
+        debugger
         this.$emit('photo', data, img)
       } catch (error) {
-        this.loading = false
+        this.checkLoading = false
         this.showImg = false
-        this.$emit('error', error.message)
+        console.log(error)
+        this.$emit('error', error)
       }
     }
     // handleReTake () {
@@ -254,6 +255,7 @@ export default {
     bottom: 0;
     left: 0;
     font-size: 16px;
+    z-index: 99;
     .item {
       padding: 10px;
       border: 1px solid #999;
@@ -262,6 +264,7 @@ export default {
     }
   }
   .close {
+    z-index: 99;
     position: absolute;
     cursor: pointer;
     right: 10px;

+ 8 - 2
src/views/components/MCover.vue

@@ -4,7 +4,7 @@
       <img src="@/assets/logo.png" alt="">
       <div class="name">蔡司视界镜架试戴体验</div>
     </div>
-    <div class="home-cover" :class="{ active: active }">
+    <div class="home-cover" :class="{ active: active }" :style="`--width: ${width}`">
       <!-- <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>
@@ -45,6 +45,12 @@
 <script>
 export default {
   name: 'm-cover',
+  props: {
+    width: {
+      type: String,
+      default: '80%'
+    }
+  },
   data () {
     return {
       active: false
@@ -155,7 +161,7 @@ export default {
     }
     &.active {
       cursor: default;
-      width: 80%;
+      width: var(--width);
       height: 80%;
       border-radius: 10px;
     }

+ 20 - 10
src/views/components/MFeature.vue

@@ -1,23 +1,26 @@
 <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="text-center text-h3 text--indigo mb-5">检测结果</div>
     <div color="#26c6da" class="d-flex justify-space-around">
-      <v-card class="text-center pa-3 mr-3" v-for="face in items" :key="face.name">
+      <v-card class="text-center pa-3 card" hover v-for="face in items" :key="face.name" width="500" @click="handlePutOn(face)">
         <img class="faceType" :src="require(`@/assets/face/${face.name}.jpg`)" alt="">
         <div class="text-h5 text--indigo py-5">
           {{ faceType[face.name]?.face }}
           相似度 {{ face.similarity.toFixed(2) }}%
         </div>
         <div class="d-flex align-center justify-center">
-          <div v-for="item in faceType[face.name].items" :key="item.type" class="text--indigo pa-3">
-            <img :src="item.img" height="50">
+          <div v-for="item in faceType[face.name].items" :key="item.type" class="text--indigo pa-3" style="flex: 1">
+            <img :src="item.img" width="100%">
           </div>
         </div>
+        <div class="pa-3 d-flex align-center justify-center">
+          <v-btn text color="primary">试戴体验</v-btn>
+        </div>
       </v-card>
     </div>
 
     <div class="d-flex mt-3">
-      <button class="btn mr-3 _primary" @click="handlePutOn">眼镜试戴</button>
+      <!-- <button class="btn mr-3 _primary" @click="handlePutOn">眼镜试戴</button> -->
       <button class="btn" @click="handleReTake">重拍</button>
     </div>
   </div>
@@ -25,13 +28,10 @@
 
 <script>
 import programType from '@/utils/program'
+import { selectGlasses } from '@/api/glass'
 export default {
   name: 'm-feature',
   props: {
-    src: {
-      type: String,
-      default: ''
-    },
     items: {
       type: Array,
       default: () => []
@@ -50,7 +50,14 @@ export default {
     }
   },
   methods: {
-    handlePutOn () {},
+    async handlePutOn (item) {
+      try {
+        const { data } = await selectGlasses({ name: item.name })
+        this.$emit('click:try', { ...item, items: data })
+      } catch (error) {
+        this.$emit('error', error.message)
+      }
+    },
     handleReTake () {
       this.$emit('reTake')
     }
@@ -87,6 +94,9 @@ export default {
 .faceType {
   width: 300px;
 }
+.card {
+  cursor: pointer;
+}
 @media screen and (max-width: 600px) {
   .faceType {
     width: 150px;

+ 355 - 0
src/views/components/MTry.vue

@@ -0,0 +1,355 @@
+<template>
+  <div class="d-flex flex-column align-center" style="height: 100%;">
+
+    <div class="d-flex black align-center justify-center" style="height: 100%; position: relative; overflow: hidden;">
+      <div class="contentBox">
+        <v-card :width="size.width" :height="size.height" class="content" ref="content">
+          <!-- <div class="point" :style="`left: ${coordinate.left.x}px;top: ${coordinate.left.y}px`"></div>
+          <div class="point" :style="`left: ${coordinate.right.x}px;top: ${coordinate.right.y}px`"></div>
+          <div class="point" :style="`left: ${coordinate.bridge.x}px;top: ${coordinate.bridge.y}px`"></div> -->
+          <img
+            v-if="chooseItem"
+            :src="chooseItem.pic_url"
+            alt=""
+            class="cover noMove"
+            :class="{ 'cursor-pointer': canMove}"
+            ref="cover"
+            @touchstart="onTouchStart"
+            @touchend="onTouchEnd"
+            @mousedown="onTouchStart"
+            @mouseup="onTouchEnd"
+            :width="tryGlassesSize.width"
+            :style="`--deg: ${tryGlassesSize.deg}deg`"
+          >
+          <img class="noMove" :src="src" alt="" :width="size.width" :height="size.height">
+        </v-card>
+
+        <div class="contentBox-tool">
+          <v-btn
+            v-for="tool in tools"
+            :key="tool.icon"
+            class="ma-2"
+            fab
+            dark
+            small
+            @click="tool.handle()"
+          >
+            <v-icon dark>
+              {{ tool.icon }}
+            </v-icon>
+          </v-btn>
+        </div>
+      </div>
+      <v-card
+        v-if="!clear"
+        class="contentBox-desc"
+        color="#385F73"
+        dark
+        width="300"
+        height="200"
+      >
+        <v-card-title class="text-h5">
+          {{ chooseItem.frametype }}
+        </v-card-title>
+
+        <v-card-subtitle>{{ chooseItem.detail_info }}</v-card-subtitle>
+        <v-card-text>
+          <p>脸型: {{ faceType }}</p>
+          <p>品牌: {{ chooseItem.brand }}</p>
+          <p>材质: {{ chooseItem.material }}</p>
+        </v-card-text>
+      </v-card>
+      <div v-show="!clear" class="pt-3 example-3d">
+        <swiper class="swiper" ref="mySwiper" :options="swiperOption" @slideChange="onSlideChange">
+          <swiper-slide v-for="(val, index) in item.items" :key="val.id + '-' + index" class="pa-3 swiper-item" :class="{'active': index === active}">
+            <div class="item d-flex flex-column align-center justify-center">
+              <div class="d-flex align-center justify-center item-img">
+                <img :src="val.pic_url" alt="">
+              </div>
+            </div>
+          </swiper-slide>
+          <!-- <div class="swiper-pagination" slot="pagination"></div> -->
+        </swiper>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+// import { debounce } from 'lodash'
+import programType from '@/utils/program'
+import { swiper, swiperSlide } from 'vue-awesome-swiper'
+import 'swiper/dist/css/swiper.css'
+export default {
+  name: 'm-try',
+  components: {
+    swiper,
+    swiperSlide
+  },
+  props: {
+    src: {
+      type: String,
+      default: ''
+    },
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      clear: false,
+      active: 0,
+      canMove: false,
+      moveObj: {
+        startX: null,
+        startY: null,
+        left: null,
+        top: null
+      },
+      swiperOption: {
+        effect: 'coverflow',
+        grabCursor: true,
+        centeredSlides: true,
+        slidesPerView: 'auto',
+        coverflowEffect: {
+          rotate: 50,
+          stretch: 0,
+          depth: 100,
+          modifier: 1,
+          slideShadows: false
+        }
+      }
+    }
+  },
+  inject: ['size'],
+  computed: {
+    faceType () {
+      return programType.find(e => e.value === this.chooseItem.facetype).face
+    },
+    chooseItem () {
+      return this.item.items[this.active]
+    },
+    tryGlassesSize () {
+      const x1 = this.item.facial_points.cheek_left_3.x
+      const y1 = this.item.facial_points.cheek_left_3.y
+      const x2 = this.item.facial_points.cheek_right_3.x
+      const y2 = this.item.facial_points.cheek_right_3.y
+      return {
+        width: x2 - x1,
+        deg: this.calculateAngle(x1, y1, x2, y2),
+        center: this.item.facial_points.nose_bridge_1
+      }
+    },
+    tools () {
+      return [
+        {
+          icon: this.clear ? 'mdi-eye-outline' : 'mdi-eye-off-outline',
+          handle: this.onClear
+        },
+        {
+          icon: 'mdi-swap-horizontal',
+          handle: this.onChange
+        },
+        {
+          icon: 'mdi-reload',
+          handle: this.onReload
+        },
+        {
+          icon: 'mdi-camera-flip-outline',
+          handle: this.onRetake
+        }
+      ]
+    },
+    coordinate () {
+      return {
+        left: this.item.facial_points.cheek_left_3,
+        right: this.item.facial_points.cheek_right_3,
+        bridge: this.item.facial_points.nose_bridge_1
+      }
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.onReload()
+      this.$refs.content.$el.addEventListener('mousemove', this.onTouchMove)
+      this.$refs.content.$el.addEventListener('mouseleave', this.onTouchLeave)
+    })
+  },
+  beforeDestroy () {
+    this.$refs.content.$el.removeEventListener('mousemove', this.onTouchMove)
+    this.$refs.content.$el.removeEventListener('mouseleave', this.onTouchLeave)
+  },
+  methods: {
+    // 清屏
+    onClear () {
+      this.clear = !this.clear
+    },
+    // 切换脸型
+    onChange () {
+      this.$emit('feature')
+    },
+    // 重拍
+    onRetake () {
+      this.$emit('retake')
+    },
+    // 重置眼镜
+    onReload () {
+      this.$refs.cover.style.left = this.tryGlassesSize.center.x + 'px'
+      this.$refs.cover.style.top = this.tryGlassesSize.center.y + 'px'
+    },
+    calculateAngle (x1, y1, x2, y2) {
+    // 计算斜率
+      const slope = (y2 - y1) / (x2 - x1)
+
+      // 计算弧度
+      const radians = Math.atan(slope)
+
+      // 将弧度转换为度
+      const degrees = radians * (180 / Math.PI)
+
+      return degrees
+    },
+    onSlideChange () {
+      console.log(this.$refs.mySwiper.swiper)
+      this.active = this.$refs.mySwiper.swiper.activeIndex
+    },
+    onTouchMove (v) {
+      if (!this.canMove) {
+        return
+      }
+      // 计算点位移动距离
+      const moveX = v.clientX - this.moveObj.startX
+      const moveY = v.clientY - this.moveObj.startY
+      // 计算点位移动后的位置
+      const left = this.moveObj.left + moveX
+      const top = this.moveObj.top + moveY
+      // 更新点位位置
+      this.$refs.cover.style.left = left + 'px'
+      this.$refs.cover.style.top = top + 'px'
+      // console.log(left, top)
+    },
+    onTouchLeave () {
+      this.onTouchEnd()
+    },
+    onTouchStart (e) {
+      this.canMove = true
+      const cover = this.$refs.cover
+      const getContentBoundingClientRect = this.$refs.content.$el.getBoundingClientRect()
+      const getGlassBoundingClientRect = cover.getBoundingClientRect()
+      const clientHeight = cover.clientHeight
+      const clientWidth = cover.clientWidth
+      this.moveObj = {
+        startX: e.clientX,
+        startY: e.clientY,
+        left: getGlassBoundingClientRect.left - getContentBoundingClientRect.left + clientWidth / 2,
+        top: getGlassBoundingClientRect.top - getContentBoundingClientRect.top + clientHeight / 2
+      }
+      // console.log(this.moveObj)
+    },
+    onTouchEnd (e) {
+      this.canMove = false
+      Object.assign(this.moveObj, {
+        startX: null,
+        startY: null,
+        left: null,
+        top: null
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.full {
+  width: 100%;
+  height: 100%;
+}
+.item {
+  border-radius: 200px;
+  height: 200px;
+  width: 200px;
+  position: relative;
+  img {
+    width: 100%;
+  }
+}
+.example-3d {
+  // background: rgba(0,0,0,.5);
+  width: 100%;
+  height: 200px;
+  padding-top: 50px;
+  // padding-bottom: 50px;
+  // background: #FFF;
+  position: absolute;
+  bottom: 0;
+}
+
+.swiper {
+  height: 100%;
+  width: 100%;
+
+  .swiper-slide {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 200px;
+    height: 150px;
+    text-align: center;
+    font-weight: bold;
+    font-size: 14px;
+    background-position: center;
+    background-size: cover;
+    color: #FFF;
+    box-shadow: 0px 0px 8px 4px #888888;
+    translate: .5s;
+    &.active {
+      background: #FFF;
+      // width: 250px;
+      // height: 200px;
+    }
+  }
+}
+.noMove {
+  -webkit-user-drag: none;  /* Safari 和 Chrome */
+  -moz-user-drag: none;     /* Firefox */
+  -o-user-drag: none;       /* Opera */
+  user-drag: none;          /* 非标准 */
+}
+.content {
+  position: relative;
+  overflow: hidden;
+  .point, .cover {
+    position: absolute;
+  }
+  .point {
+    z-index: 1;
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    background-color: red;
+  }
+  .cover {
+    transform-origin: 50% 50%;
+    transform: translate(-50%, -50%) rotate(var(--deg));
+    cursor: pointer;
+  }
+}
+.cursor-pointer {
+  cursor: pointer;
+}
+
+.contentBox {
+  position: relative;
+  &-desc {
+    position: absolute;
+    left: 0;
+    top: 0;
+  }
+  &-tool {
+    width: 50px;
+    position: absolute;
+    top: 50%;
+    transform: translate(0, -50%);
+    right: 20px;
+  }
+}
+</style>

+ 0 - 1
vue.config.js

@@ -4,7 +4,6 @@ module.exports = defineConfig({
   assetsDir: 'static',
   productionSourceMap: process.env.NODE_ENV !== 'production',
   devServer: {
-    open: true,
     // host: 'localhost',
     port: 3005,
     hot: true