# vue3-web **Repository Path**: ning-caichen-zz/vue3-web ## Basic Information - **Project Name**: vue3-web - **Description**: 基于Vue 3的前端项目模板,提供现代化Web应用开发的最佳实践,包含组件化、状态管理及路由配置等核心功能。 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2025-03-10 - **Last Updated**: 2025-04-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 后台管理办公系统 [TOC] # 一. 创建项目 ## 1.1 项目概述 **后台办公系统** 是一个基于 **Vue 3** + **Vite** + **Element Plus** 的企业级管理平台,旨在提升公司员工的办公效率和管理便捷性。该系统采用 **动态路由、动态面包屑导航、多页签导航**,提供灵活的界面交互和高效的数据管理。 ### **核心技术栈** - **Vue 3**:采用 Composition API 进行组件化开发,提升代码复用性和可维护性。 - **Vite**:作为前端构建工具,提供极速冷启动、热更新和高效打包能力。 - **Element Plus**:基于 Vue 3 的 UI 组件库,提供美观、易用的界面组件。 - **动态路由**:基于 `vue-router`,支持权限控制,动态加载页面,提升系统扩展性。 - **动态面包屑**:根据当前路由自动生成导航路径,提升用户体验和操作便捷性。 - **多页签导航**:支持多个页面同时打开,并可自由切换,提高操作效率。 ![输入图片说明](src/image.png) ## 1.2 vite创建项目 ```py """ ningcaichen@bogon 04-北京地铁内部开发平台 % npm create vue@latest > npx > create-vue Vue.js - The Progressive JavaScript Framework ✔ 请输入项目名称: … vue_web_v1 ✔ 是否使用 TypeScript 语法? … 否 / 是 ✔ 是否启用 JSX 支持? … 否 / 是 ✔ 是否引入 Vue Router 进行单页面应用开发? … 否 / 是 ✔ 是否引入 Pinia 用于状态管理? … 否 / 是 ✔ 是否引入 Vitest 用于单元测试? … 否 / 是 ✔ 是否要引入一款端到端(End to End)测试工具? › 不需要 ✔ 是否引入 ESLint 用于代码质量检测? › 否 正在初始化项目 /Users/ningcaichen/Documents/02-python相关文档/04-北京地铁内部开发平台/vue_web_v1... 项目初始化完成,可执行以下命令: cd vue_web_v1 npm install npm run dev ningcaichen@bogon 04-北京地铁内部开发平台 % cd vue_web_v1 ningcaichen@bogon vue_web_v1 % npm install """ ``` ## 1.3 项目初始化 ```py """ 01 删除自带的组件,清空路由和样式 02 按照工具 vite-plugin-vue-setup-extend """ #01 下载依赖` npm i vite-plugin-vue-setup-extend -D #02 导入包 vite.config.ts import VueSetupExtend from 'vite-plugin-vue-setup-extend' //导入 export default defineConfig({ plugins: [ vue(), vueDevTools(), VueSetupExtend(), //注册 ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }, }, }) #03 使用该模块 ``` ## 1.4 创建路由组件 ```ts // 主要是书写 /hom组件 import {createRouter, createWebHistory} from 'vue-router' import Home from '@/views/Home.vue'; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ // 重定向到home { path: '/', redirect: '/home' }, { name: 'home', path: '/home', component: Home, }, ], }) export default router ``` ## 1.5 引用element UI ```py #01 安装 npm install element-plus --save #02 全局引用 import './assets/main.css' import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' const app = createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount('#app') ``` # 二. 书写基本组件 ```py #01 基本目录如下 ningcaichen@bogon vue_web_v1 % tree src src ├── App.vue # 组件入口 ├── assets │   ├── base.css │   ├── home.css # home主页样式 │   ├── logo.svg │   └── main.css ├── components # 组件目录 │   └── layout │   ├── Breadcrumb.vue # 面包屑组件 │   ├── MainLayout.vue # 空组件:用于加载路由内容 │   ├── Sidebar.vue # 左侧菜单组件 │   └── Tags.vue # 多页签导航组件 ├── main.ts # 主配置文件 ├── router │   └── index.ts # 路由配置文件 ├── stores │   ├── counter.ts # pinia 默认存储store │   └── tabs.ts # 多页签导航状态存储文件 ├── utils │   ├── commonData.ts # 菜单数据 │   └── icons.ts # 图标数据 └── views # 路由文件 ├── Home.vue ├── Index.vue ├── Login.vue ├── admin │   ├── permission │   │   └── Permission.vue │   └── role │   └── Role.vue └── system ├── dept │   └── Dept.vue ├── station │   └── Station.vue └── user └── User.vue ``` ## 2.1 定义动态菜单数据 `src/utils/commonData.ts` ```ts // 定义接口规范 interface MenuItem { id: number authName: string icon: string path: string | null children: MenuItem[] } // 路由数据 export const menuData: { data: MenuItem[]; meta: any } = { data: [ { id: 100, authName: "人员管理", icon: "Avatar", path: '/home/personnel', children: [ { id: 100003, authName: "用户管理", path: "/home/personnel/user", icon: "Menu", children: [] }, { id: 100001, authName: "岗位管理", path: "/home/personnel/station", icon: "Pointer", children: [] }, { id: 100002, authName: "部门管理", path: "/home/personnel/dept", icon: "EditPen", children: [] } ] }, { id: 102, authName: "系统管理", icon: "HomeFilled", path: '/home/admin', children: [ { id: 102003, authName: "角色管理", path: "/home/admin/role", icon: "Food", children: [] }, { id: 102001, authName: "权限管理", path: "/home/admin/permission", icon: "Opportunity", children: [] } ] } ], meta: { msg: "获取菜单成功", status: 200 } } ``` ## 2.2 定义路由数据 `router/index.ts` ```ts import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' // 人员管理模块 import User from '@/views/system/user/User.vue' import Station from '@/views/system/station/Station.vue' import Dept from '@/views/system/dept/Dept.vue' // 系统管理模块 import Role from '@/views/admin/role/Role.vue' import Permission from '@/views/admin/permission/Permission.vue' // 布局组件 const MainLayout = () => import('@/components/layout/MainLayout.vue') const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', redirect: '/home', meta: { hidden: true ,closable: true } }, { path: '/home', name: 'Home', component: Home, meta: { title: '首页',closable: true }, // redirect: '/home/system', // 默认重定向到系统管理 children: [ // 人员管理子模块 { path: 'personnel', name: 'Personnel', component: MainLayout, meta: { title: '人员管理',closable: true }, children: [ { path: 'user', name: 'User', component: User, meta: { title: '用户管理',closable: true } }, { path: 'station', name: 'Station', component: Station, meta: { title: '岗位管理',closable: true } }, { path: 'dept', name: 'Dept', component: Dept, meta: { title: '部门管理' ,closable: true } } ] }, // 角色权限子模块 { path: 'admin', name: 'Admin', component: MainLayout, meta: { title: '系统管理' ,closable: true }, children: [ { path: 'role', name: 'Role', component: Role, meta: { title: '角色管理',closable: true } }, { path: 'permission', name: 'Permission', component: Permission, meta: { title: '权限管理',closable: true } } ] } ] } ] }) export default router ``` - 注意这里引用一个空组件 后续会引用 ```ts ``` ## 2.3 Home.vue 主页面布局 > 我们使用el-menu 组件创建布局 `src/views/Home.vue` ```ts ``` ## 2.4 添加css样式 `src/assets/home.css` ```css /* 系统容器 */ .system-container { height: 100vh; --sidebar-bg: #304156; --header-bg: #ffffff; overflow: hidden; } /* 主布局 */ .main-wrapper { height: 100%; } /* 侧边栏区域 */ .nav-sidebar { background: var(--sidebar-bg); transition: width 0.3s ease; } /* 系统LOGO */ .system-logo { height: 60px; display: flex; align-items: center; padding: 0 20px; border-bottom: 1px solid rgba(255,255,255,0.1); transition: padding 0.3s; } .logo-text { color: #fff; font-size: 18px; font-weight: 600; letter-spacing: 1px; white-space: nowrap; } .logo-collapse { padding: 0 10px; justify-content: center; } /* 折叠按钮 */ .collapse-btn { color: #b0bac5; margin-right: 12px; transition: 0.3s; } .collapse-btn:hover { color: #fff; background-color: rgba(255,255,255,0.1); } /* 菜单深度样式 */ :deep(.sidebar-menu) { border-right: none !important; } :deep(.el-menu-item), :deep(.el-sub-menu__title) { height: 46px; line-height: 46px; transition: all 0.3s; } :deep(.el-menu-item:hover), :deep(.el-sub-menu__title:hover) { background-color: rgba(255,255,255,0.08) !important; } :deep(.el-menu-item.is-active) { background: rgba(64, 158, 255, 0.1) !important; border-left: 3px solid #409EFF; } .menu-icon { margin-right: 8px; font-size: 18px; } /* 折叠状态菜单 */ :deep(.el-menu--collapse) { border-right: none; } :deep(.el-menu--collapse .el-sub-menu > .el-menu) { display: none; } :deep(.el-menu--collapse .el-sub-menu__title span), :deep(.el-menu--collapse .el-menu-item span) { display: none; } /* 头部区域 */ .operate-header { height: 56px; display: flex; justify-content: space-between; align-items: center; background: var(--header-bg); border-bottom: 1px solid #e4e7ed; padding: 0 24px; } .header-left { display: flex; align-items: center; gap: 12px; } .el-breadcrumb { font-size: 15px; } /* 内容区域 */ .content-card { background: #fff; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); padding: 16px; } .table-container { margin-top: 12px; } ``` ## 2.5 左侧导航菜单组件 `src/components/layout/Breadcrumb.vue` ```ts ``` ## 2.5 动态图标组件 icons `src/utils/icons.ts` ```ts import { User, Avatar, Pointer, Menu, EditPen, Tools, HomeFilled, Food, Opportunity, Fold, Expand } from '@element-plus/icons-vue' // 类型定义 export type IconKey = keyof typeof iconComponents // 图标映射对象(使用 as const 锁定类型) export const iconComponents = { User, Avatar, Pointer, Menu, EditPen, Tools, HomeFilled, Food, Opportunity, Fold, Expand } as const // 获取图标函数(带安全类型) export const getIcon = (iconName: string) => { const key = iconName as IconKey return iconComponents[key] || User // 默认返回User图标 } ``` ## 2.6 动态面包屑组件 `src/components/layout/Breadcrumb.vue` ```ts ``` - 图示 ![image-20250310133408264](assets/image-20250310133408264.png) ## 2.7 多页签导航 ### (1) Pinia 定义数据和方法 `stores/tabs.tx` ```ts import { defineStore } from 'pinia' // 定义标签的数据结构 export interface Tab { title: string // 标签页名称 path: string // 标签页路由路径 closable?: boolean // 是否可关闭(首页不能关闭) } // 定义 Pinia Store export const useTabsStore = defineStore('tabs', { // state:存储数据 state: () => ({ // 初始化 tabs,默认包含首页,且首页不可关闭 tabs: [{ title: '首页', path: '/home', closable: false }] as Tab[], // 当前激活的标签路径,默认激活首页 activeTab: '/home' }), // actions:定义修改 state 的方法 actions: { // 添加标签 addTab(tab: Tab) { // 先检查这个标签是否已存在 const exists = this.tabs.find(t => t.path === tab.path) if (!exists) { // 设置是否可关闭,首页不能被关闭 tab.closable = tab.path === '/home' ? false : true this.tabs.push(tab) // 添加到 tabs 列表 } // 设置当前激活的标签 this.activeTab = tab.path }, // 删除标签 removeTab(path: string) { if (path === '/home') return // 首页不能被删除 const index = this.tabs.findIndex(t => t.path === path) if (index > -1) { this.tabs.splice(index, 1) // 从 tabs 中移除 // 如果删除的是当前激活的标签,调整 activeTab if (this.activeTab === path) { // 如果被删除的标签后面还有标签,则激活下一个标签 if (index < this.tabs.length) { this.activeTab = this.tabs[index].path } // 否则激活前一个标签 else if (index - 1 >= 0) { this.activeTab = this.tabs[index - 1].path } // 如果删除后 tabs 为空,则回到首页 else { this.activeTab = '/home' } } } }, // 设置当前激活的标签 setActiveTab(path: string) { this.activeTab = path } }, // Pinia 持久化存储,保证刷新页面后状态不会丢失 persist: { key: 'tabsStore', // 存储的 key storage: localStorage, // 存储方式(localStorage 或 sessionStorage) paths: ['tabs', 'activeTab'] // 需要持久化的字段 } as any // 这里使用 `as any` 避免 TypeScript 类型错误 }) ``` ### (2) Tags 组件 `src/components/layout/Tags.vue` ```ts ``` ### (3) 添加监听器 `home.vue` 文件中添加监听器 ```ts ``` ### (4) 修改样式 ```css /* home.css 文件内添加 */ .el-main { --el-main-padding: 10px; /* 边距 */ box-sizing: border-box; display: block; flex: 1; flex-basis: auto; overflow: auto; padding: var(--el-main-padding); } ``` ## 2.8 持久化存储 ```py #01 下载 pinia-plugin-persistedstate npm install pinia-plugin-persistedstate #02 main.ts 导入 import './assets/main.css' import { createApp } from 'vue' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' const pinia = createPinia() pinia.use(piniaPluginPersistedstate) const app = createApp(App) app.use(pinia) app.use(router) app.use(ElementPlus) app.mount('#app') #03 pinia增加配置 export const useTabsStore = defineStore('tabs', { // Pinia 持久化存储,保证刷新页面后状态不会丢失 persist: { key: 'tabsStore', // 存储的 key storage: localStorage, // 存储方式(localStorage 或 sessionStorage) paths: ['tabs', 'activeTab'] // 需要持久化的字段 } as any // 这里使用 `as any` 避免 TypeScript 类型错误 }) ``` ```ts ```