tremble 2 年之前
父節點
當前提交
4e64df0564
共有 63 個文件被更改,包括 2116 次插入0 次删除
  1. 1 0
      .env
  2. 1 0
      .env.production
  3. 1 0
      .env.test
  4. 24 0
      .gitignore
  5. 3 0
      .vscode/extensions.json
  6. 13 0
      index.html
  7. 26 0
      package.json
  8. 1 0
      public/vite.svg
  9. 83 0
      src/App.vue
  10. 二進制
      src/assets/images/bg.jpg
  11. 二進制
      src/assets/images/icon_cancel.png
  12. 二進制
      src/assets/images/icon_home.png
  13. 二進制
      src/assets/images/icon_search.png
  14. 二進制
      src/assets/images/icon_tip.png
  15. 二進制
      src/assets/images/icon_title_active.png
  16. 二進制
      src/assets/images/icon_title_normal.png
  17. 二進制
      src/assets/images/img_active.png
  18. 二進制
      src/assets/images/img_normal.png
  19. 二進制
      src/assets/images/label.png
  20. 二進制
      src/assets/images/label_end.png
  21. 二進制
      src/assets/images/label_pop.png
  22. 二進制
      src/assets/images/long-image/chaguo/chaguo.jpg
  23. 二進制
      src/assets/images/long-image/duanwuxunyou/duanwuxunyou.jpg
  24. 二進制
      src/assets/images/long-image/jinhuadan/jinhuadan.jpg
  25. 二進制
      src/assets/images/long-image/maijibing/maijibing.jpg
  26. 二進制
      src/assets/images/long-image/sanmiaoshendan/sanmiaoshendan.jpg
  27. 二進制
      src/assets/images/long-image/subiao/subiao.jpg
  28. 二進制
      src/assets/images/long-image/xiajiang/xiajiang.jpg
  29. 二進制
      src/assets/images/long-image/yaoxianjiu/yaoxianjiu.jpg
  30. 二進制
      src/assets/images/long-image/zhongqiuduige/zhongqiuduige.jpg
  31. 二進制
      src/assets/images/pop.png
  32. 二進制
      src/assets/images/zhanwei.jpg
  33. 二進制
      src/assets/style/SourceHanSerifCN-Bold.otf
  34. 二進制
      src/assets/style/SourceHanSerifCN-Regular.otf
  35. 2 0
      src/assets/style/global.scss
  36. 50 0
      src/assets/style/my-reset.css
  37. 47 0
      src/assets/style/reset.css
  38. 1 0
      src/assets/vue.svg
  39. 47 0
      src/components/SquareWord.vue
  40. 262 0
      src/components/directory.vue
  41. 134 0
      src/components/menu.vue
  42. 23 0
      src/components/times/chaguo.vue
  43. 115 0
      src/components/times/conclusion.vue
  44. 23 0
      src/components/times/duanwuxunyou.vue
  45. 23 0
      src/components/times/jinhuadan.vue
  46. 23 0
      src/components/times/maijibing.vue
  47. 23 0
      src/components/times/sanmiaoshendan.vue
  48. 23 0
      src/components/times/subiao.vue
  49. 23 0
      src/components/times/xiajiang.vue
  50. 23 0
      src/components/times/yaoxianjiu.vue
  51. 23 0
      src/components/times/zhongqiuduige.vue
  52. 3 0
      src/config.js
  53. 3 0
      src/config.prod.js
  54. 93 0
      src/data/index.js
  55. 109 0
      src/main.js
  56. 5 0
      src/mixins/time-lazy-load.js
  57. 25 0
      src/router/index.js
  58. 22 0
      src/store/index.js
  59. 19 0
      src/utils/index.js
  60. 13 0
      src/views/Home.vue
  61. 233 0
      src/views/LongImage.vue
  62. 46 0
      vite.config.js
  63. 527 0
      yarn.lock

+ 1 - 0
.env

@@ -0,0 +1 @@
+VITE_APP_TITLE=dev

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VITE_APP_TITLE=dev

+ 1 - 0
.env.test

@@ -0,0 +1 @@
+VITE_APP_TITLE=test

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
+    <title>唐家湾记忆非遗一张图</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 26 - 0
package.json

@@ -0,0 +1,26 @@
+{
+  "name": "vueapp",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "test": "vite --mode test",
+    "prod": "vite  --mode production",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@rollup/plugin-inject": "^5.0.3",
+    "pinia": "^2.0.35",
+    "sass": "^1.62.0",
+    "sass-loader": "^13.2.2",
+    "swiper": "^9.2.4",
+    "vue": "^3.2.47",
+    "vue-router": "^4.1.6"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "vite": "^4.3.0"
+  }
+}

文件差異過大導致無法顯示
+ 1 - 0
public/vite.svg


+ 83 - 0
src/App.vue

@@ -0,0 +1,83 @@
+
+<template>
+  <router-view/>
+</template>
+
+<script setup>
+</script>
+
+
+<style>
+@import "@/assets/style/reset.css";
+@import "@/assets/style/my-reset.css";
+
+html, body {
+  height: 100%;
+  overscroll-behavior: none;
+  overflow: hidden;
+}
+
+#app {
+  font-family: Avenir, Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background-image: url(@/assets/images/bg.jpg);
+  background-size: cover;
+  width: 100%;
+  height: 100%;
+}
+
+* {
+  user-select: none;
+  -webkit-touch-callout: none;
+}
+
+/* 垃圾360浏览器不支持not() */
+input, textarea {
+  user-select: initial;
+}
+
+::-webkit-scrollbar { background: #dddecc; width: 0.3rem; height: 0.3rem; } /*宽度是对垂直滚动条而言,高度是对水平滚动条而言*/
+::-webkit-scrollbar-thumb { background: #828a5b; border-radius: 0.15rem; }
+::-webkit-scrollbar-corner { background: #dddecc; }
+
+.fade-out-leave-active {
+  transition: opacity 1s;
+}
+.fade-out-leave-to {
+  opacity: 0;
+}
+
+.animation-show-hide {
+  animation: show-hide 1.8s infinite;
+}
+@keyframes show-hide {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
+
+i {
+  font-style: italic;
+}
+
+.viewer-container {
+  background-color: rgba(0, 0, 0, 80%) !important;
+}
+
+.v-enter-active,
+.v-leave-active {
+  transition: opacity 0.5s ease;
+}
+
+.v-enter-from,
+.v-leave-to {
+  opacity: 0;
+}
+</style>

二進制
src/assets/images/bg.jpg


二進制
src/assets/images/icon_cancel.png


二進制
src/assets/images/icon_home.png


二進制
src/assets/images/icon_search.png


二進制
src/assets/images/icon_tip.png


二進制
src/assets/images/icon_title_active.png


二進制
src/assets/images/icon_title_normal.png


二進制
src/assets/images/img_active.png


二進制
src/assets/images/img_normal.png


二進制
src/assets/images/label.png


二進制
src/assets/images/label_end.png


二進制
src/assets/images/label_pop.png


二進制
src/assets/images/long-image/chaguo/chaguo.jpg


二進制
src/assets/images/long-image/duanwuxunyou/duanwuxunyou.jpg


二進制
src/assets/images/long-image/jinhuadan/jinhuadan.jpg


二進制
src/assets/images/long-image/maijibing/maijibing.jpg


二進制
src/assets/images/long-image/sanmiaoshendan/sanmiaoshendan.jpg


二進制
src/assets/images/long-image/subiao/subiao.jpg


二進制
src/assets/images/long-image/xiajiang/xiajiang.jpg


二進制
src/assets/images/long-image/yaoxianjiu/yaoxianjiu.jpg


二進制
src/assets/images/long-image/zhongqiuduige/zhongqiuduige.jpg


二進制
src/assets/images/pop.png


二進制
src/assets/images/zhanwei.jpg


二進制
src/assets/style/SourceHanSerifCN-Bold.otf


二進制
src/assets/style/SourceHanSerifCN-Regular.otf


+ 2 - 0
src/assets/style/global.scss

@@ -0,0 +1,2 @@
+$theme-color:rgb(255,255,255);
+$font-active-color:#783435;

+ 50 - 0
src/assets/style/my-reset.css

@@ -0,0 +1,50 @@
+@font-face {
+  font-family: 'Source Han Serif CN';
+  src: url(./SourceHanSerifCN-Regular.otf);
+}
+
+@font-face {
+  font-family: 'Source Han Serif CN-Bold';
+  src: url(./SourceHanSerifCN-Bold.otf);
+}
+
+* {
+  /* 阻止safari在用户交互设置一些元素的背景色 */
+  -webkit-tap-highlight-color: transparent;
+  font-family: 'Source Han Serif CN';
+
+}
+
+html {
+  overflow: hidden;
+}
+
+body {
+  text-align: justify;
+}
+
+a {
+  color: initial;
+  text-decoration: initial;
+  outline: none;
+}
+
+button {
+  padding: 0;
+  cursor: pointer;
+  background-color: initial;
+  border: initial;
+  outline: none;
+}
+
+img {
+  user-select: none;
+}
+
+menu {
+  list-style-type: initial;
+}
+
+li {
+  display: initial;
+}

+ 47 - 0
src/assets/style/reset.css

@@ -0,0 +1,47 @@
+/* http://meyerweb.com/eric/tools/css/reset/ 
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+	display: block;
+}
+body {
+	line-height: 1;
+}
+ol, ul {
+	list-style: none;
+}
+blockquote, q {
+	quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 47 - 0
src/components/SquareWord.vue

@@ -0,0 +1,47 @@
+<template>
+  <ul :class="['square-word',size]">
+    <li :style="{ backgroundImage: `url(${bgimg})` }" v-for="item in word" :key="item">
+      {{ item }}
+    </li>
+  </ul>
+</template>
+
+<script setup>
+const props = defineProps({
+  word: String,
+  size: {
+    default: 'normal',
+    type: String
+  }
+})
+
+let bgimg = utils.getImageUrl('label.png');
+
+
+</script>
+
+<style lang="scss" scoped>
+.square-word {
+  >li {
+    width: 4rem;
+    height: 4rem;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    font-size: 2.5rem;
+    color: $font-active-color;
+    font-weight: bold;
+    margin-bottom: .63rem;
+  }
+
+  &.mini{
+    >li{
+      width: 2.5rem;
+      height: 2.5rem;
+      font-size: 1.5rem;
+    }
+  }
+}
+</style>

+ 262 - 0
src/components/directory.vue

@@ -0,0 +1,262 @@
+<template>
+    <div class="dir-con">
+      <ul class="dir-header">
+        <li @click="swiperInstance.slideTo(index)" :class="{active:index==current}" v-for="(item,index) in dirlist" :key="item.id">
+          {{ item.name }}
+        </li>
+      </ul>
+      <img @click="emit('close')" draggable="false" class="close" :src="closeImg" alt="">
+
+      <swiper class="dir-body" :modules="[Mousewheel]" :slides-per-view="'auto'"
+       :space-between="50" :mousewheel="true"
+        @swiper="onSwiper" @slideChange="onSlideChange">
+        <swiper-slide v-for="item in dirlist" :key="item.id">
+          <SquareWord :word="item.name" size="mini"/>
+          <div class="db-slide">
+            <ul class="imgcon">
+              <li :class="`imgli${index+1}`" v-for="(sub,index) in item.imgs" :key="sub.name">
+                  <img :src="sub.img" alt="">
+                  <div class="mask">{{ sub.name }}</div>
+              </li>
+            </ul>
+          </div>
+        </swiper-slide>
+      </swiper>
+    </div>
+</template>
+
+<script setup>
+import { nextTick,onMounted, ref } from "vue"
+ // import Swiper core and required modules
+import { Mousewheel } from 'swiper';
+
+// Import Swiper Vue.js components
+import { Swiper, SwiperSlide } from 'swiper/vue';
+import SquareWord from "@/components/SquareWord.vue";
+
+// Import Swiper styles
+import 'swiper/css';
+import 'swiper/css/mousewheel';
+import 'swiper/css/pagination';
+import 'swiper/css/scrollbar';
+
+const emit = defineEmits(['close']);
+
+const dirlist = [{
+  id: 'meishi',
+  name: '美食',
+  imgs: [
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    }
+  ],
+}, {
+  id: 'minsu',
+  name: '民俗',
+  imgs: [
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    }
+  ],
+}, {
+  id: 'jiyi',
+  name: '技艺',
+  imgs: [
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    },
+    {
+      img: utils.getImageUrl(`zhanwei.jpg`),
+      name: '炸糖环'
+    }
+  ],
+}]
+
+const current =  ref(0)
+
+const swiperInstance = ref(null)
+
+const closeImg = utils.getImageUrl(`icon_cancel.png`);
+
+
+const onSlideChange = (swiper)=>{
+  current.value = swiper.activeIndex
+}
+
+const onSwiper = (swiper)=>{
+  swiperInstance.value = swiper
+  console.log('result:', swiperInstance.value );
+}
+
+onMounted(() => {
+  nextTick(()=>{
+    // swiperInstance.value.changeDirection()
+  })
+})
+
+
+</script>
+
+<style lang="scss" scoped>
+.dir-con {
+  padding: 3rem;
+  width: 100%;
+  height: 100%;
+  background-image: url(@/assets/images/bg.jpg);
+  position: fixed;
+  left: 0;
+  top: 0;
+  box-sizing: border-box;
+  z-index: 999;
+  .dir-header {
+    border-bottom: 1px dashed #978867;
+    text-align: center;
+    padding-bottom: 2rem;
+    >li {
+      margin: 4rem 6rem;
+      font-size: 2rem;
+      color: #695757;
+      cursor: pointer;
+
+      &:hover,
+      &.active {
+        color: #72928D;
+        font-weight: bold;
+      }
+    }
+  }
+
+  .close {
+    position: absolute;
+    right: 3rem;
+    top: 3rem;
+    cursor: pointer;
+  }
+
+  .dir-body{
+    width: 100%;
+    margin-top: 1.2rem;
+    height: 85%;
+    .swiper-wrapper{
+      .swiper-slide{
+        width: 80%;
+        display: flex;
+        .db-slide{
+          margin-left: 1.25rem;
+          .imgcon{
+            height: 100%;
+            font-size: 0;
+            >li{
+              display: inline-block;
+              height: 49%;
+              overflow: hidden;
+              font-size: 0;
+              box-sizing: border-box;
+              cursor: pointer;
+              position: relative;
+              &:hover{
+                .mask{
+                  background: rgba(114,146,141,0.8);
+                  height: 30%;
+                  transform: translateY(-50%);
+                  top: 50%;
+                  color: #fff;
+                }
+              }
+              .mask{
+                  position: absolute;
+                  left: 0;
+                  top: 0;
+                  bottom: 0;
+                  right: 0;
+                  width: 100%;
+                  height: 100%;
+                  background: rgba(0,0,0,0.6);
+                  z-index: 1;
+                  display: flex;
+                  color: transparent;
+                  justify-content: center;
+                  align-items: center;
+                  font-size: 1.5rem;
+                  transition: .3s ease all;
+                }
+              >img{
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+              }
+            }
+            .imgli1{
+              width: 60%;
+              margin-bottom: 1%;
+            }
+            .imgli2{
+              width: 39%;
+              margin-left: 1%;
+              margin-bottom: 1%;
+            }
+            .imgli3{
+              width: 30%;
+              
+            }
+            .imgli4{
+              width: 38%;
+              margin: 0 1%;
+            }
+            .imgli5{
+              width: 30%;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 134 - 0
src/components/menu.vue

@@ -0,0 +1,134 @@
+<template>
+  <div class="bottom-menu">
+    <ul class="b-ul">
+      <li :class="['b-li',`${idx==currentTimeIdx?'active':''}`]" @click="emit('onClickTimeItem', idx)" v-for="(timeItem, idx) in list" :key="timeItem.id">
+        <div></div>
+        <p>{{ timeItem.info.textCn }}</p>
+      </li>
+    </ul>
+
+    <ul class="b-right" >
+      <li class="br-li" @click="emit('onClickMenuItem', item)" v-for="item in rmenu" :key="item.id">
+        <img :src="item.img" alt="">
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script setup>
+import { shallowReactive } from "vue"
+
+const props = defineProps({
+  list: Array,
+  currentTimeIdx:Number
+})
+
+
+const emit = defineEmits(['onClickTimeItem','onClickMenuItem'])
+
+const rmenu = shallowReactive([{
+  name: '帮助',
+  img: utils.getImageUrl(`icon_tip.png`),
+  id: 'tip'
+}, {
+  name: '搜索',
+  img: utils.getImageUrl(`icon_search.png`),
+  id: 'search'
+}, {
+  name: '主页',
+  img: utils.getImageUrl(`icon_home.png`),
+  id: 'home'
+}
+])
+</script>
+
+<style lang="scss" scoped>
+.bottom-menu {
+  --thiscolor: #72928E;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 5rem;
+  background: rgba(255, 241, 209, 0.5);
+  box-shadow: 0px 0px 50px 0px rgba(206, 186, 141, 0.6), inset 0px 2px 2px 0px rgba(255, 255, 255, 0.25);
+  backdrop-filter: blur(20px);
+  display: flex;
+  align-items: center;
+
+  .b-ul {
+    display: flex;
+    width: 80%;
+    position: relative;
+
+    &::before {
+      border-top: 1px dashed var(--thiscolor);
+      width: 90%;
+      height: 1px;
+      content: '';
+      display: inline-block;
+      position: absolute;
+      top: 0.4rem;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+
+    .b-li {
+      text-align: center;
+      flex: 1;
+      cursor: pointer;
+      transition: .2s ease transform;
+
+      >div {
+        width: 0.8rem;
+        height: 0.8rem;
+        border-radius: 50%;
+        display: inline-block;
+        background: var(--thiscolor);
+        position: relative;
+
+        &::after {
+          position: absolute;
+          width: 200%;
+          height: 200%;
+          content: '';
+          border-radius: 50%;
+          display: inline-block;
+          border: 1px solid var(--thiscolor);
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+        }
+      }
+
+      >p {
+        color: var(--thiscolor);
+        font-size: 0.8rem;
+        margin-top: 1rem;
+      }
+
+      &:hover,&.active {
+        transform: scale(1.1);
+        --thiscolor: #783435;
+      }
+    }
+  }
+
+  .b-right {
+    display: flex;
+    width: 20%;
+    position: relative;
+    justify-content: center;
+
+    .br-li {
+      margin: 0 1rem;
+      cursor: pointer;
+      width: 2rem;
+      >img {
+        width: 100%;
+      }
+    }
+  }
+}
+</style>

+ 23 - 0
src/components/times/chaguo.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 115 - 0
src/components/times/conclusion.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="home">
+    <div class="conclusion">
+      <SquareWord :word="info.textCn" />
+      <ul class="c-ul">
+        <li class="c-li">游玩到这里就告一段落了,苏裱︿岭南苏裱﹀装裱修复技艺让书画成为历史的记忆保存下来,</li>
+        <li class="c-li">而这幅“唐家湾记忆”非遗长卷,就由你我来共同守护,使其历百年而不湮,让非遗之美光彩如初。</li>
+        <li class="c-li">谢谢你的参与,共同组成这幅非遗长卷,欢迎亲临唐家湾!</li>
+      </ul>
+    </div>
+
+    <ul class="conclusion-r">
+      <li  class="conclusion-r-li" @click="emit('onClickTimeItem', idx)" v-for="(timeItem, idx) in fixList"
+        :key="timeItem.id">
+        <div>
+          <img :src="timeItem.img" alt="">
+          <img class="active" :src="timeItem.imgactive" alt="">
+        </div>
+        <p>{{ timeItem.info.textCn }}</p>
+      </li>
+    </ul>
+
+  </div>
+</template>
+
+<script setup>
+import { defineProps, computed } from 'vue'
+import SquareWord from "@/components/SquareWord.vue";
+import timeList from "@/data/index";
+
+const props = defineProps({
+  info: Object
+})
+
+const emit = defineEmits(['onClickTimeItem'])
+
+const fixList = computed(() => {
+  let temp = timeList.map(item => {
+    return {
+      ...item,
+      img: utils.getImageUrl(`img_normal.png`),
+      imgactive: utils.getImageUrl(`img_active.png`)
+    }
+  })
+  // temp.pop()
+  return temp
+}
+)
+
+
+</script>
+
+<style lang="scss" scoped>
+.home {
+  margin-top: -2rem;
+  justify-content: flex-end!important;
+  .conclusion {
+    display: flex;
+    width: 14%;
+
+    .c-ul {
+      margin-left: 1.43rem;
+      display: flex;
+
+      .c-li {
+        display: inline-block;
+        writing-mode: vertical-lr;
+        font-size: 0.7rem;
+        color: #695757;
+        line-height: 1.5;
+        text-align: left;
+      }
+    }
+  }
+
+  .conclusion-r {
+    width: 76%;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+
+    .conclusion-r-li {
+      display: inline-block;
+      width: 22%;
+
+      >div {
+        width: 100%;
+
+        >img {
+          width: 100%;
+          cursor: pointer;
+
+          &.active {
+            display: none;
+          }
+        }
+
+        &:hover {
+          >img {
+            width: 100%;
+            display: none;
+
+            &.active {
+              display: inline-block;
+            }
+          }
+        }
+      }
+
+      >p {
+        color: #695757;
+      }
+    }
+  }
+}</style>

+ 23 - 0
src/components/times/duanwuxunyou.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/jinhuadan.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/maijibing.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/sanmiaoshendan.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    width: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/subiao.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/xiajiang.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/yaoxianjiu.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 23 - 0
src/components/times/zhongqiuduige.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="home">
+    <img :src="image" draggable="false" alt="">
+  </div>
+</template>
+
+<script setup>
+import {defineProps} from 'vue'
+const props = defineProps({
+  info: Object
+})
+
+let image = utils.getImageUrl(`long-image/${props.info.id}/${props.info.img}`);
+
+</script>
+
+<style lang="scss" scoped>
+.home{
+  >img{
+    height: 100%;
+  }
+}
+</style>

+ 3 - 0
src/config.js

@@ -0,0 +1,3 @@
+export default {
+  country: 'cn', // 部署在国内
+}

+ 3 - 0
src/config.prod.js

@@ -0,0 +1,3 @@
+export default {
+  country: 'cn', // 部署在国内
+}

+ 93 - 0
src/data/index.js

@@ -0,0 +1,93 @@
+import Chaguo from "@/components/times/chaguo.vue"
+import Duanwuxunyou from "@/components/times/duanwuxunyou.vue"
+import Jinhuadan from "@/components/times/jinhuadan.vue"
+import Maijibing from "@/components/times/maijibing.vue"
+import Sanmiaoshendan from "@/components/times/sanmiaoshendan.vue"
+import Subiao from "@/components/times/subiao.vue"
+import Xiajiang from "@/components/times/xiajiang.vue"
+import Yaoxianjiu from "@/components/times/yaoxianjiu.vue"
+import Zhongqiuduige from "@/components/times/zhongqiuduige.vue"
+import Conclusion from "@/components/times/conclusion.vue"
+
+export default [
+  {
+    id: 'chaguo',
+    info: {
+      textCn: '茶果',
+      img: 'chaguo.jpg'
+    },
+    component: Chaguo
+  },
+  {
+    id: 'duanwuxunyou',
+    info: {
+      textCn: '端午巡游',
+      img: 'duanwuxunyou.jpg'
+    },
+    component: Duanwuxunyou
+  },
+  {
+    id: 'jinhuadan',
+    info: {
+      textCn: '金花诞',
+      img: 'jinhuadan.jpg'
+    },
+    component: Jinhuadan
+  },
+  {
+    id: 'maijibing',
+    info: {
+      textCn: '麦记饼',
+      img: 'maijibing.jpg'
+    },
+    component: Maijibing
+  },
+  {
+    id: 'sanmiaoshendan',
+    info: {
+      textCn: '唐家三庙神诞',
+      img: 'sanmiaoshendan.jpg'
+    },
+    component: Sanmiaoshendan
+  },
+  {
+    id: 'subiao',
+    info: {
+      textCn: '苏裱',
+      img: 'subiao.jpg'
+    },
+    component: Subiao
+  },
+  {
+    id: 'xiajiang',
+    info: {
+      textCn: '虾酱',
+      img: 'xiajiang.jpg'
+    },
+    component: Xiajiang
+  },
+  {
+    id: 'yaoxianjiu',
+    info: {
+      textCn: '药线疚',
+      img: 'yaoxianjiu.jpg'
+    },
+    component: Yaoxianjiu
+  },
+  {
+    id: 'zhongqiuduige',
+    info: {
+      textCn: '中秋对歌',
+      img: 'zhongqiuduige.jpg'
+    },
+    component: Zhongqiuduige
+  },
+  {
+    id: 'conclusion',
+    info: {
+      textCn: '结语',
+    },
+    component: Conclusion
+  }
+  
+]

+ 109 - 0
src/main.js

@@ -0,0 +1,109 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+import { createPinia } from 'pinia'
+
+const app = createApp(App)
+app.use(router)
+.use(createPinia())
+.mount('#app');
+
+app.config.globalProperties.config = config
+
+const idealWindowInnerHeight = 1015 // 设计稿的高度
+const idealRootFontSize = 24 // 设计稿里选择的根元素尺寸
+const appNode = document.querySelector('body')
+
+if (/Mobi|Android|iPhone|SymbianOS|Windows Phone|iPad|iPod/i.test(navigator.userAgent)) {
+  app.config.globalProperties.$isMobile = true
+  document.getElementById('app').classList.add('mobile')
+  // 禁止多手指操作缩放
+  document.addEventListener('touchstart', function(event) {
+    if (event.touches.length > 1) {
+      event.preventDefault()
+    }
+  }, { passive: false, capture: true })
+
+  // // 禁止双击放大
+  // var lastTouchEnd = 0
+  // document.documentElement.addEventListener('touchend', function(event) {
+  //   var now = Date.now()
+  //   if (now - lastTouchEnd <= 300) {
+  //     event.preventDefault()
+  //   }
+  //   lastTouchEnd = now
+  // }, {
+  //   passive: false
+  // })
+} else {
+  app.config.globalProperties.$isMobile = false
+  // app.config.globalProperties.$isMobile = true
+  // document.getElementById('app').classList.add('mobile')
+}
+
+if (/WeChat/i.test(navigator.userAgent)) {
+  app.config.globalProperties.$isWeChat = true
+}
+if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
+  app.config.globalProperties.$isSafari = true
+}
+if (/firefox/i.test(navigator.userAgent)) {
+  app.config.globalProperties.$isFirefox = true
+}
+
+/* 傻逼safari中要等一下才能拿到准确的视口尺寸。所以刚加载时要做一些骚操作。所以先不显示页面。 */
+if (app.config.globalProperties.$isSafari) {
+  appNode.style.opacity = '0'
+  setTimeout(() => {
+    appNode.style.opacity = ''
+  }, 3000)
+}
+
+// 傻逼safari中往往要等一下才能拿到准确的视口尺寸,但刷新后却可以立刻拿到准确的尺寸。所以判断下视口尺寸是否有变化,有变化就刷新重来。
+const shabiSafari = window.innerHeight
+setTimeout(() => {
+  if (shabiSafari !== window.innerHeight) {
+    location.reload()
+  }
+}, 2500)
+
+function onResize() {
+  if ( app.config.globalProperties.$isMobile && window.innerHeight > window.innerWidth ) {
+    app.config.globalProperties.$isRotate = true
+    appNode.style.width = appNode.parentNode.clientHeight + 'px'
+    appNode.style.height = appNode.parentNode.clientWidth + 'px'
+    appNode.style.transformOrigin = `${appNode.parentNode.clientWidth / 2}px ${appNode.parentNode.clientWidth / 2}px`
+    appNode.style.transform = 'rotate(90deg)'
+  } else {
+    app.config.globalProperties.$isRotate = false
+    appNode.style.width = '100%'
+    appNode.style.height = '100%'
+    appNode.style.transformOrigin = ``
+    appNode.style.transform = ''
+  }
+
+  if (app.config.globalProperties.$isRotate) {
+    app.config.globalProperties.$oneRemToPx = window.innerWidth * idealRootFontSize / idealWindowInnerHeight
+    app.config.globalProperties.$windowSizeX = window.innerHeight
+    app.config.globalProperties.$windowSizeY = window.innerWidth
+    if (app.config.globalProperties.$isMobile && app.config.globalProperties.$isSafari) {
+      appNode.classList.remove('homepage-need-handle')
+    }
+  } else {
+    app.config.globalProperties.$oneRemToPx = window.innerHeight * idealRootFontSize / idealWindowInnerHeight
+    app.config.globalProperties.$windowSizeX = window.innerWidth
+    app.config.globalProperties.$windowSizeY = window.innerHeight
+    if (app.config.globalProperties.$isMobile && app.config.globalProperties.$isSafari) {
+      appNode.classList.add('homepage-need-handle')
+    }
+  }
+  document.documentElement.style.fontSize = app.config.globalProperties.$oneRemToPx + 'px'
+}
+
+onResize()
+
+window.addEventListener('resize', () => {
+  onResize()
+})
+
+

+ 5 - 0
src/mixins/time-lazy-load.js

@@ -0,0 +1,5 @@
+
+
+export default{
+
+}

+ 25 - 0
src/router/index.js

@@ -0,0 +1,25 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import HomeView from '@/views/Home.vue'
+import LongImage from '../views/LongImage.vue'
+
+const routes = [
+  {
+    path: '/',
+    name: 'home-view',
+    component: HomeView,
+  },
+  {
+    path: '/long-image',
+    name: 'long-image',
+    component: LongImage,
+  }
+  
+]
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes
+})
+
+
+export default router

+ 22 - 0
src/store/index.js

@@ -0,0 +1,22 @@
+import { defineStore } from 'pinia';
+
+const appStore = defineStore('app', {
+  state: () => ({
+    isNeedStartupVideo: true,
+    isNeedLongImageVideo: true,
+    usingChinese: true,
+    canMoveCamera: true,
+    canMoveCameraInTreasureAppr: true,
+    canPlayLongImageBgAudio: true,
+    longImageTranslateLengthRecord: null,
+    isNeedLongImageGuide: true,
+    isNeedTreasureApprGuide: true,
+    isNeedTreasureApprDetailGuide: true,
+  }),
+  getters: {},
+  actions: {
+   
+  },
+});
+
+export default appStore;

+ 19 - 0
src/utils/index.js

@@ -0,0 +1,19 @@
+export default {
+  throttle: function (fn, interval) {
+    let lastRunTime = 0
+
+    return function (...args) {
+      let elapsedTime = Date.now() - lastRunTime
+      if (elapsedTime < interval) {
+        return
+      }
+
+      let context = this
+      lastRunTime = Date.now()
+      fn.apply(context, args)
+    }
+  },
+  getImageUrl(name) {
+    return new URL(`../assets/images/${name}`, import.meta.url).href
+  }
+}

+ 13 - 0
src/views/Home.vue

@@ -0,0 +1,13 @@
+
+<template>
+  
+</template>
+
+<script setup>
+
+</script>
+
+
+<style lang="scss" scoped>
+
+</style>

+ 233 - 0
src/views/LongImage.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="long-image" @mousedown="onMouseDown" @mousemove="onMouseMove" @mouseup="onMouseUp"
+    @mouseleave="onMouseLeave" @touchstart.passive="onTouchStart" @touchmove.prevent="onTouchMove" @touchend="onTouchEnd"
+    @touchcancel="onTouchCancel" @wheel.passive="onWheel">
+    <div>
+      <component :is="timeItem.component" v-for="(timeItem, index) in timeList"
+        :info="{ ...timeItem.info, id: timeItem.id }" :style="{
+            left: `calc(100% * ${index} - ${translateLength}px)`,
+          }" @onClickTimeItem="onClickTimeItem" :key="timeItem.id" class="time-item" />
+    </div>
+
+    <Vmenu :currentTimeIdx="currentTimeIdx" @onClickMenuItem="onClickMenuItem" @onClickTimeItem="onClickTimeItem" :list="timeList" />
+
+    <teleport to="body">
+      <Transition>
+        <div v-if="isShowDir">
+          <Directory @close="isShowDir = false" />
+        </div>
+      </Transition>
+    </teleport>
+
+  </div>
+</template>
+
+<script setup>
+
+import { ref, getCurrentInstance,watch, onMounted } from "vue"
+import appStore from "@/store/index";
+import timeList from "@/data/index";
+import Vmenu from "@/components/menu.vue"
+import Directory from "@/components/directory.vue"
+
+const store = appStore();
+
+const isMouseDown = ref(false);
+const lastMoveEventTimeStamp = ref(0);
+const moveSpeed = ref(0);
+const lastTouchPos = ref(0);
+const maxTranslateLength = ref(0);
+
+
+// 动画帧相关
+const lastAnimationTimeStamp = ref(0);
+const animationFrameId = ref(0);
+
+
+// 镜头平移相关
+const translateLength = ref(0);
+
+const currentTimeIdx = ref(0);
+
+const instance = getCurrentInstance()
+const globalProperties = instance.appContext.app.config.globalProperties
+
+const isShowDir = ref(false)
+
+// 热点视频相关
+const isShowVideoHotspotDetail = ref(false);
+const videoHotspotUrl = ref('');
+
+// 时辰菜单显隐。为了兼容移动端,只好用js代替css。
+const isShowTimeList = ref(false);
+const timeBgAudioForSafari = ref(null);
+const isShowGuideImage = ref(false);
+
+
+
+const animationFrameTask = () => {
+  const timeStamp = Date.now()
+  const timeElapsed = timeStamp - lastAnimationTimeStamp.value
+
+  // 速度减慢
+  if (moveSpeed.value > 0) {
+    moveSpeed.value -= (globalProperties.$isMobile ? 0.001 : 0.003) * timeElapsed
+    if (moveSpeed.value < 0) {
+      moveSpeed.value = 0
+    }
+  } else if (moveSpeed.value < 0) {
+    moveSpeed.value += (globalProperties.$isMobile ? 0.001 : 0.003) * timeElapsed
+    if (moveSpeed.value > 0) {
+      moveSpeed.value = 0
+    }
+  }
+
+  // 根据速度更新距离
+  if (store.canMoveCamera) {
+    translateLength.value += moveSpeed.value * timeElapsed
+    if (translateLength.value < 0) {
+      translateLength.value = 0
+    } else if (translateLength.value > maxTranslateLength.value) {
+      translateLength.value = maxTranslateLength.value
+      moveSpeed.value = 0
+    }
+  }
+
+  lastAnimationTimeStamp.value = timeStamp
+  animationFrameId.value = requestAnimationFrame(animationFrameTask)
+}
+
+
+const onClickTimeItem = (index) => {
+  translateLength.value = instance.ctx.$el.offsetWidth * index
+  console.log('result:', instance.ctx.$el.offsetWidth * index);
+}
+
+const onClickMenuItem = (item) => {
+  console.log('result:', item);
+  if (item.id === 'search') {
+    isShowDir.value = true
+  }
+}
+
+const calcTranslateLimit = () => {
+  maxTranslateLength.value = globalProperties.$windowSizeX * (timeList.length - 1)
+}
+
+const onMouseDown = () => {
+  isMouseDown.value = true
+  moveSpeed.value = 0
+  lastMoveEventTimeStamp.value = 0
+  lastAnimationTimeStamp.value = Date.now()
+}
+
+
+const onMouseMove = (e) => {
+  if (isMouseDown.value) {
+    // 有些pc端浏览器比如firefox会有两次事件时间戳相同的情况发生。
+    if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
+      // 更新speed
+      const currentMoveSpeed = - e.movementX / (e.timeStamp - lastMoveEventTimeStamp.value)
+      moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
+    }
+    lastMoveEventTimeStamp.value = e.timeStamp
+  }
+}
+
+const onMouseUp = () => {
+  isMouseDown.value = false
+}
+
+const onMouseLeave = () => {
+  isMouseDown.value = false
+}
+
+const onTouchStart = (e) => {
+  isMouseDown.value = true
+  moveSpeed.value = 0
+  lastMoveEventTimeStamp.value = 0
+  lastAnimationTimeStamp.value = Date.now()
+  lastTouchPos.value = (globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX)
+}
+
+
+const onTouchMove = (e) => {
+  if (isMouseDown.value && e.changedTouches.length === 1) {
+    // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0
+    if (lastMoveEventTimeStamp.value && (e.timeStamp - lastMoveEventTimeStamp.value > 1)) {
+      // 更新speed
+      const currentMoveSpeed = - ((globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX) - lastTouchPos.value) / (e.timeStamp - lastMoveEventTimeStamp.value) * (globalProperties.$isFirefox ? 2.2 : 1.5)
+      moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1
+      lastTouchPos.value = globalProperties.$isRotate ? e.changedTouches[0].clientY : e.changedTouches[0].clientX
+    }
+    lastMoveEventTimeStamp.value = e.timeStamp
+  }
+}
+
+
+const onTouchEnd = () => {
+  isMouseDown.value = false
+}
+
+
+const onTouchCancel = () => {
+  isMouseDown.value = false
+}
+
+
+const onWheel = (e) => {
+  if (store.canMoveCamera) {
+    translateLength.value += e.deltaY
+    if (translateLength.value < 0) {
+      translateLength.value = 0
+    } else if (translateLength.value > maxTranslateLength.value) {
+      translateLength.value = maxTranslateLength.value
+      moveSpeed.value = 0
+    }
+  }
+}
+
+watch(translateLength, (vNew) => {
+    try {
+      currentTimeIdx.value = Math.round(translateLength.value / instance.ctx.$el.offsetWidth)
+      console.log(currentTimeIdx.value)
+    } catch (error) {
+      console.error('translateLength error: ', error)
+    }
+})
+
+onMounted(() => {
+  animationFrameId.value = requestAnimationFrame(animationFrameTask)
+
+  if (store.longImageTranslateLengthRecord) {
+    translateLength.value = store.longImageTranslateLengthRecord
+    store.longImageTranslateLengthRecord = null
+  }
+  calcTranslateLimit()
+  window.addEventListener('resize', calcTranslateLimit)
+})
+
+
+
+
+</script>
+
+<style lang="scss" scoped>
+.long-image {
+  height: 100%;
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+
+  .time-item {
+    position: absolute;
+    top: 0;
+    height: 100%;
+    width: 100%;
+    text-align: center;
+    justify-content: center;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 46 - 0
vite.config.js

@@ -0,0 +1,46 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path';
+import inject from '@rollup/plugin-inject'
+
+let configFileName = ''
+switch (process.env.APP_MODE) {
+case 'dev':
+  configFileName = 'config.js'
+  break
+case 'prod':
+  configFileName = 'config.prod.js'
+  break
+default:
+  configFileName = 'config.js'
+  break
+}
+
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  assetsInclude: /\.(png|jpe?g|gif|svg|woff2?|ttf|otf|eot)$/i,
+  plugins: [
+    vue(),
+    inject({
+      utils: '/src/utils/index.js',
+      config: `/src/${configFileName}`,
+    })
+  ],
+  base: './',
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, './src/'),
+    }
+  },
+  server: {
+    host: '0.0.0.0'
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: '@import "@/assets/style/global.scss";'
+      }
+    }
+  }
+})

+ 527 - 0
yarn.lock

@@ -0,0 +1,527 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/parser@^7.16.4":
+  version "7.21.4"
+  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
+  integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
+
+"@esbuild/android-arm64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31"
+  integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg==
+
+"@esbuild/android-arm@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065"
+  integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg==
+
+"@esbuild/android-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f"
+  integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA==
+
+"@esbuild/darwin-arm64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196"
+  integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ==
+
+"@esbuild/darwin-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20"
+  integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg==
+
+"@esbuild/freebsd-arm64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87"
+  integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA==
+
+"@esbuild/freebsd-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b"
+  integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw==
+
+"@esbuild/linux-arm64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6"
+  integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw==
+
+"@esbuild/linux-arm@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b"
+  integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg==
+
+"@esbuild/linux-ia32@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212"
+  integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q==
+
+"@esbuild/linux-loong64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40"
+  integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw==
+
+"@esbuild/linux-mips64el@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b"
+  integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A==
+
+"@esbuild/linux-ppc64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c"
+  integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ==
+
+"@esbuild/linux-riscv64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f"
+  integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA==
+
+"@esbuild/linux-s390x@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade"
+  integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ==
+
+"@esbuild/linux-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79"
+  integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg==
+
+"@esbuild/netbsd-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c"
+  integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA==
+
+"@esbuild/openbsd-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff"
+  integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA==
+
+"@esbuild/sunos-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5"
+  integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q==
+
+"@esbuild/win32-arm64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54"
+  integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q==
+
+"@esbuild/win32-ia32@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e"
+  integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ==
+
+"@esbuild/win32-x64@0.17.17":
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9"
+  integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg==
+
+"@jridgewell/sourcemap-codec@^1.4.13":
+  version "1.4.15"
+  resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@rollup/plugin-inject@^5.0.3":
+  version "5.0.3"
+  resolved "https://registry.npmmirror.com/@rollup/plugin-inject/-/plugin-inject-5.0.3.tgz#0783711efd93a9547d52971db73b2fb6140a67b1"
+  integrity sha512-411QlbL+z2yXpRWFXSmw/teQRMkXcAAC8aYTemc15gwJRpvEVDQwoe+N/HTFD8RFG8+88Bme9DK2V9CVm7hJdA==
+  dependencies:
+    "@rollup/pluginutils" "^5.0.1"
+    estree-walker "^2.0.2"
+    magic-string "^0.27.0"
+
+"@rollup/pluginutils@^5.0.1":
+  version "5.0.2"
+  resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
+  integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==
+  dependencies:
+    "@types/estree" "^1.0.0"
+    estree-walker "^2.0.2"
+    picomatch "^2.3.1"
+
+"@types/estree@^1.0.0":
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+  integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
+"@vitejs/plugin-vue@^4.1.0":
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073"
+  integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==
+
+"@vue/compiler-core@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.47.tgz#3e07c684d74897ac9aa5922c520741f3029267f8"
+  integrity sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
+"@vue/compiler-dom@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz#a0b06caf7ef7056939e563dcaa9cbde30794f305"
+  integrity sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==
+  dependencies:
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/compiler-sfc@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz#1bdc36f6cdc1643f72e2c397eb1a398f5004ad3d"
+  integrity sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/compiler-ssr" "3.2.47"
+    "@vue/reactivity-transform" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
+"@vue/compiler-ssr@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee"
+  integrity sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==
+  dependencies:
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/devtools-api@^6.4.5", "@vue/devtools-api@^6.5.0":
+  version "6.5.0"
+  resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
+  integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
+
+"@vue/reactivity-transform@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
+  integrity sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+
+"@vue/reactivity@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.47.tgz#1d6399074eadfc3ed35c727e2fd707d6881140b6"
+  integrity sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==
+  dependencies:
+    "@vue/shared" "3.2.47"
+
+"@vue/runtime-core@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.47.tgz#406ebade3d5551c00fc6409bbc1eeb10f32e121d"
+  integrity sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==
+  dependencies:
+    "@vue/reactivity" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/runtime-dom@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz#93e760eeaeab84dedfb7c3eaf3ed58d776299382"
+  integrity sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==
+  dependencies:
+    "@vue/runtime-core" "3.2.47"
+    "@vue/shared" "3.2.47"
+    csstype "^2.6.8"
+
+"@vue/server-renderer@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.47.tgz#8aa1d1871fc4eb5a7851aa7f741f8f700e6de3c0"
+  integrity sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==
+  dependencies:
+    "@vue/compiler-ssr" "3.2.47"
+    "@vue/shared" "3.2.47"
+
+"@vue/shared@3.2.47":
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
+  integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
+
+anymatch@~3.1.2:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
+binary-extensions@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+  integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+braces@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
+"chokidar@>=3.0.0 <4.0.0":
+  version "3.5.3"
+  resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+  integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+  dependencies:
+    anymatch "~3.1.2"
+    braces "~3.0.2"
+    glob-parent "~5.1.2"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.6.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+csstype@^2.6.8:
+  version "2.6.21"
+  resolved "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
+  integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
+
+esbuild@^0.17.5:
+  version "0.17.17"
+  resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.17.tgz#fa906ab11b11d2ed4700f494f4f764229b25c916"
+  integrity sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA==
+  optionalDependencies:
+    "@esbuild/android-arm" "0.17.17"
+    "@esbuild/android-arm64" "0.17.17"
+    "@esbuild/android-x64" "0.17.17"
+    "@esbuild/darwin-arm64" "0.17.17"
+    "@esbuild/darwin-x64" "0.17.17"
+    "@esbuild/freebsd-arm64" "0.17.17"
+    "@esbuild/freebsd-x64" "0.17.17"
+    "@esbuild/linux-arm" "0.17.17"
+    "@esbuild/linux-arm64" "0.17.17"
+    "@esbuild/linux-ia32" "0.17.17"
+    "@esbuild/linux-loong64" "0.17.17"
+    "@esbuild/linux-mips64el" "0.17.17"
+    "@esbuild/linux-ppc64" "0.17.17"
+    "@esbuild/linux-riscv64" "0.17.17"
+    "@esbuild/linux-s390x" "0.17.17"
+    "@esbuild/linux-x64" "0.17.17"
+    "@esbuild/netbsd-x64" "0.17.17"
+    "@esbuild/openbsd-x64" "0.17.17"
+    "@esbuild/sunos-x64" "0.17.17"
+    "@esbuild/win32-arm64" "0.17.17"
+    "@esbuild/win32-ia32" "0.17.17"
+    "@esbuild/win32-x64" "0.17.17"
+
+estree-walker@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+  integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+fsevents@~2.3.2:
+  version "2.3.2"
+  resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+glob-parent@~5.1.2:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+  dependencies:
+    is-glob "^4.0.1"
+
+immutable@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
+  integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
+
+is-binary-path@~2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+  version "4.0.3"
+  resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+klona@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
+  integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
+
+magic-string@^0.25.7:
+  version "0.25.9"
+  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
+  integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
+  dependencies:
+    sourcemap-codec "^1.4.8"
+
+magic-string@^0.27.0:
+  version "0.27.0"
+  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
+  integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==
+  dependencies:
+    "@jridgewell/sourcemap-codec" "^1.4.13"
+
+nanoid@^3.3.6:
+  version "3.3.6"
+  resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+  integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
+neo-async@^2.6.2:
+  version "2.6.2"
+  resolved "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+picocolors@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pinia@^2.0.35:
+  version "2.0.35"
+  resolved "https://registry.npmmirror.com/pinia/-/pinia-2.0.35.tgz#aa2597038bb55ea14ad689f83065d2814ebb8c10"
+  integrity sha512-P1IKKQWhxGXiiZ3atOaNI75bYlFUbRxtJdhPLX059Z7+b9Z04rnTZdSY8Aph1LA+/4QEMAYHsTQ638Wfe+6K5g==
+  dependencies:
+    "@vue/devtools-api" "^6.5.0"
+    vue-demi "*"
+
+postcss@^8.1.10, postcss@^8.4.21:
+  version "8.4.23"
+  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
+  integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
+  dependencies:
+    nanoid "^3.3.6"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
+readdirp@~3.6.0:
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+  dependencies:
+    picomatch "^2.2.1"
+
+rollup@^3.20.2:
+  version "3.20.7"
+  resolved "https://registry.npmmirror.com/rollup/-/rollup-3.20.7.tgz#4f045dfb388abe08dd159f8cd286dcaca1e80b28"
+  integrity sha512-P7E2zezKSLhWnTz46XxjSmInrbOCiul1yf+kJccMxT56vxjHwCbDfoLbiqFgu+WQoo9ij2PkraYaBstgB2prBA==
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+sass-loader@^13.2.2:
+  version "13.2.2"
+  resolved "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.2.tgz#f97e803993b24012c10d7ba9676548bf7a6b18b9"
+  integrity sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==
+  dependencies:
+    klona "^2.0.6"
+    neo-async "^2.6.2"
+
+sass@^1.62.0:
+  version "1.62.0"
+  resolved "https://registry.npmmirror.com/sass/-/sass-1.62.0.tgz#3686b2195b93295d20765135e562366b33ece37d"
+  integrity sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==
+  dependencies:
+    chokidar ">=3.0.0 <4.0.0"
+    immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
+
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map@^0.6.1:
+  version "0.6.1"
+  resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sourcemap-codec@^1.4.8:
+  version "1.4.8"
+  resolved "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+  integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+ssr-window@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz#dc6b3ee37be86ac0e3ddc60030f7b3bc9b8553be"
+  integrity sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==
+
+swiper@^9.2.4:
+  version "9.2.4"
+  resolved "https://registry.npmmirror.com/swiper/-/swiper-9.2.4.tgz#2fa3cf58cef586366f674a10fa56fe6eec2026fe"
+  integrity sha512-L7y3K/iiMXNYQ94FbfcJn7jex4QPnS4+voXGupTdC+UHW4XrR40QDdm4c9hXJ+Br0Il7PP0vP1W3goM9/Ly6Sg==
+  dependencies:
+    ssr-window "^4.0.2"
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+vite@^4.3.0:
+  version "4.3.1"
+  resolved "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz#9badb1377f995632cdcf05f32103414db6fbb95a"
+  integrity sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==
+  dependencies:
+    esbuild "^0.17.5"
+    postcss "^8.4.21"
+    rollup "^3.20.2"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+vue-demi@*:
+  version "0.14.0"
+  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.0.tgz#dcfd9a9cf9bb62ada1582ec9042372cf67ca6190"
+  integrity sha512-gt58r2ogsNQeVoQ3EhoUAvUsH9xviydl0dWJj7dabBC/2L4uBId7ujtCwDRD0JhkGsV1i0CtfLAeyYKBht9oWg==
+
+vue-router@^4.1.6:
+  version "4.1.6"
+  resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1"
+  integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==
+  dependencies:
+    "@vue/devtools-api" "^6.4.5"
+
+vue@^3.2.47:
+  version "3.2.47"
+  resolved "https://registry.npmmirror.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
+  integrity sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==
+  dependencies:
+    "@vue/compiler-dom" "3.2.47"
+    "@vue/compiler-sfc" "3.2.47"
+    "@vue/runtime-dom" "3.2.47"
+    "@vue/server-renderer" "3.2.47"
+    "@vue/shared" "3.2.47"