1 Star 0 Fork 0

Wayne831/vue_shop

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Params.vue 22.22 KB
一键复制 编辑 原始数据 按行查看 历史
Wayne831 提交于 2021-10-08 18:09 +08:00 . 完成了后期优化
<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"
class="el-alert"
></el-alert>
<!-- 选择商品分类区 -->
<el-row>
<el-col>
<span>选择商品分类: </span>
<!-- 选择商品分类的级联选择器 -->
<el-cascader
v-model="selectedCategory"
:options="categoryList"
:props="cascaderProps"
@change="getCategoryParams"
clearable
></el-cascader>
</el-col>
</el-row>
<!-- tabs页签区 用来实现标签页 -->
<!-- v-model是选中标签页的name的双向数据绑定 label是展示的名字 click事件只要切换标签页就触发 -->
<!-- 将name改造成only和many用于绑定 方便发送请求获取参数列表 参考API接口文档1.7.1 -->
<el-tabs v-model="activeName" @tab-click="getCategoryParams">
<!-- 添加动态参数的标签页 -->
<!-- 添加参数和属性的按钮是否禁用取决于级联选择器的选中项是否有效 即selectedCategory数组长度 用计算属性实现 -->
<el-tab-pane label="动态参数" name="many">
<el-button type="primary" size="medium" :disabled="isBtnDisabled" @click="addDialogVisible=true">添加参数</el-button>
<!-- 动态参数表格 -->
<el-table :data="manyParams" border stripe>
<!-- 展开行 -->
<el-table-column type="expand">
<template slot-scope="scope">
<!-- attr_vals是经过处理的数组 利用vals循环渲染tag标签 -->
<!-- 关闭标签事件传索引和row 可以根据索引index在数组row.attr_vals中删除对应元素 -->
<el-tag v-for="(item,index) in scope.row.attr_vals" :key="index" closable @close="handleClose(scope.row,index)">
{{item}}
</el-tag>
<!-- el-input和el-button组成动态编辑标签 -->
<!-- 若控制输入框/按钮的v-if和数据绑定的v-model都定义在data里的某一数据 就会造成所有的动态编辑标签都绑定到他们身上 -->
<!-- 这样会造成所有动态编辑标签的"联动"效果 所以要在获取参数split拆分字符串的forEach循环里后为每一项绑定这俩参数值 -->
<!-- 按下回车或失去焦点的回调也需要传row 因为控制展示输入框或按钮的布尔值和输入框内容在row身上 -->
<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>
<!-- 也需要传值scope.row 因为inputVisible在row身上 -->
<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 label="#" type="index"></el-table-column>
<!--prop是根据manyParams里的属性名设置的 就是服务端返回数据 -->
<el-table-column label="参数名称" prop="attr_name"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<!-- 编辑的时候传row 上面有编辑的网络请求所需的所有参数 我没有重新查询而是用row完成 -->
<el-button type="primary" icon="el-icon-edit" size="small" @click="showEditDialog(scope.row)">编辑</el-button>
<!-- 这次删除的时候不能像前几个组件一样单纯传row.id了 因为需要分类id和参数id 参考API接口文档1.7.3 -->
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteParams(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- 添加静态属性的标签页 -->
<el-tab-pane label="静态属性" name="only">
<el-button type="primary" size="medium" :disabled="isBtnDisabled" @click="addDialogVisible=true">添加属性</el-button>
<!-- 静态属性表格 -->
<el-table :data="onlyParams" border stripe>
<!-- 展开行 -->
<el-table-column type="expand">
<!-- 静态属性表格的展开行和动态参数的逻辑完全一致 直接复制代码 -->
<template slot-scope="scope">
<!-- attr_vals是经过处理的数组 利用vals循环渲染tag标签 -->
<!-- 关闭标签事件传索引和row 可以根据索引index在数组row.attr_vals中删除对应元素 -->
<el-tag v-for="(item,index) in scope.row.attr_vals" :key="index" closable @close="handleClose(scope.row,index)">
{{item}}
</el-tag>
<!-- el-input和el-button组成动态编辑标签 -->
<!-- 若控制输入框/按钮的v-if和数据绑定的v-model都定义在data里的某一数据 就会造成所有的动态编辑标签都绑定到他们身上 -->
<!-- 这样会造成所有动态编辑标签的"联动"效果 所以要在获取参数split拆分字符串的forEach循环里后为每一项绑定这俩参数值 -->
<!-- 按下回车或失去焦点的回调也需要传row 因为控制展示输入框或按钮的布尔值和输入框内容在row身上 -->
<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>
<!-- 也需要传值scope.row 因为inputVisible在row身上 -->
<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 label="#" type="index"></el-table-column>
<!--prop是根据onlyParams里的属性名设置的 就是服务端返回数据 -->
<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="small" @click="showEditDialog(scope.row)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="small" @click="deleteParams(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 添加动态参数和静态属性的公用对话框 因为基本一致 -->
<!-- 因为是共同 所以title不能写死 定义成一个计算属性来处理 -->
<el-dialog :title="'添加'+dialogTitle" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
<!-- 验证表单 -->
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="80px">
<!-- prop也不能乱起名字 是按照发送添加请求时所需要的参数名设置 参考API接口文档1.7.2 -->
<el-form-item :label="dialogTitle" prop="attr_name">
<el-input v-model="addForm.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="'编辑'+dialogTitle" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
<!-- 验证表单 验证规则和添加的一致所以直接用 -->
<el-form :model="editForm" :rules="addFormRules" ref="editFormRef" label-width="80px">
<!-- prop也不能乱起名字 是按照发送添加请求时所需要的参数名设置 参考API接口文档1.7.2 -->
<el-form-item :label="dialogTitle" prop="attr_name">
<el-input v-model="editForm.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>
export default {
data() {
return {
// 获取到的商品分类列表
categoryList: [],
// 级联选择器的配置项
cascaderProps: {
// 次级菜单的展开方式
expandTrigger: 'hover',
// 指定选中的值的实际值 即分类的id
value: 'cat_id',
// 指定选中的值的展示属性 即分类的名字
label: 'cat_name',
// 指定父子嵌套用的哪个属性
children: 'children',
// 不设置checkStrictly的话 默认只能选中最后一级 但不一定是三级有可能是没有children的一级二级
// 所以还是要在change事件里判断是否是三级
checkStrictly: true
},
// 级联选择器的双向数据绑定到的数组
selectedCategory: [],
// 被激活的页签名称 默认是第一个
activeName: 'many',
// 如果获取到的是动态参数就放在manyParams里 静态属性就放在onluyParams里
manyParams: [],
onlyParams: [],
// 控制添加动/静公用对话框的显示与隐藏
addDialogVisible: false,
// 公用添加对话框的表单数据绑定
addForm: {
attr_name: ''
},
// 公用添加对话框的验证规则
addFormRules: {
attr_name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
]
},
// 控制编辑动/静公用对话框的显示与隐藏
editDialogVisible: false,
// 公用编辑对话框的表单数据绑定
editForm: {
// 参考API接口文档1.7.5进行起名
id: '', // 分类id
attrId: '', // 属性id
attr_name: ''// 属性名
}
}
},
methods: {
// 这个页面需要获取的数据和Categories需要获取的数据完全一致 API也是1.6.1 可以照搬函数
// 只不过Categories组件只用了id和name pid 这个组件要用参数
async getCategoryList() {
// 不需要分页所以不用传pagenum和pagesize type省略默认获取到三级即全部等级分类
const { data: res } = await this.$http.get('categories')
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
// 因为请求时传参形式不同所以获得到数据形式不同 这里不用.result了 直接在data里
this.categoryList = res.data
},
// 级联选择器的选中项发生改变时触发回调
// categoryChange() {
// this.getCategoryParams()
// },
// tab标签页点击切换时的触发函数
// handleTabClick() {
// this.getCategoryParams()
// },
// 而categoryChange和handleTabClick都是调用getCategoryParams 因此我直接在27行/35行绑定时绑定了getCategoryParams
// 获取动态参数/静态属性并展示的网络请求应该在级联选择器选中项改变和标签页切换时都触发
// 如果只设置给级联选择器 则选中后切换标签页 数据不会改变 反之亦然
// 所以将这个操作单独封装 并在级联选择器选中项改变和标签页切换的回调中调用
async getCategoryParams() {
// 只有selectedCategory长度为3 才证明是三级分类
if (this.selectedCategory.length !== 3) {
// 不是三级分类就把数组清空 由于双向绑定 级联选择器也无法选中
this.selectedCategory = []
// 在后面的开发中补充: 还需要将参数列表情况 否则如果先选中了三级分类 再选中一级或二级
// 下方表格区根据参数列表展示的内容还存在
this.manyParams = []
this.onlyParams = []
return
}
// 当选中了三级分类 参考API接口文档1.7.1
// id通过计算属性拿到 sel通过el-tabs双向绑定的activeName拿到
// 注意必须用{params:{sel:this.activeName}}的形式 不能去掉外层params 更不能只用this.xxx否则报错
const { data: res } = await this.$http.get(`categories/${this.categoryId}/attributes`, { params: { sel: this.activeName } })
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
// 取出对应的vals 渲染到展开行里 vals是用空格分隔的字符串所以用split(' ')
res.data.forEach(item => {
// 三元表达式 当没有attr_vals时(比如自己添加的参数)就返回空数组 这样el-tag不会被渲染
item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
// 添加布尔值和输入框双向绑定的数据 每一项都要绑定在唯一的上面否则会联动
// 注意要用$set实现响应式
this.$set(item, 'inputVisible', false)
this.$set(item, 'inputValue', '')
})
// 获取成功后还要手动判断是动态参数还是静态属性 因为服务端返回的数据并不包含这一项
if (this.activeName === 'many') {
this.manyParams = res.data
} else {
this.onlyParams = res.data
}
},
// 公用添加对话框关闭后的重置操作
addDialogClosed() {
this.$refs.addFormRef.resetFields()
},
// 公用对话框的确定按钮点击回调
addParams() {
this.$refs.addFormRef.validate(async valid => {
if (!valid) {
return this.$message.error('校验未通过!')
}
// 预校验通过则发起网络请求添加属性 参考API接口文档1.7.2 需要包含请求体{}
const { data: res } = await this.$http.post(`categories/${this.categoryId}/attributes`, {
attr_name: this.addForm.attr_name,
attr_sel: this.activeName
})
if (res.meta.status !== 201) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
this.addDialogVisible = false
this.getCategoryParams()
})
},
// 点击编辑按钮的事件回调 展示公用编辑对话框
showEditDialog(params) {
this.editForm.id = params.cat_id
this.editForm.attrId = params.attr_id
this.editForm.attr_name = params.attr_name
this.editDialogVisible = true
},
// 公用编辑对话框关闭后的重置操作
editDialogClosed() {
this.$refs.editFormRef.resetFields()
},
editParams() {
this.$refs.editFormRef.validate(async valid => {
if (!valid) {
return this.$message.error('校验未通过!')
}
// 预校验通过则发起网络请求编辑属性 参考API接口文档1.7.5 需要包含请求体{}
const { data: res } = await this.$http.put(`categories/${this.editForm.id}/attributes/${this.editForm.attrId}`, {
attr_name: this.editForm.attr_name,
attr_sel: this.activeName
})
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
this.editDialogVisible = false
this.getCategoryParams()
})
},
// 点击删除按钮的回调
async deleteParams(params) {
console.log(params)
// 先弹框 让用户进行二次确认 $confirm返回值也是一个Promise
// 经过async await修饰后 如果用户确认删除 则返回值为字符串'confirm'
// 经过async await修饰后 如果用户取消删除 则返回值为字符串'cancel'
const confirmResult = await this.$confirm('此操作将永久删除该数据, 是否继续?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(err => err)// 用catch来捕获前面的所有错误 简略了() {} return
if (confirmResult !== 'confirm') {
return this.$message.info('已取消删除')
}
// 如果确认删除 参考API接口文档1.7.3
const { data: res } = await this.$http.delete(`categories/${params.cat_id}/attributes/${params.attr_id}`)
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
// 删除成功之后提示成功并刷新参数列表
this.getCategoryParams()
this.$message.success(res.meta.msg)
},
// 动态编辑标签区的按钮 点击后展示输入框和获取焦点
showInput(row) {
row.inputVisible = true
// $nextTick生命周期钩子 当页面上元素被重新渲染之后才会执行回调函数中的代码
this.$nextTick(_ => {
// input是原生DOM对象 通过其focus对象获取焦点
// 第一个ref拿到el-input 第二个ref从el-input身上拿到原生DOM节点
this.$refs.saveTagInput.$refs.input.focus()
})
},
// 动态编辑标签的回调 按下enter键和失去焦点都绑定了这个函数 代表都可以确认输入完成
handleInputConfirm(row) {
// 先去掉两端空格
row.inputValue = row.inputValue.trim()
// 优化: 判断输入是否合法 不合法就重置
if (row.inputValue.length === 0) {
row.inputValue = ''
row.inputVisible = false
return
}
// 若用户输入了有效的内容 就把值渲染成tag 即push到数组中
// 而tag是v-for遍历数组得到的 所以数组经过push发送变化 tag也会响应改变
row.attr_vals.push(row.inputValue)
// 再把输入框清空并隐藏输入框
row.inputValue = ''
row.inputVisible = false
// push完只是本地的attr_vals改变了 服务端数据并没有更新 因此刷新后就不见了 因此需要调用封装函数发起请求
this.saveAttrVals(row)
},
// 关闭tag标签的回调 代表删除一项vals
handleClose(row, index) {
row.attr_vals.splice(index, 1)
// 删除完还要发起请求同步更新服务端数据 调用封装函数
this.saveAttrVals(row)
},
// 在更改tags(添加或关闭)后 除了操作本地vals数组 还需要向服务端发送请求更新 参考API接口文档1.7.5
// 因为handleInputConfirm和handleClose都需要发送 所以封装成函数并调用 只需要收到row即可
async saveAttrVals(row) {
const { data: res } = await this.$http.put(`categories/${row.cat_id}/attributes/${row.attr_id}`, {
attr_name: row.attr_name,
attr_sel: row.attr_sel,
// 因为vals是字符串形式 拆分成了数组 所以发送请求时要重新拼成字符串
attr_vals: row.attr_vals.join(' ')
})
if (res.meta.status !== 200) {
return this.$message.error(res.meta.msg)
}
this.$message.success(res.meta.msg)
}
},
computed: {
// 按钮是否禁用 为true就禁用 判断条件是级联选择器数组长度(代表是否选中三级)
isBtnDisabled() {
if (this.selectedCategory.length !== 3) {
return true
}
return false
},
// 级联选择器选中的分类id 只有三级分类才有效 否则返回null 需要这个id获取对应的参数和属性
categoryId() {
if (this.selectedCategory.length === 3) {
return this.selectedCategory[2]
}
return null
},
// 设置公用对话框的标题
dialogTitle() {
if (this.activeName === 'many') {
return '动态参数'
}
return '静态属性'
}
},
created() {
this.getCategoryList()
}
}
</script>
<style lang="less" scoped>
.el-alert {
margin-bottom: 15px;
}
.el-tag {
margin: 0 10px;
}
.input-new-tag {
width: 120px;
margin-left: 10px;
vertical-align: bottom;
}
</style>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/wayne831/vue_shop.git
git@gitee.com:wayne831/vue_shop.git
wayne831
vue_shop
vue_shop
master

搜索帮助