项目概述
本教程将带领大家使用Vue2开发一个功能完整的企业级后台管理系统,涵盖用户权限控制、动态路由、组件化设计和API集成等核心功能。通过这个实战项目,您将掌握Vue2在实际商业项目中的应用技巧。
环境搭建与项目初始化
首先使用Vue CLI创建项目:
# 安装Vue CLI
npm install -g @vue/cli
# 创建项目
vue create admin-system
# 进入项目目录
cd admin-system
# 安装必要依赖
npm install vue-router vuex element-ui axios
项目结构设计
我们采用以下项目结构组织代码:
src/
├── api/ # 接口请求模块
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # 状态管理
├── utils/ # 工具函数
├── views/ # 页面组件
└── permissions.js # 权限控制
路由设计与权限控制
在router/index.js中配置动态路由:
import Vue from 'vue'
import Router from 'vue-router'
import store from '@/store'
Vue.use(Router)
// 公共路由(无需权限)
const constantRoutes = [
{
path: '/login',
component: () => import('@/views/Login'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
}
]
// 异步路由(根据权限动态加载)
export const asyncRoutes = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/Dashboard'),
name: 'Dashboard',
meta: { title: '控制台', icon: 'dashboard', roles: ['admin', 'editor'] }
}
]
},
{
path: '/user',
component: Layout,
redirect: '/user/list',
name: 'User',
meta: { title: '用户管理', icon: 'user', roles: ['admin'] },
children: [
{
path: 'list',
component: () => import('@/views/UserList'),
name: 'UserList',
meta: { title: '用户列表', roles: ['admin'] }
}
]
}
]
const createRouter = () => new Router({
routes: constantRoutes
})
const router = createRouter()
// 权限控制
router.beforeEach((to, from, next) => {
// 获取token
const hasToken = localStorage.getItem('token')
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
} else {
// 检查用户是否已获取权限信息
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 获取用户信息
const { roles } = await store.dispatch('user/getInfo')
// 根据角色生成可访问路由
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 动态添加路由
router.addRoutes(accessRoutes)
// 确保addRoutes完成
next({ ...to, replace: true })
} catch (error) {
// 出错则重置token并跳转到登录页
await store.dispatch('user/resetToken')
next(`/login?redirect=${to.path}`)
}
}
}
} else {
if (to.path === '/login') {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
export default router
Vuex状态管理设计
创建模块化的Vuex store管理应用状态:
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
const modulesFiles = require.context('./modules', true, /.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^./(.*).w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
export default new Vuex.Store({
modules,
getters
})
// store/modules/user.js
const state = {
token: localStorage.getItem('token'),
name: '',
avatar: '',
roles: []
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
}
const actions = {
// 用户登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
localStorage.setItem('token', data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('验证失败,请重新登录')
}
const { roles, name, avatar } = data
// 角色必须是数组
if (!roles || roles.length {
reject(error)
})
})
},
// 用户退出
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
localStorage.removeItem('token')
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
API模块封装
使用axios封装统一的API请求模块:
// utils/request.js
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// token过期
if (res.code === 401) {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error)
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
// api/user.js
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
可复用组件开发
创建一个可复用的数据表格组件:
<template>
<div class="table-container">
<div class="filter-container">
<slot name="filter"></slot>
</div>
<el-table
:data="tableData"
v-loading="loading"
border
fit
highlight-current-row
style="width: 100%;"
@sort-change="handleSortChange"
>
<template v-for="column in columns">
<el-table-column
:key="column.prop"
:prop="column.prop"
:label="column.label"
:width="column.width"
:sortable="column.sortable"
:formatter="column.formatter"
>
<template v-if="column.slot" v-slot="scope">
<slot :name="column.slot" :row="scope.row"></slot>
</template>
</el-table-column>
</template>
</el-table>
<div class="pagination-container">
<el-pagination
:current-page="pagination.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pageSize"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script>
export default {
name: 'DataTable',
props: {
columns: {
type: Array,
required: true
},
fetchData: {
type: Function,
required: true
}
},
data() {
return {
tableData: [],
loading: false,
pagination: {
currentPage: 1,
pageSize: 10,
total: 0
},
sort: {}
}
},
mounted() {
this.getData()
},
methods: {
async getData() {
this.loading = true
try {
const params = {
page: this.pagination.currentPage,
limit: this.pagination.pageSize,
sort: this.sort.prop,
order: this.sort.order
}
const response = await this.fetchData(params)
this.tableData = response.data.list
this.pagination.total = response.data.total
} catch (error) {
console.error('获取数据失败:', error)
} finally {
this.loading = false
}
},
handleSizeChange(val) {
this.pagination.pageSize = val
this.getData()
},
handleCurrentChange(val) {
this.pagination.currentPage = val
this.getData()
},
handleSortChange({ prop, order }) {
this.sort = { prop, order }
this.getData()
},
refresh() {
this.pagination.currentPage = 1
this.getData()
}
}
}
</script>
权限指令实现
创建自定义权限指令控制按钮显示:
// directives/permission.js
import store from '@/store'
function checkPermission(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`需要指定权限角色,如 v-permission="['admin']"`)
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}
// 在main.js中注册指令
import permission from '@/directives/permission'
Vue.directive('permission', permission)
// 使用示例
<el-button v-permission="['admin']">只有管理员可见</el-button>
项目部署与优化
1. 配置vue.config.js进行构建优化:
module.exports = {
productionSourceMap: false,
configureWebpack: {
externals: process.env.NODE_ENV === 'production' ? {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT'
} : {}
},
chainWebpack: config => {
// 分割代码
config.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\/]node_modules[\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\/]node_modules[\/]_?element-ui(.*)/
}
}
})
}
}
总结
通过本教程,我们完成了一个功能完整的企业级后台管理系统,涵盖了Vue2开发中的核心概念和最佳实践:
- 基于Vue CLI的项目搭建和配置
- Vue Router动态路由和权限控制实现
- Vuex模块化状态管理设计
- axios请求拦截和统一错误处理
- 可复用组件开发和自定义指令
- 项目优化和部署策略
这个项目展示了Vue2在复杂企业应用中的强大能力,希望本教程能帮助您深入理解Vue2的开发模式和最佳实践。