lvkun 2 жил өмнө
parent
commit
038f3cadc5

+ 1 - 0
package.json

@@ -35,6 +35,7 @@
     "normalize.css": "^8.0.1",
     "path-browserify": "^1.0.1",
     "pinia": "^2.0.33",
+    "pinia-plugin-persistedstate": "^3.2.0",
     "style-resources-loader": "^1.5.0",
     "vue": "^3.3.4",
     "vue-class-component": "^8.0.0-0",

+ 1 - 0
src/App.vue

@@ -13,6 +13,7 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN'
 import dayjs from 'dayjs'
 import 'dayjs/locale/zh-cn'
 import { useDesignStore } from '@/store'
+import { useRouter } from 'vue-router'
 
 dayjs.locale('zh-cn')
 

+ 9 - 6
src/layout/components/Sidebar/index.vue

@@ -37,17 +37,16 @@
         </a-menu>
     </a-layout-sider>
   </a-drawer>
-
+  <!--     v-model:collapsed="collapsed" -->
+  <!--  collapsible -->
   <a-layout-sider
     v-if="!isMobile"
-    v-model:collapsed="collapsed"
-    collapsible
-    :style="{backgroundColor: bgColor, overflow: 'hidden', overflowY: 'scroll'}"
+    :style="{backgroundColor: bgColor, overflow: 'hidden', overflowY: 'scroll', boxShadow: `0 1px 4px rgba(0,21,41,.12)`, paddingTop: '6px'}"
     breakpoint="lg"
   >
-    <div class="logo" >
+    <!-- <div class="logo" >
       <img :src="logoPng" alt="">
-    </div>
+    </div> -->
     <a-menu
       v-model:selectedKeys="selectedKeys"
       :openKeys="openKeys"
@@ -158,4 +157,8 @@ const openDrawer = () => drawerVisible.value = true
   border-bottom-left-radius: 0px;
 }
 
+/deep/ .ant-layout-sider-trigger {
+  background-color: #fff !important;
+}
+
 </style>

+ 31 - 6
src/layout/layout.vue

@@ -1,11 +1,14 @@
 <template>
     <a-layout class="a-layout" >
-      <SiderBar />
+      <Navbar />
       <a-layout >
-        <Navbar />
-        <span style="padding: 0 24px 24px;margin-top: 24px;overflow: hidden;overflow-y: scroll" >
-          <RouterView></RouterView>
-        </span>
+        <SiderBar />
+        <div class="content"  >
+          <RouterTravel />
+          <div class="router-view" >
+            <RouterView :key="useRouterTravel.keyCount" ></RouterView>
+          </div>
+        </div>
       </a-layout>
     </a-layout>
 </template>
@@ -13,14 +16,36 @@
 <script  lang="ts" setup >
 import Navbar from './navbar.vue'
 import SiderBar from './components/Sidebar/index.vue'
+import RouterTravel from './routerTravel.vue'
+import { useRouterTravelStore } from '@/store'
+
+const useRouterTravel = useRouterTravelStore()
 
 </script>
 
-<style>
+<style lang="less" scoped >
 
 .a-layout {
   /* width: 100vw; */
   height: 100vh;
+  overflow: hidden;
+  .content {
+    width: 100%;
+    height: 100%;
+    border-top-left-radius: 24px;
+    // overflow: hidden;
+    // overflow-y: scroll;
+    .router-view {
+      width: 100%;
+      height: 100%;
+      padding: 0 24px 24px;
+      padding-bottom: 160px;
+      margin-top: 24px;
+      // box-sizing: border-box;
+      overflow: hidden;
+      overflow-y: scroll;
+    }
+  }
 }
 
 #components-layout-demo-top-side-2 .logo {

+ 32 - 16
src/layout/navbar.vue

@@ -15,14 +15,19 @@
         </a-col>
       </a-row>
   </a-layout-header>
-  <a-layout-header class="header-pc" v-else :style="{backgroundColor: headerBgColor}">
-      <a-row>
-        <a-col :span="15" >
+  <a-layout-header class="header-pc" v-else :style="{backgroundColor: headerBgColor, height: '54px'}">
+      <a-row style="width: 100%;height: 54px;" justify="center" >
+        <a-col :span="2" >
+          <div class="logo" style="height: 54px;" >
+            <img :src="logoPng" alt="">
+          </div>
+        </a-col>
+        <a-col :span="13" style="height: 54px;" >
         <a-menu
           mode="horizontal"
-          :style="{ lineHeight: '64px', border: 'none' }"
+          :style="{ lineHeight: '54px', border: 'none' }"
           :selectedKeys="selectedKeys"
-      >
+        >
           <a-menu-item
             v-for="route in appRouter.$state.router.navbar.route"
             :key="route.path"
@@ -32,15 +37,15 @@
           </a-menu-item>
         </a-menu>
         </a-col>
-        <a-col :span="9" >
-          <a-row :gutter="[8, 8]" justify="end"  >
-            <a-col  class="df-center" >
+        <a-col :span="9" style="height: 54px;" >
+          <a-row :gutter="[8, 8]" justify="end"  style="height: 54px;" >
+            <a-col  class="df-center" style="height: 54px;" >
               <search />
             </a-col>
-            <a-col class="df-center" >
+            <a-col class="df-center"  style="height: 54px;">
               <a-button type="text" @click="changeTheme" > <IconTsx :name="iconName" /></a-button>
             </a-col>
-            <a-col class="df-center" >
+            <a-col class="df-center"  style="height: 54px;">
               <user />
             </a-col>
           </a-row>
@@ -80,12 +85,13 @@ const isMobile = useDeviceType()
 const changeTheme = () => designStore.changeModeltheme()
 
 const changeRouter = (route: ROUTER.RoutesProps) => {
-  if (route.link) {
-    window.open(route.path)
-  } else {
-    selectedKeys.value = [route.path]
-    router.push(route.path)
-  }
+  // if (route.link) {
+  //   window.open(route.path)
+  // } else {
+  //   selectedKeys.value = [route.path]
+  //   router.push(route.path)
+  // }
+  appRouter.changeNavbarRoute(route)
 }
 
 </script>
@@ -96,6 +102,16 @@ const changeRouter = (route: ROUTER.RoutesProps) => {
   padding: 0 24px;
 }
 
+.header-pc {
+  width: 100%;
+  position: relative;
+  height: 54px;
+  z-index: 20;
+  box-shadow: 0 1px 4px rgba(0,21,41,.12);
+  overflow: hidden;
+  padding-left: 20px;
+}
+
 .df-center {
   display: flex;
   justify-content: center;

+ 40 - 0
src/layout/pageHeader.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="page-header" >
+    <div class="title" >{{title}}</div>
+    <div class="desc" >{{desc}}</div>
+    <slot></slot>
+  </div>
+</template>
+
+<script lang="ts" setup >
+import { defineProps } from 'vue'
+
+interface IProps {
+  title: string
+  desc: string
+}
+const props = defineProps<IProps>()
+
+</script>
+
+<style lang="less" scoped >
+.page-header {
+  width: 100%;
+  height: 144px;
+  background-color: #fff;
+  padding: 20px;
+  border-radius: 8px;
+  .title {
+    color: rgba(0, 0, 0, 0.88);
+    font-weight: 600;
+    font-size: 20px;
+    line-height: 32px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+  .desc {
+    margin-top: 38px;
+  }
+}
+</style>

+ 160 - 0
src/layout/routerTravel.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="router-travel"  ref="routeTravelDom"  >
+    <div class="route-list" ref="routeListDom"  @wheel="horizontalScroll">
+      <div
+        :class="[
+          'route',
+          useRouterTravel.currentRoute?.path === item.path ? 'active' : ''
+        ]"
+        v-for="(item, index) in useRouterTravel.history"
+        :key="item.path"
+        @click="onChangeRoute(item)"
+      >
+      <a-space>
+        <span :style="{textAlign: index === 0 ? 'center' : 'right'}"  >{{item.name}}</span>
+        <ReloadIconTsx
+          :loading="loading"
+          v-if="useRouterTravel.currentRoute?.path === item.path"
+          :reload="reloadPage"
+        />
+        <CloseOutlined v-if="index != 0" @click="delRouter(item.path)" />
+      </a-space>
+      </div>
+    </div>
+    <div class="expend-button" ref="expendButtonDom" >
+      <a-space>
+        <a-dropdown>
+          <a class="ant-dropdown-link" @click.prevent>
+            <MoreOutlined style="font-size: 24px;" />
+          </a>
+          <template #overlay>
+            <a-menu>
+              <a-menu-item>
+                <a href="javascript:;" @click="useRouterTravel.closeOtherPage">关闭其他页</a>
+              </a-menu-item>
+              <a-menu-item>
+                <a href="javascript:;" @click="reloadPage">刷新当前页</a>
+              </a-menu-item>
+            </a-menu>
+          </template>
+        </a-dropdown>
+      </a-space>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouterTravelStore } from '@/store'
+import { ReloadIconTsx } from '@/components/MicroComponents'
+import { CloseOutlined, MoreOutlined } from '@ant-design/icons-vue'
+import { useSchedulerOnce } from '@/hooks'
+
+const useRouterTravel = useRouterTravelStore()
+
+const loading = ref<boolean>(false)
+
+const routeTravelDom = ref<HTMLElement>()
+
+const routeListDom = ref<HTMLElement>()
+
+const expendButtonDom = ref<HTMLElement>()
+
+const onChangeRoute = (item) => useRouterTravel.setCurrentRoute(item)
+
+const reloadPage = () => {
+  loading.value = true
+  useRouterTravel.keyCount++
+  useSchedulerOnce(() => {
+    loading.value = false
+  }, 300)
+}
+
+const delRouter = (path: string) => {
+  console.log('path:', path)
+  event?.stopPropagation()
+  useRouterTravel.del(path)
+}
+
+const horizontalScroll = (event) => {
+  const x = event.deltaY > 0 ? 100 : -100
+  routeTravelDom.value!.scrollBy(x, 0)
+  event.preventDefault()
+}
+
+</script>
+
+<style lang="less" scoped >
+@import '~@/styles/theme.less';
+.router-travel {
+  width: 100%;
+  height: 46px;
+  background-color: #fff;
+  overflow: hidden;
+  overflow-x: scroll;
+  border:  1px solid rgba(5, 5, 5, 0.06);
+  margin-left: -1px;
+  box-sizing: border-box;
+  display: flex;
+  justify-content: space-between;
+  position: relative;
+  .route-list {
+    width: 100%;
+    min-width: 1200px;
+    padding-left: 12px;
+    display: flex;
+    margin-top: 5px;
+    overflow: hidden;
+    position: absolute;
+    left: 0;
+    .route {
+      height: 40px;
+      line-height: 40px;
+      border-radius: 8px 8px 0px -0px;
+      border: 1px solid rgba(5, 5, 5, 0.06);
+      background: rgba(0, 0, 0, 0.02);
+      cursor: pointer;
+      margin-right: 4px;
+      padding: 0 8px;
+      box-sizing: border-box;
+      transition: all 0.2s;
+      display: inline-flex;
+      justify-content: center;
+      align-items: center;
+      white-space: nowrap;
+    }
+
+    .active {
+      color: @primary-color;
+      background-color: #fff;
+    }
+  }
+
+  .router-list ::-webkit-scrollbar {
+    background: transparent;
+    width: 0 !important;
+    height: 0;
+  }
+
+  .expend-button {
+    height: 43px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    cursor: pointer;
+    z-index: 1;
+    position: fixed;
+    right: 0px;
+    background-color: #fff;
+    box-shadow: 0 0px 4px rgba(0,21,41,.12);
+    padding: 0 12px;
+    box-sizing: border-box;
+  }
+}
+
+.router-travel::-webkit-scrollbar {
+    background: transparent;
+    width: 0 !important;
+    height: 0;
+  }
+</style>

+ 3 - 0
src/main.ts

@@ -10,9 +10,12 @@ import cronAnt from '@vue-js-cron/ant'
 import '@vue-js-cron/ant/dist/ant.css'
 import 'normalize.css'
 import { keyboard } from '@/directives/index'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 
 const pinia = createPinia()
 
+pinia.use(piniaPluginPersistedstate)
+
 createApp(App)
   .use(router)
   .use(pinia)

+ 4 - 1
src/pages/Iot/dashboard/deviceAccess/index.vue

@@ -1,4 +1,6 @@
 <template>
+
+  <!-- <page-header title="首頁" desc="有着一些数据统计" > </page-header> -->
     <a-card
       title="功能介绍"
     >
@@ -36,7 +38,8 @@
     </a-card>
 
     <!-- 资源 -->
-    <a-card title="资源概览" style="margin-top: 20px">
+    <a-card title="资源概览" style="margin-top: 20px"
+    >
       <a-row :gutter="[8, 8]" justify="space-between" >
         <a-col
           v-for="item in sourceList"

+ 3 - 19
src/pages/Iot/dataServer/history.vue

@@ -62,27 +62,11 @@ const columns = [
     dataIndex: 'keyLabel'
   },
   {
-    title: 'stringValue',
-    dataIndex: 'stringValue'
+    title: '结果',
+    dataIndex: 'value'
   },
   {
-    title: 'doubleValue',
-    dataIndex: 'doubleValue'
-  },
-  {
-    title: 'longValue',
-    dataIndex: 'longValue'
-  },
-  {
-    title: 'booleanValue',
-    dataIndex: 'booleanValue'
-  },
-  {
-    title: 'jsonValue',
-    dataIndex: 'jsonValue'
-  },
-  {
-    title: 'dataUnit',
+    title: '单位',
     dataIndex: 'dataUnit'
   }
 ]

+ 11 - 10
src/router/before.ts

@@ -1,18 +1,19 @@
 import router from './index'
 import { usePort } from '@/hooks/index'
-import { useUserStore } from '@/store'
+import { useRouterTravelStore, useUserStore } from '@/store'
 import { message } from 'ant-design-vue'
-import { useRouter } from 'vue-router'
+import { RouteRecordRaw } from 'vue-router'
+
+// const useRouterTravel = useRouterTravelStore()
 
 router.beforeEach((to, from, next) => {
-  // if (to.path === '/login') {
-  //   next()
-  // } else if (useUserStore().userInfo.token) {
-  //   next()
-  // } else {
-  //   message.error('登录失效,请重新登录')
-  //   router.replace({ path: '/login', query: { redirectUrl: to.fullPath } })
-  // }
   usePort(to.meta.title as string)
+  if (from.path !== '/login') {
+    useRouterTravelStore().push(to as any)
+  }
   next()
 })
+
+router.afterEach((to, from, failure) => {
+  // useRouterTravelStore().end()
+})

+ 5 - 1
src/router/index.ts

@@ -1,4 +1,4 @@
-import { createRouter, createWebHistory } from 'vue-router'
+import vueRouter, { NavigationFailure, RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'
 
 const iot = {
   path: '/iot',
@@ -409,4 +409,8 @@ const router = createRouter({
   })
 })
 
+// router.push = async function (to: vueRouter.RouteLocationRaw): Promise<void | NavigationFailure | undefined> {
+//   router.push(to)
+// }
+
 export default router

+ 4 - 0
src/store/index.ts

@@ -3,3 +3,7 @@
 export { useDesignStore } from './modules/designStore/designStore'
 
 export { useUserStore } from './modules/user/index'
+
+export { useRouterTravelStore } from './modules/commonStore/routerTravelStore'
+
+export { useAppRouter } from './router'

+ 75 - 0
src/store/modules/commonStore/routerTravelStore.ts

@@ -0,0 +1,75 @@
+import { defineStore } from 'pinia'
+import { ref, nextTick, computed } from 'vue'
+import { RouteRecordRaw } from 'vue-router'
+import router, { routes } from '@/router'
+import { useAppRouter } from '@/store/router'
+
+export const useRouterTravelStore = defineStore('routerTravelStore', () => {
+  const appRouter = useAppRouter()
+
+  const history = ref<RouteRecordRaw[]>([])
+
+  const currentRoute = ref<RouteRecordRaw>()
+
+  const currenRouteIndex = ref<number>(0)
+
+  const keyCount = ref<number>(0)
+
+  const init = () => {
+    push(JSON.parse(JSON.stringify(routes[1].children[0])))
+    currentRoute.value = history.value[0]
+  }
+
+  const del = (path: string) => {
+    const index = history.value.findIndex(item => item.path === path)
+
+    if (index === currenRouteIndex.value) {
+      currenRouteIndex.value = index - 1
+      currentRoute.value = history.value[currenRouteIndex.value]
+      history.value.splice(currenRouteIndex.value + 1, 1)
+    } else {
+      history.value.splice(index, 1)
+    }
+
+    router.push(currentRoute.value!.path)
+  }
+
+  const push = (route: RouteRecordRaw) => {
+    const index = history.value.findIndex(item => item.path === route.path)
+    if (index !== -1) {
+      currenRouteIndex.value = index
+    } else {
+      history.value.push(route)
+      currenRouteIndex.value = history.value.length - 1
+    }
+    currentRoute.value = route
+
+    console.log('父节点:', appRouter.findRootRoute(route.path))
+  }
+
+  const setCurrentRoute = (route: RouteRecordRaw) => {
+    currentRoute.value = route
+
+    router.push(currentRoute.value!.path)
+  }
+
+  const closeOtherPage = () => {
+    history.value = [JSON.parse(JSON.stringify(routes[1].children[0])), currentRoute.value]
+    currenRouteIndex.value = history.value.length - 1
+  }
+
+  init()
+
+  return {
+    history,
+    currentRoute,
+    currenRouteIndex,
+    keyCount,
+    setCurrentRoute,
+    push,
+    del,
+    closeOtherPage
+  }
+}, {
+  persist: true
+})

+ 1 - 0
src/store/modules/designStore/systemStore.ts

@@ -20,6 +20,7 @@ export const useSystemStore = defineStore('systemStore', () => {
 // /* 红-绿色盲模式 */
 // body {
 // filter: url(#protanopia);
+
 // }
 // /* 蓝-黄色盲模式 */
 // body {

+ 75 - 4
src/store/router.ts

@@ -1,12 +1,13 @@
 import { defineStore } from 'pinia'
 import { ConstantStore } from '@/enum/store'
-import { reactive } from 'vue'
+import { reactive, watch } from 'vue'
 import RootRouter from '@/router'
+import VueRouter, { RouteRecordRaw } from 'vue-router'
 
 const initAppRouterState: ROUTER.RouterRecords = {
   navbar: {
     route: [],
-    selectPath: ''
+    selectPath: []
   },
   sider: {
     route: [],
@@ -14,9 +15,9 @@ const initAppRouterState: ROUTER.RouterRecords = {
     openKeys: []
   }
 }
+
 export const useAppRouter = defineStore(ConstantStore.ROUTER, () => {
   const appRouter = reactive<ROUTER.RouterRecords>(initAppRouterState)
-  console.log('RootRouter.options.routes:', RootRouter.options.routes)
 
   const initAppRouter = () => {
     appRouter.navbar.route = RootRouter.options.routes.map((route: any) => {
@@ -35,7 +36,77 @@ export const useAppRouter = defineStore(ConstantStore.ROUTER, () => {
     initAppRouter()
   }
 
+  const changeNavbarRoute = (route: ROUTER.RoutesProps) => {
+    if (route.link) {
+      window.open(route.path)
+    } else {
+      appRouter.navbar.selectPath = [route.path]
+      RootRouter.push(route.path)
+    }
+  }
+
+  // 根据当前子路由,逐级向上查找到根路由
+  function findRootRoute (data, path: string) {
+    const routes = RootRouter.options.routes as RouteRecordRaw[]
+
+    const getParentRouteByPath = (routes: RouteRecordRaw, path: string) => {
+      if (routes.children && routes.children.length > 0) {
+        const index = routes.children.findIndex(route => route.path === path)
+        return index >= 0 ? routes[index] : false
+      } else {
+        return false
+      }
+    }
+
+    const fn = (routes: RouteRecordRaw[], path: string) => {
+      routes.forEach(route => {
+        const r = getParentRouteByPath(route, route.path)
+        // if (r) {
+
+        // }
+      })
+    }
+
+    // const fn = (routes: RouteRecordRaw[]) => {
+    //   for (let index = 0; index < routes.length; index++) {
+    //     if (routes[index].path === path) {
+    //       return routes[index]
+    //     } else {
+    //       fn(routes[index].children)
+    //     }
+    //   }
+    //   if (route.path === path) return true
+    //   else if (route.children && route.children.length > 0) {
+    //     fn(route.children)
+    //   } else return false
+    // }
+
+    // for (let index = 0; index < routes.length; index++) {
+    //   if (routes[index].path === path) {
+    //     return routes[index]
+    //   } else {
+    //     fn(routes[index].children)
+    //   }
+    // }
+
+    // for (const node of data) {
+    //   if (node.path === path) {
+    //     // 如果找到匹配的 path,表示当前节点就是根节点
+    //     return node
+    //   } else if (node.children) {
+    //     // 如果当前节点有子节点,递归搜索子节点
+    //     const result = findRootRoute(node.children, path)
+    //     if (result) {
+    //       // 如果在子节点中找到匹配的 path,返回子节点的结果
+    //       return result
+    //     }
+    //   }
+    // }
+  }
+
   return {
-    router: appRouter
+    router: appRouter,
+    changeNavbarRoute,
+    findRootRoute: (path) => findRootRoute(RootRouter.options.routes, path)
   }
 })

+ 1 - 1
src/typeing.d.ts

@@ -8,7 +8,7 @@ declare namespace ROUTER {
   type RouterRecords = {
     navbar: {
       route: RoutesProps[],
-      selectPath: string
+      selectPath: string[]
     },
     sider: {
       route: RoutesProps[],

+ 2 - 0
src/utils/UsePro.ts

@@ -5,6 +5,7 @@ import FormPro from '@/components/FormPro/index.vue'
 import RowPro from '@/components/RowPro/index.vue'
 import ColPro from '@/components/ColPro/index.vue'
 import UploadPro from '@/components/UploadPro/index'
+import PageHeader from '@/layout/pageHeader.vue'
 import { App } from 'vue'
 
 export default function (app: App) {
@@ -15,4 +16,5 @@ export default function (app: App) {
   app.component('l-row', RowPro)
   app.component('l-col', ColPro)
   app.component('upload-pro', UploadPro)
+  app.component('page-header', PageHeader)
 }

+ 21 - 16
yarn.lock

@@ -320,7 +320,7 @@
 
 "@babel/parser@^7.23.0":
   version "7.23.0"
-  resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+  resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
   integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
 
 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5":
@@ -1322,7 +1322,7 @@
 
 "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.15":
   version "1.4.15"
-  resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
   integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
 
 "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
@@ -2074,7 +2074,7 @@
 
 "@vue/compiler-core@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.6.tgz#ffc14517e0a7269983b9a93994df9669e9e03506"
+  resolved "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.6.tgz#ffc14517e0a7269983b9a93994df9669e9e03506"
   integrity sha512-2JNjemwaNwf+MkkatATVZi7oAH1Hx0B04DdPH3ZoZ8vKC1xZVP7nl4HIsk8XYd3r+/52sqqoz9TWzYc3yE9dqA==
   dependencies:
     "@babel/parser" "^7.23.0"
@@ -2084,7 +2084,7 @@
 
 "@vue/compiler-dom@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.6.tgz#683420cc201c3a48cb0841467bf19a433ffbede6"
+  resolved "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.6.tgz#683420cc201c3a48cb0841467bf19a433ffbede6"
   integrity sha512-1MxXcJYMHiTPexjLAJUkNs/Tw2eDf2tY3a0rL+LfuWyiKN2s6jvSwywH3PWD8bKICjfebX3GWx2Os8jkRDq3Ng==
   dependencies:
     "@vue/compiler-core" "3.3.6"
@@ -2092,7 +2092,7 @@
 
 "@vue/compiler-sfc@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.6.tgz#00dce2e7aa569101009c5eedec4a69e2f831d8cc"
+  resolved "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.6.tgz#00dce2e7aa569101009c5eedec4a69e2f831d8cc"
   integrity sha512-/Kms6du2h1VrXFreuZmlvQej8B1zenBqIohP0690IUBkJjsFvJxY0crcvVRJ0UhMgSR9dewB+khdR1DfbpArJA==
   dependencies:
     "@babel/parser" "^7.23.0"
@@ -2108,7 +2108,7 @@
 
 "@vue/compiler-ssr@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.6.tgz#d767602563f2596a03b44b3dea4a32c715f64915"
+  resolved "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.6.tgz#d767602563f2596a03b44b3dea4a32c715f64915"
   integrity sha512-QTIHAfDCHhjXlYGkUg5KH7YwYtdUM1vcFl/FxFDlD6d0nXAmnjizka3HITp8DGudzHndv2PjKVS44vqqy0vP4w==
   dependencies:
     "@vue/compiler-dom" "3.3.6"
@@ -2153,7 +2153,7 @@
 
 "@vue/reactivity-transform@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.6.tgz#29d615455992d253b8f21b47d084445b5d3f916d"
+  resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.6.tgz#29d615455992d253b8f21b47d084445b5d3f916d"
   integrity sha512-RlJl4dHfeO7EuzU1iJOsrlqWyJfHTkJbvYz/IOJWqu8dlCNWtxWX377WI0VsbAgBizjwD+3ZjdnvSyyFW1YVng==
   dependencies:
     "@babel/parser" "^7.23.0"
@@ -2164,14 +2164,14 @@
 
 "@vue/reactivity@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.6.tgz#436bd2997673ae2a7b6f4e1c376163f0445f9a5e"
+  resolved "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.6.tgz#436bd2997673ae2a7b6f4e1c376163f0445f9a5e"
   integrity sha512-gtChAumfQz5lSy5jZXfyXbKrIYPf9XEOrIr6rxwVyeWVjFhJwmwPLtV6Yis+M9onzX++I5AVE9j+iPH60U+B8Q==
   dependencies:
     "@vue/shared" "3.3.6"
 
 "@vue/runtime-core@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.6.tgz#749ff325c95bb3a690c512da0215339fd241872c"
+  resolved "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.6.tgz#749ff325c95bb3a690c512da0215339fd241872c"
   integrity sha512-qp7HTP1iw1UW2ZGJ8L3zpqlngrBKvLsDAcq5lA6JvEXHmpoEmjKju7ahM9W2p/h51h0OT5F2fGlP/gMhHOmbUA==
   dependencies:
     "@vue/reactivity" "3.3.6"
@@ -2179,7 +2179,7 @@
 
 "@vue/runtime-dom@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.6.tgz#30729eac969467c6be6723677d14b873918a467c"
+  resolved "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.6.tgz#30729eac969467c6be6723677d14b873918a467c"
   integrity sha512-AoX3Cp8NqMXjLbIG9YR6n/pPLWE9TiDdk6wTJHFnl2GpHzDFH1HLBC9wlqqQ7RlnvN3bVLpzPGAAH00SAtOxHg==
   dependencies:
     "@vue/runtime-core" "3.3.6"
@@ -2188,7 +2188,7 @@
 
 "@vue/server-renderer@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.6.tgz#b8a4d7254b67a5cc663f2b9e16cde019483620c9"
+  resolved "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.6.tgz#b8a4d7254b67a5cc663f2b9e16cde019483620c9"
   integrity sha512-kgLoN43W4ERdZ6dpyy+gnk2ZHtcOaIr5Uc/WUP5DRwutgvluzu2pudsZGoD2b7AEJHByUVMa9k6Sho5lLRCykw==
   dependencies:
     "@vue/compiler-ssr" "3.3.6"
@@ -2196,7 +2196,7 @@
 
 "@vue/shared@3.3.6":
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.6.tgz#bd97c22972c6519250069297d01cbed077054b7e"
+  resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.6.tgz#bd97c22972c6519250069297d01cbed077054b7e"
   integrity sha512-Xno5pEqg8SVhomD0kTSmfh30ZEmV/+jZtyh39q6QflrjdJCXah5lrnOLi9KB6a5k5aAHXMXjoMnxlzUkCNfWLQ==
 
 "@vue/vue-loader-v15@npm:vue-loader@^15.9.7":
@@ -5866,7 +5866,7 @@ lru-cache@^6.0.0:
 
 magic-string@^0.30.5:
   version "0.30.5"
-  resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
+  resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
   integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==
   dependencies:
     "@jridgewell/sourcemap-codec" "^1.4.15"
@@ -6084,7 +6084,7 @@ mz@^2.4.0:
 
 nanoid@^3.3.4, nanoid@^3.3.6:
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+  resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
   integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
 
 nanopop@^2.1.0:
@@ -6489,6 +6489,11 @@ pify@^4.0.1:
   resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz"
   integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
+pinia-plugin-persistedstate@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.0.tgz#9932ca2ae88aa6c0d6763bebc6447d7bd1f097d0"
+  integrity sha512-tZbNGf2vjAQcIm7alK40sE51Qu/m9oWr+rEgNm/2AWr1huFxj72CjvpQcIQzMknDBJEkQznCLAGtJTIcLKrKdw==
+
 pinia@^2.0.33:
   version "2.0.33"
   resolved "https://registry.npmmirror.com/pinia/-/pinia-2.0.33.tgz"
@@ -6786,7 +6791,7 @@ postcss@^8.2.6, postcss@^8.3.5, postcss@^8.4.21:
 
 postcss@^8.4.31:
   version "8.4.31"
-  resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+  resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
   integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
   dependencies:
     nanoid "^3.3.6"
@@ -8170,7 +8175,7 @@ vue-types@^3.0.0:
 
 vue@^3.3.4:
   version "3.3.6"
-  resolved "https://registry.npmmirror.com/vue/-/vue-3.3.6.tgz#bc1b129a73705db16da90aa1edde539d7401ca9d"
+  resolved "https://registry.npmjs.org/vue/-/vue-3.3.6.tgz#bc1b129a73705db16da90aa1edde539d7401ca9d"
   integrity sha512-jJIDETeWJnoY+gfn4ZtMPMS5KtbP4ax+CT4dcQFhTnWEk8xMupFyQ0JxL28nvT/M4+p4a0ptxaV2WY0LiIxvRg==
   dependencies:
     "@vue/compiler-dom" "3.3.6"