mock.js和roadhogrc.js实现模拟接口
.umirc.mock.js统一配置接口api
export default {
...require('./mock/common'),
...require('./mock/dashboard'),
...require('./mock/menu'),
...require('./mock/post'),
...require('./mock/user'),
}
下面就mock/user.js的部分接口进行分析
const EnumRoleType = {
ADMIN: 'admin',
DEFAULT: 'guest',
DEVELOPER: 'developer',
}
const userPermission = {
DEFAULT: {
visit: ['1', '2', '21', '7', '5', '51', '52', '53'],
role: EnumRoleType.DEFAULT,
},
ADMIN: {
role: EnumRoleType.ADMIN,
},
DEVELOPER: {
role: EnumRoleType.DEVELOPER,
},
}
const adminUsers = [
{
id: 0,
username: 'admin',
password: 'admin',
permissions: userPermission.ADMIN,
}, {
id: 1,
username: 'guest',
password: 'guest',
permissions: userPermission.DEFAULT,
}, {
id: 2,
username: '吴彦祖',
password: '123456',
permissions: userPermission.DEVELOPER,
},
]
登录接口:通过post提交,用户名和密码一致后,设置cookie,
[`POST ${apiPrefix}/user/login`] (req, res) {
const { username, password } = req.body
const user = adminUsers.filter(item => item.username === username)
if (user.length > 0 && user[0].password === password) {
const now = new Date()
now.setDate(now.getDate() + 1)
res.cookie('token', JSON.stringify({ id: user[0].id, deadline: now.getTime() }), {
maxAge: 900000,
httpOnly: true,
})
res.json({ success: true, message: 'Ok' })
} else {
res.status(400).end()
}
}
访问用户信息:
1.token不存在,说明还没登录
2.token存在,返回用户信息
[`GET ${apiPrefix}/user`] (req, res) {
const cookie = req.headers.cookie || ''
const cookies = qs.parse(cookie.replace(/\s/g, ''), { delimiter: ';' })
const response = {}
const user = {}
if (!cookies.token) {
res.status(200).send({ message: 'Not Login' })
return
}
const token = JSON.parse(cookies.token)
if (token) {
response.success = token.deadline > new Date().getTime()
}
if (response.success) {
const userItem = adminUsers.filter(_ => _.id === token.id)
if (userItem.length > 0) {
user.permissions = userItem[0].permissions
user.username = userItem[0].username
user.id = userItem[0].id
}
}
response.user = user
res.json(response)
}
分析页面访问流程:src/models/app.js
app.start()执行时,subscriptions对象中的函数执行
setupHistory():监听路由变化,路由每次变化后,都会讲变化后的路由保存到全局state的app属性中,
即app.locationPathname保存当前路径信息,app.locationQuery保存query信息。
setup():
1.用户信息不存在,跳转到登录页面
else if (config.openPages && config.openPages.indexOf(locationPathname) < 0) {
yield put(routerRedux.push({
pathname: '/login',
search: queryString.stringify({
from: locationPathname,
}),
}))
}
2.用户信息存在,根据用户信息,取得权限中的菜单,保存到全局state.app.menu中,
import { routerRedux } from 'dva/router'
import { parse } from 'qs'
import config from 'config'
import { EnumRoleType } from 'enums'
import { query, logout } from 'services/app'
import * as menusService from 'services/menus'
import queryString from 'query-string'
const { prefix } = config
export default {
namespace: 'app',
state: {
user: {},
permissions: {
visit: [],
},
menu: [
{
id: 1,
icon: 'laptop',
name: 'Dashboard',
router: '/dashboard',
},
],
menuPopoverVisible: false,
siderFold: window.localStorage.getItem(`${prefix}siderFold`) === 'true',
darkTheme: window.localStorage.getItem(`${prefix}darkTheme`) === 'true',
isNavbar: document.body.clientWidth < 769,
navOpenKeys: JSON.parse(window.localStorage.getItem(`${prefix}navOpenKeys`)) || [],
locationPathname: '',
locationQuery: {},
},
subscriptions: {
setupHistory ({ dispatch, history }) {
history.listen((location) => {
dispatch({
type: 'updateState',
payload: {
locationPathname: location.pathname,
locationQuery: location.query,
},
})
})
},
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
window.onresize = () => {
clearTimeout(tid)
tid = setTimeout(() => {
dispatch({ type: 'changeNavbar' })
}, 300)
}
},
},
effects: {
* query ({
payload,
}, { call, put, select }) {
const { success, user } = yield call(query, payload)
const { locationPathname } = yield select(_ => _.app)
if (success && user) {
const { list } = yield call(menusService.query)
const { permissions } = user
let menu = list
if (permissions.role === EnumRoleType.ADMIN || permissions.role === EnumRoleType.DEVELOPER) {
permissions.visit = list.map(item => item.id)
} else {
menu = list.filter((item) => {
const cases = [
permissions.visit.includes(item.id),
item.mpid ? permissions.visit.includes(item.mpid) || item.mpid === '-1' : true,
item.bpid ? permissions.visit.includes(item.bpid) : true,
]
return cases.every(_ => _)
})
}
yield put({
type: 'updateState',
payload: {
user,
permissions,
menu,
},
})
if (location.pathname === '/login') {
yield put(routerRedux.push({
pathname: '/dashboard',
}))
}
} else if (config.openPages && config.openPages.indexOf(locationPathname) < 0) {
yield put(routerRedux.push({
pathname: '/login',
search: queryString.stringify({
from: locationPathname,
}),
}))
}
},
},
reducers: {
updateState (state, { payload }) {
return {
...state,
...payload,
}
},
},
}
点击login in执行
export default {
namespace: 'login',
state: {},
effects: {
* login ({
payload,
}, { put, call, select }) {
const data = yield call(login, payload)
const { locationQuery } = yield select(_ => _.app)
if (data.success) {
const { from } = locationQuery
yield put({ type: 'app/query' })
if (from && from !== '/login') {
yield put(routerRedux.push(from))
} else {
yield put(routerRedux.push('/dashboard'))
}
} else {
throw data
}
},
},
}
如果还没有登录时,访问http://localhost:8000/user,首先执行subscriptions中的函数跳转到
http://localhost:8000/login?from=user,登录后跳转到http://localhost:8000/user而不是跳转到
http://localhost:8000/dashboard。
关于layouts:
有两个,一个是登录页面,另一个是登录后的页面
src/layouts/app.js
通过
if (openPages && openPages.includes(pathname))
判断当前路径是否是/login返回不同的login
路由权限的访问:
虽然菜单按钮根据用户的权限只是渲染能够访问的按钮,但是还要处理通过直接在地址栏输入路径访问权限之外的路由。
const current = menu.filter(item => pathToRegexp(item.route || '').exec(pathname))
const hasPermission = current.length ? permissions.visit.includes(current[0].id) : false
。
。
。
{hasPermission ? children : <Error />}