vue3+element-plus+vite實現(xiàn)動態(tài)路由菜單方式
1. 環(huán)境搭建
1.1 新建一個vite搭建的vue3項目
先執(zhí)行以下命令
npm create vite@latest my-project(你的項目名)
1.2 選擇項目框架 vue

1.3 選擇語言類型 ts

1.4 執(zhí)行命令進入到新建的項目文件中
cd my-project

1.5 下載依賴
npm i
下載項目中需要使用到的環(huán)境
npm install vue-router@4 pinia element-plus @element-plus/icons-vue
1.6 完善項目目錄結構以及環(huán)境配置
1.6.1 先清空App.vue文件中內容,增加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文件夾中新建兩個文件一個index.ts用來初始化路由和存放靜態(tài)路由一個dynamicRoutes.ts存放處理動態(tài)路由
// router/dynamicRoutes.ts
// 更新 initDynamicRoutes,確保 dynamicRoutes 被更新
import router from './index';
import { useRouteStore } from '@/store/index'; // 導入 store
import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router';
// 定義菜單項類型,確保 `name` 是 `string`
type MenuItem = Omit<RouteRecordRaw, 'component' | 'children' | 'redirect'> & {
name: string; // 必須有 name 屬性
path: string; // 必須有 path 屬性
component?: () => Promise<Component>; // 用于動態(tài)加載組件的路徑
children?: MenuItem[]; // 子路由類型
redirect?: string; // 調整 redirect 為更簡單的 string 類型
meta?: {
title: string;
};
};
// Vite 支持使用特殊的 import.meta.glob 函數(shù)從文件系統(tǒng)導入多個模塊
const modules: Record<string, () => Promise<Component>> = import.meta.glob('../views/**/**.vue');
// 初始化動態(tài)路由
export const initDynamicRoutes = (menuData: MenuItem[]) => {
const routeStore = useRouteStore(); // 獲取 store
const routerList: MenuItem[] = [];
const addedRoutes = new Set(); // 用于跟蹤已添加的路由,防止重復添加
// 遞歸處理路由
const processRoutes = (routes: MenuItem[]): MenuItem[] => {
return routes.map((item) => {
if (addedRoutes.has(item.name)) return null; // 防止重復處理
addedRoutes.add(item.name); // 標記路由為已處理
const componentLoader = modules[`../views${item.component}.vue`];
const route: MenuItem = {
path: item.path,
name: item.name as string,
component: componentLoader , // 提供默認組件以防找不到
meta: item.meta,
};
// 如果有子路由,遞歸處理
if (item.children && item.children.length > 0) {
route.children = processRoutes(item.children);
route.redirect = route.children[0]?.path; // 默認重定向到第一個子路由
} else {
route.children = undefined; // 明確設置為 undefined
}
return route;
}).filter((route) => route !== null) as MenuItem[]; // 過濾掉 null 項
};
// 頂級路由處理
const parentRouter = processRoutes(menuData);
// 根路由配置
routerList.push({
path: '/',
name: 'home',
component: () => import('../layout/AppLayout.vue'),
children: parentRouter, // 頂級路由作為子路由
redirect: parentRouter[0]?.path || '/', // 確保有默認重定向路徑
});
// 將路由存儲到 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):初始化時跳轉到上次訪問的頁面
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')
// 導航回刷新前的路由
router.replace(to)
const keys = Object.keys(to)
if (keys.includes('name')) {
sessionStorage.setItem('roterPath', JSON.stringify(to.name))
}
}
})
// 在頁面即將刷新時保存當前路由信息
window.addEventListener('beforeunload', () => {
const currentRoute = JSON.stringify(router.currentRoute.value)
sessionStorage.setItem('beforeReloadRoute', currentRoute)
})
export default router;1.6.3.2 實現(xiàn)路由持久化和白名單,需要在src目錄下新建一個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'; // 導入動態(tài)菜單數(shù)據
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) {
// 檢查是否已經加載過動態(tài)路由
if (routeStore.dynamicRoutes.length === 0) {
// 檢查是否有持久化的動態(tài)路由
const persistedRoutes = sessionStorage.getItem('dynamicRoutes'); // 使用 sessionStorage
if (persistedRoutes) {
// 如果有持久化的動態(tài)路由,直接從 sessionStorage 加載
const routerList = JSON.parse(persistedRoutes);
initDynamicRoutes(routerList); // 動態(tài)初始化路由
routeStore.setDynamicRoutes(routerList); // 將動態(tài)路由存入 Pinia
next({ ...to, replace: true }); // 確保動態(tài)路由加載后再跳轉
Vnode.component?.exposed?.startLoading(); // 啟動加載條
} else {
// 如果沒有持久化的動態(tài)路由,則使用靜態(tài)的 dynamicRouter.json
const dynamicRoutes = initDynamicRoutes(menuData); // 動態(tài)初始化路由
if (dynamicRoutes !== undefined) {
routeStore.setDynamicRoutes(dynamicRoutes); // 將動態(tài)路由存入 Pinia
sessionStorage.setItem('dynamicRoutes', JSON.stringify(dynamicRoutes)); // 存儲動態(tài)路由到 sessionStorage
next({ ...to, replace: true }); // 確保動態(tài)路由加載后再跳轉
Vnode.component?.exposed?.startLoading(); // 啟動加載條
} else {
next('/login'); // 如果沒有動態(tài)路由信息,跳轉到登錄頁面
}
}
} else {
next(); // 如果已經加載過動態(tài)路由,直接跳轉
}
} else {
// 如果沒有 token,判斷是否在白名單中
if (whileList.includes(to.path)) {
next(); // 白名單路由放行
} else {
next('/login'); // 否則跳轉到登錄頁
}
}
});
router.afterEach(() => {
Vnode.component?.exposed?.endLoading(); // 結束加載條
});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 實例
const pinia = createPinia();
// 將所有 store 模塊暴露
export { pinia, useRouteStore, useUserStore };1.6.3.2 store文件夾下新建文件useRouteStore.ts處理存儲動態(tài)路由文件
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { initDynamicRoutes } from "@/router/dynamicRoutes"; // 導入初始化動態(tài)路由的方法
import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router';
// 定義菜單項類型,確保 `name` 是 `string`
type MenuItem = Omit<RouteRecordRaw, 'component' | 'children' | 'redirect'> & {
name: string; // 必須有 name 屬性
path: string; // 必須有 path 屬性
component?: () => Promise<Component>; // 用于動態(tài)加載組件的路徑
children?: MenuItem[]; // 子路由類型
redirect?: string; // 調整 redirect 為更簡單的 string 類型
meta?: {
title: string;
};
};
// 定義路由數(shù)據 Store
export const useRouteStore = defineStore('route', () => {
// 存儲菜單數(shù)據
const menuData = ref<MenuItem[]>([]); // 根據你的菜單數(shù)據結構調整類型
// 存儲動態(tài)路由數(shù)據
const dynamicRoutes = ref<MenuItem[]>([]);
// 存儲是否已初始化路由的狀態(tài)
const isRoutesInitialized = ref<boolean>(false);
// 存儲上一次頁面刷新的路由
const beforeRouter = ref<string>('');
// 初始化動態(tài)路由
const setDynamicRoutes = (menu: any[]) => {
// 只在未初始化路由時執(zhí)行
if (!isRoutesInitialized.value) {
// 調用 initDynamicRoutes 函數(shù)來生成動態(tài)路由
initDynamicRoutes(menu);
// 將菜單數(shù)據存儲到狀態(tài)中
menuData.value = menu;
// 設置已初始化狀態(tài)
isRoutesInitialized.value = true;
}
};
// 獲取動態(tài)路由
const getDynamicRoutes = () => {
return dynamicRoutes.value;
};
// 更新動態(tài)路由
const setUpdatedDynamicRoutes = (routes: MenuItem[]) => {
dynamicRoutes.value = routes;
};
return {
menuData,
dynamicRoutes,
isRoutesInitialized, // 公開這個狀態(tài),方便其他地方判斷
setDynamicRoutes,
getDynamicRoutes,
setUpdatedDynamicRoutes, // 更新動態(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'; // 導入 store
// 創(chuàng)建 Pinia 實例
// 路由攔截 路由發(fā)生變化修改頁面title
router.beforeEach((to, from, next) => {
if (to.meta.title) {
document.title = to.meta.title;
}
next();
});
const app = createApp(App);
// // 自動注冊全局組件
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: {
// 設置別名 方便路徑引入
'@': 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/*"] // 配置路徑別名,不做配置會報錯
}
//就是這個沒有設置導致的
},
// "extends": "./tsconfig.extends.json",
"include": ["src/**/*.tsx","src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}1.6.5.4 此外vue3在插件引入時有些時候會報錯無法找到模塊“xxx”的聲明文件,此時需要在src目錄下新建一個env.d.ts文件
/// <reference types="vite/client" />
// 類型補充、環(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”的聲明文件,就將該模塊加入到下列代碼中進行補充聲明
declare module "xxxx";1.7 因為考慮是純前端模擬后端給的路由數(shù)據
所以我自己模擬一個json文件,需在public文件夾中新建dynamicRouter.json來存放模擬后端返回的路由數(shù)據,后期從接口獲取可進行更改
[
{
"path": "/principle",
"name": "principle",
"component": "/principle/index",
"meta": {
"title": "Vue3響應式原理"
}
},
{
"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"
}
}
]
}
]如下是文件對應的位置

到目前為止整體的環(huán)境已經搭建完善,大概結構如下

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="賬號:"
prop="account"
>
<el-input
v-model="user.account"
placeholder="請輸入賬號"
maxlength="20"
clearable
/>
</el-form-item>
<el-form-item
label="密碼:"
prop="password"
>
<el-input
v-model="user.password"
type="password"
placeholder="請輸入密碼"
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";
//導入模擬的動態(tài)路由數(shù)據
import menuData from '/public/dynamicRouter.json'; // 導入動態(tài)菜單數(shù)據
import { ElMessage, type FormInstance } from "element-plus";
import { useRouter } from "vue-router";
import { initDynamicRoutes } from "@/router/dynamicRoutes"; // 導入初始化動態(tài)路由的方法
import { useRouteStore, useUserStore} from "@/store";
const router = useRouter();
const routeStore = useRouteStore();
const UserStore = useUserStore();
type loginReq = {
account: string;
password: string;
};
onMounted(() => {});
//from表單校驗
const ruleFormRef = ref<FormInstance>();
// 這里存放數(shù)據
const user = reactive<loginReq>({
account: "admin",
password: "123456",
});
const users = reactive<loginReq>({
account: "admin",
password: "123456",
});
//校驗
const validatePassword = (rule: any, value: any, callback: any) => {
if (value === "") {
callback(new Error("請輸入密碼"));
} else {
callback();
}
};
const validateAccount = (rule: any, value: any, callback: any) => {
if (value === "") {
callback(new Error("請輸入賬號"));
} else {
callback();
}
};
//校驗
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 或賬戶信息,存儲在全局狀態(tài)
// 假設這里會調用登錄 API 返回一個 token
const token = "mock_token"; // 模擬登錄后返回的 token
// 使用 Pinia 保存 token,并設置到 cookie 中
// 存儲在 cookie 中的 token 可以配合 httpOnly 和 Secure 標志來增強安全性,
// 這樣可以防止 XSS 攻擊并確保 token 只有在通過 HTTPS 協(xié)議時才會被發(fā)送
UserStore.setToken(token);
ElMessage.success("登錄成功");
// 獲取菜單數(shù)據
if(routeStore.isRoutesInitialized){
// 使用 nextTick 確保路由添加完成后再進行跳轉
nextTick(() => {
// 跳轉到首頁或其他路由
router.push('/') // 假設 'home' 是你動態(tài)路由中的一個頁面名稱
.then(() => {
console.log('跳轉成功');
})
.catch((error) => {
console.error('跳轉失敗', error);
});
});
}else{
initDynamicRoutes(menuData)
// 標記路由已初始化
routeStore.isRoutesInitialized = true
// 使用 nextTick 確保路由添加完成后再進行跳轉
nextTick(() => {
// 跳轉到首頁或其他路由
router.push('/') // 假設 'home' 是你動態(tài)路由中的一個頁面名稱
.then(() => {
console.log('跳轉成功');
})
.catch((error) => {
console.error('跳轉失敗', 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中制作動態(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'; // 調整路徑為實際 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; /* 設置容器高度為視口高度 */
background-color: #fff;
}
header {
width: 150px; /* 固定寬度,左側菜單欄寬度,可以根據需要調整 */
// background-color: #f8f9fa; /* 設置背景顏色 */
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.1); /* 給左側菜單欄添加陰影 */
overflow-y: auto; /* 左側菜單支持垂直滾動 */
height: 100vh; /* 使 header 高度占滿整個屏幕 */
.el-menu {
height: 100vh; /* 高度占滿全屏 */
width: 100%; /* 設置菜單寬度為220px,避免過窄 */
font-size: 0.175rem;
font-weight: bold;
color: #fff;
overflow-y: scroll;
/* 確保菜單項寬度一致 */
.el-menu-item,
.el-sub-menu {
width: 100%; /* 確保菜單項和子菜單項的寬度自適應 */
// padding-left: 0.25rem; /* 為每個菜單項增加左側的內邊距 */
}
/* 一級菜單項 */
.el-menu-item {
padding: 10px 20px; /* 設置內邊距,避免菜單項過于擁擠 */
text-align: left; /* 左對齊文本 */
font-size: 0.175rem; /* 調整字體大小 */
}
/* 二級菜單項(子菜單) */
.el-sub-menu {
// padding-left: 10px; /* 為二級菜單增加縮進 */
background-color: #434d56; /* 給二級菜單背景設置一個較深的顏色 */
/* 子菜單項的縮進 */
.el-menu-item {
padding-left: 1rem; /* 設置二級菜單項的縮進,區(qū)別于一級菜單 */
}
}
/* 子菜單的展開箭頭樣式 */
:deep(.el-sub-menu__icon-arrow) {
color: #ffd04b; /* 設置箭頭顏色為黃色 */
}
/* 設置展開狀態(tài)時,子菜單的背景色變化 */
.el-sub-menu.is-opened {
background-color: #3a424a; /* 打開時的背景色 */
}
/* 設置菜單項和子菜單項的 hover 狀態(tài) */
.el-menu-item:hover,
.el-sub-menu:hover {
background-color: #333c44; /* 鼠標懸浮時的背景色 */
}
/* 設置當前激活的菜單項的背景顏色 */
.el-menu-item.is-active {
background-color: #ff6600; /* 激活狀態(tài)的背景色 */
}
}
.el-menu::-webkit-scrollbar{
display: none;
}
/* 自定義子菜單圖標大小 */
.el-menu-item .el-icon,
.el-sub-menu .el-icon {
font-size: 1.2rem; /* 調整圖標的大小 */
margin-right: 0.5rem; /* 給圖標增加右側的間距 */
}
:deep(.el-sub-menu__title){
width: 100%!important;
}
.el-menu-item span,
.el-sub-menu span {
font-size: 10px; /* 設置文本的字體大小 */
font-weight: bold; /* 設置文本加粗 */
}
:deep(.el-sub-menu__icon-arrow){
left: 50px!important;
}
}
main {
display: flex;
flex: 1; /* main 占據剩余空間 */
// margin-left: 250px; /* 給 main 留出與 header 相同的空間 */
padding: 20px;
overflow-y: auto; /* 支持內容區(qū)域滾動 */
background-image: linear-gradient(135deg, #102363, #346177, #3fa489, #34ec98);
flex-direction: column;
}
header::-webkit-scrollbar, main::-webkit-scrollbar {
display: none; /* 隱藏滾動條 */
}
/* 小屏幕適配 */
@media (max-width: 768px) {
.app-container {
flex-direction: column; /* 在小屏幕下轉換為上下布局 */
}
header {
width: 100%; /* 屏幕小于768px時,左側菜單占滿全寬 */
position: relative; /* 取消固定定位,方便移動 */
height: auto; /* 自動高度 */
}
main {
margin-left: 0; /* 小屏幕時不需要左側留白 */
}
}
</style>總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Vue項目本地沒有問題但部署到服務器上提示錯誤(問題解決方案)
一個 VUE 的項目在本地部署沒有問題,但是部署到服務器上的時候提示訪問資源的錯誤,遇到這樣的問題如何解決呢?下面小編給大家?guī)砹薞ue項目本地沒有問題但部署到服務器上提示錯誤的解決方法,感興趣的朋友一起看看吧2023-05-05
Vue中使用 Echarts5.0 遇到的一些問題(vue-cli 下開發(fā))
這篇文章主要介紹了Vue中使用 Echarts5.0 遇到的一些問題(vue-cli 下開發(fā)),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10
vue中實現(xiàn)子組件相互切換且數(shù)據不丟失的策略詳解
項目為數(shù)據報表,但是一個父頁面中有很多的子頁面,而且子頁面中不是相互關聯(lián),但是數(shù)據又有聯(lián)系,所以本文給大家介紹了vue中如何實現(xiàn)子組件相互切換,而且數(shù)據不會丟失,并有詳細的代碼供大家參考,需要的朋友可以參考下2024-03-03
教你如何在 Nuxt 3 中使用 wavesurfer.js
這篇文章主要介紹了如何在 Nuxt 3 中使用 wavesurfer.js,本文結合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01

