tremble 2 年之前
父节点
当前提交
df28e80fcf
共有 58 个文件被更改,包括 4910 次插入0 次删除
  1. 1 0
      .env
  2. 24 0
      .gitignore
  3. 13 0
      index.html
  4. 2022 0
      manage.js
  5. 23 0
      package.json
  6. 二进制
      public/program/Build/Build1.data.unityweb
  7. 二进制
      public/program/Build/Build1.framework.js.unityweb
  8. 1 0
      public/program/Build/Build1.loader.js
  9. 二进制
      public/program/Build/Build1.wasm.unityweb
  10. 二进制
      public/program/Build/BuildTest.data.unityweb
  11. 二进制
      public/program/Build/BuildTest.framework.js.unityweb
  12. 1 0
      public/program/Build/BuildTest.loader.js
  13. 二进制
      public/program/Build/BuildTest.wasm.unityweb
  14. 17 0
      public/program/README.txt
  15. 33 0
      public/program/ServiceWorker.js
  16. 二进制
      public/program/TemplateData/favicon.ico
  17. 二进制
      public/program/TemplateData/icons/unity-logo-dark.png
  18. 二进制
      public/program/TemplateData/icons/unity-logo-light.png
  19. 二进制
      public/program/TemplateData/progress-bar-empty-dark.png
  20. 二进制
      public/program/TemplateData/progress-bar-empty-light.png
  21. 二进制
      public/program/TemplateData/progress-bar-full-dark.png
  22. 二进制
      public/program/TemplateData/progress-bar-full-light.png
  23. 8 0
      public/program/TemplateData/style.css
  24. 二进制
      public/program/TemplateData/unity-logo-dark.png
  25. 二进制
      public/program/TemplateData/unity-logo-light.png
  26. 117 0
      public/program/index copy.html
  27. 117 0
      public/program/index.html
  28. 15 0
      public/program/manifest.webmanifest
  29. 1 0
      public/vite.svg
  30. 180 0
      src/App.vue
  31. 52 0
      src/api/http.js
  32. 二进制
      src/assets/style/SourceHanSerifCN-Bold.otf
  33. 二进制
      src/assets/style/SourceHanSerifCN-Regular.otf
  34. 2 0
      src/assets/style/global.scss
  35. 49 0
      src/assets/style/my-reset.css
  36. 47 0
      src/assets/style/reset.css
  37. 163 0
      src/components/Integral-detail.vue
  38. 39 0
      src/components/Integraltoast.vue
  39. 40 0
      src/components/Introduce.vue
  40. 68 0
      src/components/Vmenu.vue
  41. 3 0
      src/config.js
  42. 23 0
      src/main.js
  43. 53 0
      src/plugin/dialog/Alert.vue
  44. 68 0
      src/plugin/dialog/Confirm.vue
  45. 22 0
      src/plugin/dialog/Dialog-content.vue
  46. 27 0
      src/plugin/dialog/Dialog.vue
  47. 70 0
      src/plugin/dialog/Toast.vue
  48. 69 0
      src/plugin/dialog/dialog.scss
  49. 93 0
      src/plugin/dialog/index.js
  50. 32 0
      src/router/index.js
  51. 32 0
      src/utils/componentHelper.js
  52. 61 0
      src/utils/index.js
  53. 5 0
      src/utils/zindex.js
  54. 55 0
      src/views/Courtyard.vue
  55. 413 0
      src/views/Editing.vue
  56. 258 0
      src/views/Integral.vue
  57. 34 0
      vite.config.js
  58. 559 0
      yarn.lock

+ 1 - 0
.env

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

+ 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?

+ 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>

文件差异内容过多而无法显示
+ 2022 - 0
manage.js


+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "vueapp",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@rollup/plugin-inject": "^5.0.3",
+    "axios": "^1.4.0",
+    "sass": "^1.62.1",
+    "sass-loader": "^13.2.2",
+    "vue": "^3.2.47",
+    "vue-router": "^4.1.6"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^4.1.0",
+    "vite": "^4.3.0"
+  }
+}

二进制
public/program/Build/Build1.data.unityweb


二进制
public/program/Build/Build1.framework.js.unityweb


文件差异内容过多而无法显示
+ 1 - 0
public/program/Build/Build1.loader.js


二进制
public/program/Build/Build1.wasm.unityweb


二进制
public/program/Build/BuildTest.data.unityweb


二进制
public/program/Build/BuildTest.framework.js.unityweb


文件差异内容过多而无法显示
+ 1 - 0
public/program/Build/BuildTest.loader.js


二进制
public/program/Build/BuildTest.wasm.unityweb


+ 17 - 0
public/program/README.txt

@@ -0,0 +1,17 @@
+unityInstance.SendMessage('Main', 'OnClickModule', 0); //点击主楼
+unityInstance.SendMessage('Main', 'OnClickModule', 1); //点击一进院
+unityInstance.SendMessage('Main', 'OnClickModule', 2); //点击二进院
+unityInstance.SendMessage('Main', 'OnClickModule', 3); //点击后院
+unityInstance.SendMessage('Main', 'OnClickModule', 4); //点击左侧田地
+unityInstance.SendMessage('Main', 'OnClickModule', -1); //点击整体预览 -默认值,刚进入场景的时候不需要调用这个
+
+
+unityInstance.SendMessage('Main', 'OnClickType', 0); //点击构建
+unityInstance.SendMessage('Main', 'OnClickType', 1); //点击颜色
+
+
+unityInstance.SendMessage('Main', 'OnClickItem', itemIndex); //点击(颜色/构建)的某一项,从0开始
+
+
+unityInstance.SendMessage('Main', 'Save', itemIndex); //点击保存
+unityInstance.SendMessage('Main', 'Cancel', itemIndex); //点击取消

+ 33 - 0
public/program/ServiceWorker.js

@@ -0,0 +1,33 @@
+const cacheName = "DefaultCompany-HeNanMuseum-0.1";
+const contentToCache = [
+    "Build/Build1.loader.js",
+    "Build/Build1.framework.js.unityweb",
+    "Build/Build1.data.unityweb",
+    "Build/Build1.wasm.unityweb",
+    "TemplateData/style.css"
+
+];
+
+self.addEventListener('install', function (e) {
+    console.log('[Service Worker] Install');
+    
+    e.waitUntil((async function () {
+      const cache = await caches.open(cacheName);
+      console.log('[Service Worker] Caching all: app shell and content');
+      await cache.addAll(contentToCache);
+    })());
+});
+
+self.addEventListener('fetch', function (e) {
+    e.respondWith((async function () {
+      let response = await caches.match(e.request);
+      console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
+      if (response) { return response; }
+
+      response = await fetch(e.request);
+      const cache = await caches.open(cacheName);
+      console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
+      cache.put(e.request, response.clone());
+      return response;
+    })());
+});

二进制
public/program/TemplateData/favicon.ico


二进制
public/program/TemplateData/icons/unity-logo-dark.png


二进制
public/program/TemplateData/icons/unity-logo-light.png


二进制
public/program/TemplateData/progress-bar-empty-dark.png


二进制
public/program/TemplateData/progress-bar-empty-light.png


二进制
public/program/TemplateData/progress-bar-full-dark.png


二进制
public/program/TemplateData/progress-bar-full-light.png


+ 8 - 0
public/program/TemplateData/style.css

@@ -0,0 +1,8 @@
+body { padding: 0; margin: 0 }
+#unity-container { position: fixed; width: 100%; height: 100%; }
+#unity-canvas { width: 100%; height: 100%; background: #231F20 }
+#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
+#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }
+#unity-progress-bar-empty { width: 141px; height: 18px; margin-top: 10px; background: url('progress-bar-empty-dark.png') no-repeat center }
+#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
+#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

二进制
public/program/TemplateData/unity-logo-dark.png


二进制
public/program/TemplateData/unity-logo-light.png


+ 117 - 0
public/program/index copy.html

@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Unity WebGL Player | HeNanMuseum</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="unity-container">
+      <canvas id="unity-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="unity-loading-bar">
+        <div id="unity-logo"></div>
+        <div id="unity-progress-bar-empty">
+          <div id="unity-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="unity-warning"> </div>
+    </div>
+    <script>
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#unity-container");
+      var canvas = document.querySelector("#unity-canvas");
+      var loadingBar = document.querySelector("#unity-loading-bar");
+      var progressBarFull = document.querySelector("#unity-progress-bar-full");
+      var warningBanner = document.querySelector("#unity-warning");
+      let unityInstance = null
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/BuildTest.loader.js";
+      var config = {
+        dataUrl: buildUrl + "/BuildTest.data.unityweb",
+        frameworkUrl: buildUrl + "/BuildTest.framework.js.unityweb",
+        codeUrl: buildUrl + "/BuildTest.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "DefaultCompany",
+        productName: "HeNanMuseum",
+        productVersion: "0.1",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Unity keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createUnityInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((unityInstanceArg) => {
+          loadingBar.style.display = "none";
+          unityInstance = unityInstanceArg
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+
+
+      window.addEventListener('message',(res)=>{
+        if (!unityInstance) return
+        if (Object.prototype.toString.call(res.data) == "[object Object]") {
+          let data = res.data.data;
+          if (res.data.source === "changeBlock") {
+            console.log('result:changeBlock',data);
+             unityInstance.SendMessage('Main', 'OnClickModule', data);
+          }
+        }
+      })
+
+    </script>
+  </body>
+</html>

+ 117 - 0
public/program/index.html

@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>Unity WebGL Player | HeNanMuseum</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="unity-container">
+      <canvas id="unity-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="unity-loading-bar">
+        <div id="unity-logo"></div>
+        <div id="unity-progress-bar-empty">
+          <div id="unity-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="unity-warning"> </div>
+    </div>
+    <script>
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#unity-container");
+      var canvas = document.querySelector("#unity-canvas");
+      var loadingBar = document.querySelector("#unity-loading-bar");
+      var progressBarFull = document.querySelector("#unity-progress-bar-full");
+      var warningBanner = document.querySelector("#unity-warning");
+      let unityInstance = null
+
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/Build1.loader.js";
+      var config = {
+        dataUrl: buildUrl + "/Build1.data.unityweb",
+        frameworkUrl: buildUrl + "/Build1.framework.js.unityweb",
+        codeUrl: buildUrl + "/Build1.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "DefaultCompany",
+        productName: "HeNanMuseum",
+        productVersion: "0.1",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Unity keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createUnityInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((instance) => {
+          loadingBar.style.display = "none";
+          unityInstance = instance
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+
+      
+      window.addEventListener('message',(res)=>{
+        if (!unityInstance) return
+        if (Object.prototype.toString.call(res.data) == "[object Object]") {
+          let data = res.data.data;
+          if (res.data.source === "changeBlock") {
+            console.log('result:changeBlock',data);
+             unityInstance.SendMessage('Main', 'OnClickModule', data);
+          }
+        }
+      })
+    </script>
+  </body>
+</html>

+ 15 - 0
public/program/manifest.webmanifest

@@ -0,0 +1,15 @@
+{
+    "name": "HeNanMuseum",
+    "short_name": "HeNanMuseum",
+    "start_url": "index.html",
+    "display": "fullscreen",
+    "background_color": "#231F20",
+    "theme_color": "#000",
+    "description": "",
+    "icons": [{
+      "src": "TemplateData/icons/unity-logo-dark.png",
+      "sizes": "144x144",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }]
+  }

文件差异内容过多而无法显示
+ 1 - 0
public/vite.svg


+ 180 - 0
src/App.vue

@@ -0,0 +1,180 @@
+
+<template>
+  <iframe class="ifr" id="ifr" :src="`program/index.html?token=${token}`" frameborder="0"></iframe>
+  <router-view class="fix-root" :userInfo="userInfo" />
+
+  <template v-if="!$route.meta.outoflist">
+    <Vmenu @clickIntro="isShowIntro = true" />
+    <div @click="isShowIntegralDetail = true" class="integral-area">
+      <img :src="`${config.cdn_url}images/icon_integral.png`" alt="">
+      <span>积分:{{ point }}</span>
+    </div>
+  </template>
+
+  <teleport to='#app'>
+    <Transition>
+      <Introduce v-if="isShowIntro" @close="isShowIntro = false" />
+    </Transition>
+    <Transition>
+      <IntegralDetail v-if="isShowIntegralDetail" @close="isShowIntegralDetail = false" />
+    </Transition>
+  </teleport>
+</template>
+
+<script setup>
+import Vmenu from "@/components/Vmenu.vue";
+import Introduce from "@/components/Introduce.vue";
+import IntegralDetail from '@/components/Integral-detail.vue'
+
+import { ref, onMounted, reactive } from 'vue'
+import http from '@/api/http.js'
+import utils from '@/utils/index.js'
+let token = utils.getQueryByName('token')
+
+const isShowIntro = ref(false)
+const isShowIntegralDetail = ref(false)
+const point = ref(0)
+
+
+let userInfo = reactive({})
+
+const root = document.documentElement;
+root.style.setProperty('--bgImage', `url(${config.cdn_url}images/background.jpg)`);
+
+onMounted(async () => {
+  let result = (await http.get('cms/wxUser/getUserInfo')).data;
+  let info = result.data
+  userInfo = info
+  point.value = info.point
+})
+
+</script>
+
+
+<style lang="scss">
+@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;
+  width: 100%;
+  height: 100%;
+  position: relative;
+  background-image: var(--bgImage);
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-position: center bottom;
+
+  .integral-area {
+    position: absolute;
+    right: 0;
+    bottom: 40px;
+    background: linear-gradient(270deg, #C1A97A 0%, rgba(193, 169, 122, 0) 100%);
+    display: flex;
+    align-items: center;
+    padding: 0 20px 0 50px;
+    z-index: 9;
+
+    >span {
+      color: #fff;
+      margin-left: 10px;
+      font-size: 14px;
+    }
+  }
+
+  .ifr {
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    left: 0%;
+    top: 0;
+  }
+
+  .fix-root {
+    pointer-events: none;
+    * {
+      pointer-events: auto;
+    }
+  }
+}
+
+* {
+  user-select: none;
+  -webkit-touch-callout: none;
+}
+
+/* 垃圾360浏览器不支持not() */
+input,
+textarea {
+  user-select: initial;
+}
+
+::-webkit-scrollbar {
+  background: rgba(193, 169, 122, 0.5);
+  width: 0.3rem;
+  height: 0.3rem;
+}
+
+/*宽度是对垂直滚动条而言,高度是对水平滚动条而言*/
+::-webkit-scrollbar-thumb {
+  background: #fff;
+  border-radius: 0.15rem;
+}
+
+::-webkit-scrollbar-corner {
+  background: rgba(193, 169, 122, 0.5);
+}
+
+.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>

+ 52 - 0
src/api/http.js

@@ -0,0 +1,52 @@
+import axios from "axios";
+import utils from '@/utils/index.js'
+
+const isProduction = process.env.NODE_ENV === 'production'
+export const baseURL = isProduction ? '' : 'http://192.168.20.55:8047/';
+
+
+let token = utils.getQueryByName('token')
+
+const http = axios.create({
+  baseURL: isProduction ? '/api/' : (baseURL + "api/"),
+  timeout: 30000,
+  headers: {
+    token // 设置请求头
+  }
+});
+
+// 请求拦截器
+http.interceptors.request.use(
+  function (config) {
+    // 发请求前打开加载提示
+    return config;
+  },
+  function (err) {
+    return Promise.reject(err);
+  }
+);
+
+// 响应拦截器
+http.interceptors.response.use(
+  function (response) {
+    if (response.data.code !== 0) {
+      if (response.data.code === 5001) {
+        return alert("登录态失效!");
+      }
+      alert("数据请求失败!");
+    }
+    return response;
+  },
+  async function (err) {
+
+    if (!err.response) {
+      alert("网络繁忙,请稍后重试!");
+    } else {
+      alert("错误!");
+    }
+
+    return Promise.reject(err);
+  }
+);
+
+export default http;

二进制
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:#961014;

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

@@ -0,0 +1,49 @@
+@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: "Microsoft YaHei-Bold, Microsoft YaHei";
+}
+
+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;
+}

+ 163 - 0
src/components/Integral-detail.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="integral-detail">
+    <div class="id-header">
+      <img class="hot" :src="`${config.cdn_url}images/icon_copper.png`" alt="">
+      <div>
+        <p>恭喜您,获得青铜勋章</p>
+        <p>您当前的积分:142</p>
+      </div>
+    </div>
+
+    <div class="id-con">
+      <ul class="table">
+        <li>行为</li>
+        <li>积分</li>
+        <li>单日上限</li>
+      </ul>
+
+      <ul class="table" v-for="item in table" :key="item.action">
+        <li>{{ item.action }}</li>
+        <li>{{ item.integral }}</li>
+        <li>{{ item.limit }}</li>
+      </ul>
+    </div>
+
+    <div class="id-btm" @click="$emit('close')">
+        <img :src="`${config.cdn_url}images/btn_back.png`" alt="">
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from "vue"
+
+const table = [
+  {
+    action: '首次登陆',
+    integral: '+500',
+    limit: '/',
+  }, {
+    action: '每日登录',
+    integral: '+100',
+    limit: '/',
+  }, {
+    action: '点赞',
+    integral: '+100',
+    limit: '500',
+  }, {
+    action: '分享',
+    integral: '+300',
+    limit: '900',
+  }, {
+    action: '青铜勋章',
+    integral: '0-2000',
+    limit: '/',
+  }, {
+    action: '白银勋章',
+    integral: '2001-5000',
+    limit: '/',
+  }, {
+    action: '黄金勋章',
+    integral: '5001-10000',
+    limit: '/',
+  }, {
+    action: '钻石勋章',
+    integral: '10001以上',
+    limit: '/',
+  },
+]
+const bgImage = ref(`url(${config.cdn_url}images/background.jpg)`)
+
+</script>
+
+<style lang="scss" scoped>
+.integral-detail {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  left: 0;
+  top: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  background-image: v-bind(bgImage);
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-position: center bottom;
+  z-index: 999;
+  .id-header {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    >img {
+      width: 150px;
+      height: auto;
+      transform: translate(30px, -10px);
+    }
+
+    >div {
+      text-align: center;
+
+      >p {
+        font-size: 14px;
+        color: #63543D;
+
+        &:first-of-type {
+          color: $font-active-color;
+          font-size: 18px;
+          margin-bottom: 10px;
+        }
+      }
+    }
+  }
+
+  .id-con {
+    background: #FEF8F1;
+    color: #63543D;
+    display: flex;
+    padding: 4px 10px;
+    transform: translateY(-20px);
+
+    .table {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+
+      >li {
+        width: 100%;
+        padding: 6px;
+        box-sizing: border-box;
+        border-bottom: 1px solid rgba(99, 84, 61, 0.5);
+        border-right: 1px solid rgba(99, 84, 61, 0.5);
+        text-align: center;
+        font-size: 14px;
+        color: rgba(99, 84, 61, 1);
+        text-align: center;
+
+        &:last-of-type {
+          border-bottom: none;
+        }
+      }
+
+      &:last-of-type {
+        >li {
+          border-right: none;
+        }
+      }
+
+    }
+  }
+
+  .id-btm{
+    margin: 10px auto 0;
+    width: 15%;
+    text-align: center;
+    >img{
+      width: 100%;
+    }
+  }
+}
+</style>

+ 39 - 0
src/components/Integraltoast.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="video">
+    <video autoplay controls :src="`${config.cdn_url}video/video.mp4`"></video>
+    <div @click="emit('close')" class="jump">跳过</div>
+  </div>
+</template>
+
+<script setup>
+import { } from "vue"
+
+ const emit = defineEmits(['close'])
+
+
+</script>
+
+<style lang="scss" scoped>
+.video {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 99;
+
+  >video {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+
+  .jump {
+    position: absolute;
+    right: 20px;
+    top: 20px;
+    color: #fff;
+    z-index: 10;
+  }
+}
+</style>

+ 40 - 0
src/components/Introduce.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="video">
+    <video autoplay controls :src="`${config.cdn_url}video/video.mp4`"></video>
+    <div @click="emit('close')" class="jump">跳过</div>
+  </div>
+</template>
+
+<script setup>
+import { } from "vue"
+
+const emit = defineEmits(['close'])
+
+</script>
+
+<style lang="scss" scoped>
+.video {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 99;
+  backdrop-filter: blur(10px);
+  background-color: rgba($color: #000000, $alpha: 0.5);
+
+  >video {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+
+  .jump {
+    position: absolute;
+    right: 20px;
+    top: 20px;
+    color: #fff;
+    z-index: 10;
+  }
+}
+</style>

+ 68 - 0
src/components/Vmenu.vue

@@ -0,0 +1,68 @@
+<template>
+  <ul class="menu">
+    <li @click="onClickItem(item)" v-for="item in list" :key="item.id">
+      <img :src="`${config.cdn_url}images/${routeName == item.id ? item.active : item.normal}.png`" alt="">
+    </li>
+  </ul>
+</template>
+
+<script setup>
+import { reactive, computed } from "vue"
+
+import { useRoute, useRouter } from 'vue-router'
+const route = useRoute()
+const router = useRouter()
+
+const emit = defineEmits(['clickIntro'])
+const routeName = computed(() => route.name)
+const list = reactive([
+  {
+    id: 'Introduce',
+    name: '文物介绍',
+    normal: 'icon_introduction_normal',
+    active: 'icon_introduction_active',
+  },
+  {
+    id: 'Courtyard',
+    name: '我的庭院',
+    normal: 'icon_mine_normal',
+    active: 'icon_mine_active',
+  },
+  {
+    id: 'Integral',
+    name: '到处逛逛',
+    normal: 'icon_hangout_normal',
+    active: 'icon_hangout_active',
+  }
+])
+
+const onClickItem = (item) => {
+  if (item.id == 'Introduce') {
+    emit('clickIntro')
+  } else {
+    router.push({ name: item.id })
+  }
+
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+.menu {
+  position: absolute;
+  right: 20px;
+  top: 2%;
+  z-index: 9;
+  display: flex;
+  flex-direction: column;
+  width: 10%;
+  >li {
+    width: 100%;
+    margin: 2px 0;
+    >img {
+      width: 100%;
+    }
+  }
+}
+</style>

+ 3 - 0
src/config.js

@@ -0,0 +1,3 @@
+export default {
+  cdn_url: 'https://culture.4dage.com/hn_museum_game/', // 部署在国内
+}

+ 23 - 0
src/main.js

@@ -0,0 +1,23 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router from './router'
+import { setup } from '@/utils/componentHelper'
+import http from '@/api/http.js'
+import Dialog, { Toast, Alert, DialogContent } from '@/plugin/dialog/index.js'
+import '@/plugin/dialog/dialog.scss'
+
+
+const components = setup(
+  Dialog, Toast, Alert, DialogContent
+)
+
+
+const CompoentsPlugin = (app) => {
+  components.forEach(component => component.install(app))
+}
+
+const app = createApp(App)
+app.config.globalProperties.config = config
+app.config.globalProperties.$Dialog = Dialog
+app.config.globalProperties.$http = http
+app.use(router).use(CompoentsPlugin).mount('#app');

+ 53 - 0
src/plugin/dialog/Alert.vue

@@ -0,0 +1,53 @@
+<template>
+    <ui-dialog>
+        <template v-slot:header>
+            <span>{{ title }}</span>
+            <i v-if="showCloseIcon" class="iconfont icon-close" @click="close"></i>
+        </template>
+        <div v-html="content"></div>
+        <template v-slot:footer v-if="showFooter">
+            <img @click="close" :src="okImg" alt="">
+        </template>
+    </ui-dialog>
+</template>
+
+<script>
+import { defineComponent } from 'vue'
+import { isFunction, omit } from '../../utils'
+export default defineComponent({
+    name: 'ui-alert',
+    props: {
+        showCloseIcon: {
+            type: Boolean,
+            default: true,
+        },
+        showFooter: {
+            type: Boolean,
+            default: true,
+        },
+        title: {
+            type: String,
+            default: '提示',
+        },
+        okImg: {
+            type: String,
+            default: '确定',
+        },
+        func: Function,
+        content: String,
+        destroy: Function,
+    },
+    setup: function (props, ctx) {
+        const close = () => {
+            if (isFunction(props.func) && props.func() === false) {
+                return
+            }
+            isFunction(props.destroy) && props.destroy()
+        }
+        return {
+            ...omit(props, 'destroy', 'func'),
+            close,
+        }
+    },
+})
+</script>

+ 68 - 0
src/plugin/dialog/Confirm.vue

@@ -0,0 +1,68 @@
+<template>
+    <ui-dialog>
+        <template v-if="$slots.content">
+            <slot name="content" />
+        </template>
+        <template v-else>
+            <img :src="centerIcon" alt="">
+            <div class="message" v-html="content"></div>
+        </template>
+        <template v-slot:footer v-if="!hideFoot">
+            <img class="button" v-if="!single" type="cancel" @click="close('no')" :src="noImg" alt="">
+            <img class="button" @click="close('ok')" :src="okImg" alt="">
+        </template>
+
+        <template v-slot:closeIcon>
+            <img :src="closeImg" @click="close('no')" class="close" alt="">
+        </template>
+    </ui-dialog>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import { isFunction, omit } from '../../utils'
+
+export default defineComponent({
+    name: 'ui-confirm',
+    props: {
+        centerIcon: {
+            type: String,
+            default: `${config.cdn_url}images/icon_tip3.png`,
+        },
+        okImg: {
+            type: String,
+            default: `${config.cdn_url}images/btn_giveup.png`,
+        },
+        noImg: {
+            type: String,
+            default: `${config.cdn_url}images/btn_cancel2.png`,
+        },
+        closeImg: {
+            type: String,
+            default: `${config.cdn_url}images/icon_cancel_b.png`,
+        },
+        single: {
+            type: Boolean,
+            default: false,
+        },
+        hideFoot: {
+            type: Boolean,
+            default: false,
+        },
+        func: Function,
+        content: String,
+        destroy: Function,
+    },
+    setup: function (props, ctx) {
+        const close = result => {
+            if (isFunction(props.func) && props.func(result) === false) {
+                return
+            }
+            isFunction(props.destroy) && props.destroy()
+        }
+        return {
+            ...omit(props, 'destroy', 'func'),
+            close,
+        }
+    },
+})
+</script>

+ 22 - 0
src/plugin/dialog/Dialog-content.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="ui-dialog__box">
+      <header v-if="$slots.header">
+          <slot name="header"></slot>
+      </header>
+      <section>
+          <slot></slot>
+      </section>
+      <footer v-if="$slots.footer">
+          <slot name="footer"></slot>
+      </footer>
+
+      <div  v-if="$slots.footer">
+        <slot name="closeIcon"></slot>
+        
+    </div>
+  </div>
+</template>
+
+<script>
+export default { name: 'ui-dialog-content' }
+</script>

+ 27 - 0
src/plugin/dialog/Dialog.vue

@@ -0,0 +1,27 @@
+<template>
+    <teleport to="body">
+        <div class="ui-dialog" :style="{ zIndex: zIndex }" v-if="show">
+            <dialog-content>
+                <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+                    <slot :name="name" v-bind="raw" />
+                </template>
+            </dialog-content>
+        </div>
+    </teleport>
+</template>
+<script>
+import { defineComponent, ref } from 'vue'
+import zindex from '../../utils/zindex'
+import DialogContent from './Dialog-content.vue'
+export default defineComponent({
+    name: "ui-dialog",
+    setup: function (props, ctx) {
+        const show = ref(true);
+        return {
+            show,
+            zIndex: zindex(),
+        };
+    },
+    components: { DialogContent }
+})
+</script>

+ 70 - 0
src/plugin/dialog/Toast.vue

@@ -0,0 +1,70 @@
+<template>
+    <teleport to="body">
+        <transition name="slide-down" mode="out-in" appear>
+            <div class="ui-toast" :style="{ zIndex: zIndex }" v-if="show">
+                <div class="ui-toast__box" :class="[type]">
+                    <i v-if="type !== 'fixed' && type" class="icon"></i>
+                    <div class="ui-toast__msg" v-html="content"></div>
+                    <i class="iconfont icon-close close" @click="close" v-if="showClose"></i>
+                </div>
+            </div>
+        </transition>
+    </teleport>
+</template>
+<script>
+import { defineComponent, onMounted, nextTick, ref } from 'vue'
+import zindex from '../../utils/zindex'
+export default defineComponent({
+    name: 'ui-toast',
+    props: {
+        type: String,
+        delay: Number,
+        content: String,
+        destroy: Function,
+        close: Function,
+        showClose: Boolean,
+    },
+    setup: function (props, ctx) {
+        const show = ref(true)
+        const close = () => {
+            show.value = false
+            nextTick(() => {
+                if (typeof props.close == 'function') {
+                    props.close()
+                }
+                typeof props.destroy === 'function' && props.destroy()
+            })
+        }
+
+        if (props.type !== 'fixed') {
+            setTimeout(() => close(), props.delay || 3000)
+        }
+        return {
+            show,
+            type: props.type,
+            close,
+            content: props.content,
+            zIndex: zindex(),
+        }
+    },
+})
+</script>
+<style lang="scss" scoped>
+.slide-down-enter-active,
+.slide-down-leave-active {
+    will-change: transform;
+    transition: all 0.35s ease-in-out;
+}
+.slide-down-enter-from {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+}
+.slide-down-enter {
+    opacity: 1;
+    transform: translate3d(-50%, 100%, 0);
+}
+.slide-down-leave-active {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+}
+</style>

+ 69 - 0
src/plugin/dialog/dialog.scss

@@ -0,0 +1,69 @@
+.ui-dialog {
+  position: fixed;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  color: #fff;
+  background-color: rgba($color: #000000, $alpha: 0.6);
+  backdrop-filter: blur(1px);
+}
+.ui-dialog__box {
+  position: relative;
+  display: inline-block;
+  min-width: 315px;
+  min-height: 100px;
+  background-color: #fff;
+  border-radius: 10px;
+  header {
+    color: #999999;
+    padding: 0 20px;
+    height: 60px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: solid 1px rgba($color: #ffffff, $alpha: 0.16);
+    font-weight: bold;
+    i {
+      cursor: pointer;
+    }
+  }
+  section {
+    padding: 30px 20px 10px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    .message {
+      text-align: center;
+      line-height: 1.7;
+      color: #63543d;
+      font-weight: bold;
+      display: block;
+      margin-top: 10px;
+      font-size: 20px;
+    }
+  }
+  footer {
+    padding: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-top: solid 1px rgba($color: #ffffff, $alpha: 0.16);
+    .button {
+      width: 160px;
+      margin-left: 10px;
+      margin-right: 10px;
+    }
+  }
+
+  .close {
+    position: absolute;
+    right: 20px;
+    top: 20px;
+  }
+}

+ 93 - 0
src/plugin/dialog/index.js

@@ -0,0 +1,93 @@
+import Dialog from './Dialog.vue'
+import Toast from './Toast.vue'
+import Alert from './Alert.vue'
+import Confirm from './Confirm.vue'
+import DialogContent from './Dialog-content.vue'
+import { mount } from '@/utils/componentHelper'
+
+Dialog.use = function use(app) {
+  Dialog.toast = function (options) {
+    Dialog.toast.hide() //清除上一个toast
+    if (typeof options == 'string') {
+      options = {
+        content: options,
+      }
+    }
+
+    const { destroy, vNode, el } = mount(Toast, {
+      app,
+      props: {
+        ...options,
+        destroy,
+      },
+    })
+
+    if (!Dialog.toast._destroys) {
+      Dialog.toast._destroys = []
+    }
+    Dialog.toast._destroys.push(destroy)
+
+    return {
+      hide: function () {
+        let destroy = null
+        while ((destroy = Dialog.toast._destroys.shift()) && destroy) {
+          destroy()
+        }
+      }.bind(this),
+    }
+  }
+  Dialog.toast.hide = function () {
+    if (Dialog.toast._destroys && Dialog.toast._destroys.length) {
+      let destroy = Dialog.toast._destroys.pop()
+      destroy && destroy()
+    }
+  }
+  Dialog.alert = function (options) {
+    if (typeof options == 'string') {
+      options = {
+        content: options,
+      }
+    }
+
+    const { destroy } = mount(Alert, {
+      app,
+      props: { ...options, destroy: () => destroy() },
+    })
+
+    this.alert.hide = function () {
+      destroy()
+    }
+
+    return this.alert
+  }
+
+  Dialog.confirm = function (options) {
+    if (typeof options == 'string') {
+      options = {
+        content: options,
+      }
+    }
+
+    let promise
+    if (!options.func) {
+      promise = new Promise(resolve => {
+        options.func = result => resolve(result === 'ok')
+      })
+    }
+
+    const { destroy } = mount(Confirm, {
+      app,
+      props: { ...options, destroy: () => destroy() },
+    })
+
+    this.confirm.hide = function () {
+      destroy()
+    }
+
+    return promise || this.confirm
+  }
+}
+
+export { Toast, Alert, Confirm, DialogContent }
+
+export default Dialog

+ 32 - 0
src/router/index.js

@@ -0,0 +1,32 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import Courtyard from '@/views/Courtyard.vue'
+import Integral from '@/views/Integral.vue'
+import Editing from '@/views/Editing.vue'
+
+
+const routes = [
+  {
+    path: '/',
+    name: 'Courtyard',
+    component: Courtyard,
+  },
+  {
+    path: '/integral',
+    name: 'Integral',
+    component: Integral,
+  },
+  {
+    path: '/editing',
+    name: 'Editing',
+    component: Editing,
+    meta: { outoflist: true }
+  }
+]
+
+const router = createRouter({
+  history: createWebHashHistory(),
+  routes
+})
+
+
+export default router

+ 32 - 0
src/utils/componentHelper.js

@@ -0,0 +1,32 @@
+
+import { createVNode, render } from 'vue'
+
+export function mount(component, { props, children, element, app } = {}) {
+    let el = element
+    let vNode = createVNode(component, props, children)
+
+    if (app && app._context) vNode.appContext = app._context
+    if (el) render(vNode, el)
+    else if (typeof document !== 'undefined') {
+        render(vNode, (el = document.createElement('div')))
+    }
+
+    const destroy = () => {
+        if (el) render(null, el)
+        el = null
+        vNode = null
+    }
+
+    return { vNode, destroy, el }
+}
+
+export function setup(...Components) {
+    Components.forEach(Component => {
+        Component.install = function (app) {
+            Component.use && Component.use(app)
+            app.component(Component.name, Component)
+        }
+    })
+
+    return Components
+}

+ 61 - 0
src/utils/index.js

@@ -0,0 +1,61 @@
+ const utils = {
+  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
+  },
+
+  getQueryByName(key) {
+    let querys = window.location.search.substring(1).split('&')
+        for (let i = 0; i < querys.length; i++) {
+            let keypair = querys[i].split('=')
+            if (keypair.length === 2 && keypair[0] === key) {
+                try {
+                    return decodeURIComponent(keypair[1])
+                } catch (error) {
+                    return keypair[1]
+                }
+            }
+        }
+        return ''
+  }
+}
+
+/**
+ * 获取忽略指定属性的对象
+ * @param {Object} obj 源对象
+ * @param  {...any} props 忽略属性
+ */
+export function omit(obj, ...props) {
+  const result = { ...obj }
+  props.forEach(function (prop) {
+    delete result[prop]
+  })
+  return result
+}
+
+export const objectToString = Object.prototype.toString
+export const toTypeString = value => objectToString.call(value)
+
+// 获取制定对象的类型比如toRawType(1) Number
+export const toRawType = value => toTypeString(value).slice(8, -1)
+
+/**
+ * 判断是否函数
+ * @param {any} target 参数对象
+ */
+export const isFunction = target => toRawType(target) === 'Function'
+
+export default utils

+ 5 - 0
src/utils/zindex.js

@@ -0,0 +1,5 @@
+let zindex = 10000
+
+export const getZIndex = () => ++zindex
+
+export default getZIndex

+ 55 - 0
src/views/Courtyard.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="courtyard">
+    <ul class="btn-area">
+      <li @click="$router.push({ name: 'Editing' })">
+        <img :src="`${config.cdn_url}images/btn_edit.png`" alt="">
+      </li>
+      <li>
+        <img :src="`${config.cdn_url}images/btn_share.png`" alt="">
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+
+export default {
+  setup(props) {
+
+  },
+  methods: {
+
+  }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.courtyard {
+  width: 100%;
+  height: 100%;
+
+  position: relative;
+
+  .btn-area {
+    position: absolute;
+    bottom: 40px;
+    transform: translateX(-50%);
+    left: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    >li {
+      margin: 0 10px;
+      width: 40%;
+
+      >img {
+        width: 100%;
+      }
+    }
+  }
+
+
+}
+</style>

+ 413 - 0
src/views/Editing.vue

@@ -0,0 +1,413 @@
+<template>
+  <div class="editing">
+    <ul class="rightbtn-area">
+      <li>
+        <img :src="`${config.cdn_url}images/btn_concert.png`" alt="">
+      </li>
+      <li @click="mycomfirm">
+        <img :src="`${config.cdn_url}images/btn_cancel.png`" alt="">
+      </li>
+    </ul>
+
+    <div class="left-area">
+      <img class="wuding" :src="`${config.cdn_url}images/wuding.png`" alt="">
+      <ul>
+        <li @click="active = item.id" :class="{ active: item.id === active }" v-for="item in list" :key="item.id">
+          <span>{{ item.name }}</span>
+          <img :src="`${config.cdn_url}images/active.png`" alt="">
+          <div class="b-line"></div>
+        </li>
+      </ul>
+    </div>
+
+    <ul v-if="active === -1" class="btm-area">
+      <li v-for="item in steps" :key="item.id">
+        <span>{{ item.name }}</span>
+        <img :src="`${config.cdn_url}images/icon_next.png`" alt="">
+      </li>
+    </ul>
+
+    <div v-else class="btm-bujian">
+      <div class="typecon">
+        <img @click="blockTypeActive='block'" :src="`${config.cdn_url}images/icon_element_${blockTypeActive === 'block' ? 'active' : 'normal'}.png`"
+          alt="">
+        <img @click="blockTypeActive='color'" :src="`${config.cdn_url}images/icon_color_${blockTypeActive === 'color' ? 'active' : 'normal'}.png`" alt="">
+      </div>
+
+      <div class="line"></div>
+
+      <div class="b-list">
+        <ul v-if="blockTypeActive === 'block'">
+          <li @click="active = item.id" :class="{ active: item.id === elementActive }" v-for="item in activeBlock"
+            :key="item.id">
+            <img :src="`${config.cdn_url}images/img0${item.id}.png`" alt="">
+          </li>
+        </ul>
+
+        <ul v-else>
+          <li @click="active = item.id" :class="{ colorActive: item.id === elementActive }"
+            :style="{background:item.val}"
+          v-for="item in activeColor"
+            :key="item.id">
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, getCurrentInstance, unref, watch } from "vue"
+import { useRouter } from 'vue-router'
+const router = useRouter()
+
+const instance = getCurrentInstance()
+const globalProperties = instance.appContext.config.globalProperties
+
+
+const list = [
+  {
+    id: 0,
+    name: '主楼',
+  }, {
+    id: 1,
+    name: '一进楼',
+  }, {
+    id: 2,
+    name: '二进院',
+  }, {
+    id: 3,
+    name: '后院',
+  }, {
+    id: 4,
+    name: '左侧田地',
+  }, {
+    id: -1,
+    name: '整体预览',
+  }
+]
+
+const steps = [
+  {
+    id: 1,
+    name: '步骤一:选择左侧各部分'
+  },
+  {
+    id: 3,
+    name: '步骤二:搭建构件和上色'
+  },
+  {
+    id: 3,
+    name: '步骤三:保存模型'
+  }
+]
+
+const activeBlock = reactive([
+  {
+    id: 1,
+    name: '步骤一:选择左侧各部分'
+  },
+  {
+    id: 2,
+    name: '步骤二:搭建构件和上色'
+  },
+  {
+    id: 3,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 4,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 5,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 6,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 7,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 8,
+    name: '步骤三:保存模型'
+  },
+  {
+    id: 9,
+    name: '步骤三:保存模型'
+  }
+])
+const activeColor = reactive([
+  {
+    id: 1,
+    val: '#D6A98C'
+  },
+  {
+    id: 2,
+    val: '#D6A98C'
+  },
+  {
+    id: 3,
+    val: '#D6A98C'
+  },
+  {
+    id: 4,
+    val: '#D6A98C'
+  },
+  {
+    id: 5,
+    val: '#D6A98C'
+  },
+  {
+    id: 6,
+    val: '#D6A98C'
+  },
+  {
+    id: 7,
+    val: '#D6A98C'
+  }
+])
+
+
+
+const active = ref(-1)
+const blockTypeActive = ref('block')
+
+
+const elementActive = ref(2)
+
+const colorActive = ref(0)
+
+
+
+let ifrDom = document.querySelector('#ifr')
+
+watch(active, () => {
+  console.log('result:', ifrDom);
+  ifrDom.contentWindow.postMessage(
+    {
+      source: "changeBlock",
+      data: unref(active),
+    },
+    "*"
+  );
+})
+
+
+const mycomfirm = () => {
+  globalProperties.$Dialog.confirm({
+    content: '放弃编辑后,信息将不会保存',
+    func: res => {
+      router.push({ name: 'Courtyard' })
+    }
+  })
+}
+
+</script>
+
+<style lang="scss" scoped>
+.editing {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .rightbtn-area {
+    position: absolute;
+    right: 30px;
+    transform: translateY(-50%);
+    top: 50%;
+    display: flex;
+    flex-direction: column;
+    width: 20%;
+
+    >li {
+      width: 100%;
+      margin: 16px 0;
+
+      >img {
+        width: 100%;
+      }
+    }
+  }
+
+  .left-area {
+    width: 20%;
+    height: 100%;
+    background: rgba(193, 169, 122, 0.5);
+    position: absolute;
+    left: 0;
+    top: 0;
+    backdrop-filter: blur(8px);
+    display: flex;
+    flex-direction: column;
+
+    .wuding {
+      width: 120%;
+    }
+
+    >ul {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-around;
+      align-items: center;
+      height: 92%;
+
+      >li {
+        color: #fff;
+        font-size: 18px;
+        font-weight: bold;
+        width: 50%;
+        display: inline-block;
+        position: relative;
+
+        >span {
+          display: inline-block;
+          width: 100%;
+          text-align-last: justify
+        }
+
+        .b-line {
+          background: linear-gradient(116deg, rgba(99, 84, 61, 0) 0%, rgba(99, 84, 61, 0.3) 50%, rgba(99, 84, 61, 0) 100%);
+          width: 120%;
+          height: 1px;
+          position: absolute;
+          bottom: -22px;
+          left: 50%;
+          transform: translateX(-50%);
+        }
+
+        &:last-of-type {
+          .b-line {
+            display: none;
+          }
+        }
+
+        >img {
+          opacity: 0;
+          display: none;
+          pointer-events: none;
+        }
+
+        &.active {
+          position: relative;
+          color: $font-active-color;
+
+          >img {
+            left: 50%;
+            top: 50%;
+            transform: translate(-50%, -50%);
+            width: 110%;
+            position: absolute;
+            opacity: 1;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .btm-area {
+    position: absolute;
+    bottom: 6px;
+    right: 0;
+    width: 80%;
+    height: 40px;
+    background: linear-gradient(270deg, rgba(193, 169, 122, 0.8) 0%, rgba(193, 169, 122, 0.3) 100%);
+    backdrop-filter: blur(4px);
+    display: flex;
+    align-items: center;
+    color: #fff;
+    justify-content: center;
+    font-size: 12px;
+    font-weight: bold;
+
+    >li {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      >img {
+        width: 20px;
+        margin: 0 20px;
+      }
+
+      &:last-of-type {
+        >img {
+          display: none;
+        }
+      }
+    }
+
+
+  }
+
+  .btm-bujian {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    width: 80%;
+    background: linear-gradient(270deg, rgba(193, 169, 122, 0.8) 0%, rgba(193, 169, 122, 0.3) 100%);
+    backdrop-filter: blur(4px);
+    display: flex;
+    align-items: center;
+    color: #fff;
+    justify-content: space-around;
+    font-size: 12px;
+    font-weight: bold;
+    padding: 10px 0;
+
+    .typecon {
+      display: flex;
+      width: 20%;
+      justify-content: flex-end;
+
+      >img {
+        margin: 0 10px;
+      }
+    }
+
+    .line {
+      width: 1px;
+      height: 50px;
+      margin: 0 10px;
+      background: linear-gradient(116deg, rgba(99, 84, 61, 0) 0%, rgba(99, 84, 61, 0.49) 51%, rgba(99, 84, 61, 0) 100%);
+    }
+
+    .b-list {
+      width: 80%;
+      white-space: nowrap;
+      overflow-x: auto;
+      overflow-y: hidden;
+      &::-webkit-scrollbar {
+        display: none;
+      }
+      >ul {
+        display: inline-block;
+        >li {
+          display: inline-block;
+          width: 50px;
+          height: 50px;
+          margin: 0 20px;
+          background: #e8deca;
+          border-radius: 2px;
+          >img {
+            width: 100%;
+            height: 100%;
+          }
+
+          &.active {
+            border: 2px solid #fff;
+          }
+          &.colorActive {
+            border: 2px solid #961014;
+          }
+        }
+
+      }
+    }
+
+  }
+}</style>

+ 258 - 0
src/views/Integral.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="integral">
+    <div class="left-area">
+      <img class="wuding" :src="`${config.cdn_url}images/wuding2.png`" alt="">
+      <ul>
+        <li @click="onClickItem(item)" :class="{ active: item.id == active.id }" v-for="item in list" :key="item.id">
+          <div class="avatar">
+            <img :src="item.avatarUrl" alt="">
+          </div>
+          <span>{{ item.nickName }}</span>
+
+          <div class="like">
+            <img :src="`${config.cdn_url}images/icon_like_${item.id == active.id ? 'active' : 'normal'}.png`" alt="">
+            <span>{{ item.star }}</span>
+          </div>
+          <div class="b-line"></div>
+        </li>
+      </ul>
+      <div class="menu">
+        <div @click="type = 'hot'">
+          <img class="hot" :src="`${config.cdn_url}images/btn_hot_${type == 'hot' ? 'active' : 'normal'}.png`" alt="">
+        </div>
+        <div @click="type = 'plaza'">
+          <img class="plaza" :src="`${config.cdn_url}images/btn_square_${type == 'plaza' ? 'active' : 'normal'}.png`"
+            alt="">
+        </div>
+      </div>
+    </div>
+
+    <div class="center-area" @click="toggleLike">
+      <img :src="`${config.cdn_url}images/icon_like2_${current.isLike ? 'active' : 'normal'}.png`" alt="">
+      <span>{{ current.num }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch } from "vue"
+import http from '@/api/http.js'
+
+
+
+let list = ref([])
+const active = ref({})
+const type = ref('hot')
+const current = ref({
+  isLike: false,
+  num: 0,
+  id: null
+})
+
+
+const getList = async () => {
+  let result = (await http.get(`cms/model/getSort/${type.value}`)).data;
+  list.value = result.data
+}
+
+const onClickItem = (item) => {
+  active.value = item
+  getLikeStatus(item)
+}
+
+const getLikeStatus = async (item) => {
+  let result = (await http.get(`cms/model/starCheck/${item.id}`)).data;
+  current.value.isLike = result
+  current.value.num = item.star
+  current.value.id = item.id
+}
+
+const toggleLike = async () => {
+  if (!current.value.id) return
+  let url = current.value.isLike ? `cms/model/starDel/${current.value.id}` : `cms/model/star/${current.value.id}`
+  await http.get(url)
+  current.value.isLike = !current.value.isLike
+  current.value.num = current.value.num + (current.value.isLike ? +1 : -1)
+  active.value.star = current.value.num
+}
+
+// const like = async (item)=>{
+
+//   let url = /api/cms/model/starCheck/{wxUserId}
+
+//   let result = (await http.get(`cms/model/getSort/${type.value}`)).data;
+//   list.value = result.data
+// }
+
+
+watch(type, () => {
+  getList()
+}, {
+  immediate: true
+})
+
+
+
+</script>
+
+<style lang="scss" scoped>
+.integral {
+  .left-area {
+    width: 28%;
+    height: 100%;
+    background: rgba(193, 169, 122, 0.5);
+    position: absolute;
+    left: 0;
+    top: 0;
+    backdrop-filter: blur(8px);
+    display: flex;
+    flex-direction: column;
+
+    .wuding {
+      width: 120%;
+    }
+
+    >ul {
+      height: calc(100% - 8px);
+      overflow-y: auto;
+      overflow-x: hidden;
+
+      >li {
+        color: #fff;
+        font-size: 18px;
+        font-weight: bold;
+        width: 100%;
+        display: flex;
+        align-items: center;
+        position: relative;
+        padding-left: 14px;
+        height: 60px;
+
+        .avatar {
+          width: 30px;
+          height: 30px;
+          border: 1px solid #FFFFFF;
+          border-radius: 4px;
+          margin-right: 10px;
+          overflow: hidden;
+
+          >img {
+            width: 100%;
+            height: 100%;
+          }
+        }
+
+        >span {
+          display: inline-block;
+          font-size: 14px;
+          text-overflow: ellipsis;
+          overflow: hidden;
+          white-space: nowrap;
+          width: 58%;
+        }
+
+        .like {
+          width: 20px;
+          height: 20px;
+          display: flex;
+          align-items: center;
+
+          >img {
+            width: 100%;
+            height: 100%;
+          }
+
+          >span {
+            font-size: 12px;
+            font-weight: normal;
+          }
+        }
+
+        .b-line {
+          background: linear-gradient(116deg, rgba(99, 84, 61, 0) 0%, rgba(99, 84, 61, 0.3) 50%, rgba(99, 84, 61, 0) 100%);
+          width: 80%;
+          height: 1px;
+          position: absolute;
+          bottom: 0;
+          left: 50%;
+          transform: translateX(-50%);
+        }
+
+        &:last-of-type {
+          .b-line {
+            display: none;
+          }
+        }
+
+        &.active {
+          position: relative;
+          background: linear-gradient(90deg, #961014 0%, rgba(150, 16, 20, 0) 100%);
+
+          .like {
+            color: $font-active-color;
+          }
+        }
+      }
+    }
+
+    .menu {
+      position: absolute;
+      right: -20%;
+      width: 18%;
+      z-index: -1;
+
+      >div {
+        width: 100%;
+        margin-top: 20px;
+        position: relative;
+
+        &::before {
+          width: 1px;
+          height: 24px;
+          content: '';
+          display: inline-block;
+          position: absolute;
+          left: 50%;
+          transform: translateX(-50%);
+          top: -22px;
+          background: $font-active-color;
+        }
+
+        >img {
+          width: 100%;
+        }
+      }
+
+    }
+  }
+
+  .center-area {
+    position: absolute;
+    bottom: 26px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 50px;
+
+    >img {
+      width: 100%;
+    }
+
+    >span {
+      position: absolute;
+      right: 0;
+      top: -2px;
+      font-size: 12px;
+      height: 20px;
+      min-width: 20px;
+      text-align: center;
+      line-height: 20px;
+      border-radius: 50%;
+      overflow: hidden;
+      display: inline-block;
+      background: #C1A97A;
+      color: #fff;
+      box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.3);
+    }
+  }
+}
+</style>

+ 34 - 0
vite.config.js

@@ -0,0 +1,34 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path';
+import inject from '@rollup/plugin-inject'
+
+
+
+// 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/config.js',
+    })
+  ],
+  base: './',
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, './src/'),
+    }
+  },
+  server: {
+    host: '0.0.0.0'
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: '@import "@/assets/style/global.scss";'
+      }
+    }
+  }
+})

+ 559 - 0
yarn.lock

@@ -0,0 +1,559 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/parser@^7.16.4":
+  version "7.21.8"
+  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
+  integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+
+"@esbuild/android-arm64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz#4aa8d8afcffb4458736ca9b32baa97d7cb5861ea"
+  integrity sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==
+
+"@esbuild/android-arm@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.17.18.tgz#74a7e95af4ee212ebc9db9baa87c06a594f2a427"
+  integrity sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==
+
+"@esbuild/android-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.17.18.tgz#1dcd13f201997c9fe0b204189d3a0da4eb4eb9b6"
+  integrity sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==
+
+"@esbuild/darwin-arm64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz#444f3b961d4da7a89eb9bd35cfa4415141537c2a"
+  integrity sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==
+
+"@esbuild/darwin-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz#a6da308d0ac8a498c54d62e0b2bfb7119b22d315"
+  integrity sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==
+
+"@esbuild/freebsd-arm64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz#b83122bb468889399d0d63475d5aea8d6829c2c2"
+  integrity sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==
+
+"@esbuild/freebsd-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz#af59e0e03fcf7f221b34d4c5ab14094862c9c864"
+  integrity sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==
+
+"@esbuild/linux-arm64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz#8551d72ba540c5bce4bab274a81c14ed01eafdcf"
+  integrity sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==
+
+"@esbuild/linux-arm@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz#e09e76e526df4f665d4d2720d28ff87d15cdf639"
+  integrity sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==
+
+"@esbuild/linux-ia32@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz#47878860ce4fe73a36fd8627f5647bcbbef38ba4"
+  integrity sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==
+
+"@esbuild/linux-loong64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz#3f8fbf5267556fc387d20b2e708ce115de5c967a"
+  integrity sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==
+
+"@esbuild/linux-mips64el@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz#9d896d8f3c75f6c226cbeb840127462e37738226"
+  integrity sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==
+
+"@esbuild/linux-ppc64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz#3d9deb60b2d32c9985bdc3e3be090d30b7472783"
+  integrity sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==
+
+"@esbuild/linux-riscv64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz#8a943cf13fd24ff7ed58aefb940ef178f93386bc"
+  integrity sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==
+
+"@esbuild/linux-s390x@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz#66cb01f4a06423e5496facabdce4f7cae7cb80e5"
+  integrity sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==
+
+"@esbuild/linux-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz#23c26050c6c5d1359c7b774823adc32b3883b6c9"
+  integrity sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==
+
+"@esbuild/netbsd-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz#789a203d3115a52633ff6504f8cbf757f15e703b"
+  integrity sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==
+
+"@esbuild/openbsd-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz#d7b998a30878f8da40617a10af423f56f12a5e90"
+  integrity sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==
+
+"@esbuild/sunos-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz#ecad0736aa7dae07901ba273db9ef3d3e93df31f"
+  integrity sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==
+
+"@esbuild/win32-arm64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz#58dfc177da30acf956252d7c8ae9e54e424887c4"
+  integrity sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==
+
+"@esbuild/win32-ia32@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz#340f6163172b5272b5ae60ec12c312485f69232b"
+  integrity sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==
+
+"@esbuild/win32-x64@0.17.18":
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz#3a8e57153905308db357fd02f57c180ee3a0a1fa"
+  integrity sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==
+
+"@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.2.1"
+  resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.2.1.tgz#c3ccce9956e8cdca946f465188777e4e3e488f6a"
+  integrity sha512-ZTZjzo7bmxTRTkb8GSTwkPOYDIP7pwuyV+RV53c9PYUouwcbkIZIvWvNWlX2b1dYZqtOv7D6iUAnJLVNGcLrSw==
+
+"@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":
+  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"
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+axios@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.npmmirror.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
+  integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==
+  dependencies:
+    follow-redirects "^1.15.0"
+    form-data "^4.0.0"
+    proxy-from-env "^1.1.0"
+
+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"
+
+combined-stream@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+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==
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+esbuild@^0.17.5:
+  version "0.17.18"
+  resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.17.18.tgz#f4f8eb6d77384d68cd71c53eb6601c7efe05e746"
+  integrity sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==
+  optionalDependencies:
+    "@esbuild/android-arm" "0.17.18"
+    "@esbuild/android-arm64" "0.17.18"
+    "@esbuild/android-x64" "0.17.18"
+    "@esbuild/darwin-arm64" "0.17.18"
+    "@esbuild/darwin-x64" "0.17.18"
+    "@esbuild/freebsd-arm64" "0.17.18"
+    "@esbuild/freebsd-x64" "0.17.18"
+    "@esbuild/linux-arm" "0.17.18"
+    "@esbuild/linux-arm64" "0.17.18"
+    "@esbuild/linux-ia32" "0.17.18"
+    "@esbuild/linux-loong64" "0.17.18"
+    "@esbuild/linux-mips64el" "0.17.18"
+    "@esbuild/linux-ppc64" "0.17.18"
+    "@esbuild/linux-riscv64" "0.17.18"
+    "@esbuild/linux-s390x" "0.17.18"
+    "@esbuild/linux-x64" "0.17.18"
+    "@esbuild/netbsd-x64" "0.17.18"
+    "@esbuild/openbsd-x64" "0.17.18"
+    "@esbuild/sunos-x64" "0.17.18"
+    "@esbuild/win32-arm64" "0.17.18"
+    "@esbuild/win32-ia32" "0.17.18"
+    "@esbuild/win32-x64" "0.17.18"
+
+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"
+
+follow-redirects@^1.15.0:
+  version "1.15.2"
+  resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
+  integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+
+form-data@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+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"
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+  version "2.1.35"
+  resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+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==
+
+postcss@^8.1.10, postcss@^8.4.23:
+  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"
+
+proxy-from-env@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+  integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+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.21.0:
+  version "3.21.5"
+  resolved "https://registry.npmmirror.com/rollup/-/rollup-3.21.5.tgz#1fbae43dc1079497b04604707f1cf979e51bfe49"
+  integrity sha512-a4NTKS4u9PusbUJcfF4IMxuqjFzjm6ifj76P54a7cKnvVzJaG12BLVR+hgU2YDGHzyMMQNxLAZWuALsn8q2oQg==
+  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.1:
+  version "1.62.1"
+  resolved "https://registry.npmmirror.com/sass/-/sass-1.62.1.tgz#caa8d6bf098935bc92fc73fa169fb3790cacd029"
+  integrity sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==
+  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==
+
+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.5"
+  resolved "https://registry.npmmirror.com/vite/-/vite-4.3.5.tgz#3871fe0f4b582ea7f49a85386ac80e84826367d9"
+  integrity sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==
+  dependencies:
+    esbuild "^0.17.5"
+    postcss "^8.4.23"
+    rollup "^3.21.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+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"