前情提要
- 上一节讲解了如何在前端使用oauth2,如何修改用户状态和编写认证逻辑以适应oauth2
- 这一节会讲解如何做一个简单的后台管理页面
什么是后台管理页面?
- 后台管理的本质是对数据库记录的CRUD
- 后台管理页面是给工作人员使用的,用来管理和维护信息的可视化页面,将平时需要对数据库的操作进行封装,方便管理
- 用户后台管理页面就是用来管理用户信息的,不但要有用户的基本信息,和用户相关的信息也要有,比如用户权限,组群,组群权限
用表格的形式管理
- 表格是一种排列整齐,表达直观的方式,加上vuetify已经提供了便携的api来创建符合要求的表格,所以优先选择表格
- 在views中创建一个叫admin的文件夹,后台相关的文件在这里编写
- 然后创建一个User.vue的文件,用来管理用户的基本信息,代码如下(代码很长,请自行阅读并理解)
<template>
<v-container fluid>
<v-row>
<v-col cols="12">
<v-data-table :headers="headers" :items="users" class="elevation-1">
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>用户信息管理</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<!--对话框,用于修改或者新建-->
<v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }">
<!--点击这个按钮的时候,将状态切换为创建-->
<v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on" @click="state.create=true">
New User
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-form ref="userForm" v-model="valid" lazy-validation autocomplete="off">
<!--用户名和密码-->
<v-row>
<v-col cols="12" sm="6">
<v-text-field v-model="editedItem.username" :rules="rules.usernameRules" label="Username"></v-text-field>
</v-col>
<v-col cols="12" sm="6">
<v-text-field type="password" v-model="editedItem.password" :rules="rules.passwordRules" label="Password"></v-text-field>
</v-col>
</v-row>
<!--用户的状态,用以禁用用户-->
<v-row>
<v-col cols="12" sm="6">
<v-switch v-model="editedItem.enabled" :label="`Enabled: ${editedItem.enabled}`"></v-switch>
</v-col>
<!--手机号码-->
<v-col cols="12" sm="6">
<v-text-field v-model="editedItem.phone" label="Phone"></v-text-field>
</v-col>
</v-row>
<!--用户性别-->
<v-row>
<v-col cols="12">
<v-radio-group v-model="editedItem.gender" row>
<v-radio label="Unknown" color="orange" :value="0"></v-radio>
<v-radio label="Male" color="primary" :value="1"></v-radio>
<v-radio label="Female" color="pink" :value="2"></v-radio>
</v-radio-group>
</v-col>
</v-row>
<!--用户邮箱-->
<v-row>
<v-col cols="12">
<v-text-field v-model="editedItem.email" :rules="rules.emailRules" label="Email"></v-text-field>
</v-col>
</v-row>
<!--用户生日和加入日期-->
<v-row>
<v-col cols="12" sm="6">
<v-text-field type="date" v-model="editedItem.birthday" label="birthday"></v-text-field>
</v-col>
<v-col cols="12" sm="6">
<v-text-field type="date" v-model="editedItem.joinedDate" label="joined date"></v-text-field>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close">Cancel</v-btn>
<!--判断是创建还是删除-->
<v-btn v-if="state.create" :disabled="!valid" color="blue darken-1" text @click="create">Create</v-btn>
<v-btn v-if="state.update" :disabled="!valid" color="blue darken-1" text @click="update">Update</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!--删除对话框,用以再次确认-->
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="headline">Are you sure you want to delete this item?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="closeDelete()">Cancel</v-btn>
<v-btn color="blue darken-1" text @click="deleteItemConfirm()">OK</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" @click="editItem(item)">
mdi-pencil
</v-icon>
<v-icon small @click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">
Reset
</v-btn>
</template>
</v-data-table>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'UserAdmin',
data: () => ({
state: {
create: false,
update: false
},
valid: true,
dialog: false,
dialogDelete: false,
headers: [
{
text: 'Username',
align: 'start',
sortable: false,
value: 'username'
},
{
text: 'Email',
sortable: false,
value: 'email'
},
{
text: 'Enabled',
value: 'enabled'
},
{
text: 'Joined Date',
value: 'joinedDate'
},
{
text: 'Actions',
value: 'actions',
sortable: false
}
],
users: [],
editedIndex: -1,
editedItem: {
username: '',
password: '',
enabled: true,
gender: '0',
phone: '',
email: '',
icon: null,
birthday: new Date().toISOString().substr(0, 10),
joinedDate: new Date().toISOString().substr(0, 10)
},
defaultItem: {
username: '',
password: '',
enabled: true,
gender: 0,
phone: '',
email: '',
icon: null,
birthday: new Date().toISOString().substr(0, 10),
joinedDate: new Date().toISOString().substr(0, 10)
},
rules: {
usernameRules: [
v => !!v || 'Username is none!',
v => (v.length >= 1 && v.length <= 20) || 'Username must be 1-20 characters!'
],
passwordRules: [
v => !!v || 'Password is none!',
v => (v.length >= 6 && v.length <= 30) || 'Password must be 6-30 characters!'
],
emailRules: [
v => !!v || 'Email is none!',
v => /(\w+)@(\w+)\.(\w+)/.test(v) || 'Email is not valid!'
]
}
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New User' : 'Edit User'
}
},
watch: {
dialog (val) {
val || this.close()
},
dialogDelete (val) {
val || this.closeDelete()
}
},
methods: {
initialize () {
// 获取全部用户(分页默认为10个一页)
this.axios.get('http://127.0.0.1:9001/api/users')
.then(response => {
console.log(response)
// 设置用户列表
const data = response.data._embedded.users
for (var i = 0; i < data.length; ++i) {
data[i].birthday = data[i].birthday ? data[i].birthday.substr(0, 10) : null
data[i].joinedDate = data[i].joinedDate ? data[i].joinedDate.substr(0, 10) : null
}
this.users = data
}).catch(error => {
console.log(error)
})
},
editItem (item) {
this.editedIndex = this.users.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
this.state.update = true
},
deleteItem (item) {
this.editedIndex = this.users.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm () {
const user = this.users[this.editedIndex]
this.axios.delete(user._links.self.href)
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
this.users.splice(this.editedIndex, 1)
this.closeDelete()
},
close () {
this.dialog = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
this.state.create = false
this.state.update = false
},
closeDelete () {
this.dialogDelete = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
create () {
const valid = this.$refs.userForm.validate()
if (valid) {
this.axios.post('http://127.0.0.1:9001/api/users', {
username: this.editedItem.username,
password: this.editedItem.password,
enabled: this.editedItem.enabled,
gender: parseInt(this.editedItem.gender),
phone: this.editedItem.phone,
email: this.editedItem.email,
icon: this.editedItem.icon,
birthday: this.editedItem.birthday ? this.editedItem.birthday : new Date().toISOString().substr(0, 10),
joinedDate: this.editedItem.joinedDate ? this.editedItem.joinedDate : new Date().toISOString().substr(0, 10)
}
).then(response => {
console.log(response)
this.initialize()
}).catch(error => {
console.log(error)
})
this.close()
}
},
update () {
const valid = this.$refs.userForm.validate()
if (valid) {
const user = this.users[this.editedIndex]
this.axios.patch(user._links.self.href, {
username: this.editedItem.username,
password: this.editedItem.password,
enabled: this.editedItem.enabled,
gender: parseInt(this.editedItem.gender),
phone: this.editedItem.phone,
email: this.editedItem.email,
icon: this.editItem.icon,
birthday: this.editedItem.birthday ? this.editedItem.birthday : new Date().toISOString().substr(0, 10),
joinedDate: this.editedItem.joinedDate ? this.editedItem.joinedDate : new Date().toISOString().substr(0, 10)
}
).then(response => {
console.log(response)
this.initialize()
}).catch(error => {
console.log(error)
})
this.close()
}
}
},
created () {
this.initialize()
}
}
</script>
为用户后台管理添加路由
- 在router/index.js中,添加一个指向admin/User.vue的路由,代码如下
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
// 登录视图
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
// 注册视图
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue')
},
{
// 用户基本信息后台管理
path: '/admin/users',
name: 'UserAdmin',
component: () => import('../views/admin/User.vue')
}
]
const router = new VueRouter({
routes
})
export default router
测试用户管理功能
- 考虑到权限的问题,一般来说一个网站的后台管理是对普通用户不可见的,需要输入指定的url才能进入(普通用户就算输入也会因为权限不够无法使用)
- 在url中输入http://127.0.0.1:9001/admin/users进入后台管理页面,效果图如下
- 首先测试创建一个用户的功能,输入信息,点击创建
- 可以看到一个新的用户创建成功
- 然后测试修改一个用户的功能,点击右侧的小笔打开修改页面,随便修改点信息(这里显示的是数据库中密文保存的密码,在后端有相应的逻辑判断提交上来的密码是否需要修改,所以这里可以不改)
- 可以看到一个新的用户的Email成功被修改
- 最后测试一下删除功能,点击右边的垃圾桶按钮删除,选择OK删除
- 可以看到用户被删除,测试成功
相关细节以及设计目的
- 表格看见的是列表视图,不是详细视图,将判断一个用户的必要信息标记在表格上就好
- vuetify的表格自带了分页的功能,所以后端接口无需改变,不需要实现分页逻辑
- 删除一个用户在数据库中确实删除了,但是在前端只是删除了列表的一个item,而不需要重新获取全部用户,这样可以减轻后端的负担,因为获取用户这一操作是很费时的
- vuetify的表格上有自动排序功能,比如这个表格就可以对joinedDate进行排序
至此,一个简单的后台用户管理界面已完成,下一节会更加详细的设置后台用户管理界面,并且完成其它的管理功能