目录
以上是项目的服务器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)
- 本次使用自己的搭建的后端服务器,因为如果将线上接口发至互联网,大量的人修改会导致接口紊乱,所以在自己本地开发练习一下,如果计算机安装不了php,私信我,发送给你线上接口,
- 亲测可用 gitee 网址 包括php包,后端node.js代码,前端源码 以及文档接口说明https://gitee/zhang-kun8888/background-anagement-project-zk.githttps://gitee/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-icon
ref="ruleForm"
label-width="100px"
class="demo-ruleForm"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="ruleForm.username"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item label="用户密码" prop="password">
<el-input
prefix-icon="el-icon-lock"
placeholder="请输入密码"
show-password
v-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 守卫不让进,跳入login
next('/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">
<div
data-v-bcdba65e
class="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-menu
class="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-item
class="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对应的数,值是icon
iconObj: {
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: {
// 对象写法直接return
asideWidth() {
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>
图例:
解释下侧导航
- 用到了ele组件库的经典布局 `el-container` , 用到 `el-menu`, 请求测导航接口,循环,循环后打开一个测导航,全部都打开,需要配置unique-opened 保持一个子导航开启,不同的字体图标,需要用以id做key,icon做值,具体看代码实现
-
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" -
要多看组件库说明
三 列表页源码:
列表页还是比较细碎的,注意细节
<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
// 点击取消 返回值为: cancel
if (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值 :value
selectRoleId: "",
/*
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的默认值是:角色id
if (!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 warning
message: res.meta.msg,
duration: 2000,
});
return;
} else {
//状态码等于200
this.rightsList = res.data;
this.$message({
type: "success", // success error warning
message: 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,需要拼接url
roleId: 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 warning
message: res.meta.msg,
duration: 2000,
});
} else {
//状态码等于200
this.rolesList = res.data;
this.$message({
type: "success", // success error warning
message: 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 warning
message: 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就return
if (!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会刷新页面,接口返回的是最新的数据,可以将返回的数据赋值children
this.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 warning
message: res.meta.msg,
duration: 2000,
});
} else {
//状态码等于200
this.rolesList = res.data;
this.$message({
type: "success", // success error warning
message: 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 warning
message: res.meta.msg,
duration: 2000,
});
// 等于200就将数据赋值树形权限数据渲染
this.treeList = res.data;
// 在对话框开启之前需要获取所有三级权限的id 注意:必须写到ajax中不能写出去否则切换角色无法找到三级id
// 当前角色 就是父级了,是ajax请求的数据,将他传入递归函数,他里面有children,要在ajax中调用递归
// console.log(row);
//!!!调用递归事件,获取三级id
this.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)
// 删除空的children
function del(arr) {
if (typeof arr == 'undefined') return
arr.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)
// 删除原有的children
list.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就return
if (!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;
// 第二种方式 .then
await 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>
// 监听 pageSizeChange
handleSizeChange(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 warning
message: "删除失败",
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", //具体选中的那个值得属性 一般是id
label: "cat_name", //你所看到的是哪一个属性
children: "children", //子父级嵌套的属性是什么
expandTrigger: "hover", // 配置触发选项 hover/click
},
// 选中的父级分类id数组
selectedKeys: [],
// 添加分类 重置表单 点击X号
addCateDialogClosed() {
this.$refs.addCateFormRef.resetFields();
// 选中的父级分类id数组清空
this.selectedKeys = [];
// 清空之后 分类名称/分类父ID,应该也为0
this.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) {
// 父级分类的Id
this.addCateForm.cat_pid = this.selectedKeys[
this.selectedKeys.length - 1
];
// 当前分类的等级
this.addCateForm.cat_level = this.selectedKeys.length;
return 0;
} else {
/* 数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0 */
// 父级分类的Id
this.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: "",
// 分类父级id
cat_pid: 0,
// 分类等级:`0`表示一级分类;`1`表示二级分类;`2`表示三级分类
cat_level: 0,
},
// 添加分类表单的验证规则
addCateFormRules: {
cat_name: [
{ required: true, message: "请输入分类名称", trigger: "blur" },
],
},
//接口请求的分类的数据
parentCateList: [],
// 指定级联选择器的配置对象
cascaderProps: {
value: "cat_id", //具体选中的那个值得属性 一般是id
label: "cat_name", //你所看到的是哪一个属性
children: "children", //子父级嵌套的属性是什么
expandTrigger: "hover", // 配置触发选项 hover/click
},
// 选中的父级分类id数组
selectedKeys: [],
};
},
methods: {
// 监听 pageSizeChange
handleSizeChange(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;
// 第二种方式 .then
await 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,应该也为0
this.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) {
// 父级分类的Id
this.addCateForm.cat_pid = this.selectedKeys[
this.selectedKeys.length - 1
];
// 当前分类的等级
this.addCateForm.cat_level = this.selectedKeys.length;
return 0;
} else {
/* 数组长度不大于0没有选中父级,父级分类的Id和当前分类的等级还是等于0 */
// 父级分类的Id
this.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 warning
message: "删除失败",
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;
},
// 当前选中的三级分类Id
getCateId() {
// 如果数长度是3说明选中了三级分类
if (this.selectedCateKeys.length === 3) {
// return 数组中最后一项
return this.selectedCateKeys[2];
}
// 没有选中三级分类return null
return 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(); //调用函数
},
// tabs
handleClick(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,默认是否显示inputitem.inputVisible = false;
// 给item每一项添加字段inputVisible,文本框的输入值
item.inputValue = "";
在使用tag的时候,在表格获取scope.row.inputVisible,这样点击就不会所有行tag被选中、
//html
<!--
渲染每一个展开行都公用了 inputVisible,所以一所有展开行会联动,不要公用一个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>
封装函数:
- 封装函数中添加字段
// 封装函数: 获取动态参数和静态参数封装函数
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,默认是否显示input
item.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;
// 提交到接口,保存添加的tag
this.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",
},
//默认被激活的tabs
activeName: "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(); //调用函数
},
// tabs
handleClick(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,默认是否显示input
item.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;
// 提交到接口,保存添加的tag
this.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-input
placeholder="输入商品名称自动搜索。。。"
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-button
type="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-alert
title="添加商品信息"
type="info"
center
show-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-cascader
v-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/
深拷贝完成
8.2、处理attr参数
添加文档参数:
添加文档数据:
//data
addForm: {
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(" "),
};
// 将循环后的对象追加addForm
this.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-alert
title="添加商品信息"
type="info"
center
show-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-tabs
tab-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-cascader
v-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-item
v-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-dialog
title="图片预览"
: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(" "),
};
// 将循环后的对象追加addForm
this.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: {
// 获取三级分类id
getCateId() {
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-button
type="primary"
size="mini"
icon="el-icon-edit"
@click="showEditDialog"
></el-button>
<el-button
type="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-dialog
title="修改地址"
: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-cascader
v-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-item
v-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: [],
// 快递JSON
kuaidi: [],
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项目 后台管理系统
发布评论