项目说明
Vue3-admin-template
是一个后台前端解决方案,基于最新(2022
年)的前端技术构建,可以直接基于此项目进行二次开发。
技术栈
- 前端框架:
Vue3
- UI 组件库:
Element Plus
- 路由管理:
Vue Router v4
- 状态管理:
Pinia
- 网络管理:
axios
- 前端开发与构建工具:
Vite v2
- 编程语言:
TypeScript
初始化项目
新建项目
打开终端输入:
1 | npm create vite@latest |
依次输入项目名称、框架即可。
这里选择vue + ts
。
启动项目
安装依赖包
1 | npm install |
运行
1 | npm run dev |
精简项目
- index.html
将<title>
更改为vue3-admin-template
。
- src->App.vue
删除<script>
、<template>
和<style>
中的内容。
1 | <script setup lang="ts"> |
- src->components->HelloWorld.vue
删除该组件。
项目实现——极简版
路由配置
安装
1 | npm install vue-router@4 |
新建登录页面
src->views->login->index.vue
1 | <template> |
views
文件夹通常存放的是页面级文件。
新建首页页面
src->views->dashbord->index.vue
1 | <template> |
新建全局布局系统
新建src->layout->components
文件夹。
新建src->layout->index.vue
:
1 | <template> |
新建路由配置文件
新建src->router->index.ts
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' |
这里需要设置src
文件夹别名为@
:
增加tsconfig.json
的compilerOptions
键值对:
1 | // 设置 @ 路径 |
修改vite.config.ts
1 | import { defineConfig } from 'vite' |
如果Vscode
编译器报错,需要安装依赖包:
1 | npm i @types/node --D |
注:如果使用了Vite 3
版本,且在package.json
中使用了"type": "module"
的配置,则将导入path
改为import
形式:
1 | import path from 'path' |
修改App.vue
1 | <template> |
修改main.ts文件
1 | import { createApp } from 'vue' |
实际效果图:
如图所示,在地址栏输入/login
和/
即可切换路由。
配置初始路由为登录页
添加src->permission.ts
:
1 | import router from '@/router/index' |
当路由指向不是登录页且没有用户名信息时,将路由强制跳转至登录页,否则放行。
main.ts
:
1 | import '@/permission' |
注:这里只是简单示例,没有添加实际的业务需求。下同。
其他配置
ELement UI Plus配置
安装
1 | npm install element-plus --save |
配置
main.ts
1 | // 引入element-ui组件 |
scss配置
安装
1 | npm i sass --save-dev |
ESLint配置
安装
1 | npm install eslint --save-dev |
这里使用的配置选项为:
注:如果使用了Vite 3
版本,且在package.json
中使用了"type": "module"
的配置,则会在项目生成.eslintrc.cjs
文件。
如果勾选了TypeScript
配置,则需要在.eslintrc.cjs
文件中设置TypeScript
的配置文件: Linting with Type Information
1 | parserOptions: { |
更新rules
详见Github
项目地址中的.eslintrc.js
文件。仅供参考。
在Vscode
编译器中安装ESLint
插件,并打开配置文件settings
(ctrl+shift+p
),增加以下配置信息:
1 | // eslint配置 |
注:如果eslint
没有运行,可以打开Vscode
编译器右下角的ESlint
终端查找错误。
注:eslint-plugin-vue v9.0.0
以上移除了vue/name-property-casing
规则,如果在rules
中配置了需要将其删除。vue/name-property-casing
登录页面
src->views->login->index.vue
1 | <template> |
这里只做了简单的表单验证,如果成功则跳转至首页,并在浏览器中记录用户名信息。
全局布局组件系统
全局布局文件整体分为侧边栏、导航栏、页面主体区域和状态栏四个部分。具体位置见上图所示。
在src
目录下新建layout
文件夹,用于存放全局布局文件。然后在layout
文件夹下,新建components
文件夹和index.vue
文件。其中components
文件夹存放各个区域的布局文件,index.vue
为出口文件。
在components
文件夹下,分别新建侧边栏布局文件夹Sidebar
文件夹(并在文件夹内新建出口文件index.vue
),主体区域布局文件AppMain.vue
、导航栏布局文件Narvar.vue
和状态栏布局文件Footerbar.vue
。
layout
的出口文件src->layout->index.vue
:
1 | <template> |
1 | import AppMain from './components/AppMain.vue' |
这里只显示布局文件和脚本文件,样式文件详见Github
项目地址。下同。
主体区域布局文件
src->layout->components->AppMain.vue
:
1 | <template> |
这里存放路由配置文件中的具体路由显示。
状态栏布局文件
src->layout->components->Footerbar.vue
:
1 | <template> |
这里只做了简单的显示,可根据实际需求添加相应功能。
导航栏布局文件
src->layout->components->Narbar.vue
:
1 | <template> |
1 | import { ArrowDown } from '@element-plus/icons-vue' |
这里只做了简单的信息显示和退出登录功能。
菜单栏组件系统
菜单栏组件主要分为两部分,上面为项目图标和项目名称,下面为各个菜单组成的滚动条。
图标组件
在Sidebar
文件夹下新建Logo.vue
文件:
src->layout->components->Sidebar->Logo.vue
:
1 | <template> |
这里使用<router-link>
进行包裹区域,可以实现点击图标/标题跳转至首页。
1 | <script setup lang="ts"> |
注意vite
获取静态资源文件的方式。
菜单路由组件系统
在Sidebar
文件夹下,新建菜单项组件:SidebarItem.vue
,菜单内容组件Item.vue
和判断菜单类型组件Link.vue
。
本项目主要包含3种路由类型:单级路由,多级路由和外部链接路由。因此,首选更改之前的路由配置文件:
src->router->index.ts
:
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' |
src->layout->components->Sidebar->index.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
菜单路由部分首先使用el-scrollbar
滚动条组件包裹所有的el-menu
组件,然后使用v-for
指令添加路由配置文件的所有路由。
src->layout->components->Sidebar->SidebarItem.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
在菜单项组件中,首先判断路由是否包含子路由,如果包含,则会循环遍历所有的子路由,否则会根据路由的性质判断是否是外部链接。
src->layout->components->Sidebar->Link.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
src->layout->components->Sidebar->Item.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
最后每个菜单内容由图标+标题组成。
项目实现——标准版
在标准版中,会增加以下功能:
- 由于
Element UI Plus
系统本身提供的图标并不多,而且png
图片会存在一定的失真,因此本项目使用svg
管理所有的图标系统。 - 目前一些状态信息是存放在浏览器的
localStorage
中,安全性较低,本项目使用pinia
管理项目中所有的信息系统。 - 优化全局布局组件系统,扩展功能。
全局图标系统
安装
1 | npm install svg-sprite-loader -D |
Svg图标组件
在src
文件夹下新建icons
文件夹,用来处理全局图标系统。然后新建index.vue
文件和svg
文件夹,其中svg
文件夹下存放所有的.svg
图标文件。
src->icons->index.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
1 | <style lang="scss" scoped> |
vite配置
在src
文件夹下新建plugins
,用于存放项目中的插件系统,然后新建svgBuilder.ts
:
1 | import { Plugin } from 'vite' |
并在vite.config.ts
中配置:
1 | // 引入svgBuilder插件 |
这里如果提示不能找到该ts
文件,需要在tsconfig.node.json
中添加该文件:
1 | "include": ["vite.config.ts", "./src/plugins/svgBuilder.ts"] |
main.ts配置
最后将其配置到main.ts
中,注册组件:
1 | // 引入svg |
使用
例如,在全局布局组件系统中的菜单栏组件所有的图标改成svg
的形式:
src->layout->components->sidebar->item.vue
:
1 | <template> |
这里主要通过路由配置文件中每个路由的icon
参数来选择相应的图标。(默认已经将svg
图标放在src->icons->svg
中。)
注1:如果不能看到效果,需要重启项目。
注2:iconfont图标库
全局状态系统
安装
1 | npm install pinia |
main.ts配置
1 | import { createPinia } from 'pinia' |
使用
在src
目录下新建store
文件夹,存放所有的状态管理文件。
pinia
样例文件:
src->store->example.ts
:
1 | import { defineStore } from 'pinia' |
全局布局组件系统优化
增加菜单栏显示/隐藏功能
该功能主要是实现点击切换按钮时,菜单栏会只显示图标部分,隐藏所有的文字部分,再次点击时,会恢复初始状态,且菜单栏宽度保持自适应状态。
状态管理设置
首先在src->store
文件夹中,新建app.ts
,用于存放当前设备的状态。
src->store->app.ts
:
1 | import { defineStore } from 'pinia' |
切换组件实现
然后在src
文件夹中新建components
文件夹,该文件夹主要存放非页面级的组件。
在该文件夹下新建控制菜单栏显示切换的图标组件:
src->components->Hamburger->index.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
导航栏组件调整
Hamburger
组件主要是存放在导航栏组件的左侧,菜单栏组件的右侧,因此在父组件导航栏组件中,引入该图标组件:
src->layout->components->Narbar.vue
:
1 | <!-- 控制菜单栏的显示 --> |
1 | import Hamburger from '@/components/Hamburger/index.vue' |
注:这里只显示和之前代码有变化的部分,下同。
菜单栏组件调整
首先将Logo.vue
组件设置为根据collapse
状态控制是否显示标题:
src->layout->components->Sidebar->Logo.vue
:
1 | <template> |
1 | import { defineProps, reactive } from 'vue' |
使用v-if
和v-else
指令,结合传入的collapse
状态值,实时切换是否显示标题文字。
然后修改Logo.vue
的父组件index.vue
,将状态管理中的sidebar
值实时传递给Logo.vue
组件:
1 | <!-- 左上角图标 --> |
1 | import { computed } from 'vue' |
菜单选项组件的显示和隐藏主要通过样式来控制(src->style->sidebar.module.scss
)。
增加导航栏面包屑功能
该功能主要实现在导航栏上实时显示当前路由的地址,如果是嵌套路由则会根据嵌套规则显示完整的路由地址,并且点击相应的路由标题时,会切换到相应的界面。
面包屑组件实现
在src->compnents
目录下新建Hamburger->index.vue
组件,实现面包屑功能:
1 | <template> |
1 | <script setup lang="ts"> |
其主要原理就是实时监测当前路由的变化,并根据当前路由的参数遍历生成面包屑路由,并将首页路由添加至面包屑开始位置,通过点击面包屑中显示的路由标题,实现路由的切换。
导航栏组件调整
最后将其添加至父组件导航栏组件中:
src->components->Narbar.vue
:
1 | <breadcrumb id="breadcrumb-container" class="breadcrumb-container" /> |
1 | import Breadcrumb from '@/components/Breadcrumb/index.vue' |
增加标签栏导航功能
该功能主要实现在导航栏上保存已经访问过的路由,点击相应标签即可切换至相应路由,并且可以实现关闭其他、全部关闭路由等功能。
路由标签状态管理
首先在src->store
文件夹中,新建tagsViews.ts
,用于存放已访问的路由和缓存的路由,并实现添加路由、删除路由等功能。
1 | import { defineStore } from 'pinia' |
标签组件实现
在src->layout->components
下新建TagsView
文件夹,并在该文件夹下新建index.vue
和ScrollPane.vue
。
src->layout->components->TagsView->ScrollPane.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
src->layout->components->TagsView->index.vue
:
1 | <template> |
1 | <script setup lang="ts"> |
其主要原理就是首先将访问过的路由依次显示在滚动条组件中,然后通过鼠标右击事件弹出功能菜单,并通过定义在tagsViewStore
里面的功能实现相应的功能。
全局布局组件调整
最后将其添加至父组件全局布局组件中:
src->layout->index.vue
:
1 | <div> |
1 | import TagsView from './components/TagsView/index.vue' |
项目实现——完整版
全局网络请求管理系统
本项目使用Axios
作为网络请求库。
安装
1 | npm install axios |
封装拦截器
在src/utils
文件夹下新建request.ts
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
// URL地址
baseURL: process.env.VUE_APP_BASE_API,
// 连接时间
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// const userStore = useUserStore()
// // 如果有token 则加上token值
// if (userStore.token) {
// config.headers['X-Token'] = getToken()
// }
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
// 如果状态码不是20000
// 根据实际的后端接口确定状态码
if (res.code !== 20000) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
// 正确则返回数据
return res
}
},
(error) => {
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
首先配置Axios
的根URL
地址,由于项目在开发的过程中,开发环境和生产环境的服务器地址不同,因此需要在在项目中创建.env.development
和.env.production
文件,配置不同的根URL
地址:
.env.development
1
2
3
4
5# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://127.0.0.1:8080'
.env.production
1
2
3
4
5# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = 'http://10.0.0.0:8080'
其中development
为开发环境,production
为生产环境。
请求拦截器主要处理用户登录时Token
的保存业务。
响应拦截器主要处理获取服务器数据的业务。
注:响应拦截器的的状态码需要根据实际后端业务设计来编写。如果没有,可以去掉该判断。
封装后端接口
在项目文件夹下创建API
文件夹src->api
,并根据业务需求创建example.ts
文件:
src->api->example.ts
1 | import request from '@/utils/request' |
使用api
在具体的业务文件中,调用该接口即可。注意由于Axios
返回的是Promise
对象,因此最好使用async
和await
来调用。
1 | import { functionName1 } from '@/api/example' |