项目实战 (二)
一.登录权限
1.用户注册模块实现
API
接口定制
后端接口:http://localhost:3000/public/reg
export default {
reg:'/user/reg',
}
1
2
3
2
3
import config from './config/user';
import axios from '@/utils/request';
export const reg = (options) => axios.post(config.reg,options);
1
2
3
2
3
调用接口
import * as user from '@/api/user.js'
submitForm(formName) {
this.$refs[formName].validate(async valid => {
if (valid) {
try {
await user.reg(this.ruleForm);
this.$message({
type:'sucess',
message:'注册成功,请登录'
});
this.$router.push('/login');
} catch (e) {
this.$message({
type:'error',
message:e
});
}
} else {
return false;
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2.验证码获取
后端接口:http://localhost:3000/public/getCaptcha
export default {
getSlider: '/public/getSlider', // 获取轮播图接口
getCaptcha:'/public/getCaptcha' // 获取验证码
}
1
2
3
4
2
3
4
需要用户产生唯一标识,可以和验证码对应
export const setLocal = (key, value) => {
if(typeof value === 'object'){
return localStorage.setItem(key,JSON.stringify(value));
}
localStorage.setItem(key, value);
}
export const getLocal = (key,isObject) => {
if(isObject){
return JSON.parse(localStorage.getItem(key)) || {}
}
return localStorage.getItem(key) || '';
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
封装
setLocal
和getLocal
本地存储方法
import {getLocal} from '@/utils/local'
export const getCaptcha = () => axios.get(config.getCaptcha, {params: {
uid:getLocal('uid')
}});
1
2
3
4
2
3
4
获取验证码并传入当前用户的唯一标识
import {v4} from 'uuid';
import {setLocal,getLocal} from '@/utils/local';
import {getCaptcha} from '@/api/public.js'
export default {
async mounted(){
this.uid = getLocal('uid');
if(!this.uid){
setLocal('uid',v4())
}
this.getCaptcha();
},
methods: {
async getCaptcha(){
let {data} = await getCaptcha();
this.verify = data;
},
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
页面加载时获取验证码,同样点击时也可以调用
getCaptcha
切换验证码
3.登录实现
后端接口:http://localhost:3000/user/login
export default {
login:'/user/login'
}
1
2
3
2
3
export const login = (options) => axios.post(config.login, options);
1
Vuex
存储用户信息
// 设置用户信息
export const SET_USER = 'SET_USER'
// 用户登录
export const USER_LOGIN = 'USER_LOGIN';
// 设置以及获取权限
export const SET_PERMISSION = 'SET_PERMISSION'
1
2
3
4
5
6
2
3
4
5
6
定制要实现的功能
import * as user from '@/api/user'
import * as types from '../action-types';
import { setLocal,getLocal } from '@/utils/local'
export default {
state: {
userInfo: {},
hasPermission: false,
},
mutations: {
[types.SET_USER](state, payload) {
state.userInfo = payload;
setLocal('token',payload.token);
},
[types.SET_PERMISSION](state, has) {
state.hasPermission = has;
}
},
actions: {
async [types.SET_USER]({ commit, dispatch }, { payload, hasPermission }) {
commit(types.SET_USER, payload);
commit(types.SET_PERMISSION, hasPermission);
},
async [types.USER_LOGIN]({ dispatch }, userInfo) {
let result = await user.login(userInfo);
dispatch(types.SET_USER, {
payload: result.data,
hasPermission: true
});
return result;
}
}
}
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
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
实现登录逻辑
import * as types from "@/store/action-types";
import { createNamespacedHelpers } from "vuex";
let { mapActions } = createNamespacedHelpers("user");
methods: {
...mapActions([types.USER_LOGIN]),
submitForm(formName) {
this.$refs[formName].validate(async valid => {
if (valid) {
try{
let {data} = await this[types.USER_LOGIN]({...this.ruleForm,uid:this.uid});
this.$router.push('/');
}catch(e){
this.$message({type:'error',message:e});
}
} else {
alert("失败");
return false;
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
用户菜单控制
<template v-if="!hasPermission">
<el-menu-item index="login">
<router-link to="/login">登录</router-link>
</el-menu-item>
<el-menu-item index="reg">
<router-link to="/reg">注册</router-link>
</el-menu-item>
</template>
<el-submenu index="profile" v-else>
<template slot="title">{{userInfo.username}}</template>
<el-menu-item @click="$router.push('/manager')">管理后台</el-menu-item>
<el-menu-item index="logout">退出登录</el-menu-item>
</el-submenu>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
import * as types from "@/store/action-types";
import { createNamespacedHelpers } from "vuex";
let { mapActions, mapState, mapMutations } = createNamespacedHelpers("user");
export default {
computed: {
...mapState(["hasPermission", "userInfo"])
},
};
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
4.验证是否登录
后端接口:http://localhost:3000/user/validate
export default {
validate: '/user/validate'
}
1
2
3
2
3
export const validate = () => axios.get(config.validate);
1
async [types.USER_VALIDATE]({ dispatch }) {
if (!getLocal('token')) return false;
try {
let result = await user.validate();
dispatch(types.SET_USER, {
payload: result.data,
hasPermission: true
});
return true;
} catch (e) {
dispatch(types.SET_USER, {
payload: {},
hasPermission: false
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果没有token返回false,之后通过token校验用户是否登录。
[types.SET_USER](state, payload) {
state.userInfo = payload;
if (payload && payload.token) {
setLocal('token', payload.token);
} else {
localStorage.clear('token');
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如果token被修改,验证登录失败,清除token信息.
5.路由钩子鉴权
遍历hook文件添加钩子方法
import hooks from './hook'
Object.values(hooks).forEach(hook=>{
router.beforeEach(hook.bind(router));
})
1
2
3
4
2
3
4
import store from '../store';
import * as types from '../store/action-types';
const loginPermission = async function(to, from, next) {
let flag = await store.dispatch(`user/${types.USER_VALIDATE}`);
next();
}
1
2
3
4
5
6
2
3
4
5
6
config.headers.authorization = 'Bearer '+getLocal('token')
1
携带token
6.根据是否需要登录增加校验
meta:{
needLogin:true
}
1
2
3
2
3
给路由增添路由源信息
const loginPermission = async function(to, from, next) {
// 先判断是否需要登录
let needLogin = to.matched.some(item => item.meta.needLogin);
let flag = await store.dispatch(`user/${types.USER_VALIDATE}`);
if (!store.state.user.hasPermission) {
if (needLogin) { // 没权限需要登录,那就校验是否登陆过
if (!flag) { // 没登陆过
next('/login')
} else {
next();
}
} else { // 没权限不需要登录
next();
}
} else {
// 有权限
if (to.path == '/login') {
next('/');
} else {
next();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
二.路由权限
export const ADD_ROUTE = 'ADD_ROUTE' // 添加路由动作
export const SET_MENU_PERMISSION = 'SET_MENU_PERMISSION' // 表示菜单权限已经拥有
1
2
2
export const menuPermission = async function(to, from, next) {
if (store.state.user.hasPermission) {
if (!store.state.user.menuPermission) {
store.dispatch(`user/${types.ADD_ROUTE}`);
next({...to,replace:true});
} else {
next();
}
} else {
next();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
根据用户返回的权限过滤需要的路由
import router from '@/router/index'
import per from '@/router/per';
async [types.ADD_ROUTE]({ commit, state }) {
let authList = state.userInfo.authList;
if (authList) {
// 开始 规划路由
let routes = filterRouter(authList);
let route = router.options.routes.find(item => item.path === '/manager');
route.children = routes;
router.addRoutes([route]);
commit(types.SET_MENU_PERMISSION, true);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
过滤的当前用户支持的路由
const filterRouter = (authList) => {
let auths = authList.map(item => item.auth);
const filter = (authRoutes) => {
let result = authRoutes.filter(route => {
if (auths.includes(route.meta.auth)) {
if (route.children) {
route.children = filter(route.children);
}
return route;
}
})
return result
}
return filter(per);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[types.SET_MENU_PERMISSION](state, has) {
state.menuPermission = has;
}
1
2
3
2
3
三.菜单权限
1.菜单权限的处理
针对不同的用户,提供不同的菜单。
管理员权限
- 用户管理功能
- 用户统计功能
- 信息发布功能
- 文章管理功能
普通用户权限
- 个人中心功能
- 我的收藏功能
- 私信消息功能
- 我的文章功能
import { createNamespacedHelpers } from "vuex";
let { mapState } = createNamespacedHelpers("user");
export default {
data() {
return { menuList: [] };
},
mounted() {
this.menuList = this.getMenList(this.userInfo.authList);
},
computed: {
...mapState(["userInfo"])
},
methods: {
getMenList(authList) {
let menu = [];
let sourceMap = {};
authList.forEach(m => {
m.children = [];
sourceMap[m.id] = m;
if (m.pid === -1) {
menu.push(m);
} else {
sourceMap[m.pid] && sourceMap[m.pid].children.push(m);
}
});
return menu;
}
},
render() { // 递归生成菜单
let renderChildren = (data) => {
return data.map(child => {
return child.children.length ?
<el-submenu index={child._id}>
<div slot="title">{child.name}</div>
{renderChildren(child.children)}
</el-submenu> :
<el-menu-item index={child.path}>{child.name}</el-menu-item>
})
}
return <el-menu
background-color="#333"
text-color="#fff"
default-active={this.$route.path}
router={true}
>
{renderChildren(this.menuList)}
</el-menu>
}
}
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
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
websocket
封装
四.1.通过Vuex创建WebSocket
export const CREATE_WEBSOCKET = 'CREATE_WEBSOCKET';
export const SET_MESSAGE = 'SET_MESSAGE';
1
2
2
当用户登录后,创建websocket对象
export const createWebsocket = async function(to, from, next) {
if (store.state.user.hasPermission && !store.state.ws) {
store.dispatch(`${types.CREATE_WEBSOCKET}`);
next();
} else {
next();
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
[types.CREATE_WEBSOCKET](state,ws){
state.ws = ws;
}
async [types.CREATE_WEBSOCKET]({commit}){
let ws = new WS();
ws.create();
commit(types.CREATE_WEBSOCKET,ws);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
将websocket对象保存到vuex中,方便后续使用
2.WebSocket封装
- 实现监听消息
- 实现消息发送
- 实现心跳检测
- 实现断线重连
import { getLocal } from './local'
import store from '@/store'
import * as types from '@/store/action-types';
class WS {
constructor(config = {}) {
this.url = config.url || 'localhost'
this.port = config.port || 4000
this.protocol = config.protocol || 'ws'
this.time = config.time || 3000 * 10;
}
create() {
this.wsc = new WebSocket(`${this.protocol}://${this.url}:${this.port}`);
this.wsc.onopen = this.onOpen;
this.wsc.onmessage = this.onMessage;
this.wsc.onclose = this.onClose;
this.wsc.onerror = this.onError
}
onOpen = () => {
this.wsc.send(JSON.stringify({
type: 'auth',
data: getLocal('token')
}))
}
onClose = () => {
this.wsc.close()
}
send = (msg) => {
this.wsc.send(JSON.stringify(msg));
}
onMessage = (e) => {
var {type,data} = JSON.parse(e.data);
switch (type) {
case 'noAuth':
console.log('没权限')
break;
case 'heartCheck':
this.checkServer();
this.wsc.send(JSON.stringify({ type: 'heartCheck'}))
break;
default:
if(data === 'auth ok') return;
store.commit(types.SET_MESSAGE,data)
}
}
onError = () => {
setTimeout(() => {
this.create()
}, 1000);
}
checkServer() {
clearTimeout(this.handle);
this.handle = setTimeout(() => {
this.onClose();
this.onError()
}, this.time + 1000)
}
}
export default WS;
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
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
3.存放WebSocket信息
[types.SET_MESSAGE](state,msg){
state.message = msg
}
1
2
3
2
3
← 项目实战 (一) 从零搭建Vue组件库 →