涉及技术
后台
Nodejs 搭建并提供API接口文档
电商管理后台 API 接口文档(部分)
API V1 接口说明
- 接口基准地址:
http://127.0.0.1:8888/api/private/v1/
- 服务端已开启 CORS 跨域支持
- API V1 认证统一使用 Token 认证
- 需要授权的 API ,必须在请求头中使用
Authorization
字段提供token
令牌 - 使用 HTTP Status Code 标识状态
- 数据返回格式统一使用 JSON
支持的请求方法
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
通用返回状态说明
状态码 | 含义 | 说明 |
---|---|---|
200 | OK | 请求成功 |
201 | CREATED | 创建成功 |
204 | DELETED | 删除成功 |
400 | BAD REQUEST | 请求的地址不存在或者包含不支持的参数 |
401 | UNAUTHORIZED | 未授权 |
403 | FORBIDDEN | 被禁止访问 |
404 | NOT FOUND | 请求的资源不存在 |
422 | Unprocesable entity | [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误 |
500 | INTERNAL SERVER ERROR | 内部错误 |
登录
登录验证接口
- 请求路径:login
- 请求方法:post
- 请求参数
参数名 | 参数说明 | 备注 |
---|---|---|
username | 用户名 | 不能为空 |
password | 密码 | 不能为空 |
- 响应参数
参数名 | 参数说明 | 备注 |
---|---|---|
id | 用户 ID | |
rid | 用户角色 ID | |
username | 用户名 | |
mobile | 手机号 | |
邮箱 | ||
token | 令牌 | 基于 jwt 的令牌 |
- 响应数据
{
"data": {
"id": 500,
"rid": 0,
"username": "admin",
"mobile": "123",
"email": "123@qq.com",
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHm-tPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM"
},
"meta": {
"msg": "登录成功",
"status": 200
}
}
前端
Vue ElementUI
环境搭建
项目配置
main.js
按需添加配置
代码语言:javascript复制import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
// 导入全局样式表
import './assets/css/global.css'
// 导入字体图标 (阿里)
import './assets/fonts/iconfont.css'
// 导入 axios
import axios from 'axios'
// 全局的 axios 默认值
// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
// 设置拦截器
// 在请求或响应被 then 或 catch 处理前拦截它们。
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
// console.log(config)
config.headers.Authorization = window.sessionStorage.getItem('token')
// 必须 return config
return config
})
// 挂在在Vue实例中,每一个组件可以通过 $http 获取 axios 对象
Vue.prototype.$http = axios
// Vue 控制台提示
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
vue 默认配置
- 导入路由router
- 将路由挂载到Vue实例中,方便后面的使用
axios 配置
- 导入axios
- 配置axios请求的根路径,从API文档中获取
- 配置axios请求拦截器,用于处理携带token
- 将axios配置到全局Vue实例中,方便后面的使用
其他配置
- 导入Element
- 导入全局样式表,用于全局通用
- 导入字体图标,用于全局通用
App.vue
Vue入口,只需要添加路由占位符即可
代码语言:javascript复制<template>
<div id="app">
<!-- 路由占位符 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style></style>
router/index.js
删除多余的配置
代码语言:javascript复制import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
]
const router = new VueRouter({
routes
})
export default router
plugins/element.js
由于项目中的 ElementUI 选择的是按需导入组件,所有会出现这个js
代码语言:javascript复制// 按需要导入 element 组件
import Vue from 'vue'
import {
Button,
Form,
FormItem,
Input,
Container,
Header
} from 'element-ui'
// 导入弹框提示组件
import { Message } from 'element-ui'
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
Vue.use(Container)
Vue.use(Header)
// Message 要挂载在全局 Vue 的一个原型(prototype)上
// $message 是自定义名字
// 这样在每个组件中可以使用 this.$message.success("提示信息")
Vue.prototype.$message = Message
Message
比较特殊,导入的方式有所不同,后面可以通过this.$message.success('操作成功')
调用
项目开发
Login 模块
components/Login.vue
代码语言:javascript复制<template>
<div class="login_container">
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img src="@/assets/logo.png" alt="" />
</div>
<!-- 表单区域
一、数据绑定
1. 数据绑定 loginForm 对象
2. 在data中定义 loginForm 对象
3. 在 input中使用 loginForm.属性 进行双向绑定
二、数据验证
1. 为 el-form 绑定数据验证 loginFormRules 对象
2. 在data中定义 loginFormRules 对象
3. 在 el-form-item 中使用 prop="属性名"
三、表单的实例对象
定义 ref="xxx"
通过 this.$refs.xxx 获取表单的实例对象
1. 重置 this.$refs.xxx.resetFields();
2. 预验证 this.$refs.loginFormRef.validate((valid) => {}); valid 是一个boolean值
-->
<el-form
ref="loginFormRef"
class="login_form"
:model="loginForm"
:rules="loginFormRules"
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
prefix-icon="iconfont icon-user"
v-model="loginForm.username"
></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
type="password"
prefix-icon="iconfont icon-3702mima"
v-model="loginForm.password"
></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// 这是登录表单的数据绑定对象
loginForm: {
username: 'admin',
password: '123456'
},
// 这是登录表单的验证规则对象
loginFormRules: {
// 对象的属性是数组形式
username: [
{ required: true, message: '请输入登录名', trigger: 'blur' },
{
min: 3,
max: 10,
message: '长度在 3 到 10 个字符之间',
trigger: 'blur'
}
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
}
},
methods: {
// 点击重置按钮事件
resetLoginForm() {
//console.log(this)
this.$refs.loginFormRef.resetFields()
},
// 点击登录按钮事件
login() {
this.$refs.loginFormRef.validate(async valid => {
// console.log(valid);
// 如果验证不成功,直接返回
if (!valid) {
return
}
// 发起请求
// 如果返回的结果是 Promise,可以使用 await 和 async 简化操作
// const result = await this.$http.post("login", this.loginForm);
// ES6 解构写法 { data: res }
const { data: res } = await this.$http.post('login', this.loginForm)
console.log(res)
if (res.meta.status != 200) {
// return console.log("登录失败");
return this.$message.error('登录失败')
}
// console.log("登录成功");
this.$message.success('登录成功')
// 1. 将登录成功之后的 token,保存到客户端的 sessionStorage 中
// 1.1 项目中除了登录外的其他API接口,必须在登录后才能访问
// 1.2 token 只在当前网站打开期间生效,所以将 token 保存在 sesisonStorage 中
// 扩展: localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。
// sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。
window.sessionStorage.setItem('token', res.data.token)
// 2. 通过编程式导航跳转到后台主页,路径地址是 /home
this.$router.push('/home')
})
}
}
}
</script>
<style lang="less" scoped>
.login_container {
background-color: #2b4b6b;
height: 100%;
}
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.avatar_box {
width: 130px;
height: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
}
.btns {
// 弹性布局,放置尾部
display: flex;
justify-content: flex-end;
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
}
</style>
这里我们使用了 ElementUI 的第一个组件 el-form
数据绑定、数据验证、数据实例
代码语言:javascript复制<el-form
ref="loginFormRef"
class="login_form"
:model="loginForm"
:rules="loginFormRules"
>
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
prefix-icon="iconfont icon-user"
v-model="loginForm.username"
></el-input>
.....
数据绑定
- 在
el-form
中使用:model
数据绑定,绑定一个在 data() 中定义的对象 - 在
el-input
中使用v-model
进行数据双向绑定,格式v-model="loginForm.username"
数据验证
- 在
el-form
中使用:rules
数据验证,绑定一个在 data() 中定义的对象 - 在
el-form-item
中使用prop
属性设置为需校验的字段名,格式prop="username"
对象实例
- 在
el-form
中使用ref
映射一个名称,可通过this.$refs
获取,格式ref="loginFormRef"
- 获取实例后,可以对表单实例进行重置、预验证等操作
登录、重置
代码语言:javascript复制<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
登录
- 当点击登录按钮,调用
login
方法 - 先对表单预验证,需要使用表单实例
this.$refs.loginFormRef.validate(valid => console.log(valid))
,返回结果true或false,表示表单是否通过预验证 - 当通过预验证后,可以通过 axios 发起请求 this.http.post('login', this.loginForm),这里的 http 就是 axios 实例,前面我们已经在Vue实例中注册了,由于结果返回 promise 对象,我们可以通过 ES6 语法进行解构 const { data: res } = await this.
- 如果服务器响应成功,可以通过
this.$message.success('登录成功')
返回成功消息 - 将服务器返回的 token,保存到客户端的 sessionStorage 中,后面的各种请求都必须携带 token 值
- 保存token后,通过编程式导航跳转到后台主页,格式
this.$router.push('/home')
tips: localStorage生命周期是永久,这意味着除非用户显示在浏览器提供的UI上清除localStorage信息,否则这些信息将永远存在。 sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了
重置
- 当点击重置按钮,调用
resetLoginForm
方法 - 通过表单实例对表单进行重置操作
this.$refs.loginFormRef.resetFields()
router/index.js
配置相关的路由信息
代码语言:javascript复制import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/components/Login.vue'
import Home from '@/components/Home.vue'
import Welcome from '@/components/Welcome.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
component: Login
},
{
path: '/home',
component: Home,
// 实现首页路由重定向到子路由Welcome
redirect: '/welcome',
// 子路由
children: [
// 根据服务器的path设置
{ path: '/welcome', component: Welcome }
]
}
]
const router = new VueRouter({
routes
})
// 挂在路由导航守卫
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行,两种形式【next() 放行、 next('/login') 强制跳转】
router.beforeEach((to, from, next) => {
// 1. 如果用户访问的是登录页面,直接放行
if (to.path == '/login') {
return next()
}
// 2. 如果用户访问有权限的页面
// 2.1 获取token
const token = window.sessionStorage.getItem('token')
// 2.2 判断是否存在token
if (!token) {
// 强制跳转到登录页面
return next('/login')
}
next()
})
export default router
路由重定向、子路由
- 使用
redirect
实现重定向 - 使用
children
配置子路由
路由导航守卫
- 导航守卫有三个属性,分别是
to
将要访问的路径、from
代表从哪个路径跳转而来 、next
是一个函数,表示放行,有两种形式:next()
表示放行 、next('/login')
表示强制跳转 - 如果用户访问的是登录页面
to.path == '/login'
,直接放行next()
- 如果用户访问的是其他有权限的页面,首先从客户端的 sessionStorage 中获取 token,如果存在则放行
next()
,如果不存在则跳转登录页面next('/login')
演示
Home 模块
components/Home.vue
代码语言:javascript复制<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<img src="@/assets/logo.png" />
<span>电商后台管理系统</span>
</div>
<el-button type="info" @click="logout">退出</el-button>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<!-- 折叠 -->
<div class="toggle-button" @click="toggerCollapse">|||</div>
<!-- 侧边栏菜单区域
unique-opened 是否只保持一个子菜单的展开
collapse 是否水平折叠收起菜单
collapse-transition 是否开启折叠动画
router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转
-->
<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#409EFF"
unique-opened
:collapse="isCollapse"
:collapse-transition="false"
router
:default-active="activePath"
>
<!-- 一级菜单 -->
<el-submenu
v-for="(item, index) in menuList"
:key="item.id"
:index="index ''"
>
<!-- 一级菜单的模板 -->
<template slot="title">
<!-- 图标 -->
<i :class="iconsObj[item.id]"></i>
<!-- 文本 -->
<span>{{ item.authName }}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item
v-for="subItem in item.children"
:key="subItem.id"
:index="'/' subItem.path"
@click="saveNavState('/' subItem.path)"
>
<!-- 二级菜单的模板 -->
<template slot="title">
<!-- 图标 -->
<i class="el-icon-menu"></i>
<!-- 文本 -->
<span>{{ subItem.authName }}</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 右侧内容区域 -->
<el-main>
<!-- 路由占位符 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data() {
return {
// 左侧菜单数据
menuList: [],
// 以每个菜单的id作为 key值,找到对应的图片
iconsObj: {
'125': 'iconfont icon-user',
'103': 'iconfont icon-tijikongjian',
'101': 'iconfont icon-shangpin',
'102': 'iconfont icon-danju',
'145': 'iconfont icon-baobiao'
},
// 控制菜单折叠
isCollapse: false,
// 被激活的链接地址
activePath: ''
}
},
// 生命周期函数
created() {
this.getMenuList()
this.activePath = window.sessionStorage.getItem('activePath')
},
methods: {
logout() {
// 1. 清空 sessionStorage
window.sessionStorage.clear()
// 2. 跳转到登录页面
this.$router.push('/login')
},
// 获取所有的菜单
async getMenuList() {
const { data: res } = await this.$http.get('menus')
// console.log(res)
// 如果失败
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.menuList = res.data
},
// 点击按钮切换菜单折叠与展开
toggerCollapse() {
this.isCollapse = !this.isCollapse
},
// 保存链接的激活状态
saveNavState(activePath) {
window.sessionStorage.setItem('activePath', activePath)
this.activePath = activePath
}
}
}
</script>
<style lang="less" scoped>
.home-container {
height: 100%;
}
.el-header {
background-color: #373d41;
display: flex;
justify-content: space-between;
padding-left: 15px;
align-items: center;
color: #fff;
font-size: 16px;
> div {
display: flex;
align-items: center;
> img {
width: 50px;
height: 50px;
}
> span {
margin-left: 15px;
}
}
}
.el-aside {
background-color: #333744;
.el-menu {
border-right: none;
}
}
.el-main {
background-color: #eaedf1;
}
.iconfont {
margin-right: 10px;
}
.toggle-button {
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
letter-spacing: 0.5em;
cursor: pointer;
}
</style>
这里我们使用了 ElementUI 组件 el-container
、el-menu
- 头部区域
el-header
- 侧边栏区域
el-aside
- 主内容区域
el-main
退出
- 当点击退出按钮,调用
logout
方法 - 清空客户端 sessionStorage 中保存的 token,格式
window.sessionStorage.clear()
- 使用导航式编程跳转到登录页面,格式
this.$router.push('/login')
菜单
代码语言:javascript复制<el-menu
background-color="#333744"
text-color="#fff"
active-text-color="#409EFF"
unique-opened
:collapse="isCollapse"
:collapse-transition="false"
router
:default-active="activePath"
>
<!-- 一级菜单 -->
<el-submenu
v-for="(item, index) in menuList"
:key="item.id"
:index="index ''"
>
<!-- 一级菜单的模板 -->
<template slot="title">
<!-- 图标 -->
<i :class="iconsObj[item.id]"></i>
<!-- 文本 -->
<span>{{ item.authName }}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item
v-for="subItem in item.children"
:key="subItem.id"
:index="'/' subItem.path"
@click="saveNavState('/' subItem.path)"
>
<!-- 二级菜单的模板 -->
<template slot="title">
<!-- 图标 -->
<i class="el-icon-menu"></i>
<!-- 文本 -->
<span>{{ subItem.authName }}</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
el-menu
可选属性
- unique-opened 是否只保持一个子菜单的展开
- collapse 是否水平折叠收起菜单
- collapse-transition 是否开启折叠动画
- router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转,在二级菜单中,我们将
:index
设置为数据结果中的 path即可实现跳转,注意路径按需要是否添加/
- default-active 是否激活菜单选中状态
el-submenu
表示一级菜单
el-menu-item
表示二级菜单
<template slot="scope">
表示作用域插槽
生命周期函数
生命周期函数,详细参考文档
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
比如 created
钩子可以用来在一个实例被创建之后执行代码:
// 生命周期函数
created() {
this.getMenuList()
this.activePath = window.sessionStorage.getItem('activePath')
},
子路由渲染
- 当点击左侧二级菜单时,由于右侧内容区域添加了路由占位符
<router-view></router-view>
,用于给 Home 组件的子组件进行页面渲染 - 子组件路由配置 在
router/index.js
,根据 children 里面的 path 进行跳转
演示
User 模块
components/User.vue
代码语言:javascript复制<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区域 -->
<el-card class="box-card">
<!-- 搜索与添加区域
gutter 间距
-->
<el-row :gutter="20">
<el-col :span="8">
<!-- 数据双向绑定 -->
<el-input
placeholder="请输入内容"
clearable
@clear="getUserList"
v-model="queryInfo.query"
>
<el-button
@click="getUserList"
slot="append"
icon="el-icon-search"
></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="addDialogVisible = true"
>添加用户</el-button
>
</el-col>
</el-row>
<!-- 用户列表区域
:data 指定数据源
border 边框
stripe 斑马线
-->
<el-table :data="userList" border stripe>
<!-- type="index" 索引列 -->
<el-table-column type="index"></el-table-column>
<el-table-column label="姓名" prop="username"></el-table-column>
<el-table-column label="邮箱" prop="email"></el-table-column>
<el-table-column label="电话" prop="mobile"></el-table-column>
<el-table-column label="角色" prop="role_name"></el-table-column>
<el-table-column label="状态">
<!-- 作用域插槽渲染 -->
<template slot-scope="scope">
<!-- scope.row 相当于当前行的所有数据 -->
<el-switch
v-model="scope.row.mg_state"
@change="userStateChanged(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="175px">
<!-- 作用域插槽渲染 -->
<template slot-scope="scope">
<!-- 修改用户 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.id)"
></el-button>
<!-- 删除 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.id)"
></el-button>
<!-- 提示信息 -->
<el-tooltip
effect="dark"
content="分配角色"
placement="top"
:enterable="false"
>
<!-- 分配角色 -->
<el-button
type="warning"
icon="el-icon-setting"
size="mini"
@click="setRole(scope.row)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页区域
handleSizeChange 监听 pageSize 改变时会触发
handleCurrentChange 监听 currentPage 改变时会触发
:current-page 当前页数
:page-sizes 每页显示个数选择器的选项设置
:page-size 每页显示条目个数
layout 组件布局,子组件名用逗号分隔
:total 总条目数
-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[1, 2, 5, 10]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
<!-- 添加用户的对话框 -->
<el-dialog
title="添加用户"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<!-- 内容主体区域
:model 数据绑定对象
:rules 数据验证规则
ref 引用对象,可以拿到form实例
-->
<el-form
:model="addForm"
:rules="addFormRules"
ref="addFormRef"
label-width="70px"
>
<!-- prop 使用具体的校验规则 -->
<el-form-item label="用户名" prop="username">
<!-- v-model 数据双向绑定,绑定到 addForm 对象中 -->
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password" type="password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input v-model="addForm.mobile"></el-input>
</el-form-item>
</el-form>
<!-- 底部按钮区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addUser">确 定</el-button>
</span>
</el-dialog>
<!-- 修改用户的对话框 -->
<el-dialog
title="修改用户信息"
:visible.sync="editDialogVisible"
width="50%"
@close="editDialogClosed"
>
<el-form :model="editForm" ref="editFormRef" :rules="editFormRules">
<el-form-item label="用户名">
<!-- v-model 数据双向绑定,绑定到 editForm 对象中 -->
<el-input v-model="editForm.username" disabled></el-input>
</el-form-item>
<!-- prop 使用具体的校验规则 -->
<el-form-item label="邮箱" prop="email">
<!-- v-model 数据双向绑定,绑定到 editForm 对象中 -->
<el-input v-model="editForm.email"></el-input>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<!-- v-model 数据双向绑定,绑定到 editForm 对象中 -->
<el-input v-model="editForm.mobile"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="editUserInfo">确 定</el-button>
</span>
</el-dialog>
<!-- 分配角色的对话框 -->
<el-dialog
title="分配角色"
:visible.sync="setRoleDialogVisible"
width="50%"
@close="setRoleDialogClosed"
>
<div>
<el-form :model="userInfo" label-widtd="80px">
<el-form-item label="当前的用户">
<el-input v-model="userInfo.username" disabled></el-input>
</el-form-item>
<el-form-item label="当前的角色">
<el-input v-model="userInfo.role_name" disabled></el-input>
</el-form-item>
<el-form-item label="分配新角色">
<el-select
v-model="selectedRoleId"
placeholder="请选择"
style="width:100%"
>
<el-option
v-for="item in rolesList"
:key="item.id"
:label="item.roleName"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="setRoleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="saveRoleInfo">确 定</el-button>
</span>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
data() {
// 验证邮箱的规则
var checkEmail = (rule, value, callback) => {
const regEmail = /^([a-zA-Z]|[0-9])(w|-) @[a-zA-Z0-9] .([a-zA-Z]{2,4})$/
if (regEmail.test(value)) {
// 合法
return callback()
}
callback(new Error('请输入合法的邮箱'))
}
// 验证手机号的规则
var checkMobile = (rule, value, callback) => {
const regMobile = /^[1][3,4,5,7,8][0-9]{9}$/
if (regMobile.test(value)) {
// 合法
return callback()
}
callback(new Error('请输入合法的手机'))
}
return {
// 获取用户列表的参数对象
queryInfo: {
query: '',
// 当前的页数
pagenum: 1,
// 当前每页显示多少条数据
pagesize: 2
},
// 用户列表
userList: [],
// 列表总数
total: 0,
// 控制添加用户对话框的显示与隐藏
addDialogVisible: false,
// 添加用户的表单数据
addForm: {
username: '',
password: '',
email: '',
mobile: ''
},
// 添加表单的验证规则对象
addFormRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{
min: 3,
max: 10,
message: '用户名长度在 3 - 10 个字符之间',
trigger: 'blur'
}
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
min: 3,
max: 10,
message: '密码长度在 6 - 12 个字符之间',
trigger: 'blur'
}
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }
],
mobile: [
{ required: true, message: '请输入手机', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }
]
},
// 控制修改用户对话框的显示与隐藏
editDialogVisible: false,
// 查询到的用户信息对象
editForm: {},
// 修改表单的验证规则对象
editFormRules: {
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }
],
mobile: [
{ required: true, message: '请输入手机', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }
]
},
// 控制分配角色对话框的显示与隐藏
setRoleDialogVisible: false,
// 需要被分配角色的用户信息
userInfo: {},
// 所有角色的数据列表
rolesList: [],
// 已选中的角色id值
selectedRoleId: ''
}
},
created() {
this.getUserList()
},
methods: {
async getUserList() {
const { data: res } = await this.$http.get('users', {
params: this.queryInfo
})
console.log(res)
if (res.meta.status !== 200) {
return this.$message.err('获取用户列表失败')
}
this.userList = res.data.users
this.total = res.data.total
},
// 监听 条数 改变的事件
handleSizeChange(newSize) {
// console.log(newSize)
this.queryInfo.pagesize = newSize
this.getUserList()
},
// 监听 页码 改变的事件
handleCurrentChange(newPage) {
// console.log(newPage)
this.queryInfo.pagenum = newPage
this.getUserList()
},
// 监听 switch 开关状态的改变
async userStateChanged(userInfo) {
console.log(userInfo)
const { data: res } = await this.$http.put(
`users/${userInfo.id}/state/${userInfo.mg_state}`
)
if (res.meta.status !== 200) {
userInfo.mg_state = !userInfo.ms_state
return this.$message.err(res.meta.msg)
}
this.$message.success(res.meta.msg)
},
// 监听添加用户对话框的关闭事件
addDialogClosed() {
// 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
this.$refs.addFormRef.resetFields()
},
// 点击按钮添加新用户
addUser() {
this.$refs.addFormRef.validate(async valid => {
// console.log(valid)
if (!valid) {
return
}
// 可以发起添加用户的网络请求
const { data: res } = await this.$http.post('users', this.addForm)
if (res.meta.status !== 201) {
this.$message.error(res.meta.msg)
}
// 隐藏添加用户的对话框
this.addDialogVisible = false
// 刷新列表
this.getUserList()
// 提示信息
this.$message.success(res.meta.msg)
})
},
// 展示编辑用户的对话框
async showEditDialog(id) {
// console.log(id)
const { data: res } = await this.$http.get('users/' id)
// console.log(res)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.editDialogVisible = true
this.editForm = res.data
},
// 监听修改用户对话框的关闭事件
editDialogClosed() {
this.$refs.editFormRef.resetFields()
},
// 修改用户信息并提交
editUserInfo() {
this.$refs.editFormRef.validate(async valid => {
console.log(valid)
if (!valid) {
return
}
// 发起修改用户信息的数据请求
const { data: res } = await this.$http.put(
'users/' this.editForm.id,
{
email: this.editForm.email,
mobile: this.editForm.mobile
}
)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
// 隐藏添加用户的对话框
this.editDialogVisible = false
// 刷新列表
this.getUserList()
// 提示信息
this.$message.success(res.meta.msg)
})
},
// 根据id删除用户
async removeUserById(id) {
// console.log(id)
// 弹框询问用户是否删除数据
const confirmRes = await this.$confirm(
'此操作将永久删除该用户, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(err => err) // 捕获了用户的取消行为的异常,下面正常输出 cancel
// 如果用户确认删除,则返回值为字符串 confirm
// 如果用户取消删除,则返回值为字符串 cancel
// console.log(res)
if (confirmRes !== 'confirm') {
return this.$message.info('已经取消删除操作')
}
const { data: res } = await this.$http.delete('users/' id)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
this.getUserList()
},
// 展示分配角色的对话框
async setRole(userInfo) {
this.userInfo = userInfo
// 在展示对话框之前,获取所有角色的列表
const { data: res } = await this.$http.get('roles')
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.rolesList = res.data
this.setRoleDialogVisible = true
},
// 点击按钮,保存分配角色
async saveRoleInfo() {
if (!this.selectedRoleId) {
return this.$message.error('请选择要分配的角色')
}
const { data: res } = await this.$http.put(
`users/${this.userInfo.id}/role`,
{
rid: this.selectedRoleId
}
)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
this.getUserList()
this.setRoleDialogVisible = false
},
// 监听分配角色对话框的关闭事件
setRoleDialogClosed() {
this.selectedRoleId = ''
this.userInfo = {}
}
}
}
</script>
<style lang="less" scoped></style>
这里我们使用了 ElementUI 组件 el-breadcrumb
、 el-card
、 el-row
、 el-table
、 el-pagination
、 el-dialog
等等
搜索栏
代码语言:javascript复制<el-row :gutter="20">
<el-col :span="8">
<!-- 数据双向绑定 -->
<el-input
placeholder="请输入内容"
clearable
@clear="getUserList"
v-model="queryInfo.query"
>
<el-button
@click="getUserList"
slot="append"
icon="el-icon-search"
></el-button>
</el-input>
</el-col>
</el-row>
- 在
el-input
中使用了数据双向绑定v-model="queryInfo.query"
,当点击el-button
调用 getUserList 方法时候,该queryInfo
会作为参数传入接口中进行查询 - clearable 表示是否可清空,默认false,显式调用为 true
- clear 在点击由
clearable
属性生成的清空按钮时触发 - 在
el-button
中slot="append"
是设置UI样式,紧追el-input
框
用户列表
代码语言:javascript复制<!-- 用户列表区域
:data 指定数据源
border 边框
stripe 斑马线
-->
<el-table :data="userList" border stripe>
<!-- type="index" 索引列 -->
<el-table-column type="index"></el-table-column>
<el-table-column label="姓名" prop="username"></el-table-column>
<el-table-column label="邮箱" prop="email"></el-table-column>
<el-table-column label="电话" prop="mobile"></el-table-column>
<el-table-column label="角色" prop="role_name"></el-table-column>
<el-table-column label="状态">
<!-- 作用域插槽渲染 -->
<template slot-scope="scope">
<!-- scope.row 相当于当前行的所有数据 -->
<el-switch
v-model="scope.row.mg_state"
@change="userStateChanged(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="175px">
<!-- 作用域插槽渲染 -->
<!-- <template slot-scope="scope"> -->
<template v-slot="scope">
<!-- 修改用户 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.id)"
></el-button>
<!-- 删除 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.id)"
></el-button>
<!-- 提示信息 -->
<el-tooltip
effect="dark"
content="分配角色"
placement="top"
:enterable="false"
>
<!-- 分配角色 -->
<el-button
type="warning"
icon="el-icon-setting"
size="mini"
@click="setRole(scope.row)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
- 当
el-table
元素中注入data
对象数组后,在el-table-column
中用prop
属性来对应对象中的键名即可填入数据,用label
属性来定义表格的列名。 - 通过
Scoped slot
可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据
用户列表动态数据
代码语言:javascript复制<el-table-column label="状态">
<!-- 作用域插槽渲染 -->
<template slot-scope="scope">
<!-- scope.row 相当于当前行的所有数据 -->
<el-switch
v-model="scope.row.mg_state"
@change="userStateChanged(scope.row)"
>
</el-switch>
</template>
......
这里使用新的 ElementUI 组件 Switch 开关
- 绑定
v-model
到一个Boolean
类型的变量。 @change
switch 状态发生变化时的回调函数
列表分页
代码语言:javascript复制<!-- 分页区域
handleSizeChange 监听 pageSize 改变时会触发
handleCurrentChange 监听 currentPage 改变时会触发
:current-page 当前页数
:page-sizes 每页显示个数选择器的选项设置
:page-size 每页显示条目个数
layout 组件布局,子组件名用逗号分隔
:total 总条目数
-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[1, 2, 5, 10]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
这里使用新的 ElementUI 组件 Pagination 分页
size-change
pageSize 改变时会触发current-change
currentPage 改变时会触发current-page
当前页数,支持 .sync 修饰符page-sizes
每页显示个数选择器的选项设置page-size
每页显示条目个数,支持 .sync 修饰符layout
组件布局,子组件名用逗号分隔total
总条目数
// 监听 条数 改变的事件
handleSizeChange(newSize) {
// console.log(newSize)
this.queryInfo.pagesize = newSize
this.getUserList()
},
// 监听 页码 改变的事件
handleCurrentChange(newPage) {
// console.log(newPage)
this.queryInfo.pagenum = newPage
this.getUserList()
}
添加用户
代码语言:javascript复制<el-col :span="4">
<el-button type="primary" @click="addDialogVisible = true">添加用户</el-button>
</el-col>
当点击添加用户按钮,会触发 addDialogVisible = true
事件,弹出Dialog对话框
<!-- 添加用户的对话框 -->
<el-dialog
title="添加用户"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<!-- 内容主体区域
:model 数据绑定对象
:rules 数据验证规则
ref 引用对象,可以拿到form实例
-->
<el-form
:model="addForm"
:rules="addFormRules"
ref="addFormRef"
label-width="70px"
>
<!-- prop 使用具体的校验规则 -->
<el-form-item label="用户名" prop="username">
<!-- v-model 数据双向绑定,绑定到 addForm 对象中 -->
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password" type="password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input v-model="addForm.mobile"></el-input>
</el-form-item>
</el-form>
<!-- 底部按钮区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addUser">确 定</el-button>
</span>
</el-dialog>
el-dialog
:visible.sync
是否显示 Dialog,支持 .sync 修饰符,必须在data()
中定义该 boolean 值@close
特定事件,Dialog 关闭的回调
el-form
:model
数据绑定的对象,必须在data()
中定义:rules
数据验证规则,必须在data()
中定义ref
表单的实例,可以通过this.$refs.xxRef
调用指定表单实例
el-form-item
prop
属性,设置为需校验的字段名即可,必须在data()
中定义v-model
数据双向绑定,必须在data()
中定义
el-button
当点击确定按钮,会调用 @click="addUser"
事件
// 点击按钮添加新用户
addUser() {
this.$refs.addFormRef.validate(async valid => {
// console.log(valid)
if (!valid) {
return
}
// 可以发起添加用户的网络请求
const { data: res } = await this.$http.post('users', this.addForm)
if (res.meta.status !== 201) {
this.$message.error(res.meta.msg)
}
// 隐藏添加用户的对话框
this.addDialogVisible = false
// 刷新列表
this.getUserList()
// 提示信息
this.$message.success(res.meta.msg)
})
}
- 使用
this.$refs.xxRef
调用表单实例的 validate 方法,该方法对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise,这里使用 ES6 语法解构,如果不通过直接返回。 - 当预验证通过后,可以发起添加用户的网络请求,此时的
this.addForm
就是对话框的内容 - 如果添加成功,隐藏对话框,刷新列表,提示信息即可
修改用户
代码语言:javascript复制<el-table-column label="操作" width="175px">
<!-- 作用域插槽渲染 -->
<template v-slot="scope">
<!-- 修改用户 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="showEditDialog(scope.row.id)"
></el-button>
......
tips: 参考文档:插槽 参考文档:作用域插槽 在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即
v-slot
指令)。它取代了slot
和slot-scope
这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC。 在向具名插槽提供内容的时候,我们可以在一个<template>
元素上使用v-slot
指令,并以v-slot
的参数的形式提供其名称 现在<template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-slot
的<template>
中的内容都会被视为默认插槽的内容。 注意v-slot
只能添加在<template>
上 (只有一种例外情况),这一点和已经废弃的slot
attribute 不同。
- 这里使用作用域插槽
v-slot="scope"
渲染,其中scope
为自定义名称,当调用scope.row
代表的是当前行的所有数据 - 当点击修改按钮,会调用
@click="showEditDialog(scope.row.id)"
事件,把当前行的id值scope.row.id
作为参数传入 - 使用 axios 请求API
this.$http.get('users/' id)
获取当前该id的用户,并设置this.editForm = res.data
为该用户的信息
删除用户
代码语言:javascript复制<template v-slot="scope">
<!-- 删除 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="removeUserById(scope.row.id)"
></el-button>
......
代码语言:javascript复制// 根据id删除用户
async removeUserById(id) {
// console.log(id)
// 弹框询问用户是否删除数据
const confirmRes = await this.$confirm(
'此操作将永久删除该用户, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(err => err) // 捕获了用户的取消行为的异常,下面正常输出 cancel
// 如果用户确认删除,则返回值为字符串 confirm
// 如果用户取消删除,则返回值为字符串 cancel
// console.log(res)
if (confirmRes !== 'confirm') {
return this.$message.info('已经取消删除操作')
}
const { data: res } = await this.$http.delete('users/' id)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
this.getUserList()
}
这里引用了一个新的 ElementUI 组件 MessageBox 弹框, 需要导入并挂载在全局Vue中
代码语言:javascript复制// MessageBox 要挂载在全局 Vue 的一个原型(prototype)上
Vue.prototype.$confirm = MessageBox.confirm
- 使用
this.$confirm
调用弹框,当点击取消会抛出异常,捕获后返回 cancel 字符串,当点击确定会返回 confirm 字符串,通过这两个字符串判断用户的实际操作 - 通过 axios 调用 API
this.$http.delete('users/' id)
,会返回 Promise 对象,可以使用 ES6 语法解构
分配角色
代码语言:javascript复制<!-- 提示信息 -->
<el-tooltip
effect="dark"
content="分配角色"
placement="top"
:enterable="false"
>
<!-- 分配角色 -->
<el-button
type="warning"
icon="el-icon-setting"
size="mini"
@click="setRole(scope.row)"
></el-button>
</el-tooltip>
这里使用了新的 ElementUI 组件 Tooltip 文字提示
effect
默认提供的主题,可选dark/light
placement
Tooltip 的出现位置enterable
鼠标是否可进入到 tooltip 中
<!-- 分配角色的对话框 -->
<el-dialog
title="分配角色"
:visible.sync="setRoleDialogVisible"
width="50%"
@close="setRoleDialogClosed"
>
<div>
<el-form :model="userInfo" label-widtd="80px">
<el-form-item label="当前的用户">
<el-input v-model="userInfo.username" disabled></el-input>
</el-form-item>
<el-form-item label="当前的角色">
<el-input v-model="userInfo.role_name" disabled></el-input>
</el-form-item>
<el-form-item label="分配新角色">
<el-select
v-model="selectedRoleId"
placeholder="请选择"
style="width:100%"
>
<el-option
v-for="item in rolesList"
:key="item.id"
:label="item.roleName"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="setRoleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="saveRoleInfo">确 定</el-button>
</span>
</el-dialog>
这里使用了新的 ElementUI 组件 Select 选择器
el-select
中的v-model
的值为当前被选中的el-option
的 value 属性值,该值要在data()
中定义el-option
使用v-for
遍历角色集合,其中value
是选项的值,label
选项的标签,若不设置则默认与value
相同