vue+springmvc前后端分离开发(十六)(做一个简单的后台用户管理界面)

前情提要

  • 上一节讲解了如何在前端使用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进行排序

至此,一个简单的后台用户管理界面已完成,下一节会更加详细的设置后台用户管理界面,并且完成其它的管理功能

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值