2-21-案例-后台管理系统

效果展示

创建项目

在命令行中输入

1
vue create demo-management

勾选Less,其他默认,这样一个项目就创建出来了

制作登录页面

首先删除App.vue中的大部分代码,只留下一个router-view组件,用于后续页面的渲染。

接着将Bootstrap相关样式文件复制过来,放在assets/css路径下面。

另外创建一个index.css,用于设置全局样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:root {

font-size: 13px;

}

#app,

html,

body {

height: 100%;

}

这里如果没有设置height100%,后续登录页面深蓝色的背景颜色就显示不出来。

创建MyLogin.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
200
201
202
203
204
205
206
207
208
209
<template>

<div class="login-container">

<div class="login-box">

//
text-center会让img居中,再配合avatar-box的width100%,图片就会在login-box的中间

<div class="text-center avatar-box">

// img-thumbnail会让图片有一圈白色的内边框

<img src="../../assets/logo.png" class="avatar img-thumbnail"/>

</div>

//p-4代表padding:
4rem,其中rem是在:root节点的font-size基础上增加倍数,4rem就是4倍font-size。这样form-login跟底部就会有13X4px的距离

<div class="form-login p-4">

// form-group相当于mb-3,form-inline是通过flex让子组件在同一行内居中

<div class="form-group form-inline ">

<label for="username">用户名</label>

// form-control是Bootstrap特有的输入控件样式。

<input type="text" class="form-control ml-2" id="username"
placeholder="请输入用户名" autocomplete="off"
v-model.trim="username"/>

</div>

<div class="form-group form-inline">

<label for="pwd">密码</label>

<input type="password" class="form-control ml-5" id="pwd"
placeholder="请输入密码" v-model.trim="pwd" />

</div>

// d-flex代表display: flex; justify-content-end会让组件靠右排列。

<div class="form-group form-inline d-flex justify-content-end">

<button class="btn btn-secondary mr-2"
@click="reset">重置</button>

<button class="btn btn-primary" @click="login">登录</button>

</div>

</div>

</div>

</div>

</template>

<script>

export default {

name: "Login",

data() {

return {

username: "",

pwd: ""

}

},

methods: {

reset() {

this.username = "";

this.pwd = "";

},

login() {

if(this.username === "") {

alert('用户名不能为空');

return;

}

if(this.pwd === "") {

alert('密码不能为空');

return;

}

if(this.username === "admin" && this.pwd == "666") {

localStorage.setItem('token', 'Bearer XXX');

this.$router.push('/home');

return;

}

this.reset();

alert('用户名或密码错误!');

}

}

}

</script>

<style lang="less" scoped>

.login-container {

background-color: #35495e;

height=100%;

.login-box {

display: absolute;

left: 50%;

top: 50%;

transform: translate(-50%, -50%);

width: 400px;

height: 250px;

background-color: #fff;

border-radius: 3px;

box-shadow: 0 0 6px rgba(255, 255, 255, .5);

// login-box从底部向上排列

.login-box {

position: absolute;

left: 0;

bottom: 0;

width: 100%;

}

}

}

.form-control {

flex: 1; //让输入控件拉长占住剩余空间

}

.avatar-box {

position: absolute;

top: -65px;

left: 0;

width: 100%;

.avatar {

width: 120px;

height: 120px;

border-radius: 50% !important;

box-shadow: 0 0 6px #efefef;

}

}

</style>

最后在制作路由

在命令行中输入

1
npm i vue-router -S

安装路由器

新建文件夹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
29
30
31
32
33
34
35
import Vue from 'vue';

import VueRouter from 'vue-router';

import Login from '@/components/MyLogin.vue'

Vue.use(VueRouter);

const router = new VueRouter({

routes: [

{ path: '/login', component: Login }

]

});

export default router;

在main.js中引入Bootstrap和index样式,并注册router

import router from './router';

import './assets/css/bootstrap.css';

import './assets/css/index.css';

new Vue({

render: h => h(App),

router

});

制作主页面

增加MyHome.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
<template>

<div class="home-container">

<Header></Header>

<div class="home-main-box">

<Aside></Aside>

<div class="home-main-body">

<router-view></router-view>

</div>

</div>

</div>

</template>

<script>

import Header from '@/components/subcomponents/MyHeader.vue';

import Aside from '@/components/subcomponents/MyAside.vue';

export default {

name: "Home",

components: [ Header, Aside ]

}

</script>

<style lang="less" scoped>

.home-container {

display: flex;
direction: column; //垂直排列

height: 100%;

.home-main-box {

display: flex;

height: 100%;

.home-main-body {

flex: 1;

padding: 15px;

}

}

}

</style>

注册路由

1
2
3
4
5
6
7
import Home from '@/components/MyHome.vue';

......

{ path: '/', redirect: '/home' },

{ path: '/home', component: Home }

制作Header

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
<template>

// align-items-center是让元素在超级放大时保持垂直居中

<div class="header-container d-flex justify-content-between
align-items-center p-3">

// user-select-none让文本无法被框选

<div class="layout-header-left d-flex align-items-center
user-select-none">

<img src="../../assets/heima.png" />

<h4 class="m-3">黑马后台管理系统</h4>

</div>

<div class="layout-header-right">

<button class="btn btn-light" @click="logout">退出登录</button>

</div>

</div>

</template>

<script>

export default {

name: "Header",

methods: {

logout() {

localStorage.removeItem('token');

this.$router.push('/login');

}

}

}

</script>

<style>

.header-container {

border: 1px solid #eaeaea;

width: 100%;

height: 60px;

.layout-header-left img {

height: 50px;

}

}

</style>

制作菜单Aside

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
<template>

<div class="aside-container">

<ul class="user-select-none menu">

<li v-for="item in menuList" :key="item.id" class="menu-item">

<router-link :to="item.pah">{{ item.name }}</router-link>

</li>

</ul>

</div>

</template>

<script>

export default {

name: "Aside",

data() {

return {

menuList: [

{ id: 1, name: '用户管理', path: '/home/users'},

{ id: 1, name: '权限管理', path: '/home/rights'},

{ id: 1, name: '商品管理', path: '/home/goods'},

{ id: 1, name: '订单管理', path: '/home/orders'},

{ id: 1, name: '系统设置', path: '/home/settings'}

]

}

}

}

</script>

<style>

.aside-container {

height: 100%;

width: 250px;

border-right: 1px solid #eaeaea;

.menu {

list-style-type: none;

padding: 0;

.menu-item {

line-height: 50px;

font-weight: bold;

font-size: 14px;

font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue',
sans-serif;

&:hover {

background-color: #efefef;

cursor: pointer;

}

a {

color: black;

display: block;

padding-left: 30px;

&:hover {

text-decoration: none;

}

}

}

}

}

//
router-link-active是router-link元素特有的选中状态class,可以用来实现选中后的效果。

.router-link-active {

background-color: #efefef;

box-sizing: border-box;

position: relative;

&::before {

content: ''; // 如果没有content,绿色小长条会显示不出来。

display: block;

position: absolute; //绝对定位让绿色小长条能够盖在背景上面

left: 0;

top: 0;

width: 4px;

height: 100%;

background-color: #42b983;

}

}

</style>

制作内容页面

新增menu/MyUser.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
<template>

<div class="user-container">

<h4 class="text-center">用户管理</h4>

<table class="table table-striped table-bordered table-hover">

<thead>

<tr>

<th>#</th>

<th>姓名</th>

<th>年龄</th>

<th>地区</th>

<th>操作</th>

</tr>

</thead>

<tbody>

<tr v-for="item in userList" :key="item.id">

<td>{{ item.id }}</td>

<td>{{ item.name }}</td>

<td>{{ item.age }}</td>

<td>{{ item.position }}</td>

<td>

<a href="#" @click.prevent="gotoDetail(item.id)">详情</a>

</td>

</tr>

</tbody>

</table>

</div>

</template>

<script>

export default {

name: "User",

data() {

return {

userList: [

{ id: 1, name: '章三', age: 12, position: '北京' },

{ id: 2, name: '李四', age: 22, position: '上海' },

{ id: 3, name: '王五', age: 32, position: '广州' },

{ id: 4, name: '赵六', age: 42, position: '深圳' },

]

}

},

methods: {

gotoDetail(id) {

this.$router.push('/home/userinfo/' + id);

}

}

}

</script>

<style>

</style>

新增详情页UserDetail.vue

<template>

<div class="userdetail-container">

<button class="btn btn-light btn-sm"
@click="$router.push('/home/users')">返回</button>

<h4 class="text-center">用户详情------{{ id }}</h4>

</div>

</template>

<script>

export default {

name: 'UserDetail',

props: ['id']

}

</script>

<style>

</style>

注册路由

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
import Users from '@/component/menu/MyUsers.vue';

import Rights from '@/component/menu/MyRights.vue';

import Orders from '@/component/menu/MyOrders.vue';

import Goods from '@/component/menu/MyGoods.vue';

import Settings from '@/component/menu/MySettings.vue';

import UserDetail from '@/component/user/MyUserDetail.vue';

......

{ path: '/home', component: Home,

redirect: '/home/users',

children: [

{ path: 'users', component: Users },

{ path: 'rights', component: Rights },

{ path: 'orders', component: Orders },

{ path: 'goods', component: Goods },

{ path: 'settings', component: Settings },

{ path: 'userinfo/:id', component: UserDetail, props: true }

]

}

增加导航守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
router.beforeEach(function(to, from, next) {

if (!to.path.startsWith('/home') || localStorage.getItem('token'))
{

next();

return;

}

next('/login');

});

参考:

Vue2.0-19.案例 -
安装和配置路由

Vue2.0-20.案例 -
基于路由渲染登录组件

Vue2.0-21.案例 -
模拟登录功能

Vue2.0-22.案例 -
说明Token认证时token的格式

Vue2.0-23.案例 -
实现后台主页的基础布局

Vue2.0-24.案例 -
退出登录并访问控制权限

Vue2.0-25.案例 -
实现子路由的嵌套展示

Vue2.0-26.案例 -
点击进入用户详情页

Vue2.0-27.案例 -
升级用户详情页的路由规则

Vue2.0-28.案例 -
路由Path的注意点

Vue2.0-29.案例 - 拓展 -
如何控制页面的权限


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!