目录

以上是项目的服务器php、后端、前端、已经可以正常运行

一 登录:

登录页进度条:戳这里Vue项目电商后台管理系统 nprogress--进度条_活在风浪里的博客-CSDN博客

二 侧导航

三 列表页源码:

四 角色分配

五 、权限页面开发

权限列表:

角色列表:

1、面包屑

2、卡片区-添加按钮

3、获取表格数据:

4、渲染列表

​ 5、分配权限

​ 5.2、分配一级 二级权限

5.3、渲染一级权限

5.4 、二级权限渲染

5.5、渲染三级权限

​ 6、优化bug

7、实现删除tag标签

8、分配权限

递归 分配权限取出三级id

线性转化树形

树形转为线性 :

分配权限确定按钮

9、添加用户

10、编辑用户

11、删除用户

哈哈! 权限列表完!​

六、商品管理

1、商品分类

2、分页:

3、编辑:

4、删除:

5、添加分类:

6、商品分类源码:

七、分类参数

1、渲染级联选择器和面包屑和el-alert

2、只允许获取三级分类

3、实现没有选中三级分类添加按钮就不显示

4、参数获取

5、渲染 动态参数表格、静态属性表格:

6、添加参数:

7、修改参数:

8、删除参数:

9、表格展开操作:

10 、优化分类参数bug,选中非三级分类,清空表格

八、商品列表

商品表格

添加商品

​1、面包屑和步骤条:

2、渲染页面 基本信息form

3、请求级联选择器的数据

4、实现不选中分类不允许进行下一步

5、商品参数  (动态参数)

6、商品属性 (静态属性)

7、商品图片 (重要!)

8、富文本 添加按钮操作

8.1、处理goods_cat

8.2、处理attr参数

九、商品订单-列表

十、数据统计-ECharts

十一、结束 上线部署服务器


十万字 从零到一 完成电商后台管理项目 看完如果不会你揍我

  • 前言:3步
  • 1 .安装搭建服务器,
  • 2 .运行后端代码(node app.js),
  • 3 .运行前端代码(npm run serve)
  1. 本次使用自己的搭建的后端服务器,因为如果将线上接口发至互联网,大量的人修改会导致接口紊乱,所以在自己本地开发练习一下,如果计算机安装不了php,私信我,发送给你线上接口,
  2. 亲测可用 gitee 网址 包括php包,后端node.js代码,前端源码 以及文档接口说明https://gitee.com/zhang-kun8888/background-anagement-project-zk.githttps://gitee.com/zhang-kun8888/background-anagement-project-zk.git

效果示例图:

篇幅有限就不一一展示功能了

Gitee clone下载文件说明

打开步骤

1 开启php

2 点击MySOL管理器导入后端代码

3 如何查看是否导入成功?

4 运行 后端代码,下载依赖 cnpm i  启动命令 node app.js  、运行前端代码,下载依赖 cnpm i 启动命令npm run serve

当然了这是在本地开启了后端服务器,前后端自己干,才这么麻烦,如果是线上接口,修改一下公共网址,

可直接运行前端的代码

5 Gitee截图事例说明

 以上是项目的服务器php、后端、前端、已经可以正常运行

实在安装不上找我要线上接口,只需要修改前端代码的公共网址,可直接运行,不用自己服务器、后端。。。配置了

接下来说前端代码的细节

一 登录

  • 登录没什么困难,在点击登录按钮时,调用接口,传递后端要的必填参数,存储token,进行全局前置守卫判断,后置路由钩子获取dom
  • 退出登录,则删除token,
<template><div id="login"><div class="formBox"><div class="imgBox"><img src="../assets/logo.png" alt="" /></div><!-- main --><div class="main"><el-form:model="ruleForm":rules="rules"status-iconref="ruleForm"label-width="100px"class="demo-ruleForm"><el-form-item label="用户名" prop="username"><el-inputv-model="ruleForm.username"prefix-icon="el-icon-user"></el-input></el-form-item><el-form-item label="用户密码" prop="password"><el-inputprefix-icon="el-icon-lock"placeholder="请输入密码"show-passwordv-model="ruleForm.password"></el-input></el-form-item><el-form-item><el-button type="primary" @click="submitForm">提交</el-button><el-button @click="resetForm('ruleForm')">重置</el-button></el-form-item></el-form></div></div></div>
</template><script>
import { login } from "./api";
export default {data() {return {ruleForm: {password: "123456",username: "admin",},rules: {username: [{ required: true, message: "请输入用户名", trigger: "blur" },],password: [{ required: true, message: "请输入密码", trigger: "blur" }],},};},methods: {async submitForm() {await login(this.ruleForm).then((res) => {// console.log(res);localStorage.setItem("token", JSON.stringify(res.data.token));this.$router.push("/main");});},resetForm(formName) {this.$refs[formName].resetFields();},},computed: {},components: {},created() {},
};
</script><style lang="scss" scoped>
#login {width: 100%;height: 100%;background-color: #3f4b6b;position: relative;.formBox {border-radius: 5px;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: #fff;width: 500px;height: 350px;.imgBox {position: absolute;top: -70px;left: 50%;transform: translateX(-50%);background-color: #fff;border-radius: 80px;width: 120px;height: 120px;box-sizing: border-box;padding: 15px;overflow: hidden;box-shadow: 1px 1px 15px #ddd;img {width: 100%;height: 100%;}}::v-deep.demo-ruleForm {margin-top: 100px;margin-right: 50px;}}
}
</style>

图例

router/index 页面

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)const routes = [{path: '/',redirect: 'login'}, {path: '/login',name: 'Login',meta: {title: '后台管理登录',requiredPath: false,},component: () =>import ('../views/Login.vue')},{path: '/main',name: '',component: () =>import ('../views/main/main.vue'),children: [{path: '/',name: '',meta: {title: '欢迎来到后台管理主页',requiredPath: true,},component: () =>import ('../views/main/index.vue')},{path: '/users',name: '',meta: {title: "后台管理列表页",requiredPath: true},component: () =>import ('../views/main/users.vue')}]}]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})// 在路由元信息配置守卫 requiredPath为true, 适合守卫多个页面 vue3next() 变成return true
router.beforeEach((to, from, next) => {if (localStorage.getItem('token')) {next()} else {if (to.meta.requiredPath) { //没有token requiredPath为true 守卫不让进,跳入loginnext('/Login')} else {next()}}})// 导航后置守卫(当你真正进入到某个页面之后才执行)
router.afterEach((to, from) => {// 设置路由的标题 (可自定义)document.title = to.meta.title || '常驻标题'// 将所有的页面切换之后滚动到最顶部window.scrollTo(0, 0)
})
export default router

登录页进度条:戳这里Vue项目电商后台管理系统 nprogress--进度条_活在风浪里的博客-CSDN博客

 二 侧导航

进入后 书写侧边栏导航(这个也没什么难度,细心噢,别把ele的标签复制多 或 少le)

<template><div id="about"><!-- 高100% --><el-container class="box"><!-- el-header高60PX --><el-header class="header"><span @click="$router.push('/main')">电商管理系统</span><el-button type="primary" @click="outLogin">退出</el-button></el-header><!-- 高100% - 60PX (el-header高60PX) --><el-container class="header_main"><!-- aside --><el-aside :width="asideWidth" class="aside"><divdata-v-bcdba65eclass="top-bar"@click="isCollapse = !isCollapse">|||</div><!-- 1 unique-opened 保持一个子导航开启,因为循环的是所有,打开一个就会打开全部,所以开启 unique-opened,它是el-menu的属性,开启router属性值目的是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转,在el-menu-item标签里设置跳转属性index='xx'2 还要开启el-submenu的属性唯一标识index它的值是字符串或空它是可选值,如果你是循环必须要开始唯一标识,因为循环的是所有,打开一个就会打开全部,所以要一个字符串唯一标识来区分如果是一个个写的导航不用也可以default-active="/users" 默认当前激活菜单的 index,它是el-menu的属性在el-menu标签写default-active="/users"--><el-menuclass="el-menu-vertical-demo"background-color="#333744"text-color="#fff"active-text-color="#409fff"unique-opened:collapse="isCollapse":collapse-transition="false"router><!-- 动画不能写死,是变动的 --><!-- 2 循环还要开启el-submenu的属性唯一标识index它的值是字符串或空 --><el-submenu:index="value.id + ''"v-for="value in menuList":key="value.id"><template slot="title"><i :class="iconObj[value.id]"></i><span>{{ value.authName }}</span></template><!-- 和文件夹没关系,要进入的是users  现在是请求的数据动态的路径--><el-menu-itemclass="el-menu":index="'/' + item.path"v-for="item in value.children":key="item.id"><i :class="iconObj[item.id]"></i><span>{{ item.authName }}</span></el-menu-item></el-submenu></el-menu></el-aside><!-- main里面有子路由就得给main配坑 --><el-main> <router-view></router-view> </el-main></el-container></el-container></div>
</template><script>
import { menus } from "../api";
export default {data() {return {// 默认的是否折合aside侧边栏isCollapse: false,// 左侧菜单数据menuList: null,// 解决循环字体图标,请求的数据默认有id,自定义一个数据键是id对应的数,值是iconiconObj: {125: "iconfont icon-test1",103: "iconfont ego-box",101: "iconfont shangpin-xianxing",102: "iconfont dingdan",145: "iconfont shujutongjixuanzhong",110: "el-icon-user-solid",111: "el-icon-tickets",112: "el-icon-film",104: "el-icon-s-goods",115: "el-icon-star-off",121: "el-icon-delete-solid",107: "el-icon-s-promotion",146: "el-icon-s-flag",},};},methods: {outLogin() {localStorage.removeItem("token");this.$router.replace("/login");},async getMenus() {await menus().then((result) => {// console.log(result);this.menuList = result.data;console.log(this.menuList);}).catch((err) => {throw new Error(err);});},},computed: {// 对象写法直接returnasideWidth() {return this.isCollapse == true ? "64px" : "200px";},},components: {},created() {this.getMenus();},
};
</script><style lang="scss" scoped>
* {box-sizing: border-box;
}
i {margin-right: 15px;
}
#about,
.box {width: 100%;height: 100%;background-color: #eee;
}
.header {background-color: #373d41;display: flex;align-items: center;justify-content: space-between;padding: 0 15px;span {color: #fff;font-size: 25px;cursor: pointer;}
}
//如果导航太多可能会溢出
.header_main {height: calc(100% - 60px);.aside {background-color: #343744;height: 100%;overflow: auto;}
}.top-bar {background-color: #8f97b3;text-align: center;color: #fff;// 鼠标放上去变成小手cursor: pointer;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {width: 200px;min-height: 400px;
}
::v-deep.el-menu {border: none;
}
</style>

图例:

解释下侧导航

  1. 用到了ele组件库的经典布局 `el-container` ,  用到 `el-menu`, 请求测导航接口,循环,循环后打开一个测导航,全部都打开,需要配置unique-opened 保持一个子导航开启,不同的字体图标,需要用以id做key,icon做值,具体看代码实现
  2. unique-opened 保持一个子导航开启,因为循环的是所有,打开一个就会打开全部,

    所以开启 unique-opened,它是el-menu的属性,开启router属性值目的是否使用 vue-router 的模式,
                  启用该模式会在激活导航时以 index 作为 path 进行路由跳转,
                  在el-menu-item标签里设置跳转属性index='xx'

    2 还要开启el-submenu的属性唯一标识index它的值是字符串或空null
                它是可选值,如果你是循环必须要开启唯一标识,因为循环的是所有,打开一个就会打开全部,
                所以要一个字符串唯一标识来区分
                如果是一个个写的导航不用也可以,就是不循环一个个复制导航手写导航名字

    default-active="/users" 默认当前激活菜单的 index,它是el-menu的属性
                在el-menu标签写default-active="/users"

  3. 要多看组件库说明

三 列表页源码:

列表页还是比较细碎的,注意细节

<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><!-- 搜索 添加 --><el-row :gutter="20"><el-col :span="6"><el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getUserList"><el-button slot="append" icon="el-icon-search" @click="getUserList"></el-button></el-input></el-col><el-col :span="4"><el-button type="primary" @click="addDialogVisible = true">添加用户</el-button></el-col></el-row><!-- 用户列表区域 --><el-table :data="userlist" border stripe><!-- stripe: 斑马条纹border:边框--><el-table-column type="index" label="#"></el-table-column><el-table-column prop="username" label="姓名"></el-table-column><el-table-column prop="email" label="邮箱"></el-table-column><el-table-column prop="mobile" label="电话"></el-table-column><el-table-column prop="role_name" label="角色"></el-table-column><el-table-column label="状态"><template slot-scope="scope"><el-switch v-model="scope.row.mg_state" @change="userStateChanged(scope.row)"></el-switch></template></el-table-column><el-table-column label="操作"><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini" circle @click="showEditDialog(scope.row.id)"></el-button><el-button type="danger" icon="el-icon-delete" size="mini" circle @click="removeUserById(scope.row.id)"></el-button><el-tooltip class="item" effect="dark" content="角色分配" :enterable="false" placement="top"><el-button type="warning" icon="el-icon-setting" size="mini" circle @click="showSetRole(scope.row)"></el-button></el-tooltip></template></el-table-column></el-table><!-- 分页区域 --><el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[5, 15, 20, 35]" :page-size="queryInfo.pagesize" layout="sizes, prev, pager, next, jumper,total" :total="total"></el-pagination></el-card><!-- 添加用户的对话框 --><el-dialog title="添加用户" center :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed"><!-- 内容主体 --><el-form :model="addUserForm" ref="addUserFormRef" :rules="addUserFormRules" label-width="100px"><el-form-item label="用户名" prop="username"><el-input v-model="addUserForm.username"></el-input></el-form-item><el-form-item label="密码" prop="password"><el-input v-model="addUserForm.password"></el-input></el-form-item><el-form-item label="邮箱" prop="email"><el-input v-model="addUserForm.email"></el-input></el-form-item><el-form-item label="手机" prop="mobile"><el-input v-model="addUserForm.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="editUserForm" ref="editUserFormRef" :rules="editUserFormRules" label-width="70px"><el-form-item label="用户名"><el-input v-model="editUserForm.username" disabled></el-input></el-form-item><el-form-item label="邮箱" prop="email"><el-input v-model="editUserForm.email"></el-input></el-form-item><el-form-item label="手机" prop="mobile"><el-input v-model="editUserForm.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="editUser">确 定</el-button></span></el-dialog><!-- 分配角色对话框 --><el-dialog title="分配角色" :visible.sync="setRoleDialogVisible" width="50%" @close="setRoleDialogClosed"><div><p>当前用户:{{userInfo.username}}</p><p>当前角色:{{userInfo.role_name}}</p><p>分配角色:<el-select v-model="selectRoleId" filterable allow-create default-first-option placeholder="请选择文章标签"><el-option v-for="item in rolesLsit" :key="item.id" :label="item.roleName" :value="item.id"></el-option></el-select></p></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></div>
</template><script>
export default {data() {// 自定义邮箱规则var checkEmail = (rule, value, callback) => {const regEmail = /^\w+@\w+(\.\w+)+$/;if (regEmail.test(value)) {// 合法邮箱return callback();}callback(new Error("请输入合法邮箱"));};// 自定义手机号规则var checkMobile = (rule, value, callback) => {const regMobile = /^1[34578]\d{9}$/;if (regMobile.test(value)) {return callback();}// 返回一个错误提示callback(new Error("请输入合法的手机号码"));};return {// 获取用户列表查询参数对象queryInfo: {query: "",// 当前页数pagenum: 1,// 每页显示多少数据pagesize: 5,},userlist: [],total: 0,// 添加用户对话框addDialogVisible: false,// 用户添加addUserForm: {username: "",password: "",email: "",mobile: "",},// 用户添加表单验证规则addUserFormRules: {username: [{ required: true, message: "请输入用户名", trigger: "blur" },{min: 2,max: 10,message: "用户名的长度在2~10个字",trigger: "blur",},],password: [{ required: true, message: "请输入用户密码", trigger: "blur" },{min: 6,max: 18,message: "用户密码的长度在6~18个字",trigger: "blur",},],email: [{ required: true, message: "请输入邮箱", trigger: "blur" },{ validator: checkEmail, trigger: "blur" },],mobile: [{ required: true, message: "请输入手机号码", trigger: "blur" },{ validator: checkMobile, trigger: "blur" },],},// 修改用户editDialogVisible: false,editUserForm: {},// 编辑用户表单验证editUserFormRules: {email: [{ required: true, message: "请输入邮箱", trigger: "blur" },{ validator: checkEmail, trigger: "blur" },],mobile: [{ required: true, message: "请输入手机号码", trigger: "blur" },{ validator: checkMobile, trigger: "blur" },],},// 分配角色对话框setRoleDialogVisible: false,// 当前需要被分配角色的用户userInfo: {},// 所有角色数据列表rolesLsit: [],// 已选中的角色Id值selectRoleId: "",};},created() {this.getUserList();},methods: {async getUserList() {const { data: res } = await this.$http.get("users", {params: this.queryInfo,});if (res.meta.status !== 200) {return this.$message.error("获取用户列表失败!");}console.log(res.data);this.userlist = res.data.users;this.total = res.data.total;},// 监听 pagesize改变的事件handleSizeChange(newSize) {// console.log(newSize)this.queryInfo.pagesize = newSize;this.getUserList();},// 监听 页码值 改变事件handleCurrentChange(newSize) {// console.log(newSize)this.queryInfo.pagenum = newSize;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.mg_state;return this.$message.error("更新用户状态失败");}this.$message({type: "success",message: '<i class="el-icon-s-promotion"></i>操作成功!',showClose: true,center: true,dangerouslyUseHTMLString: true,});},// 监听 添加用户对话框的关闭事件addDialogClosed() {this.$refs.addUserFormRef.resetFields();},// 添加用户addUser() {// 提交请求前,表单预验证this.$refs.addUserFormRef.validate(async (valid) => {// console.log(valid)// 表单预校验失败if (!valid) return;const { data: res } = await this.$http.post("users", this.addUserForm);if (res.meta.status !== 201) {this.$message.error("添加用户失败!");}this.$message.success("添加用户成功!");// 隐藏添加用户对话框this.addDialogVisible = false;this.getUserList();});},// 编辑用户信息async showEditDialog(id) {const { data: res } = await this.$http.get("users/" + id);if (res.meta.status !== 200) {return this.$message.error("查询用户信息失败!");}console.log(res);this.editUserForm = res.data;this.editDialogVisible = true;},// 监听修改用户对话框的关闭事件editDialogClosed() {this.$refs.editUserFormRef.resetFields();},// 修改用户信息editUser() {// 提交请求前,表单预验证this.$refs.editUserFormRef.validate(async (valid) => {// console.log(valid)// 表单预校验失败if (!valid) return;const { data: res } = await this.$http.put("users/" + this.editUserForm.id,{email: this.editUserForm.email,mobile: this.editUserForm.mobile,});if (res.meta.status !== 200) {this.$message.error("更新用户信息失败!");}// 隐藏添加用户对话框this.editDialogVisible = false;this.$message.success("更新用户信息成功!");this.getUserList();});},// 删除用户async removeUserById(id) {const confirmResult = await this.$confirm("此操作将永久删除该用户, 是否继续?","提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).catch((err) => err);// 点击确定 返回值为:confirm// 点击取消 返回值为: cancelif (confirmResult !== "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();},// 展示分配角色的对话框async showSetRole(role) {this.userInfo = role;// 展示对话框之前,获取所有角色列表const { data: res } = await this.$http.get("roles");if (res.meta.status !== 200) {return this.$message.error("获取角色列表失败!");}this.rolesLsit = res.data;this.setRoleDialogVisible = true;},// 分配角色async saveRoleInfo() {if (!this.selectRoleId) {return this.$message.error("请选择要分配的角色");}const { data: res } = await this.$http.put(`users/${this.userInfo.id}/role`,{ rid: this.selectRoleId });if (res.meta.status !== 200) {return this.$message.error("更新用户角色失败!");}this.$message.success("更新角色成功!");this.getUserList();this.setRoleDialogVisible = false;},// 分配角色对话框关闭事件setRoleDialogClosed() {this.selectRoleId = "";this.userInfo = {};},},
};
</script><style lang="less" scoped>
</style>

列表

修改

删除

文字提示

分页

文档

Element组件

分页详解:

  • 改变 layout="total, sizes, prev, pager, next, jumper" 顺序可以修改 分页布局
  • @size-change="handleSizeChangeEvent"当前页事件 赋值后端规定参数 第一页 pagenum
  • @current-change="handleCurrentChangeEvent"页数改变事件 赋值后端规定参数 页容量 pagesize
/*templeate
*/<div class="block"><el-pagination :page-sizes="[3, 5, 10]" layout="total, sizes, prev, pager, next, jumper" :total="total" :page-size="3" @size-change="handleSizeChangeEvent" @current-change="handleCurrentChangeEvent"></el-pagination></div>
/*data
*/// 后端必填参数obj: {// 第一页pagenum: 1,// 页容量pagesize: 10,},
/*methods*///这个是请求后端数据,我是经过封装api的,使用的时候可以直接用axios去请求数据,list() {getUserList(this.obj).then((res) => {console.log(res);// 将请求的数据赋值给变量this.tableData = res.users;// 总条数this.total = res.total;});},// 当前页事件 有默认参数newVal 赋值给默认定义的数据当前页(后端参数pagenum ),这样点击当前页,就把前端的第几页传给后端了,页数就会跟着变handleCurrentChangeEvent(newVal) {// console.log(newVal);// 将newVal赋值当前页,页数改变重新请求this.obj.pagenum = newVal;this.list();},// 页数改变事件 有默认参数newVal, 赋值给后端规定的 pagesize页容量,这样点击分页的页容量切换,//后端的参数pagesize也会变,页面改变了重新渲染handleSizeChangeEvent(newVal) {// console.log(nrwVal);this.obj.pagesize = newVal;this.list();},
  • 有一篇专门的博客说这个分页可以点击这里查看参考

四 角色分配

  • dialog对话框

文档

分配角色详解:

  • 上面文档说了,需要用户id(拼接url),角色id(body参数体),看一下Select 选择器Element官网怎么定义属性、事件

  • 可以看出  v-model的值为当前被选中的el-option的value属性值, v-model等于:value等于角色id,得到角色id了,还需要后端用户id,这个可以根据点击分配权限按钮获取点击那一行,scope.row,
/*分配角色按钮
*/
<el-button type="warning" icon="el-icon-s-tools" circle @click="showSetRole(scope.row)"> </el-button>
  • 现在后端必填参数全部得到,可以定义Select 选择器标签了
  /*template*/<el-dialog title="分配角色" :visible.sync="setRoleDialogVisible" width="50%" @close="setRoleDialogClosed"><div><p>当前用户:{{userInfo.username}}</p><br /><p>当前角色:{{userInfo.role_name}}</p><br /><p>分配角色:<!-- v-model的值为当前被选中的el-option的value属性值, v-model等于:value等于角色id --><el-select v-model="selectRoleId" filterable allow-create default-first-option placeholder="请选择文章标签"><!--el-select的:value是不同id 选项1 选项2  :label是不同值--><el-option v-for="item in rolesList" :key="item.id" :label="item.roleName" :value="item.id"></el-option></el-select></p></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>
 /*data数据
*/
// 分配角色对话框setRoleDialogVisible: false,
// 当前需要被分配角色的用户userInfo: {},
// 所有角色数据列表rolesList: [],
// 已选中的角色Id值 :valueselectRoleId: "",

/*methods
*/// 展示分配角色的对话框async showSetRole(role) {//将点击的那一行数据赋值定义的userInfo,因为上面用户角色、用户姓名需要用到this.userInfo = role;// 展示对话框之前,获取所有角色列表,没角色怎么渲染,//rolesAjax()是我封装好的接口,根据自己写的来定义await rolesAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取角色列表失败!");}// 将获取的数据赋值自定义全部角色数据,用于渲染,如 主管、text、等this.rolesList = res.data;this.setRoleDialogVisible = true;});},// 分配角色 确定按钮async saveRoleInfo() {// selectRoleId就是下拉选择的v-model的默认值,v-model的默认值是:角色idif (!this.selectRoleId) {return this.$message.error("请选择要分配的角色");}// 用户id 角色id后端必传参数rolesOkBtnAjax(this.userInfo.id, {rid: this.selectRoleId,}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("更新用户角色失败!");}});this.$message.success("更新角色成功!");location.reload();this.setRoleDialogVisible = false;},// 分配角色对话框关闭事件setRoleDialogClosed() {this.selectRoleId = "";this.userInfo = {};},

列表页完 。

五 、权限页面开发

权限列表:

权限列表图例:

源码:

<template><div id="rights"><!-- 权限列表 --><!-- 面包屑导航区 --><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><el-table :data="rightsList" border stripe><el-table-column type="index" label="#"></el-table-column><el-table-column label="权限名称" prop="authName"></el-table-column><el-table-column label="路径"><!-- 给路径加上 / --><template slot-scope="scope">{{'/'+ scope.row.path}}</template></el-table-column><el-table-column label="权限等级" prop="level"><!-- 用el-tag判断 --><template slot-scope="scope"><el-tag v-if="scope.row.level === '0'">一级</el-tag><el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag><el-tag type="danger" v-else>三级</el-tag></template></el-table-column></el-table></el-card></div>
</template><script>
import { rightsAjax } from "../api";
export default {name: "Rights", //对应路由的name,params传参用路由的name 没有 /data() {return {// 定义权限列表数据rightsList: [],};},mounted() {this.getList();},methods: {async getList() {//同步方式执行异步请求,等待then的结果await rightsAjax().then((res) => {console.log(res);if (res.meta.status != 200) {this.$message({type: "error", // success error warningmessage: res.meta.msg,duration: 2000,});return;} else {//状态码等于200this.rightsList = res.data;this.$message({type: "success", // success error warningmessage: res.meta.msg,duration: 2000,});}});},},
};
</script><style lang="scss" scoped >
</style>
  • 这个没有难点,主要是el表格的熟练度掌握,用到了el-tag
  • 自定义列插槽 复杂点的在下面这个吧
       <el-table-column label="路径"><!-- 给路径加上 / --><template slot-scope="scope">{{'/'+ scope.row.path}}</template></el-table-column><el-table-column label="权限等级" prop="level"><!-- 用el-tag判断 level是请求的数据,代表权限等级--><template slot-scope="scope"><el-tag v-if="scope.row.level === '0'">一级</el-tag><el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag><el-tag type="danger" v-else>三级</el-tag></template></el-table-column>

角色列表:

  • 先看ajax文档事例:
  • 请求路径:roles

  • 请求方法:get

  • 响应数据说明

    • 第一层为角色信息

    • 第二层开始为权限说明,权限一共有 3 层权限

    • 最后一层权限,不包含 children 属性

  • 响应数据:

  • {"data": [{"id": 30,"roleName": "主管","roleDesc": "技术负责人","children": [{"id": 101,"authName": "商品管理","path": null,"children": [{"id": 104,"authName": "商品列表","path": null,"children": [{"id": 105,"authName": "添加商品","path": null}]}]}]}],"meta": {"msg": "获取成功","status": 200}
    }

源码:

<template><!-- 角色列表页 --><div id="roles"><!-- 面包屑导航区 --><el-breadcrumb separator-class="el-icon-arrow-right"><el-breadcrumb-item :to="{ path: '/main' }">首页</el-breadcrumb-item><el-breadcrumb-item>权限管理</el-breadcrumb-item><el-breadcrumb-item>角色列表</el-breadcrumb-item></el-breadcrumb><!-- 卡片 --><el-card><!-- 添加角色按钮 --><el-row><el-col><el-button type="primary" @click="AddRoleDialogVisible=true">添加角色</el-button></el-col></el-row><!-- 表格 --><!-- type    对应列的类型。如果设置了 selection 则显示多选框;如果设置了 index 则显示该行的索引(从 1 开始计算);如果设置了 expand 则显示为一个可展开的按钮--><!-- 展开行 --><el-table :data="rolesList" border stripe><el-table-column type="expand" width="50"><template slot-scope="scope"><!-- 给行加边框 加在el-row上--><el-row v-for="(item1,index1) in scope.row.children" :key="item1.id" :class="['bd-bottom','display-center',index1==0?'bt-top':'']"><!-- 渲染一级权限 --><el-col :span="5"><el-tag closable @close="removeId(scope.row,item1.id)">{{item1.authName}}</el-tag><i class="el-icon-caret-right"></i></el-col><!-- 渲染二级、三级权限,二级权限的行中又分二级、三级  --><el-col :span="19"><!-- 二级权限占6分,三级权限占13分,撑满二级权限el-col 19分 --><el-row v-for="(item2,index2) in item1.children" :key="item2.id" :class="['display-center',index2==0?'':'bt-top']"><!-- 注意:边框是加给行el-row,如果加给列就不会显示一行都有边框 --><el-col :span="6"><el-tag type="success" closable @close="removeId(scope.row,item2.id)">{{item2.authName}}</el-tag><i class="el-icon-caret-right"></i></el-col><!-- 三级 --><el-col :span="13"><el-tag type="warning" closable @close="removeId(scope.row,item3.id)" v-for="item3 in item2.children" :key="item3.id">{{item3.authName}}</el-tag></el-col></el-row></el-col></el-row><!-- v-pre vue中跳过标签,不会编译html原样输出 pre标签--><!-- <pre>{{scope.row}}</pre> --></template></el-table-column><el-table-column label="序号" type="index" width="60"></el-table-column><el-table-column label="角色名称" prop="roleName"></el-table-column><el-table-column label="角色描述" prop="roleDesc"></el-table-column><el-table-column label="操作" width="300"><!-- size   尺寸  string  medium / small / mini   —type   类型  string  primary / success / warning / danger / info / text常用图标搜索:search 编辑:edit  对号:check邮件:message 收藏(星):star-off删除:delete--><template slot-scope="scope"><el-button type="primary" size="mini" icon="el-icon-edit" @click="showEditDialog(scope.row.id)">编辑</el-button><el-button type="danger" size="mini" icon="el-icon-delete" @click="removeRoleById(scope.row.id)">删除</el-button><el-button type="warning" size="mini" icon="el-icon-setting" @click="showDialogVisible(scope.row)">分配权限</el-button></template></el-table-column></el-table><!-- 分配权限对话框 --><el-dialog title="分配权限" :visible.sync="dialogVisible" width="50%" @close="resetDefaultKeys"><!-- 树形控件 :data展示的数据  :props="defaultProps"定义展示的内容, --><!-- node-key="id" 指定只要选中了节点就是选中了我的id值 --><!-- :default-checked-keys='defaultKeys' 默认选中的数组 就是让所有三级被勾选--><!-- ref="treeRef" 获取当前dom --><!-- getCheckedKeys 返回目前被选中的节点的 key 所组成的数组  --><!-- getHalfCheckedKeys  返回目前半选中的节点的 key 所组成的数组--><el-tree :data="treeList" :props="defaultProps" ref="treeRef" :default-checked-keys='defaultKeys' default-expand-all show-checkbox node-key="id"></el-tree><!-- 树形控件结束 --><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="onClick">确 定</el-button></span></el-dialog><!-- 添加角色对话框 --><el-dialog title="添加角色" :visible.sync="AddRoleDialogVisible" width="40%" @close="addRoleDialogClosed"><el-form :model="addRoleForm" ref="addRoleFormRef" :rules="addRoleFormRules" label-width="100px"><el-form-item label="角色名称" prop="roleName"><el-input v-model="addRoleForm.roleName"></el-input></el-form-item><el-form-item label="角色描述" prop="roleDesc"><el-input v-model="addRoleForm.roleDesc"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="AddRoleDialogVisible = false">取 消</el-button><el-button type="primary" @click="addRoles">确 定</el-button></span></el-dialog><!-- 编辑角色对话框 --><el-dialog title="编辑角色" :visible.sync="editRoleDialogVisible" width="40%"><el-form :model="editRoleForm" ref="editRoleFormRef" :rules="editRoleFormRules" label-width="100px"><el-form-item label="角色名称" prop="roleName"><el-input v-model="editRoleForm.roleName"></el-input></el-form-item><el-form-item label="角色描述" prop="roleDesc"><el-input v-model="editRoleForm.roleDesc"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editRoleDialogVisible = false">取 消</el-button><el-button type="primary" @click="editRolesBtn">确 定</el-button></span></el-dialog></el-card></div>
</template><script>
// users用户列表页(展示分配角色的对话框)也用到这个接口所以直接调用
import {rolesAjax,deleteAjax,rightsTreeAjax,roleAuthorizationAjax,addRolesAjax,ediRolesAjax,ediRoles1Ajax,deleteRolesAjax,
} from "../api";
export default {name: "Roles",data() {return {// 定义所有的角色数据变量rolesList: [],// 分配权限的默认值dialogVisible: false,// 权限数据treeList: [],// 定义树形控件展示的内容defaultProps: {//  父子节点通过那个属性嵌套children: "children",// 看到的是那个值,树形控件显示的节点label: "authName",},// 默认展开被选中的数组,将三级id递归进来,//注意:点击x关闭对话框需要将数组清空,因为点击不一样,不能让之前的id还存在在数组中defaultKeys: [],//当前即将分配权限的Id,需要拼接urlroleId: 0,//   添加用户对话框AddRoleDialogVisible: false,// 添加角色表单addRoleForm: {},//   添加角色表单验证addRoleFormRules: {roleName: [{ required: true, message: "请输入角色名称", trigger: "blur" },],roleDesc: [{ required: true, message: "请输入角色描述", trigger: "blur" },],},//   编辑用户对话框editRoleDialogVisible: false,//   编辑角色信息editRoleForm: {},//   编辑角色表单验证editRoleFormRules: {roleName: [{ required: true, message: "请输入角色名称", trigger: "blur" },],roleDesc: [{ required: true, message: "请输入角色描述", trigger: "blur" },],},};},methods: {// 请求角色数据async getList() {//同步方式执行异步请求,等待then的结果await rolesAjax().then((res) => {// console.log(res);if (res.meta.status != 200) {// 如果状态码不是200就return 错误消息return this.$message({type: "error", // success error warningmessage: res.meta.msg,duration: 2000,});} else {//状态码等于200this.rolesList = res.data;this.$message({type: "success", // success error warningmessage: res.meta.msg,duration: 2000,});}});},// 删除角色tag标签removeId(row, rightId) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteAjax(row.id, rightId).then((res) => {// console.log(res);if (res.meta.status == 200) {//如果重新调用list会刷新页面,接口返回的是最新的数据,可以将返回的数据赋值children// this.getList();row.children = res.data;this.$message({type: "success",message: "删除成功!",});}});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},// 显示分配权限弹框事件async showDialogVisible(row) {this.roleId = row.id;// 获取分配权限数据await rightsTreeAjax().then((res) => {// console.log(res);// 如果状态码不是200就会return这一句if (res.meta.status !== 200)return this.$message({type: "error", // success error warningmessage: res.meta.msg,duration: 2000,});// 等于200就将数据赋值树形权限数据渲染this.treeList = res.data;// 在对话框开启之前需要获取所有三级权限的id 注意:必须写到ajax中不能写出去否则切换角色无法找到三级id// 当前角色 就是父级了,是ajax请求的数据,将他传入递归函数,他里面有children,要在ajax中调用递归// console.log(row);this.getKeys(row, this.defaultKeys);this.dialogVisible = true;// 我将上面两行写在ajax外,导致无法找到三级id,因为递归的数据是在ajax中得到的,所以写在ajax中// 正确思路应该是 点击权限按钮-请求数据-赋值tree树形数据-调用递归-显示弹框// 用思维解bug:点击无法在弹框前调用递归,肯定是调用递归时问题});},// 递归函数 需求:将三级的id添加到arr数组中getKeys(node, defaultKeys) {//没有children追加到defaultKeys,因为一二级有children所以执行下一步递归if (!node.children) return defaultKeys.push(node.id);// 这个地方很精妙!// 如果有children就将循环的每一项,再次调用一下本函数// 递归:在运行中自己调用自己// 这个函数就会一次次遍历,最终执行到三级权限,没有children就会执行上一句代码node.children.forEach((item) => this.getKeys(item, defaultKeys));},//注意:点击x关闭对话框需要将三级id数组清空,因为点击不一样,不能让之前的id还存在在数组中resetDefaultKeys() {this.defaultKeys = [];},// 点击按钮关闭分配权限弹框async onClick() {//element 定义用ref获取keys// console.log(this.$refs.treeRef.getCheckedNodes());// 定义一个数组用于合并全选、半选的权限const mergeKeys = [...this.$refs.treeRef.getHalfCheckedKeys(),...this.$refs.treeRef.getCheckedKeys(),];// console.log(mergeKeys); // [101,104,2033,555,556]const idStr = mergeKeys.join(",");// console.log(idStr);//  '101,104,2033,555,556'// 发起请求await roleAuthorizationAjax(this.roleId, { rids: idStr }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("分配权限失败!");}this.$message.success("分配权限成功!");this.getList();this.dialogVisible = false;});},// 添加角色addRoles() {this.$refs.addRoleFormRef.validate(async (valid) => {if (!valid) return;addRolesAjax(this.addRoleForm).then((res) => {console.log(res);if (res.meta.status !== 201) {this.$message.error("添加角色失败!");}this.$message.success("添加角色成功!");this.AddRoleDialogVisible = false;this.getList();});});},// 添加角色对话框的关闭addRoleDialogClosed() {this.$refs.addRoleFormRef.resetFields();},// 获取编辑角色async showEditDialog(id) {await ediRolesAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200)return this.$message.error("查询角色信息失败!");this.editRoleForm = res.data;this.editRoleDialogVisible = true;});},// 编辑确定按钮editRolesBtn() {this.$refs.editRoleFormRef.validate(async (valid) => {// console.log(valid); // 空就是false 有值就是true// 表单非空校验失败,是false就returnif (!valid) return;await ediRoles1Ajax(this.editRoleForm.roleId, {roleName: this.editRoleForm.roleName,roleDesc: this.editRoleForm.roleDesc,}).then((res) => {if (res.meta.status !== 200) {this.$message.error("更新角色信息失败!");}// 隐藏编辑角色对话框this.editRoleDialogVisible = false;this.$message.success("更新角色信息成功!");this.getList();});});},// 删除用户removeRoleById(id) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteRolesAjax(id).then((res) => {// console.log(res);if (res.meta.status == 200) {//如果重新调用list会刷新页面,接口返回的是最新的数据,可以将返回的数据赋值childrenthis.getList();this.$message({type: "success",message: "删除成功!",});}});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},},created() {this.getList();},
};
</script><style lang="scss" scoped >
.el-tag {margin: 7px;
}
// 加上底边框
.bd-bottom {border-bottom: 1px solid #eee;// 只让一级的的el-row有margin值,因为bd-bottom在el-row上,// 所以给bd-bottom加就相当于给一级权限加,二级权限的el-row// 是不需要margin值,要撑满二级权限的19份margin: 0 55px;
}
// 加上,上边框(只加第一个,判断第0个索引)
.bt-top {border-top: 1px solid #eee;
}
// 让tag居中
.display-center {display: flex;align-items: center;justify-content: center;
}
</style>

一步步分析,一点点写 :

1、面包屑

    <!-- 面包屑导航区 --><el-breadcrumb separator-class="el-icon-arrow-right"><el-breadcrumb-item :to="{ path: '/main' }">首页</el-breadcrumb-item><el-breadcrumb-item>权限管理</el-breadcrumb-item><el-breadcrumb-item>角色列表</el-breadcrumb-item></el-breadcrumb>

2、卡片区-添加按钮

 <!-- 卡片 --><el-card><!-- 添加角色按钮 --><el-row ><el-col ><el-button type="primary" >添加角色</el-button></el-col></el-row><!-- 表格 --></el-card>

图例:

3、获取表格数据:

<script>
// users用户列表页(展示分配角色的对话框)也用到这个接口所以直接调用
import { rolesAjax } from "../api";
export default {name: "Roles",data() {return {// 定义所有的角色数据变量rolesList: [],};},created() {this.getList();},methods: {async getList() {//同步方式执行异步请求,等待then的结果await rolesAjax().then((res) => {// console.log(res);if (res.meta.status != 200) {// 如果状态码不是200就return 错误消息return this.$message({type: "error", // success error warningmessage: res.meta.msg,duration: 2000,});} else {//状态码等于200this.rolesList = res.data;this.$message({type: "success", // success error warningmessage: res.meta.msg,duration: 2000,});}});},},};
</script>

4、渲染列表

 <!-- 表格 --><!-- type:对应列的类型。如果设置了 selection 则显示多选框;如果设置了 index 则显示该行的索引(从 1 开始计算);如果设置了 expand 则显示为一个可展开的按钮--><el-table :data="rolesList" border stripe><el-table-column label="展开权限" type="expand" width="120"></el-table-column><el-table-column label="序号" type="index" width="60"></el-table-column><el-table-column label="角色名称" prop="roleName"></el-table-column><el-table-column label="角色描述" prop="roleDesc"></el-table-column><el-table-column label="操作" width="200"><!-- size  尺寸  string  medium / small / mini   —type   类型  string  primary / success / warning / danger / info / text常用图标:搜索:search 编辑:edit  对号:check邮件:message 收藏(星):star-off删除:delete--><template slot-scope="scope"><el-button type="primary" size="mini" icon="el-icon-edit"></el-button><el-button type="danger" size="mini" icon="el-icon-delete"></el-button><el-button type="warning" size="mini" icon="el-icon-setting"></el-button></template></el-table-column></el-table>
  • 分配权限等下做,先做简单的,以上展开是空的

 5、分配权限

  • 5.1、 先打印出来数据看一下,代码被编译输出了

  • 5.1、 使用html原样输出 pre标签, 代码就不是一大坨了

 5.2、分配一级 二级权限

代码使用el-row

 效果:

5.3、渲染一级权限

为一级权限el-tag加边框

//展开行的表格html<el-table-column  type="expand" width="50"><template slot-scope="scope"><!-- 给行加边框 加在el-row上--><el-row v-for="(item1,index1) in scope.row.children" :key="item1.id":class="['bd-bottom',index1==0?'bt-top':'']"><!-- 渲染一级权限 --><el-col :span="5"><el-tag >{{item1.authName}}</el-tag></el-col><!-- 渲染二级、三级权限 --><el-col :span="19">2</el-col></el-row><!-- v-pre vue中跳过标签,不会编译html原样输出 pre标签--><!-- <pre>{{scope.row}}</pre> --></template></el-table-column>
//对应的css<style lang="scss" scoped >
.el-tag {margin: 7px;
}
// 加上底边框
.bd-bottom {border-bottom: 1px solid #eee;// 只让一级的的el-row有margin值,因为bd-bottom在el-row上,// 所以给bd-bottom加就相当于给一级权限加,二级权限的el-row// 是不需要margin值,要撑满二级权限的19份margin: 0 55px;
}
// 加上,上边框(只加第一个,判断第0个索引)
.bt-top {border-top: 1px solid #eee;
}
</style>

图例:

加箭头图标

5.4 、二级权限渲染

  • 在二级权限中 又写了一个el-row,要占满二级权限的19份,分别占6份、13份

图例:

  • 二级权限是绿色的,修改el-tag的type值,顺便在加上右箭头:

  • 给二级权限加上边框,第一个索引对应的会多出一个上边框,所以将第一个索引的上边框取消掉

5.5、渲染三级权限

 6、优化bug

  • 防止屏幕太小,导致布局乱套
//位置:src/assets 公共样式 在main引入
html,
body,
#app {width: 100%;height: 100%;overflow-x: hidden;background-color: #f6f6f6;/* 防止屏幕过小导致页面布局乱套,所以加上最小宽度 */min-width: 1000px;
}
  • 让el-tag居中

7、实现删除tag标签

  • 文档

<el-tag type="warning" closable @close="removeId(scope.row,item3.id)" v-for="item3 in item2.children" :key="item3.id">{{item3.authName}}</el-tag>

当然这里同时为一级、二级也添加@close事件 ,就不一一写了,

  // 删除角色tag标签removeId(row, rightId) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteAjax(row.id, rightId).then((res) => {// console.log(res);if (res.meta.status == 200) {//如果重新调用list会刷新页面,接口返回的是最新的数据,可以将返回的数据赋值children// this.getList();row.children = res.data;this.$message({type: "success",message: "删除成功!",});}});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

8、分配权限

  • 首先给按钮绑定一个事件弹出一个对话框dialog

 // 默认展开被选中的数组,其实就是将三级id递归进来,//注意:点击x关闭对话框需要将数组清空,因为点击不一样,不能让之前的id还存在在数组中defaultKeys: [],
 // 显示分配权限弹框async showDialogVisible(row) {
//点击确定按钮接口要用角色id,先存起来this.roleId = row.id;// 获取分配权限数据await rightsTreeAjax().then((res) => {// console.log(res);// 如果状态码不是200就会return这一句if (res.meta.status !== 200)return this.$message({type: "error", // success error warningmessage: res.meta.msg,duration: 2000,});// 等于200就将数据赋值树形权限数据渲染this.treeList = res.data;// 在对话框开启之前需要获取所有三级权限的id 注意:必须写到ajax中不能写出去否则切换角色无法找到三级id// 当前角色 就是父级了,是ajax请求的数据,将他传入递归函数,他里面有children,要在ajax中调用递归// console.log(row);
//!!!调用递归事件,获取三级idthis.getKeys(row, this.defaultKeys);this.dialogVisible = true;// 我将上面两行写在ajax外,导致无法找到三级id,因为递归的数据是在ajax中得到的,所以写在ajax中// 正确思路应该是 点击权限按钮-请求数据-赋值tree树形数据-调用递归-显示弹框// 用思维解bug:点击无法在弹框前调用递归,肯定是调用递归时问题});},
  • 递归函数获取三级id
 // 递归函数 需求:将三级的id添加到arr数组中getKeys(node, defaultKeys) {//没有children追加到defaultKeys,因为一二级有children所以执行下一步递归if (!node.children) return defaultKeys.push(node.id);// 这个地方很精妙!// 如果有children就将循环的每一项,再次调用一下本函数// 递归:在运行中自己调用自己// 这个函数就会一次次遍历,最终执行到三级权限,没有children就会执行上一句代码node.children.forEach((item) => this.getKeys(item, defaultKeys));},

-----------------------------

递归 分配权限取出三级id

写一个递归取出这个树形结构的数组 的三级id 一个简单例子

-----------------------------

​var list = {"id": 30,"roleName": "主管","roleDesc": "技术负责人",// 一级有children"children": [{"id": 101,"authName": "商品管理","path": null,// 二级有children"children": [{"id": 104,"authName": "商品列表","path": null,// 三级 没有children"children": [{"id": 105,"authName": "添加商品","path": null}, {"id": 1098,"authName": "商品1","path": null}, {"id": 1055,"authName": "添加8465","path": null}, ]}]}]};​

递归 遍历

 var arr = [];// 递归函数 需求:将三级的id添加到arr数组中function getKeys(node, arr) {//没有children追加到arr,因为一二级有children所以执行下一步递归if (!node.children) return arr.push(node.id)
//一二级有children所以执行这一步递归,将遍历的每一项,再次调用这个函数,函数运行中自己调用自己称为递归node.children.forEach(v => getKeys(v, arr))}getKeys(list, arr)console.log(arr);//得到了三级的id 105、1098、1055

但是有的时候后台的数据不是我们想要的就需要自己转化数据结构

线性转化树形

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body></body>
<script>
// 线性转化树形let list = [{name: '一级',id: '1',pid: null}, {name: '二级',id: '2',pid: '1'}, {name: '三级1',id: '3-1',pid: '2'}, {name: '三级2',id: '3-2',pid: '2'}]let treeArr = []function funtree(arr, tree, id) {if (typeof arr == 'undefined') return // 递归结束条件arr.forEach(item => {if (item.pid == id) {tree.push(item) // 一级if (!item.children) {item.children = []funtree(arr, item.children, item.id)}}})}let copyArr = JSON.parse(JSON.stringify(list))// 根据第一级的pid去传funtree() 函数的第三个参数funtree(copyArr, treeArr, null)// 删除空的childrenfunction del(arr) {if (typeof arr == 'undefined') returnarr.forEach(item => {item.children.length != 0 ? del(item.children) : delete item.children;})}del(treeArr)console.log(list);console.log(treeArr);
</script></html>

树形转为线性 :

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body></body>
<script>// 树形转为线性let treeArr = [{name: '一级',id: '1',children: [{name: '二级',id: '2',children: [{name: '三级1',id: '3-1',}, {name: '三级2',id: '3-2',}]}]}]let list = []function fn(arr, list) {if (!arr) { // 递归结束条件return}arr.forEach(item => {list.push(item)if (item.children) { // 如果存在children属性fn(item.children, list)}})}let copyArr = JSON.parse(JSON.stringify(treeArr))fn(copyArr, list)// 删除原有的childrenlist.forEach(item => {if (item.children) {delete item.children}})console.log(treeArr);console.log(list);
</script></html>

-----------------------------

分配权限确定按钮

  • 思路:就是将全选、半选的权限数组转为字符串,(接口要求),将角色id拼接url,字符串作为post请求体

api接口封装在另一个页面,我是这么写的

//  角色授权
export const roleAuthorizationAjax = (id, data) => request({ method: "POST", url: `roles/${id}/rights`, data });
// 分配权限确定按钮async onClick() {//element 定义用ref获取keys// console.log(this.$refs.treeRef.getCheckedNodes());// 定义一个数组用于合并全选、半选的权限const mergeKeys = [...this.$refs.treeRef.getHalfCheckedKeys(),...this.$refs.treeRef.getCheckedKeys(),];// console.log(mergeKeys); // [101,104,2033,555,556]const idStr = mergeKeys.join(",");// console.log(idStr);//  '101,104,2033,555,556'// 发起请求await roleAuthorizationAjax(this.roleId, { rids: idStr }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("分配权限失败!");}this.$message.success("分配权限成功!");this.getList();// 点击按钮关闭分配权限弹框this.dialogVisible = false;});},

至此点击分配权限对话框按钮,已完成!

9、添加用户

 

data:

//   添加用户对话框默认值AddRoleDialogVisible: false,

html:

 <!-- 添加角色对话框 --><el-dialog title="添加角色" :visible.sync="AddRoleDialogVisible" width="40%" @close="addRoleDialogClosed"><el-form :model="addRoleForm" ref="addRoleFormRef" :rules="addRoleFormRules" label-width="100px"><el-form-item label="角色名称" prop="roleName"><el-input v-model="addRoleForm.roleName"></el-input></el-form-item><el-form-item label="角色描述" prop="roleDesc"><el-input v-model="addRoleForm.roleDesc"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="AddRoleDialogVisible = false">取 消</el-button><el-button type="primary" @click="addRoles">确 定</el-button></span></el-dialog>

确定按钮是事件:

   // 添加角色addRoles() {this.$refs.addRoleFormRef.validate(async (valid) => {if (!valid) return;addRolesAjax(this.addRoleForm).then((res) => {console.log(res);if (res.meta.status !== 201) {this.$message.error("添加角色失败!");}this.$message.success("添加角色成功!");this.AddRoleDialogVisible = false;this.getList();});});},// 添加角色对话框的关闭addRoleDialogClosed() {this.$refs.addRoleFormRef.resetFields();},

10、编辑用户 

 // 获取编辑角色async showEditDialog(id) {await ediRolesAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200)return this.$message.error("查询角色信息失败!");this.editRoleForm = res.data;this.editRoleDialogVisible = true;});},// 编辑确定按钮editRolesBtn() {this.$refs.editRoleFormRef.validate(async (valid) => {// console.log(valid); // 空就是false 有值就是true// 表单非空校验失败,是false就returnif (!valid) return;await ediRoles1Ajax(this.editRoleForm.roleId, {roleName: this.editRoleForm.roleName,roleDesc: this.editRoleForm.roleDesc,}).then((res) => {if (res.meta.status !== 200) {this.$message.error("更新角色信息失败!");}// 隐藏编辑角色对话框this.editRoleDialogVisible = false;this.$message.success("更新角色信息成功!");this.getList();});});},

11、删除用户

 // 删除用户removeRoleById(id) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteRolesAjax(id).then((res) => {// console.log(res);if (res.meta.status == 200) {     this.getList();this.$message({type: "success",message: "删除成功!",});}});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

哈哈! 权限列表完!

六、商品管理

1、商品分类

效果图: 

 文档:

好了需求清楚后,代码写起。。

  • 请求数据渲染表格
 // 表格所需的数据cateList: [],
// 请求接口赋值表格所需的数据async getCateList() {// 第一种方式 解构data变成res// let { data: res } = await getCategoriesListAjax(this.queryInfo);// //   console.log(res.total);// //  console.log(res.result);// this.total = res.total;// this.cateList = res.result;// 第二种方式 .thenawait getCategoriesListAjax(this.queryInfo).then((res) => {this.total = res.data.total;this.cateList = res.data.result;});},

注意:element-ui并没有treeTable控件 ,需要我们自行下载插件

github网址:树形表格控件地址

下载依赖:

cnpm i vue-table-with-tree-grid -S

在main.js注册全局组件

import Vue from 'vue'
import ZkTable from 'vue-table-with-tree-grid'Vue.use(ZkTable);

Api

 treeTable

 <!-- 表格 --><!-- data数据 必须是树形结构 --><!-- columns 设置属性 --><!-- border边框 --><!-- show-index 显示索引 index-text="#" 定义索引文字 border 是否显示纵向分割线:show-row-hover 鼠标悬停时,是否高亮当前行 :expand-type="false" 关闭展开行 :selection-type="false" 关闭选择框 --><tree-table class="treeTable" :data="cateList" :columns="columns" :selection-type="false" :expand-type="false" index-text="索引" :show-row-hover="false" show-index border><tree-table >
   // 定义表格字段columns: [{label: "分类名称",prop: "cat_name",},{label: "是否有效",// 当前列 自定义模板type: "template",template: "isOk", //模板名字,还要在插槽中用slot='xx'指定 例如:<template slot="isOk" slot-scope="scope">},{label: "排序",// 当前列 自定义模板type: "template",template: "order",},{label: "操作",// 当前列 自定义模板type: "template",template: "opt",width: "200", //指定宽度},],

2、分页:

 // 查询条件queryInfo: {type: 3,pagenum: 1,pagesize: 5,},
<!-- 分页 --><el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
 // 监听 pageSizeChangehandleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getCateList();},// 监听 页数改变handleCurrentChange(newPage) {// console.log(newPage);this.queryInfo.pagenum = newPage;this.getCateList();},

3、编辑:

// data// 编辑对话框 控制editCateDialogVisible: false,// 编辑分类表单验证editCateFormRules: {cat_name: [{ required: true, message: "请输入分类名称", trigger: "blur" },],},// 编辑表单 绑定对象editCateForm: {},
// html <!-- 编辑分类的对话框 --><el-dialog title="编辑分类" :visible.sync="editCateDialogVisible" width="50%"><el-form :model="editCateForm" :rules="editCateFormRules" ref="editCateFormRef" label-width="100px"><el-form-item label="分类名称:" prop="cat_name"><el-input v-model="editCateForm.cat_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editCateDialogVisible = false">取 消</el-button><el-button type="primary" @click="editBtn">确 定</el-button></span></el-dialog>
// Js// 获取编辑内容async showEditCateDialog(id) {await getEditCategoriesListAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200)return this.$message.error("获取分类失败!");this.editCateForm = res.data;this.editCateDialogVisible = true;});},// 编辑确定按钮editBtn() {this.$refs.editCateFormRef.validate(async (valid) => {if (!valid) return;await editCategoriesListAjax(this.editCateForm.cat_id, {cat_name: this.editCateForm.cat_name,}).then((res) => {if (res.meta.status !== 200)return this.$message.error("更新分类名失败!");this.$message.success("更新分类名成功!");this.getCateList();this.editCateDialogVisible = false;});});},},

4、删除:

//Js
// 删除removeCate(id) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteCategoriesListAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message({type: "error", // success error warningmessage: "删除失败",duration: 2000,});}this.getCateList();this.$message({type: "success",message: "删除成功!",});});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

 5、添加分类:

 //接口请求的分类的数据parentCateList: [],// 添加分类dialogVisible: false,
// 添加分类按钮显示对话框showAddCateDialog() {//思路: 显示对话框之前,获取父级分类的数据,[type可选参数]如果不传递,则默认获取所有级别的分类this.getParentCateList();this.dialogVisible = true;},// 获取父级分类的数据async getParentCateList() {getCategoriesListAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取父级分类失败!");}this.parentCateList = res.data;});},

<!-- 添加分类的对话框 --><!-- ref="addCateFormRef"  点x号获取关闭事件--><!--  @close="addCateDialogClosed" 关闭事件 --><!-- this.$refs.addCateFormRef.resetFields(); 可以通过$refs获取dom关闭 elm规定 --><el-dialog title="添加分类" :visible.sync="dialogVisible" width="50%" @close="addCateDialogClosed"><el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px"><el-form-item label="分类名称:" prop="cat_name"><el-input v-model="addCateForm.cat_name"></el-input></el-form-item><el-form-item label="父级分类:"><!-- :options:数据源 --><!-- :props:指定配置对象有value、children、label... --><!--  v-model=""选中的父级分类id数组,必须指定的是数组,不能是具体的值,因为它是多级分类的数组  --><!-- @change 只要选中的父级分类id数组发生变化就会触发 --><!-- 默认第一级不能选中,可以加这个属性change-on-select --><el-cascader v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChanged" change-on-select="true" clearable filterable style="width: 100%"></el-cascader></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="addCate">确 定</el-button></span></el-dialog>
//data// 指定级联选择器的配置对象cascaderProps: {value: "cat_id", //具体选中的那个值得属性 一般是idlabel: "cat_name", //你所看到的是哪一个属性children: "children", //子父级嵌套的属性是什么expandTrigger: "hover", // 配置触发选项 hover/click},// 选中的父级分类id数组selectedKeys: [],
 // 添加分类 重置表单 点击X号addCateDialogClosed() {this.$refs.addCateFormRef.resetFields();// 选中的父级分类id数组清空this.selectedKeys = [];// 清空之后 分类名称/分类父ID,应该也为0this.addCateForm.cat_level = 0;this.addCateForm.cat_pid = 0;},
//只要选中的父级分类id数组发生变化就会触发, <el-cascader>的@change事件parentCateChanged(val) {// val形参就是变化后父级分类id数组// console.log(val);/* 思路: 如果分类id数组长度>0,说明选中了父级,因为添加的在后面,它父分类的id是数组中最后一项。就将数组最后一项赋值父级id,就得到父分类的id level等级表示: `0`表示一级分类;`1`表示二级分类;`2`表示三级分类, 得出数组长度就是 level等级,数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0*/// 如果selectKeys 数组的长度>0 说明选中父级分类,反之没有选中父级if (this.selectedKeys.length > 0) {// 父级分类的Idthis.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1];// 当前分类的等级this.addCateForm.cat_level = this.selectedKeys.length;return 0;} else {/* 数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0 */// 父级分类的Idthis.addCateForm.cat_pid = 0;// 当前分类的等级this.addCateForm.cat_level = 0;}},

6、商品分类源码:

<template><div id=""><!-- 面包屑导航区 --><el-breadcrumb separator-class="el-icon-arrow-right"><el-breadcrumb-item :to="{ path: '/main' }">首页</el-breadcrumb-item><el-breadcrumb-item>商品管理</el-breadcrumb-item><el-breadcrumb-item>商品分类</el-breadcrumb-item></el-breadcrumb><!-- 表格 --><el-card><el-row><el-col><el-button type="primary" @click="showAddCateDialog">添加分类</el-button></el-col></el-row><!-- 表格 --><!-- data数据 必须是树形结构 --><!-- columns 设置属性 --><!-- border边框 --><!-- show-index 显示索引 index-text="#" 定义索引文字 border 是否显示纵向分割线:show-row-hover 鼠标悬停时,是否高亮当前行 :expand-type="false" 关闭展开行 :selection-type="false" 关闭选择框 --><tree-table class="treeTable" :data="cateList" :columns="columns" :selection-type="false" :expand-type="false" index-text="索引" :show-row-hover="false" show-index border><!-- 是否有效 --><template slot="isOk" slot-scope="scope"><i class="el-icon-success" style="color: lightgreen" v-if="scope.row.cat_deleted === false"></i></template><!-- 排序 --><template slot="order" slot-scope="scope"><el-tag size="mini" v-if="scope.row.cat_level === 0">一级</el-tag><el-tag size="mini" type="success" v-else-if="scope.row.cat_level === 1">二级</el-tag><el-tag size="mini" type="warning" v-else>三级</el-tag></template><!-- 操作 --><template slot="opt" slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditCateDialog(scope.row.cat_id)">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini" @click="removeCate(scope.row.cat_id)">删除</el-button></template></tree-table><!-- 分页 --><el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total"></el-pagination><!-- 添加分类的对话框 --><!-- ref="addCateFormRef"  点x号获取关闭事件--><!--  @close="addCateDialogClosed" 关闭事件 --><!-- this.$refs.addCateFormRef.resetFields(); 可以通过$refs获取dom关闭 elm规定 --><el-dialog title="添加分类" :visible.sync="dialogVisible" width="50%" @close="addCateDialogClosed"><el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px"><el-form-item label="分类名称:" prop="cat_name"><el-input v-model="addCateForm.cat_name"></el-input></el-form-item><el-form-item label="父级分类:"><!-- :options:数据源 --><!-- :props:指定配置对象有value、children、label... --><!--  v-model=""选中的父级分类id数组,必须指定的是数组,不能是具体的值,因为它是多级分类的数组  --><!-- @change 只要选中的父级分类id数组发生变化就会触发 --><!-- 默认第一级不能选中,可以加这个属性change-on-select --><el-cascader v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChanged" change-on-select="true" clearable filterable style="width: 100%"></el-cascader></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="addCate">确 定</el-button></span></el-dialog><!-- 编辑分类的对话框 --><el-dialog title="编辑分类" :visible.sync="editCateDialogVisible" width="50%"><el-form :model="editCateForm" :rules="editCateFormRules" ref="editCateFormRef" label-width="100px"><el-form-item label="分类名称:" prop="cat_name"><el-input v-model="editCateForm.cat_name"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="editCateDialogVisible = false">取 消</el-button><el-button type="primary" @click="editBtn">确 定</el-button></span></el-dialog></el-card></div>
</template><script>
import {getCategoriesListAjax,addCategoriesListAjax,editCategoriesListAjax,getEditCategoriesListAjax,deleteCategoriesListAjax,
} from "../api";
export default {name: "", //对应路由的name,params传参用路由的name 没有 /data() {return {total: 0,// 定义表格字段columns: [{label: "分类名称",prop: "cat_name",},{label: "是否有效",// 当前列 自定义模板type: "template",template: "isOk", //模板名字,还要在插槽中用slot='xx'指定 例如:<template slot="isOk" slot-scope="scope">},{label: "排序",// 当前列 自定义模板type: "template",template: "order",},{label: "操作",// 当前列 自定义模板type: "template",template: "opt",width: "200", //指定宽度},],// 查询条件queryInfo: {type: 3,pagenum: 1,pagesize: 5,},// 表格所需的数据cateList: [],// 编辑对话框 控制editCateDialogVisible: false,// 编辑分类表单验证editCateFormRules: {cat_name: [{ required: true, message: "请输入分类名称", trigger: "blur" },],},// 编辑表单 绑定对象editCateForm: {},//  添加用户对话框dialogVisible: false,// 添加分类dialogVisible: false,// 添加分类对象,后端参数addCateForm: {// 将要添加分类名称cat_name: "",// 分类父级idcat_pid: 0,// 分类等级:`0`表示一级分类;`1`表示二级分类;`2`表示三级分类cat_level: 0,},// 添加分类表单的验证规则addCateFormRules: {cat_name: [{ required: true, message: "请输入分类名称", trigger: "blur" },],},//接口请求的分类的数据parentCateList: [],// 指定级联选择器的配置对象cascaderProps: {value: "cat_id", //具体选中的那个值得属性 一般是idlabel: "cat_name", //你所看到的是哪一个属性children: "children", //子父级嵌套的属性是什么expandTrigger: "hover", // 配置触发选项 hover/click},// 选中的父级分类id数组selectedKeys: [],};},methods: {// 监听 pageSizeChangehandleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getCateList();},// 监听 页数改变handleCurrentChange(newPage) {// console.log(newPage);this.queryInfo.pagenum = newPage;this.getCateList();},// 请求接口赋值表格所需的数据async getCateList() {// 第一种方式 解构data变成res// let { data: res } = await getCategoriesListAjax(this.queryInfo);// //   console.log(res.total);// //  console.log(res.result);// this.total = res.total;// this.cateList = res.result;// 第二种方式 .thenawait getCategoriesListAjax(this.queryInfo).then((res) => {this.total = res.data.total;this.cateList = res.data.result;});},// 添加分类按钮显示对话框showAddCateDialog() {//思路: 显示对话框之前,获取父级分类的数据,[type可选参数]如果不传递,则默认获取所有级别的分类this.getParentCateList();this.dialogVisible = true;},// 获取父级分类的数据async getParentCateList() {getCategoriesListAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取父级分类失败!");}this.parentCateList = res.data;});},// 添加分类 重置表单 点击X号addCateDialogClosed() {this.$refs.addCateFormRef.resetFields();// 选中的父级分类id数组清空this.selectedKeys = [];// 清空之后 分类名称/分类父ID,应该也为0this.addCateForm.cat_level = 0;this.addCateForm.cat_pid = 0;},//只要选中的父级分类id数组发生变化就会触发, <el-cascader>的@change事件parentCateChanged(val) {// val形参就是变化后父级分类id数组// console.log(val);/* 思路: 如果分类id数组长度>0,说明选中了父级,因为添加的在后面,它父分类的id是数组中最后一项。就将数组最后一项赋值父级id,就得到父分类的id level等级表示: `0`表示一级分类;`1`表示二级分类;`2`表示三级分类, 得出数组长度就是 level等级,数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0*/// 如果selectKeys 数组的长度>0 说明选中父级分类,反之没有选中父级if (this.selectedKeys.length > 0) {// 父级分类的Idthis.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1];// 当前分类的等级this.addCateForm.cat_level = this.selectedKeys.length;return 0;} else {/* 数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0 */// 父级分类的Idthis.addCateForm.cat_pid = 0;// 当前分类的等级this.addCateForm.cat_level = 0;}},// 添加分类addCate() {// 测试添加的值,父分类id是否是数组最后一项,// this.addCateForm 是 添加分类对象,后端参数有:cat_level分类层级、cat_name分类名称、cat_pid分类父 ID// console.log(this.addCateForm);this.$refs.addCateFormRef.validate(async (valid) => {if (!valid) return;await addCategoriesListAjax(this.addCateForm).then((res) => {// console.log(res);if (res.meta.status !== 201) {return this.$message.error("添加分类失败!");}this.$message.success("添加分类成功!");this.getCateList();this.dialogVisible = false;});});},// 删除removeCate(id) {this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {deleteCategoriesListAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message({type: "error", // success error warningmessage: "删除失败",duration: 2000,});}this.getCateList();this.$message({type: "success",message: "删除成功!",});});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},// 获取编辑内容async showEditCateDialog(id) {await getEditCategoriesListAjax(id).then((res) => {// console.log(res);if (res.meta.status !== 200)return this.$message.error("获取分类失败!");this.editCateForm = res.data;this.editCateDialogVisible = true;});},// 编辑确定按钮editBtn() {this.$refs.editCateFormRef.validate(async (valid) => {if (!valid) return;await editCategoriesListAjax(this.editCateForm.cat_id, {cat_name: this.editCateForm.cat_name,}).then((res) => {if (res.meta.status !== 200)return this.$message.error("更新分类名失败!");this.$message.success("更新分类名成功!");this.getCateList();this.editCateDialogVisible = false;});});},},created() {this.getCateList();},
};
</script><style lang="scss" scoped >
</style>

七、分类参数

文档:

效果图:

1、渲染级联选择器和面包屑和el-alert

//html<!-- 卡片视图 --><el-card><!-- 警告区域 --><!-- :closable="false" 关闭X号 注意要加:因为他是布尔值不是字符串 不加会报错:Expected Boolean, got String with value "false". --><!-- show-icon 显示图标 --><el-alert title="注意:只允许为第三级分类设置相关参数!" type="warning" show-icon :closable="false"></el-alert><!-- 选择商品分类区域 --><el-row class="cat_opt"><el-col><span>选择商品分类:</span><!-- 商品分类的级联选择框 --><el-cascader v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange"></el-cascader></el-col></el-row></el-card>
//JS<script>
import { getCategoriesListAjax } from "../api";
export default {name: "Params", //对应路由的name,params传参用路由的name 没有 /created() {this.getCateList();},data() {return {//   级联选择框双向绑定数组selectedCateKeys: [],// 商品分类列表cateList: [],//级联选择框的配置对象cateProps: {expandTrigger: "hover",value: "cat_id",label: "cat_name",children: "children",},};},methods: {//   获取所有的商品分类列表async getCateList() {await getCategoriesListAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取商品数据列表失败!");}this.cateList = res.data;});},// change监听的是v-model数组的变化handleChange(val) {// 里面有默认形参val,val就是级联选择框双向绑定数组console.log(val);},},components: {},
};
</script><style lang="scss" scoped >
.cat_opt {margin: 15px 0;
}
</style>

2、只允许获取三级分类

  • 监听级联选择器数组的长度等于3就是三级分类

 // change监听的是v-model数组的变化handleChange(val) {// 里面有默认形参val,val就是级联选择框双向绑定数组// console.log(val);// 只允许选中三级分类思路:监听数组的长度等于3就是三级分类if (this.selectedCateKeys.length !== 3) {this.selectedCateKeys = [];return;}// 是三级分类执行以下代码},

3、实现没有选中三级分类添加按钮就不显示

 computed: {//思路: 根据级联选择数组长度判断是否是3级,(length长度),返回true or false,在用按钮disable自定义属性绑定这个函数isDisable() {if (this.selectedCateKeys.length !== 3) return true;return false;},},

4、参数获取

文档:

拿到三级分类id

computed: {//添加按钮是否禁用思路:// 根据级联选择数组长度判断是否是3级,(length长度),返回true or false,在用按钮disable自定义属性绑定这个函数isDisable() {if (this.selectedCateKeys.length !== 3) return true;return false;},// 当前选中的三级分类IdgetCateId() {// 如果数长度是3说明选中了三级分类if (this.selectedCateKeys.length === 3) {// return 数组中最后一项return this.selectedCateKeys[2];}// 没有选中三级分类return nullreturn null;},},

明确了参数后,就可以发起接口请求了,注意:在级联选择器需要获取数据,在切换tabs也需要获取数据,所以将获取数据封装成函数而且不能直接赋值,因为有两个表格,所以要判断是那个表格

methods: {//   获取所有的商品分类列表async getCateList() {await getCategoriesListAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取商品数据列表失败!");}this.cateList = res.data;});},// change监听的是v-model数组的变化handleChange(val) {// 里面有默认形参val,val就是级联选择框双向绑定数组// console.log(val);// 只允许选中三级分类思路:监听数组的长度等于3就是三级分类if (this.selectedCateKeys.length !== 3) {this.selectedCateKeys = [];return;}// 是三级分类执行以下代码this.getParamsData(); //调用函数},// tabshandleClick(val) {// console.log(this.activeName);this.getParamsData();},// 获取动态参数和静态参数封装函数getParamsData() {getParamsListAjax(this.getCateId, { sel: this.activeName }).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取参数列表失败!");}// 不能直接赋值,因为有两个表格,所以要判断是那个表格if (this.activeName === "many") {this.manyTableData = res.data;} else {this.onlyTableData = res.data;}});},},

5、渲染 动态参数表格、静态属性表格:

<!-- 动态参数表格 start--><el-table :data="manyTableData" border stripe><!-- 展开列 --><el-table-column type="expand">1</el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="参数名称" prop="attr_name"></el-table-column><el-table-column><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button></template></el-table-column></el-table><!-- 动态参数表格 end -->
  <!-- 静态属性表格 start--><el-table :data="onlyTableData" border stripe><!-- 展开列 --><!-- 展开列 --><el-table-column type="expand"></el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="属性名称" prop="attr_name"></el-table-column><el-table-column><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button></template></el-table-column></el-table><!-- 静态属性表格 end-->

图例:

6、添加参数:

文档:

//  computed
//动态参数和静态属性公用一个对话框,所以对话框title不能写死getTitleText() {if (this.activeName === "many") {return "动态参数";}return "静态属性";},
//data//   控制添加参数对话框的显示与隐藏addDialogVisible: false,//   添加表单的数据对象addFrom: {attr_name: "",},//   添加表单的验证规则addFromRules: {attr_name: [{ required: true, message: "请输入参数名称", trigger: "blur" },],},
//html<!-- 添加参数对话框 动态参数和静态属性公用一个--><el-dialog :title=" '添加' + getTitleText" :visible.sync="addDialogVisible" width="50%"><el-form :model="addFrom" :rules="addFromRules" ref="addFromRef" @close="addDialogClosed" label-width="100px"><el-form-item :label="getTitleText" prop="attr_name"><el-input v-model="addFrom.attr_name"></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="addParams">确 定</el-button></span></el-dialog>
// JS// 监听对话框的关闭事件addDialogClosed() {this.$refs.addFromRef.resetFields();},// 添加参数async addParams() {this.$refs.addFromRef.validate(async (valid) => {if (!valid) return;await addParamsListAjax(this.getCateId,{attr_name: this.addFrom.attr_name,attr_sel: this.activeName,}).then(res=>{if (res.meta.status !== 201) {return this.$message.error("添加参数失败!");}this.$message.success("添加参数成功!");this.addDialogVisible = false;// 调用封装函数 获取动态参数和静态参数,因为渲染不同表格是在这个封装函数中写的this.getParamsData();});});},

7、修改参数:

动态属性、静态参数都要添加事件,公用一个对话框,只是title不一样,先查询获取,在确定提交

删除、编辑、提交编辑一般都是点击事件传id,编辑先获取,在回填,在提交

文档:

// data //   编辑对话框editDialogVisible: false,//   修改表单数据对象editFrom: {},//   修改表单验证规则editFromRules: {attr_name: [{ required: true, message: "请输入参数名称", trigger: "blur" },],},
//html<!-- 编辑参数对话框 --><el-dialog :title=" '修改' + getTitleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"><el-form :model="editFrom" :rules="editFromRules" ref="editFromRef" label-width="100px"><el-form-item :label="getTitleText" prop="attr_name"><el-input v-model="editFrom.attr_name"></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="editParams">确 定</el-button></span></el-dialog>
// JS // 显示编辑对话框 并 请求编辑数据,回填async showEditDialogEv(attrId) {//  当前属性id//  console.log(attrId);await getEditParamsListAjax(this.getCateId, attrId, {attr_sel: this.activeTabsName,}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取分类失败!");}this.editFrom = res.data;this.editDialogVisible = true;});},// 重置修改表单editDialogClosed() {this.$refs.editFromRef.resetFields();},// 修改参数async editParams() {this.$refs.editFromRef.validate(async (valid) => {if (!valid) return;await EditOkParamsListAjax(this.getCateId, this.editFrom.attr_id, {attr_name: this.editFrom.attr_name,attr_sel: this.activeName,}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("修改参数失败!");}this.$message.success("修改参数成功!");this.getParamsData();this.editDialogVisible = false;});});},

8、删除参数:

文档:

//JS// 根据Id删除对应的参数项async removeParams(attrId) {const confirmResult = await this.$confirm("此操作将永久删除该参数, 是否继续?","提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).catch((err) => err);if (confirmResult !== "confirm") {return this.$message.info("已取消删除!");}await deleteEditOkParamsListAjax(this.getCateId, attrId).then((res) => {if (res.meta.status !== 200) {return this.$message.error("删除参数失败!");}this.$message.success("删除参数成功!");this.getParamsData();});},

添加、编辑公用一个组件,最重要的是这个封装函数,根据不同的tab值(only,many),判断是哪个表格 

调用接口传入不同的值,最后调用这个封装函数,判断tab值,赋值所对应表格,实现公用一个组件,最后渲染不同的值

// 封装函数: 获取动态参数和静态参数封装函数getParamsData() {// getCateId 计算属性三级id ------activeName tab默认选中值getParamsListAjax(this.getCateId, { sel: this.activeName }).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取参数列表失败!");}// 不能直接赋值,因为有两个表格,所以要判断是那个表格if (this.activeName === "many") {this.manyTableData = res.data;} else {this.onlyTableData = res.data;}});},

9、表格展开操作:

9.1、渲染tab

需要在封装函数中转化一下

如果不判断空数组的话直接字符串转化数组,如果数组为空就会渲染出空的tag标签,这不是我们想要的,所以判断数组中是否为空

控制台打印:

tag渲染:

9.2、input和button 切换

         渲染每一个展开行都公用了 inputVisible,所以展开行的tag会联动,不要公用一个inputVisible,因为有多行都用到了tag,解决:在请求获取动态参数和静态参数封装函数的时候,               
 // 给item每一项添加字段inputVisible,默认是否显示input 

  item.inputVisible = false;
  // 给item每一项添加字段inputVisible,文本框的输入值
  item.inputValue = "";
  在使用tag的时候,在表格获取scope.row.inputVisible,这样点击就不会所有行tag被选中、   

//html<!--
渲染每一个展开行都公用了 inputVisible,所以一所有展开行会联动,不要公用一个inputVisible,因为有多行都用到了tag,解决:在请求获取动态参数和静态参数封装函数的时候,               // 给item每一项添加字段inputVisible,默认是否显示inputitem.inputVisible = false;// 给item每一项添加字段inputVisible,文本框的输入值item.inputValue = "";在使用tag的时候,用scope.row.inputVisible,这样点击就不会所有行tag被选中、     -->
<el-input class="input-new-tag" v-if="scope.row.inputVisible"
v-model="scope.row.inputValue" ref="saveTagInput"
size="small" @keyup.enter.native="handleInputConfirm(scope.row)"
@blur="handleInputConfirm(scope.row)">
</el-input><el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>

封装函数:

  • 封装函数中添加字段
// 封装函数: 获取动态参数和静态参数封装函数getParamsData() {// getCateId 计算属性三级id ------activeName tab默认选中值getParamsListAjax(this.getCateId, { sel: this.activeName }).then((res) => {// console.log(res);res.data.forEach((item) => {//通过三元表达式判断attr_vals是否为空item.attr_vals = item.attr_vals ? item.attr_vals.split(" ") : [];// 解决点击一个另一个也会变化,不能公用一个//防止tag联动 !!!// 给item每一项添加字段inputVisible,默认是否显示inputitem.inputVisible = false;// 给item每一项添加字段inputVisible,文本框的输入值item.inputValue = "";});if (res.meta.status !== 200) {return this.$message.error("获取参数列表失败!");}// 不能直接赋值,因为有两个表格,所以要判断是那个表格if (this.activeName === "many") {this.manyTableData = res.data;} else {this.onlyTableData = res.data;}});
// JS// 文本框失去焦点,或者按下Enter触发handleInputConfirm(row) {// 输入的内容为空时,清空if (row.inputValue.trim().length === 0) {row.inputValue = "";row.inputVisible = false;return;}// 不为空去掉前后空格,追加到数组中row.attr_vals.push(row.inputValue.trim());row.inputValue = "";row.inputVisible = false;// 提交到接口,保存添加的tagthis.saveAttrVals(row);},    // 将对attr_vals(Tag) 的操作 调用接口保存到数据库async saveAttrVals(row) {await EditTagsOkParamsListAjax(this.getCateId, row.attr_id, {attr_name: row.attr_name,attr_sel: row.attr_sel,// 后台的attr_vals是字符串空格隔开的,我们在获取的时候转为数组了,// 现在提交数据到接口,还是要再次转为字符串attr_vals: row.attr_vals.join(" "),}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("修改参数项失败!");}this.$message.success("修改参数项成功!");});},// 点击按钮显示输入框showInput(row) {row.inputVisible = true;//   让输入框自动获取焦点// $nextTick方法的作用:当页面元素被重新渲染之后,才会至指定回调函数中的代码this.$nextTick(() => {this.$refs.saveTagInput.$refs.input.focus();});},// 删除对应的参数可选项也需要调用接口,所以将接口封装函数handleClose(i, row) {// 获取当前行删除一个,调用接口封装row.attr_vals.splice(i, 1);this.saveAttrVals(row);},

10 、优化分类参数bug,选中非三级分类,清空表格

分类参数完整源码:

<template><div id="params"><!-- 面包屑导航区 --><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><!-- 警告区域 --><!-- :closable="false" 关闭X号 注意要加:因为他是布尔值不是字符串 不加会报错:Expected Boolean, got String with value "false". --><!-- show-icon 显示图标 --><el-alert title="注意:只允许为第三级分类设置相关参数!" type="warning" show-icon :closable="false"></el-alert><!-- 选择商品分类区域 --><el-row class="cat_opt"><el-col><span>选择商品分类:</span><!-- 商品分类的级联选择框 --><el-cascader v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader></el-col></el-row><!-- tabs --><el-tabs v-model="activeName" @tab-click="handleClick"><el-tab-pane label="动态参数" name="many"><el-button type="primary" :disabled="isDisable" @click="addDialogVisible = true">添加属性</el-button><!-- 动态参数表格 start--><el-table :data="manyTableData" border stripe><!-- 展开列 --><el-table-column type="expand"><!--插槽自定义列 渲染tag标签 start --><template slot-scope="scope"><!-- tag start --><el-tag v-for="(item, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(i, scope.row)">{{item}}</el-tag><!-- tag end --><!-- input与button 输入 start--><el-input class="input-new-tag" v-if="scope.row.inputVisible" v-model="scope.row.inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm(scope.row)" @blur="handleInputConfirm(scope.row)"></el-input><el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button><!-- input与button 输入 end--></template><!--插槽自定义列 渲染tag标签 start --></el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="参数名称" prop="attr_name"></el-table-column><el-table-column><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialogEv(scope.row.attr_id)">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(scope.row.attr_id)">删除</el-button></template></el-table-column></el-table><!-- 动态参数表格 end --></el-tab-pane><el-tab-pane label="静态属性" name="only"><el-button type="primary" :disabled="isDisable" @click="addDialogVisible = true">添加属性</el-button><!-- 静态属性表格 start--><el-table :data="onlyTableData" border stripe><!-- 展开列 --><el-table-column type="expand"><!--插槽自定义列 渲染tag标签 start --><template slot-scope="scope"><!-- tag start --><el-tag v-for="(item, i) in scope.row.attr_vals" :key="i" closable @close="handleClose(i, scope.row)">{{item}}</el-tag><!-- tag end --><!-- input与button 输入 start--><!--                 渲染每一个展开行都公用了 inputVisible,所以展开行的tag会联动,不要公用一个inputVisible,因为有多行都用到了tag,解决:在请求获取动态参数和静态参数封装函数的时候,               // 给item每一项添加字段inputVisible,默认是否显示input           item.inputVisible = false;// 给item每一项添加字段inputVisible,文本框的输入值item.inputValue = "";在使用tag的时候,用scope.row.inputVisible,这样点击就不会所有行tag被选中、   --><el-input class="input-new-tag" v-if="scope.row.inputVisible" v-model="scope.row.inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm(scope.row)" @blur="handleInputConfirm(scope.row)"></el-input><el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button><!-- input与button 输入 end--></template><!--插槽自定义列 渲染tag标签 start --></el-table-column><!-- 索引列 --><el-table-column type="index"></el-table-column><el-table-column label="属性名称" prop="attr_name"></el-table-column><el-table-column label="操作"><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialogEv(scope.row.attr_id)">编辑</el-button><el-button type="danger" icon="el-icon-delete" size="mini" @click="removeParams(scope.row.attr_id)">删除</el-button></template></el-table-column></el-table><!-- 静态属性表格 end--></el-tab-pane></el-tabs></el-card><!-- 添加参数对话框 动态参数和静态属性公用一个--><el-dialog :title=" '添加' + getTitleText" :visible.sync="addDialogVisible" width="50%"><el-form :model="addFrom" :rules="addFromRules" ref="addFromRef" @close="addDialogClosed" label-width="100px"><el-form-item :label="getTitleText" prop="attr_name"><el-input v-model="addFrom.attr_name"></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="addParams">确 定</el-button></span></el-dialog><!-- 编辑参数对话框 --><el-dialog :title=" '修改' + getTitleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"><el-form :model="editFrom" :rules="editFromRules" ref="editFromRef" label-width="100px"><el-form-item :label="getTitleText" prop="attr_name"><el-input v-model="editFrom.attr_name"></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="editParams">确 定</el-button></span></el-dialog></div>
</template><script>
import {getCategoriesListAjax,getParamsListAjax,addParamsListAjax,getEditParamsListAjax,EditOkParamsListAjax,deleteEditOkParamsListAjax,EditTagsOkParamsListAjax,
} from "../api";
export default {name: "Params", //对应路由的name,params传参用路由的name 没有 /created() {this.getCateList();},data() {return {//   级联选择框双向绑定数组selectedCateKeys: [],// 商品分类列表cateList: [],//级联选择框的配置对象cateProps: {expandTrigger: "hover",value: "cat_id",label: "cat_name",children: "children",},//默认被激活的tabsactiveName: "many",//   动态参数数据manyTableData: [],//   静态属性数据onlyTableData: [],//   控制添加参数对话框的显示与隐藏addDialogVisible: false,//   添加表单的数据对象addFrom: {attr_name: "",},//   添加表单的验证规则addFromRules: {attr_name: [{ required: true, message: "请输入参数名称", trigger: "blur" },],},//   编辑对话框editDialogVisible: false,//   修改表单数据对象editFrom: {},//   修改表单验证规则editFromRules: {attr_name: [{ required: true, message: "请输入参数名称", trigger: "blur" },],},};},methods: {//   获取所有的商品分类列表async getCateList() {await getCategoriesListAjax().then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取商品数据列表失败!");}this.cateList = res.data;});},// change监听的是v-model数组的变化handleChange(val) {// 里面有默认形参val,val就是级联选择框双向绑定数组// console.log(val);// 是三级分类执行以下代码this.getParamsData(); //调用函数},// tabshandleClick(val) {// console.log(this.activeName);this.getParamsData();},// 封装函数: 获取动态参数和静态参数封装函数async getParamsData() {// 只允许选择三级分类:不是三级分类清空表格// 通过数组的长度判断if (this.selectedCateKeys.length !== 3) {this.selectedCateKeys = [];// 清空表格数据this.manyTableData = [];this.onlyTableData = [];return;}// getCateId 计算属性三级id,activeName tab默认选中值await getParamsListAjax(this.getCateId, { sel: this.activeName }).then((res) => {// console.log(res);res.data.forEach((item) => {//通过三元表达式判断attr_vals是否为空item.attr_vals = item.attr_vals ? item.attr_vals.split(" ") : [];// 解决点击一个tag另一行的tag也会变化,不能公用一个初始值,在获取接口数据是,添加字段// 给item每一项添加字段inputVisible,默认是否显示inputitem.inputVisible = false;// 给item每一项添加字段inputVisible,文本框的输入值item.inputValue = "";});if (res.meta.status !== 200) {return this.$message.error("获取参数列表失败!");}// 不能直接赋值,因为有两个表格,所以要判断是那个表格if (this.activeName === "many") {this.manyTableData = res.data;} else {this.onlyTableData = res.data;}});},// 监听对话框的关闭事件addDialogClosed() {this.$refs.addFromRef.resetFields();},// 添加参数async addParams() {this.$refs.addFromRef.validate(async (valid) => {if (!valid) return;await addParamsListAjax(this.getCateId, {attr_name: this.addFrom.attr_name,attr_sel: this.activeName,}).then((res) => {if (res.meta.status !== 201) {return this.$message.error("添加参数失败!");}this.$message.success("添加参数成功!");this.addDialogVisible = false;// 调用封装函数 获取动态参数和静态参数,因为渲染不同表格是在这个封装函数中写的this.getParamsData();});});},// 显示编辑对话框 并 请求编辑数据,回填async showEditDialogEv(attrId) {//  当前属性id//  console.log(attrId);await getEditParamsListAjax(this.getCateId, attrId, {attr_sel: this.activeTabsName,}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取分类失败!");}this.editFrom = res.data;this.editDialogVisible = true;});},// 重置修改表单editDialogClosed() {this.$refs.editFromRef.resetFields();},// 修改参数async editParams() {this.$refs.editFromRef.validate(async (valid) => {if (!valid) return;await EditOkParamsListAjax(this.getCateId, this.editFrom.attr_id, {attr_name: this.editFrom.attr_name,attr_sel: this.activeName,}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("修改参数失败!");}this.$message.success("修改参数成功!");this.getParamsData();this.editDialogVisible = false;});});},// 根据Id删除对应的参数项async removeParams(attrId) {const confirmResult = await this.$confirm("此操作将永久删除该参数, 是否继续?","提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).catch((err) => err);if (confirmResult !== "confirm") {return this.$message.info("已取消删除!");}await deleteEditOkParamsListAjax(this.getCateId, attrId).then((res) => {if (res.meta.status !== 200) {return this.$message.error("删除参数失败!");}this.$message.success("删除参数成功!");this.getParamsData();});},// 文本框失去焦点,或者按下Enter触发handleInputConfirm(row) {// 输入的内容为空时,清空if (row.inputValue.trim().length === 0) {row.inputValue = "";row.inputVisible = false;return;}// 不为空去掉前后空格,追加到数组中row.attr_vals.push(row.inputValue.trim());row.inputValue = "";row.inputVisible = false;// 提交到接口,保存添加的tagthis.saveAttrVals(row);},// 将对attr_vals(Tag) 的操作 调用接口保存到数据库async saveAttrVals(row) {await EditTagsOkParamsListAjax(this.getCateId, row.attr_id, {attr_name: row.attr_name,attr_sel: row.attr_sel,// 后台的attr_vals是字符串空格隔开的,我们在获取的时候转为数组了,// 现在提交数据到接口,还是要再次转为字符串attr_vals: row.attr_vals.join(" "),}).then((res) => {if (res.meta.status !== 200) {return this.$message.error("修改参数项失败!");}this.$message.success("修改参数项成功!");});},// 点击按钮显示输入框showInput(row) {row.inputVisible = true;//   让输入框自动获取焦点// $nextTick方法的作用:当页面元素被重新渲染之后,才会至指定回调函数中的代码this.$nextTick(() => {this.$refs.saveTagInput.$refs.input.focus();});},// 删除对应的参数可选项也需要调用接口,所以将接口封装函数handleClose(i, row) {// 获取当前行删除一个,调用接口封装row.attr_vals.splice(i, 1);this.saveAttrVals(row);},},computed: {//添加按钮是否禁用思路:// 根据级联选择数组长度判断是否是3级,(length长度),//返回true or false,在用按钮disable自定义属性绑定这个函数isDisable() {if (this.selectedCateKeys.length !== 3) return true;return false;},// 当前选中的三级分类Id !!重要,后面接口都要用getCateId() {if (this.selectedCateKeys.length === 3) {// console.log(this.selectedCateKeys[2]);return this.selectedCateKeys[2];}return null;},//动态参数和静态属性公用一个对话框,所以对话框title不能写死getTitleText() {if (this.activeName === "many") {return "动态参数";}return "静态属性";},},
};
</script><style lang="scss" scoped >
.cat_opt {margin: 15px 0;
}.el-tag + .el-tag {margin-left: 10px;
}
.button-new-tag {margin-left: 10px;height: 32px;line-height: 30px;padding-top: 0;padding-bottom: 0;
}
.input-new-tag {width: 90px;margin-left: 10px;vertical-align: bottom;
}
</style>

 分类参数完!

八、商品列表

商品表格

效果图:

源码:

<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><el-row :gutter="20"><el-col :span="6"><el-inputplaceholder="输入商品名称自动搜索。。。"v-model="queryInfo.query"clearable@input="searchUser"></el-input></el-col><el-col :span="4"><el-button type="primary" @click="showDialogBtn">添加商品</el-button></el-col></el-row><!-- 表格数据 --><el-table :data="goodsList" border stripe><el-table-column type="index"></el-table-column><el-table-column label="商品名称" prop="goods_name"></el-table-column><el-table-column label="商品价格(元)" prop="goods_price" width="100px"></el-table-column><el-table-column label="商品重量" prop="goods_weight" width="70px"></el-table-column><el-table-column label="商品数量" prop="goods_number" width="70px"></el-table-column><el-table-column label="创建时间" prop="add_time" width="140px"><template slot-scope="scope">{{ scope.row.add_time | formatePrice }}</template></el-table-column><el-table-column label="操作" width="130px"><template slot-scope="scope"><el-button type="primary" icon="el-icon-edit" size="mini"></el-button><el-buttontype="danger"icon="el-icon-delete"size="mini"@click="removeById(scope.row.goods_id)"></el-button></template></el-table-column></el-table><!-- 分页区域 --><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="queryInfo.pagenum":page-sizes="[5, 10, 15, 20]":page-size="queryInfo.pagesize"layout="total, sizes, prev, pager, next, jumper":total="total"background></el-pagination></el-card></div>
</template><script>
import _ from "lodash";
import { getGoodsListAjax, deleteGoodsListAjax, addGoodsUserAjax } from "../../api";
export default {data() {return {queryInfo: {query: "",pagenum: 1,pagesize: 10,},// 商品列表goodsList: [],// 商品总数total: 0,// 默认添加变量dialogVisible: false,};},created() {this.getGoodsList();},methods: {// 根据分页获取对应的商品列表async getGoodsList() {await getGoodsListAjax(this.queryInfo).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取商品列表失败!");}this.goodsList = res.data.goods;//   console.log(this.goodsList)this.total = res.data.total;});},handleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getGoodsList();},handleCurrentChange(newSize) {this.queryInfo.pagenum = newSize;this.getGoodsList();},// 通过Id删除商品async removeById(id) {const confirmResult = await this.$confirm("此操作将永久删除该商品, 是否继续?","提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).catch((err) => err);if (confirmResult !== "confirm") {return this.$message.info("已取消删除!");}await deleteGoodsListAjax(id).then((res) => {if (res.meta.status !== 200) {return this.$message.error("删除商品失败!");}this.$message.success("删除商品成功!");this.getGoodsList();});},// 添加商品showDialogBtn() {this.$router.push('/goods/add');},//点击搜索 输入搜索防抖searchUser: _.debounce(function () {this.getGoodsList();}, 1000),// 添加确定按钮addBtn() {this.dialogVisible = false;addGoodsUserAjax(this.ruleForm).then((res) => {if (res.meta.status !== 201) {return this.$message.error("添加商品失败!");}this.$message.success("添加商品成功!");// 添加成功了就重新渲染this.getGoodsList();});},// 点X关闭添加框resetAddForm() {(this.ruleForm.goods_price = ""),(this.ruleForm.goods_name = ""),(this.ruleForm.goods_weight = ""),(this.ruleForm.goods_number = "");},},
};
</script><style lang="less" scoped>
</style>
  • 点击添加按钮跳转到good/add
  • //router/index{// 这个是浏览器url显示的路径 !!path: '/goods/add',name: '',meta: {title: "添加商品",requiredPath: true},component: () =>//路径的文件位置import ('../views/main/goods/add.vue'),}

添加商品

效果图:

1、面包屑和步骤条:

​​ data() {return {// 步骤条默认激活 与左侧Tab联动activeIndex: "0",};},
// html<!-- 面包屑导航区 --><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><!-- 提示 --><el-alerttitle="添加商品信息"type="info"centershow-icon:closable="false"></el-alert><!-- 步骤条 --><el-steps:space="200":active="activeIndex - 0"finish-status="success"align-center><el-step title="基本信息"></el-step><el-step title="商品参数"></el-step><el-step title="商品属性"></el-step><el-step title="商品图片"></el-step><el-step title="商品内容"></el-step><el-step title="完成"></el-step></el-steps></el-card>

tab和步骤条实现同步思路:就是让tab绑定activeIndex,el组件提供tab的name属性可以与v-model绑定,但是activeIndex绑定name成字符串了,可以利用隐式转换,字符串与number运算会自动转为数值在比较activeIndex-0,同时注意el-tabs 的子节点只允许是el-tab-pane,所以将form包裹在tabs外

2、渲染页面 基本信息form

 <!--  label-position="top" 指定文字在上面 --><el-form:model="addForm":rules="addFormRules"label-width="100px"label-position="top"><!-- 注意el-tabs 的子节点只允许是el-tab-pane,所以将form包裹在tabs外,form的子项包裹在el-tab-pane中 --><!-- 写好了form发现tabs高度不够了,可以将style="height: 200px"删除 --><el-tabs tab-position="left" v-model="activeIndex"><el-tab-pane label="基本信息" name="0"><el-form-item label="商品名称" prop="goods_name"><el-input v-model="addForm.goods_name"></el-input></el-form-item><el-form-item label="商品价格" prop="price"><!--  type="number el组件规定只能输入数值 --><el-input v-model="addForm.goods_price" type="number"></el-input></el-form-item><el-form-item label="商品重量" prop="goods_weight"><el-input v-model="addForm.goods_weight" type="number"></el-input></el-form-item><el-form-item label="商品数量" prop="goods_number"><el-input v-model="addForm.goods_number" type="number"></el-input></el-form-item><el-form-item label="商品分类" prop="goods_cat"><el-cascaderv-model="addForm.goods_cat":options="cateList":props="cascaderProps"@change="handleChange"></el-cascader></el-form-item></el-tab-pane><el-tab-pane label="商品参数" name="1">配置管理</el-tab-pane><el-tab-pane label="商品属性" name="2">角色管理</el-tab-pane><el-tab-pane label="商品图片" name="3">定时任务补偿</el-tab-pane><el-tab-pane label="商品内容" name="4">定时任务补偿</el-tab-pane></el-tabs></el-form>

3、请求级联选择器的数据

//data// 添加商品的表单对象addForm: {goods_name: "",goods_price: 0,goods_weight: 0,goods_number: 0,// 级联选择器的分类数组goods_cat: [],// 图片的数组pics: [],// 商品详情描述goods_introduce: "",attrs: [],},//验证规则addFormRules: {goods_name: [{ required: true, message: "请输入商品名称", trigger: "blur" },],goods_price: [{ required: true, message: "请输入商品价格", trigger: "blur" },],goods_weight: [{ required: true, message: "请输入商品重量", trigger: "blur" },],goods_number: [{ required: true, message: "请输入商品数量", trigger: "blur" },],goods_cat: [{ required: true, message: "请选择商品分类", trigger: "blur" },],},// 商品列表cateList: [],// 级联选择器配置cascaderProps: {expandTrigger: "hover",label: "cat_name",value: "cat_id",children: "children",},
// JS// 级联选择器选中项变化时触发,会直接绑定到handleChange() {if (this.addForm.goods_cat.length !== 3) {this.addForm.goods_cat = [];}},

4、实现不选中分类不允许进行下一步

  • 思路:监听tabs的change事件,如果选中三级分类,就可以执行下一步

 beforeTabLeave (activeName, odlActiveName) {// 未选中商品分类阻止Tab标签跳转if (odlActiveName === '0' && this.addForm.goods_cat.length !== 3) {this.$message.error('请先选择商品分类')return false}

5、商品参数  (动态参数)

文档说明:

效果图:

tabs的点击事件:

根据点击激活的tbas激活值,来判断是否点击了商品参数,(activeIndex) 

 将三级分类id设置成计算属性获取:

@tab-click事件

  • 因为后台返回的attr_vals数据是以逗号分隔的字符串,空字符串转数组是空数组,会渲染空复选框所以要判空
 // Tab标签被选中时触发async tabClicked() {// 访问动态参数面板if (this.activeIndex === "1") {await GetAddParamsAjax(this.getCateId, { sel: "many" }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取动态参数列表失败!");}//因为后台返回的attr_vals数据是以逗号分隔的字符串,空字符串转数组是空数组,会渲染空复选框所以要判空res.data.forEach((item) => {item.attr_vals =item.attr_vals.length === 0 ? [] : item.attr_vals.split(" ");});this.manyTableData = res.data;});} else if (this.activeIndex === "2") {await GetAddParamsAjax(this.getCateId, { sel: "only" }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取静态态属性列表失败!");}this.onlyTableData = res.data;});}},

attr_vals字符串转为数组后就可以使用复选框渲染了,但是复选框有默认外边距,在样式中用最高权重中解决

.el-checkbox {// 复选框样式 上右下左margin: 0 8px 0 0 !important;
}

效果图例:

6、商品属性 (静态属性)

文档说明

tabs事件 

 效果图:

获取好数据后就可以用el-input循环渲染 了

成功:

7、商品图片 (重要!)

上传图片 

el-upload结构---保存到服务器--将返回的临时路径追加到添加对象的pics数组   完成上传

 效果图:

el组件:

 文档:

//data// 图片上传地址uploadURL: "http://127.0.0.1:8888/api/private/v1/upload",// 图片上传组件的请求对象headerObj: {Authorization: JSON.parse(window.sessionStorage.getItem("token")),},
//html<el-tab-pane label="商品图片" name="3"><!-- action: 图片上传的API接口地址 --><!-- 文档接口是upload action指定接口地址,但是不能直接指定upload,指定upload是相对浏览器的 相当于:http://localhost:8080/#/goods/add/upload但是图片地址不是,所以要指定完整地址,注意还要添加token,因为el-upload不使用我们的axios,它自己有自己封装的,但是它也想到这一点所以提供了headers属性,它接受一个对象,属性是Authorization ,值是token字符串--><!--on-preview预览事件  on-remove删除事件 list-type指定展示方式 on-success--><el-upload:action="uploadURL":on-preview="handlePreview":on-remove="handleRemove":headers="headerObj"list-type="picture":on-success="handleSuccess"><el-button size="small" type="primary">点击上传</el-button></el-upload></el-tab-pane>

此时服务器已经存储了图片,下一步还要添加到表单中

商品文档: 

代码:

 将临时路径追加到添加对象的pic数组中,以对象形式

 // 监听图片上传成功事件handleSuccess(response) {console.log(response);// 1.拼接得到一个图片信息对象 临时路径const picInfo = { pic: response.data.tmp_path };// 2.将图片信息对象,push到pics数组中this.addForm.pics.push(picInfo);},

 删除图片

思路:删除事件默认接受file形参,就是要删除的图片临时路径,用findIndex找到pics数组中对应要删除的哪一项,splice方法删除

// 处理移除图片的操作handleRemove(file) {// console.log(file);// 1.获取将要删除图片的临时路径const filePath = file.response.data.tmp_path;// 2.从pics数组中,找到图片对应的索引值//pics数组中的每一项等于要删除的哪一项,就在pics数组中将其删除const i = this.addForm.pics.findIndex((v) => v.pic === filePath);// 3.调用splice方法,移除图片信息this.addForm.splice(i, 1);},

 预览图片

思路:在预览事件中得到url真正图片地址,,复制一个对话框用src来展示

html

 data

//JS
// 处理图片预览handlePreview(file) {// console.log(file); //url就是真正路径this.picPreviewPath = file.response.data.url;this.previewDialogVisible = true;},

8、富文本 添加按钮操作

github官方网址

下载

cnpm install vue-quill-editor –save

引入

//main.js// 导入富文本编辑器
import VueQuillEditor from 'vue-quill-editor'
// 导入富文本编辑器样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'

在页面使用

         <!-- 富文本编辑器 --><quill-editor v-model="addForm.goods_introduce"></quill-editor><!-- 添加商品 --><el-button type="primary" class="btnAdd" @click="addGoods">添加商品</el-button>

在公共样式表位置设置富文本的最小高度

  /* 全局富文本样式 */.ql-editor {min-height: 300px!important;}

8.1、处理goods_cat

添加文档:需要将goods_cat字符串,但是级联选择器需要数组,所以只能深拷贝一份相同的

 如下图:

 使用Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

完成深拷贝

下载

cnpm i --save lodash

中文官网  建议撸一遍,收获颇多

官方网址http://​ https://www.lodashjs.com/ ​

深拷贝完成

8.2、处理attr参数

添加文档参数:

添加文档数据:

//dataaddForm: {goods_name: "",goods_price: 0,goods_weight: 0,goods_number: 0,// 级联选择器的分类数组goods_cat: [],// 图片的数组pics: [],// 富文本v-model绑定值,后台提供的参数goods_introduce: "",// 在页面上操作的静态、动态最终都要追加到attr数组,以对象形式attrs: [],},

参数列表渲染后的表格其实就是

动态参数的attr_value是一个数组,文档要求传递字符串

静态属性的 attr_value本来就是字符串

  // 添加商品addGoods() {// async 是加在 (valid)前的this.$refs.addFormRef.validate(async (valid) => {if (!valid) return this.$message.error("请填写必要的表单项!");// 发送请求前:需对提交的表单进行处理// this.addForm.goods_cat = this.addForm.goods_cat.join(',')// 以上写法不对:级联选择器绑定的对象goods_cat要求是数组对象 !!/* 添加文档,需要将goods_cat转字符串,但是级联选择器需要数组,所以只能深拷贝一份相同的 */// 解决办法: 包:lodash 方法(深拷贝):cloneDeep(boj)// console.log(this.addForm);// 将this.addForm进行深拷贝const form = _.cloneDeep(this.addForm);// 深拷贝后装换form的数据,form只为添加用,addForm只为级联选择器用form.goods_cat = form.goods_cat.join(",");// console.log(form);// 处理动态参数this.manyTableData.forEach((item) => {// 后台文档只需要这两个属性就可以const newInfo = {attr_id: item.attr_id,// 动态参数的attr_value是一个数组,文档要求传递字符串attr_value: item.attr_vals.join(" "),};// 将循环后的对象追加addFormthis.addForm.attrs.push(newInfo);});// 处理静态属性this.onlyTableData.forEach((item) => {const newInfo = {attr_id: item.attr_id,// 静态属性的 attr_value本来就是字符串attr_value: item.attr_vals,};this.addForm.attrs.push(newInfo);});// 现在addForm.attrs已经有这两个属性了再将它赋值form(深拷贝用于添加)form.attrs = this.addForm.attrs;// 发起请求添加商品// 商品名称必须是唯一的await addGoodsUserAjax(form).then((res) => {if (res.meta.status !== 201)return this.$message.error("添加商品失败!");this.$message.success("添加商品成功!");this.$router.push("/goods");});});},

添加 源码:

<template><div id="add"><!-- 面包屑导航区 --><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><!-- 提示 --><el-alerttitle="添加商品信息"type="info"centershow-icon:closable="false"></el-alert><!-- 步骤条 设置active属性,接受一个Number--><el-steps:space="200":active="activeIndex - 0"finish-status="success"align-center><el-step title="基本信息"></el-step><el-step title="商品参数"></el-step><el-step title="商品属性"></el-step><el-step title="商品图片"></el-step><el-step title="商品内容"></el-step><el-step title="完成"></el-step></el-steps><!-- tabs --><!-- tab和步骤条实现同步思路:就是让tab绑定activeIndex,tab el组件提供name属性与v-model绑定 但是activeIndex绑定到name成字符串了,可以利用隐式转换,字符串与number运算会自动转为数值在比较activeIndex-0,同时注意el-tabs 的子节点只允许是el-tab-pane,所以将form包裹在tabs外--><!--  label-position="top" 指定文字在上面 --><el-form:model="addForm":rules="addFormRules"ref="addFormRef"label-width="100px"label-position="top"><!-- 注意el-tabs 的子节点只允许是el-tab-pane,所以将form包裹在tabs外,form的子项包裹在el-tab-pane中 --><!-- 写好了form发现tabs高度不够了,可以将style="height: 200px"删除 所有数据都在form的双向数据表单中--><el-tabstab-position="left"v-model="activeIndex":before-leave="beforeTabLeave"@tab-click="tabClicked"><el-tab-pane label="基本信息" name="0"><el-form-item label="商品名称" prop="goods_name"><el-input v-model="addForm.goods_name"></el-input></el-form-item><el-form-item label="商品价格" prop="price"><!--  type="number el组件规定只能输入数值 --><el-input v-model="addForm.goods_price" type="number"></el-input></el-form-item><el-form-item label="商品重量" prop="goods_weight"><el-input v-model="addForm.goods_weight" type="number"></el-input></el-form-item><el-form-item label="商品数量" prop="goods_number"><el-input v-model="addForm.goods_number" type="number"></el-input></el-form-item><el-form-item label="商品分类" prop="goods_cat"><el-cascaderv-model="addForm.goods_cat":options="cateList":props="cascaderProps"@change="handleChange"></el-cascader></el-form-item></el-tab-pane><el-tab-pane label="商品参数" name="1"><!-- 渲染表单的Item项 指定label值--><el-form-itemv-for="item in manyTableData":key="item.attr_id":label="item.attr_name"><!-- 复选框组 --><el-checkbox-group v-model="item.attr_vals"><!-- 复选框 --><el-checkbox:label="cb"v-for="(cb, i) in item.attr_vals":key="i"border></el-checkbox></el-checkbox-group> </el-form-item></el-tab-pane><el-tab-pane label="商品属性" name="2"><el-form-item:label="item.attr_name"v-for="item in onlyTableData":key="item.attr_id"><el-input v-model="item.attr_vals"></el-input></el-form-item></el-tab-pane><el-tab-pane label="商品图片" name="3"><!-- action: 图片上传的API接口地址 --><!-- 文档接口是upload action指定接口地址,但是不能直接指定upload,指定upload是相对浏览器的 相当于:http://localhost:8080/#/goods/add/upload但是图片地址不是,所以要指定完整地址,注意还要添加token,因为el-upload不使用我们的axios,它自己有自己封装的,但是它也想到这一点所以提供了headers属性,它接受一个对象,属性是Authorization ,值是token字符串--><!--on-preview预览事件  on-remove删除事件 list-type指定展示方式 on-success--><el-upload:action="uploadURL":on-preview="handlePreview":on-remove="handleRemove":headers="headerObj"list-type="picture":on-success="handleSuccess"><el-button size="small" type="primary">点击上传</el-button></el-upload></el-tab-pane><el-tab-pane label="商品内容" name="4"><!-- 富文本编辑器 --><quill-editor v-model="addForm.goods_introduce"></quill-editor><!-- 添加商品 --><el-button type="primary" class="btnAdd" @click="addGoods">添加商品</el-button></el-tab-pane></el-tabs></el-form></el-card><!-- 预览图片对话框 --><el-dialogtitle="图片预览":visible.sync="previewDialogVisible"width="50%"><img :src="picPreviewPath" alt="" class="previewImg" /></el-dialog></div>
</template><script>
import {getCategoriesListAjax,GetAddParamsAjax,addGoodsUserAjax,
} from "../../api";
import _ from "lodash";
export default {name: "", //对应路由的name,params传参用路由的name 没有 /data() {return {// 步骤条默认激活 与左侧Tab联动activeIndex: "0",// 添加商品的表单对象addForm: {goods_name: "",goods_price: 0,goods_weight: 0,goods_number: 0,// 级联选择器的分类数组goods_cat: [],// 图片的数组pics: [],// 富文本v-model绑定值,后台提供的参数goods_introduce: "",// 在页面上操作的静态、动态最终都要追加到attr数组,以对象形式attrs: [],},//验证规则addFormRules: {goods_name: [{ required: true, message: "请输入商品名称", trigger: "blur" },],goods_price: [{ required: true, message: "请输入商品价格", trigger: "blur" },],goods_weight: [{ required: true, message: "请输入商品重量", trigger: "blur" },],goods_number: [{ required: true, message: "请输入商品数量", trigger: "blur" },],goods_cat: [{ required: true, message: "请选择商品分类", trigger: "blur" },],},// 商品列表cateList: [],// 级联选择器配置cascaderProps: {expandTrigger: "hover",label: "cat_name",value: "cat_id",children: "children",},// 动态参数列表数据manyTableData: [],// 静态属性列表数据onlyTableData: [],// 图片上传地址uploadURL: "http://127.0.0.1:8888/api/private/v1/upload",// 图片上传组件的请求对象headerObj: {Authorization: JSON.parse(window.sessionStorage.getItem("token")),},// 预览图片默认值picPreviewPath: "",// 图片预览对话框previewDialogVisible: false,};},methods: {// 获取商品分类数据列表async getCateList() {await getCategoriesListAjax().then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取商品列表失败!");}this.cateList = res.data;});},// 级联选择器选中项变化时触发,会直接绑定到handleChange() {if (this.addForm.goods_cat.length !== 3) {this.addForm.goods_cat = [];}},// 未选中商品分类阻止Tab标签跳转//activeName:当前激活的tabs索引,odlActiveName:之前被激活的索引beforeTabLeave(activeName, odlActiveName) {// 如果是基本信息tabs并且没有选中三级分类 return false阻止下一步并提示if (odlActiveName === "0" && this.addForm.goods_cat.length !== 3) {this.$message.error("请先选择商品分类");return false; //el 提供 return false阻止下一步}},// Tab标签被选中时触发async tabClicked() {// 访问动态参数面板if (this.activeIndex === "1") {await GetAddParamsAjax(this.getCateId, { sel: "many" }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取动态参数列表失败!");}//因为后台返回的attr_vals数据是以逗号分隔的字符串,空字符串转数组是空数组,会渲染空复选框所以要判空res.data.forEach((item) => {item.attr_vals =item.attr_vals.length === 0 ? [] : item.attr_vals.split(" ");});this.manyTableData = res.data;});// 如果点击的是商品静态属性} else if (this.activeIndex === "2") {await GetAddParamsAjax(this.getCateId, { sel: "only" }).then((res) => {if (res.meta.status !== 200) {return this.$message.error("获取静态态属性列表失败!");}this.onlyTableData = res.data;});}},// 处理图片预览handlePreview(file) {// console.log(file); //url就是真正路径this.picPreviewPath = file.response.data.url;this.previewDialogVisible = true;},// 处理移除图片的操作handleRemove(file) {// console.log(file);// 1.获取将要删除图片的临时路径const filePath = file.response.data.tmp_path;// 2.从pics数组中,找到图片对应的索引值//pics数组中的每一项等于要删除的哪一项,就在pics数组中将其删除const i = this.addForm.pics.findIndex((v) => v.pic === filePath);// 3.调用splice方法,移除图片信息this.addForm.pics.splice(i, 1);},// 监听图片上传成功事件handleSuccess(response) {// console.log(response);// 1.拼接得到一个图片信息对象 临时路径const picInfo = { pic: response.data.tmp_path };// 2.将图片信息对象,push到pics数组中this.addForm.pics.push(picInfo);},// 添加商品addGoods() {// async 是加在 (valid)前的this.$refs.addFormRef.validate(async (valid) => {if (!valid) return this.$message.error("请填写必要的表单项!");// 发送请求前:需对提交的表单进行处理// this.addForm.goods_cat = this.addForm.goods_cat.join(',')// 以上写法不对:级联选择器绑定的对象goods_cat要求是数组对象 !!/* 添加文档,需要将goods_cat转字符串,但是级联选择器需要数组,所以只能深拷贝一份相同的 */// 解决办法: 包:lodash 方法(深拷贝):cloneDeep(boj)// console.log(this.addForm);// 将this.addForm进行深拷贝const form = _.cloneDeep(this.addForm);// 深拷贝后装换form的数据,form只为添加用,addForm只为级联选择器用form.goods_cat = form.goods_cat.join(",");// console.log(form);// 处理动态参数this.manyTableData.forEach((item) => {// 后台文档只需要这两个属性就可以const newInfo = {attr_id: item.attr_id,// 动态参数的attr_value是一个数组,文档要求传递字符串attr_value: item.attr_vals.join(" "),};// 将循环后的对象追加addFormthis.addForm.attrs.push(newInfo);});// 处理静态属性this.onlyTableData.forEach((item) => {const newInfo = {attr_id: item.attr_id,// 静态属性的 attr_value本来就是字符串attr_value: item.attr_vals,};this.addForm.attrs.push(newInfo);});// 现在addForm.attrs已经有这两个属性了再将它赋值form(深拷贝用于添加)form.attrs = this.addForm.attrs;// 发起请求添加商品// 商品名称必须是唯一的await addGoodsUserAjax(form).then((res) => {if (res.meta.status !== 201)return this.$message.error("添加商品失败!");this.$message.success("添加商品成功!");this.$router.push("/goods");});});},},computed: {// 获取三级分类idgetCateId() {if (this.addForm.goods_cat.length === 3) {return this.addForm.goods_cat[2];}return null;},},components: {},created() {this.getCateList();},
};
</script><style lang="scss" scoped>
.el-steps {margin: 15px 0;
}
.el-checkbox {// 复选框样式 上右下左margin: 0 8px 0 0 !important;
}
.previewImg {width: 100%;
}
.btnAdd {margin-top: 15px;
}
</style>

添加商品完!

九、商品订单-列表

因为物流属于隐私所以咱们就简单实现样式!定义三级联动的js在页面导入,实现三级联动

注意:需要用到时间线展示物流信息,element 2.6.3版本才有,物流接口不能用,咱没权限访问,所以用了一个json模拟,我将json定义在public里面在使用的页面有axios请求,在main.js引入axiso,并挂载到原型上

<template><div id=""><!-- 面包屑导航区 --><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><el-row><el-col :span="6"><el-input placeholder="请输入内容"><el-button slot="append" icon="el-icon-search"></el-button></el-input></el-col></el-row><!-- 订单列表 --><el-table :data="orderList" border stripe><el-table-column type="index" label="#"></el-table-column><el-table-column label="订单编号" prop="order_number"></el-table-column><el-table-column label="订单价格" prop="order_price"></el-table-column><el-table-column label="是否付款"><template slot-scope="scope"><el-tag type="danger" size="mini" v-if="scope.row.pay_status">未付款</el-tag><el-tag type="success" size="mini" v-else>已付款</el-tag></template></el-table-column><el-table-column label="是否发货" prop="is_send"></el-table-column><el-table-column label="下单时间"><template slot-scope="scope">{{ scope.row.create_time | formatePrice }}</template></el-table-column><el-table-column label="操作" width="200"><template slot-scope="scope"><el-buttontype="primary"size="mini"icon="el-icon-edit"@click="showEditDialog"></el-button><el-buttontype="success"size="mini"icon="el-icon-location"@click="locationVi"></el-button></template></el-table-column></el-table><!-- 分页区域 --><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="queryInfo.pagenum":page-sizes="[5, 10, 15, 20]":page-size="queryInfo.pagesize"layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination></el-card><!-- 编辑对话框 --><el-dialogtitle="修改地址":visible.sync="addressDialogVisible"width="50%"><!-- form --><el-form:model="addressForm":rules="addressFormRules"ref="addressFormRef"label-width="100px"><el-form-item label="省市区/县" prop="address1"><!-- cityData 就是城市js --><el-cascaderv-model="addressForm.address1":options="cityData":props="{ expandTrigger: 'hover' }"></el-cascader></el-form-item><el-form-item label="详细地址" prop="address2"><el-input v-model="addressForm.address2"></el-input></el-form-item></el-form><!-- form --><span slot="footer" class="dialog-footer"><el-button @click="addressDialogVisible = false">取 消</el-button><el-button type="primary" @click="addressDialogVisible = false">确 定</el-button></span> </el-dialog>、<!-- 展示物流 --><el-dialog title="展示物流" :visible.sync="kDDialogVisible" width="50%"><!-- 时间线 --><el-timeline><el-timeline-itemv-for="(item, index) in kuaidi":key="index":timestamp="item.time">{{item.context}}</el-timeline-item></el-timeline><span slot="footer" class="dialog-footer"><el-button @click="kDDialogVisible = false">取 消</el-button><el-button type="primary" @click="kDDialogVisible = false">确 定</el-button></span></el-dialog></div>
</template><script>
import { GetOrdersAjax } from "../../api.js";
// 导入省市区js
import cityData from "./citydata.js";
export default {name: "", //对应路由的name,params传参用路由的name 没有 /data() {return {// 订单列表查询参数queryInfo: {query: "",pagenum: 1,pagesize: 10,},total: 0,// 订单列表orderList: [],// 快递JSONkuaidi: [],addressDialogVisible: false,// 快递对话框kDDialogVisible: false,addressForm: {//  三级联动address1: [],address2: "",},addressFormRules: {address1: [{ required: true, message: "请选择省市区县", trigger: "blur" },],address2: [{ required: true, message: "请输入详细地址", trigger: "blur" },],},cityData,};},methods: {async getOrderList() {await GetOrdersAjax(this.queryInfo).then((res) => {// console.log(res);if (res.meta.status !== 200) {return this.$message.error("获取订单列表失败!");}this.total = res.data.total;this.orderList = res.data.goods;});},// 分页handleSizeChange(newSize) {this.queryInfo.pagesize = newSize;this.getOrderList();},handleCurrentChange(newSize) {this.queryInfo.pagenum = newSize;this.getOrderList();},showEditDialog() {this.addressDialogVisible = true;},// 位置locationVi() {// json放在public中this.$axios('/kuaidi.json').then((res) => {this.kuaidi = res.data.data;// console.log(this.kuaidi);});this.kDDialogVisible = true;},},created() {this.getOrderList();},
};
</script><style lang="scss" scoped>
.el-cascader {width: 100%;
}
</style>

十、数据统计-ECharts

我们快要结束了

Apache ECharts官方文档

1、下载依赖

cnpm install echarts --save 

2、导入

import echarts from 'echarts'

如果报错请使用这种引入

const echarts = require('echarts')

3、为Echarts准备一个Dom容器定义Echarts宽高,不然它放哪

// 例如:<!-- 卡片视图 --><el-card><!-- 2.为Echarts准备一个Dom --><div id="main" style="width: 750px;height:400px;"></div></el-card>

4、初始化 Echarts获取定义它的dom,所以在mounterd初始化

// 例如
// 4.基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById('main'))

5、准备数据和配置项可以直接在官网粘过来

//指定图表的配置项和数据var option = {title: {text: 'ECharts 入门示例'},tooltip: {},legend: {data: ['销量']},xAxis: {data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']},yAxis: {},series: [{name: '销量',type: 'bar',data: [5, 20, 36, 10, 10, 20]}]}

6、展示数据

 // 6.展示数据myChart.setOption(option)

上面是事例,如何快速上手,一般我们的数据来自后台,不会使用官方的数据

所以我们借助lodash来完成合并,自己手动合并也可以要递归,直接使用扩展运算符和Object.assign不可以,思路:先将后台的数据定义在data中,再将请求的数据与之合并,将合并后的变量传给setOption

使用lodash合并图例:效果图正常

看下lodash打印后合并的对象 :全部递归了平铺了

在看手动合并的对象形式:效果图乱套了,原因是Object.assign()只是单纯的合并没有递归,渲染时找不到准确的数据

解释:Object.assign()

手动合并:

假设现在有两个对象:

var obj1 = { name:'张三' };
var obj2= { age:18 };

我们想要得到obj3 = {name:’张三’,age:18};

合并对象:Object.assign(obj1,obj2,obj3) 是把obj2,obj3,... 合并到obj1中; 返回值是 合并后的obj1;

例如:

let copy = Object.assign({},obj2,obj3)

注意:

Object.assign()方法,如果多个对象具有相同的属性,则后者也会覆盖前者的属性值,栗子:

var obj1 = { name:'张三' };
var obj2= { name:'李四' };
var obj3 = Object.assign(obj1,obj2);
console.log(obj3);

合并数组:

​let arr = concat(arr,arr1,arr2);或​let arr = [...arr1,arr2]

源码:

<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><!-- 2.为Echarts准备一个Dom --><div id="main" style="width: 850px; height: 400px"></div></el-card></div>
</template><script>
import { reportsAjax } from "../api";// 1.下载依赖 cnpm install echarts --save 并 导入echarts
// 如果报错请使用这种引入 const echarts = require('echarts')
import * as echarts from "echarts";// 引入lodash 用于合并后台数据
import _ from "lodash";export default {data() {return {// 定义后台需要合并的数据options: {title: {text: "用户来源",},tooltip: {trigger: "axis",axisPointer: {type: "cross",label: {backgroundColor: "#E9EEF3",},},},grid: {left: "3%",right: "4%",bottom: "3%",containLabel: true,},xAxis: [{boundaryGap: false,},],yAxis: [{type: "value",},],},};},async mounted() {// 3.基于准备好的dom,初始化echarts实例,在mounted钩子函数中var myChart = echarts.init(document.getElementById("main"));await reportsAjax().then((res) => {if (res.meta.status !== 200) return this.$message("获取折线图数据失败!");// 4.准备数据项和配置项,借用lodash的合并对象方法const result = _.merge(res.data, this.options);// 5.展示数据myChart.setOption(result);});},
};
</script><style lang="less" scoped></style>

十一、结束 上线部署服务器

手写一个服务器代码将 《vue电商后台管理系统》部署上去 上线、打包_技术前端,忠于热爱-CSDN博客

结束 ! 感谢一路的耐心,再此说几句煽情的话 ·愿小伙伴都能早早下班、少些bug,多多陪陪家人

2022/3/19 13:15

结合 服务器+后端+前端,完成 vue项目 后台管理系统相关推荐

  1. vue项目-后台管理系统

    前言: 一个传唱度极高的前端项目,及其适合像博主这样的小白练手.特以此文章记录下项目基本的创作思路,并且文末附赠项目源码,思维导图及一些后台文件,有兴趣可自取.希望对朋友们有用. 结构: 一,登录页 ...

  2. VUE项目后台管理系统(四)左边菜单动态展示,不仅可以折叠,而且点击不同的菜单,右边展示不同的页面

    目录 左边菜单动态的展示 左边菜单的属性介绍 遍历菜单 左边菜单折叠效果 不同的菜单右面展示不同的页面 左边菜单动态的展示 首先后端的接口要返回菜单的list集合,是json格式,我使用的是pytho ...

  3. 前端开发Vue项目实战:电商后台管理系统(一)前后端搭建

    目录 1. 项目概述 2. 前端项目初始化步骤 3. 后台项目的环境安装配置 下载安装phpStudy,添加数据库 Postman测试工具 验证 1. 项目概述 电商项目基本业务概述: 根据不同的应用 ...

  4. SpringBoot和Vue集成Markdown和多级评论——基于SpringBoot和Vue的后台管理系统项目系列博客(二十三)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  5. SpringBoot实现分页查询——基于SpringBoot和Vue的后台管理系统项目系列博客(七)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  6. SpringBoot实现代码生成器——基于SpringBoot和Vue的后台管理系统项目系列博客(十)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  7. SpringBoot实现1对1、1对多、多对多关联查询——基于SpringBoot和Vue的后台管理系统项目系列博客(十八)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  8. 【Vue基础】前端工程化Vue项目

    一.创建Vue项目步骤 1.新建一个文件夹,起名为vue_project01 2.在该文件夹中打开cmd,输入指令vue ui,打开图形化界面 3.此时跳转到网页,根据以下步骤配置vue项目 1)项目 ...

  9. 【前端】Vue项目:旅游App-博客总结

    博客 链接 [前端]Vue项目:旅游App-(1)搭建项目.重置css.配置router和store(pinia) https://blog.csdn.net/karshey/article/deta ...

最新文章

  1. nginx 官方手册 php,nginx + php 的配置
  2. Scala基础教程(八):模式匹配、正则表达式
  3. 【Linux网络编程】网络协议入门
  4. c语言prime函数怎么用_C语言 要发就发
  5. C++继承时名字的遮蔽
  6. vue入门:(组件)
  7. Eclipse,提交代码,版本比较时,不忽略空格
  8. ubuntu安装opencv的c++开发环境
  9. quartus仿真10:74283的基本功能
  10. 用div代替textarea,实现在文本框中使用回车br和设置字体功能
  11. verilog语法实例学习(3)
  12. UniX和类UNIX系统入侵检测方法
  13. 人工智能智能制作PPT构想---论文与PPT介绍
  14. IT资源专业搜索-www.easysoo.cn
  15. 加拿大工作签证-(1)
  16. 国家开放大学专科计算机应用实训项目,国家开放大学电大专科《微机系统与维护》网络课实训1实训3作业及答案.docx...
  17. 《网络是怎样连接的》-----户根勤,读书笔记
  18. 成功解决百度网盘下载文件时遇到 下载总进度一直处于99.9%,显示一直下载不下来的问题
  19. 指数用计算机怎么打,数学上的乘方指数在电脑上怎么打?问题是什么?
  20. 计算机一级ms必考知识点,计算机一级MsOffice考试知识点整理重点.docx

热门文章

  1. Using setJavaScriptEnabled can introduce XSS vulnerabilities into you application
  2. ONF开源白皮书:SDN解决方案案例——校园SDN
  3. 【计算机组成原理】冯诺伊曼结构和计算机性能指标
  4. “App开发者需要更新此App以在此iOS版本上正常工作 ” 解决方法
  5. oracle求两个字段的日期差
  6. wifidog+authpuppy认证流程详解
  7. 二十六 .ajax登录 认证 验证码(session)
  8. android 4.4 短信拦截,Android 4.4 KitKat升级率已经接近18%
  9. 在图片上添加文字并生成图片
  10. 腾讯云-产品开通和密钥查看