组件的书写逻辑
组件调用demo,selectTreeDemo 为功能组件
<template>
<div >
<h1>Test View</h1>
<!-- <selectTree style="overflow: hidden;"></selectTree> -->
<button @click="changePikerStyle">click me</button>
<button @click="selectAll">数组设置</button>
<van-popup v-model="showPicker" position="bottom" style="">
<selectTreeDemo ref="selectTree" :treesData="data16.treeData" :isMainStyle="isMain" @selectNodes="onSelectNodes" @changePikerStyle="changePikerStyle" :tempSelect="data16.tempSelect" :show="data16.showPicker">
</selectTreeDemo>
</van-popup>
<br>
<br>
<freeze></freeze>
</div>
</template>
<script>
// import selectTree from '../components/selectTree/index.vue'
import selectTreeDemo from '../components/selectTreeNew/index.vue'
import freeze from '../components/freeze/index.vue'
import { data } from './data.js'
export default {
components: {
// selectTree,
selectTreeDemo,
freeze
},
data () {
return {
showPicker: false,
treeData: [],
dataObj: data.second,
isMain: '',
data16: {
treeData: data.arr,
tempSelect: [],
showPicker: false
},
value: [],
isAll: true
}
},
methods: {
onSelectNodes (nodes, isMain) {
this.data16.tempSelect = nodes.map(node => node.label)
this.isMain = isMain
console.log(this.data16.tempSelect,'value')
this.changePikerStyle()
},
changePikerStyle(){
this.isMain = this.dataObj.isMain
this.data16.tempSelect = this.data16.tempSelect.length > 0 ? this.data16.tempSelect : this.dataObj.selected
console.log('changePikerStyle',this.data16)
this.showPicker = !this.showPicker
this.data16.showPicker = this.showPicker
},
selectAll(){
if(this.isAll){
this.data16.treeData = data.arr
} else {
this.data16.treeData = data.arr1
}
this.isAll = !this.isAll
console.log('selectAll',this.isAll,this.data16.treeData)
}
}
}
</script>
<style>
</style>
以下为实际的组件功能实现代码
<template>
<div>
<div class="van-picker__toolbar">
<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;">
<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 style="
height: 24px;
background: #EBEBEB;
border-radius: 13px;
display: flex;">
<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;">
<div v-for="child in treeData" :key="child.id">
<TreeItem1 ref="treeItem" :node="child" :isMain="isMain" @node-checked="onNodeChecked"></TreeItem1>
</div>
</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 },
tempSelect: { type: Array, required: false },
show: { type: Boolean, required: false }
},
data() {
return {
treeData: [],
tempTreeData: [],
value: '',
checkedAll: false,
isMain: true,
selectedNodes: [],
index: 0
}
},
watch: {
show(val) {
if (val) {
console.log('show', val, this.treesData, this.tempSelect)
this.reset()
}
},
treesData: {
deep: true, // 添加深度监听
handler(newVal) {
this.reset()
}
},
treeData: {
deep: true, // 添加深度监听
handler(newVal) {
console.log('treeData', this.index++, newVal)
}
}
},
mounted() {
this.reset()
},
methods: {
// 1.依据输入数据进行重置
reset() {
this.value = ''
this.isMain = this.isMainStyle
this.tempTreeData = this.transformData(this.treesData)
this.tempTreeData = Object.freeze(this.transformData(this.treesData))
this.treeData = this.formTreeData(this.tempTreeData, this.isMain)
if (this.tempSelect && this.tempSelect.length > 0) {
this.selectedNodes = this.setSelectedNodes(this.tempSelect, this.treeData, this.isMain)
} else {
this.selectedNodes = []
}
// 设置treeData的选中状态
this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
console.log('treeDataFr', Object.isFrozen(this.treeData))
console.log('treeData', this.treeData)
this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
},
// 对数据进行清洗,满足使用标准
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) {
// let trees = [...tree]
// return trees
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
},
// 设置selected数组的值
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
},
// 对treeData树选中的节点进行设置
setTreeDataChecked (tree, arr, isMain) {
console.log('setTreeDataChecked001',tree[0].checked,arr[0],isMain)
// tree的节点node的label与arr的arrnode的label匹配,设置node的checked为true
if (isMain) {
tree.forEach(node => {
if (arr.some(arrnode => arrnode.label === node.label)) {
this.$set(node, 'checked', true);
} else {
this.$set(node, 'checked', false);
}
})
} else {
tree.forEach(treeson => {
treeson.children.forEach(childnode => {
if (arr.some(arrnode => arrnode.label === childnode.label)) {
this.$set(childnode, 'checked', true);
} else {
this.$set(childnode, 'checked', false);
}
})
})
}
console.log('setTreeDataChecked002',tree[0].checked,arr[0],isMain)
},
// 判断是否全选
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
},
// 2.主从店切换
changeisMain (isMain) {
if (this.isMain === isMain) return
this.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)
},
// 3.主从店不切换时的筛选
onChange() {
// 从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)
},
// 4.全选
handleCheckedAll() {
this.checkedAll = !this.checkedAll
console.log('handleCheckedAll', this.checkedAll, this.isMain, this.treeData[0].checked)
// 全选/全不选-设置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)
})
})
}
console.log('handleCheckedAllselectedNodes', this.selectedNodes)
// 节点去重
this.selectedNodes = this.selectedNodes.filter((node, index, self) =>
index === self.findIndex((t) => (
t.label === node.label
))
)
console.log('treeDataFr', Object.isFrozen(this.treeData))
this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
console.log('treeDataFr', Object.isFrozen(this.treeData))
console.log('handleCheckedAlltreeData', this.treeData)
},
// 进行选中数据处理
onNodeChecked (node) {
this.nodeChecked(node)
this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
// console.log('onNodeChecked', this.selectedNodes)
},
nodeChecked(node) {
if (node.checked) {
this.selectedNodes.push(node)
} else {
this.selectedNodes.splice(this.selectedNodes.findIndex(selectedNode => selectedNode.label === node.label), 1)
}
},
// 返回查询的数据
onConfirm() {
this.$emit('selectNodes', this.selectedNodes, this.isMain)
this.value = ''
},
cancel() {
this.$emit('changePikerStyle')
this.value = ''
console.log('cancel')
},
// 按照value过滤treeData树形结构的数据
filterTreeData(treeData, 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)
}
}
}
</script>
<style scoped>
.changeBox {
display: flex;
justify-content: space-between;
padding: 0 0 8px;
border-bottom: 1px solid #F2F4F7;
}
.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>
单组件设置
<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="../../images/saling.png" style="width: 48px;" v-if="node.label1 === '营业中'">
<img src="../../images/saling1.png" style="width: 48px;" v-else-if="node.label1 === '待开业'">
<img src="../../images/saling2.png" style="width: 48px;" v-else-if="node.label1 === '已撤销'">
</span>
{{ node.label }}
<img src="../../images/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="../../images/saling.png" style="width: 48px;" v-if="child.label1 === '营业中'">
<img src="../../images/saling1.png" style="width: 48px;" v-else-if="child.label1 === '待开业'">
<img src="../../images/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) {
}
},
'node.checked': {
handler(newVal) {
console.log('node.checked',newVal)
},
deep: true
}
},
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>