1.前端框架

1.1简介

vue-element-admin
vue-element-admin是基于element-ui 的一套后台管理系统集成方案。
功能:https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能
GitHub地址:https://github.com/PanJiaChen/vue-element-admin
项目在线预览:https://panjiachen.gitee.io/vue-element-admin

vue-admin-template
vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。
GitHub地址:https://github.com/PanJiaChen/vue-admin-template

1.2安装

  • 在github上下载并解压
  • npm install
  • npm run dev

1.3源码目录

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
55
56
|-dist 生产环境打包生成的打包项目
|-mock 产生模拟数据
|-public 包含会被自动打包到项目根路径的文件夹
|-index.html 唯一的页面
|-src
|-api 包含接口请求函数模块
|-table.js 表格列表mock数据接口的请求函数
|-user.js 用户登陆相关mock数据接口的请求函数
|-assets 组件中需要使用的公用资源
|-404_images 404页面的图片
|-components 非路由组件
|-SvgIcon svg图标组件
|-Breadcrumb 面包屑组件(头部水平方向的层级组件)
|-Hamburger 用来点击切换左侧菜单导航的图标组件
|-icons
|-svg 包含一些svg图片文件
|-index.js 全局注册SvgIcon组件,加载所有svg图片并暴露所有svg文件名的数组
|-layout
|-components 组成整体布局的一些子组件
|-mixin 组件中可复用的代码
|-index.vue 后台管理的整体界面布局组件
|-router
|-index.js 路由器
|-store
|-modules
|-app.js 管理应用相关数据
|-settings.js 管理设置相关数据
|-user.js 管理后台登陆用户相关数据
|-getters.js 提供子模块相关数据的getters计算属性
|-index.js vuex的store
|-styles
|-xxx.scss 项目组件需要使用的一些样式(使用scss)
|-utils 一些工具函数
|-auth.js 操作登陆用户的token cookie
|-get-page-title.js 得到要显示的网页title
|-request.js axios二次封装的模块
|-validate.js 检验相关工具函数
|-index.js 日期和请求参数处理相关工具函数
|-views 路由组件文件夹
|-dashboard 首页
|-login 登陆
|-App.vue 应用根组件
|-main.js 入口js
|-permission.js 使用全局守卫实现路由权限控制的模块
|-settings.js 包含应用设置信息的模块
|-.env.development 指定了开发环境的代理服务器前缀路径
|-.env.production 指定了生产环境的代理服务器前缀路径
|-.eslintignore eslint的忽略配置
|-.eslintrc.js eslint的检查配置
|-.gitignore git的忽略配置
|-.npmrc 指定npm的淘宝镜像和sass的下载地址
|-babel.config.js babel的配置
|-jsconfig.json 用于vscode引入路径提示的配置
|-package.json 当前项目包信息
|-package-lock.json 当前项目依赖的第三方包的精确信息
|-vue.config.js webpack相关配置(如: 代理服务器)

1.4实现登陆&自动登陆&退出登陆

  1. 修改配置代理接口
  2. 修改响应码和请求头带token
  3. 修改前端转发给后端的路径
  4. 测试地址
  5. 不用指定 token 参数
  6. 修改登录页面
  7. 删除多余路由
vue.config.js
  • 注释掉mock接口配置
  • 配置代理转发请求到目标接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // before: require('./mock/mock-server.js')
    proxy: {
    '/dev-api': { // 匹配所有以 '/dev-api'开头的请求路径
    target: 'http://localhost:8800', // java后端
    changeOrigin: true, // 支持跨域
    pathRewrite: { // 重写路径: 去掉路径中开头的'/dev-api'
    '^/dev-api': ''
    }
    }
    }
src/utils/request.js

code
替换上图的if语句

1
2
3
4
5
6
7
8
const token = store.getters.token
if (token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
// config.headers['X-Token'] = getToken()
config.headers['token'] = token
}

替换掉20000
1
2
3
4
5
6
7
8
9
10
if (res.code !== 200) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}

src/api/user.js

直接替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import request from '@/utils/request'

export function login(data) {
return request({
url: '/admin/system/index/login',
method: 'post',
data
})
}

export function getInfo(token) {
return request({
url: '/admin/system/index/info',
method: 'get',
params: { token }
})
}

export function logout() {
return request({
url: '/admin/system/index/logout',
method: 'post'
})
}

创建indexController测试
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
55
56
57
58
59
package com.atguigu.controller;

import com.atguigu.result.Result;
import com.atguigu.system.SysRole;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Array;
import java.util.*;

@RestController
@RequestMapping("/admin/system/index/")
@Api(tags = "后台登录管理")
public class IndexController {

@ApiOperation("用户登录")
@PostMapping("/login")
public Result login(){
//{"code":200,"data":{"token":"admin-token"}}
Map map = new HashMap();
map.put("token","admin");
return Result.ok(map);
}

@ApiOperation("获取信息")
@GetMapping("/info")
public Result info(){
/**
* {
* "code": 200,
* "data": {
* "roles": [
* "admin"
* ],
* "introduction": "I am a super administrator",
* "avater": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
* "name": "Super Admin"
* }
* }
*/

Map map = new HashMap();
map.put("name","admin");
map.put("avater","https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
map.put("introduction","查询的角色信息");
// List<SysRole> sysRoleList = new ArrayList<>();
map.put("roles", Arrays.asList("角色1","角色2"));
return Result.ok(map);
}

@ApiOperation("用户退出")
@PostMapping("/logout")
public Result logout(){
return Result.ok();
}

}

  • 出现Cannot read properties of undefined (reading ‘cancelToken’)错误,登录不进去
    • 把src/utils/request.js重新导入后就好了
  • 图片加载不出来
  • 全部复制粘贴容易报错,可以一段的粘贴
src/store/modules/user.js

不用指定token参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getInfo({ commit }) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
const { data } = response

if (!data) {
return reject('Verification failed, please Login again.')
}

const { name, avatar } = data

commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
src/views/login/index.vue

更改页面标题

1
2
3
<div class="title-container">
<h3 class="title">硅谷通用权限系统</h3>
</div>

用户名检查只检查长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const validateUsername = (rule, value, callback) => {
if (value.length<5) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
src/router/index.js

删除多余路由

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
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},

{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},

{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},

// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]

2.角色类表

修改 src/router/index.js 文件

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
path: '/system',
component: Layout,
meta: {
title: '系统管理',
icon: 'el-icon-s-tools'
},
alwaysShow: true,
children: [
{
path: 'sysRole',
component: () => import('@/views/system/sysRole/list'),
meta: {
title: '角色管理',
icon: 'el-icon-user-solid'
},
}
]
},

code

这是因为没有创建vue视图
如果创建后还报错,重启服务

创建system/sysRole/list.vue文件
1
2
3
4
5
<template>
<div class="app-container">
角色列表
</div>
</template>

code

3.分页查询

  1. src/views/system/sysRole/list.vue

    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
    <template>
    <div class="app-container">
    <!--查询表单-->
    <div class="search-div">
    <el-form label-width="70px" size="small">
    <el-row>
    <el-col :span="24">
    <el-form-item label="角色名称">
    <el-input v-model="searchObj.roleName" style="width: 100%" placeholder="角色名称" />
    </el-form-item>
    </el-col>
    </el-row>
    <el-row style="display: flex">
    <el-button type="primary" icon="el-icon-search" size="mini" @click="fetchData()">
    搜索
    </el-button>
    <el-button icon="el-icon-refresh" size="mini" @click="resetData">
    重置
    </el-button>
    </el-row>
    </el-form>
    </div>
    <!-- 表格 -->
    <el-table v-loading="listLoading" :data="list" stripe border style="width: 100%; margin-top: 10px">
    <el-table-column label="序号" width="70" align="center">
    <template slot-scope="scope">
    {{ (page - 1) * limit + scope.$index + 1 }}
    </template>
    </el-table-column>

    <el-table-column prop="roleName" label="角色名称" />
    <el-table-column prop="roleCode" label="角色编码" />
    <el-table-column prop="createTime" label="创建时间" width="160" />
    <el-table-column label="操作" width="200" align="center">
    <template slot-scope="scope">
    <el-button type="primary" icon="el-icon-edit" size="mini" title="修改" @click="edit(scope.row.id)" />
    <el-button type="danger" icon="el-icon-delete" size="mini" title="删除" @click="removeDataById(scope.row.id)" />
    </template>
    </el-table-column>
    </el-table>
    <!-- 分页组件 -->
    <el-pagination
    :current-page="page"
    :total="total"
    :page-size="limit"
    style="padding: 30px 0; text-align: center"
    layout="total, prev, pager, next, jumper"
    @current-change="fetchData"
    />
    </div>
    </template>
    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
    <script>
    import api from '@/api/system/sysRole'
    export default {
    // 定义数据模型
    data() {
    return {
    listLoading: true, // 数据是否正在加载
    list: [], // 角色列表
    total: 0, // 总记录数
    page: 1, // 页码
    limit: 2, // 每页记录数
    searchObj: {} // 查询条件
    }
    },
    // 页面渲染之前获取数据
    created() {
    this.fetchData()
    },
    // 定义方法
    methods: {
    // 分页数据查询
    fetchData(currentPage = 1) {
    // currentPage <el-pagination> 分页的组件获取当前页面并把值传递到当前方法
    this.page = currentPage
    api.findPage(this.page, this.limit, this.searchObj).then(res => {
    this.listLoading = false
    this.list = res.data.records
    this.total = res.data.total
    })
    }
    }
    }
    </script>
  2. src/api/system/sysRole.js —-这个js是定义方法的,list.vue引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /*
    角色管理相关的API请求函数
    */
    import request from '@/utils/request'
    // java后台controller通用地址
    const api_name = '/admin/system/sysRole'

    // 自定义方法
    export default {
    // 分页查询 pageNum,PageSize,searchObj
    findPage(page, limit, searchObj) {
    // 发起异步请求
    return request({
    url: `${api_name}/${page}/${limit}`,
    method: 'get',
    params: searchObj
    })
    }
    }

  3. 日期格式化

    1
    2
    3
    4
    spring:
    jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  4. 效果
    code

4.角色删除

code

  1. methods方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    removeDataById(sysRoleId) {
    this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
    }).then(() => {
    api.removeDataById(sysRoleId).then(res => {
    if (res.code) {
    this.$message.success(res.message)
    // 重新查询
    this.fetchData(this.page)
    } else {
    this.$message.error(res.message)
    }
    })
    }).catch(() => {
    this.$message.info('以取消删除')
    })
    }
  2. sysRole.js方法

    1
    2
    3
    4
    5
    6
    removeDataById(sysRoleId) {
    return request({
    url: `${api_name}/remove/${sysRoleId}`,
    method: 'delete'
    })
    }

5.剩余功能及完整代码

sysRole.vue

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<template>
<div class="app-container">
<!--查询表单-->
<div class="search-div">
<el-form label-width="70px" size="small">
<el-row>
<el-col :span="24">
<el-form-item label="角色名称">
<el-input v-model="searchObj.roleName" style="width: 100%" placeholder="角色名称" />
</el-form-item>
</el-col>
</el-row>
<el-row style="display: flex">
<el-button type="primary" icon="el-icon-search" size="mini" @click="fetchData()">
搜索
</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetData">
重置
</el-button>
</el-row>
</el-form>
</div>
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add">添 加</el-button>
<el-button class="btn-add" size="mini" @click="batchRemove()">批量删除</el-button>
</div>
<!-- 表格 -->
<el-table v-loading="listLoading" :data="list" stripe border style="width: 100%; margin-top: 10px"
@selection-change="handleSelectionChange">
<el-table-column type="selection" />
<el-table-column label="序号" width="70" align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>

<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleCode" label="角色编码" />
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" title="修改" @click="edit(scope.row.id)" />
<el-button type="danger" icon="el-icon-delete" size="mini" title="删除" @click="removeDataById(scope.row.id)" />
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination :current-page="page" :total="total" :page-size="limit" style="padding: 30px 0; text-align: center"
layout="total, prev, pager, next, jumper" @current-change="fetchData" />
<!-- 添加或修改的表单 -->
<el-dialog title="添加/修改" :visible.sync="dialogVisible" width="40%">
<el-form ref="dataForm" :model="sysRole" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item label="角色名称">
<el-input v-model="sysRole.roleName" />
</el-form-item>
<el-form-item label="角色编码">
<el-input v-model="sysRole.roleCode" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysRole'
const defaultForm = {
id: '',
roleName: '',
roleCode: ''
}
export default {
// 定义数据模型
data() {
return {
listLoading: true, // 数据是否正在加载
list: [], // 角色列表
total: 0, // 总记录数
page: 1, // 页码
limit: 2, // 每页记录数
searchObj: {}, // 查询条件
dialogVisible: false, // 控制弹出层显示效果,默认隐藏
sysRole: defaultForm, // 获取角色对象
idList: []
}
},
// 页面渲染之前获取数据
created() {
this.fetchData()
},
// 定义方法
methods: {
// 分页数据查询
fetchData(currentPage = 1) {
// currentPage <el-pagination> 分页的组件获取当前页面并把值传递到当前方法
this.page = currentPage
api.findPage(this.page, this.limit, this.searchObj).then(res => {
this.listLoading = false
this.list = res.data.records
this.total = res.data.total
})
},
removeDataById(sysRoleId) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.removeDataById(sysRoleId).then(res => {
if (res.code) {
this.$message.success(res.message)
// 重新查询
this.fetchData(this.page)
} else {
this.$message.error(res.message)
}
})
}).catch(() => {
this.$message.info('以取消删除')
})
},
add() {
// 清空数据
this.resetData()
// 弹出添加的表单
this.dialogVisible = true
},
// 添加或更新
saveOrUpdate() {
this.dialogVisible = false // 隐藏表单
if (!this.sysRole.id) {
this.save()
} else {
this.update()
}
},
// 添加
save() {
api.save(this.sysRole).then(response => {
this.$message.success('添加成功')
// 隐藏弹出层
this.dialogVisible = false
// 重新查询数据
this.fetchData(this.page)
})
},
// 修改方法
update() {
api.update(this.sysRole).then(res => {
this.$message.success('修改成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 重置方法
resetData() {
this.sysRole = {}
this.idList = []
},
// 修改
edit(sysRoleId) {
// 弹出层
this.dialogVisible = true
api.getById(sysRoleId).then(res => {
this.sysRole = res.data
})
},
// 批量删除
batchRemove() {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
api.batchRemove(this.idList).then(res => {
this.$message.success('删除成功')
this.fetchData(this.page)
this.resetData()
})
}).catch(() => {
this.$message.info('以取消删除')
})
},
// 批量选取
handleSelectionChange(selection) {
this.resetData()
selection.forEach(sysRole => {
var sysRoleId = sysRole.id
this.idList.push(sysRoleId)
})
}

}
}
</script>


sysRole.js
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
55
56
57
58
59
60
61
62
63
/*
角色管理相关的API请求函数
*/
import request from '@/utils/request'
// java后台controller通用地址
const api_name = '/admin/system/sysRole'

// 自定义方法
export default {
// 分页查询 pageNum,PageSize,searchObj
findPage(page, limit, searchObj) {
// 发起异步请求
return request({
url: `${api_name}/${page}/${limit}`,
method: 'get',
params: searchObj
})
},

// 单行删除
removeDataById(sysRoleId) {
return request({
url: `${api_name}/remove/${sysRoleId}`,
method: 'delete'
})
},

// 添加操作
save(sysRole) {
return request({
url: `${api_name}/save`,
method: 'post',
data: sysRole
})
},

// 获取
getById(sysRoleId) {
return request({
url: `${api_name}/get/${sysRoleId}`,
method: 'get'
})
},

// 修改
update(sysRole) {
return request({
url: `${api_name}/update/`,
method: 'put',
data: sysRole
})
},

// 批量删除方法
batchRemove(idList) {
return request({
url: `${api_name}/batchRemove`,
method: 'delete',
data: idList
})
}
}