实现功能
- 查询
- 主从店切换
- 多选
- 全选/全不选
gitee地址:gitee.com/li_xing150/…
可以在检索框进行数据的检索
通过全选按钮进行全选/全不选
可实现多选,可实现主从店切换,切换时数据全部刷新
代码功能逻辑
调用代码如下
调用代码
AboutView.vue
<template>
<div class="about">
<button @click="togglePicker">测试</button>
<div v-if="data16.showPicker">
<selectTreeDemo
ref="selectTree"
:treesData="data16.treeData"
:selectedValues="data16.selectedValues"
:show="data16.showPicker"
:isMainStyle="data16.isMain"
@selectNodes="onSelectNodes"
@changePikerStyle="changePikerStyle"
>
</selectTreeDemo>
</div>
</div>
</template>
<script>
import selectTreeDemo from '../components/selectTreeNew/index.vue'
import { data } from './data.js'
export default {
name: 'Screen-detail',
components: { selectTreeDemo },
data () {
return {
dataObj: data.second,
data16:{
treeData:data.arr,//树形结构
selectedValues:[],//选中的值
showPicker:false,//是否展开弹框
isMain:true,//是否为主数据
},
}
},
methods: {
// 打开和关闭弹窗
togglePicker() {
this.data16.showPicker = !this.data16.showPicker
},
// 确认,返回选中的数据
onSelectNodes (nodes, isMain) {
this.data16.selectedValues = nodes.map(node => node.label)
this.data16.isMain = isMain
},
// 取消,重置为默认的数据
changePikerStyle(){
this.data16.isMain = this.dataObj.isMain
this.data16.selectedValues = this.data16.selectedValues.length > 0 ? this.data16.selectedValues : this.dataObj.selected
this.togglePicker()
},
}
}
组件index源码
index.vue
<template>
<div>
<div class="van-picker__toolbar" style="display: flex; justify-content: space-between">
<button type="button" class="van-picker__cancel" style="padding: 0;font-size: 14px;" @click="cancel">取消</button>
<div class="van-ellipsis van-picker__title">门店</div>
<button type="button" class="van-picker__confirm" style="padding: 0;font-size: 14px; font-weight: 600;" @click="onConfirm">确认</button>
</div>
<div style="display: flex;">
<input
v-model="value"
shape="round"
:style="{ width: '100%', height: 'fit-content', paddingTop: '0', paddingLeft: '0', paddingRight: '0' }"
placeholder="请输入搜索关键词"
input-align="center"
@change="onChange"
@clear="onChange"
>
<!-- <van-search v-model="value" shape="round" style="width: 100%;height: fit-content;padding-top: 0;padding-left: 0;padding-right: 0;" placeholder="请输入搜索关键词" input-align="center" @change="onChange" @clear="onChange" /> -->
<!-- <div style="width: 20%;" @click="onChange">🔍</div>
<div style="width: 20%;" @click="reset">重置</div> -->
</div>
<div class="changeBox">
<div class="storeBox">
<div class="storeStype" :class="{ active: isMain }" @click="changeisMain(true)">主店</div>
<div class="storeStype" :class="{ active: !isMain }" @click="changeisMain(false)">从属店</div>
</div>
<div>
全选
<input class="checkbox" style="top: 3px; margin-left: 8px;" type="checkbox" :checked="checkedAll"
@change="handleCheckedAll" />
</div>
</div>
<div style="overflow-y: auto;height: 200px;margin-top: 5px;">
<TreeItem1 ref="treeItem" v-for="child in treeData" :node="child" :isMain="isMain" @node-checked="onNodeChecked" :key="child.id"></TreeItem1>
</div>
</div>
</template>
<script>
import TreeItem1 from './treeItem.vue'
export default {
components: {
TreeItem1
},
props: {
treesData: { type: Array, required: true },
isMainStyle: { type: Boolean, required: false, default: true },
selectedValues: { type: Array, required: false },
show: { type: Boolean, required: false }
},
data() {
return {
treeData: [],
tempTreeData: [],
value: '',
checkedAll: false,
isMain: true,
selectedNodes: []
}
},
watch: {
show(val) {
if (val) {
window.console.log('show', val, this.treesData, this.selectedValues)
this.reset()
}
},
treesData: {
deep: true, // 添加深度监听
handler(newVal) {
window.console.log('treesData')
this.reset()
}
},
selectedValues: {
deep: true, // 添加深度监听
handler(newVal) {
window.console.log('selectedValues', this.selectedValues)
this.reset()
}
}
},
mounted() {
this.reset()
},
methods: {
// 数据处理和转换
transformData(data) {
// 对数据进行清洗,满足使用标准
return data.map(shop => {
const transformedShop = {
id: shop.shopCode, // 使用 shopCode 作为 id
label: shop.shopName, // 将 shopName 作为 label
label1: shop.shopStatus, // 将 shopName 作为 label
checked: false,
collapsed: true,
children: []
}
if (shop.rooms && shop.rooms.length > 0) {
transformedShop.children = shop.rooms.map(room => {
return {
id: room.roomCode, // 使用 roomCode 作为 id
label: room.roomName, // 将 roomName 作为 label
label1: room.roomStatus, // 将 shopName 作为 label
parentNode: shop.shopCode, // 将 shopCode 作为 parentNode
checked: false,
collapsed: true,
children: []
}
})
}
return transformedShop
})
},
formTreeData (tree, isMain) {
// 根据是否为主店或从店生成树形结构
if (isMain) {
return tree
} else {
return this.getCoTreeData(tree)
}
},
getCoTreeData(treeData) {
// 获取从店的数据结构
let tree = []
treeData.forEach(node => {
if (node.children.length) {
tree.push({ ...node, collapsed: false })
}
})
return tree
},
filterTreeData(treeData, value) {
// 按照value过滤树形结构的数据
return treeData.map(node => {
if (node.label.includes(value)) {
return {
...node,
children: node.children ? this.filterTreeData(node.children, value) : []
}
} else if (node.children) {
const filteredChildren = this.filterTreeData(node.children, value)
if (filteredChildren.length > 0) {
return {
...node,
children: filteredChildren
}
}
}
return null
}).filter(Boolean)
},
// 数据选择和交互
setSelectedNodes (arr, tree, isMain) {
// 设置选中节点
let arrNode = []
if (isMain) {
tree.forEach(node => {
if (arr.includes(node.label) || arr.includes(node.id)) {
arrNode.push(node)
}
})
} else {
tree.forEach(node => {
node.children.forEach(child => {
if (arr.includes(child.label) || arr.includes(child.id)) {
arrNode.push(child)
}
})
})
}
return arrNode
},
setTreeDataChecked (tree, arr, isMain) {
// 设置树形结构中节点的选中状态
// tree的节点node的label与arr的arrnode的label匹配,设置node的checked为true
if (isMain) {
tree.forEach(node => {
if (arr.some(arrnode => arrnode.label === node.label)) {
node.checked = true
} else {
node.checked = false
}
})
} else {
tree.forEach(treeson => {
treeson.children.forEach(childnode => {
if (arr.some(arrnode => arrnode.label === childnode.label)) {
childnode.checked = true
} else {
childnode.checked = false
}
})
})
}
},
isAllChecked (tree, selectedNodes, isMain) {
// 判断是否全选
let allTree = isMain ? tree : tree.map(node => node.children).flat()
const isAllChecked = allTree.every(node => {
const correspondingNode = selectedNodes.find(selectedNode => selectedNode.label === node.label)
console.log('isAllChecked', correspondingNode, node.checked)
return correspondingNode && node.checked
})
return isAllChecked
},
nodeChecked(node) {
// 若数据未选中,则加入选中列表
// 若数据选中,则移出选中列表
if (node.checked) {
this.selectedNodes.push(node)
} else {
this.selectedNodes.splice(this.selectedNodes.findIndex(selectedNode => selectedNode.label === node.label), 1)
}
},
// 用户交互
reset() {
// 1.依据输入数据进行重置
window.console.log('selectedValues', this.selectedValues, this.show)
this.value = ''
this.isMain = this.isMainStyle
this.tempTreeData = this.transformData(this.treesData)
this.treeData = this.formTreeData(this.tempTreeData, this.isMain)
if (this.selectedValues && this.selectedValues.length > 0) {
this.selectedNodes = this.setSelectedNodes(this.selectedValues, this.treeData, this.isMain)
} else {
this.selectedNodes = []
}
// 设置treeData的选中状态
this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
window.console.log('selectedValues', this.treeData[0])
},
changeisMain (isMain) {
// 2.主从店切换
if (this.isMain === isMain) return
this.isMain = isMain
console.log('isMain', isMain)
this.checkedAll = false
// 重置treeData
this.treeData = this.formTreeData(this.tempTreeData, isMain)
// 置空selectedNodes
this.selectedNodes = []
this.setTreeDataChecked(this.treeData, this.selectedNodes, isMain)
// 筛选数据
this.treeData = this.filterTreeData(this.treeData, this.value)
},
onChange() {
// 3.主从店不切换时的筛选
// 从tempTreeData中筛选数据
this.treeData = this.formTreeData(this.tempTreeData, this.isMain)
this.treeData = this.filterTreeData(this.treeData, this.value)
this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
},
handleCheckedAll() {
// 4.全选/全不选
this.checkedAll = !this.checkedAll
// 全选/全不选-设置selecteddNodes
if (this.isMain) {
this.treeData.forEach(treenode => {
treenode.checked = this.checkedAll
this.nodeChecked(treenode)
})
} else {
this.treeData.forEach(treenode => {
treenode.children.forEach(child => {
child.checked = this.checkedAll
this.nodeChecked(child)
})
})
}
// 节点去重
this.selectedNodes = this.selectedNodes.filter((node, index, self) =>
index === self.findIndex((t) => (
t.label === node.label
))
)
this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
},
onNodeChecked (node) {
// 进行选中数据处理
this.nodeChecked(node)
this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
// console.log('onNodeChecked', this.selectedNodes)
},
onConfirm() {
// 返回查询的数据
this.$emit('selectNodes', this.selectedNodes, this.isMain)
this.value = ''
},
cancel() {
// 重置为默认数据
this.$emit('changePikerStyle')
this.value = ''
console.log('cancel')
},
}
}
</script>
<style scoped>
.changeBox {
display: flex;
justify-content: space-between;
padding: 0 0 8px;
border-bottom: 1px solid #F2F4F7;
}
.storeBox{
height: 24px;
background: #EBEBEB;
border-radius: 13px;
display: flex;
}
.treeBox {
margin-top: 8px;
overflow: scroll;
height: 200px;
}
.treeBox::-webkit-scrollbar {
display: none;
/* 隐藏滚动条 */
}
.search-wrapper {
position: relative;
}
.clear-btn {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
.storeStype {
text-align: center;
width: 80px;
height: 24px;
line-height: 24px;
color: #999999;
border-radius: 13px;
}
.active {
background: #3C60C3;
color: #fff;
}
.van-search .van-cell {
padding: 0 !important;
}
.van-search__content--round{
background-color: #EDF2FC;
}
</style>
treeItem.vue
<template>
<div>
<!-- 父节点 -->
<div class="treeItem">
<div @click="toggleCollapse" style="display: flex; align-items: center; position: relative;">
<span style="padding: 3px; border-radius: 10%;display: flex;justify-content: center;align-items: center;">
<img src="./picture/saling.png" style="width: 48px;" v-if="node.label1 === '营业中'">
<img src="./picture/saling1.png" style="width: 48px;" v-else-if="node.label1 === '待开业'">
<img src="./picture/saling2.png" style="width: 48px;" v-else-if="node.label1 === '已撤销'">
</span>
{{ node.label }}
<img src="./picture/angle.png" :class="{ 'downIcon--down': !collapsed }" class="downIcon" v-if="node.children.length">
</div>
<div>
<input v-show="isMain" class="checkbox" type="checkbox" :checked="node.checked" @change="handleCheck('',node)" />
</div>
</div>
<!-- 子节点区域 -->
<transition name="collapse">
<ul v-show="!collapsed">
<li v-for="child in node.children" :key="child.id">
<!-- 递归渲染子节点 -->
<div class="treeItem">
<div style="display: flex;align-items: center;">
<span style="padding: 3px;padding-left: 15px; border-radius: 10%;display: flex;justify-content: center;align-items: center;">
<img src="./picture/saling.png" style="width: 48px;" v-if="child.label1 === '营业中'">
<img src="./picture/saling1.png" style="width: 48px;" v-else-if="child.label1 === '待开业'">
<img src="./picture/saling2.png" style="width: 48px;" v-else-if="child.label1 === '已撤销'">
</span>
{{ child.label }}
</div>
<input v-if="!isMain" class="checkbox" type="checkbox" :checked="child.checked"
@change="handleCheck(child, node)" />
</div>
</li>
</ul>
</transition>
</div>
</template>
<script>
export default {
name: 'TreeItem',
props: {
node: { type: Object, required: true },
isMain: { type: Boolean, default: true }
},
data() {
return {
collapsed: true
}
},
watch: {
'node.collapsed'(newVal) {
// console.log('collapsed',this.node.id,newVal)
this.collapsed = newVal
},
'isMain': {
handler(newVal) {
}
}
},
mounted() {
this.collapsed = this.node.collapsed
},
methods: {
toggleCollapse() {
this.collapsed = !this.collapsed
},
handleCheck(child, node) {
if (!child) {
node.checked = !node.checked
let nodeT = { ...node, checked: node.checked }
this.$emit('node-checked', nodeT)
} else {
child.checked = !child.checked
child.parentNode = node.id
this.$emit('node-checked', child)
}
}
}
}
</script>
<style>
.labelStyle {
font-weight: 400;
font-family: PingFangSC, PingFang SC;
font-size: 12px;
color: #3C60C3;
line-height: 17px;
height: 18px;
text-align: left;
font-style: normal;
background: #F0F4FF;
border-radius: 2px;
border: 1px solid #3C60C3;
padding: 0px 2px;
margin-right: 5px;
}
.labelStyleDyy {
color: #3CC3C3;
background: #F0FFFF;
border: 1px solid #3CC3C3;
}
.labelStyleYcx {
color: #ABAEB3;
background: #fff;
border: 1px solid #ABAEB3;
}
li {
list-style: none;
line-height: 25px;
}
.treeItem {
display: flex;
justify-content: space-between;
margin: 8px 0px 16px;
}
.downIcon {
margin-left: 6px;
width: 10px;
height: 10px;
}
.downIcon--down {
transform: rotate(90deg);
}
.checkbox {
position: relative;
/* 隐藏原生复选框 */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/* 设置自定义复选框样式 */
width: 16px;
height: 16px;
border: 1px solid #ccc;
border-radius: 50%;
cursor: pointer;
}
/* 自定义复选框选中状态样式 */
.checkbox:checked {
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.checkbox:checked::after {
content: '\e728';
font-family: vant-icon;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}</style>