电商后台管理系统
一、初始化项目
1.安装Vue 脚手架
npm install --g vue-cli
2.通过Vue 脚手架创建项目
方式一:
vue create 项目名
方式二:
通过可视化面板
安装插件:vue-cli-plugin-element
安装运行依赖:axios
- 运行项目
npm run serve
3.初始化git远程仓库
(1)注册
(2)添加公钥
(3)创建仓库
(4)本地仓库托管到远程仓库中
- 创建远程仓库别名
git remote add origin https://gitee.com/xiao1tao/vue_electricitymanagementsystem.git
- 推送本地分支到远程仓库
git push -u origin master
4.配置后台项目
(1)安装MySQL数据库
(2)安装Node.js环境
(3)导入提供的数据库
(4)运行后台项目
- 安装依赖
npm install
- 运行
node .\app.js
(5)测试接口
二、登录与退出功能
1.login页面创建基本步骤
在components文件夹中创建Login.vue文件,
在router.js文件中导入组件Login.vue,
在App.vue根组件中方路由占位符**< router-view>< /router-view>,重定向(redirect)根目录到login**,
2.基本页面布局
(1)安装依赖less与less-loader
npm install less@3.9.0 less-loader@4.1.0 --save-dev
(2) 引用element-ui组件
- 官方复制组件
- 自己组件中粘贴复制的组件
- 在plugins文件夹中的element.js中导入所用组件
(3)引入icon图标
通过element中input表单的prefix-icon属性给表单添加icon图标
将图标文件fonts放在assets文件夹下,在main.js文件中引入字体图标
import './assets/fonts/iconfont.css'
(4)全局样式
在assets创建css文件夹,在css文件夹中创建global.css文件,其中书写全局样式,在main.js中导入全局样式。
import './assets/css/global.css'
3.页面功能实现
(1)表单数据绑定
为el-form绑定属性**:model="loginForm",为输入框el-input绑定属性v-model="loginForm.username",密码框类似,密码框设为只读添加属性type="password"**
<el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima" type="password" \></el-input>
(2)表单验证规则
Form 组件提供了表单验证的功能,只需要通过
rules
属性传入约定的验证规则,并将 Form-Item 的prop
属性设置为需校验的字段名即可
- 给el-for添加属性**:rules="loginFormRules"**
- 在data中添加验证规则
- 给el-form-item添加验证规则prop="username",注意不是el-input中添加。
(3)表单的重置
首先为表单添加ref引用,引用的值就是表单的实例对象,然后通过this.$refs拿到表单的引用对象(即表单的实例对象),再通过调用表单的resetFields方法重置表单
- 首先为表单el-form添加ref引用ref="loginFormRef",loginFormRef就是表单的实例对象;
- 给重置按钮添加点击事件,执行this.$refs.loginFormRef.resetFields()。
4)登录前的预验证
当点击登录按钮时,不因该直接发起网络请求,而是先对表单进行预验证,当验证通过之后再发起网络请求,否则提示用户表单数据不合法。
原理:通过表单的实例对象调用validate方法。具体用法可查看官网。
- 给登录按钮添加点击事件;
- 点击事件中通过实例对象调用validate方法this.$refs.loginFormRef.validate()
(5)配置axios
- 首先在main.js中导入axios
import axios from 'axios'
- 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
- 将axios挂载到vue的原型对象上(这样每一个组件都可以通过this直接访问到$http,从而发起axios请求)
Vue.prototype.$http = axios
(6)发起登录请求
post请求的第二个参数类型是对象,是发送请求所携带的参数,因为this.loginForm是对象,所以可以直接写this.loginForm;
post请求的返回值是promise,所以可以通过async和await简化promise;
通过解构赋值获取返回结果中可能有用的data数据;
- 首先判断预验证是否成功;
- 成功后发起post请求;
- 解构赋值后通过状态码判断是否请求成功
login() {
this.$refs.loginFormRef.validate(async valid => {
if(!valid) return;
const {data: res} = await this.$http.post('login', this.loginForm) // post请求的第二个参数类型是对象,是发送请求所携带的参数,因为this.loginForm是对象,所以可以直接写this.loginForm
if(res.meta.status !== 200) return this.$message.error("登录失败!");
this.$message.success("登录成功!");
})
}
(7)登录的弹窗提示
- 首先在element.js中挂载Message到Vue原型对象上(这样所有组件都可以通过this访问**$message**,从而可以进行弹框提示)
import {Message} from 'element-ui'
Vue.prototype.$message = Message
- 设置弹出提示
this.$message.error("登录失败!")
(8)登录成功后的行为
- 将token保存到客户端sessionStorage中
window.sessionStorage.setItem("token", res.data.token)
- 通过编程式导航跳转到后台主页
this.$router.push('/home')
(9)路由导航首位控制访问权限
如果用户没有登录,但是直接通过URL访问特定页面,需要重新导航到登录页面。即home页面应该是登录状态才允许被访问,未登录状态因该是不允许访问的。
- 在router文件夹中的index.js文件中挂载路由导航守卫
// 挂载路由导航守卫
router.beforeEach((to, from, next) => {
// to: 将要访问的路径
// from 代表从哪个路径跳转而来
// next: 是一个函数,表示放行
// next() 放行 next('/login') 强行跳转
if(to.path === '/login') return next()
// 获取token
const tokenStr = window.sessionStorage.getItem('token')
if(!tokenStr) return next('/login')
next()
})
(10)退出功能
在home页面中添加退出按钮,添加点击事件,实现退出功能(清空token,跳转到登录页)
- 给退出按钮添加点击事件logout
window.sessionStorage.clear();
this.$router.push('/login')
(11)提交代码到远程仓库
- 提交本地仓库到远程仓库
git push -u origin master
- 提交login分支到远程仓库
git push -u origin login
三、主页布局
1.整体页面布局
(1)整体容器布局
使用element-ui中的Container 布局容器。
- 复制所需的容器布局到home页面(此时template标签内不用div标签了)
- 修改容器背景颜色及大小(每一个element-ui组件名就是一个类名,如el-main就是一个类名)
(2)head区域结构布局
使用flex布局实现左右贴边,css中使用嵌套布局
2.左侧菜单布局
(1)基本布局
使用element-ui的NavMenu导航菜单,el-submenu一级菜单,el-menu-item二级菜单
(2)请求拦截器
需要授权的 API ,必须在请求头中使用
Authorization
字段提供token
令牌,所以需要请求拦截器,在请求前对请求进行预处理,然后再发送请求
- 通过axios请求拦截器添加token,保证拥有获取数据的权限
import axios from 'axios'
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/' // 配置请求的根路径
// 请求拦截器
axios.interceptors.request.use(config => {
config.headers.Authorization = window.sessionStorage.getItem('token')
// 在最后必须return config
return config
})
Vue.prototype.$http = axios
(3)通过请求获取菜单数据
当home刚一加载时,就要获取左侧菜单,所以需要使用生命周期函数created.
- 在created中调用函数getMenuList
this.getMenuList
- 在methods中书写gitMenuList函数
// 获取所有的菜单
async getMenuList() {
const {data: res} = await this.$http.get('menus')
if(res.meta.status !== 200) return this.$message.error(res.meta.msg)
this.menulist = res.data
console.log(this.menulist);
}
- 在data中声明一个空数组menulist用于存放请求过来的有用的数据
this.menulist = res.data
(4)渲染左侧菜单数据
通过v-for渲染一级二级菜单
- 渲染一级菜单
<el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">
- 渲染二级菜单,和渲染一级菜单相同
<el-menu-item :index="subItem.id + ''" v-for="subItem in item.children" :key="subItem.id">
- 注: 由于每次展开某个一级菜单,所有一级菜单都会展开,原因是index值相同,所以给每个一级菜单动态绑定不同的index值
(5)美化左侧菜单
- 设置菜单文字高亮颜色: el-menu标签的active-text-color属性设置为所需高亮颜色
- 更改二级菜单图标: 在官网寻找icon图片然后更改i标签类名
- 更改一级菜单图标: 由于一级菜单图标都不一样而且element官网中没有某写图标,且都是动态渲染,所以我们可以把字体图标库中的图标名存放到data中,再进行动态渲染,片段代码实例:
<!-- 图标 -->
<i :class="iconsObj[item.id]"></i>
data() {
return {
iconsObj: {
'125': 'iconfont icon-user',
'103': 'iconfont icon-tijikongjian',
'101': 'iconfont icon-shangpin',
'102': 'iconfont icon-danju',
'145': 'iconfont icon-baobiao'
}
}
}
- 展开菜单只展开一个,展开一个菜单时其他菜单关闭: 给el-menu标签添加属性unique-opened,默认true
(6)左侧菜单的折叠与展开功能
- 在el-aside内部,el-menu之前添加一个div,并添加样式和绑定事件
<div class="toggle-button" @click="toggleCollapse">|||</div>
- menu有一个属性collapse用于控制是否水平折叠收起菜单,所以给el-menu动态绑定属性collapse,所以可以通过点击事件切换collapse的值,
this.isCollapse = !this.isCollapse
- 此时有个默认动画(很丑),将menu的collapse-transition属性默认值由true改为false,从而关闭动画
:collapse-transition="false"
- 此时菜单折叠了,但是背景区域没有变小,原因是el-aside宽度为固定值200,没有发生变化,所以可以根据是否折叠改变宽度.
<el-aside :width="isCollapse ? '64px' : '200px'">
(7)首页路由的重定向
登录成功后,main区域展示欢迎提示,给home路由添加重定向到它的子路由welcome
- components文件夹中定义welcome组件;
- 将welcome作为home子路由(嵌套路由),并给home重定向到welcome;
(8)将左侧菜单改为路由连接
menu的router属性: 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转.
- 给el-menu标签添加router属性,默认为true
- 更改二级菜单index值
<el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children" :key="subItem.id">
3.用户列表开发
3.1 基本布局
(1)创建用户列表组件
在components文件夹中创建文件user文件夹,在user文件夹中创建文件Users.vue.
- 创建Users.vue组件
- 将Users作为home的子路由(因为Users列表也是显示下main区域的)
(2)解决二级菜单点击不高亮问题
menu的default-active属性,当二级菜单el-menu-item的index值与menu的default-active属性的值相同时,激活高亮.(也就是说你想要哪个菜单高亮,就需要将菜单的index值赋给menu的default-active属性).
实现原理:点击某个二级导航时,将二级导航的index保存到sessionStorage中,然后点击后就会刷新页面,刷新页面重新渲染页面时(使用声明周期函数created)再将值取出来赋值给default-active,也就行点击后,先保存index值,再赋值.
- 给el-menu动态绑定default-active属性
:default-active="activePath"
- 给二级菜单绑定单击事件
@click="saveNavState('/' + subItem.path)"
- data中定义activePath保存激活的index
- 将activePath动态绑定到default-active上
- 在created中给data中的activePath赋值
this.activePath = window.sessionStorage.getItem('activePath')
执行过程: 首先点击一个二级菜单后,将index存到sessionStorage中,将点击事件传递过来的index赋值给data中的activePath,然后此时页面就会重新渲染,刚一渲染完成就执行created中的将sessionStorage的index保存到data的activePath中.
(3)绘制用户列表的基本UI结构
- 绘制面包屑导航区域el-breadcrumb
- 绘制卡片视图区域el-card
- 绘制搜索和添加按钮区域
- 通过官方提供的Layout布局(一行24个格子)的分栏间隔控制搜索框与按钮框的位置
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容">
<el-button slot="append" icon="el-icon-search"></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary">添加用户</el-button>
</el-col>
</el-row>
(4)获取用户列表数据
当一点击到用户列表,就要加载用户列表数据,所以在页面一创建就要获取用户列表数据,所以要在生命周期函数上created发送请求获取数据.
- created上执行getUserList方法,getUserList方法中是关于请求的代码
this.getUserList()
- methods中编写getUserList函数
async getUserList() {
const {data: res} = await this.$http.get('users', { params: this.queryInfo})
if(res.meta.status !== 200) {
return this.$message.error("获取用户列表失败")
}
// 将请求的数据保存到data中去
this.userlist = res.data.users
this.totle = res.data.total
console.log(res);
}
3.2 用户列表中的表格
(1)渲染用户列表数据
使用element-ui中的Table 表格,el-table的data绑定数据源,即userlist, el-table-column的label是表格列题目, prop是表格内容.el-table的border属性为表格添加边框, stripe属性为表格设置为带斑马纹表格. 设置索引列,给el-table-column的type属性设置为index
(2)渲染表格中状态列的显示效果
通过作用域插槽,获取表格那一行的所有数据,从而获得mg_stats的布尔值,再将其绑定到el-switch上,从而控制开关
- 在状态那一格中的el-table-column中插入template模板(用于获取这一行表格的数据)
- 在template模板中插入switch开关
- switch开关双向绑定mg_state
<el-switch v-model="scope.row.mg_state"></el-switch>
<el-table-column label="状态" prop="mg_state">
<!-- 作用域插槽必须要在template中,子插槽可以理解为是表格中的每一行,然后通过作用域插槽获取这一行的数据 -->
<template slot-scope="scope">
{{ scope.mg_state }}
<el-switch v-model="scope.row.mg_state"></el-switch>
</template>
</el-table-column>
(3)渲染表格的操作列表
通过作用域插槽渲染操作列表,以为删除等操作需要数据,所以只能通过作用域插槽
添加三个按钮(el-input)
更改按钮大小,颜色,icon
使用el-tooltip给按钮添加提示
<el-table-column label="操作" width="180px">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-tooltip effect="dark" content="编辑角色" placement="top" :enterable="false">
<el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
</el-tooltip>
<!-- 删除按钮 -->
<el-tooltip effect="dark" content="删除角色" placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-delete" size="mini"></el-button>
</el-tooltip>
<!-- 分配角色按钮 -->
<el-tooltip effect="dark" content="分配角色" placement="top" :enterable="false">
<el-button type="warning" icon="el-icon-setting" size="mini"></el-button>
</el-tooltip>
</template>
</el-table-column>
(4)实现表格数据的分页显示效果
使用element-ui中的Pagination 分页的el-pagination;
主要就是el-pagination的使用.
(5)实现表格中用户状态的修改
使用el-switch的change事件
- 给el-switch绑定change事件
<el-switch v-model="scope.row.mg_state" @change="userStateChanged(scope.row)"></el-switch>
- 声明userStateChanged函数
// 监听switch开关的改变
async userStateChanged(userinfo) {
const {data: res} = await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
if(res.meta.status !== 200) {
userinfo.mg_state = !userinfo.mg_state
return this.$message.error('更新用户状态失败')
}
this.$message.success("更新用户状态成功")
}
(6)实现表格上的搜索功能
- 将inpunt文本框与queryInfo双向绑定
v-model="queryInfo.query"
- 为搜索图标按钮绑定单机事件(搜索有缺陷,只能搜索当前页的内容,不是全局搜索)
@click="getUserList"
- 为input文本框添加清楚功能:给input文本框添加属性clearable
- 当input文本框清空后表格页回复开始样式,给input文本框添加clear事件
@clear="getUserList"
3.3 添加用户按钮
(1)实现添加用户的功能
使用Dialog 对话框组件。
需要设置
visible
属性,它接收Boolean
,当为true
时显示 Dialog。Dialog 分为两个部分:body
和footer
,footer
需要具名为footer
的slot
。title
属性用于定义标题,它是可选的,默认值为空。
- 复制粘贴组件
- data中添加addDialogVisible控制添加用户对话框的显示与隐藏
(2)添加用户对话框中渲染添加用户的表单
使用Form 表单表单中的表单验证。
在el-form中通过**:model="ruleForm"绑定数据源,通过:rules="rules"**绑定验证规则,**ref="ruleForm"**表示数据的引用(可以通过它获取表单数据)。
在el-form-item通过prop设置验证规则(rules)中的字段名.
在el-input中通过**v-model="ruleForm.name"**绑定数据源中的某个数据。
(3)实现邮箱与手机的自定义验证规则
使用Form 表单表单中的自定义表单验证。
使用步骤:在data(在return之前定义,且之间分号隔开)中一个函数这个就是自定义的验证规则,然后在响应验证字段通过validator添加自定义的验证规则。
- data中自定义验证规则
data() {
// 验证邮箱的自定义验证规则
var checkEmail = (rule, value, cb) => {
var regEmail = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
if (regEmail.test(value)) {
// 合法的邮箱
return cb();
} else {
cb(new Error("邮箱码格式不正确"));
}
};
return {},
- 在验证字段中通过validator添加自定义验证规则
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{ validator: checkEmail, trigger: "blur" },
],
(4)实现添加用户表单的重置功能
当点击添加用户,输入了一些信息后退出对话框,当再点击进去时上一次输入的信息还带那里,所以需要对添加用户表单进行重置。
- 给添加用户对话框添加close事件
@close="addDialogClosed"
- 在close事件addDialogClosed中触发表单的resetFields()方法重置表单。
this.$refs.addFormRef.resetFields()
(5)添加用户的表单预验证
- 给对话框表单的确定按钮添加点击事件
@click="addUser"
- 事件中通过表单的引用(this.$refs)获取表单实例对象(this.$refs.addFormRef),从而触发表单的validate事件进行表单验证
this.$refs.addFormRef.validate()
(6)发起网络请求添加新用户
addUser() {
this.$refs.addFormRef.validate(async (valid) => {
if (!valid) return;
// 发起添加用户的请求'
const { data: res } = await this.$http.post("users", this.addForm);
if (res.meta.status !== 201) {
this.$message.error("添加用户失败");
} else {
this.$message.success("添加用户成功");
// 隐藏对话框
this.addDialogVisible = false;
// 重新获取用户列表数据
this.getUserList();
}
});
},
3.4 修改用户按钮
(1)修改用户信息对话框的渲染
和添加用户对话框方法一致.
(2)根据ID查询信息
- 给修改按钮的点击事件通过作用域插槽传入id作为事件的参数
@click="showEditDialog(scope.row.id)"
- 发起请求
- 将请求得到的数据保存到data中的editForm中
(3)默认补充修改表单的原本内容
- 将修改用户框的表单数据源绑定到editForm
<el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="70px">
- 再对input文本框和editForm中的内容进行双向绑定
(4)实现修改表单关闭之后的重置操作
给修改用户的对话框添加close事件,在close事件中通过表单的引用调用表单实例对象上的resetFields方法
(5)修改表单提交前的预验证
给修改用户表单的确定按钮添加点击事件editUserInfo,在事件中通过表单的引用调用表单实例对象上的validate方法
(6)发送请求修改用户信息
// 修改用户信息并提交
editUserInfo() {
this.$refs.editFormRef.validate(async 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("更新用户信息失败!")
}
this.editDialogVisible = false
this.getUserList()
this.$message.success("更新用户信息成功!")
})
}
3.5 删除用户按钮
(1)添加弹框提示
使用element-ui中的 MessageBox 弹框组件。使用前提需要挂载config函数,然后点击事件中通过this.$confirm添加弹出提示,在弹出框中如果点击 确定 则返回confirm字符串,如果点击 取消,将会报错,所以要catch,此时点击取消,则返回cancel字符串。
- 导入MessageBox组件,并挂载confirm(这样所有组件都可以通过this访问$confirm,从而可以进行弹框提示)
Vue.prototype.$confirm =MessageBox.confirm
- 给删除按钮的点击事件(removeUserById)中添加弹窗提示
// 根据id删除对应用户信息
async removeUserById() {
// 弹框询问是否删除
const res = await this.$confirm("此操作将永久删除该用户?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).catch( err => err)
// 在弹出框中如果点击 确定 则返回confirm字符串
// 在弹出框中如果点击 取消 则返回cancel字符串
if(res !== 'confirm') {
return this.$message.info('已取消删除')
}
},
(2)发送请求删除用户数据
// 根据id删除对应用户信息
async removeUserById(id) {
// 弹框询问是否删除
const resResult = await this.$confirm("此操作将永久删除该用户?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).catch( err => err)
// 在弹出框中如果点击 确定 则返回confirm字符串
// 在弹出框中如果点击 取消 则返回cancel字符串
if(resResult !== 'confirm') {
return this.$message.info('已取消删除')
}
const {data: res} = await this.$http.delete('users/' + id)
if(res.meta.status !== 200) {
return this.$message.error("删除用户失败")
}
this.$message.success("删除用户成功")
this.getUserList()
},
3.5 分配角色按钮
(1)给分配角色按钮添加弹出对话框
使用el-dialog。
(2)将用户信息渲染到页面中
(3)发送请求获取所有角色列表
// 展示分配角色的对话框
async setRole(userInfo) {
this.userInfo = userInfo
// 在展示对话框之前获取所有角色列表
const {data:res} = await this.$http.get('roles')
if(res.meta.status !== 200) {
return this.$message.error('获取角色列表失败')
}
this.rolesList = res.data
this.setRoleDialogVisible = true
}
(4)将获取到的角色列表渲染为下拉菜单
使用el-select组件,v-model双向绑定下拉菜单选中的值,下拉列表通过v-for循环渲染出来。el-option中:label为看到的文本内容,:value为选中的值
(5)实现分配角色功能
// 点击按钮,分配角色
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('更新角色失败!')
}
this.$message.success("更新角色成功!")
this.getUserList()
this.setRoleDialogVisible = false
},
(6)重置下拉列表
监听对话框的关闭事件close,重置。
4.权限列表开发
4.1 基本布局
(1)创建权限列表组件
在components文件夹中创建power文件夹,并在文件夹中创建Rights.vue。同时在router文件夹中的index.js中导入Rights组件,并添加为Home路由的子路由。
(2)添加面包屑导航和卡片视图
4.2 获取数据并渲染
(1)请求权限列表数据
在data中定义rightsList存放获取的权限列表数据,在created中调用getRightsList函数,在methods中定义getRightsList函数,用于请求获取数据并将数据保存到rightsList中。
(2)权限列表表格的渲染
使用el-table标签,给它动态绑定data到rightsList,同时添加边框border和斑马纹stripe;然后使用el-table-column添加表格列,它的prop属性绑定表格的内容(即rightsList中内容),label为表格列名称。
(3)美化表格中的权限等级
使用Tag标签,将Tag标签通过作用域插槽添加到表格中的权限等级那一格,再通过v-if,v-else-if,v-else判断渲染那种等级的标签。
<el-table-column prop="level" label="权限等级">
<template slot-scope="scope">
<el-tag v-if="scope.row.level === '0'">一级</el-tag>
<el-tag v-else-if="scope.row.level === '1'" type="success">二级</el-tag>
<el-tag v-else type="warning">三级</el-tag>
</template>
</el-table-column>
5.角色列表开发
5.1 基本布局
(1)创建角色列表组件并设置路由
(2)面包屑和卡片布局
注意卡片中要通过el-row设置第一行,el-col设置第一行的第一格,在第一格中添加 添加角色按钮。
(3)获取角色列表数据
在data中定义rolelist用于存放请求获取的角色列表数据,在created中调用getRolesList函数,在methods中声明getRolesList函数用于发送请求获取数据。
(4)渲染table数据
使用el-table和el-table-column渲染表格。索引列:将el-table-column的type属性设置为index,展开列:将el-table-column的type属性设置为expand;在操作那一格通过作用域插槽添加按钮。
5.2 简单按钮功能实现
第p76需要自己做,暂时先空着
5.3 权限分配按钮功能实现
(1)添加分配权限对话框
从官网复制弹窗对话框(Dialog 对话框)粘贴到组件中,给权限按钮添加点击事件,点击事件中控制弹窗对话框的visible.sync属性的布尔值从而控制对话框的弹出。
(2)发送请求获取权限列表的数据
// 展示分配权限的对话框
async showSetRightDialog() {
// 获取所有权限的数据
const {data:res} = await this.$http.get('rights/tree')
if(res.meta.status !== 200) {
return this.$message.error("获取权限数据失败!")
}
// 把获取到权限数据保存到data中
this.rightslist = res.data
this.setRightDialogVisible = true
},
(3)树形结构展示数据
使用Tree 树形控件,el-tree的data属性绑定数据源,prop属性绑定数据绑定的字段。
- 复制粘贴Tree 树形控件
- 绑定data和prop(rightslist为请求获取的数据)
<el-tree :data="rightslist" :props="treeProps"></el-tree>
- data中定义treeProps
treeProps: {
// 字段名称
label: 'authName',
// 字段内容
children: 'children'
}
(4)优化树形控件
- 添加复选框
给el-tree添加show-checkbox树形
- 添加node-key属性,值为id(作用:将复选框选中的id设置为权限的id,用于后面已有权限的默认选中)
- 添加default-expand-all节点默认展开全部节点
(5)默认选中已有权限
当点击权限按钮时,将所有三级权限的id保存到一个数组中,再通过el-tree的default-checked-keys绑定此数组(defKeys),就能实现默认选中。再通过递归的方式将角色的三级权限id保存起来赋给default-checked-keys,从而实现三级权限的默认选中。
- data中定义defKeys用于保存默认选中的节点Id
- methods中定义getLeafKeys函数通过递归获取角色所有三级权限的id,并给defKeys。
// 通过递归的形式,获取角色下所有三级权限的id,并保存到defkeys数组中
getLeafKeys(node, arr) {
// 如果当前 node 不包含children 属性,则是三级节点
if (!node.children) {
return arr.push(node.id)
}
// 递归
node.children.forEach(item => {
this.getLeafKeys(item,arr)
});
},
- BUG:当点击另一个角色的权限按钮时,defKeys保存的id不停增加,最后可能到最默认选中所有。解决:给对话框el-dialog感到close事件,此事件中清空defKeys
(6)发送请求实现权限分配功能
当点击分配权限按钮后立即保存当前角色id(roleId)用于后面发送请求;然后点击确定按钮时获取树形结构的全选与半选的状态的id值并合并为一个数组,并使用都会将他们连接(请求需要),在发送请求时将此参数携带实现权限分配功能。(有点复杂,看视频)
5.4 展开区域
(1)一级权限
在展开区域通过作用域插槽实现,同时通过作用域插槽获取表格那一行的数据。通过element提供的栅格系统(el-row,el-col一行24格),对展开区域进行划分,从而分别实现一级二级一级三级权限的渲染。
- 在el-row中使用v-for指令便利获得一级权限的相关信息,并在cl-col中使用tag标签渲染
<el-row v-for="(item1) in scope.row.children" :key="item1.id">
<el-col :span="5" ><el-tag>{{item1.authName}}</el-tag></el-col>
- 美化一级权限:添加上下边框和icon小图标
<el-row :class="['bdbottop', i1 === 0 ? 'bdtop' :'']" v-for="(item1, i1) in scope.row.children" :key="item1.id">
(2)二级权限
在一级权限的el-col中嵌套el-row为二级权限同时也在其中添加el-col。渲染过程也是通过v-for循环item1,一级动态绑定样式(数组形式)
(3)三级权限
在二级权限中的el-col中通过v-for渲染三级权限(由于三级权限没有对应的四级权限了,所以不用el-row了)。
(4)美化权限结构
- 设置固定宽度,全局中设置固定宽度1336px
- 一级二级权限居中:给一级二级的row添加样式
(5)给三级权限添加删除样式
通过tag标签的closable属性给它添加删除样式,在通过tag标签的close事件,当点击删除时触发此事件,在此事件中进行弹框提示是否删除。
- 给tag标签添加属性和绑定事件
<el-tag type="warning" v-for="item3 in item2.children" :key="item3.id" closable @close="reoveRightById()"\>{{ item3.authName }}</el-tag\>
- 编写close事件
// 根据id删除对应权限
async reoveRightById(role, rightId) {
// 弹框提示是否删除
const confirmResult = await this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).catch(err => err)
if(confirmResult !== 'confirm') {
return this.$message.info("取消了删除")
}
const {data: res} = await this.$http.delete(`roles/${role.id}/rights/${rightId}`)
if(res.meta.status !== 200) {
return this.$message.erorr("删除权限失败")
}
// 这里不能使用 this.getRolesList(),因为这是全部重新渲染,权限结构要合璧,所以直接通过role.children重新更新数据即可
role.children = res.data
},
(6)给一级二级权限添加删除样式
将三级权限的closable属性以及close事件复制给一级二级权限的tag标签即可。
6.商品分类
6.1 基本布局
(1)组件商品分类组件并添加路由
(2)添加面包屑和卡片区域
(3)获取商品分类的数据列表
// 获取商品分类数据
async getCateList() {
const { data: res } = await this.$http.get("categories", {params: this.querInfo});
console.log(res);
if (res.meta.status !== 200) {
return this.$message.error("获取商品分类数据失败!");
}
this.catelist = res.data.result;
this.total = res.data.total;
},
(4)渲染树形表格
element-ui中没有提供类似的,所以使用第三方库vue-table-with-tree-grid(用法查github)。
- 在main.js中导入这个库,再进行注册组件
Vue.component('tree-table', TreeTable)
- 将tree-table中的data属性绑定catelist(数据源,和element-ui中的表格属性类似),将columns属性绑定columns(这是定义在data配置项中的一个数组,columns中的label就是这一列的名称,prop就是catelist中的数据)
<tree-table :data="catelist" :columns="columns"></tree-table>
// 为table指定列的定义
columns: [
{
lable: '分类名称',
prop: 'cat_name'
}
]
- 隐藏复选框:添加selection-type属性并设置为false
- 移除展开行:添加expand-type属性并设置为false
- 添加索引列:添加show-index属性并设置为true
- 修改索引列名称:通过**index-text="#"**属性
- 添加列与列之间的分割线:添加border属性并设置为true
- 移除鼠标悬停时的高亮效果:添加show-row-hover属性并设置为false
(5)这个组件中作用域插槽的使用
通过data配置项中的columns的对象添加type(属性值:template,意思是这一列要渲染作用域插槽)属性和template(属性值:插槽名字)属性。
- 在columns数组中添加新对象同时添加type和template属性
- 在tree-table标签中添加template标签,其中template标签的slot属性为插槽名称(对应column中的template),同时添加slot-scope属性获取这一行的数据。
- 在template标签中插入i标签设置相关样式,并通过v-if进行渲染。
(6)渲染 排序 列
也是通过作用域插槽和v-if进行渲染。
(7)渲染 操作 列
通过作用域插槽添加按钮。
(8)分页效果
6.2 添加分类按钮(p105-p110)
(1)基本布局
- 添加弹框以及基本样式
(2)获取父级分类列表
当点击添加按钮时就要获取父级分类列表
// 获取父级分类的数据列表
async getParentCateList() {
const {data:res} = await this.$http.get('categories', {params: {type: 2}})
console.log(res);
if(res.meta.status !== 200) {
return this.$message.error("获取父级分类数据失败!")
}
this.ParentCateList = res.data
}
(3)渲染父级分类所对应的级联选择器
未完
7.分类参数
7.1 基本布局
(1)添加组件
(2)添加面包屑和卡片视图
(3)添加Alert警告
使用Alert警告组件
7.2 选择商品区域
(5)获取商品分类数据
// 获取所有商品分类列表
async getCateList() {
const { data: res } = await this.$http.get("categories");
if (res.meta.status !== 200) {
return this.$message.error("获取商品分类失败!");
}
this.catelist = res.data;
},
(6)级联选择框的渲染
复制级联选择框,更改相应属性。
(7)控制级联选择器的选择范围
控制只能选中三级分类。但是现在官方已经配置好了,只能选中三级分类。
7.3 动态参数,以及静态属性
(1)渲染tab页签
使用tab页签标签。el-tabs中的v-model绑定el-tab-pane中的name属性的值,即表示处于哪一页。
(2)渲染按钮
在el-tab-pane中添加按钮,当级联选择框未选择时禁用,选择了才可点击。
方法:使用计算属性判断级联选择器的值的长度是否为3,不为三返回false,再绑定到按钮的disabled属性控制是否禁用。
(3)获取参数数据
// 级联选择框中项变化,会触发这个函数
async handleChange() {
// 证明选中的不是三级分类
if (this.selectedKeys.length !== 3) {
this.selectedKeys = [];
return;
}
// 根据所选分类的Id,和当前所处的面板,获取对应的参数
const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes`, {params: {sel:this.activeName}})
if(res.meta.status !== 200) {
return this.$message.error("获取参数列表失败!")
}
console.log(res.data);
},
(4)将获取参数数据的代码封装为一个函数
分装为函数,供级联选择器和添加按钮调用。
// 获取参数的列表数据
async getParamsData() {
// 证明选中的不是三级分类
if (this.selectedKeys.length !== 3) {
this.selectedKeys = [];
return;
}
// 根据所选分类的Id,和当前所处的面板,获取对应的参数
const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes`, {params: {sel:this.activeName}})
if(res.meta.status !== 200) {
return this.$message.error("获取参数列表失败!")
}
console.log(res.data);
},
(5)判断获取到的数据
由于获取参数和获取属性的使用的同一个请求,所以需要判断,将获取到的数据判断是哪种类型的请求后将数据保存到不同的数组中。
(6)渲染动态参数表格
静态属性表格一样。
7.4 按钮
(1)添加按钮
给按钮添加点击事件,触发对话框。对话框要监听关闭事件重置表单。
- 给确定添加点击事件
// 点击按钮添加参数
addParams() {
this.$refs.addFormRef.validate(async valid => {
{
if(!valid) return
const {data:res} = await this.$http.post(`categories/${this.cateId}/attributes`, {
attr_name: this.addForm.attr_name,
attr_sel: this.activeName
})
if(res.meta.status !== 201) {
return this.$message.error('添加参数失败!')
}
this.$message.success('添加参数成功!')
this.addDialogVisible = false
this.getParamsData()
}
})
}
(2)修改按钮
方法和添加按钮一直。
// 点击按钮修改参数信息
editParams() {
this.$refs.editFormRef.validate(async valid => {
if(!valid) return
const {data:res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`, {
attr_name: this.editForm.attr_name,
attr_sel: this.activeName
})
if(res.meta.status !== 200) {
return this.$message.error("修改参数失败!")
}
this.$message.success("修改参数成功!")
this.getParamsData()
this.editDialogVisible = false
})
}
(3)删除按钮(未完成)
7.5 展开页(未完成p130)
8.商品列表
8.1 基本布局
(1)添加组件
(2)添加面包屑导航以及卡片视图
8.2 页面渲染
(1)获取商品列表
// 根据分页获取商品列表
async getGoodsList() {
const { data: res } = await this.$http.get('goods', {
params: this.queryInfo
});
if(res.meta.status !== 200) {
return this.$message.error("获取商品列表失败!")
}
this.$message.success("获取商品列表成功!")
this.goodslist = res.data.goods
this.total = res.data.total
},
(2)渲染table表格
(3)创建过滤器处理时间
首先在main.js中定义全局的时间过滤器,再在组件中通过作用域插槽渲染时间那一列的时间数据。
- 全局定义过滤器
Vue.filter('dateFormat', function(originVal) {
const dt = new Date(originVal)
const y = dt.getFullYear()
const m = (dt.getMonth() + 1 + '').padStart(2,'0')
const d = (dt.getDate() + '').padStart(2,'0')
const hh = (dt.getHours() + '').padStart(2,'0')
const mm = (dt.getMinutes() + '').padStart(2,'0')
const ss = (dt.getSeconds() + '').padStart(2,'0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})
- 在表格的那一列重新渲染时间
<el-table-column label="创建时间" prop="add_time" width="140px">
<template slot-scope="scope">
{{scope.row.add_time | dateFormat}}
</template>
</el-table-column>
(4)分页功能
(5)搜索与清空
8.3 按钮功能呢实现
(1)删除按钮
// 删除按钮功能实现
async removeById(id) {
const confirmResult = await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(err => err)
if(confirmResult !== 'confirm') {
return this.$message.info('已取消删除!')
}
const {data:res} = await this.$http.delete(`goods/${id}`)
if(res.meta.status !== 200) {
return this.$message.error('删除失败!')
}
this.$message.success("删除成功!")
this.getGoodsList()
}
(2)添加商品按钮
给按钮添加点击事件,点击事件中通过**this.$router.push('/goods/add')**跳转到添加按钮组件页面。
组件详情见9.添加商品页面。
9.添加商品页面
9.1 基本布局
(1)面包屑导航加卡片视图
(2)步骤条
使用element-ui中的Steps 步骤条组件。
(3)美化步骤条
使用align-center属性使步骤条文本居中显示,active属性控制步骤条哪一步激活状态。
(4)添加tab栏
使用element-ui中的Tabs 标签页组件,tab-position属性控制向哪对齐。
(5)步骤条与tab栏的联动
通过步骤条的active属性和tab栏的v-model属性绑定同一个数据从而实现联动。
(6)添加form表单
在tab栏标签外加form表单标签。注:form表单的label-position属性控制文本和输入框的位置。
9.2 tab栏页面的绘制
(1)基本信息
通过el-form-item标签添加表单项。
- 获取商品分类数据
// 获取所有商品分类参数
async getCateList() {
const {data:res} = await this.$http.get('categories')
if(res.meta.status !== 200) {
return this.$omessage.error('获取商品分类数据失败!')
}
this.catelist = res.data
}
- 绘制商品分类的级联选择器
- 阻止标签页的切换(给el-tabs标签添加before-leave属性)
(2)商品参数(p160)
未完。一直到p175
大师傅士大夫撒旦