当前位置: 代码网 > it编程>编程语言>Javascript > vue3+element-plus+vite实现动态路由菜单方式

vue3+element-plus+vite实现动态路由菜单方式

2025年02月13日 Javascript 我要评论
1. 环境搭建1.1 新建一个vite搭建的vue3项目先执行以下命令npm create vite@latest my-project(你的项目名)1.2 选择项目框架 vue1.3 选择语言类型

1. 环境搭建

1.1 新建一个vite搭建的vue3项目

先执行以下命令

npm create vite@latest my-project(你的项目名)

1.2 选择项目框架 vue

1.3 选择语言类型 ts

1.4 执行命令进入到新建的项目文件中

cd  my-project

1.5 下载依赖

npm i

下载项目中需要使用到的环境

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

1.6 完善项目目录结构以及环境配置

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状态管理和路由管理

1.6.3.1 router文件夹中新建两个文件一个index.ts用来初始化路由和存放静态路由一个dynamicroutes.ts存放处理动态路由

// 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>; // 用于动态加载组件的路径
  children?: menuitem[]; // 子路由类型
  redirect?: string; // 调整 redirect 为更简单的 string 类型
  meta?: {
    title: string;
  };
};
// vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块
const modules: record<string, () => promise<component>> = import.meta.glob('../views/**/**.vue');

// 初始化动态路由
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";
// 静态路由
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" },
];

// 创建路由
const router = createrouter({
  history: createwebhashhistory(), // 路由模式
  routes, // 静态路由
});

// 路由守卫:初始化时跳转到上次访问的页面
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 实现路由持久化和白名单,需要在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'; // 导入动态菜单数据

const whilelist = ['/login']; // 白名单
const vnode = createvnode(loadingbar);
render(vnode, document.body);

router.beforeeach(async (to, from, next) => {
  const routestore = useroutestore(); // 获取 pinia 中的路由状态
  const token = cookies.get('token'); // 从 cookie 获取 token

  // 判断是否有 token,存在则说明用户已登录
  if (token) {
    // 检查是否已经加载过动态路由
    if (routestore.dynamicroutes.length === 0) {
      // 检查是否有持久化的动态路由
      const persistedroutes = sessionstorage.getitem('dynamicroutes');  // 使用 sessionstorage
      if (persistedroutes) {
        // 如果有持久化的动态路由,直接从 sessionstorage 加载
        const routerlist = json.parse(persistedroutes);
        initdynamicroutes(routerlist); // 动态初始化路由
        routestore.setdynamicroutes(routerlist); // 将动态路由存入 pinia
        next({ ...to, replace: true }); // 确保动态路由加载后再跳转
        vnode.component?.exposed?.startloading(); // 启动加载条
      } else {
        // 如果没有持久化的动态路由,则使用静态的 dynamicrouter.json
        const dynamicroutes = initdynamicroutes(menudata); // 动态初始化路由
        if (dynamicroutes !== undefined) {
          routestore.setdynamicroutes(dynamicroutes); // 将动态路由存入 pinia
          sessionstorage.setitem('dynamicroutes', json.stringify(dynamicroutes)); // 存储动态路由到 sessionstorage
          next({ ...to, replace: true }); // 确保动态路由加载后再跳转
          vnode.component?.exposed?.startloading(); // 启动加载条
        } else {
          next('/login'); // 如果没有动态路由信息,跳转到登录页面
        }
      }
    } else {
      next(); // 如果已经加载过动态路由,直接跳转
    }
  } 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';

// 创建 pinia 实例
const pinia = createpinia();

// 将所有 store 模块暴露
export { pinia, useroutestore, useuserstore };

1.6.3.2 store文件夹下新建文件useroutestore.ts处理存储动态路由文件

import { definestore } from 'pinia';
import { ref } from 'vue';
import { initdynamicroutes } from "@/router/dynamicroutes"; // 导入初始化动态路由的方法
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>; // 用于动态加载组件的路径
  children?: menuitem[]; // 子路由类型
  redirect?: string; // 调整 redirect 为更简单的 string 类型
  meta?: {
    title: string;
  };
};
// 定义路由数据 store
export const useroutestore = definestore('route', () => {
  // 存储菜单数据
  const menudata = ref<menuitem[]>([]); // 根据你的菜单数据结构调整类型

  // 存储动态路由数据
  const dynamicroutes = ref<menuitem[]>([]);

  // 存储是否已初始化路由的状态
  const isroutesinitialized = ref<boolean>(false);

  // 存储上一次页面刷新的路由
  const beforerouter = ref<string>('');

  // 初始化动态路由
  const setdynamicroutes = (menu: any[]) => {
    // 只在未初始化路由时执行
    if (!isroutesinitialized.value) {
      // 调用 initdynamicroutes 函数来生成动态路由
      initdynamicroutes(menu);

      // 将菜单数据存储到状态中
      menudata.value = menu;

      // 设置已初始化状态
      isroutesinitialized.value = true;
    }
  };

  // 获取动态路由
  const getdynamicroutes = () => {
    return dynamicroutes.value;
  };

  // 更新动态路由
  const setupdateddynamicroutes = (routes: menuitem[]) => {
    dynamicroutes.value = routes;
  };

  return {
    menudata,
    dynamicroutes,
    isroutesinitialized, // 公开这个状态,方便其他地方判断
    setdynamicroutes,
    getdynamicroutes,
    setupdateddynamicroutes, // 更新动态路由的函数
    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
// 创建 pinia 实例

// 路由拦截 路由发生变化修改页面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" />
// 类型补充、环境变量
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;
  // 更多环境变量...
}

// 如果遇到路径缺失找不到的情况
// 无法找到模块“xxx”的声明文件,就将该模块加入到下列代码中进行补充声明
declare module "xxxx";

1.7 因为考虑是纯前端模拟后端给的路由数据

所以我自己模拟一个json文件,需在public文件夹中新建dynamicrouter.json来存放模拟后端返回的路由数据,后期从接口获取可进行更改

[
  {
    "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"
        }
      }
    ]
  }
]

如下是文件对应的位置

到目前为止整体的环境已经搭建完善,大概结构如下

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";
//导入模拟的动态路由数据
import menudata from '/public/dynamicrouter.json'; // 导入动态菜单数据
import { elmessage, type forminstance } from "element-plus";
import { userouter } from "vue-router";
import { initdynamicroutes } from "@/router/dynamicroutes"; // 导入初始化动态路由的方法
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>();
// 这里存放数据
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 或账户信息,存储在全局状态
          // 假设这里会调用登录 api 返回一个 token
          const token = "mock_token"; // 模拟登录后返回的 token
          // 使用 pinia 保存 token,并设置到 cookie 中
          // 存储在 cookie 中的 token 可以配合 httponly 和 secure 标志来增强安全性,
          // 这样可以防止 xss 攻击并确保 token 只有在通过 https 协议时才会被发送
          userstore.settoken(token);
          elmessage.success("登录成功");
          // 获取菜单数据
          if(routestore.isroutesinitialized){
            // 使用 nexttick 确保路由添加完成后再进行跳转
            nexttick(() => {
              // 跳转到首页或其他路由
              router.push('/') // 假设 'home' 是你动态路由中的一个页面名称
                .then(() => {
                  console.log('跳转成功');
                })
                .catch((error) => {
                  console.error('跳转失败', error);
                });
            });
          }else{
            initdynamicroutes(menudata)
              // 标记路由已初始化
            routestore.isroutesinitialized = true
            // 使用 nexttick 确保路由添加完成后再进行跳转
            nexttick(() => {
              // 跳转到首页或其他路由
              router.push('/') // 假设 'home' 是你动态路由中的一个页面名称
                .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中制作动态路由菜单

<!-- 自定义编辑的样式不采用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 创建左右布局 */
  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; /* 设置二级菜单项的缩进,区别于一级菜单 */
      }
    }

    /* 子菜单的展开箭头样式 */
    :deep(.el-sub-menu__icon-arrow) {
      color: #ffd04b; /* 设置箭头颜色为黄色 */
    }

    /* 设置展开状态时,子菜单的背景色变化 */
    .el-sub-menu.is-opened {
      background-color: #3a424a; /* 打开时的背景色 */
    }

    /* 设置菜单项和子菜单项的 hover 状态 */
    .el-menu-item:hover,
    .el-sub-menu:hover {
      background-color: #333c44; /* 鼠标悬浮时的背景色 */
    }

    /* 设置当前激活的菜单项的背景颜色 */
    .el-menu-item.is-active {
      background-color: #ff6600; /* 激活状态的背景色 */
    }
  }
  .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; /* 支持内容区域滚动 */
  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>

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com