v3-admin 是一个中后台管理系统基础解决方案,基于 Vue三、TypeScript、Element-Plus 和 Vue-Cli 4.5css
GitHub地址:github.com/v3-projects… ,已上生产环境。欢迎各位提 Issues、PR,以为能够的点个 Star 支持一下!html
- 用户管理
- 登陆
- 注销
- 权限验证
- 页面权限
- 指令权限
- 多环境
- development
- test
- production
- 全局功能
- svg
- 国际化
- 多主题切换(内置黑暗主题)
- 动态侧边栏
- 动态面包屑
- 标签页快捷导航
- Screenfull 全屏
- 自适应收缩侧边栏
- 错误页面
- 401
- 404
- Dashboard
- admin
- editor
- 自动部署
复制代码
# v3-admin
├─ .env.development # 开发环境
├─ .env.production # 生产环境
├─ .env.test # 测试环境
├─ .eslintrc.js # eslint
├─ deploy # 自动部署
├─ public
│ ├─ favicon.ico
│ ├─ index.html
├─ src
│ ├─ @types # ts 声明
│ ├─ api # api 接口
│ ├─ assets # 静态资源
│ ├─ components # 全局组件
│ ├─ config # 全局配置
│ ├─ constant # 常量/枚举
│ ├─ directives # 全局指令
│ ├─ layout # 布局
│ ├─ locales # 国际化
│ ├─ model # 全局 model
│ ├─ plugins # 插件
│ ├─ router # 路由
│ ├─ store # vuex store
│ ├─ styles # 全局样式
│ ├─ utils # 全局公共方法
│ └─ views # 全部页面
│ ├─ App.vue # 入口页面
│ ├─ main.ts # 入口文件
│ ├─ permission.ts # 权限管理
│ └─ shims.d.ts # 模块注入
├─ tsconfig.json # ts 编译配置
└─ vue.config.js # vue-cli 配置
复制代码
# 克隆项目
git clone https://github.com/v3-projects/v3-admin
# 进入项目目录
cd v3-admin
# 安装依赖
yarn
# 启动项目
yarn dev
复制代码
// 默认false,设置 true 的时候该路由不会在侧边栏出现
hidden: true
// 设置 noRedirect 的时候该路由在面包屑导航中不可被点击
redirect: 'noRedirect'
// 设定路由的名字,必定要填写否则重置路由可能会出问题
name: 'router-name'
meta: {
// 设置该路由进入的权限,支持多个权限叠加
roles: ['admin', 'editor']
// 设置该路由在侧边栏和面包屑中展现的名字
title: 'title'
// 设置该路由的图标,记得将 svg 导入 @/assets/svg-icons/icons
icon: 'svg-name'
// 默认true,若是设置为false,则不会在面包屑中显示
breadcrumb: false
// 默认false,若是设置为true,它则会固定在 tags-view 中
affix: true
// 当一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式
// 只有一个时,会将那个子路由当作根路由显示在侧边栏
// 若想无论路由下面的 children 声明的个数都显示你的根路由
// 能够设置alwaysShow: true,这样就会忽略以前定义的规则,一直显示根路由
alwaysShow: true
// 当设置了该属性,进入路由时,则会高亮 activeMenu 属性对应的侧边栏
activeMenu: '/dashboard'
}
复制代码
constantRoutes
把不须要判断权限的路由放置在常驻路由里面,如 /login
、/dashboard
。前端
asyncRoutes
放置需求动态判断权限并经过 addRoute
动态添加的路由。vue
添加动态路由方法:node
案例:在 @/router/asyncModules
下面建立例如 permission.ts
文件webpack
import { RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'
const permissionRouter: Array<RouteRecordRaw> = [
{
path: '/permission',
component: Layout,
name: 'Permission',
redirect: '/permission/directive',
meta: {
title: 'permission',
icon: 'lock',
roles: ['admin', 'editor'], // 能够在根路由中设置角色
alwaysShow: true // 将始终显示根菜单
},
children: [
{
path: 'page',
component: () => import(/* webpackChunkName: "permission-page" */ '@/views/permission/page.vue'),
name: 'PagePermission',
meta: {
title: 'pagePermission',
roles: ['admin'] // 或者在子导航中设置角色
}
},
{
path: 'directive',
component: () => import(/* webpackChunkName: "permission-directive" */ '@/views/permission/directive.vue'),
name: 'DirectivePermission',
meta: {
title: 'directivePermission' // 若是未设置角色,则表示:该页面不须要权限,但会继承根路由的角色
}
}
]
}
]
export default permissionRouter
复制代码
侧边栏 @/layout/components/sidebar
是经过读取路由并结合权限判断而动态生成的(换句话说就是常驻路由 + 有权限的路由)ios
能够在侧边栏中配置一个外链,只要你在 path 中填写了合法的 url 路径,当你点击侧边栏的时候就会帮你新开这个页面nginx
{
path: 'link',
component: Layout,
children: [
{
path: 'https://github.com/v3-projects/v3-admin',
meta: { title: 'link', icon: 'link' },
name: 'Link'
}
]
}
复制代码
面包屑 @/components/bread-crumb
也是根据路由动态生成的,为路由设置 breadcrumb: false
时该路由将不会出如今面包屑中,设置 redirect: 'noRedirect'
时该路由在面包屑中不能被点击git
登陆时经过获取当前用户的权限(角色)去比对路由表,生成当前用户具备的权限可访问的路由表,经过 addRoute
动态挂载到 router 上github
控制代码都在 @/permission.ts
中,这里可根据具体的业务作响应的修改:
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import router from '@/router'
import { RouteLocationNormalized } from 'vue-router'
import { useStore } from './store'
import { UserActionTypes } from './store/modules/user/action-types'
import { PermissionActionType } from './store/modules/permission/action-types'
import { UserMutationTypes } from './store/modules/user/mutation-types'
import { ElMessage } from 'element-plus'
import { whiteList } from './config/white-list'
import rolesSettings from './config/roles'
NProgress.configure({ showSpinner: false })
router.beforeEach(async(to: RouteLocationNormalized, _: RouteLocationNormalized, next: any) => {
NProgress.start()
const store = useStore()
// 判断该用户是否登陆
if (store.state.user.token) {
if (to.path === '/login') {
// 若是登陆,并准备进入 login 页面,则重定向到主页
next({ path: '/' })
NProgress.done()
} else {
// 检查用户是否已得到其权限角色
if (store.state.user.roles.length === 0) {
try {
// 注意:角色必须是一个对象数组! 例如: ['admin'] 或 ['developer', 'editor']
await store.dispatch(UserActionTypes.ACTION_GET_USER_INFO, undefined)
if (rolesSettings.openRoles) {
// 获取接口返回的 roles
const roles = store.state.user.roles
// 根据角色生成可访问的 routes
store.dispatch(PermissionActionType.ACTION_SET_ROUTES, roles)
} else {
// 没有开启角色功能,则启用默认角色
store.commit(UserMutationTypes.SET_ROLES, rolesSettings.defaultRoles)
store.dispatch(PermissionActionType.ACTION_SET_ROUTES, rolesSettings.defaultRoles)
}
// 动态地添加可访问的 routes
store.state.permission.dynamicRoutes.forEach((route) => {
router.addRoute(route)
})
// 确保添加路由已完成
// 设置 replace: true, 所以导航将不会留下历史记录
next({ ...to, replace: true })
} catch (err) {
// 删除 token,并重定向到登陆页面
store.dispatch(UserActionTypes.ACTION_RESET_TOKEN, undefined)
ElMessage.error(err || 'Has Error')
next('/login')
NProgress.done()
}
} else {
next()
}
}
} else {
// 若是没有 token
if (whiteList.indexOf(to.path) !== -1) {
// 若是在免登陆的白名单中,则直接进入
next()
} else {
// 其余没有访问权限的页面将被重定向到登陆页面
next('/login')
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
复制代码
假如你的业务场景中没有 角色
的概念,那么在 @/config/roles
里能够关闭角色功能,关闭后系统将启用默认角色(通常为最高权限的 admin 角色),即每一个登陆的用户均可见全部路由
interface RolesSettings {
// 是否开启角色功能(开启后须要后端配合,在查询用户详情接口返回当前用户的所属角色)
openRoles: boolean
// 当角色功能关闭时,当前登陆用户的默认角色将生效(默认为admin,拥有全部权限)
defaultRoles: Array<string>
}
const rolesSettings: RolesSettings = {
openRoles: true,
defaultRoles: ['admin']
}
export default rolesSettings
复制代码
简单快速的实现按钮级别的权限判断(已注册到全局,可直接使用):
<el-tag v-permission="['admin']">admin可见</el-tag>
<el-tag v-permission="['editor']">editor可见</el-tag>
<el-tag v-permission="['admin','editor']">admin和editor均可见</el-tag>
复制代码
但在某些状况下,不适合使用 v-permission
。例如:Element 的 el-tab 或 el-table-column 以及其它动态渲染 dom 的场景。你只能经过手动设置 v-if 来实现。
这时候可使用权限判断函数
import { checkPermission } from '@/utils/permission'
复制代码
<el-tab-pane v-if="checkPermission(['admin'])" label="Admin">admin可见</el-tab-pane>
<el-tab-pane v-if="checkPermission(['editor'])" label="Editor">editor可见</el-tab-pane>
<el-tab-pane v-if="checkPermission(['admin','editor'])" label="AdminEditor">admin和editor均可见</el-tab-pane>
复制代码
大体的流程以下:
graph LR 页面/交互 --> 统一管理的API --> 封装的service.ts --> 服务器
@/api/login.ts
import { request } from '@/utils/service'
interface UserRequestData {
username: string
password: string
}
export function accountLogin(data: UserRequestData) {
return request({
url: 'user/login',
method: 'post',
data
})
}
复制代码
@/utils/service.ts
是基于 axios 的封装,封装了全局 request 拦截器、response 拦截器、统一的错误处理、统一作了超时处理、baseURL设置、CancelToken 等。
项目开发完成,打包代码时,内置两种环境:
# 打包测试环境
yarn build:test
# 打包正式环境
yarn build:prod
复制代码
在 .env.production
等 .env.xxx
文件中,配置了该环境对应的变量:
# 当前运行的环境
NODE_ENV=production
# 当前环境对应接口的 baseURL
VUE_APP_BASE_API = 'https://www.xxx.com'
复制代码
获取方式:
console.log(process.env.NODE_ENV)
console.log(process.env.VUE_APP_BASE_API)
复制代码
规范代码很重要!
.eslintrc.js
文件中@/config/vue.custom.config.ts
中将 lintOnSave
设置为 false
yarn lint
(提交代码前执行该命令,特别是在你 lintOnSave 为 false 的状况下)package.json
中配置了 gitHooks,每次 commit 时都将检测代码
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"vue-cli-service lint",
"git add"
]
}
复制代码
@/config
文件夹下存放了内置的一些配置项,好比 white-list
路由白名单,vue.custom.config
vue.config 文件配置等。
其中的 vue.custom.config
里就有 proxy
进行反向代理。
与之对应的生产环境,则可使用 nginx
来作反向代理。
const vueDefaultConfig = {
// ...其余配置
devServer: {
// ...其余配置
proxy: {
'/api/': {
target: 'http://xxxxxx/api/',
ws: true,
pathRewrite: {
'^/api/': ''
},
changeOrigin: true,
secure: false
}
}
}
}
module.exports = vueDefaultConfig
复制代码
这种方案对于前端来讲没有什么工做量,和正常发送请求写法上没有任何区别,工做量基本都在后端这里。
实现 CORS 以后,不论是开发环境仍是生产环境,都能方便的调用接口。
全局 @/components/svg-icon
组件,图标存放在 @/assets/svg-icons/icons
无需在页面中引入组件,可直接使用
<!-- name 为 svg 文件名 -->
<!-- class 可修改默认样式 -->
<svg-icon name="user" font-size="20px" class="icon" />
复制代码
推荐 iconfont
在 deploy/index.js
文件里填写服务器的IP、端口、用户名、密码等信息,再执行 yarn deploy
命令便可自动发布打包好的 dist 文件到对应的服务器
注意:该文件中的用户名密码等信息为敏感信息,勿上传到远程仓库,这一点很重要!
新增主题样式文件
src/style/theme/dark/index.scss
src/style/theme/dark/setting.scss
注册新的主题
src/style/theme/register.scss
src/config/theme.ts
Google 一下能够解决 99% 的报错
剩下 1% 能够加群问我,而后我偷偷去 Google
将 @/config/vue.custom.config.ts
文件中 publicPath
的值从 ./
修改成 /
无人问津的交流(吹水)群:1014374415