Vue电商项目—商品管理—分类参数模块-08
1.1 参数管理概述
商品参数用于显示商品的固定的特征信息,可以通过电商平台商品详情页面直观的看到。
1.2 商品分类选择
1. 选择商品分类
页面基本布局
加载商品分类数据
实现商品分类的级联选择效果
// 获取所有的商品分类列表
async getCateList() {
const {data: res} = await this.$http.get('categories');
if(res.meta.status !== 200) return this.$message.error('获取商品分类失败!');
this.cateList = res.data;
// console.log(this.cateList);
}
2. 控制级联菜单分类选择
只允许选择三级分类
通过计算属性的方式获取分类 ID
// 级联选择器双向绑定到的数组
selectCateKeys: []
// 证明选中的不是三级分类
if(this.selectCateKeys.length !== 3) {
this.selectCateKeys = [];
this.manyTableData = [];
this.onlyTableData = [];
return
}
// 当前选中的三级分类的id
cateId() {
if(this.selectCateKeys.length === 3) {
return this.selectCateKeys[2]
}
return null;
}
1.3 实现参数列表
1. 根据选择的商品分类加载对应的参数数据
参数列表布局
根据分类 id 加载参数列表数据
// 根据所选的分类ID,和当前所处的面板,获取对应的参数
const {data: res} = await this.$http.get(`categories/${this.cateId}/attributes`, {params: {sel: this.activeName}});
2. 处理标签数据格式
将字符串形式的数据分隔为数组。
// 将参数列表中的attr_vals 字符串,转换为 数组
res.data.forEach(item => {
// 如果新添加的参数是没有展开项数据的
item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : [];
// 解决:当点击一个+New Tag,其他行的也受影响问题,以及文本框中的内容,共用问题
// 往参数列表数组中,新增两个属性
// 控制文本框的显示与隐藏
item.inputVisible = false;
// 文本框中输入的值
item.inputValue = '';
});
3. 控制添加标签文本框的显示
$nextTick 的执行时机: DOM 更新完毕之后
<!-- 点击 +New Tag,切换到输入框,失去焦点 或 按回车键,恢复到 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>
// 点击按钮,展示文本框
showInput(row) {
// 展示文本框
row.inputVisible = true;
// 让文本框自动获取焦点
// $nextTick(),// 当我们修改了 data 中 tagInputVisible 的值以后,
// 如果要操作文本框,必须等页面重新渲染完毕之后才可以,
// 所以,必须把操作文本框的代码放到 $nextTick 中,当作回调去执行
// $nextTick 的执行时机,是在DOM 更新完毕之后
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
}
4. 实现标签动态添加的文本框控制逻辑
控制标签输入域的显示和隐藏
对输入的内容进行数据绑定
// 将参数列表中的attr_vals 字符串,转换为 数组
res.data.forEach(item => {
// 如果新添加的参数是没有展开项数据的
item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : [];
// 解决:当点击一个+New Tag,其他行的也受影响问题,以及文本框中的内容,共用问题
// 往参数列表数组中,新增两个属性
// 控制文本框的显示与隐藏
item.inputVisible = false;
// 文本框中输入的值
item.inputValue = '';
});
5. 实现标签的添加和删除操作
添加标签和删除标签使用的是同一个接口,参数是一样的。
// 将对 attr_vals 的操作,保存到数据库
async saveAttrVals(row) {
// 发送请求
const {data: res} = await this.$http.put(`categories/${this.cateId}/attributes/${row.attr_id}`, {
attr_name: row.attr_name,
attr_sel: row.attr_sel,
attr_vals: row.attr_vals.join(' ')
});
if(res.meta.status !== 200) {
return this.$message.error('修改参数项失败!');
}
this.$message.success('修改参数信息成功!');
}
1.4 实现动态参数与静态属性添加
动态参数与静态属性表单重用
添加动态参数与静态属性使用的是同一个接口,参数是一样的
// 点击 确定, 添加参数
addParams() {
this.$refs.addCataRef.validate( async valid => {
// 预验证失败
if(!valid) return
// 发送请求
const {data: res} = await this.$http.post(`categories/${this.cateId}/attributes`, {
attr_name: this.addCataForm.attr_name,
attr_sel: this.activeName
});
if(res.meta.status !== 201) {
return this.$message.error('添加参数失败');
}
this.$message.success('添加参数成功');
// 隐藏对话框
this.addCateDialogVisible = false;
// 刷新数据列表
this.getParamsData();
});
}
该模块完整代码
<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-alert title="注意:只允许第三级分类设置相关参数" type="warning" show-icon :closable='false'></el-alert>
<!-- 选择商品分类区域 -->
<el-row class="cat_opt">
<el-col>
<span>选择商品分类:</span>
<!-- 选择商品分类的级联选择框 -->
<el-cascader
expand-trigger="hover"
:options="cateList"
:props="cateProps"
v-model="selectCateKeys"
@change="handleChange">
</el-cascader>
</el-col>
</el-row>
<!-- tab页 切换区域 -->
<!-- v-model 绑定值,选中选项卡的 name -->
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<!-- 添加动态参数的面板 -->
<el-tab-pane label="动态参数" name="many">
<!-- 添加动态参数的按钮 -->
<el-button type="primary" size='mini' :disabled='isBtnDisabled' @click="showAddDialog">添加参数</el-button>
<!-- 动态参数的表格 -->
<el-table :data='manyTableData' border stripe style="width: 100%">
<!-- 展开行 -->
<el-table-column type="expand">
<!-- 作用域插槽,生成Tag -->
<template slot-scope="scope">
<!-- 循环渲染的Tag标签 -->
<el-tag v-for="(item, index) in scope.row.attr_vals" :key="index" closable @close='handleClose(index, scope.row)'> {{item}}</el-tag>
<!-- 点击 +New Tag,切换到输入框,失去焦点 或 按回车键,恢复到 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>
</template>
</el-table-column>
<!-- 索引列 -->
<el-table-column type='index' label="#"></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="showEditDialog(scope.row.attr_id)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteCate(scope.row.attr_id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 添加静态属性的面板 -->
<el-tab-pane label="静态属性" name="only">
<!-- 添加静态属性的按钮 -->
<el-button type="primary" size='mini' :disabled='isBtnDisabled' @click="showAddDialog">添加参数</el-button>
<!-- 静态属性的表格 -->
<el-table :data='onlyTableData' border stripe style="width: 100%">
<!-- 展开行 -->
<el-table-column type="expand">
<!-- 作用域插槽,生成Tag -->
<template slot-scope="scope">
<!-- 循环渲染的Tag标签 -->
<el-tag v-for="(item, index) in scope.row.attr_vals" :key="index" closable @close='handleClose(index, scope.row)'> {{item}}</el-tag>
<!-- 点击 +New Tag,切换到输入框,失去焦点 或 按回车键,恢复到 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>
</template>
</el-table-column>
<!-- 索引列 -->
<el-table-column type='index' label="#"></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="showEditDialog(scope.row.attr_id)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="deleteCate(scope.row.attr_id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 添加参数的对话框 -->
<el-dialog :title=" '添加' + titleText" :visible.sync="addCateDialogVisible" width="50%" @close='addDialogClosed'>
<!-- 添加表单 -->
<el-form :model="addCataForm" :rules="addCataRules" ref="addCataRef" label-width="100px">
<el-form-item :label="titleText" prop="attr_name">
<el-input v-model="addCataForm.attr_name"></el-input>
</el-form-item>
</el-form>
<!-- 底部区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="addCateDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addParams">确 定</el-button>
</span>
</el-dialog>
<!-- 修改参数的对话框 -->
<el-dialog :title="'修改' + titleText " :visible.sync="editCateDialogVisible" width="50%" @close='editDialogClosed'>
<!-- 修改表单 -->
<el-form :model="editCataForm" :rules="editCataRules" ref="editCataRef" label-width="100px">
<el-form-item :label="titleText" prop="attr_name">
<el-input v-model="editCataForm.attr_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="editParams">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// 商品分类列表数据
cateList: [],
// 级联选择器的配置对象
cateProps: {
label: 'cat_name',
value: 'cat_id',
children: 'children'
},
// 级联选择器双向绑定到的数组
selectCateKeys: [],
// tab页切换栏,被激活页签的名称
activeName: 'many',
// 动态数据列表
manyTableData: [],
// 静态数据列表
onlyTableData: [],
// 控制添加参数对话框的显示与隐藏
addCateDialogVisible: false,
// 添加参数的表单数据对象
addCataForm: {
attr_name: ''
},
// 添加参数的表单数据验证规则
addCataRules: {
attr_name: [
{required: true, message: '请输入参数名称', trigger: 'blur'}
]
},
// 控制修改参数对话框的显示与隐藏
editCateDialogVisible: false,
// 修改参数的表单数据对象
editCataForm: {
attr_name: ''
},
// 修改参数的表单数据验证规则
editCataRules: {
attr_name: [
{required: true, message: '请输入参数名称', trigger: 'blur'}
]
},
// 控制删除对话框的显示与隐藏
deleteCateDialogVisible: false
}
},
created() {
// 获取商品分类列表数据
this.getCateList();
},
computed: {
// 如果按钮需要被禁用,则返回true,否则返回false
isBtnDisabled() {
// 通过 selectCateKeys 数组的长度,判断是否选中了 商品分类,来启用 按钮
if(this.selectCateKeys.length !== 3) {
return true
}
return false
},
// 当前选中的三级分类的id
cateId() {
if(this.selectCateKeys.length === 3) {
return this.selectCateKeys[2]
}
return null;
},
// 动态计算标题的文本
titleText() {
if(this.activeName === 'many') {
return '动态参数'
}
return '静态属性'
}
},
methods: {
// 获取所有的商品分类列表
async getCateList() {
const {data: res} = await this.$http.get('categories');
if(res.meta.status !== 200) return this.$message.error('获取商品分类失败!');
this.cateList = res.data;
// console.log(this.cateList);
},
// 级联选择器选中项的改变事件
handleChange() {
// 调用获取参数列表数据函数
this.getParamsData();
},
// Tab 页切换栏的点击事件
handleTabClick() {
// console.log(this.activeName);
// 调用获取参数列表数据函数
this.getParamsData();
},
// 获取参数的列表数据
async getParamsData() {
// 证明选中的不是三级分类
if(this.selectCateKeys.length !== 3) {
this.selectCateKeys = [];
this.manyTableData = [];
this.onlyTableData = [];
return
}
// 证明选中的是三级分类
// console.log(this.selectCateKeys);
// 根据所选的分类ID,和当前所处的面板,获取对应的参数
const {data: res} = await this.$http.get(`categories/${this.cateId}/attributes`, {params: {sel: this.activeName}});
if(res.meta.status !== 200 ) {
return this.$message.error('获取参数列表失败!');
}
// 将参数列表中的attr_vals 字符串,转换为 数组
res.data.forEach(item => {
// 如果新添加的参数是没有展开项数据的
item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : [];
// 解决:当点击一个+New Tag,其他行的也受影响问题,以及文本框中的内容,共用问题
// 往参数列表数组中,新增两个属性
// 控制文本框的显示与隐藏
item.inputVisible = false;
// 文本框中输入的值
item.inputValue = '';
});
console.log(res.data);
// 判断获取的数据是动态参数,还是静态属性
if(this.activeName === 'many') {
this.manyTableData = res.data;
}else {
this.onlyTableData = res.data;
}
},
// 点击 添加参数 按钮,显示添加参数对话框
showAddDialog() {
this.addCateDialogVisible = true;
},
// 添加参数对话框的关闭事件
addDialogClosed() {
this.$refs.addCataRef.resetFields();
},
// 点击 确定, 添加参数
addParams() {
this.$refs.addCataRef.validate( async valid => {
// 预验证失败
if(!valid) return
// 发送请求
const {data: res} = await this.$http.post(`categories/${this.cateId}/attributes`, {
attr_name: this.addCataForm.attr_name,
attr_sel: this.activeName
});
if(res.meta.status !== 201) {
return this.$message.error('添加参数失败');
}
this.$message.success('添加参数成功');
// 隐藏对话框
this.addCateDialogVisible = false;
// 刷新数据列表
this.getParamsData();
});
},
// 点击 编辑 按钮,显示修改参数对话框
async showEditDialog(attrId) {
// 根据获取的ID,发送请求
const {data: res} = await this.$http.get(`categories/${this.cateId}/attributes/${attrId}`, {
attr_sel: this.activeName
});
if(res.meta.status !== 200) {
return this.$message.error('获取参数信息失败!');
}
this.editCataForm = res.data;
this.editCateDialogVisible = true;
},
// 重置表单数据
editDialogClosed() {
this.$refs.editCataRef.resetFields();
},
// 点击 修改按钮,修改参数信息
editParams() {
this.$refs.editCataRef.validate(async valid => {
if(!valid) return
const {data: res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editCataForm.attr_id}`, {
attr_name: this.editCataForm.attr_name,
attr_sel: this.activeName
});
if(res.meta.status !== 200) {
return this.$message.error('修改参数信息失败!');
}
this.$message.success('修改参数信息成功!');
this.getParamsData();
this.editCateDialogVisible = false;
});
},
// 点击 删除按钮,删除对应的参数信息
async deleteCate(attrId) {
this.deleteCateDialogVisible = true;
const confirm = await this.$confirm('此操作将永久删除这条数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(err => err);
console.log(confirm); // cancel 取消 confirm 确定
if(confirm !== 'confirm') {
return this.$message.info('已取消删除');
}
// 发送请求
const{data: res} = await this.$http.delete(`categories/${this.cateId}/attributes/${attrId}`);
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
}
// 如果没有return,则说明输入的内容
row.attr_vals.push(row.inputValue.trim());
// 清空文本框的内容,并切换回 按钮
row.inputValue = '';
row.inputVisible = false;
this.saveAttrVals(row);
},
// 点击按钮,展示文本框
showInput(row) {
// 展示文本框
row.inputVisible = true;
// 让文本框自动获取焦点
// $nextTick(),当页面上元素被重新渲染之后,才会执行回调函数中的代码
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
// 删除对应的参数可选项
handleClose(index, row) {
row.attr_vals.splice(index, 1);
this.saveAttrVals(row);
},
// 将对 attr_vals 的操作,保存到数据库
async saveAttrVals(row) {
// 发送请求
const {data: res} = await this.$http.put(`categories/${this.cateId}/attributes/${row.attr_id}`, {
attr_name: row.attr_name,
attr_sel: row.attr_sel,
attr_vals: row.attr_vals.join(' ')
});
if(res.meta.status !== 200) {
return this.$message.error('修改参数项失败!');
}
this.$message.success('修改参数信息成功!');
}
}
}
</script>
<style lang="less" scoped>
.cat_opt {
margin: 15px 0;
}
// 展开页下的小Tag
.el-tag {
margin-right: 15px;
}
.input-new-tag {
width: 120px;
}
.el-button {
margin-bottom: 10px;
}
</style>