SyuanYu 1 год назад
Родитель
Сommit
13b8883bbb

+ 302 - 9
package-lock.json

@@ -8,6 +8,7 @@
       "name": "ntcri",
       "version": "0.0.0",
       "dependencies": {
+        "@googlemaps/js-api-loader": "^1.16.3",
         "@mdi/font": "^7.2.96",
         "@vuepic/vue-datepicker": "^5.4.0",
         "animate.css": "^4.1.1",
@@ -16,7 +17,7 @@
         "leaflet.markercluster": "^1.5.3",
         "moment": "^2.29.4",
         "pinia": "^2.1.4",
-        "swiper": "^10.1.0",
+        "swiper": "^10.3.1",
         "vue": "^3.2.47",
         "vue-i18n": "^9.2.2",
         "vue-router": "^4.2.2",
@@ -404,6 +405,15 @@
         "node": ">=12"
       }
     },
+    "node_modules/@googlemaps/js-api-loader": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.3.tgz",
+      "integrity": "sha512-JMN0UaEzAjK6h949WtgXwCm1Xxz+ikELTLRvHuP577ea9Wbx6fEn4Gv3+YHcm04JS4g1iz2tpm4Vw+7G8sUgSQ==",
+      "dependencies": {
+        "@rollup/plugin-terser": "^0.4.3",
+        "fast-deep-equal": "^3.1.3"
+      }
+    },
     "node_modules/@intlify/core-base": {
       "version": "9.2.2",
       "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
@@ -461,11 +471,58 @@
         "node": ">= 14"
       }
     },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "dependencies": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.22",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
+      "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
     "node_modules/@mdi/font": {
       "version": "7.2.96",
       "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.2.96.tgz",
@@ -477,6 +534,27 @@
       "integrity": "sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA==",
       "dev": true
     },
+    "node_modules/@rollup/plugin-terser": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+      "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
+      "dependencies": {
+        "serialize-javascript": "^6.0.1",
+        "smob": "^1.0.0",
+        "terser": "^5.17.4"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      },
+      "peerDependencies": {
+        "rollup": "^2.0.0||^3.0.0||^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "rollup": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@types/geojson": {
       "version": "7946.0.10",
       "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
@@ -627,6 +705,17 @@
         "vue": ">=3.2.0"
       }
     },
+    "node_modules/acorn": {
+      "version": "8.11.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/animate.css": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
@@ -681,6 +770,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+    },
     "node_modules/chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -719,6 +813,11 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+    },
     "node_modules/csstype": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
@@ -797,6 +896,11 @@
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
     "node_modules/fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -1091,6 +1195,14 @@
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
       "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
     },
+    "node_modules/randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dependencies": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1112,7 +1224,7 @@
       "version": "3.23.0",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.0.tgz",
       "integrity": "sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==",
-      "dev": true,
+      "devOptional": true,
       "bin": {
         "rollup": "dist/bin/rollup"
       },
@@ -1124,6 +1236,25 @@
         "fsevents": "~2.3.2"
       }
     },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
     "node_modules/sass": {
       "version": "1.63.3",
       "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.3.tgz",
@@ -1141,6 +1272,19 @@
         "node": ">=14.0.0"
       }
     },
+    "node_modules/serialize-javascript": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+      "dependencies": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "node_modules/smob": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
+      "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ=="
+    },
     "node_modules/source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -1157,10 +1301,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
     "node_modules/swiper": {
-      "version": "10.1.0",
-      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.1.0.tgz",
-      "integrity": "sha512-E+wh+hcSbwlRfXuwBTclcOOikOjNdSF0a2Sdg3J4cIWtHO64A7SaLRfezfrJ67CW3GEc15AduYU2YKlElsjqsQ==",
+      "version": "10.3.1",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.3.1.tgz",
+      "integrity": "sha512-24Wk3YUdZHxjc9faID97GTu6xnLNia+adMt6qMTZG/HgdSUt4fS0REsGUXJOgpTED0Amh/j+gRGQxsLayJUlBQ==",
       "funding": [
         {
           "type": "patreon",
@@ -1175,6 +1328,23 @@
         "node": ">= 4.7.0"
       }
     },
+    "node_modules/terser": {
+      "version": "5.27.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
+      "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1480,6 +1650,15 @@
       "dev": true,
       "optional": true
     },
+    "@googlemaps/js-api-loader": {
+      "version": "1.16.3",
+      "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.3.tgz",
+      "integrity": "sha512-JMN0UaEzAjK6h949WtgXwCm1Xxz+ikELTLRvHuP577ea9Wbx6fEn4Gv3+YHcm04JS4g1iz2tpm4Vw+7G8sUgSQ==",
+      "requires": {
+        "@rollup/plugin-terser": "^0.4.3",
+        "fast-deep-equal": "^3.1.3"
+      }
+    },
     "@intlify/core-base": {
       "version": "9.2.2",
       "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
@@ -1522,11 +1701,49 @@
         "@intlify/shared": "9.2.2"
       }
     },
+    "@jridgewell/gen-mapping": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+      "requires": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
+    "@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+    },
+    "@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+    },
+    "@jridgewell/source-map": {
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
+      "requires": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
       "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
+    "@jridgewell/trace-mapping": {
+      "version": "0.3.22",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
+      "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
+      "requires": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
     "@mdi/font": {
       "version": "7.2.96",
       "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.2.96.tgz",
@@ -1538,6 +1755,16 @@
       "integrity": "sha512-paR9M9ZT7rKbh2boksNUynuSZMHhqRYnEZOm/KrZTjQ4/FzyhjLHuvw/8XYzP+E7fS4+/Ms/82EN1pl/OFsiIA==",
       "dev": true
     },
+    "@rollup/plugin-terser": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+      "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
+      "requires": {
+        "serialize-javascript": "^6.0.1",
+        "smob": "^1.0.0",
+        "terser": "^5.17.4"
+      }
+    },
     "@types/geojson": {
       "version": "7946.0.10",
       "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
@@ -1673,6 +1900,11 @@
         "date-fns-tz": "^1.3.7"
       }
     },
+    "acorn": {
+      "version": "8.11.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg=="
+    },
     "animate.css": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
@@ -1718,6 +1950,11 @@
         "fill-range": "^7.0.1"
       }
     },
+    "buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+    },
     "chokidar": {
       "version": "3.5.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -1742,6 +1979,11 @@
         "delayed-stream": "~1.0.0"
       }
     },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+    },
     "csstype": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
@@ -1801,6 +2043,11 @@
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
     "fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -1968,6 +2215,14 @@
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
       "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
     },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
     "readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -1986,11 +2241,16 @@
       "version": "3.23.0",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.0.tgz",
       "integrity": "sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==",
-      "dev": true,
+      "devOptional": true,
       "requires": {
         "fsevents": "~2.3.2"
       }
     },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+    },
     "sass": {
       "version": "1.63.3",
       "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.3.tgz",
@@ -2002,6 +2262,19 @@
         "source-map-js": ">=0.6.2 <2.0.0"
       }
     },
+    "serialize-javascript": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+      "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+      "requires": {
+        "randombytes": "^2.1.0"
+      }
+    },
+    "smob": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz",
+      "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ=="
+    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -2012,10 +2285,30 @@
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
       "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
     },
+    "source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
     "swiper": {
-      "version": "10.1.0",
-      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.1.0.tgz",
-      "integrity": "sha512-E+wh+hcSbwlRfXuwBTclcOOikOjNdSF0a2Sdg3J4cIWtHO64A7SaLRfezfrJ67CW3GEc15AduYU2YKlElsjqsQ=="
+      "version": "10.3.1",
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-10.3.1.tgz",
+      "integrity": "sha512-24Wk3YUdZHxjc9faID97GTu6xnLNia+adMt6qMTZG/HgdSUt4fS0REsGUXJOgpTED0Amh/j+gRGQxsLayJUlBQ=="
+    },
+    "terser": {
+      "version": "5.27.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.1.tgz",
+      "integrity": "sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==",
+      "requires": {
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      }
     },
     "to-regex-range": {
       "version": "5.0.1",

+ 2 - 1
package.json

@@ -9,6 +9,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@googlemaps/js-api-loader": "^1.16.3",
     "@mdi/font": "^7.2.96",
     "@vuepic/vue-datepicker": "^5.4.0",
     "animate.css": "^4.1.1",
@@ -17,7 +18,7 @@
     "leaflet.markercluster": "^1.5.3",
     "moment": "^2.29.4",
     "pinia": "^2.1.4",
-    "swiper": "^10.1.0",
+    "swiper": "^10.3.1",
     "vue": "^3.2.47",
     "vue-i18n": "^9.2.2",
     "vue-router": "^4.2.2",

+ 17 - 6
src/assets/css/style.css

@@ -191,6 +191,7 @@ input:focus-visible {
 .college-bg-img {
   width: 100vw;
   height: 100%;
+  padding-top: 40px;
   background-image: url("@/assets/img/college-group/background.png");
   background-position: center;
   background-size: contain;
@@ -240,27 +241,27 @@ input:focus-visible {
     bottom: 25%;
   }
 }
-.college-banner .description-item h1 {
+.college-banner .description-item h2 {
   text-align: end;
-  margin-bottom: 2.1875em;
+  margin-bottom: 1em;
   font-size: 2.5em;
   font-weight: 500;
   text-shadow: 0.125em 0.125em 0.625em #333;
   word-wrap: break-word;
 }
 @media (max-width: 1280px) {
-  .college-banner .description-item h1 {
+  .college-banner .description-item h2 {
     margin-bottom: 1.25em;
     font-size: 2.25em;
   }
 }
 @media (max-width: 960px) {
-  .college-banner .description-item h1 {
+  .college-banner .description-item h2 {
     font-size: 2em;
   }
 }
 @media (max-width: 600px) {
-  .college-banner .description-item h1 {
+  .college-banner .description-item h2 {
     font-size: 1.5em;
     margin-bottom: 0.9375em;
   }
@@ -288,7 +289,7 @@ input:focus-visible {
     font-size: 0.875em;
   }
 }
-.college-banner .description-item h1,
+.college-banner .description-item h2,
 .college-banner .description-item p {
   color: #fff;
   letter-spacing: 0.0625em;
@@ -845,6 +846,16 @@ input:focus-visible {
   }
 }
 
+.consent-form {
+  max-width: 800px;
+  font-size: 1rem;
+  line-height: 2.2;
+}
+.consent-form h5 {
+  margin: 10px 0;
+  font-size: 1.125rem;
+}
+
 .back-link {
   display: block;
   text-align: center;

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/css/style.css.map


+ 15 - 3
src/assets/css/style.scss

@@ -202,6 +202,7 @@ input:focus-visible {
 .college-bg-img {
   width: 100vw;
   height: 100%;
+  padding-top: 40px;
   background-image: url("@/assets/img/college-group/background.png");
   background-position: center;
   background-size: contain;
@@ -246,9 +247,9 @@ input:focus-visible {
       bottom: 25%;
     }
 
-    h1 {
+    h2 {
       text-align: end;
-      margin-bottom: 2.1875em;
+      margin-bottom: 1em;
       font-size: 2.5em;
       font-weight: 500;
       text-shadow: 0.125em 0.125em 0.625em #333;
@@ -290,7 +291,7 @@ input:focus-visible {
       }
     }
 
-    h1,
+    h2,
     p {
       color: #fff;
       letter-spacing: 0.0625em;
@@ -945,6 +946,17 @@ input:focus-visible {
   }
 }
 
+.consent-form {
+  max-width: 800px;
+  font-size: 1rem;
+  line-height: 2.2;
+
+  h5 {
+    margin: 10px 0;
+    font-size: 1.125rem;
+  }
+}
+
 .back-link {
   display: block;
   text-align: center;

+ 18 - 14
src/components/Navbar.vue

@@ -48,10 +48,10 @@ function handleClick(url) {
 }
 
 const collegeList = reactive([
-  {
-    title: "college_group_1",
-    url: "/college-group/future",
-  },
+  // {
+  //   title: "college_group_1",
+  //   url: "/college-group/future",
+  // },
   {
     title: "college_group_2",
     url: "/college-group/craft",
@@ -60,17 +60,21 @@ const collegeList = reactive([
     title: "college_group_3",
     url: "/college-group/cross",
   },
+  // {
+  //   title: "college_group_4",
+  //   url: "/college-group/online",
+  // },
+  // {
+  //   title: "college_group_5",
+  //   url: "/college-group/craft-for-all",
+  // },
+  // {
+  //   title: "college_group_6",
+  //   url: "/college-group/life",
+  // },
   {
-    title: "college_group_4",
-    url: "/college-group/online",
-  },
-  {
-    title: "college_group_5",
-    url: "/college-group/craft-for-all",
-  },
-  {
-    title: "college_group_6",
-    url: "/college-group/life",
+    title: "成果展現",
+    url: "/achievement",
   },
   // {
   //   title: "修護工藝",

+ 7 - 1
src/router/index.js

@@ -5,6 +5,7 @@ import { useMainStore } from "@/stores/store";
 const Home = defineAsyncComponent(() => import('@/views/Home.vue'));
 const Login = defineAsyncComponent(() => import('@/views/Login.vue'));
 const Crafts = defineAsyncComponent(() => import('@/views/Crafts.vue'));
+const Achievement = defineAsyncComponent(() => import('@/views/Achievement.vue'));
 const News = defineAsyncComponent(() => import('@/views/News.vue'));
 const NewsDetail = defineAsyncComponent(() => import('@/views/NewsDetail.vue'));
 // const Article = defineAsyncComponent(() => import('@/views/Article.vue'));
@@ -246,7 +247,7 @@ const routes = [
         },
       },
       {
-        path: 'life/craftJourney',
+        path: 'life/craft-journey',
         name: 'CraftJourney',
         component: CraftJourney,
         meta: {
@@ -270,6 +271,11 @@ const routes = [
     name: 'Proposal',
     component: Proposal
   },
+  {
+    path: '/achievement',
+    name: 'Achievement',
+    component: Achievement,
+  },
   {
     path: '/news',
     name: 'News',

+ 642 - 0
src/views/Achievement.vue

@@ -0,0 +1,642 @@
+<script setup>
+import { ref, reactive, onMounted, watch } from "vue";
+import { useMainStore } from "@/stores/store";
+import { useI18n } from "vue-i18n";
+import axios from "axios";
+import moment from "moment";
+import Navbar from "@/components/Navbar.vue";
+import CourseCard from "@/components/CourseCard.vue";
+import ArticleCard from "@/components/ArticleCard.vue";
+
+const { t } = useI18n();
+const store = useMainStore();
+
+// // 所有文章
+// let articles = reactive({
+//   list: [],
+// });
+
+// (async () => {
+//   try {
+//     const response = await axios.get(
+//       `${store.apiUrl}/api/get_article?group_id=3`
+//     );
+//     console.log("response", response);
+//     articles.list = response.data.articles;
+//   } catch (error) {
+//     console.error(error);
+//   }
+// })();
+
+const breadcrumbs = reactive([
+  {
+    title: "home.title",
+    disabled: false,
+    href: "/",
+  },
+  {
+    title: "navbar.craft_groups",
+    disabled: true,
+  },
+  {
+    title: "成果展現",
+    disabled: true,
+  },
+]);
+
+/* 希望工程 Start */
+
+onMounted(() => {
+  store.getFavoriteClass();
+});
+
+const faqList = [
+  {
+    q: "問:社區關懷據點(或其他弱勢族群)想導入工藝手作陪伴,應如何做?",
+    a: "答:原有執行之其他課程,經評估可部分以工藝手作取代,確認日期及時段,可洽 049-2334141 分機 237 楊小姐,媒合就地或鄰近工藝師前往教學。",
+  },
+  {
+    q: "問:職能治療師若想加入「臺灣綠工藝希望工程」工藝手作陪伴,應如何參與?",
+    a: "答:請與 049-2334141 分機 237 楊小姐聯繫,將會媒合就地或鄰近有教學需求之單位或團體。",
+  },
+  {
+    q: "問:工藝手作陪伴有什麼好處?",
+    a: "答:工藝手作活動可以達到認知上的訓練,最主要是因為工藝可提供豐富的感官刺激,透過創作的想像力、創意,以及作品自我表達能力的滿足感,在認知上的訓練就非常有效果。在運用手部動作過程中,可刺激活化腦部功能,延緩失智失能。在社交人際互動過程中,在製作工藝品中,成員間互相協助幫忙,形成良好互動,甚至是在完成作品與其他的好朋友互相分享。",
+  },
+  {
+    q: "問:想要投入弱勢族群教學的工藝師,有限定其工藝種類嗎?",
+    a: "答:無限制工藝種類,但工藝師規劃手作時以安全性高、操作不複雜為考量。",
+  },
+  {
+    q: "問:工藝師應規劃幾堂的課程較為適合?",
+    a: "答:以每週執行一次,每次 2 小時,持續 6 至 8 週為宜,若對於未曾執行過工藝手作之族群,可先安排體驗式課程,再漸進式導入,惟仍依各單位需求彈性調整。",
+  },
+  {
+    q: "問:工藝師若想加入「臺灣綠工藝希望工程」工藝手作陪伴,應如何參與?",
+    a: "答:請與 049-2334141 分機 237 楊小姐聯繫,請規劃並提供適合的課程,將會媒合就地或鄰近有教學需求之單位或團體。",
+  },
+];
+
+// 活動現場
+let activityPageNum = ref(1); // 頁數(預設第一頁)
+let activityPageAmount = ref(12); // 每頁顯示筆數
+let activityTotalPages = ref(1); // 總頁數
+
+// 媒體報導
+let mediaPageNum = ref(1); // 頁數(預設第一頁)
+let mediaPageAmount = ref(12); // 每頁顯示筆數
+let mediaTotalPages = ref(1); // 總頁數
+
+let loading = ref(false);
+const activityList = ref(null);
+const mediaList = ref(null);
+
+watch(activityPageNum, () => {
+  getClasses();
+  activityList.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+watch(mediaPageNum, () => {
+  getTgcReport();
+  mediaList.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+const tgcImg = reactive({
+  classes: [],
+});
+
+// 獲取活動現場資料
+async function getClasses() {
+  loading.value = true;
+  let url = `${store.apiUrl}/api/get_tgc_img?page_num=${activityPageNum.value}&page_amount=${activityPageAmount.value}`;
+
+  try {
+    const response = await axios.get(url);
+    tgcImg.classes = response.data.tgc_pic;
+    activityTotalPages.value = store.getTotalPages(response.data.total_num, 12);
+    console.log(response);
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getClasses();
+
+const tgcReport = reactive({
+  classes: [],
+});
+
+// 獲取媒體報導資料
+async function getTgcReport() {
+  loading.value = true;
+  let url = `${store.apiUrl}/api/get_tgc_report?page_num=${mediaPageNum.value}&page_amount=${mediaPageAmount.value}`;
+
+  try {
+    const response = await axios.get(url);
+    tgcReport.classes = response.data.tgc_report;
+    mediaTotalPages.value = store.getTotalPages(response.data.total_num, 12);
+
+    console.log("report", response);
+  } catch (error) {
+    loading.value = false;
+    console.error(error);
+  }
+}
+
+getTgcReport();
+
+// 媒體報導
+let coursePageNum = ref(1); // 頁數(預設第一頁)
+let coursePageAmount = ref(6); // 每頁顯示筆數
+let courseTotalPages = ref(1); // 總頁數
+let courseLoading = ref(false);
+let courseList = ref(null);
+
+const courseData = reactive({
+  classes: [],
+});
+
+// 切換分頁時回到列表上方
+watch(coursePageNum, () => {
+  getClass();
+  courseList.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+// group_sort 希望工程
+async function getClass() {
+  courseLoading.value = true;
+
+  try {
+    const response = await axios.get(
+      `${store.apiUrl}/api/get_class_name?group_id=9&is_check=1&page_num=${coursePageNum.value}&page_amount=${coursePageAmount.value}`
+    );
+    courseTotalPages.value = store.getTotalPages(response.data.total_num, 6);
+    courseData.classes = response.data.classes;
+    courseLoading.value = false;
+    console.log("getClass 希望工程", courseData.classes);
+  } catch (error) {
+    courseLoading.value = false;
+    console.error(error);
+  }
+}
+
+getClass();
+/* 希望工程 End */
+</script>
+
+<template>
+  <div class="college-bg-img">
+    <Navbar />
+    <v-container fluid class="college-content pb-16 px-lg-0">
+      <div class="college-banner">
+        <img
+          class="d-none d-md-block"
+          src="@/assets/img/college-group/banner.png"
+          alt="臺灣工藝學習平台"
+        />
+        <img
+          class="d-block d-md-none"
+          src="@/assets/img/college-group/banner-mb.webp"
+          alt="臺灣工藝學習平台"
+        />
+        <div class="description-item">
+          <h2>成果展現</h2>
+          <!-- <p>
+            {{ groupDescribe }}
+          </p> -->
+        </div>
+      </div>
+      <div class="main-block">
+        <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 pa-0">
+          <template v-slot:title="{ item }">
+            {{ t(item.title) }}
+          </template>
+        </v-breadcrumbs>
+
+        <!-- 生活工藝 -->
+        <v-container class="pa-0 life-list">
+          <router-link to="/college-group/life/shop">
+            <v-row>
+              <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
+                <img
+                  src="@/assets/img/college-group/life/cover-01.png"
+                  alt="臺灣工藝學習平台"
+                />
+              </v-col>
+              <v-col cols="12" md="8" lg="7" class="content">
+                <h3 class="mb-5">旅物 SHOP</h3>
+                <p>
+                  《旅物 •
+                  SHOP》位於自然生態豐富的臺灣工藝文化園區,明亮穿透的空間設計,連結窗外工藝植物園,到室內的生活工藝選物,這是一間由土地孕育而生的工藝選品店,從採擷、手作,到用物於生活,是材料與手、物與物、人與物、人與人彼此流動交會,共度美好日常的店頭。包含崇尚自然循環,創造生活風格的臺灣在地「臺灣綠工藝」品牌、扶植青年創業的「青年孵化器」、工藝中心開發的周邊商品「品工藝」以及在地咖啡品牌「一日咖啡」等四個子品牌,推廣「自然、循環、平衡、寬容、生命力」的綠工藝精神,更是鼓勵青年實驗開店、青年工藝教育者的孵化平臺。
+                </p>
+              </v-col>
+            </v-row>
+          </router-link>
+
+          <router-link to="/college-group/life/craft-journey">
+            <v-row>
+              <v-col cols="12" md="8" lg="7" class="content">
+                <h3 class="mb-10">工藝行旅</h3>
+                <p>
+                  工藝文化深度旅遊,品味在地、標記生活 <br />
+                  打造以工藝為核心的文化深度旅遊路徑,結合工藝體驗、自然風土、地方美食、在地物產、
+                  建築等觀光資源,旅人緩步慢行,暫離喧囂、以手作沉澱自我,發現生命中的寧靜與感動。
+                  現在一起出發,感受工藝行旅的魅力和樂趣!
+                </p>
+              </v-col>
+              <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
+                <img
+                  src="@/assets/img/college-group/life/cover-02.png"
+                  alt="臺灣工藝學習平台"
+                />
+              </v-col>
+            </v-row>
+          </router-link>
+
+          <router-link to="/college-group/life/apprentice/about">
+            <v-row>
+              <v-col cols="12" md="8" lg="7" class="content">
+                <h3 class="mb-10">一日學徒</h3>
+                <p>
+                  全民工藝教育倡議平台|生活 x 手作 x 體驗 <br />
+                  以「手做」,經歷生活中的一切美好! <br />
+                  用「一日」,體會過去師徒制三年四個月的工藝學習精神與態度!
+                  <br />
+                  由工藝教育者親自規畫各類手作工藝課程,隨時提供最新課程資訊,民眾依據喜好選擇課程,
+                  體驗半天或一天的手作工藝學習,尋找就近的工藝教育者學習各種小物知識經驗與技藝。
+                </p>
+              </v-col>
+              <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
+                <img
+                  src="@/assets/img/college-group/life/cover-03.png"
+                  alt="臺灣工藝學習平台"
+                />
+              </v-col>
+            </v-row>
+          </router-link>
+
+          <router-link to="/college-group/life/campus">
+            <v-row>
+              <v-col cols="12" md="4" lg="5" class="pa-0 overflow-hidden">
+                <img
+                  src="@/assets/img/college-group/life/cover-04.png"
+                  alt="臺灣工藝學習平台"
+                />
+              </v-col>
+              <v-col cols="12" md="8" lg="7" class="content">
+                <h3 class="mb-10">校園扎根</h3>
+                <p>
+                  工藝校園扎根計畫以工藝融入學校教育,媒合教師與工藝教育者,跨域合作開發具創造力、觀察力、美學素養、批判思考以及手作體驗的工藝教材教案,實踐工藝之於學習、生活的價值。符合教育部
+                  12 年國教課綱的「核心素養」,以及 STEAM
+                  教育的五大精神「跨領域、動手做、生活應用、解決問題及五感學習」,以「做中學,學中做」培育新世代的工藝人才。
+                </p>
+              </v-col>
+            </v-row>
+          </router-link>
+        </v-container>
+
+        <!-- 希望工程 -->
+        <div class="cfa-content">
+          <div class="title">
+            <h2>{{ t("about_craft_for_all") }}</h2>
+          </div>
+
+          <v-row>
+            <v-col cols="12" md="6">
+              <p class="mb-5">
+                以工藝為媒介,實踐社會價值功能。臺灣工藝學習平台啟動「臺灣綠工藝希望工程」,本於自然、循環、平衡、寬容、生命力等綠工藝精神,融合人文關懷,深化工藝與社會的互動連結,為所有人帶來「希望‧療癒‧陪伴」的力量。
+              </p>
+              <p>
+                「臺灣綠工藝希望工程」是公民互助精神的體現,以「One Community,
+                One
+                Craft(OCOC)」一社群一工藝理念,凝聚社會力量,以工藝師、職能治療師提供核心支持服務,以社區、NGO團體、相關民間組織及政府單位為平台,在社福體系中的醫療機構、長照機構與社區照顧關懷據點等,向社會上包含樂齡長者、兒童、婦女、身心障礙者等所有人,傳達工藝手作的溫暖。以工藝投入社會服務,引領身心靈向上提升,也形成一股陪伴的安定力量,促進社會關係的融合與共好。
+              </p>
+            </v-col>
+            <v-col cols="12" md="6">
+              <div class="about-img">
+                <img
+                  class="img-fluid"
+                  src="@/assets/img/college-group/life/生活工藝素材-24.png"
+                  alt="臺灣工藝學習平台"
+                />
+              </div>
+            </v-col>
+          </v-row>
+
+          <div class="title" ref="activityList">
+            <h2>{{ t("event") }}</h2>
+          </div>
+
+          <v-row>
+            <v-col
+              sm="6"
+              md="4"
+              cols="12"
+              class="pa-5"
+              v-for="(item, index) in tgcImg.classes"
+              :key="index"
+            >
+              <div class="main-card">
+                <div class="card-info">
+                  <div class="overflow-hidden">
+                    <v-img
+                      class="mx-auto cover-img"
+                      :lazy-src="item.pic_url"
+                      :src="item.pic_url"
+                      height="16.875em"
+                      cover
+                      alt="臺灣工藝學習平台"
+                    >
+                      <template v-slot:placeholder>
+                        <div
+                          class="d-flex align-center justify-center fill-height"
+                        >
+                          <v-progress-circular
+                            color="grey-lighten-4"
+                            indeterminate
+                          ></v-progress-circular>
+                        </div>
+                      </template>
+                    </v-img>
+                  </div>
+
+                  <div class="card-title">
+                    <h3>{{ item.pic_name }}</h3>
+                  </div>
+                </div>
+              </div>
+            </v-col>
+          </v-row>
+
+          <v-pagination
+            v-model="activityPageNum"
+            :length="activityTotalPages"
+            rounded="circle"
+            class="mt-16"
+          ></v-pagination>
+
+          <div class="title" ref="mediaList">
+            <h2>{{ t("media_coverage") }}</h2>
+          </div>
+
+          <ul class="tgc-report">
+            <li
+              class="d-flex align-center justify-space-between"
+              v-for="(item, index) in tgcReport.classes"
+              :key="index"
+            >
+              <div class="info">
+                <a :href="item.url" target="_blank">
+                  <p class="report-title font-weight-normal">
+                    {{ item.title }}
+                  </p>
+                </a>
+              </div>
+              <div class="date time-block">
+                <span>
+                  {{ moment(`${item.create_time}`).format("YYYY-MM-DD") }}</span
+                >
+              </div>
+            </li>
+          </ul>
+          <v-pagination
+            v-model="mediaPageNum"
+            :length="mediaTotalPages"
+            rounded="circle"
+            class="mt-16"
+          ></v-pagination>
+
+          <div>
+            <h2 class="title">{{ t("faq") }}</h2>
+          </div>
+
+          <v-expansion-panels>
+            <v-expansion-panel v-for="item in faqList" :key="item">
+              <v-expansion-panel-title>{{ item.q }}</v-expansion-panel-title>
+              <v-expansion-panel-text v-html="item.a"> </v-expansion-panel-text>
+            </v-expansion-panel>
+          </v-expansion-panels>
+
+          <div ref="courseList">
+            <h2 class="title">{{ t("craft_for_all") }}</h2>
+          </div>
+
+          <div class="d-flex justify-center mb-10" v-if="courseLoading">
+            <v-progress-circular
+              color="grey-lighten-4"
+              indeterminate
+            ></v-progress-circular>
+          </div>
+
+          <v-row v-else>
+            <v-col
+              sm="6"
+              lg="4"
+              cols="12"
+              v-for="(item, index) in courseData.classes"
+              :key="index"
+              class="pa-5"
+            >
+              <CourseCard :data="item" />
+            </v-col>
+          </v-row>
+
+          <v-pagination
+            v-model="coursePageNum"
+            :length="courseTotalPages"
+            rounded="circle"
+            class="mt-16"
+          ></v-pagination>
+        </div>
+      </div>
+    </v-container>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+p {
+  line-height: 1.75em;
+  letter-spacing: 0.0625em;
+}
+.navbar {
+  margin-top: 0;
+}
+
+.description-item {
+  h2 {
+    margin-bottom: 1.5em;
+  }
+}
+
+.content {
+  padding: 0;
+  width: 90%;
+
+  @media (max-width: 600px) {
+    width: 85%;
+  }
+  .main-block {
+    padding: 6.25em 5em;
+    margin-top: -30%;
+    background-color: #fff;
+
+    @media (max-width: 960px) {
+      padding: 6.25em 3.125em;
+    }
+
+    @media (max-width: 600px) {
+      padding: 6.25em 1.25em;
+    }
+
+    h2 {
+      font-size: 1.875em;
+      font-weight: 500;
+      line-height: 2em;
+      margin-left: 0.625em;
+
+      @media (max-width: 960px) {
+        font-size: 1.5em;
+      }
+
+      @media (max-width: 600px) {
+        margin-left: 0;
+        margin-bottom: 3.125em;
+      }
+    }
+    .title {
+      margin: 5em 0;
+      @media (max-width: 600px) {
+        margin: 3.125em 0;
+      }
+    }
+    .v-breadcrumbs {
+      position: relative;
+      z-index: 100;
+      justify-content: flex-start;
+      @media (max-width: 600px) {
+        justify-content: center;
+      }
+    }
+  }
+}
+
+/* 生活工藝 */
+.life-list {
+  margin-top: 6.25em;
+
+  @media (max-width: 600px) {
+    margin-top: 0;
+  }
+
+  a {
+    display: block;
+    margin: 3.125em 0;
+    border: 0.125em solid var(--purple);
+    border-radius: 1.25em;
+    overflow: hidden;
+    &:hover {
+      img {
+        transform: scale(1.1);
+      }
+    }
+  }
+
+  h3 {
+    font-size: 1.5em;
+    font-weight: 500;
+  }
+
+  img {
+    width: 100%;
+    height: 20.625em;
+    min-height: 100%;
+    object-fit: cover;
+    transition: all 0.5s;
+    @media (max-width: 1280px) {
+      height: 23.125em;
+    }
+    @media (max-width: 600px) {
+      height: 12.5em;
+    }
+  }
+
+  .content {
+    padding: 2.1875em 3.75em;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    @media (max-width: 1280px) {
+      padding: 2.5em;
+    }
+  }
+}
+
+/* 希望工程 */
+.cfa-content {
+  .about-img {
+    width: 80%;
+    height: auto;
+    margin: auto;
+  }
+
+  .card-info {
+    height: 100%;
+    padding-bottom: 0;
+    .card-title {
+      margin: auto;
+      border-bottom: none;
+      cursor: pointer;
+      h3 {
+        font-weight: 500;
+      }
+    }
+  }
+
+  p {
+    font-weight: 400;
+    letter-spacing: 0.0625em;
+    line-height: 1.875em;
+  }
+
+  .tgc-report {
+    li {
+      padding: 1.875em 0;
+      border-bottom: 0.0625em solid #c9c9c9;
+      .info {
+        a {
+          transition: all 0.3s;
+          &:hover {
+            opacity: 0.7;
+          }
+        }
+      }
+    }
+    .report-title {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      @media (max-width: 960px) {
+        white-space: normal;
+      }
+    }
+  }
+  .time-block {
+    padding: 0 0 0 1.25em;
+    span {
+      width: 5.625em;
+      display: block;
+    }
+  }
+  .v-expansion-panel-title,
+  .v-expansion-panel-text {
+    font-size: 1em;
+    line-height: 1.875em;
+    letter-spacing: 0.0625em;
+  }
+  .v-expansion-panel-text {
+    padding: 0.9375em 1.25em;
+    font-size: 1em;
+  }
+}
+</style>

+ 12 - 14
src/views/CollegeGroup/Craft.vue

@@ -152,7 +152,6 @@ const articles = reactive({
 });
 
 (async () => {
-  // let url = `${store.apiUrl}/api/get_group_classes_and_articles?group_id=6`;
   let classUrl = `${store.apiUrl}/api/get_class_name?group_id=6`;
   let articlesUrl = `${store.apiUrl}/api/get_article?group_id=6`;
 
@@ -259,7 +258,7 @@ const articles = reactive({
   </div>
 
   <ul class="course-list">
-    <li v-for="(item, index) in classes.list" :key="index" class="mb-10">
+    <li v-for="(item, index) in classes.list" :key="index">
       <v-card variant="outlined" class="d-flex flex-column align-center pa-0">
         <h2 class="text-center ma-0 pa-3">{{ item.name }}</h2>
         <v-row class="justify-center align-center content">
@@ -322,9 +321,9 @@ const articles = reactive({
     </v-col>
   </v-row> -->
 
-  <h2 class="title" id="repairCourse">{{ t("craft_restoration") }}</h2>
+  <!-- <h2 class="title" id="repairCourse">{{ t("craft_restoration") }}</h2> -->
 
-  <v-row>
+  <!-- <v-row>
     <v-col cols="12" md="6">
       <h3 class="text-center mb-5">國家工藝檢測修護平臺</h3>
       <div class="video">
@@ -353,9 +352,9 @@ const articles = reactive({
         ></iframe>
       </div>
     </v-col>
-  </v-row>
+  </v-row> -->
 
-  <v-row class="mt-16 article-block">
+  <!-- <v-row class="mt-16 article-block">
     <v-col cols="12" sm="7" md="8">
       <h4 class="mb-5">首創工藝聯合醫院,診斷修復再現舊物新生命</h4>
       <p>
@@ -382,15 +381,15 @@ const articles = reactive({
         >
       </span>
     </v-col>
-  </v-row>
+  </v-row> -->
 
   <!-- <div class="d-flex justify-end">
     <button class="past-btn">觀看已過期課程</button>
   </div> -->
 
-  <h2 class="title">{{ t("craft_restorer") }}</h2>
+  <!-- <h2 class="title">{{ t("craft_restorer") }}</h2> -->
 
-  <v-row class="master-list">
+  <!-- <v-row class="master-list">
     <v-col
       cols="12"
       md="6"
@@ -429,15 +428,15 @@ const articles = reactive({
         </ul>
       </section>
     </v-col>
-  </v-row>
+  </v-row> -->
 
-  <h2 class="title" id="repairArticle">{{ t("restoration_story") }}</h2>
+  <!-- <h2 class="title" id="repairArticle">{{ t("restoration_story") }}</h2>
 
   <ul class="story-list">
     <li v-for="(item, index) in articles.list" :key="index" class="mb-16">
       <ArticleCard :data="item" type="article" />
     </li>
-  </ul>
+  </ul> -->
 </template>
 
 <style lang="scss" scoped>
@@ -512,8 +511,7 @@ p {
   }
 }
 
-.course-list,
-.story-list {
+.course-list {
   img {
     width: 100%;
     max-width: 18.75em;

+ 1 - 1
src/views/CollegeGroup/Life.vue

@@ -67,7 +67,7 @@ const breadcrumbs = reactive([
       </v-row>
     </router-link>
 
-    <router-link to="/college-group/life/craftJourney">
+    <router-link to="/college-group/life/craft-journey">
       <v-row>
         <v-col cols="12" md="8" lg="7" class="content">
           <h3 class="mb-10">工藝行旅</h3>

+ 7 - 2
src/views/CollegeGroup/Life/Apprentice/Main.vue

@@ -11,10 +11,15 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "college_group_6",
-    href: "/college-group/life",
+    title: "成果展現",
+    href: "/achievement",
     disabled: false,
   },
+  // {
+  //   title: "college_group_6",
+  //   href: "/college-group/life",
+  //   disabled: false,
+  // },
   {
     title: "一日學徒",
     disabled: true,

+ 14 - 2
src/views/CollegeGroup/Life/Campus.vue

@@ -10,6 +10,7 @@ const store = useMainStore();
 let campus = reactive({
   list: [],
 });
+let loading = ref(true);
 
 (async () => {
   try {
@@ -21,6 +22,7 @@ let campus = reactive({
       (e) => e.group_sort === "校園扎根"
     );
 
+    loading.value = false;
     console.log("campus.list", campus.list);
   } catch (error) {
     console.error(error);
@@ -34,8 +36,8 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "college_group_6",
-    href: "/college-group/life",
+    title: "成果展現",
+    href: "/achievement",
     disabled: false,
   },
   {
@@ -67,10 +69,20 @@ const breadcrumbs = reactive([
       </p>
     </v-col>
 
+    <v-col cols="12" v-if="loading">
+      <div class="d-flex justify-center my-10">
+        <v-progress-circular
+          color="grey-lighten-4"
+          indeterminate
+        ></v-progress-circular>
+      </div>
+    </v-col>
+
     <v-col
       cols="12"
       sm="6"
       md="4"
+      v-else
       v-for="item in campus.list"
       :key="item"
       class="py-5 info"

+ 2 - 2
src/views/CollegeGroup/Life/CraftJourney.vue

@@ -32,8 +32,8 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "college_group_6",
-    href: "/college-group/life",
+    title: "成果展現",
+    href: "/achievement",
     disabled: false,
   },
   {

+ 8 - 4
src/views/CollegeGroup/Life/Shop.vue

@@ -14,10 +14,15 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "college_group_6",
-    href: "/college-group/life",
+    title: "成果展現",
+    href: "/achievement",
     disabled: false,
   },
+  // {
+  //   title: "college_group_6",
+  //   href: "/college-group/life",
+  //   disabled: false,
+  // },
   {
     title: "旅物 SHOP",
     disabled: true,
@@ -150,8 +155,7 @@ const shopImgs = [
           <li>營業時間:週二至週日 09:00-20:00</li>
           <li>週一休(逢國定假日及連續假期照常開館)</li>
           <li>
-            地點:臺灣工藝學習平台
-            生活工藝館一樓(南投縣草屯鎮中正路573號)
+            地點:臺灣工藝學習平台 生活工藝館一樓(南投縣草屯鎮中正路573號)
           </li>
         </ul>
 

+ 5 - 5
src/views/CollegeGroup/Main.vue

@@ -112,15 +112,11 @@ async function getGroup(id) {
           alt="臺灣工藝學習平台"
         />
         <div class="description-item">
-          <h1>{{ groupName }}</h1>
+          <h2>{{ groupName }}</h2>
           <p>
             {{ groupDescribe }}
           </p>
         </div>
-        <!-- <h1>{{ title }}</h1>
-        <p class="description-item">
-          {{ description }}
-        </p> -->
       </div>
       <div class="main-block" :class="{ life: isLifeChildren }">
         <router-view></router-view>
@@ -130,6 +126,10 @@ async function getGroup(id) {
 </template>
 
 <style lang="scss" scoped>
+.navbar {
+  margin-top: 0;
+}
+
 .content {
   padding: 0;
   width: 90%;

+ 74 - 117
src/views/CollegeGroup/Online.vue

@@ -13,7 +13,7 @@ onMounted(() => {
   store.getFavoriteClass();
 });
 
-const breadcrumbs = reactive([
+const onlineBreadcrumbs = reactive([
   {
     title: "home.title",
     disabled: false,
@@ -29,81 +29,58 @@ const breadcrumbs = reactive([
   },
 ]);
 
-// const categoryList = reactive([
-//   {
-//     id: 1256,
-//     title: "匠作之手",
-//     active: true,
-//   },
-//   {
-//     id: 1255,
-//     title: "手作學習",
-//     active: false,
-//   },
-//   {
-//     id: 1221,
-//     title: "工藝行旅",
-//     active: false,
-//   },
-//   {
-//     id: 1253,
-//     title: "檢測修復",
-//     active: false,
-//   },
-// ]);
-
-let loading = ref(false);
-let pageNum = ref(1); // 頁數(預設第一頁)
-let pageAmount = ref(18); // 每頁顯示筆數
-let totalPages = ref(1); // 總頁數
-let selectCategory = ref([]); // 選擇類別
-let isFilter = ref(false); // 篩選狀態
-let searchList = reactive([]); // 篩選條件
-let totalNum = ref(0); // 資料總筆數
-
-const courese = reactive({
+let loadingOnline = ref(false);
+let pageNumOnline = ref(1); // 頁數(預設第一頁)
+let pageAmountOnline = ref(18); // 每頁顯示筆數
+let totalPagesOnline = ref(1); // 總頁數
+let selectOnlineCategory = ref([]); // 選擇類別
+let isOnlineFilter = ref(false); // 篩選狀態
+let searchOnlineList = reactive([]); // 篩選條件
+let totalNumOnline = ref(0); // 資料總筆數
+
+const onlineCourese = reactive({
   list: [],
 });
 
-async function getClass() {
-  loading.value = true;
-  let url = `${store.apiUrl}/api/get_online_courese?org=udemy&page_num=${pageNum.value}&page_amount=${pageAmount.value}`;
+async function getOnlineClass() {
+  loadingOnline.value = true;
+  let url = `${store.apiUrl}/api/get_online_courese?org=udemy&page_num=${pageNumOnline.value}&page_amount=${pageAmountOnline.value}`;
 
   // 判斷篩選
-  if (isFilter.value) {
-    searchList.map((item) => {
+  if (isOnlineFilter.value) {
+    searchOnlineList.map((item) => {
       url += item;
     });
   }
 
   try {
     const response = await axios.get(url);
-    courese.list = response.data.online_coures;
-    totalPages.value = store.getTotalPages(response.data.total_num, 18);
-    loading.value = false;
-    totalNum.value = response.data.total_num;
-    console.log("courese.list", courese.list);
+    onlineCourese.list = response.data.online_coures;
+    totalPagesOnline.value = store.getTotalPages(response.data.total_num, 18);
+    loadingOnline.value = false;
+    totalNumOnline.value = response.data.total_num;
+    console.log("onlineCourese.list", onlineCourese.list);
   } catch (error) {
-    loading.value = false;
+    loadingOnline.value = false;
     console.error(error);
   }
 }
 
-getClass();
+getOnlineClass();
 
 const listLocation = ref(null);
 
-watch(pageNum, () => {
-  getClass();
+watch(pageNumOnline, () => {
+  getOnlineClass();
   listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
 });
 
-watch(selectCategory, (val) => {
+watch(selectOnlineCategory, (val) => {
   if (val.includes("全部")) {
-    selectCategory.value = [];
-    isFilter.value = false;
-    pageNum.value = 1;
-    getClass();
+    selectOnlineCategory.value = [];
+    isOnlineFilter.value = false;
+    pageNumOnline.value = 1;
+    getOnlineClass();
   } else {
     selectFilter("category", val);
   }
@@ -111,20 +88,20 @@ watch(selectCategory, (val) => {
 
 // 篩選課程
 async function selectFilter(type, val) {
-  isFilter.value = true;
-  pageNum.value = 1; // 篩選時返回第一頁
-  searchList.splice(0, searchList.length); // 清空陣列
+  isOnlineFilter.value = true;
+  pageNumOnline.value = 1; // 篩選時返回第一頁
+  searchOnlineList.splice(0, searchOnlineList.length); // 清空陣列
 
   if (type === "category") {
-    searchList.push(`&category=${val}`);
+    searchOnlineList.push(`&category=${val}`);
   }
 
-  getClass();
+  getOnlineClass();
 }
 
 let activeCategory = ref(1256);
 
-function setCategory(id) {
+function setOnlineCategory(id) {
   activeCategory.value = id;
   // 移除 active
   // categoryList.map((item) => {
@@ -137,17 +114,20 @@ function setCategory(id) {
   getImediaVideo();
 }
 
-const searchLocation = ref(null);
+const searchOnlineLocation = ref(null);
 let imedia = reactive({
   list: [],
 });
 let imediaCurrentPage = ref(1); // 當前頁數(預設第一頁)
-let imediaTotalPages = ref(1); // 總頁數
+let imediatotalPagesOnline = ref(1); // 總頁數
 
 // 切換分頁時回到列表上方
 watch(imediaCurrentPage, () => {
   getImediaVideo();
-  searchLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+  searchOnlineLocation.value.scrollIntoView({
+    behavior: "smooth",
+    block: "start",
+  });
 });
 
 let videoLoading = ref(false);
@@ -186,13 +166,13 @@ async function getImediaVideo() {
     });
     console.log("categoryList.value", categoryList.value);
 
-    imediaTotalPages.value = store.getTotalPages(imedia.list.length, 8); // 計算頁數
-    console.log("imediaTotalPages", imediaTotalPages.value);
+    imediatotalPagesOnline.value = store.getTotalPages(imedia.list.length, 8); // 計算頁數
+    console.log("imediatotalPagesOnline", imediatotalPagesOnline.value);
     console.log("imedia list", imedia.list);
 
     videoLoading.value = false;
   } catch (error) {
-    loading.value = false;
+    loadingOnline.value = false;
     console.error(error);
   }
 }
@@ -203,20 +183,20 @@ let videoCategory = ref(null);
 
 watch(videoCategory, (val) => {
   console.log("videoCategory", val);
-  setCategory(val);
+  setOnlineCategory(val);
 });
 </script>
 
 <template>
-  <v-breadcrumbs :items="breadcrumbs" divider="/" class="mt-10 p-0">
+  <v-breadcrumbs :items="onlineBreadcrumbs" divider="/" class="mt-10 p-0">
     <template v-slot:title="{ item }">
       {{ t(item.title) }}
     </template>
   </v-breadcrumbs>
 
-  <div
+  <!-- <div
     class="d-flex flex-column flex-sm-row align-center justify-end my-16"
-    ref="searchLocation"
+    ref="searchOnlineLocation"
   >
     <div class="search">
       <span>
@@ -241,9 +221,9 @@ watch(videoCategory, (val) => {
         {{ t("no_found") }}
       </div>
     </div>
-  </div>
+  </div> -->
 
-  <v-row>
+  <v-row class="mt-16">
     <v-col md="2" cols="12">
       <v-label class="d-block filter-list">
         <p class="pb-5 pe-3 mb-5 mb-sm-0">
@@ -264,7 +244,7 @@ watch(videoCategory, (val) => {
       <!-- <ul class="btn-list">
         <li v-for="(item, index) in categoryList" :key="index" class="mx-3">
           <v-btn
-            @click="setCategory(item.id, index)"
+            @click="setOnlineCategory(item.id, index)"
             class="mb-5"
             :class="{ active: item.active }"
             variant="outlined"
@@ -372,7 +352,7 @@ watch(videoCategory, (val) => {
       <v-pagination
         v-model="imediaCurrentPage"
         class="mt-16"
-        :length="imediaTotalPages"
+        :length="imediatotalPagesOnline"
       ></v-pagination>
     </v-col>
     <!-- <v-col sm="10" cols="12">
@@ -431,53 +411,30 @@ watch(videoCategory, (val) => {
   </div>
 
   <v-row class="mt-5 mt-sm-0">
-    <v-col cols="12">
-      <v-row>
-        <v-col
-          cols="12"
-          md="6"
-          lg="4"
-          v-for="(item, index) in courese.list"
-          :key="index"
-          class="pa-5"
-        >
-          <CourseCard :data="item" />
-
-          <!-- <div class="main-card">
-            <a :href="item.video_url" target="_blank">
-              <section class="card-title">
-                <h3>{{ item.title }}</h3>
-              </section>
-            </a>
-
-            <a :href="item.video_url" target="_blank">
-              <div class="card-info">
-                <ul>
-                  <li class="d-flex align-center">
-                    <p class="mb-0 ms-3">分類: {{ item.category }}</p>
-                  </li>
-                  <li class="d-flex align-center">
-                    <p class="mb-0 ms-3">來源: {{ item.content }}</p>
-                  </li>
-                </ul>
-              </div>
-            </a>
-          </div> -->
-        </v-col>
-      </v-row>
+    <v-col
+      cols="12"
+      md="6"
+      lg="4"
+      v-for="(item, index) in onlineCourese.list"
+      :key="index"
+      class="pa-5"
+    >
+      <CourseCard :data="item" />
     </v-col>
-  </v-row>
 
-  <v-pagination
-    v-model="pageNum"
-    :length="totalPages"
-    rounded="circle"
-    class="mt-16"
-  ></v-pagination>
+    <v-col cols="12">
+      <v-pagination
+        v-model="pageNumOnline"
+        :length="totalPagesOnline"
+        rounded="circle"
+        class="mt-16"
+      ></v-pagination>
 
-  <span class="text-gray total-item"
-    >{{ t("total_count") }}:{{ totalNum }}</span
-  >
+      <span class="text-gray total-item"
+        >{{ t("total_count") }}:{{ totalNumOnline }}</span
+      >
+    </v-col>
+  </v-row>
 </template>
 
 <style lang="scss">

+ 4 - 2
src/views/CollegeGroup/Proposal.vue

@@ -22,9 +22,9 @@ const breadcrumbs = reactive([
     href: "/",
   },
   {
-    title: "college_group_1",
+    title: "crafts.title",
     disabled: false,
-    href: "/college-group/future",
+    href: "/crafts",
   },
   {
     title: "research_plan",
@@ -75,6 +75,8 @@ function handlerBuyImg(title) {
     img = store.getImageUrl("college-group/future/proposal/購買連結-3.png");
   } else if (title === "國家書店") {
     img = store.getImageUrl("college-group/future/proposal/購買連結-4.png");
+  } else if (title === "TAAZE") {
+    img = store.getImageUrl("college-group/future/proposal/購買連結-5.png");
   }
 
   return img;

+ 216 - 31
src/views/CourseDetail.vue

@@ -7,6 +7,15 @@ import VueDatePicker from "@vuepic/vue-datepicker";
 import "@vuepic/vue-datepicker/dist/main.css";
 import axios from "axios";
 import moment from "moment";
+// Swiper
+import { Swiper, SwiperSlide } from "swiper/vue";
+// Import Swiper styles
+import "swiper/css";
+import "swiper/css/free-mode";
+import "swiper/css/navigation";
+import "swiper/css/thumbs";
+// import required modules
+import { FreeMode, Navigation, Thumbs } from "swiper/modules";
 import Navbar from "@/components/Navbar.vue";
 import CourseCard from "@/components/CourseCard.vue";
 
@@ -14,6 +23,7 @@ const { t } = useI18n();
 const route = useRoute();
 const store = useMainStore();
 const courseId = route.params.id; // 網址參數
+const modules = [FreeMode, Navigation, Thumbs]; // Swiper
 
 onMounted(() => {
   store.getFavoriteClass();
@@ -67,9 +77,16 @@ let courseLoading = ref(false);
     );
     course.data = response.data.classes[0];
     isInner.value = course.data.is_inner;
-    console.log("courseData", course.data);
     courseNameId.value = course.data.class_name_id;
-    console.log(" courseNameId.value", courseNameId.value);
+
+    console.log("courseData", course.data);
+    
+    if (course.data.work_imgs) {
+      course.data.work_imgs = JSON.parse(
+        course.data.work_imgs.replace(/'/g, '"')
+      );
+    }
+
     getOtherClass();
 
     if (course.data.group_id === 1) {
@@ -286,16 +303,25 @@ function isDateExpired(dateString) {
   const registrationEndDate = new Date(dateString);
   // 獲取當前時間
   const currentDate = new Date();
-
-  console.log("registrationEndDate", registrationEndDate);
-  console.log("currentDate", currentDate);
-
   if (registrationEndDate > currentDate) {
     return true; // 日期已過期
   } else {
     return false; // 日期未過期
   }
 }
+
+// Swiper
+const thumbsSwiper = ref(null);
+
+const setThumbsSwiper = (swiper) => {
+  thumbsSwiper.value = swiper;
+};
+
+let slides = [
+  "https://event.culture.tw/userFiles/NTCRI/JpgFile/01/30442/AD/30442_3_0_108876.jpg",
+  "https://event.culture.tw/userFiles/NTCRI/JpgFile/01/30442/AD/30442_3_0_32614.jpg",
+  "https://event.culture.tw/userFiles/NTCRI/JpgFile/01/30442/AD/30442_3_0_125566.jpg",
+];
 </script>
 
 <template>
@@ -319,7 +345,7 @@ function isDateExpired(dateString) {
 
     <Transition v-else>
       <v-row class="justify-center">
-        <v-col cols="12" md="3" class="title pa-0">
+        <v-col cols="12" md="4" class="title pa-0">
           <img
             src="@/assets/img/course/detail-background.png"
             alt="臺灣工藝學習平台"
@@ -327,7 +353,8 @@ function isDateExpired(dateString) {
           />
           <h2 class="mb-5 mb-md-0 me-md-5">{{ course.data.name }}</h2>
         </v-col>
-        <v-col cols="12" :md="dynamicCols" class="pa-0 d-flex justify-center">
+
+        <!-- <v-col cols="12" :md="dynamicCols" class="pa-0 d-flex justify-center">
           <v-img
             class="cover-img"
             :class="{ small: isInner === 0 }"
@@ -344,13 +371,135 @@ function isDateExpired(dateString) {
               </div>
             </template>
           </v-img>
+        </v-col> -->
+
+        <v-col cols="12" md="7">
+          <!-- work_imgs -->
+
+          <div v-if="course.data.work_imgs">
+            <!-- 輪播 -->
+            <swiper
+              :style="{
+                '--swiper-navigation-color': '#fff',
+                '--swiper-pagination-color': '#fff',
+              }"
+              :spaceBetween="10"
+              :navigation="true"
+              :thumbs="{ swiper: thumbsSwiper }"
+              :modules="modules"
+              class="slider-item"
+            >
+              <!-- 封面圖 -->
+              <swiper-slide>
+                <v-img
+                  class="cover-img"
+                  :class="{ small: isInner === 0 }"
+                  :lazy-src="store.getImageSrc(course.data)"
+                  :src="store.getImageSrc(course.data)"
+                  :alt="course.data.name"
+                >
+                  <template v-slot:placeholder>
+                    <div class="d-flex align-center justify-center fill-height">
+                      <v-progress-circular
+                        color="grey-lighten-4"
+                        indeterminate
+                      ></v-progress-circular>
+                    </div>
+                  </template>
+                </v-img>
+              </swiper-slide>
+              <swiper-slide
+                v-for="(item, index) in course.data.work_imgs"
+                :key="index"
+              >
+                <v-img
+                  class="cover-img"
+                  :class="{ small: isInner === 0 }"
+                  :lazy-src="`${store.imgUrl}/${item}`"
+                  :src="`${store.imgUrl}/${item}`"
+                  :alt="course.data.name"
+                >
+                  <template v-slot:placeholder>
+                    <div class="d-flex align-center justify-center fill-height">
+                      <v-progress-circular
+                        color="grey-lighten-4"
+                        indeterminate
+                      ></v-progress-circular>
+                    </div>
+                  </template>
+                </v-img>
+              </swiper-slide>
+            </swiper>
+            <!-- 輪播縮圖 -->
+            <swiper
+              @swiper="setThumbsSwiper"
+              :spaceBetween="10"
+              :slidesPerView="4"
+              :freeMode="true"
+              :watchSlidesProgress="true"
+              :modules="modules"
+              class="mySwiper mt-2"
+            >
+              <swiper-slide>
+                <v-img
+                  class="thumbnail-img"
+                  :class="{ small: isInner === 0 }"
+                  :lazy-src="store.getImageSrc(course.data)"
+                  :src="store.getImageSrc(course.data)"
+                  :alt="course.data.name"
+                >
+                  <template v-slot:placeholder>
+                    <div class="d-flex align-center justify-center fill-height">
+                      <v-progress-circular
+                        color="grey-lighten-4"
+                        indeterminate
+                      ></v-progress-circular>
+                    </div>
+                  </template>
+                </v-img>
+              </swiper-slide>
+              <swiper-slide
+                v-for="(item, index) in course.data.work_imgs"
+                :key="index"
+                class="thumbnail-img"
+              >
+                <v-img
+                  class="thumbnail-img"
+                  :class="{ small: isInner === 0 }"
+                  :lazy-src="`${store.imgUrl}/${item}`"
+                  :src="`${store.imgUrl}/${item}`"
+                  :alt="course.data.name"
+                >
+                  <template v-slot:placeholder>
+                    <div class="d-flex align-center justify-center fill-height">
+                      <v-progress-circular
+                        color="grey-lighten-4"
+                        indeterminate
+                      ></v-progress-circular>
+                    </div>
+                  </template>
+                </v-img>
+              </swiper-slide>
+            </swiper>
+          </div>
 
-          <!-- <img
-            :src="store.getImageSrc(course.data)"
-            alt="臺灣工藝學習平台"
+          <v-img
+            v-else
             class="cover-img"
             :class="{ small: isInner === 0 }"
-          /> -->
+            :lazy-src="store.getImageSrc(course.data)"
+            :src="store.getImageSrc(course.data)"
+            :alt="course.data.name"
+          >
+            <template v-slot:placeholder>
+              <div class="d-flex align-center justify-center fill-height">
+                <v-progress-circular
+                  color="grey-lighten-4"
+                  indeterminate
+                ></v-progress-circular>
+              </div>
+            </template>
+          </v-img>
         </v-col>
 
         <v-col cols="12">
@@ -397,25 +546,25 @@ function isDateExpired(dateString) {
                   </td>
                 </tr>
                 <!-- <tr>
-                <td>課程地點</td>
-                <td>{{ course.data.address }}</td>
-              </tr> -->
+                  <td>課程地點</td>
+                  <td>{{ course.data.address }}</td>
+                </tr> -->
                 <tr>
                   <td>{{ t("form.all_study_groups") }}</td>
                   <td>{{ groupName }}</td>
                 </tr>
                 <!-- <tr>
-                <td>報名對象</td>
-                <td></td>
-              </tr> -->
-                <!-- <tr>
-                <td>收費方式</td>
-                <td></td>
-              </tr> -->
-                <!-- <tr>
-                <td>報名方式</td>
-                <td></td>
-              </tr> -->
+                  <td>報名對象</td>
+                  <td></td>
+                </tr>
+                <tr>
+                  <td>收費方式</td>
+                  <td></td>
+                </tr>
+                <tr>
+                  <td>報名方式</td>
+                  <td></td>
+                </tr> -->
                 <tr
                   v-if="
                     isInner !== 0 &&
@@ -869,8 +1018,13 @@ function isDateExpired(dateString) {
       padding-bottom: 0 !important;
     }
     img {
-      max-height: 25em;
-      object-fit: contain;
+      height: 25em;
+      // object-fit: contain;
+
+      @media (max-width: 600px) {
+        height: 15em;
+      }
+
       &.small {
         width: auto;
         height: auto;
@@ -878,6 +1032,26 @@ function isDateExpired(dateString) {
     }
   }
 
+  .thumbnail-img {
+    img {
+      width: 100%;
+      height: 7em;
+      object-fit: cover;
+      cursor: pointer;
+      transition: all 0.1s;
+
+      &:hover {
+        opacity: 0.8;
+      }
+
+      @media (max-width: 600px) {
+        & {
+          height: 4em;
+        }
+      }
+    }
+  }
+
   .title {
     position: relative;
     display: flex;
@@ -892,11 +1066,14 @@ function isDateExpired(dateString) {
       color: #201715;
     }
     .bg-img {
-      width: 15.625em;
+      width: 15em;
       position: absolute;
       z-index: -1;
-      right: -0.9375em;
-      bottom: -6.25em;
+
+      @media (max-width: 960px) {
+        width: 10em;
+        transform: rotate(90deg);
+      }
     }
   }
 
@@ -1063,4 +1240,12 @@ function isDateExpired(dateString) {
     line-height: 1.625em;
   }
 }
+
+.slider-item {
+  img {
+    width: 100%;
+    max-height: 400px;
+    object-fit: cover;
+  }
+}
 </style>

+ 147 - 26
src/views/Courses/Create.vue

@@ -215,6 +215,7 @@ let course = reactive({
   syllabus: "", // 課程章節(課綱)
   organizer: "", // 主辦單位
   cover_img_file: "", // 課程圖片
+  work_imgs: "", // 作品集圖片
   group_id: 2, // 學群編號(技藝)
   group_sort: "", // 學群細分
   special_class_list_name: "",
@@ -259,13 +260,26 @@ async function insertClassName() {
 
   console.log("insertClassName", course);
 
+  // 封面圖片
   if (coverImg.value !== "") {
     course.cover_img_file = coverImg.value;
   }
 
+  // 作品集圖片
+  if (portfolioImg.value.length) {
+    course.work_imgs = portfolioImg.value;
+  }
+
   const formData = new FormData();
   for (const key in course) {
-    formData.append(key, course[key]);
+    if (key === "work_imgs") {
+      course.work_imgs.forEach((file) => {
+        formData.append("work_imgs", file);
+      });
+    } else {
+      formData.append(key, course[key]);
+    }
+    // formData.append(key, course[key]);
   }
 
   try {
@@ -556,6 +570,8 @@ const fileInputClick = (ref) => {
 // 作品集圖片
 let portfolioImg = ref([]);
 let portfolioImgList = ref([]);
+let portfolioImgUrl = ref([]); // 圖片網址
+let portfolioModel = ref(null);
 
 const handlePortfolioImg = (files) => {
   console.log("files", files);
@@ -564,7 +580,7 @@ const handlePortfolioImg = (files) => {
     const file = files[index];
     console.log("file", file);
     let url = URL.createObjectURL(file);
-    portfolioImgList.value.push(url);
+    portfolioImgUrl.value.push(url);
     console.log("portfolioImgList", portfolioImgList.value);
   }
 };
@@ -573,6 +589,7 @@ watch(portfolioImg, (newFiles) => {
   if (newFiles.length > 0) {
     handlePortfolioImg(newFiles);
   }
+  console.log("portfolioImg", portfolioImg.value);
 });
 
 // 封面圖片
@@ -958,8 +975,9 @@ let consent_2 = ref(false);
                       一、本平台因執行業務蒐集您的個人資料包含:姓名、工作室名稱及地址、電話、電子信箱、匯款帳號、作品集、教學履歷。
                     </li>
                     <li>
-                      二、本平台將使用cookies
-                      進行各項網路資源服務之管理及記錄,包括蒐集IP位址、瀏覽網頁、使用檔案及時間等軌跡資料。
+                      二、本平台將使用 cookies
+                      進行各項網路資源服務之管理及記錄,包括蒐集 IP
+                      位址、瀏覽網頁、使用檔案及時間等軌跡資料。
                     </li>
                   </ul>
                 </li>
@@ -1002,27 +1020,59 @@ let consent_2 = ref(false);
                 </li>
 
                 <li>
-                  <h5>陸、保證線上課程內容於開課後均符合下列事項:</h5>
+                  <h5>陸、保證課程內容均符合下列事項:</h5>
+                  <p>
+                    使用者同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:
+                  </p>
                   <ul>
                     <li>
-                      內容未有錯誤、對第三人之人身攻擊、鼓吹暴力、仇恨或歧視特定及不特定族群之攻擊性內容、對身體產生生理傷害和依賴性之菸草製品及管制藥品及其用具、散播色情或具裸露之內容、違反公序良俗或其他經甲方判斷不當之內容。
+                      一、上載、張貼、公布或傳送任何不實、詐欺、誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片、影音或任何形式的檔案於本服務上。
+                    </li>
+                    <li>
+                      二、侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利。
+                    </li>
+                    <li>三、違反依法律或契約所應負之保密義務。</li>
+                    <li>四、冒用他人名義或以匿名方式使用本服務。</li>
+                    <li>
+                      五、上載、張貼、傳輸或散佈任何含有電腦病毒或任何對電腦軟、硬體產生中斷、破壞或限制功能之程式碼之資料。
                     </li>
                     <li>
-                      無侵害他人智慧財產權(包括但不限於著作權、商標權、專利權、營業秘密及其他專門技術)之情事。
+                      六、上載、張貼、傳輸或散布有關廣告、贊助、促銷、銷售之訊息
                     </li>
-                    <li>無任何置入性行銷之商業行為。</li>
-                    <li>無其他違反法令之情事。</li>
+                    <li>
+                      七、從事不法交易行為或張貼虛假不實、引人犯罪之訊息。
+                    </li>
+                    <li>
+                      八、提供槍枝、毒品、禁藥、盜版軟體或其他違禁物之訊息。
+                    </li>
+                    <li>九、提供賭博資訊或以任何方式引誘他人參與賭博。</li>
+                    <li>十、以任何方法傷害未成年人。</li>
+                    <li>十一、偽造訊息來源或以任何方式干擾傳輸來源之認定。</li>
+                    <li>
+                      十二、干擾或中斷本服務或伺服器或連結本服務之網路,或不遵守連結至本服務之相關需求、程序、政策或規則等。
+                    </li>
+                    <li>
+                      十三、追蹤他人或其他干擾他人或為前述目前蒐集或儲存他人之個人資訊。
+                    </li>
+                    <li>十四、本平台有正當理由認為不適當之行為。</li>
                   </ul>
                 </li>
 
                 <li>
-                  <h5>柒、同意書之效力</h5>
+                  <h5>柒、授權條款</h5>
+                  <p>
+                    您同意在本網站刊登之課程或體驗活動等相關資訊(包含與課程有關之照片、課綱及回饋等),授權本平台得為推廣本網站或該等課程之目的,刊登於本平台經營之其他網站或行動應用軟體系統(APP),提供予他人瀏覽,並得作為廣告素材使用。本人願聲明上傳此平台之課程或相關內容均由本人獨立完成或享有著作權,絕無不法侵害他人著作權或其他權利之情事。若有涉及著作權或其他相關糾紛,願自負法律責任。
+                  </p>
+                </li>
+
+                <li>
+                  <h5>捌、同意書之效力</h5>
                   <ul>
                     <li>
-                      一、當您開始使用本平台的功能及服務時,即表示您已閱讀瞭解並同意本同意書的內容。
+                      一、當您開始使用本平台的功能及服務時,即表示您已閱讀瞭解並同意本書約內容;如有違反者,將由立書人負一切相關法律責任
                     </li>
                     <li>
-                      二、本平台保留隨時修改本同意書之權利,內容修改時將於本平台網站公告。如您未於公告後一個月內提出異議或仍繼續使用本平台相關服務,將視為您已同意並接受本平台所更改之內容。
+                      二、本平台保留隨時修改本書之權利,內容修改時將於本平台網站公告。如您未於公告後一個月內提出異議或仍繼續使用本平台相關服務,將視為您已同意並接受本平台所更改之內容。
                     </li>
                   </ul>
                 </li>
@@ -1032,13 +1082,13 @@ let consent_2 = ref(false);
 
               <v-checkbox
                 v-model="consent_1"
-                label="一、本人已充分知悉上述告知事項"
+                label="本人已充分知悉上述告知事項"
                 hide-details
                 color="purple"
               ></v-checkbox>
               <v-checkbox
                 v-model="consent_2"
-                label="二、本人同意本平台蒐集、處理、利用本人之個人資料執行業務管理與服務推廣相關業務目的之提供。"
+                label="本人同意本平台蒐集、處理、利用本人之個人資料執行業務管理與服務推廣相關業務目的之提供。"
                 hide-details
                 color="purple"
               ></v-checkbox>
@@ -2664,6 +2714,87 @@ let consent_2 = ref(false);
                         </v-col>
                       </v-row> -->
                     </v-col>
+
+                    <v-col cols="12" class="me-auto">
+                      <v-label class="d-block">
+                        <p class="d-flex">
+                          {{ t("form.upload_portfolio_image")
+                          }}<span class="mark">*</span>
+                        </p>
+
+                        <v-file-input
+                          multiple
+                          v-model="portfolioImg"
+                          ref="portfolioImgRef"
+                          label="File input"
+                          variant="outlined"
+                          :placeholder="t('form.portfolio_image')"
+                          style="display: none"
+                        ></v-file-input>
+                      </v-label>
+
+                      <v-btn
+                        @click="fileInputClick('portfolio')"
+                        color="purple"
+                        class="my-5"
+                      >
+                        {{ t("form.choose_image") }}
+                      </v-btn>
+
+                      <div
+                        v-if="portfolioImgUrl.length"
+                        class="step-01 image-preview"
+                      >
+                        <v-slide-group
+                          v-model="portfolioModel"
+                          class="pa-4"
+                          selected-class="bg-success"
+                          show-arrows
+                        >
+                          <v-slide-group-item
+                            v-for="(item, index) in portfolioImgUrl"
+                            :key="item"
+                          >
+                            <v-card
+                              color="grey-lighten-1"
+                              class="ma-4"
+                              height="160"
+                              width="200"
+                            >
+                              <div
+                                class="d-flex fill-height align-center justify-center"
+                              >
+                                <img :src="portfolioImgUrl[index]" alt="" />
+                                <!-- <v-scale-transition>
+                              <v-icon
+                                v-if="isSelected"
+                                color="white"
+                                size="48"
+                                icon="mdi-close-circle-outline"
+                              ></v-icon>
+                            </v-scale-transition> -->
+                              </div>
+                            </v-card>
+                          </v-slide-group-item>
+                        </v-slide-group>
+                      </div>
+
+                      <!-- <v-row class="img-list">
+                        <v-col cols="4" v-for="(item, index) in 6" :key="index">
+                          <div
+                            v-if="!portfolioImgList.length"
+                            class="item"
+                          ></div>
+                          <div
+                            v-else
+                            class="item"
+                            :style="{
+                              backgroundImage: `url('${portfolioImgList[index]}')`,
+                            }"
+                          ></div>
+                        </v-col>
+                      </v-row> -->
+                    </v-col>
                   </v-row>
                 </v-col>
 
@@ -2821,8 +2952,9 @@ let consent_2 = ref(false);
 
 .image-preview {
   img {
+    width: 100%;
     height: 100%;
-    object-fit: contain;
+    object-fit: cover;
   }
 }
 
@@ -2951,17 +3083,6 @@ input[type="checkbox"] {
   transform: scale(1.5);
 }
 
-.consent-form {
-  max-width: 800px;
-  font-size: 1rem;
-  line-height: 2.2;
-
-  h5 {
-    margin: 10px 0;
-    font-size: 1.125rem;
-  }
-}
-
 .school-form {
   max-width: 800px;
   margin: 50px auto 0;

+ 219 - 107
src/views/Courses/Proposal.vue

@@ -5,6 +5,7 @@ import { useI18n } from "vue-i18n";
 import "@vuepic/vue-datepicker/dist/main.css";
 import axios from "axios";
 import Navbar from "@/components/Navbar.vue";
+import { Loader } from "@googlemaps/js-api-loader";
 
 const { t } = useI18n();
 const store = useMainStore();
@@ -55,46 +56,84 @@ const breadcrumbs = reactive([
   },
 ]);
 
-// // Google Map
-// const states = reactive({
-//   google: null,
-//   map: null,
-//   markers: null,
-// });
-
-// const initMap = async () => {
-//   const loader = new Loader({
-//     apiKey: "AIzaSyClxiB7zcQDyGaB1r3Ww_VQG950AtjVoAk",
-//     version: "weekly",
-//     libraries: ["places"],
-//     language: "zh-TW",
-//   });
-//   states.google = await loader.load();
-//   states.map = new states.google.maps.Map(document.getElementById("map"), {
-//     center: { lat: 25.0425, lng: 121.5468 },
-//     zoom: 11,
-//     mapTypeControl: false,
-//     fullscreenControl: false,
-//   });
-// };
+// Google Map
+const states = reactive({
+  google: null,
+  map: null,
+  markers: null,
+});
 
-// onMounted(async () => {
-//   await initMap();
-// });
+const initMap = async () => {
+  try {
+    const loader = new Loader({
+      apiKey: "AIzaSyDETbkTAmZNOECx2u4rmNudHvyoQTgDbc4",
+      version: "weekly",
+      libraries: ["places"],
+      language: "zh-TW",
+    });
+    states.google = await loader.load();
+    states.map = new states.google.maps.Map(document.getElementById("map"), {
+      center: { lat: 25.0425, lng: 121.5468 },
+      zoom: 11,
+      mapTypeControl: false,
+      fullscreenControl: false,
+    });
+  } catch (error) {
+    console.error("Failed to load Google Maps API:", error);
+  }
+};
+
+onMounted(async () => {
+  await initMap();
+});
+
+const getCoordinates = async () => {
+  if (states.google) {
+    const geocoder = new states.google.maps.Geocoder();
+    geocoder.geocode({ address: location.address }, (results, status) => {
+      if (status === states.google.maps.GeocoderStatus.OK) {
+        if (results[0]) {
+          const lat = results[0].geometry.location.lat();
+          const lng = results[0].geometry.location.lng();
+          location.Lat = lat;
+          location.Lng = lng;
+          console.log("Latitude:", lat);
+          console.log("Longitude:", lng);
+        } else {
+          console.error("No results found");
+        }
+      } else {
+        alert("無法取得經緯度,請確認地址是否正確");
+        console.error("Geocoder failed due to: " + status);
+      }
+    });
+  } else {
+    console.error("Google Maps API is not loaded yet");
+  }
+};
 
 // const getCoordinates = async () => {
 //   const geocoder = new states.google.maps.Geocoder();
+//   console.log("getCoordinates", geocoder);
 //   geocoder.geocode({ address: location.address }, (results, status) => {
 //     if (status === states.google.maps.GeocoderStatus.OK) {
 //       location.Lat = results[0].geometry.location.lat();
 //       location.Lng = results[0].geometry.location.lng();
+//       console.log(
+//         "results[0].geometry.location.lat()",
+//         results[0].geometry.location.lat()
+//       );
+//       console.log(
+//         "results[0].geometry.location.lng()",
+//         results[0].geometry.location.lng()
+//       );
 //     }
 
-//     // 將地圖中心設為取得的經緯度,並調整縮放等級
+//     // // 將地圖中心設為取得的經緯度,並調整縮放等級
 //     // states.map.setCenter({ lat: location.Lat, lng: location.Lng });
 //     // states.map.setZoom(15);
 
-//     // 設定地址圖標
+//     // // 設定地址圖標
 //     // const marker = new google.maps.Marker({
 //     //   position: { lat: location.Lat, lng: location.Lng },
 //     //   map: states.map,
@@ -221,12 +260,11 @@ let proposal = reactive({
   category: "", // 工藝類別
   introduction: "", // 課程簡介
   organizer: "", // 主辦單位
-  cover_img_file: "", // 封面圖片
   fee_method: "", // 課程價位
   number_limit: "", // 課程名額
   number_minimum: "", // 最低開課人數
   people: "", // 適合對象
-  // files: [], // 課程簡報(檔案)
+  files: "", // 作品集圖片
 });
 
 let proposalFiles = ref([]); // 課程簡報
@@ -239,15 +277,20 @@ function insertProposal(id) {
 
   proposal.school_id = id;
 
-  // 封面圖片
-  if (coverImg.value !== "") {
-    proposal.cover_img_file = coverImg.value;
+  // 作品集圖片
+  if (portfolioImg.value.length) {
+    proposal.files = portfolioImg.value;
   }
 
-  // 課程檔案
-  if (proposalFiles.value.length) {
-    proposal["files"] = proposalFiles.value;
-  }
+  // // 封面圖片
+  // if (coverImg.value !== "") {
+  //   proposal.files = coverImg.value;
+  // }
+
+  // // 課程檔案
+  // if (proposalFiles.value.length) {
+  //   proposal["files"] = proposalFiles.value;
+  // }
 
   console.log("proposal", proposal);
 
@@ -373,6 +416,8 @@ const fileInputClick = (ref) => {
 // 作品集圖片
 let portfolioImg = ref([]);
 let portfolioImgList = ref([]);
+let portfolioImgUrl = ref([]); // 圖片網址
+let portfolioModel = ref(null);
 
 const handlePortfolioImg = (files) => {
   console.log("files", files);
@@ -381,7 +426,7 @@ const handlePortfolioImg = (files) => {
     const file = files[index];
     console.log("file", file);
     let url = URL.createObjectURL(file);
-    portfolioImgList.value.push(url);
+    portfolioImgUrl.value.push(url);
     console.log("portfolioImgList", portfolioImgList.value);
   }
 };
@@ -390,6 +435,7 @@ watch(portfolioImg, (newFiles) => {
   if (newFiles.length > 0) {
     handlePortfolioImg(newFiles);
   }
+  console.log("portfolioImg", portfolioImg.value);
 });
 
 // 封面圖片
@@ -474,12 +520,7 @@ function checkField(num) {
     schoolForm.value.validate();
     for (const key in location) {
       // 判斷是否有空值
-      if (
-        key !== "Lat" &&
-        key !== "Lng" &&
-        key !== "is_pass_proposal" &&
-        key !== "principal_user_email"
-      ) {
+      if (key !== "is_pass_proposal" && key !== "principal_user_email") {
         if (!location[key]) {
           console.log("!location[key]", key);
           errorField.value = true;
@@ -639,20 +680,51 @@ function removeTeacher(index) {
                     ></v-text-field>
                   </v-label>
 
-                  <v-label class="d-block mb-5">
+                  <v-label class="d-block mb-3">
                     <p class="d-flex mb-5">
                       {{ t("form.location_address")
                       }}<span class="mark">*</span>
                     </p>
+                    <div class="d-flex">
+                      <v-text-field
+                        v-model="location.address"
+                        :rules="[requiredRule]"
+                        :title="t('form.location_address')"
+                        density="compact"
+                        variant="outlined"
+                        :placeholder="t('form.safe_accessible_location')"
+                      ></v-text-field>
+
+                      <v-btn
+                        @click="getCoordinates()"
+                        color="purple"
+                        variant="flat"
+                        class="ms-5"
+                      >
+                        取得經緯度
+                      </v-btn>
+                    </div>
+                  </v-label>
+
+                  <div class="d-flex">
                     <v-text-field
-                      v-model="location.address"
-                      :rules="[requiredRule]"
-                      :title="t('form.location_address')"
+                      label="經度"
+                      v-model="location.Lng"
                       density="compact"
                       variant="outlined"
-                      :placeholder="t('form.safe_accessible_location')"
+                      disabled
+                      class="me-2"
                     ></v-text-field>
-                  </v-label>
+
+                    <v-text-field
+                      label="緯度"
+                      v-model="location.Lat"
+                      density="compact"
+                      variant="outlined"
+                      disabled
+                      class="ms-2"
+                    ></v-text-field>
+                  </div>
 
                   <v-label class="d-block mb-5">
                     <p class="d-flex mb-5">
@@ -765,11 +837,11 @@ function removeTeacher(index) {
         <v-window-item :value="2">
           <v-card-text class="mb-16 resume-content">
             <!-- <button class="resume-btn">
-                    <p class="d-flex flex-column align-center">
-                      <v-icon icon="mdi-plus"></v-icon>
-                      新增工藝教育者
-                    </p>
-                  </button> -->
+              <p class="d-flex flex-column align-center">
+                <v-icon icon="mdi-plus"></v-icon>
+                新增工藝教育者
+              </p>
+            </button> -->
 
             <button class="resume-btn">
               <p class="d-flex flex-column align-center">
@@ -888,48 +960,48 @@ function removeTeacher(index) {
                         </v-col>
 
                         <!-- <v-col cols="12">
-                                <v-label class="d-block">
-                                  <p class="d-flex">講師作品集</p>
-
-                                  <v-file-input
-                                    multiple
-                                    v-model="portfolioImg"
-                                    ref="portfolioImgRef"
-                                    label="File input"
-                                    variant="outlined"
-                                    :placeholder="t('form.choose_image')"
-                                    @change="handlePortfolioImg"
-                                    style="display: none"
-                                  ></v-file-input>
-                                </v-label>
-
-                                <v-btn
-                                  @click="fileInputClick('portfolio')"
-                                  color="purple"
-                                  class="my-5"
-                                  >選擇相片上傳</v-btn
-                                >
-
-                                <v-row class="img-list">
-                                  <v-col
-                                    cols="4"
-                                    v-for="(item, index) in 6"
-                                    :key="index"
-                                  >
-                                    <div
-                                      v-if="!portfolioImgList.length"
-                                      class="item"
-                                    ></div>
-                                    <div
-                                      v-else
-                                      class="item"
-                                      :style="{
-                                        backgroundImage: `url('${portfolioImgList[index]}')`,
-                                      }"
-                                    ></div>
-                                  </v-col>
-                                </v-row>
-                              </v-col> -->
+                          <v-label class="d-block">
+                            <p class="d-flex">講師作品集</p>
+
+                            <v-file-input
+                              multiple
+                              v-model="portfolioImg"
+                              ref="portfolioImgRef"
+                              label="File input"
+                              variant="outlined"
+                              :placeholder="t('form.choose_image')"
+                              @change="handlePortfolioImg"
+                              style="display: none"
+                            ></v-file-input>
+                          </v-label>
+
+                          <v-btn
+                            @click="fileInputClick('portfolio')"
+                            color="purple"
+                            class="my-5"
+                            >選擇相片上傳</v-btn
+                          >
+
+                          <v-row class="img-list">
+                            <v-col
+                              cols="4"
+                              v-for="(item, index) in 6"
+                              :key="index"
+                            >
+                              <div
+                                v-if="!portfolioImgList.length"
+                                class="item"
+                              ></div>
+                              <div
+                                v-else
+                                class="item"
+                                :style="{
+                                  backgroundImage: `url('${portfolioImgList[index]}')`,
+                                }"
+                              ></div>
+                            </v-col>
+                          </v-row>
+                        </v-col> -->
 
                         <v-col cols="12">
                           <v-label class="d-block">
@@ -958,7 +1030,6 @@ function removeTeacher(index) {
 
                   <v-card-actions class="justify-center">
                     <v-spacer></v-spacer>
-
                     <v-btn
                       :text="t('form.cancel')"
                       @click="resumeDialog = false"
@@ -1361,31 +1432,68 @@ function removeTeacher(index) {
 
                     <v-file-input
                       multiple
-                      v-model="coverImg"
-                      ref="coverImgRef"
+                      v-model="portfolioImg"
+                      ref="portfolioImgRef"
                       label="File input"
                       variant="outlined"
                       :placeholder="t('form.portfolio_image')"
-                      @change="handleCoverImg"
                       style="display: none"
                     ></v-file-input>
                   </v-label>
 
                   <v-btn
-                    @click="fileInputClick('cover')"
+                    @click="fileInputClick('portfolio')"
                     color="purple"
                     class="my-5"
                   >
                     {{ t("form.choose_image") }}
                   </v-btn>
 
-                  <div class="step-01 image-preview">
+                  <div
+                    v-if="portfolioImgUrl.length"
+                    class="step-01 image-preview"
+                  >
+                    <v-slide-group
+                      v-model="portfolioModel"
+                      class="pa-4"
+                      selected-class="bg-success"
+                      show-arrows
+                    >
+                      <v-slide-group-item
+                        v-for="(item, index) in portfolioImgUrl"
+                        :key="item"
+                      >
+                        <v-card
+                          color="grey-lighten-1"
+                          class="ma-4"
+                          height="160"
+                          width="200"
+                        >
+                          <div
+                            class="d-flex fill-height align-center justify-center"
+                          >
+                            <img :src="portfolioImgUrl[index]" alt="" />
+                            <!-- <v-scale-transition>
+                              <v-icon
+                                v-if="isSelected"
+                                color="white"
+                                size="48"
+                                icon="mdi-close-circle-outline"
+                              ></v-icon>
+                            </v-scale-transition> -->
+                          </div>
+                        </v-card>
+                      </v-slide-group-item>
+                    </v-slide-group>
+                  </div>
+
+                  <!-- <div class="step-01 image-preview">
                     <img
                       v-if="coverImg"
                       :src="coverImgUrl"
                       alt="上傳圖片預覽"
                     />
-                  </div>
+                  </div> -->
                 </v-col>
 
                 <v-col cols="12" md="5">
@@ -1462,7 +1570,10 @@ function removeTeacher(index) {
           style="bottom: 15px"
         >
           <v-icon icon="mdi-alert"></v-icon>
-          <span class="ms-2"> 尚有欄位未填寫 </span>
+          <span v-if="location.Lat !== '' && location.Lng !== ''" class="ms-2">
+            尚有欄位未填寫
+          </span>
+          <span v-else class="ms-2"> 尚未取得經緯度 </span>
         </div>
         <!-- <div
           v-if="errorEventField"
@@ -1519,20 +1630,21 @@ function removeTeacher(index) {
 
   .step-01 {
     &.image-preview {
-      height: 17.8125em;
+      height: 17em;
     }
   }
 
   .step-02 {
     &.image-preview {
-      height: 14.6875em;
+      height: 14em;
     }
   }
 
   .image-preview {
     img {
+      width: 100%;
       height: 100%;
-      object-fit: contain;
+      object-fit: cover;
     }
   }
 

+ 4 - 1
src/views/Courses/SetUp.vue

@@ -104,6 +104,7 @@ async function login() {
             </v-card-item>
 
             <v-card-actions class="d-flex flex-column">
+              <router-link to="/setup-courses/proposal">
               <v-btn
                 :loading="isLoading"
                 variant="flat"
@@ -111,8 +112,10 @@ async function login() {
                 class="px-8 mt-10"
                 size="large"
               >
-                <router-link to="/setup-courses/proposal">開始提案</router-link>
+              開始提案
+                <!-- <router-link to="/setup-courses/proposal">開始提案</router-link> -->
               </v-btn>
+            </router-link>
               <small class="text-gray mt-8"
                 >若提案通過後無法創建課程,請聯絡管理員。</small
               >

+ 1425 - 189
src/views/Crafts.vue

@@ -4,10 +4,12 @@ import { useRoute, useRouter } from "vue-router";
 import { useMainStore } from "@/stores/store";
 import { useI18n } from "vue-i18n";
 import axios from "axios";
+import moment from "moment";
 import Navbar from "@/components/Navbar.vue";
 import PDFViewer from "@/components/PDFViewer.vue";
 import craftsPdfList from "@/utils/useCraftsPdf";
 import ArticleCard from "@/components/ArticleCard.vue";
+import CourseCard from "@/components/CourseCard.vue";
 import CraftsArticle from "@/components/CraftsArticle.vue";
 
 const { t } = useI18n();
@@ -33,6 +35,8 @@ onMounted(() => {
       updatePDF(book[0].files);
     }, 1000);
   }
+
+  store.getFavoriteClass();
 });
 
 let searchInput = ref("");
@@ -184,6 +188,23 @@ let read = reactive({
   }
 })();
 
+let plan = reactive({
+  list: [],
+});
+
+// 研究計畫
+(async () => {
+  try {
+    const response = await axios.get(
+      `${store.apiUrl}/api/get_article?category=研究計畫`
+    );
+    plan.list = response.data.articles;
+    console.log("線上閱讀", plan.list);
+  } catch (error) {
+    console.error(error);
+  }
+})();
+
 let book = reactive({
   list: [],
 });
@@ -213,6 +234,186 @@ function handlePdfUrl(pdf) {
   let url = file.file1;
   return url;
 }
+
+/* 線上工藝 Start */
+let loadingOnline = ref(false);
+let pageNumOnline = ref(1); // 頁數(預設第一頁)
+let pageAmountOnline = ref(9); // 每頁顯示筆數
+let totalPagesOnline = ref(1); // 總頁數
+let selectOnlineCategory = ref([]); // 選擇類別
+let isOnlineFilter = ref(false); // 篩選狀態
+let searchOnlineList = reactive([]); // 篩選條件
+let totalNumOnline = ref(0); // 資料總筆數
+
+const onlineCourese = reactive({
+  list: [],
+});
+
+async function getOnlineClass() {
+  loadingOnline.value = true;
+  let url = `${store.apiUrl}/api/get_online_courese?org=udemy&page_num=${pageNumOnline.value}&page_amount=${pageAmountOnline.value}`;
+
+  // 判斷篩選
+  if (isOnlineFilter.value) {
+    searchOnlineList.map((item) => {
+      url += item;
+    });
+  }
+
+  try {
+    const response = await axios.get(url);
+    onlineCourese.list = response.data.online_coures;
+    totalPagesOnline.value = store.getTotalPages(response.data.total_num, 9);
+    loadingOnline.value = false;
+    totalNumOnline.value = response.data.total_num;
+    console.log("onlineCourese.list", onlineCourese.list);
+  } catch (error) {
+    loadingOnline.value = false;
+    console.error(error);
+  }
+}
+
+getOnlineClass();
+
+const listLocation = ref(null);
+
+watch(pageNumOnline, () => {
+  getOnlineClass();
+  listLocation.value.scrollIntoView({ behavior: "smooth", block: "start" });
+});
+
+watch(selectOnlineCategory, (val) => {
+  if (val.includes("全部")) {
+    selectOnlineCategory.value = [];
+    isOnlineFilter.value = false;
+    pageNumOnline.value = 1;
+    getOnlineClass();
+  } else {
+    selectFilter("category", val);
+  }
+});
+
+// 篩選課程
+async function selectFilter(type, val) {
+  isOnlineFilter.value = true;
+  pageNumOnline.value = 1; // 篩選時返回第一頁
+  searchOnlineList.splice(0, searchOnlineList.length); // 清空陣列
+
+  if (type === "category") {
+    searchOnlineList.push(`&category=${val}`);
+  }
+
+  getOnlineClass();
+}
+
+let activeCategory = ref(1256);
+
+function setOnlineCategory(id) {
+  activeCategory.value = id;
+  // 移除 active
+  // categoryList.map((item) => {
+  //   if (item.active) {
+  //     item.active = false;
+  //   }
+  // });
+  // categoryList[index].active = true;
+  console.log("activeCategory.value", activeCategory.value);
+  getImediaVideo();
+}
+
+const searchOnlineLocation = ref(null);
+let imedia = reactive({
+  list: [],
+});
+let imediaCurrentPage = ref(1); // 當前頁數(預設第一頁)
+let imediatotalPagesOnline = ref(1); // 總頁數
+
+// 切換分頁時回到列表上方
+watch(imediaCurrentPage, () => {
+  getImediaVideo();
+  searchOnlineLocation.value.scrollIntoView({
+    behavior: "smooth",
+    block: "start",
+  });
+});
+
+let videoLoading = ref(false);
+let categoryList = ref([]);
+
+async function getImediaVideo() {
+  videoLoading.value = true;
+
+  let url = `${store.apiUrl}/api/get_media_data?page=${imediaCurrentPage.value}&page_size=8`;
+
+  if (activeCategory.value) {
+    url += `&media_categories_id=${activeCategory.value}`;
+  }
+
+  try {
+    imedia.list = [];
+    const response = await axios.post(url);
+    const list = JSON.parse(response.data.data);
+    list.data.map((item) => {
+      if (item.video_link) {
+        imedia.list.push(item);
+      }
+    });
+    console.log("Imedia data", response.data);
+    console.log("list", list);
+    console.log("list category", list.category[0]);
+    categoryList.value = [];
+    list.category[0].categories.map((item) => {
+      // console.log('item',item);
+      // media_category_id
+      let data = {
+        id: item.media_category_id,
+        title: item.name[1].zh_TW,
+      };
+      categoryList.value.push(data);
+    });
+    console.log("categoryList.value", categoryList.value);
+
+    imediatotalPagesOnline.value = store.getTotalPages(imedia.list.length, 8); // 計算頁數
+    console.log("imediatotalPagesOnline", imediatotalPagesOnline.value);
+    console.log("imedia list", imedia.list);
+
+    videoLoading.value = false;
+  } catch (error) {
+    loadingOnline.value = false;
+    console.error(error);
+  }
+}
+
+getImediaVideo();
+
+let videoCategory = ref(null);
+
+watch(videoCategory, (val) => {
+  console.log("videoCategory", val);
+  setOnlineCategory(val);
+});
+/* 線上工藝 End */
+
+/* 修護故事 Start */
+const restorationArticles = reactive({
+  list: [],
+});
+
+(async () => {
+  // let url = `${store.apiUrl}/api/get_group_classes_and_articles?group_id=6`;
+  // let classUrl = `${store.apiUrl}/api/get_class_name?group_id=6`;
+  let articlesUrl = `${store.apiUrl}/api/get_article?group_id=6`;
+
+  try {
+    // const classData = await axios.get(classUrl);
+    const articlesData = await axios.get(articlesUrl);
+    // classes.list = classData.data.classes;
+    restorationArticles.list = articlesData.data.articles;
+  } catch (error) {
+    console.error(error);
+  }
+})();
+/* 修護故事 End */
 </script>
 
 <template>
@@ -249,6 +450,331 @@ function handlePdfUrl(pdf) {
           </v-col>
         </v-row>
 
+        <!-- 線上工藝 -->
+        <h2 class="mb-16 pb-5">{{ t("college_group_4") }}</h2>
+
+        <v-row class="mt-16">
+          <v-col md="2" cols="12">
+            <v-label class="d-block filter-list">
+              <p class="pb-5 pe-3 mb-5 mb-sm-0">
+                {{ t("form.craft_category") }}<span class="mark">*</span>
+              </p>
+              <v-select
+                v-model="videoCategory"
+                placeholder="請選擇類別"
+                :items="categoryList"
+                item-title="title"
+                item-value="id"
+                :rules="[requiredRule]"
+                density="compact"
+                variant="outlined"
+              ></v-select>
+            </v-label>
+
+            <!-- <ul class="btn-list">
+              <li
+                v-for="(item, index) in categoryList"
+                :key="index"
+                class="mx-3"
+              >
+                <v-btn
+                  @click="setOnlineCategory(item.id, index)"
+                  class="mb-5"
+                  :class="{ active: item.active }"
+                  variant="outlined"
+                >
+                  {{ item.title }}
+                </v-btn>
+              </li>
+            </ul> -->
+          </v-col>
+          <v-col md="10" cols="12">
+            <div class="d-flex justify-center mb-10" v-if="videoLoading">
+              <v-progress-circular
+                color="grey-lighten-4"
+                indeterminate
+              ></v-progress-circular>
+            </div>
+
+            <v-row class="video-list">
+              <v-col
+                cols="12"
+                md="6"
+                v-for="(item, index) in imedia.list"
+                :key="index"
+                class="mb-5 mb-md-0"
+              >
+                <v-dialog
+                  transition="dialog-top-transition"
+                  width="853"
+                  class="video-dialog"
+                >
+                  <template v-slot:activator="{ props }">
+                    <div class="img-box">
+                      <v-img
+                        v-bind="props"
+                        class="mx-auto cover-img"
+                        :lazy-src="item.image_m"
+                        cover
+                        :src="item.image_m"
+                      >
+                        <template v-slot:placeholder>
+                          <div
+                            class="d-flex align-center justify-center fill-height"
+                          >
+                            <v-progress-circular
+                              color="grey-lighten-4"
+                              indeterminate
+                            ></v-progress-circular>
+                          </div>
+                        </template>
+                      </v-img>
+                    </div>
+                    <!-- <div class="img-box">
+                      <img v-bind="props" :src="item.image_m" alt="" />
+                    </div> -->
+                  </template>
+                  <template v-slot:default="{ isActive }">
+                    <v-card>
+                      <v-toolbar color="blue" class="ps-5 pe-7">
+                        <div
+                          class="w-100 d-flex justify-space-between align-center"
+                        >
+                          <h3>{{ item.metadata[1].name }}</h3>
+                          <div class="close-btn">
+                            <v-icon
+                              @click="isActive.value = false"
+                              icon="mdi-close"
+                            ></v-icon>
+                          </div>
+                        </div>
+                      </v-toolbar>
+                      <v-card-text class="pa-10">
+                        <div class="video-box">
+                          <video controls>
+                            <source
+                              :src="`${
+                                item.video_link.video_source.mp4[3]
+                                  ? item.video_link.video_source.mp4[3]
+                                  : item.video_link.video_source.mp4[2]
+                              }?api_access_key=08c9cdf9-a1d6-4997-943c-4f08f34ed2f7`"
+                              type="video/mp4"
+                            />
+                            Your browser does not support the video tag.
+                          </video>
+                        </div>
+                      </v-card-text>
+                      <!-- <v-card-actions class="justify-end">
+                        <v-btn variant="text" @click="isActive.value = false"
+                          >Close</v-btn
+                        >
+                      </v-card-actions> -->
+                    </v-card>
+                  </template>
+                </v-dialog>
+
+                <!-- <img :src="item.image_m" alt="" /> -->
+                <h3>{{ item.metadata[1].name }}</h3>
+                <p>{{ item.metadata[1].desc }}</p>
+                <div class="d-flex justify-end mt-3">
+                  <span class="text-gray"
+                    >{{
+                      moment(`${item.create_time}`).format("YYYY-MM-DD")
+                    }}|點閱次數:{{ item.use_count }}</span
+                  >
+                </div>
+              </v-col>
+            </v-row>
+
+            <v-pagination
+              v-model="imediaCurrentPage"
+              class="mt-16"
+              :length="imediatotalPagesOnline"
+            ></v-pagination>
+          </v-col>
+          <!-- <v-col sm="10" cols="12">
+            <v-row class="video-list">
+              <v-col
+                cols="12"
+                md="6"
+                v-for="(item, index) in videoData"
+                :key="index"
+                class="mb-5 mb-md-0"
+              >
+                <video controls class="w-100">
+                  <source :src="item.video" type="video/mp4" />
+                  <a :href="item.video"></a>
+                </video>
+                <h3>{{ item.title }}</h3>
+                <p>{{ item.introduction }}</p>
+                <div class="d-flex justify-end mt-3">
+                  <span class="text-gray"
+                    >{{ item.date }}|點閱次數:{{ item.ctr }}</span
+                  >
+                </div>
+              </v-col>
+            </v-row>
+          </v-col> -->
+        </v-row>
+
+        <!-- 線上體驗課程 -->
+        <h2 class="mb-16 pb-5">
+          {{ t("online_experience_courses") }}
+        </h2>
+
+        <v-row class="mt-5 mt-sm-0">
+          <v-col
+            cols="12"
+            md="6"
+            lg="4"
+            v-for="(item, index) in onlineCourese.list"
+            :key="index"
+            class="pa-5"
+          >
+            <CourseCard :data="item" />
+          </v-col>
+
+          <div class="mx-auto d-flex flex-column">
+            <v-pagination
+              v-model="pageNumOnline"
+              :length="totalPagesOnline"
+              rounded="circle"
+              class="mt-16"
+            ></v-pagination>
+
+            <span class="text-gray total-item"
+              >{{ t("total_count") }}:{{ totalNumOnline }}</span
+            >
+          </div>
+        </v-row>
+
+        <!-- 修護工藝 -->
+        <div class="restoration-content">
+          <h2 class="mb-16 pb-5">
+            {{ t("craft_restoration") }}
+          </h2>
+
+          <v-row>
+            <v-col cols="12" md="6">
+              <h3 class="text-center mb-5">國家工藝檢測修護平臺</h3>
+              <div class="video">
+                <iframe
+                  width="560"
+                  height="315"
+                  src="https://www.youtube.com/embed/eIOkU2q9WHA"
+                  title="YouTube video player"
+                  frameborder="0"
+                  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+                  allowfullscreen
+                ></iframe>
+              </div>
+            </v-col>
+            <v-col cols="12" md="6">
+              <h3 class="text-center mb-5">臺灣工藝聯合醫院-草鞋墩分院</h3>
+              <div class="video">
+                <iframe
+                  width="560"
+                  height="315"
+                  src="https://www.youtube.com/embed/Rb318IkMo80"
+                  title="YouTube video player"
+                  frameborder="0"
+                  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
+                  allowfullscreen
+                ></iframe>
+              </div>
+            </v-col>
+          </v-row>
+
+          <v-row class="mt-16 article-block">
+            <v-col cols="12" sm="7" md="8">
+              <h4 class="mb-5">首創工藝聯合醫院,診斷修復再現舊物新生命</h4>
+              <p>
+                工藝中心在愛物惜物、自然永續的綠工藝精神下,成立「臺灣工藝檢測修護聯盟」,串聯專業學術單位、團體或個人工作室等成為一個工藝的照護聯合網,就像一所工藝的聯合醫院,為博物館、常民工藝或是生活收藏等需求者,提供檢測、維護及修復的服務,猶如醫院的體檢、衛教保健到診斷治療,全臺分區各有據點,提供大眾就近在地的友善服務。
+                「臺灣工藝聯合醫院-草鞋墩分院」就是照護聯合網絡的一個據點,由廖偉淇、蔣昆原、張璽元、葉璨榮、陳宜妙5位修復師進駐,服務項目有陶瓷、金屬、漆及複合媒材等修復,歡迎大家隨時來問診交流、學習工藝保養維護,親自體驗舊物新生的感動。
+                <br />
+                「國家工藝檢測修護平臺」開始受理申請,有檢測、修復及維護需求者,皆可透過「國家工藝檢測修護平臺」網站提出申請,或電話撥打0800-222-800洽詢。
+              </p>
+            </v-col>
+            <v-col
+              cols="12"
+              sm="5"
+              md="4"
+              class="d-flex flex-column align-center align-md-end justify-space-around"
+            >
+              <section>
+                <h4>檢測x修復x維護</h4>
+                <h4>保存x循環x再生</h4>
+              </section>
+              <span class="btn mt-10 mt-sm-0">
+                <v-btn block size="large"
+                  >點我前往 <br />
+                  「國家工藝檢測修護平臺」</v-btn
+                >
+              </span>
+            </v-col>
+          </v-row>
+
+          <!-- 工藝修護師 -->
+          <h2 class="mb-16 pb-5">
+            {{ t("craft_restorer") }}
+          </h2>
+
+          <v-row class="master-list">
+            <v-col
+              cols="12"
+              md="6"
+              class="v-col-md-6 v-col-12 d-flex flex-column flex-md-row align-center justify-md-center"
+            >
+              <img
+                src="@/assets/img/college-group/repair/修護-12.webp"
+                alt="臺灣工藝學習平台"
+              />
+              <section class="mt-5 mt-md-0 ms-md-5">
+                <h3>廖偉淇</h3>
+                <span class="d-block mt-3 mb-5">金工木印工作室</span>
+                <ul>
+                  <li>主要修護媒材</li>
+                  <li>金屬:金/銀首飾、銀/銅茶具、餐具。</li>
+                  <li>陶瓷:花器、餐具、茶道具、香道具。</li>
+                  <li>主要修復技法:鋦瓷、漆繕、金工鍛造、脫腊鑄造。</li>
+                </ul>
+              </section>
+            </v-col>
+            <v-col
+              cols="12"
+              md="6"
+              class="v-col-md-6 v-col-12 d-flex flex-column flex-md-row align-center justify-md-center"
+            >
+              <img
+                src="@/assets/img/college-group/repair/修護-13.webp"
+                alt="臺灣工藝學習平台"
+              />
+              <section class="mt-5 mt-md-0 ms-md-5">
+                <h3>陳高登</h3>
+                <span class="d-block mt-3 mb-5">金工木印工作室</span>
+                <ul>
+                  <li>主要修護媒材</li>
+                  <li>陶瓷器物</li>
+                </ul>
+              </section>
+            </v-col>
+          </v-row>
+
+          <!-- 修護故事 -->
+          <h2 id="bookList" class="mb-16">{{ t("restoration_story") }}</h2>
+
+          <ul class="story-list">
+            <li
+              v-for="(item, index) in restorationArticles.list"
+              :key="index"
+              class="mb-16"
+            >
+              <ArticleCard :data="item" type="article" />
+            </li>
+          </ul>
+        </div>
+
+        <!-- 國際專欄 -->
         <h2 id="articleList" class="mb-16 pb-5">{{ t("crafts.title_1") }}</h2>
 
         <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation">
@@ -260,7 +786,10 @@ function handlePdfUrl(pdf) {
               placeholder="關鍵字搜尋"
             />
             <button @click="search()">
-              <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學習平台" />
+              <img
+                src="@/assets/img/news/news-search-icon.png"
+                alt="臺灣工藝學習平台"
+              />
             </button>
           </span>
           <div
@@ -274,6 +803,7 @@ function handlePdfUrl(pdf) {
 
         <CraftsArticle />
 
+        <!-- 線上閱讀 -->
         <h2 ref="readRef" id="readList">{{ t("crafts.title_2") }}</h2>
 
         <v-row class="justify-center align-start mt-16 read-list">
@@ -347,6 +877,7 @@ function handlePdfUrl(pdf) {
           <PDFViewer :file="fileName" />
         </div>
 
+        <!-- 工藝書單 -->
         <h2 id="bookList" class="mb-16">{{ t("crafts.title_3") }}</h2>
 
         <!-- <div class="search pt-5 my-10 me-sm-16" ref="searchLocation ">
@@ -358,7 +889,10 @@ function handlePdfUrl(pdf) {
               placeholder="關鍵字搜尋"
             />
             <button @click="search()">
-              <img src="@/assets/img/news/news-search-icon.png" alt="臺灣工藝學習平台" />
+              <img
+                src="@/assets/img/news/news-search-icon.png"
+                alt="臺灣工藝學習平台"
+              />
             </button>
           </span>
           <div
@@ -418,6 +952,7 @@ function handlePdfUrl(pdf) {
           :length="totalPages"
         ></v-pagination> -->
 
+        <!-- 工藝學刊 -->
         <h2 id="journal">{{ t("crafts.title_4") }}</h2>
 
         <div class="journal-content">
@@ -479,6 +1014,7 @@ function handlePdfUrl(pdf) {
           </div>
         </div>
 
+        <!-- 碩博士論文補助 -->
         <h2 id="thesisGrant">{{ t("crafts.title_5") }}</h2>
 
         <ul class="mt-10">
@@ -491,271 +1027,971 @@ function handlePdfUrl(pdf) {
             <ArticleCard :data="item" type="article" />
           </li>
         </ul>
+
+        <!-- 研發補助 -->
+        <h2 id="thesisGrant" class="mb-16">
+          {{ t("research_and_development_subsidy") }}
+        </h2>
+
+        <v-container class="pa-0 pa-sm-3">
+          <div class="research-content">
+            <img
+              src="@/assets/img/college-group/future/素材-07.png"
+              alt="臺灣工藝學習平台"
+            />
+            <section>
+              <h3 class="mb-10">工藝跨域研創補助計畫【徵件至9月28日止】</h3>
+              <span class="d-flex align-center mb-2">
+                <v-icon
+                  color="gray"
+                  icon="mdi-calendar-range"
+                  class="me-2"
+                ></v-icon>
+                2023/05/18 09:00 ~ 2023/09/28 17:00
+              </span>
+              <span class="d-flex align-center">
+                <v-icon
+                  color="gray"
+                  icon="mdi-map-marker"
+                  class="me-2"
+                ></v-icon>
+                臺灣工藝學習平台
+              </span>
+              <p class="mt-8">
+                臺灣工藝學習平台為實踐從總體經濟觀上思考整體工藝文化產業價值,及厚實臺灣工藝產業未來發展之國際文化競爭力,徵求類型含物件型研創、新材料研創、技術性研創、社會與環境議題研創等,申請團隊可提出相關實績說明或規畫構想,最高獲得
+                #新臺幣50萬元
+                補助,歡迎國內相關機構、產業、公司等法人或團體以及個人創作者等單位踴躍提案申請!
+              </p>
+            </section>
+
+            <section>
+              <p class="sub-title mb-5">附件下載</p>
+              <ul>
+                <li class="dot">
+                  <span class="dot"></span>
+                  <a
+                    href="https://event.culture.tw/userFiles/NTCRI/DownloadFile/01/30166/01/30166_00_825471.pdf"
+                    target="_blank"
+                    >工藝跨域研創補助須知.pdf</a
+                  >
+                </li>
+                <!-- <li class="dot py-2 py-sm-0">
+                  <a href="">工藝跨域研創補助計畫申請書.docx</a>
+                </li>
+                <li class="dot">
+                  <a href="">工藝跨域研創補助計畫申請書.odt</a>
+                </li> -->
+              </ul>
+            </section>
+
+            <p class="text-end mt-5">
+              聯絡資訊 : 049-2334141 分機134 王小姐、分機136 張小姐
+            </p>
+
+            <img
+              class="mt-16"
+              src="@/assets/img/college-group/future/素材-08.png"
+              alt="臺灣工藝學習平台"
+            />
+          </div>
+        </v-container>
+
+        <!-- 研究計畫 -->
+        <h2 id="thesisGrant" class="mb-16">
+          {{ t("research_plan") }}
+        </h2>
+
+        <v-container class="book-block">
+          <v-row
+            v-for="(item, index) in plan.list"
+            :key="index"
+            class="align-center mb-16"
+          >
+            <v-col cols="12" md="4" class="d-flex flex-column align-center">
+              <a
+                v-if="store.isMobile"
+                :href="store.getPDF(item.fileName)"
+                target="_blank"
+                class="cover-img"
+              >
+                <img
+                  :src="`${store.imgUrl}/${item.cover_img}`"
+                  alt="臺灣工藝學習平台"
+                />
+                <button class="read-btn">點我閱讀</button>
+              </a>
+
+              <!-- <router-link v-else :to="`/crafts/${item.fileName}`" class="cover-img"> -->
+              <router-link
+                v-else
+                :to="`/college-group/future/proposal/${item.article_id}`"
+                class="cover-img"
+              >
+                <img
+                  :src="`${store.imgUrl}/${item.cover_img}`"
+                  alt="臺灣工藝學習平台"
+                />
+                <button class="read-btn">點我閱讀</button>
+              </router-link>
+              <h3 v-html="item.title"></h3>
+            </v-col>
+            <v-col cols="12" md="8" class="px-md-16 pb-md-16 mb-md-10">
+              <p class="mt-10 content" v-html="item.depiction"></p>
+            </v-col>
+          </v-row>
+        </v-container>
       </div>
     </v-container>
   </div>
 </template>
 
-<style lang="scss" scoped>
+<style lang="scss">
 .article-content {
   margin-bottom: 9.375em;
-}
-
-h2 {
-  padding-top: 6.25em;
-  font-size: 1.875em;
-  font-weight: 500;
-  text-align: center;
-}
 
-.bg-img {
-  background-image: url("@/assets/img/crafts/background.png");
-  background-size: cover;
-  background-position: top;
-  @media (max-width: 960px) {
-    background-size: contain;
-    background-repeat: repeat;
+  // 線上工藝 Start
+  .filter-list .v-select .v-field__input {
+    padding-top: 0.55em !important;
   }
-}
 
-.read-list,
-.book-list {
-  p {
-    margin-bottom: 0.625em;
+  .video-list {
+    h3 {
+      margin: 0.5em 0 0.3em;
+      font-size: 1.125em;
+      font-weight: 500;
+    }
+    p {
+      // 超過兩行則省略
+      overflow: hidden;
+      text-overflow: ellipsis;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      line-break: after-white-space;
+    }
+    h3,
+    p,
+    span {
+      line-height: 1.5em;
+      letter-spacing: 0.0625em;
+    }
+    span {
+      font-size: 0.875em;
+    }
+
+    img {
+      cursor: pointer;
+    }
+
+    .img-box {
+      overflow: hidden;
+      border-radius: 20px;
+      .v-img {
+        height: 225px;
+        width: 100%;
+        object-fit: cover;
+        border-radius: 20px;
+        transition: all 0.5s;
+        cursor: pointer;
+
+        @media (max-width: 1200px) {
+          height: 160px;
+        }
+
+        @media (max-width: 960px) {
+          height: 32vw;
+        }
+
+        @media (max-width: 600px) {
+          height: 37vw;
+        }
+
+        &:hover {
+          transform: scale(1.1);
+        }
+      }
+    }
   }
-  h3,
-  p {
-    font-weight: 500;
-    text-align: center;
-    line-height: 1.875em;
+
+  video {
+    border-radius: 1.25em;
   }
-}
 
-.read-list {
-  .v-img {
-    width: 100%;
-    // height: 100%;
-    max-height: 25em;
-    object-fit: cover;
-    cursor: pointer;
+  // 線上工藝 End
 
+  h2 {
+    padding-top: 5em;
+    font-size: 1.875em;
+    font-weight: 500;
+    text-align: center;
     @media (max-width: 600px) {
-      width: 50%;
+      padding-top: 3em;
     }
   }
 
-  @media (max-width: 600px) {
-    section {
-      width: 50%;
-      padding: 0 0.625em;
-      font-size: 0.875em;
+  .bg-img {
+    background-image: url("@/assets/img/crafts/background.png");
+    background-size: cover;
+    background-position: top;
+    @media (max-width: 960px) {
+      background-size: contain;
+      background-repeat: repeat;
     }
   }
 
-  p {
-    width: 18.125em;
-
-    @media (max-width: 600px) {
-      width: auto;
+  .read-list,
+  .book-list {
+    p {
+      margin-bottom: 0.625em;
+    }
+    h3,
+    p {
+      font-weight: 500;
+      text-align: center;
+      line-height: 1.875em;
     }
   }
-}
 
-.book-list {
-  margin-bottom: 6.25em;
-  .info {
-    width: 26.875em;
-    padding: 0.625em 1.25em;
-    position: absolute;
-    right: -2.5em;
-    bottom: -3.75em;
-    z-index: 1;
-    background: rgba(255, 255, 255, 0.8);
-    border: 0.125em solid #c8cbcc;
+  .read-list {
+    .v-img {
+      width: 100%;
+      // height: 100%;
+      max-height: 25em;
+      object-fit: cover;
+      cursor: pointer;
 
-    @media (max-width: 1280px) {
-      width: 25em;
+      @media (max-width: 600px) {
+        width: 50%;
+      }
     }
 
     @media (max-width: 600px) {
-      width: 20.625em;
-      right: -0.1875em;
-      bottom: -5em;
+      section {
+        width: 50%;
+        padding: 0 0.625em;
+        font-size: 0.875em;
+      }
     }
 
     p {
-      width: 18.75em;
-      margin-bottom: 0;
-      text-align: start;
+      width: 18.125em;
+
+      @media (max-width: 600px) {
+        width: auto;
+      }
     }
-    span {
-      display: block;
-      margin-top: auto;
+  }
+
+  .book-list {
+    margin-bottom: 6.25em;
+    .info {
+      width: 26.875em;
+      padding: 0.625em 1.25em;
       position: absolute;
-      bottom: 0.9375em;
-      right: 0.9375em;
+      right: -2.5em;
+      bottom: -3.75em;
+      z-index: 1;
+      background: rgba(255, 255, 255, 0.8);
+      border: 0.125em solid #c8cbcc;
+
+      @media (max-width: 1280px) {
+        width: 25em;
+      }
+
+      @media (max-width: 600px) {
+        width: 20.625em;
+        right: -0.1875em;
+        bottom: -5em;
+      }
+
+      p {
+        width: 18.75em;
+        margin-bottom: 0;
+        text-align: start;
+      }
+      span {
+        display: block;
+        margin-top: auto;
+        position: absolute;
+        bottom: 0.9375em;
+        right: 0.9375em;
+      }
     }
   }
-}
 
-.pdf-list {
-  display: flex;
-  margin: 6.25em 0;
-  .side-title {
-    padding: 4.375em 0 4.375em 0.625em;
+  .pdf-list {
     display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    border-radius: 0.3125em;
-    background-color: var(--purple);
-    h5 {
-      padding: 1.25em 0.625em;
-      position: relative;
-      font-size: 1.625em;
-      font-weight: 400;
-      writing-mode: vertical-rl; // 垂直
-      color: var(--purple);
-      letter-spacing: 0.125em;
+    margin: 6.25em 0;
+    .side-title {
+      padding: 4.375em 0 4.375em 0.625em;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      border-radius: 0.3125em;
+      background-color: var(--purple);
+      h5 {
+        padding: 1.25em 0.625em;
+        position: relative;
+        font-size: 1.625em;
+        font-weight: 400;
+        writing-mode: vertical-rl; // 垂直
+        color: var(--purple);
+        letter-spacing: 0.125em;
+        background-color: #fff;
+
+        @media (max-width: 600px) {
+          font-size: 1.25em;
+        }
+
+        &::after,
+        &::before {
+          content: "";
+          display: block;
+          height: 0.9375em;
+          width: 105%;
+          background: #fff;
+          position: absolute;
+        }
+
+        &::after {
+          top: -0.4em;
+          right: -0.2em;
+          transform: rotate(-15deg);
+
+          @media (max-width: 1200px) {
+            top: -0.5em;
+            right: -0.1875em;
+          }
+        }
+        &::before {
+          bottom: -0.4em;
+          right: -0.2em;
+          transform: rotate(15deg);
+
+          @media (max-width: 1200px) {
+            bottom: -0.5em;
+            right: -0.1875em;
+          }
+        }
+      }
+    }
+    ul {
       background-color: #fff;
+      padding: 3.125em 3.125em 0 3.125em;
 
       @media (max-width: 600px) {
-        font-size: 1.25em;
+        padding: 1.5em 1.5em 0 1.5em;
       }
 
-      &::after,
-      &::before {
-        content: "";
-        display: block;
-        height: 0.9375em;
-        width: 105%;
-        background: #fff;
-        position: absolute;
+      li {
+        margin-bottom: 1.5em;
+        padding-bottom: 1.5em;
+        border-bottom: 0.0625em dashed #ccc;
+
+        &:last-child {
+          margin-bottom: 0;
+          // padding-bottom: 0;
+        }
+        h3 {
+          font-size: 1.375em;
+          line-height: 1.875em;
+          @media (max-width: 600px) {
+            font-size: 1.125em;
+          }
+        }
+        h4 {
+          margin: 0.9375em 0;
+          font-size: 1.125em;
+          line-height: 1.625em;
+          color: var(--gray);
+          @media (max-width: 600px) {
+            font-size: 1em;
+          }
+        }
+        h3,
+        h4 {
+          font-weight: 500;
+        }
+        h3,
+        h4,
+        p {
+          letter-spacing: 0.0625em;
+        }
+        p {
+          font-size: 0.875em;
+          color: var(--gray);
+        }
+        a {
+          display: inline-block;
+          padding: 0.3125em 0.625em;
+          margin-top: 1.25em;
+          font-size: 0.875em;
+          border-radius: 0.3125em;
+          border: 0.125em solid var(--gray);
+          transition: all 0.3s;
+
+          &:hover {
+            opacity: 0.7;
+          }
+
+          span {
+            display: flex;
+            align-items: center;
+          }
+        }
       }
+    }
+  }
 
-      &::after {
-        top: -0.4em;
-        right: -0.2em;
-        transform: rotate(-15deg);
+  .journal-content {
+    margin-top: 5em;
+    p {
+      line-height: 1.75em;
+    }
+    .list {
+      letter-spacing: 0.0625em;
+      .title {
+        margin-top: 6.25em;
+        padding-bottom: 1.25em;
+        font-size: 1.25em;
+        border-bottom: 0.125em solid var(--purple);
+      }
 
-        @media (max-width: 1200px) {
-          top: -0.5em;
-          right: -0.1875em;
-        }
+      .title,
+      ul li {
+        display: flex;
+        justify-content: space-between;
       }
-      &::before {
-        bottom: -0.4em;
-        right: -0.2em;
-        transform: rotate(15deg);
 
-        @media (max-width: 1200px) {
-          bottom: -0.5em;
-          right: -0.1875em;
+      ul {
+        margin-top: 1.25em;
+        li {
+          margin-top: 0.625em;
+          padding-bottom: 0.625em;
+          border-bottom: 0.125em dashed #dddddd;
+          // &:first-child {
+          //   border-bottom: 0.125em dashed #ccc;
+          // }
+          p {
+            font-weight: 400;
+          }
         }
       }
     }
   }
-  ul {
-    background-color: #fff;
-    padding: 3.125em 3.125em 0 3.125em;
+}
+
+.video-dialog {
+  h3 {
+    font-size: 1.25em;
+    font-weight: 500;
+    letter-spacing: 1px;
+  }
+
+  .close-btn {
+    .v-icon {
+      transition: all 0.2s;
+
+      &:hover {
+        opacity: 0.5 !important;
+      }
+    }
+  }
+
+  .v-card {
+    padding: 0;
+  }
+
+  .video-box {
+    position: relative;
+    padding-bottom: 56.5%;
+    overflow: hidden;
+    video {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100% !important;
+      height: 100% !important;
+    }
+  }
+}
+
+// 研發補助
+.research-content {
+  margin: auto;
+  // max-width: 50em;
+  letter-spacing: 0.0625em;
+
+  p {
+    line-height: 1.75em;
+  }
+
+  section {
+    padding: 3.125em;
+    margin-top: 1.25em;
+    border: 0.0625em solid #ccc;
+    border-radius: 0.3125em;
 
     @media (max-width: 600px) {
-      padding: 1.5em 1.5em 0 1.5em;
+      padding: 1.5em;
     }
 
-    li {
-      margin-bottom: 1.5em;
-      padding-bottom: 1.5em;
-      border-bottom: 0.0625em dashed #ccc;
+    h3 {
+      font-weight: 500;
+      font-size: 1.75em;
+      line-height: 2.25em;
 
-      &:last-child {
-        margin-bottom: 0;
-        // padding-bottom: 0;
-      }
-      h3 {
+      @media (max-width: 600px) {
         font-size: 1.375em;
-        line-height: 1.875em;
-        @media (max-width: 600px) {
-          font-size: 1.125em;
-        }
       }
-      h4 {
-        margin: 0.9375em 0;
-        font-size: 1.125em;
-        line-height: 1.625em;
-        color: var(--gray);
-        @media (max-width: 600px) {
-          font-size: 1em;
-        }
+    }
+
+    a,
+    span {
+      line-height: 1.375em;
+    }
+
+    .sub-title {
+      font-size: 1.125em;
+    }
+
+    li {
+      display: flex;
+      align-items: center;
+      &::before {
+        content: "•";
+        font-size: 2em;
+        color: #bca2b5;
+        display: inline-block;
       }
-      h3,
-      h4 {
-        font-weight: 500;
+    }
+  }
+}
+
+// 研究計畫
+.book-block {
+  line-height: 1.875em;
+  letter-spacing: 0.0625em;
+
+  @media (max-width: 600px) {
+    padding: 0 !important;
+  }
+
+  h3 {
+    text-align: center;
+    font-weight: 500;
+  }
+  h4 {
+    font-size: 1.125em;
+    font-weight: 400;
+  }
+  .cover-img {
+    position: relative;
+    cursor: pointer;
+
+    img {
+      @media (max-width: 960px) {
+        max-height: 31.25em;
       }
-      h3,
-      h4,
-      p {
-        letter-spacing: 0.0625em;
+    }
+
+    &:hover {
+      .read-btn {
+        background-color: #99b1bb;
       }
-      p {
-        font-size: 0.875em;
-        color: var(--gray);
+    }
+    .read-btn {
+      padding: 0.5em 3.125em;
+      position: absolute;
+      right: -3.125em;
+      bottom: 2.8125em;
+      font-size: 1.125em;
+      letter-spacing: 0.125em;
+      text-shadow: 0.0625em 0.0625em 0.1875em #939393;
+      background-color: #a7c2cd;
+      color: white;
+      cursor: pointer;
+      clip-path: polygon(20% 0%, 100% 0%, 80% 100%, 0% 100%);
+      transition: all 0.3s;
+      &:hover {
+        background-color: #99b1bb;
       }
-      a {
-        display: inline-block;
-        padding: 0.3125em 0.625em;
-        margin-top: 1.25em;
-        font-size: 0.875em;
-        border-radius: 0.3125em;
-        border: 0.125em solid var(--gray);
-        transition: all 0.3s;
 
-        &:hover {
-          opacity: 0.7;
-        }
-
-        span {
-          display: flex;
-          align-items: center;
-        }
+      @media (max-width: 600px) {
+        padding: 0.5em 2.1875em;
+        right: -0.9375em;
+        font-size: 1em;
       }
     }
   }
+
+  .content {
+    // 超過兩行則省略
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 5;
+    -webkit-box-orient: vertical;
+    line-break: after-white-space;
+  }
 }
 
-.journal-content {
-  margin-top: 5em;
+// 修護工藝
+.restoration-content {
+  h3 {
+    font-weight: 400;
+  }
+
+  .video {
+    position: relative;
+    padding-bottom: 56%;
+    overflow: hidden;
+    iframe {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100% !important;
+      height: 100% !important;
+    }
+  }
+
   p {
+    font-weight: 400;
+    letter-spacing: 0.0625em;
     line-height: 1.75em;
   }
-  .list {
-    letter-spacing: 0.0625em;
-    .title {
-      margin-top: 6.25em;
-      padding-bottom: 1.25em;
-      font-size: 1.25em;
-      border-bottom: 0.125em solid var(--purple);
+
+  .article-block {
+    h4 {
+      font-size: 1.625em;
+      font-weight: 400;
+      line-height: 2.5em;
     }
 
-    .title,
-    ul li {
-      display: flex;
-      justify-content: space-between;
+    .v-btn {
+      height: 4.375em;
+      color: #fff;
+      font-weight: 400;
+      border-radius: 6.25em;
+      background-color: var(--brown);
+      @media (max-width: 960px) {
+        font-size: 0.875em;
+      }
+      @media (max-width: 600px) {
+        font-size: 1em;
+      }
     }
+  }
 
-    ul {
-      margin-top: 1.25em;
+  // 工藝修護師
+  .master-list {
+    img {
+      width: 15.625em;
+      height: 15.625em;
+      object-fit: cover;
+    }
+    h3 {
+      font-size: 1.25em;
+      color: #000;
+    }
+    section {
+      text-align: center;
+      color: #606064;
       li {
-        margin-top: 0.625em;
-        padding-bottom: 0.625em;
-        border-bottom: 0.125em dashed #dddddd;
-        // &:first-child {
-        //   border-bottom: 0.125em dashed #ccc;
-        // }
-        p {
-          font-weight: 400;
-        }
+        line-height: 1.75em;
       }
     }
   }
+
+  // 修護故事
+  .story-list {
+    img {
+      width: 100%;
+      max-width: 18.75em;
+      object-fit: cover;
+      @media (max-width: 960px) {
+        max-width: 100%;
+        height: auto;
+      }
+    }
+    h3 {
+      font-size: 1.5em;
+      font-weight: 500;
+      line-height: 1.875em;
+      letter-spacing: 0.0625em;
+    }
+    p {
+      // 超過兩行則省略
+      overflow: hidden;
+      text-overflow: ellipsis;
+      display: -webkit-box;
+      -webkit-line-clamp: 4;
+      -webkit-box-orient: vertical;
+      line-break: after-white-space;
+    }
+
+    .date {
+      font-size: 1.25em;
+      font-weight: 400;
+    }
+  }
 }
+
+// h2 {
+//   padding-top: 6.25em;
+//   font-size: 1.875em;
+//   font-weight: 500;
+//   text-align: center;
+// }
+
+// .bg-img {
+//   background-image: url("@/assets/img/crafts/background.png");
+//   background-size: cover;
+//   background-position: top;
+//   @media (max-width: 960px) {
+//     background-size: contain;
+//     background-repeat: repeat;
+//   }
+// }
+
+// .read-list,
+// .book-list {
+//   p {
+//     margin-bottom: 0.625em;
+//   }
+//   h3,
+//   p {
+//     font-weight: 500;
+//     text-align: center;
+//     line-height: 1.875em;
+//   }
+// }
+
+// .read-list {
+//   .v-img {
+//     width: 100%;
+//     // height: 100%;
+//     max-height: 25em;
+//     object-fit: cover;
+//     cursor: pointer;
+
+//     @media (max-width: 600px) {
+//       width: 50%;
+//     }
+//   }
+
+//   @media (max-width: 600px) {
+//     section {
+//       width: 50%;
+//       padding: 0 0.625em;
+//       font-size: 0.875em;
+//     }
+//   }
+
+//   p {
+//     width: 18.125em;
+
+//     @media (max-width: 600px) {
+//       width: auto;
+//     }
+//   }
+// }
+
+// .book-list {
+//   margin-bottom: 6.25em;
+//   .info {
+//     width: 26.875em;
+//     padding: 0.625em 1.25em;
+//     position: absolute;
+//     right: -2.5em;
+//     bottom: -3.75em;
+//     z-index: 1;
+//     background: rgba(255, 255, 255, 0.8);
+//     border: 0.125em solid #c8cbcc;
+
+//     @media (max-width: 1280px) {
+//       width: 25em;
+//     }
+
+//     @media (max-width: 600px) {
+//       width: 20.625em;
+//       right: -0.1875em;
+//       bottom: -5em;
+//     }
+
+//     p {
+//       width: 18.75em;
+//       margin-bottom: 0;
+//       text-align: start;
+//     }
+//     span {
+//       display: block;
+//       margin-top: auto;
+//       position: absolute;
+//       bottom: 0.9375em;
+//       right: 0.9375em;
+//     }
+//   }
+// }
+
+// .pdf-list {
+//   display: flex;
+//   margin: 6.25em 0;
+//   .side-title {
+//     padding: 4.375em 0 4.375em 0.625em;
+//     display: flex;
+//     flex-direction: column;
+//     justify-content: space-between;
+//     border-radius: 0.3125em;
+//     background-color: var(--purple);
+//     h5 {
+//       padding: 1.25em 0.625em;
+//       position: relative;
+//       font-size: 1.625em;
+//       font-weight: 400;
+//       writing-mode: vertical-rl; // 垂直
+//       color: var(--purple);
+//       letter-spacing: 0.125em;
+//       background-color: #fff;
+
+//       @media (max-width: 600px) {
+//         font-size: 1.25em;
+//       }
+
+//       &::after,
+//       &::before {
+//         content: "";
+//         display: block;
+//         height: 0.9375em;
+//         width: 105%;
+//         background: #fff;
+//         position: absolute;
+//       }
+
+//       &::after {
+//         top: -0.4em;
+//         right: -0.2em;
+//         transform: rotate(-15deg);
+
+//         @media (max-width: 1200px) {
+//           top: -0.5em;
+//           right: -0.1875em;
+//         }
+//       }
+//       &::before {
+//         bottom: -0.4em;
+//         right: -0.2em;
+//         transform: rotate(15deg);
+
+//         @media (max-width: 1200px) {
+//           bottom: -0.5em;
+//           right: -0.1875em;
+//         }
+//       }
+//     }
+//   }
+//   ul {
+//     background-color: #fff;
+//     padding: 3.125em 3.125em 0 3.125em;
+
+//     @media (max-width: 600px) {
+//       padding: 1.5em 1.5em 0 1.5em;
+//     }
+
+//     li {
+//       margin-bottom: 1.5em;
+//       padding-bottom: 1.5em;
+//       border-bottom: 0.0625em dashed #ccc;
+
+//       &:last-child {
+//         margin-bottom: 0;
+//         // padding-bottom: 0;
+//       }
+//       h3 {
+//         font-size: 1.375em;
+//         line-height: 1.875em;
+//         @media (max-width: 600px) {
+//           font-size: 1.125em;
+//         }
+//       }
+//       h4 {
+//         margin: 0.9375em 0;
+//         font-size: 1.125em;
+//         line-height: 1.625em;
+//         color: var(--gray);
+//         @media (max-width: 600px) {
+//           font-size: 1em;
+//         }
+//       }
+//       h3,
+//       h4 {
+//         font-weight: 500;
+//       }
+//       h3,
+//       h4,
+//       p {
+//         letter-spacing: 0.0625em;
+//       }
+//       p {
+//         font-size: 0.875em;
+//         color: var(--gray);
+//       }
+//       a {
+//         display: inline-block;
+//         padding: 0.3125em 0.625em;
+//         margin-top: 1.25em;
+//         font-size: 0.875em;
+//         border-radius: 0.3125em;
+//         border: 0.125em solid var(--gray);
+//         transition: all 0.3s;
+
+//         &:hover {
+//           opacity: 0.7;
+//         }
+
+//         span {
+//           display: flex;
+//           align-items: center;
+//         }
+//       }
+//     }
+//   }
+// }
+
+// .journal-content {
+//   margin-top: 5em;
+//   p {
+//     line-height: 1.75em;
+//   }
+//   .list {
+//     letter-spacing: 0.0625em;
+//     .title {
+//       margin-top: 6.25em;
+//       padding-bottom: 1.25em;
+//       font-size: 1.25em;
+//       border-bottom: 0.125em solid var(--purple);
+//     }
+
+//     .title,
+//     ul li {
+//       display: flex;
+//       justify-content: space-between;
+//     }
+
+//     ul {
+//       margin-top: 1.25em;
+//       li {
+//         margin-top: 0.625em;
+//         padding-bottom: 0.625em;
+//         border-bottom: 0.125em dashed #dddddd;
+//         // &:first-child {
+//         //   border-bottom: 0.125em dashed #ccc;
+//         // }
+//         p {
+//           font-weight: 400;
+//         }
+//       }
+//     }
+//   }
+// }
 </style>

Некоторые файлы не были показаны из-за большого количества измененных файлов