欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3+element-plus+vite實(shí)現(xiàn)動(dòng)態(tài)路由菜單方式

 更新時(shí)間:2025年01月25日 09:16:18   作者:未眸憾  
文章詳細(xì)介紹了如何使用Vite搭建一個(gè)Vue 3 TypeScript項(xiàng)目,并配置了路由、狀態(tài)管理、插件和環(huán)境,項(xiàng)目結(jié)構(gòu)包括路由持久化、白名單、動(dòng)態(tài)路由和權(quán)限控制,此外,還模擬了一個(gè)后端返回的路由數(shù)據(jù)文件

1. 環(huán)境搭建

1.1 新建一個(gè)vite搭建的vue3項(xiàng)目

先執(zhí)行以下命令

npm create vite@latest my-project(你的項(xiàng)目名)

1.2 選擇項(xiàng)目框架 vue

1.3 選擇語言類型 ts

1.4 執(zhí)行命令進(jìn)入到新建的項(xiàng)目文件中

cd  my-project

1.5 下載依賴

npm i

下載項(xiàng)目中需要使用到的環(huán)境

npm install vue-router@4 pinia element-plus @element-plus/icons-vue

1.6 完善項(xiàng)目目錄結(jié)構(gòu)以及環(huán)境配置

1.6.1 先清空App.vue文件中內(nèi)容,增加router-view作為路由出口

<template>
  <router-view />
</template>

<script setup lang="ts">
</script>

<style scoped lang="scss">
#app {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: space-around;
}
</style>

1.6.2 在src目錄下新建文件夾layout,在該文件中新建文件AppLayout.vue (文件名看自己)

1.6.3 在src目錄下分別新建文件夾store和router分別用來pinia狀態(tài)管理和路由管理

1.6.3.1 router文件夾中新建兩個(gè)文件一個(gè)index.ts用來初始化路由和存放靜態(tài)路由一個(gè)dynamicRoutes.ts存放處理動(dòng)態(tài)路由

// router/dynamicRoutes.ts
// 更新 initDynamicRoutes,確保 dynamicRoutes 被更新
import router from './index';
import { useRouteStore } from '@/store/index'; // 導(dǎo)入 store
import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router';

// 定義菜單項(xiàng)類型,確保 `name` 是 `string`
type MenuItem = Omit<RouteRecordRaw, 'component' | 'children' | 'redirect'> & {
  name: string; // 必須有 name 屬性
  path: string; // 必須有 path 屬性
  component?: () => Promise<Component>; // 用于動(dòng)態(tài)加載組件的路徑
  children?: MenuItem[]; // 子路由類型
  redirect?: string; // 調(diào)整 redirect 為更簡單的 string 類型
  meta?: {
    title: string;
  };
};
// Vite 支持使用特殊的 import.meta.glob 函數(shù)從文件系統(tǒng)導(dǎo)入多個(gè)模塊
const modules: Record<string, () => Promise<Component>> = import.meta.glob('../views/**/**.vue');

// 初始化動(dòng)態(tài)路由
export const initDynamicRoutes = (menuData: MenuItem[]) => {
  const routeStore = useRouteStore(); // 獲取 store
  const routerList: MenuItem[] = [];
  const addedRoutes = new Set(); // 用于跟蹤已添加的路由,防止重復(fù)添加

  // 遞歸處理路由
  const processRoutes = (routes: MenuItem[]): MenuItem[] => {
    return routes.map((item) => {
      if (addedRoutes.has(item.name)) return null; // 防止重復(fù)處理
      addedRoutes.add(item.name); // 標(biāo)記路由為已處理

      const componentLoader = modules[`../views${item.component}.vue`];
      const route: MenuItem = {
        path: item.path,
        name: item.name as string,
        component: componentLoader , // 提供默認(rèn)組件以防找不到
        meta: item.meta,
      };

      // 如果有子路由,遞歸處理
      if (item.children && item.children.length > 0) {
        route.children = processRoutes(item.children);
        route.redirect = route.children[0]?.path; // 默認(rèn)重定向到第一個(gè)子路由
      } else {
        route.children = undefined; // 明確設(shè)置為 undefined
      }

      return route;
    }).filter((route) => route !== null) as MenuItem[]; // 過濾掉 null 項(xiàng)
  };

  // 頂級(jí)路由處理
  const parentRouter = processRoutes(menuData);

  // 根路由配置
  routerList.push({
    path: '/',
    name: 'home',
    component: () => import('../layout/AppLayout.vue'),
    children: parentRouter, // 頂級(jí)路由作為子路由
    redirect: parentRouter[0]?.path || '/', // 確保有默認(rèn)重定向路徑
  });
  
  // 將路由存儲(chǔ)到 store 中
  routeStore.dynamicRoutes = routerList;
  // 添加路由到 Vue Router
  routerList.forEach((route) => {
    router.addRoute(route as RouteRecordRaw);
  });
};
// router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import { useRouteStore } from "@/store";
// 靜態(tài)路由
const routes: RouteRecordRaw[] = [
  {
    path: "/login",
    name: "login",
    component: () => import("@/views/login/index.vue"),
  },
  {
    path: "/404",
    component: () => import("@/views/error-page/404.vue"),
  },
  {
    path: "/401",
    component: () => import("@/views/error-page/401.vue"),
  },
  // 匹配所有路徑
  { path: "/:pathMatch(.*)", redirect: "/login" },
];

// 創(chuàng)建路由
const router = createRouter({
  history: createWebHashHistory(), // 路由模式
  routes, // 靜態(tài)路由
});

// 路由守衛(wèi):初始化時(shí)跳轉(zhuǎn)到上次訪問的頁面
window.addEventListener('DOMContentLoaded', () => {
  const routeStore = useRouteStore()

  const beforeReloadRoute = sessionStorage.getItem('beforeReloadRoute')
  if (beforeReloadRoute) {
    const to = JSON.parse(beforeReloadRoute)
    routeStore.beforeRouter = to.path
    // 清除保存的路由信息
    sessionStorage.removeItem('beforeReloadRoute')
    // 導(dǎo)航回刷新前的路由
    router.replace(to)
    const keys = Object.keys(to)
    if (keys.includes('name')) {
      sessionStorage.setItem('roterPath', JSON.stringify(to.name))
    }
  }
})

// 在頁面即將刷新時(shí)保存當(dāng)前路由信息
window.addEventListener('beforeunload', () => {
  const currentRoute = JSON.stringify(router.currentRoute.value)
  sessionStorage.setItem('beforeReloadRoute', currentRoute)
})


export default router;

1.6.3.2 實(shí)現(xiàn)路由持久化和白名單,需要在src目錄下新建一個(gè)permission.ts文件

import { createVNode, render } from 'vue';
import { initDynamicRoutes } from '@/router/dynamicRoutes';
import router from './router/index';
import loadingBar from '@/component/loadingBar.vue';
import Cookies from 'js-cookie'; // 引入 js-cookie
import { useRouteStore } from '@/store/index';
import menuData from '/public/dynamicRouter.json'; // 導(dǎo)入動(dòng)態(tài)菜單數(shù)據(jù)

const whileList = ['/login']; // 白名單
const Vnode = createVNode(loadingBar);
render(Vnode, document.body);

router.beforeEach(async (to, from, next) => {
  const routeStore = useRouteStore(); // 獲取 Pinia 中的路由狀態(tài)
  const token = Cookies.get('token'); // 從 cookie 獲取 token

  // 判斷是否有 token,存在則說明用戶已登錄
  if (token) {
    // 檢查是否已經(jīng)加載過動(dòng)態(tài)路由
    if (routeStore.dynamicRoutes.length === 0) {
      // 檢查是否有持久化的動(dòng)態(tài)路由
      const persistedRoutes = sessionStorage.getItem('dynamicRoutes');  // 使用 sessionStorage
      if (persistedRoutes) {
        // 如果有持久化的動(dòng)態(tài)路由,直接從 sessionStorage 加載
        const routerList = JSON.parse(persistedRoutes);
        initDynamicRoutes(routerList); // 動(dòng)態(tài)初始化路由
        routeStore.setDynamicRoutes(routerList); // 將動(dòng)態(tài)路由存入 Pinia
        next({ ...to, replace: true }); // 確保動(dòng)態(tài)路由加載后再跳轉(zhuǎn)
        Vnode.component?.exposed?.startLoading(); // 啟動(dòng)加載條
      } else {
        // 如果沒有持久化的動(dòng)態(tài)路由,則使用靜態(tài)的 dynamicRouter.json
        const dynamicRoutes = initDynamicRoutes(menuData); // 動(dòng)態(tài)初始化路由
        if (dynamicRoutes !== undefined) {
          routeStore.setDynamicRoutes(dynamicRoutes); // 將動(dòng)態(tài)路由存入 Pinia
          sessionStorage.setItem('dynamicRoutes', JSON.stringify(dynamicRoutes)); // 存儲(chǔ)動(dòng)態(tài)路由到 sessionStorage
          next({ ...to, replace: true }); // 確保動(dòng)態(tài)路由加載后再跳轉(zhuǎn)
          Vnode.component?.exposed?.startLoading(); // 啟動(dòng)加載條
        } else {
          next('/login'); // 如果沒有動(dòng)態(tài)路由信息,跳轉(zhuǎn)到登錄頁面
        }
      }
    } else {
      next(); // 如果已經(jīng)加載過動(dòng)態(tài)路由,直接跳轉(zhuǎn)
    }
  } else {
    // 如果沒有 token,判斷是否在白名單中
    if (whileList.includes(to.path)) {
      next(); // 白名單路由放行
    } else {
      next('/login'); // 否則跳轉(zhuǎn)到登錄頁
    }
  }
});

router.afterEach(() => {
  Vnode.component?.exposed?.endLoading(); // 結(jié)束加載條
});

1.6.3.2 store文件夾下新建文件index.ts初始化pinia倉

// store/index.ts
import { createPinia } from 'pinia';
import { useRouteStore } from './useRouteStore';
import { useUserStore } from './tokenStore';

// 創(chuàng)建 pinia 實(shí)例
const pinia = createPinia();

// 將所有 store 模塊暴露
export { pinia, useRouteStore, useUserStore };

1.6.3.2 store文件夾下新建文件useRouteStore.ts處理存儲(chǔ)動(dòng)態(tài)路由文件

import { defineStore } from 'pinia';
import { ref } from 'vue';
import { initDynamicRoutes } from "@/router/dynamicRoutes"; // 導(dǎo)入初始化動(dòng)態(tài)路由的方法
import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router';

// 定義菜單項(xiàng)類型,確保 `name` 是 `string`
type MenuItem = Omit<RouteRecordRaw, 'component' | 'children' | 'redirect'> & {
  name: string; // 必須有 name 屬性
  path: string; // 必須有 path 屬性
  component?: () => Promise<Component>; // 用于動(dòng)態(tài)加載組件的路徑
  children?: MenuItem[]; // 子路由類型
  redirect?: string; // 調(diào)整 redirect 為更簡單的 string 類型
  meta?: {
    title: string;
  };
};
// 定義路由數(shù)據(jù) Store
export const useRouteStore = defineStore('route', () => {
  // 存儲(chǔ)菜單數(shù)據(jù)
  const menuData = ref<MenuItem[]>([]); // 根據(jù)你的菜單數(shù)據(jù)結(jié)構(gòu)調(diào)整類型

  // 存儲(chǔ)動(dòng)態(tài)路由數(shù)據(jù)
  const dynamicRoutes = ref<MenuItem[]>([]);

  // 存儲(chǔ)是否已初始化路由的狀態(tài)
  const isRoutesInitialized = ref<boolean>(false);

  // 存儲(chǔ)上一次頁面刷新的路由
  const beforeRouter = ref<string>('');

  // 初始化動(dòng)態(tài)路由
  const setDynamicRoutes = (menu: any[]) => {
    // 只在未初始化路由時(shí)執(zhí)行
    if (!isRoutesInitialized.value) {
      // 調(diào)用 initDynamicRoutes 函數(shù)來生成動(dòng)態(tài)路由
      initDynamicRoutes(menu);

      // 將菜單數(shù)據(jù)存儲(chǔ)到狀態(tài)中
      menuData.value = menu;

      // 設(shè)置已初始化狀態(tài)
      isRoutesInitialized.value = true;
    }
  };

  // 獲取動(dòng)態(tài)路由
  const getDynamicRoutes = () => {
    return dynamicRoutes.value;
  };

  // 更新動(dòng)態(tài)路由
  const setUpdatedDynamicRoutes = (routes: MenuItem[]) => {
    dynamicRoutes.value = routes;
  };

  return {
    menuData,
    dynamicRoutes,
    isRoutesInitialized, // 公開這個(gè)狀態(tài),方便其他地方判斷
    setDynamicRoutes,
    getDynamicRoutes,
    setUpdatedDynamicRoutes, // 更新動(dòng)態(tài)路由的函數(shù)
    beforeRouter
  };
});

1.6.4 在src目錄下新建文件夾plugins,在該文件夾中新建文件element-plus.ts

/* Element-plus組件庫 */
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

import { App } from 'vue'

export default {
  install (app: App) {
    app.use(ElementPlus, {
      locale: zhCn
    })
  }
}

1.6.5 需要來配置main.ts,vite.config.ts以及tsconfig.json

1.6.5.1 main.ts配置

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import ElementPlus from "./plugins/element-plus";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import { pinia } from '@/store/index';  // 導(dǎo)入 store
// 創(chuàng)建 Pinia 實(shí)例

// 路由攔截 路由發(fā)生變化修改頁面title
router.beforeEach((to, from, next) => {
  if (to.meta.title) {
    document.title = to.meta.title;
  }
  next();
});
const app = createApp(App);
// // 自動(dòng)注冊(cè)全局組件

app.use(router).use(ElementPlus).use(pinia).mount("#app");
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}

1.6.5.2 vite.config.ts配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
    // 設(shè)置別名 方便路徑引入
      '@': path.resolve(__dirname, 'src'),
    }
  }
})

1.6.5.3 tsconfig.json配置

{ 
  "compilerOptions": 
  { 
    "target": "ESNext", 
    "useDefineForClassFields": true, 
    "module": "ESNext", 
    "moduleResolution": "Node", 
    "strict": true, 
    "jsx": "preserve", 
    "sourceMap": true, 
    "resolveJsonModule": true, 
    "isolatedModules": true, 
    "esModuleInterop": true, 
    "lib": ["ESNext", "DOM"], 
    "skipLibCheck": true, 
    "noEmit": true,
    "paths": {
      "@/*": ["./src/*"]  // 配置路徑別名,不做配置會(huì)報(bào)錯(cuò)
    }
    //就是這個(gè)沒有設(shè)置導(dǎo)致的
     }, 
    // "extends": "./tsconfig.extends.json",
    "include": ["src/**/*.tsx","src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
    "references": [{ "path": "./tsconfig.node.json" }]
}

1.6.5.4 此外vue3在插件引入時(shí)有些時(shí)候會(huì)報(bào)錯(cuò)無法找到模塊“xxx”的聲明文件,此時(shí)需要在src目錄下新建一個(gè)env.d.ts文件

 /// <reference types="vite/client" />
// 類型補(bǔ)充、環(huán)境變量
declare module "*.vue" {
  import type { DefineComponent } from "vue";
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

// eslint-disable-next-line no-unused-vars
interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string;
  readonly VITE_API_BASEURL: string;
  // 更多環(huán)境變量...
}

// 如果遇到路徑缺失找不到的情況
// 無法找到模塊“xxx”的聲明文件,就將該模塊加入到下列代碼中進(jìn)行補(bǔ)充聲明
declare module "xxxx";

1.7 因?yàn)榭紤]是純前端模擬后端給的路由數(shù)據(jù)

所以我自己模擬一個(gè)json文件,需在public文件夾中新建dynamicRouter.json來存放模擬后端返回的路由數(shù)據(jù),后期從接口獲取可進(jìn)行更改

[
  {
    "path": "/principle", 
    "name": "principle", 
    "component": "/principle/index",
    "meta": {
      "title": "Vue3響應(yīng)式原理"
    }
  },
  {
    "path": "/ref",
    "name": "ref", 
    "meta": {
      "title": "ref類"
    },
    "children": [
      {
        "path": "/ref/index", 
        "name": "ref", 
        "component": "/ref/common/ref", 
        "meta": {
          "title": "ref"
        }
      },
      {
        "path": "/ref/toRaw", 
        "name": "toRaw", 
        "component": "/ref/common/toRaw", 
        "meta": {
          "title": "toRaw"
        }
      },
      {
        "path": "/ref/toRef", 
        "name": "toRef", 
        "component": "/ref/common/toRef", 
        "meta": {
          "title": "toRef"
        }
      },
      {
        "path": "/ref/toRefs", 
        "name": "toRefs", 
        "component": "/ref/common/toRefs", 
        "meta": {
          "title": "toRefs"
        }
      },
      {
        "path": "/ref/isRef", 
        "name": "isRef", 
        "component": "/ref/no-common/isRef", 
        "meta": {
          "title": "isRef"
        }
      },
      {
        "path": "/ref/Ref", 
        "name": "Ref", 
        "component": "/ref/no-common/Ref", 
        "meta": {
          "title": "Ref"
        }
      },
      {
        "path": "/ref/shallowRef", 
        "name": "shallowRef", 
        "component": "/ref/no-common/shallowRef", 
        "meta": {
          "title": "shallowRef"
        }
      },
      {
        "path": "/ref/triggerRef", 
        "name": "triggerRef", 
        "component": "/ref/no-common/triggerRef", 
        "meta": {
          "title": "triggerRef"
        }
      }
    ]
  }
]

如下是文件對(duì)應(yīng)的位置

到目前為止整體的環(huán)境已經(jīng)搭建完善,大概結(jié)構(gòu)如下

2. 在views文件夾下新建文件夾login

在其中新建文件index.vue

<template>
  <div class="login">
    //登錄框
    <div class="loginPart">
      <h2>用戶登錄</h2>
      <el-form
        ref="ruleFormRef"
        :model="user"
        status-icon
        :rules="rules"
        label-width="100px"
        class="demo-ruleForm"
        style="transform: translate(-30px)"
      >
        <el-form-item
          label="賬號(hào):"
          prop="account"
        >
          <el-input
            v-model="user.account"
            placeholder="請(qǐng)輸入賬號(hào)"
            maxlength="20"
            clearable
          />
        </el-form-item>
        <el-form-item
          label="密碼:"
          prop="password"
        >
          <el-input
            v-model="user.password"
            type="password"
            placeholder="請(qǐng)輸入密碼"
            maxlength="20"
            show-password
            clearable
          />
        </el-form-item>
   
        <el-button
          class="btn"
          type="primary"
          @click="onSubmit(ruleFormRef)"
        >
          登錄
        </el-button>
  
      </el-form>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from "vue";
//導(dǎo)入模擬的動(dòng)態(tài)路由數(shù)據(jù)
import menuData from '/public/dynamicRouter.json'; // 導(dǎo)入動(dòng)態(tài)菜單數(shù)據(jù)
import { ElMessage, type FormInstance } from "element-plus";
import { useRouter } from "vue-router";
import { initDynamicRoutes } from "@/router/dynamicRoutes"; // 導(dǎo)入初始化動(dòng)態(tài)路由的方法
import { useRouteStore, useUserStore} from "@/store";
const router = useRouter();
const routeStore = useRouteStore();
const UserStore = useUserStore();

type loginReq = {
  account: string;
  password: string;
};

onMounted(() => {});
//from表單校驗(yàn)
const ruleFormRef = ref<FormInstance>();
// 這里存放數(shù)據(jù)
const user = reactive<loginReq>({
  account: "admin",
  password: "123456",
});
const users = reactive<loginReq>({
  account: "admin",
  password: "123456",
});
//校驗(yàn)
const validatePassword = (rule: any, value: any, callback: any) => {
  if (value === "") {
    callback(new Error("請(qǐng)輸入密碼"));
  } else {
    callback();
  }
};
const validateAccount = (rule: any, value: any, callback: any) => {
  if (value === "") {
    callback(new Error("請(qǐng)輸入賬號(hào)"));
  } else {
    callback();
  }
};

//校驗(yàn)
const rules = reactive({
  password: [{ validator: validatePassword, trigger: "blur" }],
  account: [{ validator: validateAccount, trigger: "blur" }],
});
const changeRegist = () => {
  router.replace("/regist");
};

const onSubmit = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.validate((valid) => {
    if (valid) {
          // 如果需要保存 token 或賬戶信息,存儲(chǔ)在全局狀態(tài)
          // 假設(shè)這里會(huì)調(diào)用登錄 API 返回一個(gè) token
          const token = "mock_token"; // 模擬登錄后返回的 token
          // 使用 Pinia 保存 token,并設(shè)置到 cookie 中
          // 存儲(chǔ)在 cookie 中的 token 可以配合 httpOnly 和 Secure 標(biāo)志來增強(qiáng)安全性,
          // 這樣可以防止 XSS 攻擊并確保 token 只有在通過 HTTPS 協(xié)議時(shí)才會(huì)被發(fā)送
          UserStore.setToken(token);
          ElMessage.success("登錄成功");
          // 獲取菜單數(shù)據(jù)
          if(routeStore.isRoutesInitialized){
            // 使用 nextTick 確保路由添加完成后再進(jìn)行跳轉(zhuǎn)
            nextTick(() => {
              // 跳轉(zhuǎn)到首頁或其他路由
              router.push('/') // 假設(shè) 'home' 是你動(dòng)態(tài)路由中的一個(gè)頁面名稱
                .then(() => {
                  console.log('跳轉(zhuǎn)成功');
                })
                .catch((error) => {
                  console.error('跳轉(zhuǎn)失敗', error);
                });
            });
          }else{
            initDynamicRoutes(menuData)
              // 標(biāo)記路由已初始化
            routeStore.isRoutesInitialized = true
            // 使用 nextTick 確保路由添加完成后再進(jìn)行跳轉(zhuǎn)
            nextTick(() => {
              // 跳轉(zhuǎn)到首頁或其他路由
              router.push('/') // 假設(shè) 'home' 是你動(dòng)態(tài)路由中的一個(gè)頁面名稱
                .then(() => {
                  console.log('跳轉(zhuǎn)成功');
                })
                .catch((error) => {
                  console.error('跳轉(zhuǎn)失敗', error);
                });
            });
          }
    }
  })
};

</script>
<style scoped lang="scss">
.login {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.login__particles {
  height: 100%;
  width: 100%;
  background-size: cover;
  background-repeat: no-repeat;
  background-image: url("@/assets/0001.jpg");
  opacity: 0.9;
  position: fixed;
  pointer-events: none;
}

h2 {
  margin: 0 0 30px;
  padding: 0;
  color: #fff;
  text-align: center;
  /*文字居中*/
}
.btn {
  transform: translate(170px);
  width: 80px;
  height: 40px;
  font-size: 15px;
}
</style>

3. layout中制作動(dòng)態(tài)路由菜單

<!-- 自定義編輯的樣式不采用element-plus -->
<template>
  <div class="app-container">
    <header>
      <div class="menu">
        <el-col :span="24">
          <el-menu
            :router="true"
            active-text-color="#ffd04b"
            background-color="#545c64"
            class="el-menu-vertical-demo"
            text-color="#fff"
            @open="handleOpen"
            @close="handleClose"
            :unique-opened="true"
            :default-active="defaultPath"
          >
            <div
              v-for="(item, index) in menuList[0].children"
              :key="index"
            >
              <el-menu-item
                v-if="!item.children"
                :index="item.path"
                :route="item.path"
              >
                <!-- <el-icon><setting /></el-icon> -->
                <span>{{ item.meta?.title }}</span>
              </el-menu-item>

              <el-sub-menu
                v-if="item.children"
                :index="item.path"
              >
                <template #title>
                  <!-- <el-icon><setting /></el-icon> -->
                  <span>{{ item.meta?.title }}</span>
                </template>
                <el-menu-item-group>
                  <el-menu-item
                    v-for="(child, childIndex) in item.children"
                    :key="childIndex"
                    :index="child.path"
                  >
                    {{ child.meta?.title }}
                  </el-menu-item>
                </el-menu-item-group>
              </el-sub-menu>
            </div>
          </el-menu>
        </el-col>
      </div>
    </header>
    <main>
      <router-view />
    </main>
  </div>
</template>

<script lang="ts" setup>
import { computed, onMounted } from "vue";
import { useRouteStore } from '@/store'; // 調(diào)整路徑為實(shí)際 Store 文件位置
import { useRouter } from "vue-router";
const router = useRouter()
const routeStore = useRouteStore();
const menuList = computed(() => routeStore.dynamicRoutes);
const defaultPath = ref<string>('')
const handleOpen = (key: string, keyPath: string[]) => {
  console.log('key', key);
  console.log('keyPathpen',  keyPath);
};

const handleClose = (key: string, keyPath: string[]) => {
  console.log('close', key, keyPath);
};

onMounted(() => {
  defaultPath.value = routeStore.beforeRouter
});
</script>

<style lang="scss" scoped>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box; /* 確保所有元素都遵循邊框盒模型 */
  touch-action: none;
}

.app-container {
  display: flex; /* 使用 flexbox 創(chuàng)建左右布局 */
  justify-content: space-between;
  height: 100vh; /* 設(shè)置容器高度為視口高度 */
  background-color: #fff;
}

header {
  width: 150px; /* 固定寬度,左側(cè)菜單欄寬度,可以根據(jù)需要調(diào)整 */
  // background-color: #f8f9fa; /* 設(shè)置背景顏色 */
  box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.1); /* 給左側(cè)菜單欄添加陰影 */
  overflow-y: auto; /* 左側(cè)菜單支持垂直滾動(dòng) */
  height: 100vh; /* 使 header 高度占滿整個(gè)屏幕 */
  .el-menu {
  height: 100vh; /* 高度占滿全屏 */
  width: 100%; /* 設(shè)置菜單寬度為220px,避免過窄 */
  font-size: 0.175rem;
  font-weight: bold;
  color: #fff;
  overflow-y: scroll;

  /* 確保菜單項(xiàng)寬度一致 */
  .el-menu-item,
    .el-sub-menu {
      width: 100%; /* 確保菜單項(xiàng)和子菜單項(xiàng)的寬度自適應(yīng) */
      // padding-left: 0.25rem; /* 為每個(gè)菜單項(xiàng)增加左側(cè)的內(nèi)邊距 */
    }

    /* 一級(jí)菜單項(xiàng) */
    .el-menu-item {
      padding: 10px 20px; /* 設(shè)置內(nèi)邊距,避免菜單項(xiàng)過于擁擠 */
      text-align: left; /* 左對(duì)齊文本 */
      font-size: 0.175rem; /* 調(diào)整字體大小 */
    }

    /* 二級(jí)菜單項(xiàng)(子菜單) */
    .el-sub-menu {
      // padding-left: 10px; /* 為二級(jí)菜單增加縮進(jìn) */
      background-color: #434d56; /* 給二級(jí)菜單背景設(shè)置一個(gè)較深的顏色 */

      /* 子菜單項(xiàng)的縮進(jìn) */
      .el-menu-item {
        padding-left: 1rem; /* 設(shè)置二級(jí)菜單項(xiàng)的縮進(jìn),區(qū)別于一級(jí)菜單 */
      }
    }

    /* 子菜單的展開箭頭樣式 */
    :deep(.el-sub-menu__icon-arrow) {
      color: #ffd04b; /* 設(shè)置箭頭顏色為黃色 */
    }

    /* 設(shè)置展開狀態(tài)時(shí),子菜單的背景色變化 */
    .el-sub-menu.is-opened {
      background-color: #3a424a; /* 打開時(shí)的背景色 */
    }

    /* 設(shè)置菜單項(xiàng)和子菜單項(xiàng)的 hover 狀態(tài) */
    .el-menu-item:hover,
    .el-sub-menu:hover {
      background-color: #333c44; /* 鼠標(biāo)懸浮時(shí)的背景色 */
    }

    /* 設(shè)置當(dāng)前激活的菜單項(xiàng)的背景顏色 */
    .el-menu-item.is-active {
      background-color: #ff6600; /* 激活狀態(tài)的背景色 */
    }
  }
  .el-menu::-webkit-scrollbar{
    display: none;
  }
  /* 自定義子菜單圖標(biāo)大小 */
  .el-menu-item .el-icon,
  .el-sub-menu .el-icon {
    font-size: 1.2rem; /* 調(diào)整圖標(biāo)的大小 */
    margin-right: 0.5rem; /* 給圖標(biāo)增加右側(cè)的間距 */
  }
  :deep(.el-sub-menu__title){
    width: 100%!important;
  }
  .el-menu-item span,
  .el-sub-menu span {
    font-size: 10px; /* 設(shè)置文本的字體大小 */
    font-weight: bold; /* 設(shè)置文本加粗 */
  }
  :deep(.el-sub-menu__icon-arrow){
    left: 50px!important;
  }
}

main {
  display: flex;
  flex: 1; /* main 占據(jù)剩余空間 */
  // margin-left: 250px; /* 給 main 留出與 header 相同的空間 */
  padding: 20px;
  overflow-y: auto; /* 支持內(nèi)容區(qū)域滾動(dòng) */
  background-image: linear-gradient(135deg, #102363, #346177, #3fa489, #34ec98);
  flex-direction: column;
}

header::-webkit-scrollbar, main::-webkit-scrollbar {
  display: none; /* 隱藏滾動(dòng)條 */
}

/* 小屏幕適配 */
@media (max-width: 768px) {
  .app-container {
    flex-direction: column; /* 在小屏幕下轉(zhuǎn)換為上下布局 */
  }

  header {
    width: 100%; /* 屏幕小于768px時(shí),左側(cè)菜單占滿全寬 */
    position: relative; /* 取消固定定位,方便移動(dòng) */
    height: auto; /* 自動(dòng)高度 */
  }

  main {
    margin-left: 0; /* 小屏幕時(shí)不需要左側(cè)留白 */
  }
}


</style>

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 手把手教你vue實(shí)現(xiàn)動(dòng)態(tài)路由

    手把手教你vue實(shí)現(xiàn)動(dòng)態(tài)路由

    動(dòng)態(tài)路由可以根據(jù)不同用戶登錄獲取不一樣的路由層級(jí),可隨時(shí)調(diào)配路由,下面這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)動(dòng)態(tài)路由的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • Vue中 Vue.prototype使用詳解

    Vue中 Vue.prototype使用詳解

    本文將結(jié)合實(shí)例代碼,介紹Vue中 Vue.prototype使用,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • vue項(xiàng)目運(yùn)行或打包時(shí),頻繁內(nèi)存溢出情況問題

    vue項(xiàng)目運(yùn)行或打包時(shí),頻繁內(nèi)存溢出情況問題

    這篇文章主要介紹了vue項(xiàng)目運(yùn)行或打包時(shí),頻繁內(nèi)存溢出情況的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue項(xiàng)目本地沒有問題但部署到服務(wù)器上提示錯(cuò)誤(問題解決方案)

    Vue項(xiàng)目本地沒有問題但部署到服務(wù)器上提示錯(cuò)誤(問題解決方案)

    一個(gè) VUE 的項(xiàng)目在本地部署沒有問題,但是部署到服務(wù)器上的時(shí)候提示訪問資源的錯(cuò)誤,遇到這樣的問題如何解決呢?下面小編給大家?guī)砹薞ue項(xiàng)目本地沒有問題但部署到服務(wù)器上提示錯(cuò)誤的解決方法,感興趣的朋友一起看看吧
    2023-05-05
  • Vue中使用 Echarts5.0 遇到的一些問題(vue-cli 下開發(fā))

    Vue中使用 Echarts5.0 遇到的一些問題(vue-cli 下開發(fā))

    這篇文章主要介紹了Vue中使用 Echarts5.0 遇到的一些問題(vue-cli 下開發(fā)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 利用Vue+ElementUi實(shí)現(xiàn)評(píng)論功能

    利用Vue+ElementUi實(shí)現(xiàn)評(píng)論功能

    這篇文章主要介紹了如何利用Vue+ElementUi實(shí)現(xiàn)評(píng)論功能,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-04-04
  • vue中實(shí)現(xiàn)子組件相互切換且數(shù)據(jù)不丟失的策略詳解

    vue中實(shí)現(xiàn)子組件相互切換且數(shù)據(jù)不丟失的策略詳解

    項(xiàng)目為數(shù)據(jù)報(bào)表,但是一個(gè)父頁面中有很多的子頁面,而且子頁面中不是相互關(guān)聯(lián),但是數(shù)據(jù)又有聯(lián)系,所以本文給大家介紹了vue中如何實(shí)現(xiàn)子組件相互切換,而且數(shù)據(jù)不會(huì)丟失,并有詳細(xì)的代碼供大家參考,需要的朋友可以參考下
    2024-03-03
  • vue組件props屬性監(jiān)聽不到值變化問題

    vue組件props屬性監(jiān)聽不到值變化問題

    這篇文章主要介紹了vue組件props屬性監(jiān)聽不到值變化問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 教你如何在 Nuxt 3 中使用 wavesurfer.js

    教你如何在 Nuxt 3 中使用 wavesurfer.js

    這篇文章主要介紹了如何在 Nuxt 3 中使用 wavesurfer.js,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-01-01
  • vue獲取DOM元素并設(shè)置屬性的兩種實(shí)現(xiàn)方法

    vue獲取DOM元素并設(shè)置屬性的兩種實(shí)現(xiàn)方法

    下面小編就為大家?guī)硪黄獀ue獲取DOM元素并設(shè)置屬性的兩種實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09

最新評(píng)論