这部分就是后台系统很常见的功能、也是主要的功能了,把这部分内容都完成了,一个后台的管理系统也基本完成的七七八八了。
发送 Token
前面在用户登录成功后,服务器会给客户端返回一个 token,那么以后客户端每次发送请求都要带上 token,服务器验证通过了才会返回数据。
因为我们用了koa-jwt
,所以只需要在每条请求头上加上Authorization
属性,值是Bearer {token值}
,然后让 Koa 在接收请求之前验证一下 token 即可,我们把token
绑定到axios
的header
里,这样每次发送请求默认都会带上token
了。
打开src/main.js
,在路由跳转的钩子里加上这句:
//main.js
...
router.beforeEach((to, from, next) => {
const token = sessionStorage.getItem('demo-token')
if (to.meta.requiresAuth) {
if (token !== 'null' && token != null) {
// 全局设定header的token验证,注意Bearer后有个空格
Vue.prototype.$http.defaults.headers.common['Authorization'] = 'Bearer ' + token
next()
} else {
message.warning('请先登录')
next('/login')
}
} else {
next()
}
})
获取数据
我们先在数据库里手动添加一条数据,然后再写一个获取数据库里数据的方法。

打开server/models/management.js
,新增一个getOperator
方法:
//models/management.js
import db from '../config/db.js' // 引入user的表结构
const managementModel = '../schema/management.js'
const DemoDb = db.Demo // 引入数据
const Management = DemoDb.import(managementModel) // 用sequelize的import方法引入表结构
const getOperator = async function(params) {
const result = await personnelTable.findAndCountAll({
offset: (params.page - 1) * params.pageSize,
limit: params.pageSize
})
return result // 返回数据
}
export default {
getOperator
}
通常数据库里的数据都是成千上万条的,如果一次性获取全部数据,一来是数据库读取速度慢,二来前端页面一下子渲染这么多数据也会需要较长时间,所以我们一般采用分页这种友好的方式去获取数据,这里选用里findAndCountAll
这个方法,offset
表示页码,limit
表示每页的数据条数;如果要一次性获取全部的数据,可以用findAll
这个方法。
同样需要修改controller/management.js
:
//controllers/management.js
import management from '../models/management.js'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
const getOperator = async function(ctx, next) {
const data = ctx.request.body
const result = await management.getOperator(data)
if (result !== null) {
ctx.response.body = {
success: true,
result: result
}
} else {
ctx.response.body = {
success: false,
data: '获取数据出错'
}
}
}
export default {
getOperator
}
更新路由routes/router.js
:
//routes/router.js
import ManagementController from './../controllers/management.js'
import jwt from 'koa-jwt'
...
export default function (app) {
router.post('/user/:id', UserController.getUserInfo)
router.post('/api/user', UserController.getUserAuth)
router.post('/api/getOperator', jwt({secret: 'vue-koa-demo'}), ManagementController.getOperator)
这里的jwt({secret: 'vue-koa-demo'})
就是要求前端如果想获取/api/getOperator
这个 API 的数据,就必须带上token
,也就是之前用户登录成功后服务器返回给用户的token
,我们在上文已经把这个token
绑定到axios
的请求头上了。
修改app.js
,捕捉jwt
验证失败的错误信息:
//app.js
...
app.use(async function (ctx, next) { // 如果JWT验证失败,返回验证失败信息
try {
await next()
} catch (err) {
if (err.status === 401) {
ctx.status = 401
ctx.body = {
success: false,
token: null,
info: '没有权限'
}
} else {
throw err
}
}
})
app.on('error', function (err, ctx) {
console.log('server error', err)
})
...
接着我们需要在前端发起数据的请求了,这个请求应该在我们页面刚加载的时候就去服务器里请求数据,所以我们在mounted()
方法里面调用获取数据的方法,修改src/components/userTable.vue
:
//userTable.vue
...
// 动态获取数据
async request () {
let _this = this
let url = '/api/getOperator'
let params = this.params
this.$http.post(url,params)
.then(res => {
res.data.result.rows.map((item, index) => {
item.key = index
}) //给每条数据添加唯一的key值
_this.total = res.data.result.count
_this.dataSource = res.data.result.rows
})
},
mounted () {
this.request()
}
...
然后我们刷新一下页面,会发现我们已经拿到刚才添加到数据库里面的两条数据了:

添加数据、更新数据、删除数据
上图中的四个按键功能创建员工
编辑员工
员工详情
删除员工
在前端页面里都共用了一个 model,所以打算把四个按键功能的方法也通过封装的方式写在一起。
修改server/models/management.js
:
//models/management.js
...
// 新增数据
const createOperator = async function (data){
const userInfo = await Management.create({ // 用await控制异步操作,将返回的Promise对象里的数据返回出来。也就实现了“同步”的写法获取异步IO操作的数据
username: data.username,
sex: data.sex,
state: data.state,
interest: data.interest,
birthday: data.birthday
});
return userInfo // 返回数据
}
// 更新数据
const updateOperator = async function (data) {
const result = await Management.update(
{
username: data.username,
sex: data.sex,
state: data.state,
interest: data.interest,
birthday: data.birthday
},
{
where: {
id: data.id
}
}
)
return result // 返回数据
}
//删除数据
const deleteOperator = async function (params) {
const result = await Management.destroy({
where: {
id: params.id
}
})
return result // 返回数据
}
export default {
createOperator, // 导出createOperator的方法,将会在controller里调用
getOperator,
deleteOperator,
updateOperator
}
...
同样需要修改controller/management.js
:
//controller/management.js
...
//更新数据
const updateOperator = async function (ctx, next) {
const data = ctx.request.body
data.birthday = moment(data.birthday).format('YYYY-MM-DD')
const result = await management.updateOperator(data)
if (result !== null) {
ctx.response.body = {
success: true,
result: result
}
} else {
ctx.response.body = {
success: false,
result: '更新失败'
}
}
}
//删除数据
const deleteOperator = async function (ctx, next) {
const data = ctx.request.body
const result = await management.deleteOperator(data)
if (result > 0) {
ctx.response.body = {
success: true,
result: '删除成功'
}
} else {
ctx.response.body = {
success: false,
data: '删除失败'
}
}
}
export default {
createOperator, // 导出createOperator的方法,将会在router.js里调用
getOperator,
deleteOperator,
updateOperator
}
更新路由routes/router.js
:
`routes/router.js`
...
router.post('/api/createOperator', jwt({secret: 'vue-koa-demo'}), ManagementController.createOperator)
router.post('/api/deleteOperator', jwt({secret: 'vue-koa-demo'}), ManagementController.deleteOperator)
router.post('/api/updateOperator', jwt({secret: 'vue-koa-demo'}), ManagementController.updateOperator)
...
修改userForm.vue
里面的handleCreate
方法:
//userForm.vue
import { Form, Select, Radio, message } from 'ant-design-vue'
import moment from 'moment'
const FormItem = Form.Item
const Option = Select.Option
const RadioGroup = Radio.Group
...
async handleCreate () {
const form = this.formRef.form
let _this = this
form.validateFields(async (err, values) => {
if (err) {
return
}
let params = {
username: values.username,
sex: values.sex,
state: values.state,
birthday: values.birthday,
interest: values.interest,
id: _this.title === '创建员工' ? null : _this.userInfo.id
}
let url= _this.title === '创建员工' ? '/api/createOperator' : '/api/updateOperator'
this.$http.post(url,params)
.then((res) => {
if (res.data.id ) {
message.success('创建成功')
form.resetFields()
_this.$emit('hideForm', 'update')
}
if (res.data.result.length) {
message.success('更新成功')
_this.$emit('hideForm', 'update', params)
}
})
})
},
最后修改Helloword.vue
里面的handleOperator
:
//Helloword.vue
import { Modal, message } from 'ant-design-vue'
import UserTable from './UserTable'
import UserForm from './UserForm'
import SearchForm from './SearchForm'
...
//操作员工
// 操作员工
handleOperator (type) {
let _this = this
let deleteId
if (type === 'create') {
this.title = '创建员工'
this.visible = true
// 因为共用一个Form表单,当用户新建员工时,即使选中了table里面的某一行数据,打开的From表单应该只有默认值,所以这里要清空userInfo;通过selectItem来判断用户有没有选中table的数据
this.userInfo = null
} else if (type === 'edit' || type === 'detail') {
if (this.selectItem.id == undefined) {
Modal.info({
title: '信息',
content: '请选择一个用户'
})
return
}
this.title = (type === 'edit' ? '编辑用户' : '用户详情')
this.visible = true
this.userInfo = this.selectItem
} else if (type === 'delete') {
deleteId = this.selectItem.id
if (this.selectItem.id == undefined) {
Modal.info({
title: '信息',
content: '请选择一个用户'
})
return
}
Modal.confirm({
content: '确定要删除此用户吗?',
onOk: async () => {
let url = '/api/deleteOperator'
let params = {
id: deleteId
}
_this.$http.post(url,params)
.then((res) => {
if (res.data.result === '删除成功') {
message.success('删除成功')
this.requestList= !_this.requestList
this.selectItem.id = undefined
this.hackReset = false
this.$nextTick(() => {
this.hackReset = true
})
}
})
}
})
}
},
好了,下面让我们来看下效果:

OK!没问题。
如果看了代码,会发现其实操作数据库没有那么难(感谢大神们 🙏),增、删、改、查分别都有对应的方法可以调用
数据查找
最后,我们做一下按条件查找的功能,这个教程就接近尾声了。其实做到这里,整个前后端交互逻辑,大家应该都已经比较清楚了。router
作为前端的访问路径,controllers
是前后端数据交互的缓冲层,model
是后端访问数据库的方法。
修改server/models/management.js
,新增searchOperator
方法:
//models/management.js`
...
// 按设置条件进行查找
const searchOperator = async function (params) {
params.state === '全部' ? params.state = ['咸鱼一条', '风华浪子', '北大才子一枚', '百度FE', '创业者'] : params.state = [params.state]
params.sex === '全部' ? params.sex = ['男', '女'] : params.sex = [params.sex]
const result = await Management.findAll({
where: {
state: {
$in: params.state
},
sex: {
$in: params.sex
},
birthday: {
$between: [params.birthday[0], params.birthday[1]]
}
}
})
return result // 返回数据
}
...
修改server/controllers/management.js
,新增searchOperator
方法:
controllers/management.js
...
const searchOperator = async function (ctx, next) {
const data = ctx.request.body
console.log(data)
data.birthday[0] = moment(data.birthday[0]).format('YYYY-MM-DD')
data.birthday[1] = moment(data.birthday[1]).format('YYYY-MM-DD')
const result = await management.searchOperator(data)
if (result !== null) {
ctx.response.body = {
success: true,
result: result
}
} else {
ctx.response.body = {
success: false,
data: '获取数据出错'
}
}
}
...
更新routes/router.js
:
// router.js`
...
router.post('/api/searchOperator', jwt({secret: 'vue-koa-demo'}), ManagementController.searchOperator)
...
更改前端页面SearchFrom.vue
里面的handleSubmit
方法:
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (!err) {
// 如果用户没有选择时间段,就给一个大范围的时间段搜索范围
if (!values.birthday) {
values.birthday = []
values.birthday[0] = moment(new Date(1900, 0, 1)).format('YYYY-MM-DD')
values.birthday[1] = moment(new Date(2100, 0, 1,)).format('YYYY-MM-DD')
}
this.$emit('searchOperator', values)
}
})
},
我们来看下最终的效果:

大功告成。