危险

为之则易,不为则难

0%

09_人资后台

✔ 掌握权限控制的整体思路。

✔ 掌握权限控制碰到的问题及解决。

权限受控的主体思路

我们已经做到了给用户分配角色,给角色分配权限,这个权限怎么和我们的路由以及左侧菜单结合起来呢(如何应用/生效)?

在权限管理页面中,每个路由都有一个标识,如果用户拥有这个标识,那么用户就可以访问这个路由模块,如果没有这个标识,就不能访问路由模块。

用什么来实现呢?

vue-router 提供了一个叫做 addRoutes 的 API 方法,这个方法的含义是动态添加路由规则,思路如下。

image-20200901164312005

拆分静态和动态路由

  1. 从 constantRoutes 中删除动态路由 asyncRoutes,src/router/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export const asyncRoutes = [
departmentRouter,
roleRouter,
employeeRouter,
permissionRouter,
attendanceRouter,
approvalRouter,
salaryRouter,
socialRouter
]
export const constantRoutes = [
// ...
// ...asyncRoutes,
// 404 page must be placed at the end !!!
{
path: '*',
redirect: '/404',
hidden: true
}
]
  1. 先通过 addRoutes 添加所有的动态路由测试一下,permission.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
// #1
import {
asyncRoutes
} from '@/router'

router.beforeEach(async function(to, from, next) {
// 开启进度条
NProgress.start()
// 如果有 token
if (store.getters.token) {
// 继续判断是不是去登录页
if (to.path === '/login') {
// 表示去的是登录页,则跳到主页
next('/')
} else {
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo')
// 先动态添加所有的试试
// #2
router.addRoutes(asyncRoutes)
}
// 否则直接放行
next()
}
}
// ...
})
  1. 测试:在地址栏输入对应的路由地址试试,例如 http://localhost:8888/#/permissions

  2. 问题:页面是能出来,刷新的时候为什么还是 404 了?

处理 404 问题

  1. 删除 router/index.js 中的 { path: '*', redirect: '/404', hidden: true } 路由配置项。

  2. permission.js

1
2
3
4
5
router.addRoutes([...asyncRoutes, {
path: '*',
redirect: '/404',
hidden: true
}])

问题:刷新的时候白屏了?

处理白屏问题

通过 addRoutes 动态新增的路由是异步的,不会即可生效,为了保证生效,需要让用户重新进一下首页(重新走一下 beforeEach 的逻辑)。

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
import {
asyncRoutes
} from '@/router'

router.beforeEach(async function(to, from, next) {
// 开启进度条
NProgress.start()
// 如果有 token
if (store.getters.token) {
// 继续判断是不是去登录页
if (to.path === '/login') {
// 表示去的是登录页,则跳到主页
next('/')
} else {
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo')
// 先动态添加所有的试试
router.addRoutes([...asyncRoutes, {
path: '*',
redirect: '/404',
hidden: true
}])
// 怎么来的,就怎么重新进一次
// #1
next({
// path: '/'
// path: to.path
...to,
// 避免重新进页面历史重复的问题
replace: true,
})
// #2
return
}
// 否则直接放行
next()
}
}
})

筛选路由逻辑

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
// ...
router.beforeEach(async function(to, from, next) {
// 开启进度条
NProgress.start()
// 如果有 token
if (store.getters.token) {
// 继续判断是不是去登录页
if (to.path === '/login') {
// 表示去的是登录页,则跳到主页
next('/')
} else {
// 否则直接放行
if (!store.getters.userId) {
// #1
const {
roles: {
menus
}
} = await store.dispatch('user/getUserInfo')
// #2
const filterRoutes = asyncRoutes.filter(item => menus.includes(item.children[0].name))
// #3
router.addRoutes([
...filterRoutes,
{
path: '*',
redirect: '/404',
hidden: true
}
])
next({
...to,
replace: true
})
return
}
next()
}
} else {
// 如果没有 token
if (whiteList.indexOf(to.path) > -1) {
// 在白名单,也放行
next()
} else {
// 否则拦截到登录页
next('/login')
}
}
// 注意:如果在路由全局前置导航守卫中,通过 next 拦截到了其他页面,后置守卫不会被触发
NProgress.done()
})

在地址栏手动输入筛选后的路由地址进行测试。

🤔 格外注意:需要把 router/modules 中每一个路由文件的 name 和后端返回的 menus 保持一致。

侧边栏的问题

  1. 问题:router.options.routes 只会拿到初始化时的 routes 规则,后续动态新增的拿不到。

  2. 思路:把动态路由也存储到 Vuex 一份,store/modules/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
25
26
27
// #1
import {
constantRoutes
} from '@/router'
export default {
namespaced: true,
state: {
token: getToken(),
userInfo: {},
// #2
routes: constantRoutes || []
},
mutations: {
// ...
// #3
setRoutes(state, filterRoutes) {
// 注意这儿,每次应该是在初始的 constantRoutes 基础上添加,最好不要使用 push,防止切换用户账号后累加的问题
// 为了 vuex 中的路由信息和 router 实例中的路由信息保持统一,最后最好再加一个 404(不加也不影响功能)
state.routes = [...constantRoutes, ...filterRoutes, {
path: '*',
redirect: '/404',
hidden: true
}]
}
},
// ...
}
  1. permission.js
1
store.commit('user/setRoutes', filterRoutes)
  1. 建立获取 Vuex 中路由的快捷访问,store/getters.js
1
2
3
4
5
const getters = {
// ...
routes: (state) => state.user.routes,
}
export default getters
  1. 通过 mapGetters 取出 routes,layout/components/Sidebar/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
components: {
SidebarItem,
Logo
},
computed: {
// #1
...mapGetters(['sidebar', 'routes']),
// #2
/* routes() {
return this.$router.options.routes
}, */
},
}
</script>
  1. 给另外一个账号分配权限测试。

员工 => 给“乔海”分配角色 => 角色 => 给角色分配权限 => 重新登录“乔海”的账号查看是否生效。

🤔 注意:后端对管理员的角色做了特殊处理,永远具有所有权限,所以请用其他账号测试。

  1. 新问题:通过浏览器地址栏手动输入的时候,发现竟能访问侧边栏没有的路由。

原因:因为我们前面在 addRoutes 的时候,一直都是往 router 实例添加,乔海受到了前面管理员的影响。

退出重置路由

目标

当多个账号相互切换时,处理 router 相互影响的问题。

内容

  1. 使用项目提供好的路由重置方法,router/index.js
1
2
3
4
5
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
  1. 重置路由实例和 Vuex 中的路由数据,store/modules/user.js
1
2
3
4
5
6
7
8
9
10
11
12
logout(context) {
// 删除 token(包含 Vuex 和 Cookie 中的)
context.commit('removeToken')
// 删除用户资料
context.commit('removeUserInfo')
// 重置路由实例
resetRouter()
// 子模块调用子模块的action 可以 将 commit的第三个参数 设置成 { root: true } 就表示当前的context不是子模块了 而是父模块
context.commit('user/setRoutes', [], {
root: true
})
}

功能权限应用

  1. 封装自定义指令, src/main.js
1
2
3
4
5
6
7
8
9
10
11
Vue.directive('permission', {
// 会在指令作用的元素插入到页面完成以后触发
inserted(el, binding) {
const points = store.state.user.userInfo?.roles?.points || []
if (!points.includes(binding.value)) {
el.remove()
// el.disabled = true
// el.className += ' is-disabled'
}
}
})
  1. 应用自定义指令,src/views/employee/index.vue
1
<el-button v-permission="'add-employee'" size="mini" type="primary" @click="$router.push('/employee/detail')">添加员工</el-button>

首页-基本结构和数字滚动

  1. 安装数字滚动插件,vue-count-to
1
npm i vue-count-to
  1. 基本结构, src/views/dashboard/index.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
<template>
<div class="dashboard">
<div class="container">
<!-- 左侧内容 -->
<div class="left">
<div class="panel">
<!-- 个人信息 -->
<div class="user-info">
<img class="avatar" src="../../assets/common/defaultHead.png" alt="">
<div class="company-info">
<div class="title">
江苏传智播客教育科技股份有限公司
<span>体验版</span>
</div>
<div class="depart">庆山 | 传智播客-总裁办</div>
</div>
</div>
<!-- 代办 -->
<div class="todo-list">
<div class="todo-item">
<span>组织总人数</span>
<!-- 起始值 终点值 滚动时间 -->
<span>228</span>
</div>
<div class="todo-item">
<span>正式员工</span>
<span>334</span>
</div>
<div class="todo-item">
<span>合同待签署</span>
<span>345</span>
</div>
<div class="todo-item">
<span>待入职</span>
<span>890</span>
</div>
<div class="todo-item">
<span>本月待转正</span>
<span>117</span>
</div>
<div class="todo-item">
<span>本月待离职</span>
<span>234</span>
</div>
<div class="todo-item">
<span>接口总访问</span>
<span>789</span>
</div>
</div>
</div>
<!-- 快捷入口 -->
<div class="panel">
<div class="panel-title">快捷入口</div>
<div class="quick-entry">
<div class="entry-item">
<div class="entry-icon approval" />
<span>假期审批</span>
</div>
<div class="entry-item">
<div class="entry-icon social" />
<span>社保管理</span>
</div>
<div class="entry-item">
<div class="entry-icon role" />
<span>角色管理</span>
</div>
<div class="entry-item">
<div class="entry-icon salary" />
<span>薪资设置</span>
</div>
<div class="entry-item">
<div class="entry-icon bpm" />
<span>流程设置</span>
</div>
</div>
</div>
<!-- 图表数据 -->
<div class="panel">
<div class="panel-title">社保申报数据</div>
<div class="chart-container">
<div class="chart-info">
<div class="info-main">
<span>申报人数</span>
<span>223</span>
</div>
<div class="info-list">
<div class="info-list-item">
<span>待申报(人)</span>
<span>117</span>
</div>
<div class="info-list-item">
<span>申报中(人)</span>
<span>167</span>
</div>
<div class="info-list-item">
<span>已申报(人)</span>
<span>24</span>
</div>
</div>
</div>
<div class="chart">
<!-- 图表 -->
</div>
</div>
</div>
<!-- 图表数据 -->
<div class="panel">
<div class="panel-title">公积金申报数据</div>
<div class="chart-container">
<div class="chart-info">
<div class="info-main">
<span>申报人数</span>
<span>335</span>
</div>
<div class="info-list">
<div class="info-list-item">
<span>待申报(人)</span>
<span>345</span>
</div>
<div class="info-list-item">
<span>申报中(人)</span>
<span>109</span>
</div>
<div class="info-list-item">
<span>已申报(人)</span>
<span>77</span>
</div>
</div>
</div>
<div class="chart">
<!-- 图表 -->
</div>
</div>
</div>
</div>
<!-- 右侧内容 -->
<div class="right">
<!-- 帮助链接 -->
<div class="panel">
<div class="help">
<div class="help-left">
<div class="panel-title">帮助链接</div>
<div class="help-list">
<div class="help-block">
<i class="icon-entry" />
入门指南
</div>
<div class="help-block">
<i class="icon-help" />
在线帮助手册
</div>
<div class="help-block">
<i class="icon-support" />
联系技术支持
</div>
<div class="help-block">
<i class="icon-add" />
添加链接
</div>
</div>
</div>
<div class="help-right">
<div class="calendar">
<!-- <el-calendar /> -->
<el-calendar />
</div>
</div>
</div>
</div>
<!-- 通知公告 -->
<div class="panel">
<div class="panel-title">通知公告</div>
<div class="information-list">
<div class="information-list-item">
<img src="@/assets/common/img.jpeg" alt="">
<div>
<p>
<span class="col">朱继柳</span> 发布了
第1期“传智大讲堂”互动讨论获奖名单公布
</p>
<p>2018-07-21 15:21:38</p>
</div>
</div>
<div class="information-list-item">
<img src="@/assets/common/img.jpeg" alt="">
<div>
<p>
<span class="col">朱继柳</span> 发布了
第1期“传智大讲堂”互动讨论获奖名单公布
</p>
<p>2018-07-21 15:21:38</p>
</div>
</div>
<div class="information-list-item">
<img src="@/assets/common/img.jpeg" alt="">
<div>
<p>
<span class="col">朱继柳</span> 发布了
第1期“传智大讲堂”互动讨论获奖名单公布
</p>
<p>2018-07-21 15:21:38</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import CountTo from 'vue-count-to'
export default {
components: {
CountTo
}
}
</script>

<style scoped lang="scss">
.dashboard {
background: #f5f6f8;
width: 100%;
min-height: calc(100vh - 80px);

::v-deep .el-calendar-day {
height: 40px;
}

::v-deep .el-calendar-table__row td,
::v-deep .el-calendar-table tr td:first-child,
::v-deep .el-calendar-table__row td.prev {
border: none;
}

.date-content {
height: 40px;
text-align: center;
line-height: 40px;
font-size: 14px;
}

.date-content .rest {
color: #fff;
border-radius: 50%;
background: rgb(250, 124, 77);
width: 20px;
height: 20px;
line-height: 20px;
display: inline-block;
font-size: 12px;
margin-left: 10px;
}

.date-content .text {
width: 20px;
height: 20px;
line-height: 20px;
display: inline-block;

}

::v-deep .el-calendar-table td.is-selected .text {
background: #409eff;
color: #fff;
border-radius: 50%;
}

::v-deep .el-calendar__header {
display: none
}

.container {
display: flex;

.right {
width: 40%;

.panel {
margin-left: 8px;
}

:nth-child(1) {
margin-top: 0;
}
}

.left {
flex: 1;

:nth-child(1) {
margin-top: 0;
}
}

.panel {
background-color: #fff;

margin-top: 8px;
padding: 20px;

.panel-title {
font-size: 16px;
color: #383c4e;
font-weight: 500;
}

// 用户信息样式
.user-info {
display: flex;

.avatar {
width: 48px;
height: 48px;
border-radius: 12px;
background-color: #d9d9d9;
line-height: 48px;
text-align: center;
}

.username {
width: 30px;
height: 30px;
text-align: center;
line-height: 30px;
border-radius: 50%;
background: #04c9be;
color: #fff;
margin-right: 4px;
}

.company-info {
margin-left: 10px;
height: 48px;
display: flex;
flex-direction: column;
justify-content: space-around;

.title {
color: #383c4e;
font-weight: 500;
font-size: 16px;
font-family: PingFang SC, PingFang SC-Medium;

span {
font-size: 12px;
background: #f5f6f8;
text-align: center;
padding: 2px 8px;
border-radius: 2px;
color: #697086;
}
}

.depart {
font-size: 14px;
color: #697086;
font-weight: 400;
}
}
}

// 代办样式
.todo-list {
margin-top: 10px;
display: flex;
flex-wrap: wrap;

.todo-item {
width: 18%;
height: 90px;
display: flex;
flex-direction: column;
padding: 10px;
justify-content: space-around;

:nth-child(1) {
color: #697086;
font-size: 14px;
}

:nth-child(2) {
color: #383c4e;
font-size: 30px;
font-weight: 500;
}
}
}

// 快捷入口
.quick-entry {
margin-top: 16px;
display: flex;

.entry-item {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 60px;

&:nth-child(1) {
margin-left: 0px;
}

.entry-icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: #f5f6f8;
background-size: cover;

&.approval {
background-image: url('~@/assets/common/approval.png');
}

&.social {
background-image: url('~@/assets/common/social.png');
}

&.salary {
background-image: url('~@/assets/common/salary.png');
}

&.role {
background-image: url('~@/assets/common/role.png');
}

&.bpm {
background-image: url('~@/assets/common/bpm.png');
}
}

span {
color: #697086;
font-size: 14px;
margin-top: 8px;
}
}
}

// 图表数据
.chart-container {
display: flex;

.chart-info {
width: 240px;
margin-top: 10px;

.info-main {
padding: 10px;
display: flex;
flex-direction: column;

:nth-child(1) {
font-size: 14px;
color: #697086;
}

:nth-child(2) {
margin-top: 10px;
font-size: 30px;
color: #04c9be;
font-weight: 500;
}
}

.info-list {
background: #f5f6f8;
border-radius: 4px;
padding: 12px 15px;
display: flex;
flex-wrap: wrap;
align-items: center;

.info-list-item {
width: 50%;
margin-top: 10px;
display: flex;
flex-direction: column;

:nth-child(1) {
font-size: 14px;
color: #697086;
}

:nth-child(2) {
margin-top: 10px;
font-size: 30px;
color: #383c4e;
font-weight: 500;
}
}
}
}

.chart {
flex: 1
}
}

// 帮助链接
.help {
display: flex;

.help-left {
width: 40%;
}

.help-right {
flex: 1;
}

.help-list {
.help-block {
background: #f5f6f8;
border-radius: 4px;
width: 264px;
height: 54px;
padding: 17px 10px;
font-size: 14px;
color: #697086;
margin-top: 10px;

i {
width: 14px;
height: 14px;
display: inline-block;
background-size: cover;
vertical-align: middle;
}

i.icon-help {
background-image: url("~@/assets/common/help.png");
}

i.icon-support {
background-image: url("~@/assets/common/support.png");
}

i.icon-add {
background-image: url("~@/assets/common/add.png");
}

i.icon-entry {
background-image: url("~@/assets/common/entry.png");
}
}
}
}

// 通知公告
.information-list {
margin-top: 20px;

.information-list-item {
display: flex;
align-items: center;
margin: 15px 0;

img {
width: 40px;
height: 40px;
border: 50%;
}

.col {
color: #8a97f8;
}

div :nth-child(2) {
color: #697086;
font-size: 14px;
}
}
}
}
}
}
</style>
  1. 首页基本结构, src/views/dashboard/index.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
<div class="todo-list">
<div class="todo-item">
<span>组织总人数</span>
<!-- 起始值 终点值 动画时间 -->
<count-to :start-val="0" :end-val="228" :duration="1000" />
</div>
<div class="todo-item">
<span>正式员工</span>
<count-to :start-val="0" :end-val="334" :duration="1000" />
</div>
<div class="todo-item">
<span>合同待签署</span>
<count-to :start-val="0" :end-val="345" :duration="1000" />
</div>
<div class="todo-item">
<span>待入职</span>
<count-to :start-val="0" :end-val="890" :duration="1000" />
</div>
<div class="todo-item">
<span>本月待转正</span>
<count-to :start-val="0" :end-val="117" :duration="1000" />
</div>
<div class="todo-item">
<span>本月待离职</span>
<count-to :start-val="0" :end-val="234" :duration="1000" />
</div>
<div class="todo-item">
<span>接口总访问</span>
<count-to :start-val="0" :end-val="789" :duration="1000" />
</div>
</div>

首页-个人信息展示

  1. 在 getters 中开放公司名称和所在部门属性, src/store/getters.js
1
2
3
4
5
6
const getters = {
// ...
company: state => state.user.userInfo.company, // 公司名称
departmentName: state => state.user.userInfo.departmentName // 部门名称
}
export default getters
  1. 在首页将个人信息的数据替换成 Vuex 中的数据, src/views/dashboard/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="depart">{{ name }} | {{ company }}-{{ departmentName }}</div>
</template>

<script>
import CountTo from 'vue-count-to'
import {
mapGetters
} from 'vuex'
export default {
components: {
CountTo
},
// 计算属性
computed: {
...mapGetters(['name', 'avatar', 'company', 'departmentName']) // 映射给了计算属性
}
}
</script>

首页-企业数据获取

  1. 封装获取数据的 API, src/api/home.js
1
2
3
4
5
6
7
8
9
10
import request from '@/utils/request'

/**
* 获取首页数据
*/
export function getHomeData() {
return request({
url: '/home/data'
})
}
  1. 初始化时获取数据,并替换企业数据, src/views/dashboard/index.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
<template>
<div class="dashboard">
<div class="container">
<!-- 左侧内容 -->
<div class="left">
<div class="panel">
<!-- 个人信息 -->
<!-- ... -->
<!-- 代办 -->
<div class="todo-list">
<div class="todo-item">
<span>组织总人数</span>
<!-- 起始值 终点值 动画时间 -->
<count-to :start-val="0" :end-val="homeData.employeeTotal" :duration="1000" />
</div>
<div class="todo-item">
<span>正式员工</span>
<count-to :start-val="0" :end-val="homeData.regularEmployeeTotal" :duration="1000" />
</div>
<div class="todo-item">
<span>合同待签署</span>
<count-to :start-val="0" :end-val="homeData.contractSignTotal" :duration="1000" />
</div>
<div class="todo-item">
<span>待入职</span>
<count-to :start-val="0" :end-val="homeData.toBeEmployed" :duration="1000" />
</div>
<div class="todo-item">
<span>本月待转正</span>
<count-to :start-val="0" :end-val="homeData.toBeConfirmed" :duration="1000" />
</div>
<div class="todo-item">
<span>本月待离职</span>
<count-to :start-val="0" :end-val="homeData.toBeDismissed" :duration="1000" />
</div>
<div class="todo-item">
<span>接口总访问</span>
<count-to :start-val="0" :end-val="homeData.interfaceAccessTotal" :duration="1000" />
</div>
</div>
</div>
<!-- 快捷入口 -->
<!-- ... -->
<!-- 社保申报数据 -->
<div class="panel">
<div class="panel-title">社保申报数据</div>
<div class="chart-container">
<div class="chart-info">
<div class="info-main">
<span>申报人数</span>
<!-- homeData: {} -->
<count-to :start-val="0" :end-val="homeData.socialInsurance?.declarationTotal" :duration="1000" />

</div>
<div class="info-list">
<div class="info-list-item">
<span>待申报(人)</span>
<count-to :start-val="0" :end-val="homeData.socialInsurance?.toDeclareTotal" :duration="1000" />
</div>
<div class="info-list-item">
<span>申报中(人)</span>
<count-to :start-val="0" :end-val="homeData.socialInsurance?.declaringTotal" :duration="1000" />
</div>
<div class="info-list-item">
<span>已申报(人)</span>
<count-to :start-val="0" :end-val="homeData.socialInsurance?.declaredTotal" :duration="1000" />
</div>
</div>
</div>
<div class="chart">
<!-- 图表 -->
</div>
</div>
</div>
<!-- 公积金申报数据 -->
<div class="panel">
<div class="panel-title">公积金申报数据</div>
<div class="chart-container">
<div class="chart-info">
<div class="info-main">
<span>申报人数</span>
<count-to :start-val="0" :end-val="homeData.providentFund?.declarationTotal" :duration="1000" />
</div>
<div class="info-list">
<div class="info-list-item">
<span>待申报(人)</span>
<count-to :start-val="0" :end-val="homeData.providentFund?.toDeclareTotal" :duration="1000" />
</div>
<div class="info-list-item">
<span>申报中(人)</span>
<count-to :start-val="0" :end-val="homeData.providentFund?.declaringTotal" :duration="1000" />
</div>
<div class="info-list-item">
<span>已申报(人)</span>
<count-to :start-val="0" :end-val="homeData.providentFund?.declaredTotal" :duration="1000" />
</div>
</div>
</div>
<div class="chart">
<!-- 图表 -->
</div>
</div>
</div>
</div>
<!-- 右侧内容 -->
<!-- ... -->
</div>
</div>
</template>

<script>
import CountTo from 'vue-count-to'
import {
mapGetters
} from 'vuex'
import {
getHomeData
} from '@/api/home'
export default {
name: 'Dashboard',
components: {
CountTo
},
data() {
return {
homeData: {} // 存放首页数据的对象
}
},
// 计算属性
computed: {
...mapGetters(['name', 'avatar', 'company', 'departmentName']) // 映射给了计算属性
},
created() {
this.getHomeData()
},
methods: {
async getHomeData() {
this.homeData = await getHomeData()
}
}
}
</script>

首页-通知消息获取

  1. 封装获取消息的 API, src/api/home.js
1
2
3
4
5
export function getMessageList() {
return request({
url: '/home/notice'
})
}
  1. 初始化时获取消息列表, src/views/dashboard/index.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
<template>
<div class="dashboard">
<div class="container">
<!-- 左侧内容 -->
<!-- ... -->
<!-- 右侧内容 -->
<div class="right">
<!-- 帮助链接 -->
<!-- ... -->
<!-- 通知公告 -->
<div class="panel">
<div class="panel-title">通知公告</div>
<div class="information-list">
<div v-for="(item,index) in list" :key="index" class="information-list-item">
<img :src="item.icon" alt="">
<div>
<p>
{{ item.notice }}
</p>
<p>{{ item.createTime }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import CountTo from 'vue-count-to'
import {
mapGetters
} from 'vuex'
// #2
import {
getHomeData,
getMessageList
} from '@/api/home'
export default {
name: 'Dashboard',
components: {
CountTo
},
data() {
return {
homeData: {}, // 存放首页数据的对象
// #1
list: []
}
},
// 计算属性
computed: {
...mapGetters(['name', 'avatar', 'company', 'departmentName']) // 映射给了计算属性
},
created() {
this.getHomeData()
// #3
this.getMessageList()
},
methods: {
async getHomeData() {
this.homeData = await getHomeData()
},
// #4
async getMessageList() {
this.list = await getMessageList()
}
}
}
</script>