Bladeren bron

feat: add horizontal nav (#45)

* feat: add horizontal nav

* workflow: update linter.yml

* fix: update

* fix: update

* fix: update

* Rename Link.vue to link.vue

* Rename SidebarItem.vue to sidebarItem.vue

* Rename Logo.vue to logo.vue

* Rename AppMain.vue to appMain.vue

* Rename Navbar.vue to navbar.vue

* fix: update

* fix: update

* fix: update

* workflow: update linter.yml

* fix: update

* chore: update

* workflow: update

* fix: update

* fix: update

* fix: update

* perf: 外链功能实现方式改变

* style: update nav style

* perf: 优化国际化

* fix: update

* fix: update
啝裳 3 jaren geleden
bovenliggende
commit
fd8de45bff

+ 5 - 5
package.json

@@ -29,7 +29,7 @@
     "dayjs": "^1.10.6",
     "dotenv": "^8.2.0",
     "echarts": "^5.1.2",
-    "element-plus": "^1.1.0-beta.12",
+    "element-plus": "^1.1.0-beta.16",
     "element-resize-detector": "^1.2.3",
     "font-awesome": "^4.7.0",
     "lodash-es": "^4.17.21",
@@ -40,10 +40,10 @@
     "path-to-regexp": "^6.2.0",
     "pinia": "^2.0.0-rc.6",
     "resize-observer-polyfill": "^1.5.1",
-    "responsive-storage": "^1.0.9",
+    "responsive-storage": "^1.0.10",
     "sortablejs": "1.13.0",
     "v-contextmenu": "^3.0.0",
-    "vue": "3.2.11",
+    "vue": "^3.2.19",
     "vue-i18n": "^9.2.0-beta.3",
     "vue-json-pretty": "^2.0.2",
     "vue-router": "^4.0.11",
@@ -66,7 +66,7 @@
     "@typescript-eslint/parser": "^4.31.0",
     "@vitejs/plugin-vue": "^1.6.0",
     "@vitejs/plugin-vue-jsx": "^1.1.7",
-    "@vue/compiler-sfc": "3.2.11",
+    "@vue/compiler-sfc": "^3.2.19",
     "@vue/eslint-config-prettier": "^6.0.0",
     "@vue/eslint-config-typescript": "^7.0.0",
     "autoprefixer": "^10.2.4",
@@ -91,7 +91,7 @@
     "stylelint-order": "^4.1.0",
     "typescript": "^4.4.2",
     "unplugin-element-plus": "^0.0.1",
-    "vite": "^2.5.7",
+    "vite": "^2.5.10",
     "vite-plugin-mock": "^2.9.6",
     "vite-plugin-style-import": "^1.2.1",
     "vite-svg-loader": "^2.2.0",

+ 8 - 10
src/assets/iconfont/iconfont.css

@@ -1,14 +1,8 @@
 @font-face {
-  font-family: "iconfont";
-  src: url("iconfont.eot?t=1619360751585"); /* IE9 */
-  src: url("iconfont.eot?t=1619360751585#iefix") format("embedded-opentype"),
-    /* IE6-IE8 */
-      url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAARMAAsAAAAACbwAAAP9AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDQAqGEIRiATYCJAMYCw4ABCAFhG0Hfhs2CMgOJS2d2ECiAwlgBN+vlf0+zE5mg6wAXeJZIjpmuSejwkZFpjyxij57rCLU/p9b+c8NWt6VZsJElIM/LOtvdta6uELEuI1RpSiTq877eC1W5aXc/38/V+f5b2lrettv68Xkvj8zA0+cJTJJQyKSxKSSKBWPlNixsM5M+mTE89a4m0BnrRop+8ZmViBbIQ4KxE1DVyC7cisjwtDamp4zi3ihatOz9BfAc//xMQNNkU1SM3Hk4cujGgyMi5MjaYPhCecti35/94OKjG1AIR72hm9Li+Vt0nSLYro9YHl8K0nj4vjWyaffTiaTMMEW0xUVO53ZUn8t//CqJBMNgbPB31+pjC/TNMH4FprC+FaayuRTmsTkiFuGvdnj1KXAOIiV4K+OJFWaiyg0Ta9XlNpZFnNMMU87Fu6JpEd7eVb2opxYflpfv3a6POca5EdfirMurHNGCE8kd+I1Aga56YZpli8QTd2pkM13zTZ3LWH/yp07FYsroO6K+8wWApapQmIJS/sos3zFfAfHn7h7YNkyBXLpDpx4e1dclrGWJZBjHTZlH7GTZu5+x+JBl122Uu7Dnneswsb7GzdAYdAeAQNsuPCgpUXf26No0Vtakrt6EdLv73FsGfVc+stJczdJknB3izcB36+MxPaRNRuxkhDbHm3uWlgXdtH0nPRZONzVMmLbhDTJuGaPNCfq0vWC2f87RVXNo+ZKmG1whgeqbK2MNlctwFnlMtis7d3MsMNhGBuNtnQt7IjdW4k7NbE3+trR+Ch7WcWwpjkOHUkI4nbFjG3xcHdz1HEIabtS38eCpsmEOO2WrO1tMUMd5tprJzKSyJyclDN2xNNTI/96bFx+DY/OZ1M/Agzf5q9pwwDDd2mR2fk3+hv//gtHjxX1/Gv7BYy+e/TXTxieKPKA/wJB1v9p9MQFZWkUnqbM59f7CDSOyHhvg3RyEX+W+EJf98UsIXcTWksyJD1rIGutYxf+Nqj6dmQarevsprPVOM7vW4RJE2UWtjjLIMz3BpK5PkA23x678L9AtdRvaMyPNOicjLIr9m0II3eYocJRk1bjkqEG/FT5eG7oXUI97FVYafRSgixk5KTGuoZmbRz9yPZxTCiiN3FOJcoCPmmM3Q+93oAUZAE3qrzOxXmwq76e9r1SnRrwQa69GKTgkEayKk5iUAX4Ubs0l9t6/yVIF+alYCPzLlQJxIQYG5c0qtNAYI+jnzTvuWwfEqFrwnF0th/FBPhIxhBEXlkOkAT7d3NDKq6Oa4tiUJd6Po5Sse74jO9VXgcdcdUmUuQoUaOJnt4Dl+JXXeEyPxSI1voUw1/7MCwcm0NW4TI0Df09vyvCmMFradjrDakM0Z+bXAAAAAA=")
-      format("woff2"),
-    url("iconfont.woff?t=1619360751585") format("woff"),
-    url("iconfont.ttf?t=1619360751585") format("truetype"),
-    /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
-      url("iconfont.svg?t=1619360751585#iconfont") format("svg"); /* iOS 4.1- */
+  font-family: "iconfont"; /* Project id 2208059 */
+  src: url("iconfont.woff2?t=1632557807050") format("woff2"),
+    url("iconfont.woff?t=1632557807050") format("woff"),
+    url("iconfont.ttf?t=1632557807050") format("truetype");
 }
 
 .iconfont {
@@ -19,6 +13,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.team-iconinternationality::before {
+  content: "\e67a";
+}
+
 .team-iconshanchu::before {
   content: "\e617";
 }

BIN
src/assets/iconfont/iconfont.eot


File diff suppressed because it is too large
+ 0 - 2
src/assets/iconfont/iconfont.js


+ 9 - 2
src/assets/iconfont/iconfont.json

@@ -1,10 +1,17 @@
 {
   "id": "2208059",
-  "name": "CURD-TS",
+  "name": "pure-admin",
   "font_family": "iconfont",
   "css_prefix_text": "team-icon",
-  "description": "增删查改xi't",
+  "description": "pure-admin",
   "glyphs": [
+    {
+      "icon_id": "18367956",
+      "name": "中英文2 中文",
+      "font_class": "internationality",
+      "unicode": "e67a",
+      "unicode_decimal": 59002
+    },
     {
       "icon_id": "6184565",
       "name": "删除",

+ 0 - 41
src/assets/iconfont/iconfont.svg

@@ -1,41 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<!--
-2013-9-30: Created.
--->
-<svg>
-<metadata>
-Created by iconfont
-</metadata>
-<defs>
-
-<font id="iconfont" horiz-adv-x="1024" >
-  <font-face
-    font-family="iconfont"
-    font-weight="500"
-    font-stretch="normal"
-    units-per-em="1024"
-    ascent="896"
-    descent="-128"
-  />
-    <missing-glyph />
-    
-    <glyph glyph-name="shanchu" unicode="&#58903;" d="M200.0896 729.324308l657.250462-657.218954-33.429662-33.429662-657.250462 657.250462zM166.675692 72.0896l657.218954 657.250462 33.429662-33.429662-657.250462-657.250462z"  horiz-adv-x="1024" />
-
-    
-    <glyph glyph-name="show-main-container" unicode="&#59512;" d="M0 896v-1024h1024V896z m409.6-921.6H102.4v307.2h102.4v-204.8h204.8zM409.6 691.2H204.8v-204.8H102.4V793.6h307.2z m512-614.4v-102.4h-307.2v102.4h204.8v204.8h102.4z m0 614.4v-204.8h-102.4V691.2h-204.8V793.6h307.2z"  horiz-adv-x="1024" />
-
-    
-    <glyph glyph-name="hidden-main-container" unicode="&#59521;" d="M0 896v-1024h1024V896z m409.6-716.8v-204.8H307.2v204.8H102.4v102.4h307.2z m0 409.6v-102.4H102.4V588.8h204.8V793.6h102.4z m512-409.6h-204.8v-204.8h-102.4v307.2h307.2z m0 307.2h-307.2V793.6h102.4v-204.8h204.8z"  horiz-adv-x="1024" />
-
-    
-    <glyph glyph-name="exit-fullscreen" unicode="&#58922;" d="M366.2 714.2c-1 8-10.8 11.4-16.5 5.7l-53.1-53.1L134.2 829c-3.8 3.8-10 3.8-13.7 0L69 777.7c-3.8-3.8-3.8-10 0-13.7l162.4-162.4-53.3-53.3c-5.7-5.7-2.3-15.5 5.7-16.5l194.6-23c6.2-0.7 11.5 4.5 10.8 10.8l-23 194.6z m12.3-453.3l-194.7-23c-8-1-11.4-10.8-5.7-16.5l53.3-53.3L69 5.899999999999977c-3.8-3.8-3.8-10 0-13.7l51.5-51.4c3.8-3.8 10-3.8 13.7 0l162.4 162.3 53.1-53.1c5.7-5.7 15.5-2.3 16.5 5.7l23 194.4c0.7 6.3-4.5 11.5-10.7 10.8z m269.4 248l194.7 23c8 1 11.4 10.8 5.7 16.5L795 601.6l162.4 162.3c3.8 3.8 3.8 10 0 13.7L905.9 829c-3.8 3.8-10 3.8-13.7 0L729.7 666.8l-53.1 53.1c-5.7 5.7-15.6 2.3-16.5-5.7l-23-194.5c-0.6-6.3 4.6-11.5 10.8-10.8zM795 168.20000000000005l53.3 53.3c5.7 5.7 2.3 15.5-5.7 16.5L648 261c-6.2 0.7-11.5-4.5-10.8-10.8l23-194.6c1-8 10.8-11.4 16.5-5.7l53.1 53.1 162.4-162.3c3.8-3.8 10-3.8 13.7 0l51.5 51.4c3.8 3.8 3.8 10 0 13.7L795 168.20000000000005z m0 0"  horiz-adv-x="1024" />
-
-    
-    <glyph glyph-name="fullscreen" unicode="&#58923;" d="M229.8 733l55.7 55.7c6 6 2.4 16.2-6 17.2l-203.2 24c-6.5 0.8-12-4.7-11.3-11.3l24-203.2c1-8.4 11.3-11.9 17.2-6l55.4 55.4 169.6-169.4c3.9-3.9 10.4-3.9 14.3 0l53.8 53.6c3.9 3.9 3.9 10.4 0 14.3L229.8 733z m447.3-237.6c3.9-3.9 10.4-3.9 14.3 0L861 664.9l55.4-55.4c6-6 16.2-2.4 17.2 6l24 203c0.8 6.5-4.7 12-11.3 11.3l-203.2-24c-8.4-1-11.9-11.3-6-17.2l55.7-55.7-169.5-169.4c-3.9-3.9-3.9-10.4 0-14.3l53.8-53.8z m256.6-343.9c-1 8.4-11.3 11.9-17.2 6L861 102 691.4 271.5c-3.9 3.9-10.4 3.9-14.3 0l-53.8-53.6c-3.9-3.9-3.9-10.4 0-14.3L792.9 34l-55.7-55.7c-6-6-2.4-16.2 6-17.2l203.2-24c6.5-0.8 12 4.7 11.3 11.3l-24 203.1z m-588.1 120c-3.9 3.9-10.4 3.9-14.3 0L161.7 102l-55.4 55.4c-6 6-16.2 2.4-17.2-6l-24-203c-0.8-6.5 4.7-12.1 11.3-11.3l203.2 24c8.4 1 11.9 11.3 6 17.2l-55.7 55.5 169.6 169.4c3.9 3.9 3.9 10.4 0 14.3l-53.9 54z m0 0"  horiz-adv-x="1024" />
-
-    
-
-
-  </font>
-</defs></svg>

BIN
src/assets/iconfont/iconfont.ttf


BIN
src/assets/iconfont/iconfont.woff


BIN
src/assets/iconfont/iconfont.woff2


+ 1 - 0
src/assets/svg/iconinternationality.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="iconinternationality" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"><path d="M478.33 433.6l-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362L368 281.65L401.17 362z" fill="currentColor"></path><path d="M267.84 342.92a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73c39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36c-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93c.92 1.19 1.83 2.35 2.74 3.51c-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59c22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z" fill="currentColor"></path></svg>

+ 31 - 0
src/config/index.ts

@@ -1,3 +1,5 @@
+import { App } from "vue";
+import axios from "axios";
 let config: object = {};
 
 const setConfig = (cfg?: unknown) => {
@@ -22,4 +24,33 @@ const getConfig = (key?: string) => {
   return config;
 };
 
+// 获取项目动态全局配置
+export const getServerConfig = async (app: App): Promise<undefined> => {
+  app.config.globalProperties.$config = getConfig();
+  return axios({
+    baseURL: "",
+    method: "get",
+    url:
+      process.env.NODE_ENV === "production"
+        ? "/manages/serverConfig.json"
+        : "/serverConfig.json"
+  })
+    .then(({ data: config }) => {
+      let $config = app.config.globalProperties.$config;
+      // 自动注入项目配置
+      if (app && $config && typeof config === "object") {
+        $config = Object.assign($config, config);
+        app.config.globalProperties.$config = $config;
+        // 设置全局配置
+        setConfig($config);
+      }
+      // 设置全局baseURL
+      app.config.globalProperties.$baseUrl = $config.baseURL;
+      return $config;
+    })
+    .catch(() => {
+      throw "请在public文件夹下添加serverConfig.json配置文件";
+    });
+};
+
 export { getConfig, setConfig };

+ 0 - 0
src/layout/components/AppMain.vue → src/layout/components/appMain.vue


+ 0 - 5
src/layout/components/index.ts

@@ -1,5 +0,0 @@
-export { default as Navbar } from "./Navbar.vue";
-export { default as Sidebar } from "./sidebar/index.vue";
-export { default as AppMain } from "./AppMain.vue";
-export { default as setting } from "./setting/index.vue";
-export { default as tag } from "./tag/index.vue";

+ 118 - 127
src/layout/components/Navbar.vue → src/layout/components/navbar.vue

@@ -8,98 +8,84 @@
 
     <Breadcrumb class="breadcrumb-container" />
 
-    <div class="right-menu">
+    <div class="vertical-header-right">
       <!-- 全屏 -->
       <screenfull v-show="!deviceDetection()" />
       <!-- 国际化 -->
-      <div
-        v-show="!deviceDetection()"
-        class="inter"
-        :title="currentLocale ? '中文' : 'English'"
-        @click="toggleLang"
-      >
-        <img :src="currentLocale ? ch : en" />
-      </div>
-      <i
-        class="el-icon-setting hsset"
-        :title="$t('message.hssystemSet')"
-        @click="onPanel"
-      ></i>
+      <el-dropdown trigger="click">
+        <iconinternationality />
+        <template #dropdown>
+          <el-dropdown-menu class="translation">
+            <el-dropdown-item
+              :style="{
+                background: locale === 'zh' ? '#1b2a47' : '',
+                color: locale === 'zh' ? '#f4f4f5' : '#000'
+              }"
+              @click="translationCh"
+              >简体中文</el-dropdown-item
+            >
+            <el-dropdown-item
+              :style="{
+                background: locale === 'en' ? '#1b2a47' : '',
+                color: locale === 'en' ? '#f4f4f5' : '#000'
+              }"
+              @click="translationEn"
+              >English</el-dropdown-item
+            >
+          </el-dropdown-menu>
+        </template>
+      </el-dropdown>
       <!-- 退出登陆 -->
       <el-dropdown trigger="click">
         <span class="el-dropdown-link">
-          <img :src="favicon" />
+          <img
+            src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
+          />
           <p>{{ usename }}</p>
         </span>
         <template #dropdown>
-          <el-dropdown-menu>
+          <el-dropdown-menu class="logout">
             <el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
               $t("message.hsLoginOut")
             }}</el-dropdown-item>
           </el-dropdown-menu>
         </template>
       </el-dropdown>
+      <i
+        class="el-icon-setting"
+        :title="$t('message.hssystemSet')"
+        @click="onPanel"
+      ></i>
     </div>
   </div>
 </template>
 
 <script lang="ts">
-import {
-  defineComponent,
-  onMounted,
-  unref,
-  watch,
-  getCurrentInstance
-} from "vue";
+import { defineComponent, unref, watch, getCurrentInstance } from "vue";
 import Breadcrumb from "/@/components/ReBreadCrumb";
 import Hamburger from "/@/components/ReHamBurger";
 import screenfull from "../components/screenfull/index.vue";
 import { useRouter, useRoute } from "vue-router";
 import { useAppStoreHook } from "/@/store/modules/app";
 import { storageSession } from "/@/utils/storage";
-import ch from "/@/assets/ch.png";
-import en from "/@/assets/en.png";
 import favicon from "/favicon.ico";
 import { emitter } from "/@/utils/mitt";
 import { deviceDetection } from "/@/utils/deviceDetection";
 import { useI18n } from "vue-i18n";
+import iconinternationality from "/@/assets/svg/iconinternationality.svg";
 
-let routerArrays: Array<object> = [
-  {
-    path: "/welcome",
-    parentPath: "/",
-    meta: {
-      title: "message.hshome",
-      icon: "el-icon-s-home",
-      showLink: true,
-      savedPosition: false
-    }
-  }
-];
 export default defineComponent({
   name: "Navbar",
   components: {
     Breadcrumb,
     Hamburger,
-    screenfull
+    screenfull,
+    iconinternationality
   },
   // @ts-ignore
   computed: {
     // eslint-disable-next-line vue/return-in-computed-property
     currentLocale() {
-      if (
-        !this.$storage.routesInStorage ||
-        this.$storage.routesInStorage.length === 0
-      ) {
-        // eslint-disable-next-line vue/no-side-effects-in-computed-properties
-        this.$storage.routesInStorage = routerArrays;
-      }
-
-      if (!this.$storage.locale) {
-        // eslint-disable-next-line
-        this.$storage.locale = { locale: "zh" };
-        useI18n().locale.value = "zh";
-      }
       switch (this.$storage.locale?.locale) {
         case "zh":
           return true;
@@ -114,25 +100,9 @@ export default defineComponent({
     const pureApp = useAppStoreHook();
     const router = useRouter();
     const route = useRoute();
-
     let usename = storageSession.getItem("info")?.username;
-
     const { locale, t } = useI18n();
 
-    // 国际化语言切换
-    const toggleLang = (): void => {
-      switch (instance.locale.locale) {
-        case "zh":
-          instance.locale = { locale: "en" };
-          locale.value = "en";
-          break;
-        case "en":
-          instance.locale = { locale: "zh" };
-          locale.value = "zh";
-          break;
-      }
-    };
-
     watch(
       () => locale.value,
       () => {
@@ -155,28 +125,31 @@ export default defineComponent({
       pureApp.toggleSideBar();
     }
 
-    onMounted(() => {
-      document
-        .querySelector(".el-dropdown__popper")
-        ?.setAttribute("class", "resetTop");
-      document
-        .querySelector(".el-popper__arrow")
-        ?.setAttribute("class", "hidden");
-    });
+    // 简体中文
+    function translationCh() {
+      instance.locale = { locale: "zh" };
+      locale.value = "zh";
+      window.location.reload();
+    }
+
+    // English
+    function translationEn() {
+      instance.locale = { locale: "en" };
+      locale.value = "en";
+      window.location.reload();
+    }
 
     return {
-      pureApp,
-      toggleSideBar,
+      locale,
       usename,
-      toggleLang,
-      logout,
-      ch,
-      en,
+      pureApp,
       favicon,
+      logout,
       onPanel,
-      deviceDetection,
-      locale,
-      t
+      translationCh,
+      translationEn,
+      toggleSideBar,
+      deviceDetection
     };
   }
 });
@@ -185,13 +158,13 @@ export default defineComponent({
 <style lang="scss" scoped>
 .navbar {
   width: 100%;
-  height: 50px;
+  height: 48px;
   overflow: hidden;
   background: #fff;
   box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
 
   .hamburger-container {
-    line-height: 46px;
+    line-height: 48px;
     height: 100%;
     float: left;
     cursor: pointer;
@@ -203,56 +176,46 @@ export default defineComponent({
     }
   }
 
-  .breadcrumb-container {
-    float: left;
-  }
-
-  .right-menu {
-    position: absolute;
-    right: 0;
+  .vertical-header-right {
     display: flex;
-    align-items: center;
+    min-width: 280px;
     height: 48px;
-    line-height: 48px;
+    align-items: center;
+    color: #000000d9;
+    justify-content: flex-end;
 
-    .inter {
-      width: 40px;
-      height: 48px;
-      display: flex;
-      align-items: center;
-      justify-content: space-around;
+    .screen-full {
+      cursor: pointer;
 
       &:hover {
-        cursor: pointer;
-        background: #f0f0f0;
-      }
-
-      img {
-        width: 25px;
+        background: #f6f6f6;
       }
     }
 
-    .hsset {
-      width: 40px;
+    .iconinternationality {
       height: 48px;
-      display: flex;
-      align-items: center;
-      justify-content: space-around;
-      margin-right: 5px;
+      width: 40px;
+      padding: 11px;
+      cursor: pointer;
 
       &:hover {
-        cursor: pointer;
-        background: #f0f0f0;
+        background: #f6f6f6;
       }
     }
 
     .el-dropdown-link {
-      width: 70px;
+      width: 100px;
+      height: 48px;
+      padding: 10px;
       display: flex;
       align-items: center;
       justify-content: space-around;
-      margin-right: 10px;
       cursor: pointer;
+      color: #000000d9;
+
+      &:hover {
+        background: #f6f6f6;
+      }
 
       p {
         font-size: 14px;
@@ -261,22 +224,50 @@ export default defineComponent({
       img {
         width: 22px;
         height: 22px;
+        border-radius: 50%;
+      }
+    }
+
+    .el-icon-setting {
+      height: 48px;
+      width: 40px;
+      padding: 11px;
+      display: flex;
+      cursor: pointer;
+      align-items: center;
+
+      &:hover {
+        background: #f6f6f6;
       }
     }
   }
+
+  .breadcrumb-container {
+    float: left;
+  }
 }
-// single element-plus reset
-.el-dropdown-menu__item {
-  padding: 0 10px;
-}
 
-.el-dropdown-menu {
-  padding: 6px 0;
+.translation {
+  .el-dropdown-menu__item {
+    padding: 0 40px !important;
+  }
+
+  .el-dropdown-menu__item:focus,
+  .el-dropdown-menu__item:not(.is-disabled):hover {
+    color: #606266;
+    background: #f0f0f0;
+  }
 }
 
-.el-dropdown-menu__item:focus,
-.el-dropdown-menu__item:not(.is-disabled):hover {
-  color: #606266;
-  background: #f0f0f0;
+.logout {
+  .el-dropdown-menu__item {
+    padding: 0 18px !important;
+  }
+
+  .el-dropdown-menu__item:focus,
+  .el-dropdown-menu__item:not(.is-disabled):hover {
+    color: #606266;
+    background: #f0f0f0;
+  }
 }
 </style>

+ 1 - 1
src/layout/components/panel/index.vue

@@ -3,7 +3,7 @@ import { ref } from "vue";
 import { useEventListener, onClickOutside } from "@vueuse/core";
 import { emitter } from "/@/utils/mitt";
 
-let show = ref(false);
+let show = ref<Boolean>(false);
 const target = ref(null);
 onClickOutside(target, () => {
   show.value = false;

+ 2 - 8
src/layout/components/screenfull/index.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { useFullscreen } from "@vueuse/core";
-
 const { isFullscreen, toggle } = useFullscreen();
 </script>
 
@@ -23,15 +22,10 @@ const { isFullscreen, toggle } = useFullscreen();
 
 <style lang="scss" scoped>
 .screen-full {
-  width: 40px;
-  height: 48px;
+  width: 36px;
+  height: 62px;
   display: flex;
   align-items: center;
   justify-content: space-around;
-
-  &:hover {
-    cursor: pointer;
-    background: #f0f0f0;
-  }
 }
 </style>

+ 115 - 37
src/layout/components/setting/index.vue

@@ -1,14 +1,18 @@
 <script setup lang="ts">
+import { split } from "lodash-es";
 import panel from "../panel/index.vue";
 import { useRouter } from "vue-router";
 import { emitter } from "/@/utils/mitt";
 import { templateRef } from "@vueuse/core";
-import { reactive, ref, unref, useCssModule } from "vue";
 import { storageLocal, storageSession } from "/@/utils/storage";
+import { reactive, ref, unref, useCssModule, getCurrentInstance } from "vue";
 
 const router = useRouter();
 const { isSelect } = useCssModule();
 
+const instance =
+  getCurrentInstance().appContext.app.config.globalProperties.$storage;
+
 // 默认灵动模式
 const markValue = ref(storageLocal.getItem("showModel") || "smart");
 
@@ -80,37 +84,54 @@ function onChange({ label }) {
   emitter.emit("tagViewsShowModel", label);
 }
 
-const firstTheme = templateRef<HTMLElement | null>("firstTheme", null);
-const secondTheme = templateRef<HTMLElement | null>("secondTheme", null);
-
-const dataTheme = ref(storageLocal.getItem("data-theme") || "dark");
-if (dataTheme.value) {
-  storageLocal.setItem("data-theme", unref(dataTheme));
-  window.document.body.setAttribute("data-theme", unref(dataTheme));
-}
-
-// dark主题
-function onDark() {
-  storageLocal.setItem("data-theme", "dark");
-  window.document.body.setAttribute("data-theme", "dark");
-  toggleClass(true, isSelect, unref(firstTheme));
-  toggleClass(false, isSelect, unref(secondTheme));
-}
-
-// light主题
-function onLight() {
-  storageLocal.setItem("data-theme", "light");
-  window.document.body.setAttribute("data-theme", "light");
-  toggleClass(false, isSelect, unref(firstTheme));
-  toggleClass(true, isSelect, unref(secondTheme));
+const verticalDarkDom = templateRef<HTMLElement | null>(
+  "verticalDarkDom",
+  null
+);
+const verticalLightDom = templateRef<HTMLElement | null>(
+  "verticalLightDom",
+  null
+);
+const horizontalDarkDom = templateRef<HTMLElement | null>(
+  "horizontalDarkDom",
+  null
+);
+const horizontalLightDom = templateRef<HTMLElement | null>(
+  "horizontalLightDom",
+  null
+);
+
+let dataTheme =
+  ref(storageLocal.getItem("responsive-layout")) ||
+  ref({
+    layout: "horizontal-dark"
+  });
+
+if (unref(dataTheme)) {
+  // 设置主题
+  let theme = split(unref(dataTheme).layout, "-")[1];
+  window.document.body.setAttribute("data-theme", theme);
+  // 设置导航模式
+  let layout = split(unref(dataTheme).layout, "-")[0];
+  window.document.body.setAttribute("data-layout", layout);
 }
 
+// 侧边栏Logo
 function logoChange() {
   unref(logoVal) === "1"
     ? storageLocal.setItem("logoVal", "1")
     : storageLocal.setItem("logoVal", "-1");
   emitter.emit("logoChange", unref(logoVal));
 }
+
+function setTheme(layout: string, theme: string, dom: HTMLElement) {
+  dataTheme.value.layout = `${layout}-${theme}`;
+  window.document.body.setAttribute("data-layout", layout);
+  window.document.body.setAttribute("data-theme", theme);
+  instance.layout = { layout: `${layout}-${theme}` };
+  toggleClass(true, isSelect, unref(dom));
+  toggleClass(false, isSelect, unref(dom));
+}
 </script>
 
 <template>
@@ -124,9 +145,9 @@ function logoChange() {
         placement="bottom"
       >
         <li
-          :class="dataTheme === 'dark' ? $style.isSelect : ''"
-          ref="firstTheme"
-          @click="onDark"
+          :class="dataTheme.layout === 'vertical-dark' ? $style.isSelect : ''"
+          ref="verticalDarkDom"
+          @click="setTheme('vertical', 'dark', verticalDarkDom)"
         >
           <div></div>
           <div></div>
@@ -140,9 +161,43 @@ function logoChange() {
         placement="bottom"
       >
         <li
-          :class="dataTheme === 'light' ? $style.isSelect : ''"
-          ref="secondTheme"
-          @click="onLight"
+          :class="dataTheme.layout === 'vertical-light' ? $style.isSelect : ''"
+          ref="verticalLightDom"
+          @click="setTheme('vertical', 'light', verticalLightDom)"
+        >
+          <div></div>
+          <div></div>
+        </li>
+      </el-tooltip>
+
+      <el-tooltip
+        class="item"
+        effect="dark"
+        content="暗色主题"
+        placement="bottom"
+      >
+        <li
+          :class="dataTheme.layout === 'horizontal-dark' ? $style.isSelect : ''"
+          ref="horizontalDarkDom"
+          @click="setTheme('horizontal', 'dark', horizontalDarkDom)"
+        >
+          <div></div>
+          <div></div>
+        </li>
+      </el-tooltip>
+
+      <el-tooltip
+        class="item"
+        effect="dark"
+        content="暗色主题"
+        placement="bottom"
+      >
+        <li
+          :class="
+            dataTheme.layout === 'horizontal-light' ? $style.isSelect : ''
+          "
+          ref="horizontalLightDom"
+          @click="setTheme('horizontal', 'light', horizontalLightDom)"
         >
           <div></div>
           <div></div>
@@ -237,18 +292,19 @@ function logoChange() {
 .theme-stley {
   margin-top: 25px;
   width: 100%;
-  height: 60px;
+  height: 180px;
   display: flex;
+  flex-wrap: wrap;
   justify-content: space-around;
 
   li {
-    width: 30%;
-    height: 100%;
+    margin: 10px;
+    width: 36%;
+    height: 70px;
     background: #f0f2f5;
     position: relative;
     overflow: hidden;
     cursor: pointer;
-    background-color: #f0f2f5;
     border-radius: 4px;
     box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
 
@@ -265,7 +321,7 @@ function logoChange() {
           height: 30%;
           top: 0;
           right: 0;
-          background-color: #fff;
+          background: #fff;
           box-shadow: 0 0 1px #888;
           position: absolute;
         }
@@ -278,7 +334,7 @@ function logoChange() {
           width: 30%;
           height: 100%;
           box-shadow: 0 0 1px #888;
-          background-color: #fff;
+          background: #fff;
           border-radius: 4px 0 0 4px;
         }
 
@@ -287,12 +343,34 @@ function logoChange() {
           height: 30%;
           top: 0;
           right: 0;
-          background-color: #fff;
+          background: #fff;
           box-shadow: 0 0 1px #888;
           position: absolute;
         }
       }
     }
+
+    &:nth-child(3) {
+      div {
+        &:nth-child(1) {
+          width: 100%;
+          height: 30%;
+          background: #1b2a47;
+          box-shadow: 0 0 1px #888;
+        }
+      }
+    }
+
+    &:nth-child(4) {
+      div {
+        &:nth-child(1) {
+          width: 100%;
+          height: 30%;
+          background: #fff;
+          box-shadow: 0 0 1px #888;
+        }
+      }
+    }
   }
 }
 </style>

+ 0 - 51
src/layout/components/sidebar/Link.vue

@@ -1,51 +0,0 @@
-<template>
-  <component :is="type" v-bind="linkProps(to)">
-    <slot />
-  </component>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, unref } from "vue";
-import { isUrl } from "/@/utils/is";
-
-export default defineComponent({
-  name: "Link",
-  props: {
-    to: {
-      type: String,
-      required: true
-    }
-  },
-  setup(props) {
-    const isExternal = computed(() => {
-      return isUrl(props.to);
-    });
-
-    const type = computed(() => {
-      if (unref(isExternal)) {
-        return "a";
-      }
-      return "router-link";
-    });
-
-    function linkProps(to) {
-      if (unref(isExternal)) {
-        return {
-          href: to,
-          target: "_blank",
-          rel: "noopener"
-        };
-      }
-      return {
-        to: to
-      };
-    }
-
-    return {
-      isExternal,
-      type,
-      linkProps
-    };
-  }
-});
-</script>

+ 0 - 116
src/layout/components/sidebar/SidebarItem.vue

@@ -1,116 +0,0 @@
-<template>
-  <div v-if="!item.hidden">
-    <template
-      v-if="
-        hasOneShowingChild(item.children, item) &&
-        (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
-        !item.alwaysShow
-      "
-    >
-      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
-        <el-menu-item
-          :index="resolvePath(onlyOneChild.path)"
-          :class="{ 'submenu-title-noDropdown': !isNest }"
-        >
-          <i :class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
-          <template #title>
-            <span>{{ $t(onlyOneChild.meta.title) }}</span>
-          </template>
-        </el-menu-item>
-      </app-link>
-    </template>
-
-    <el-sub-menu
-      v-else
-      ref="subMenu"
-      :index="resolvePath(item.path)"
-      popper-append-to-body
-    >
-      <template #title>
-        <i :class="item.meta.icon"></i>
-        <span>{{ $t(item.meta.title) }}</span>
-      </template>
-      <sidebar-item
-        v-for="child in item.children"
-        :key="child.path"
-        :is-nest="true"
-        :item="child"
-        :base-path="resolvePath(child.path)"
-        class="nest-menu"
-      />
-    </el-sub-menu>
-  </div>
-</template>
-
-<script lang="ts">
-import path from "path";
-import AppLink from "./Link.vue";
-import { defineComponent, PropType, ref } from "vue";
-import { RouteRecordRaw } from "vue-router";
-import { isUrl } from "/@/utils/is.ts";
-
-export default defineComponent({
-  name: "SidebarItem",
-  components: { AppLink },
-  props: {
-    item: {
-      type: Object as PropType<RouteRecordRaw>,
-      required: true
-    },
-    isNest: {
-      type: Boolean,
-      default: false
-    },
-    basePath: {
-      type: String,
-      default: ""
-    }
-  },
-  setup(props) {
-    const onlyOneChild = ref<RouteRecordRaw>({} as any);
-
-    function hasOneShowingChild(
-      children: RouteRecordRaw[] = [],
-      parent: RouteRecordRaw
-    ) {
-      const showingChildren = children.filter((item: any) => {
-        if (item.hidden) {
-          // 不显示hidden属性为true的菜单
-          return false;
-        } else {
-          onlyOneChild.value = item;
-          return true;
-        }
-      });
-
-      if (showingChildren.length === 1) {
-        return true;
-      }
-
-      if (showingChildren.length === 0) {
-        // @ts-ignore
-        onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
-        return true;
-      }
-      return false;
-    }
-
-    // const resolvePath = (routePath: string) => {
-    //   return path.resolve(props.basePath, routePath);
-    // };
-
-    function resolvePath(routePath) {
-      if (isUrl(routePath)) {
-        return routePath;
-      }
-      if (isUrl(this.basePath)) {
-        return props.basePath;
-      }
-      // @ts-ignore
-      return path.resolve(props.basePath, routePath);
-    }
-
-    return { hasOneShowingChild, resolvePath, onlyOneChild };
-  }
-});
-</script>

+ 254 - 0
src/layout/components/sidebar/horizontal.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="horizontal-header">
+    <div class="horizontal-header-left" @click="backHome">
+      <i class="fa fa-optin-monster"></i>
+      <h4>{{ settings.title }}</h4>
+    </div>
+    <el-menu
+      :default-active="activeMenu"
+      unique-opened
+      router
+      class="horizontal-header-menu"
+      mode="horizontal"
+      @select="menuSelect"
+    >
+      <sidebar-item
+        v-for="route in routeStore.wholeRoutes"
+        :key="route.path"
+        :item="route"
+        :base-path="route.path"
+      />
+    </el-menu>
+    <div class="horizontal-header-right">
+      <!-- 全屏 -->
+      <screenfull v-show="!deviceDetection()" />
+      <!-- 国际化 -->
+      <el-dropdown trigger="click">
+        <iconinternationality />
+        <template #dropdown>
+          <el-dropdown-menu class="translation">
+            <el-dropdown-item
+              :style="{
+                background: locale === 'zh' ? '#1b2a47' : '',
+                color: locale === 'zh' ? '#f4f4f5' : '#000'
+              }"
+              @click="translationCh"
+              >简体中文</el-dropdown-item
+            >
+            <el-dropdown-item
+              :style="{
+                background: locale === 'en' ? '#1b2a47' : '',
+                color: locale === 'en' ? '#f4f4f5' : '#000'
+              }"
+              @click="translationEn"
+              >English</el-dropdown-item
+            >
+          </el-dropdown-menu>
+        </template>
+      </el-dropdown>
+      <!-- 退出登陆 -->
+      <el-dropdown trigger="click">
+        <span class="el-dropdown-link">
+          <img
+            src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
+          />
+          <p>{{ usename }}</p>
+        </span>
+        <template #dropdown>
+          <el-dropdown-menu class="logout">
+            <el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
+              $t("message.hsLoginOut")
+            }}</el-dropdown-item>
+          </el-dropdown-menu>
+        </template>
+      </el-dropdown>
+      <i
+        class="el-icon-setting"
+        :title="$t('message.hssystemSet')"
+        @click="onPanel"
+      ></i>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {
+  computed,
+  defineComponent,
+  unref,
+  watch,
+  getCurrentInstance
+} from "vue";
+import { useI18n } from "vue-i18n";
+import settings from "/@/settings";
+import { emitter } from "/@/utils/mitt";
+import SidebarItem from "./sidebarItem.vue";
+import { algorithm } from "/@/utils/algorithm";
+import screenfull from "../screenfull/index.vue";
+import { useRoute, useRouter } from "vue-router";
+import { storageSession } from "/@/utils/storage";
+import { deviceDetection } from "/@/utils/deviceDetection";
+import { usePermissionStoreHook } from "/@/store/modules/permission";
+import iconinternationality from "/@/assets/svg/iconinternationality.svg";
+
+let routerArrays: Array<object> = [
+  {
+    path: "/welcome",
+    parentPath: "/",
+    meta: {
+      title: "message.hshome",
+      icon: "el-icon-s-home",
+      showLink: true,
+      savedPosition: false
+    }
+  }
+];
+export default defineComponent({
+  name: "sidebar",
+  components: { SidebarItem, screenfull, iconinternationality },
+  // @ts-ignore
+  computed: {
+    // eslint-disable-next-line vue/return-in-computed-property
+    currentLocale() {
+      if (
+        !this.$storage.routesInStorage ||
+        this.$storage.routesInStorage.length === 0
+      ) {
+        // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+        this.$storage.routesInStorage = routerArrays;
+      }
+
+      if (!this.$storage.locale) {
+        // eslint-disable-next-line
+        this.$storage.locale = { locale: "zh" };
+        useI18n().locale.value = "zh";
+      }
+      switch (this.$storage.locale?.locale) {
+        case "zh":
+          return true;
+        case "en":
+          return false;
+      }
+    }
+  },
+  setup() {
+    const instance =
+      getCurrentInstance().appContext.config.globalProperties.$storage;
+    const routeStore = usePermissionStoreHook();
+    const route = useRoute();
+    const router = useRouter();
+    const routers = useRouter().options.routes;
+    let usename = storageSession.getItem("info")?.username;
+    const { locale, t } = useI18n();
+
+    watch(
+      () => locale.value,
+      () => {
+        //@ts-ignore
+        // 动态title
+        document.title = t(unref(route.meta.title));
+      }
+    );
+
+    // 退出登录
+    const logout = (): void => {
+      storageSession.removeItem("info");
+      router.push("/login");
+    };
+
+    function onPanel() {
+      emitter.emit("openPanel");
+    }
+
+    const activeMenu = computed(() => {
+      const { meta, path } = route;
+      if (meta.activeMenu) {
+        return meta.activeMenu;
+      }
+      return path;
+    });
+
+    const menuSelect = (indexPath: string): void => {
+      let parentPath = "";
+      let parentPathIndex = indexPath.lastIndexOf("/");
+      if (parentPathIndex > 0) {
+        parentPath = indexPath.slice(0, parentPathIndex);
+      }
+      // 找到当前路由的信息
+      function findCurrentRoute(routes) {
+        return routes.map(item => {
+          if (item.path === indexPath) {
+            // 切换左侧菜单 通知标签页
+            emitter.emit("changLayoutRoute", {
+              indexPath,
+              parentPath
+            });
+          } else {
+            if (item.children) findCurrentRoute(item.children);
+          }
+        });
+      }
+      findCurrentRoute(algorithm.increaseIndexes(routers));
+    };
+
+    function backHome() {
+      router.push("/welcome");
+    }
+
+    // 简体中文
+    function translationCh() {
+      instance.locale = { locale: "zh" };
+      locale.value = "zh";
+      window.location.reload();
+    }
+
+    // English
+    function translationEn() {
+      instance.locale = { locale: "en" };
+      locale.value = "en";
+      window.location.reload();
+    }
+
+    return {
+      locale,
+      usename,
+      settings,
+      routeStore,
+      activeMenu,
+      logout,
+      onPanel,
+      backHome,
+      menuSelect,
+      translationCh,
+      translationEn,
+      deviceDetection
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.translation {
+  .el-dropdown-menu__item {
+    padding: 0 40px !important;
+  }
+
+  .el-dropdown-menu__item:focus,
+  .el-dropdown-menu__item:not(.is-disabled):hover {
+    color: #606266;
+    background: #f0f0f0;
+  }
+}
+
+.logout {
+  .el-dropdown-menu__item {
+    padding: 0 18px !important;
+  }
+
+  .el-dropdown-menu__item:focus,
+  .el-dropdown-menu__item:not(.is-disabled):hover {
+    color: #606266;
+    background: #f0f0f0;
+  }
+}
+</style>

+ 11 - 22
src/layout/components/sidebar/Logo.vue → src/layout/components/sidebar/logo.vue

@@ -1,9 +1,17 @@
+<script setup lang="ts">
+import settings from "/@/settings";
+
+const props = defineProps({
+  collapse: Boolean
+});
+</script>
+
 <template>
-  <div class="sidebar-logo-container" :class="{ collapse: collapse }">
+  <div class="sidebar-logo-container" :class="{ collapse: props.collapse }">
     <transition name="sidebarLogoFade">
       <router-link
-        v-if="collapse"
-        key="collapse"
+        v-if="props.collapse"
+        key="props.collapse"
         :title="settings.title"
         class="sidebar-logo-link"
         to="/"
@@ -25,25 +33,6 @@
   </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from "vue";
-import settings from "/@/settings";
-
-export default defineComponent({
-  props: {
-    collapse: {
-      type: Boolean,
-      required: true
-    }
-  },
-  setup() {
-    return {
-      settings
-    };
-  }
-});
-</script>
-
 <style lang="scss" scoped>
 .sidebar-logo-container {
   position: relative;

+ 104 - 0
src/layout/components/sidebar/sidebarItem.vue

@@ -0,0 +1,104 @@
+<script setup lang="ts">
+import path from "path";
+import { PropType, ref } from "vue";
+import { RouteRecordRaw } from "vue-router";
+
+const props = defineProps({
+  item: {
+    type: Object as PropType<RouteRecordRaw>
+  },
+  isNest: {
+    type: Boolean,
+    default: false
+  },
+  basePath: {
+    type: String,
+    default: ""
+  }
+});
+
+type childrenType = {
+  path?: string;
+  noShowingChildren?: boolean;
+  children?: RouteRecordRaw[];
+  meta?: {
+    icon?: string;
+    title?: string;
+  };
+};
+
+const onlyOneChild = ref<RouteRecordRaw | childrenType>({} as any);
+
+function hasOneShowingChild(
+  children: RouteRecordRaw[] = [],
+  parent: RouteRecordRaw
+) {
+  const showingChildren = children.filter((item: any) => {
+    if (item.hidden) {
+      // 不显示hidden属性为true的菜单
+      return false;
+    } else {
+      onlyOneChild.value = item;
+      return true;
+    }
+  });
+
+  if (showingChildren.length === 1) {
+    return true;
+  }
+
+  if (showingChildren.length === 0) {
+    onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
+    return true;
+  }
+  return false;
+}
+
+function resolvePath(routePath) {
+  return path.resolve(props.basePath, routePath);
+}
+</script>
+
+<template>
+  <template
+    v-if="
+      hasOneShowingChild(props.item.children, props.item) &&
+      (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
+      !props.item.alwaysShow
+    "
+  >
+    <el-menu-item
+      :index="resolvePath(onlyOneChild.path)"
+      :class="{ 'submenu-title-noDropdown': !isNest }"
+    >
+      <i
+        :class="
+          onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
+        "
+      />
+      <template #title>
+        <span>{{ $t(onlyOneChild.meta.title) }}</span>
+      </template>
+    </el-menu-item>
+  </template>
+
+  <el-sub-menu
+    v-else
+    ref="subMenu"
+    :index="resolvePath(props.item.path)"
+    popper-append-to-body
+  >
+    <template #title>
+      <i :class="props.item.meta.icon"></i>
+      <span>{{ $t(props.item.meta.title) }}</span>
+    </template>
+    <sidebar-item
+      v-for="child in props.item.children"
+      :key="child.path"
+      :is-nest="true"
+      :item="child"
+      :base-path="resolvePath(child.path)"
+      class="nest-menu"
+    />
+  </el-sub-menu>
+</template>

+ 9 - 7
src/layout/components/sidebar/index.vue → src/layout/components/sidebar/vertical.vue

@@ -1,11 +1,12 @@
 <template>
-  <div :class="{ 'has-logo': showLogo }">
+  <div :class="['sidebar-container', showLogo ? 'has-logo' : '']">
     <Logo v-if="showLogo === '1'" :collapse="isCollapse" />
     <el-scrollbar wrap-class="scrollbar-wrapper">
       <el-menu
         :default-active="activeMenu"
         :collapse="isCollapse"
         unique-opened
+        router
         :collapse-transition="false"
         mode="vertical"
         @select="menuSelect"
@@ -22,14 +23,14 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, ref, onBeforeMount } from "vue";
-import { useRoute, useRouter } from "vue-router";
-import { useAppStoreHook } from "/@/store/modules/app";
-import SidebarItem from "./SidebarItem.vue";
-import { algorithm } from "/@/utils/algorithm";
+import Logo from "./logo.vue";
 import { emitter } from "/@/utils/mitt";
-import Logo from "./Logo.vue";
+import SidebarItem from "./sidebarItem.vue";
+import { algorithm } from "/@/utils/algorithm";
 import { storageLocal } from "/@/utils/storage";
+import { useRoute, useRouter } from "vue-router";
+import { useAppStoreHook } from "/@/store/modules/app";
+import { computed, defineComponent, ref, onBeforeMount } from "vue";
 import { usePermissionStoreHook } from "/@/store/modules/permission";
 
 export default defineComponent({
@@ -61,6 +62,7 @@ export default defineComponent({
         parentPath = indexPath.slice(0, parentPathIndex);
       }
       // 找到当前路由的信息
+      // eslint-disable-next-line no-inner-declarations
       function findCurrentRoute(routes) {
         return routes.map(item => {
           if (item.path === indexPath) {

+ 57 - 10
src/layout/index.vue

@@ -1,3 +1,41 @@
+<script lang="ts">
+let routerArrays: Array<object> = [
+  {
+    path: "/welcome",
+    parentPath: "/",
+    meta: {
+      title: "message.hshome",
+      icon: "el-icon-s-home",
+      showLink: true,
+      savedPosition: false
+    }
+  }
+];
+export default {
+  computed: {
+    layout() {
+      if (!this.$storage.layout) {
+        // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+        this.$storage.layout = { layout: "vertical-dark" };
+      }
+      if (
+        !this.$storage.routesInStorage ||
+        this.$storage.routesInStorage.length === 0
+      ) {
+        // eslint-disable-next-line vue/no-side-effects-in-computed-properties
+        this.$storage.routesInStorage = routerArrays;
+      }
+      if (!this.$storage.locale) {
+        // eslint-disable-next-line
+        this.$storage.locale = { locale: "zh" };
+        useI18n().locale.value = "zh";
+      }
+      return this.$storage?.layout.layout;
+    }
+  }
+};
+</script>
+
 <script setup lang="ts">
 import {
   ref,
@@ -10,13 +48,20 @@ import {
   useCssModule
 } from "vue";
 import options from "/@/settings";
+import { useI18n } from "vue-i18n";
 import { toggleClass } from "/@/utils/operate";
 import { useEventListener } from "@vueuse/core";
 import { useAppStoreHook } from "/@/store/modules/app";
 import fullScreen from "/@/assets/svg/full_screen.svg";
 import exitScreen from "/@/assets/svg/exit_screen.svg";
 import { useSettingStoreHook } from "/@/store/modules/settings";
-import { Navbar, Sidebar, AppMain, setting, tag } from "./components";
+
+import navbar from "./components/navbar.vue";
+import tag from "./components/tag/index.vue";
+import appMain from "./components/appMain.vue";
+import setting from "./components/setting/index.vue";
+import Vertical from "./components/sidebar/vertical.vue";
+import Horizontal from "./components/sidebar/horizontal.vue";
 
 interface setInter {
   sidebar: any;
@@ -118,19 +163,25 @@ onBeforeMount(() => {
 </script>
 
 <template>
-  <div :class="set.classes" class="app-wrapper">
+  <div :class="['app-wrapper', set.classes]">
     <div
-      v-if="set.device === 'mobile' && set.sidebar.opened"
+      v-show="
+        set.device === 'mobile' &&
+        set.sidebar.opened &&
+        layout.includes('vertical')
+      "
       class="drawer-bg"
       @click="handleClickOutside(false)"
     />
-    <!-- 侧边栏 -->
-    <sidebar class="sidebar-container" v-if="!containerHiddenSideBar" />
+    <Vertical v-show="!containerHiddenSideBar && layout.includes('vertical')" />
     <div class="main-container">
       <div :class="{ 'fixed-header': set.fixedHeader }">
         <!-- 顶部导航栏 -->
-        <navbar v-show="!containerHiddenSideBar" />
+        <navbar
+          v-show="!containerHiddenSideBar && layout.includes('vertical')"
+        />
         <!-- tabs标签页 -->
+        <Horizontal v-show="layout.includes('horizontal')" />
         <tag>
           <span @click="onFullScreen">
             <fullScreen v-if="!containerHiddenSideBar" />
@@ -194,10 +245,6 @@ $sideBarWidth: 210px;
   transition: width 0.28s;
 }
 
-.hideSidebar .fixed-header {
-  width: calc(100% - 54px);
-}
-
 .mobile .fixed-header {
   width: 100%;
 }

+ 9 - 34
src/main.ts

@@ -1,6 +1,7 @@
 import App from "./App.vue";
 import router from "./router";
 import { setupStore } from "/@/store";
+import { getServerConfig } from "./config";
 import { createApp, Directive } from "vue";
 import { usI18n } from "../src/plugins/i18n";
 import { useTable } from "../src/plugins/vxe-table";
@@ -14,13 +15,8 @@ import "./assets/iconfont/iconfont.js";
 import "./assets/iconfont/iconfont.css";
 import "v-contextmenu/dist/themes/default.css";
 
-import { setConfig, getConfig } from "./config";
-import axios from "axios";
-
 const app = createApp(App);
 
-app.config.globalProperties.$config = getConfig();
-
 // 响应式storage
 import Storage from "responsive-storage";
 // @ts-ignore
@@ -47,6 +43,13 @@ app.use(Storage, {
     default: Storage.getData(undefined, "locale") ?? {
       locale: "zh"
     }
+  },
+  // layout模式以及主题
+  layout: {
+    type: Object,
+    default: Storage.getData(undefined, "layout") ?? {
+      layout: "vertical-dark"
+    }
   }
 });
 
@@ -56,35 +59,7 @@ Object.keys(directives).forEach(key => {
   app.directive(key, (directives as { [key: string]: Directive })[key]);
 });
 
-// 获取项目动态全局配置
-export const getServerConfig = async (): Promise<undefined> => {
-  return axios({
-    baseURL: "",
-    method: "get",
-    url:
-      process.env.NODE_ENV === "production"
-        ? "/manages/serverConfig.json"
-        : "/serverConfig.json"
-  })
-    .then(({ data: config }) => {
-      let $config = app.config.globalProperties.$config;
-      // 自动注入项目配置
-      if (app && $config && typeof config === "object") {
-        $config = Object.assign($config, config);
-        app.config.globalProperties.$config = $config;
-        // 设置全局配置
-        setConfig($config);
-      }
-      // 设置全局baseURL
-      app.config.globalProperties.$baseUrl = $config.baseURL;
-      return $config;
-    })
-    .catch(() => {
-      throw "请在public文件夹下添加serverConfig.json配置文件";
-    });
-};
-
-getServerConfig().then(async () => {
+getServerConfig(app).then(async () => {
   setupStore(app);
   app.use(router).use(useElementPlus).use(useTable).use(usI18n);
   await router.isReady();

+ 11 - 2
src/router/index.ts

@@ -4,8 +4,10 @@ import {
   RouteComponent,
   createWebHashHistory
 } from "vue-router";
+import { split } from "lodash-es";
 import { i18n } from "/@/plugins/i18n";
 import NProgress from "/@/utils/progress";
+import { openLink } from "/@/utils/link";
 import { storageSession, storageLocal } from "/@/utils/storage";
 import { usePermissionStoreHook } from "/@/store/modules/permission";
 
@@ -128,13 +130,20 @@ const whiteList = ["/login", "/register"];
 router.beforeEach((to, _from, next) => {
   const name = storageSession.getItem("info");
   NProgress.start();
+  const externalLink = to?.redirectedFrom?.fullPath;
   // @ts-ignore
   const { t } = i18n.global;
   // @ts-ignore
-  to.meta.title ? (document.title = t(to.meta.title)) : "";
+  if (!externalLink) to.meta.title ? (document.title = t(to.meta.title)) : "";
   if (name) {
     if (_from?.name) {
-      next();
+      // 如果路由包含http 则是超链接 反之是普通路由
+      if (externalLink && externalLink.includes("http")) {
+        openLink(`http${split(externalLink, "http")[1]}`);
+        NProgress.done();
+      } else {
+        next();
+      }
     } else {
       // 刷新
       if (usePermissionStoreHook().wholeRoutes.length === 0)

+ 4 - 6
src/style/element-ui.scss

@@ -36,9 +36,7 @@
 
 // dropdown
 .el-dropdown-menu {
-  a {
-    display: block;
-  }
+  padding: 2px 0 2px 0 !important;
 }
 
 // to fix el-date-picker css style
@@ -51,6 +49,6 @@
 }
 
 // el-tooltip,的权重
-.is-dark {
-  z-index: 99999 !important;
-}
+// .is-dark {
+//   z-index: 99999 !important;
+// }

+ 0 - 10
src/style/index.scss

@@ -84,12 +84,6 @@ ul {
   display: none !important;
 }
 
-.resetTop {
-  top: 48px !important;
-  outline: 0;
-  box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
-}
-
 // 灰色模式
 .html-grey {
   filter: grayscale(100%);
@@ -115,7 +109,3 @@ ul {
 .mobile-spacing {
   margin: 0;
 }
-
-.el-popper[data-popper-placement^="bottom"] > .el-popper__arrow {
-  top: 0 !important;
-}

+ 333 - 50
src/style/sidebar.scss

@@ -8,11 +8,13 @@
   // 子菜单背景
   $subMenuBg,
   // 鼠标覆盖子菜单时的背景
-  $subMenuHover
+  $subMenuHover,
+  // vertical模式下主体内容距离网页文档左侧的距离
+  $sideBarWidth,
+  $navTextColor
 ) {
   $menuText: #7a80b4;
   $menuActiveText: #7a80b4;
-  $sideBarWidth: 210px;
 
   .main-container {
     min-height: 100%;
@@ -21,9 +23,13 @@
     position: relative;
   }
 
+  .el-popper.is-light {
+    border: none !important;
+  }
+
   .sidebar-container {
     transition: width 0.28s;
-    width: $sideBarWidth !important;
+    width: $sideBarWidth;
     background-color: $menuBg;
     height: 100%;
     position: fixed;
@@ -35,16 +41,15 @@
     overflow: hidden;
     box-shadow: 0 0 1px #888;
 
-    // reset element-plus css
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
     .horizontal-collapse-transition {
       transition: 0s width ease-in-out, 0s padding-left ease-in-out,
         0s padding-right ease-in-out;
     }
 
-    .scrollbar-wrapper {
-      overflow-x: hidden !important;
-    }
-
     .el-scrollbar__bar.is-vertical {
       right: 0;
     }
@@ -72,9 +77,7 @@
     .el-menu {
       border: none;
       height: 100%;
-      background-color: transparent;
-      // background-color: $menuBg !important;
-      // width: 100% !important;
+      background-color: transparent !important;
     }
 
     .el-menu-item,
@@ -85,7 +88,7 @@
     // menu hover
     .submenu-title-noDropdown,
     .el-sub-menu__title {
-      background: $menuBg;
+      // background: $menuBg;
 
       &:hover {
         background-color: $menuHover !important;
@@ -106,7 +109,7 @@
       color: $subMenuActiveText !important;
     }
 
-    & .nest-menu .el-sub-menu > .el-sub-menu__title,
+    .el-menu .el-menu--inline .el-sub-menu__title,
     & .el-sub-menu .el-menu-item {
       font-size: 12px;
       min-width: $sideBarWidth !important;
@@ -118,51 +121,157 @@
     }
   }
 
-  .hideSidebar {
-    .sidebar-container {
-      width: 54px !important;
+  .horizontal-header {
+    display: flex;
+    justify-content: space-around;
+    background-color: $menuBg;
+    width: 100%;
+    height: 62px;
+    align-items: center;
+
+    .horizontal-header-left {
+      display: flex;
+      height: 100%;
+      width: auto;
+      min-width: 200px;
+      align-items: center;
+      padding-left: 10px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+
+      &:hover {
+        background: $menuHover;
+      }
+
+      i {
+        font-size: 30px;
+        color: #1890ff;
+        margin-right: 4px;
+      }
+
+      h4 {
+        font-size: 16px;
+        font-weight: 700;
+        color: $navTextColor;
+        transition: all 0.5s;
+      }
     }
 
-    .main-container {
-      margin-left: 54px;
+    .horizontal-header-menu {
+      height: 100%;
+      min-width: 0;
+      flex: 1;
+      // todo::
+      overflow-x: auto;
+      align-items: center;
     }
 
-    .submenu-title-noDropdown {
-      padding: 0 !important;
-      position: relative;
+    .horizontal-header-right {
+      display: flex;
+      min-width: 280px;
+      align-items: center;
+      color: $navTextColor;
+      justify-content: flex-end;
 
-      .el-tooltip {
-        padding: 0 !important;
+      .screen-full {
+        cursor: pointer;
+
+        &:hover {
+          background: $menuHover;
+        }
       }
-    }
 
-    .el-sub-menu {
-      overflow: hidden;
+      .iconinternationality {
+        height: 62px;
+        width: 40px;
+        padding: 11px;
+        cursor: pointer;
+        color: $navTextColor;
 
-      & > .el-sub-menu__title {
-        .el-sub-menu__icon-arrow {
-          display: none;
+        &:hover {
+          background: $menuHover;
         }
       }
-    }
 
-    .el-menu--collapse {
-      margin-left: -5px; //需优化的地方
-      .el-sub-menu {
-        & > .el-sub-menu__title {
-          & > span {
-            height: 0;
-            width: 0;
-            overflow: hidden;
-            visibility: hidden;
-            display: inline-block;
-          }
+      .el-dropdown-link {
+        width: 100px;
+        height: 62px;
+        padding: 10px;
+        display: flex;
+        align-items: center;
+        justify-content: space-around;
+        cursor: pointer;
+        color: $navTextColor;
+
+        &:hover {
+          background: $menuHover;
         }
+
+        p {
+          font-size: 14px;
+        }
+
+        img {
+          width: 22px;
+          height: 22px;
+          border-radius: 50%;
+        }
+      }
+
+      .el-icon-setting {
+        height: 62px;
+        width: 40px;
+        padding: 11px;
+        display: flex;
+        cursor: pointer;
+        align-items: center;
+
+        &:hover {
+          background: $menuHover;
+        }
+      }
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      background-color: transparent;
+      width: 100% !important;
+    }
+
+    .el-menu-item,
+    .el-sub-menu__title {
+      color: $menuText;
+    }
+
+    .submenu-title-noDropdown,
+    .el-sub-menu__title {
+      height: 60px;
+      background: $menuBg;
+
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    .is-active > .el-sub-menu__title,
+    .is-active.submenu-title-noDropdown {
+      color: $subMenuActiveText !important;
+      border-bottom-color: #409eff;
+
+      i {
+        color: $subMenuActiveText !important;
       }
     }
+
+    .is-active {
+      transition: color 0.3s;
+      color: $subMenuActiveText !important;
+      border-bottom-color: #409eff;
+    }
   }
 
-  // 菜单折叠
+  // vertical菜单折叠
   .el-menu--vertical {
     .el-menu--popup {
       background-color: $subMenuBg !important;
@@ -196,6 +305,81 @@
       }
     }
 
+    // 子菜单中还有子菜单
+    .el-menu .el-sub-menu__title {
+      font-size: 12px;
+      min-width: $sideBarWidth !important;
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    .is-active {
+      transition: color 0.3s;
+      color: $subMenuActiveText !important;
+    }
+
+    .nest-menu .el-sub-menu > .el-sub-menu__title,
+    .el-menu-item {
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+  }
+
+  // horizontal菜单折叠
+  .el-menu--horizontal {
+    .el-menu--popup {
+      background-color: $subMenuBg !important;
+
+      .el-menu-item {
+        color: $menuText;
+        background-color: $subMenuBg;
+
+        &:hover {
+          background-color: $subMenuHover;
+        }
+      }
+
+      .el-sub-menu__title {
+        color: $menuText;
+      }
+    }
+
+    // 无子菜单时激活border-bottom
+    .router-link-exact-active > .submenu-title-noDropdown {
+      height: 60px;
+      border-bottom: 2px solid var(--el-menu-active-color);
+    }
+
+    // 子菜单中还有子菜单
+    .el-menu .el-sub-menu__title {
+      font-size: 12px;
+      min-width: $sideBarWidth !important;
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    & > .el-menu {
+      i {
+        margin-right: 16px;
+      }
+    }
+
+    .is-active > .el-sub-menu__title,
+    .is-active.submenu-title-noDropdown {
+      color: $subMenuActiveText !important;
+
+      i {
+        color: $subMenuActiveText !important;
+      }
+    }
+
     .is-active {
       transition: color 0.3s;
       color: $subMenuActiveText !important;
@@ -204,7 +388,6 @@
     .nest-menu .el-sub-menu > .el-sub-menu__title,
     .el-menu-item {
       &:hover {
-        // you can use $subMenuHover
         background-color: $menuHover !important;
       }
     }
@@ -222,7 +405,7 @@
   // 手机端
   .mobile {
     .main-container {
-      margin-left: 0;
+      margin-left: 0 !important;
     }
 
     .sidebar-container {
@@ -247,36 +430,136 @@
   }
 }
 
-// 暗色主题
-body[data-theme="dark"] {
+body[data-layout="vertical"] {
+  .hideSidebar {
+    .fixed-header {
+      width: calc(100% - 54px);
+    }
+
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px !important;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+      }
+    }
+
+    .el-sub-menu {
+      overflow: hidden;
+
+      & > .el-sub-menu__title {
+        .el-sub-menu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+
+    .el-menu--collapse {
+      margin-left: -5px; //需优化的地方
+      .el-sub-menu {
+        & > .el-sub-menu__title {
+          & > span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+}
+
+// vertical模式下暗色主题
+body[data-layout="vertical"][data-theme="dark"] {
+  $subMenuActiveText: #f4f4f5;
+  $menuBg: #1b2a47;
+  $menuHover: #2a395b;
+  $subMenuBg: #1f2d3d;
+  $subMenuHover: #001528;
+  $sideBarWidth: 210px;
+  $navTextColor: #fff;
+
+  @include merge-style(
+    $subMenuActiveText,
+    $menuBg,
+    $menuHover,
+    $subMenuBg,
+    $subMenuHover,
+    $sideBarWidth,
+    $navTextColor
+  );
+}
+
+// vertical模式下亮色主题
+body[data-layout="vertical"][data-theme="light"] {
+  $subMenuActiveText: #409eff;
+  $menuBg: #fff;
+  $menuHover: #e0ebf6;
+  $subMenuBg: #fff;
+  $subMenuHover: #e0ebf6;
+  $sideBarWidth: 210px;
+  $navTextColor: #7a80b4;
+
+  @include merge-style(
+    $subMenuActiveText,
+    $menuBg,
+    $menuHover,
+    $subMenuBg,
+    $subMenuHover,
+    $sideBarWidth,
+    $navTextColor
+  );
+}
+
+// horizontal模式下暗色主题
+body[data-layout="horizontal"][data-theme="dark"] {
   $subMenuActiveText: #f4f4f5;
   $menuBg: #1b2a47;
   $menuHover: #2a395b;
   $subMenuBg: #1f2d3d;
   $subMenuHover: #001528;
+  $sideBarWidth: 0;
+  $navTextColor: #fff;
 
   @include merge-style(
     $subMenuActiveText,
     $menuBg,
     $menuHover,
     $subMenuBg,
-    $subMenuHover
+    $subMenuHover,
+    $sideBarWidth,
+    $navTextColor
   );
 }
 
-// 亮色主题
-body[data-theme="light"] {
+// horizontal模式下亮色主题
+body[data-layout="horizontal"][data-theme="light"] {
   $subMenuActiveText: #409eff;
   $menuBg: #fff;
   $menuHover: #e0ebf6;
   $subMenuBg: #fff;
   $subMenuHover: #e0ebf6;
+  $sideBarWidth: 0;
+  $navTextColor: #7a80b4;
 
   @include merge-style(
     $subMenuActiveText,
     $menuBg,
     $menuHover,
     $subMenuBg,
-    $subMenuHover
+    $subMenuHover,
+    $sideBarWidth,
+    $navTextColor
   );
 }

+ 12 - 0
src/utils/link.ts

@@ -0,0 +1,12 @@
+export const openLink = (link: string) => {
+  const $a: HTMLElement = document.createElement("a");
+  $a.setAttribute("href", link);
+  $a.setAttribute("target", "_blank");
+  $a.setAttribute("rel", "noreferrer noopener");
+  $a.setAttribute("id", "external");
+  document.getElementById("external") &&
+    document.body.removeChild(document.getElementById("external"));
+  document.body.appendChild($a);
+  $a.click();
+  $a.remove();
+};

+ 136 - 55
yarn.lock

@@ -420,11 +420,6 @@
   dependencies:
     chalk "^4.0.0"
 
-"@element-plus/icons@^0.0.11":
-  version "0.0.11"
-  resolved "https://registry.npmjs.org/@element-plus/icons/-/icons-0.0.11.tgz#9b187c002774548b911850d17fa5fc2f9a515f57"
-  integrity sha512-iKQXSxXu131Ai+I9Ymtcof9WId7kaXvB1+WRfAfpQCW7UiAMYgdNDqb/u0hgTo2Yq3MwC4MWJnNuTBEpG8r7+A==
-
 "@eslint/eslintrc@^0.4.3":
   version "0.4.3"
   resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
@@ -786,6 +781,16 @@
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
+"@vue/compiler-core@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.19.tgz#b537dd377ce51fdb64e9b30ebfbff7cd70a64cb9"
+  integrity sha512-8dOPX0YOtaXol0Zf2cfLQ4NU/yHYl2H7DCKsLEZ7gdvPK6ZSEwGLJ7IdghhY2YEshEpC5RB9QKdC5I07z8Dtjg==
+  dependencies:
+    "@babel/parser" "^7.15.0"
+    "@vue/shared" "3.2.19"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
 "@vue/compiler-dom@3.2.11":
   version "3.2.11"
   resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.11.tgz#d066f8e1f1812b4e881593819ade0fe6d654c776"
@@ -794,7 +799,31 @@
     "@vue/compiler-core" "3.2.11"
     "@vue/shared" "3.2.11"
 
-"@vue/compiler-sfc@3.2.11", "@vue/compiler-sfc@^3.0.11":
+"@vue/compiler-dom@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.19.tgz#0607bc90de6af55fde73b09b3c4d0bf8cb597ed8"
+  integrity sha512-WzQoE8rfkFjPtIioc7SSgTsnz9g2oG61DU8KHnzPrRS7fW/lji6H2uCYJfp4Z6kZE8GjnHc1Ljwl3/gxDes0cw==
+  dependencies:
+    "@vue/compiler-core" "3.2.19"
+    "@vue/shared" "3.2.19"
+
+"@vue/compiler-sfc@3.2.19", "@vue/compiler-sfc@^3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.19.tgz#d412195a98ebd49b84602f171719294a1d9549be"
+  integrity sha512-pLlbgkO1UHTO02MSpa/sFOXUwIDxSMiKZ1ozE5n71CY4DM+YmI+G3gT/ZHZ46WBId7f3VTF/D8pGwMygcQbrQA==
+  dependencies:
+    "@babel/parser" "^7.15.0"
+    "@vue/compiler-core" "3.2.19"
+    "@vue/compiler-dom" "3.2.19"
+    "@vue/compiler-ssr" "3.2.19"
+    "@vue/ref-transform" "3.2.19"
+    "@vue/shared" "3.2.19"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
+"@vue/compiler-sfc@^3.0.11":
   version "3.2.11"
   resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.11.tgz#628fa12238760d9b9b339ac2e125a759224fadbf"
   integrity sha512-cUIaS8mgJrQ6yucj2AupWAwBRITK3W/a8wCOn9g5fJGtOl8h4APY8vN3lzP8HIJDyEeRF3I8SfRhL+oX97kSnw==
@@ -826,6 +855,14 @@
     "@vue/compiler-dom" "3.2.11"
     "@vue/shared" "3.2.11"
 
+"@vue/compiler-ssr@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.19.tgz#3e91ecf70f8f961c5f63eacd2139bcdab9a7a07c"
+  integrity sha512-oLon0Cn3O7WEYzzmzZavGoqXH+199LT+smdjBT3Uf3UX4HwDNuBFCmvL0TsqV9SQnIgKvBRbQ7lhbpnd4lqM3w==
+  dependencies:
+    "@vue/compiler-dom" "3.2.19"
+    "@vue/shared" "3.2.19"
+
 "@vue/devtools-api@^6.0.0-beta.13", "@vue/devtools-api@^6.0.0-beta.14", "@vue/devtools-api@^6.0.0-beta.15":
   version "6.0.0-beta.15"
   resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz#ad7cb384e062f165bcf9c83732125bffbc2ad83d"
@@ -845,12 +882,12 @@
   dependencies:
     vue-eslint-parser "^7.0.0"
 
-"@vue/reactivity@3.2.11":
-  version "3.2.11"
-  resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.11.tgz#ec04d33acaf2b92cca2960535bec81b26cc5772b"
-  integrity sha512-hEQstxPQbgGZq5qApzrvbDmRdK1KP96O/j4XrwT8fVkT1ytkFs4fH2xNEh9QKwXfybbQkLs77W7OfXCv5o6qbA==
+"@vue/reactivity@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.19.tgz#fc6e0f0106f295226835cfed5ff5f84d927bea65"
+  integrity sha512-FtachoYs2SnyrWup5UikP54xDX6ZJ1s5VgHcJp4rkGoutU3Ry61jhs+nCX7J64zjX992Mh9gGUC0LqTs8q9vCA==
   dependencies:
-    "@vue/shared" "3.2.11"
+    "@vue/shared" "3.2.19"
 
 "@vue/ref-transform@3.2.11":
   version "3.2.11"
@@ -863,28 +900,52 @@
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
-"@vue/runtime-core@3.2.11":
-  version "3.2.11"
-  resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.11.tgz#0dbe801be4bd0bfde253226797e7d304c8fdda30"
-  integrity sha512-horlxjWwSvModC87WdsWswzzHE5IexmKkQA65S5vFgP5hLUBW+HRyScDeuB/RRcFmqnf+ozacNCfap0kqcpODw==
+"@vue/ref-transform@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.19.tgz#cf0f986486bb26838fbd09749e927bab19745600"
+  integrity sha512-03wwUnoIAeKti5IGGx6Vk/HEBJ+zUcm5wrUM3+PQsGf7IYnXTbeIfHHpx4HeSeWhnLAjqZjADQwW8uA4rBmVbg==
   dependencies:
-    "@vue/reactivity" "3.2.11"
-    "@vue/shared" "3.2.11"
+    "@babel/parser" "^7.15.0"
+    "@vue/compiler-core" "3.2.19"
+    "@vue/shared" "3.2.19"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
 
-"@vue/runtime-dom@3.2.11":
-  version "3.2.11"
-  resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.11.tgz#04f9054a9e64bdf156c2fc22cad67cfaa8b84616"
-  integrity sha512-cOK1g0INdiCbds2xrrJKrrN+pDHuLz6esUs/crdEiupDuX7IeiMbdqrAQCkYHp5P1KLWcbGlkmwfVD7HQGii0Q==
+"@vue/runtime-core@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.19.tgz#807715b7f4728abb84fa4a8efdbe37d8ddb4c6d3"
+  integrity sha512-qArZSWKxWsgKfxk9BelZ32nY0MZ31CAW2kUUyVJyxh4cTfHaXGbjiQB5JgsvKc49ROMNffv9t3/qjasQqAH+RQ==
   dependencies:
-    "@vue/runtime-core" "3.2.11"
-    "@vue/shared" "3.2.11"
+    "@vue/reactivity" "3.2.19"
+    "@vue/shared" "3.2.19"
+
+"@vue/runtime-dom@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.19.tgz#7e8bf645754703e360fa132e4be9113edf2377bb"
+  integrity sha512-hIRboxXwafeHhbZEkZYNV0MiJXPNf4fP0X6hM2TJb0vssz8BKhD9cF92BkRgZztTQevecbhk0gu4uAPJ3dxL9A==
+  dependencies:
+    "@vue/runtime-core" "3.2.19"
+    "@vue/shared" "3.2.19"
     csstype "^2.6.8"
 
+"@vue/server-renderer@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.19.tgz#870bcec9f7cdaee0c2187a169b6e636ab4362fb1"
+  integrity sha512-A9FNT7fgQJXItwdzWREntAgWKVtKYuXHBKGev/H4+ByTu8vB7gQXGcim01QxaJshdNg4dYuH2tEBZXCNCNx+/w==
+  dependencies:
+    "@vue/compiler-ssr" "3.2.19"
+    "@vue/shared" "3.2.19"
+
 "@vue/shared@3.2.11":
   version "3.2.11"
   resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.11.tgz#01899f54949caf1ac241de397bd17069632574de"
   integrity sha512-ovfXAsSsCvV9JVceWjkqC/7OF5HbgLOtCWjCIosmPGG8lxbPuavhIxRH1dTx4Dg9xLgRTNLvI3pVxG4ItQZekg==
 
+"@vue/shared@3.2.19":
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.2.19.tgz#111ec3da18337d86274446984c49925b1b2b2dd7"
+  integrity sha512-Knqhx7WieLdVgwCAZgTVrDCXZ50uItuecLh9JdLC8O+a5ayaSyIQYveUK3hCRNC7ws5zalHmZwfdLMGaS8r4Ew==
+
 "@vueuse/core@^6.4.1":
   version "6.4.1"
   resolved "https://registry.npmjs.org/@vueuse/core/-/core-6.4.1.tgz#21416997a23bfb4924a5082ed6fa959027f80d04"
@@ -1532,7 +1593,7 @@ dargs@^7.0.0:
   resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc"
   integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==
 
-dayjs@1.x, dayjs@^1.10.6:
+dayjs@^1.10.6, dayjs@^1.10.7:
   version "1.10.7"
   resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
   integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
@@ -1707,18 +1768,17 @@ electron-to-chromium@^1.3.830:
   resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.840.tgz#3f2a1df97015d9b1db5d86a4c6bd4cdb920adcbb"
   integrity sha512-yRoUmTLDJnkIJx23xLY7GbSvnmDCq++NSuxHDQ0jiyDJ9YZBUGJcrdUqm+ZwZFzMbCciVzfem2N2AWiHJcWlbw==
 
-element-plus@^1.1.0-beta.12:
-  version "1.1.0-beta.12"
-  resolved "https://registry.npmjs.org/element-plus/-/element-plus-1.1.0-beta.12.tgz#2fd646f3c2f787dfb282d091ec6aa6945d1bec6a"
-  integrity sha512-IkFHcYWGDzHqeL2LpDM5ZQnuviqkNj8Bu9zg/Q6KImGk8xo4rqMf+e4rBHinIpeKzKdVag0c2TDABNBfKvjaPA==
+element-plus@^1.1.0-beta.16:
+  version "1.1.0-beta.16"
+  resolved "https://registry.npmjs.org/element-plus/-/element-plus-1.1.0-beta.16.tgz#4409d9e33d005693f6039f5ed1fe05e301b3170d"
+  integrity sha512-4BZEldnIfFZs5A/saRqaWE4PwTot4p3YZU7qsDr3ev2zp35pcCL9TtpWMLIvNTMxvxKew0HTDPTk9fAWIZFQrQ==
   dependencies:
-    "@element-plus/icons" "^0.0.11"
     "@popperjs/core" "^2.10.1"
     "@vueuse/core" "~6.1.0"
     async-validator "^3.4.0"
-    dayjs "1.x"
+    dayjs "^1.10.7"
     lodash "^4.17.21"
-    mitt "^2.1.0"
+    memoize-one "^5.2.1"
     normalize-wheel "^1.0.1"
     resize-observer-polyfill "^1.5.1"
 
@@ -1817,9 +1877,9 @@ esbuild@0.11.3:
   integrity sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==
 
 esbuild@^0.12.17:
-  version "0.12.28"
-  resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.12.28.tgz#84da0d2a0d0dee181281545271e0d65cf6fab1ef"
-  integrity sha512-pZ0FrWZXlvQOATlp14lRSk1N9GkeJ3vLIwOcUoo3ICQn9WNR4rWoNi81pbn6sC1iYUy7QPqNzI3+AEzokwyVcA==
+  version "0.12.29"
+  resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.12.29.tgz#be602db7c4dc78944a9dbde0d1ea19d36c1f882d"
+  integrity sha512-w/XuoBCSwepyiZtIRsKsetiLDUVGPVw1E/R3VTFSecIy8UR7Cq3SOtwKHJMFoVqqVG36aGkzh4e8BvpO1Fdc7g==
 
 escalade@^3.1.1:
   version "3.1.1"
@@ -2939,6 +2999,11 @@ mdn-data@2.0.14:
   resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
   integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
 
+memoize-one@^5.2.1:
+  version "5.2.1"
+  resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+  integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
 meow@^8.0.0:
   version "8.1.2"
   resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897"
@@ -3081,7 +3146,12 @@ multimatch@^4.0.0:
     arrify "^2.0.1"
     minimatch "^3.0.4"
 
-nanoid@^3.1.23:
+nanocolors@^0.1.5:
+  version "0.1.12"
+  resolved "https://registry.npmjs.org/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
+  integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
+
+nanoid@^3.1.23, nanoid@^3.1.25:
   version "3.1.25"
   resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
   integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
@@ -3504,7 +3574,7 @@ postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.
     source-map "^0.6.1"
     supports-color "^6.1.0"
 
-postcss@^8.1.10, postcss@^8.2.6, postcss@^8.3.6:
+postcss@^8.1.10, postcss@^8.2.6:
   version "8.3.6"
   resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea"
   integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==
@@ -3513,6 +3583,15 @@ postcss@^8.1.10, postcss@^8.2.6, postcss@^8.3.6:
     nanoid "^3.1.23"
     source-map-js "^0.6.2"
 
+postcss@^8.3.6:
+  version "8.3.7"
+  resolved "https://registry.npmjs.org/postcss/-/postcss-8.3.7.tgz#ec88563588c8da8e58e7226f7633b51ae221eeda"
+  integrity sha512-9SaY7nnyQ63/WittqZYAvkkYPyKxchMKH71UDzeTmWuLSvxTRpeEeABZAzlCi55cuGcoFyoV/amX2BdsafQidQ==
+  dependencies:
+    nanocolors "^0.1.5"
+    nanoid "^3.1.25"
+    source-map-js "^0.6.2"
+
 preact@^10.4.8:
   version "10.5.14"
   resolved "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz#0b14a2eefba3c10a57116b90d1a65f5f00cd2701"
@@ -3713,12 +3792,12 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.19.0, resolve@^1.20.0:
     is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
-responsive-storage@^1.0.9:
-  version "1.0.9"
-  resolved "https://registry.npmjs.org/responsive-storage/-/responsive-storage-1.0.9.tgz#389544da44fbd34adb8bd210dd6b4d6d4795642f"
-  integrity sha512-GODZF3a50hvnBvAntqDgiEub4+xXRcdACH1wnsM8KVmSnSd0IKqCcrE0fwk5b6y3lPW2h+ZpB3bBMcQpkuTeJg==
+responsive-storage@^1.0.10:
+  version "1.0.10"
+  resolved "https://registry.npmjs.org/responsive-storage/-/responsive-storage-1.0.10.tgz#a872d7a1ea4f78f3e77d60ae62910483114cd445"
+  integrity sha512-M4j7C7/2xAxEfXuwoyt4Xh9N14o0pItsBnMK/BrNOi0mGpxcpDERNrSCPFvVM0avmpi4tliYduqU96ffOT16Ww==
   dependencies:
-    vue "^3.1.1"
+    vue "^3.2.19"
 
 restore-cursor@^3.1.0:
   version "3.1.0"
@@ -3741,9 +3820,9 @@ rimraf@^3.0.2:
     glob "^7.1.3"
 
 rollup@^2.38.5:
-  version "2.56.3"
-  resolved "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff"
-  integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==
+  version "2.57.0"
+  resolved "https://registry.npmjs.org/rollup/-/rollup-2.57.0.tgz#c1694475eb22e1022477c0f4635fd0ac80713173"
+  integrity sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==
   optionalDependencies:
     fsevents "~2.3.2"
 
@@ -4446,10 +4525,10 @@ vite-svg-loader@^2.2.0:
     "@vue/compiler-sfc" "^3.0.11"
     svgo "^2.3.0"
 
-vite@^2.5.7:
-  version "2.5.7"
-  resolved "https://registry.npmjs.org/vite/-/vite-2.5.7.tgz#e495be9d8bcbf9d30c7141efdccacde746ee0125"
-  integrity sha512-hyUoWmRPhjN1aI+ZSBqDINKdIq7aokHE2ZXiztOg4YlmtpeQtMwMeyxv6X9YxHZmvGzg/js/eATM9Z1nwyakxg==
+vite@^2.5.10:
+  version "2.5.10"
+  resolved "https://registry.npmjs.org/vite/-/vite-2.5.10.tgz#c598e3b5a7e1956ffc52eb3b3420d177fc2ed2a5"
+  integrity sha512-0ObiHTi5AHyXdJcvZ67HMsDgVpjT5RehvVKv6+Q0jFZ7zDI28PF5zK9mYz2avxdA+4iJMdwCz6wnGNnn4WX5Gg==
   dependencies:
     esbuild "^0.12.17"
     postcss "^8.3.6"
@@ -4505,14 +4584,16 @@ vue-types@^4.1.0:
   dependencies:
     is-plain-object "5.0.0"
 
-vue@3.2.11, vue@^3.1.1:
-  version "3.2.11"
-  resolved "https://registry.npmjs.org/vue/-/vue-3.2.11.tgz#6b92295048df705ddac558fd3e3ed553e55e57c8"
-  integrity sha512-JkI3/eIgfk4E0f/p319TD3EZgOwBQfftgnkRsXlT7OrRyyiyoyUXn6embPGZXSBxD3LoZ9SWhJoxLhFh5AleeA==
+vue@^3.2.19:
+  version "3.2.19"
+  resolved "https://registry.npmjs.org/vue/-/vue-3.2.19.tgz#da2c80a6a0271c7097fee9e31692adfd9d569c8f"
+  integrity sha512-6KAMdIfAtlK+qohTIUE4urwAv4A3YRuo8uAbByApUmiB0CziGAAPs6qVugN6oHPia8YIafHB/37K0O6KZ7sGmA==
   dependencies:
-    "@vue/compiler-dom" "3.2.11"
-    "@vue/runtime-dom" "3.2.11"
-    "@vue/shared" "3.2.11"
+    "@vue/compiler-dom" "3.2.19"
+    "@vue/compiler-sfc" "3.2.19"
+    "@vue/runtime-dom" "3.2.19"
+    "@vue/server-renderer" "3.2.19"
+    "@vue/shared" "3.2.19"
 
 vuedraggable@^4.1.0:
   version "4.1.0"

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