需求是这样的,产品想要更直观的展示已选的地区数据,要求弄两个树来展示,不用往下拉就能看到已选的地区。
交互效果说明,全选、省份和城市可以选中和取消,全选选中添加要把全国及以下所有层级数据选中添加,全选选中取消要把全国及以下所有层级数据选中取消;省份选中添加要把省份及以下所有层级数据选中添加,省份选中取消要把省份及以下所有层级数据选中取消。
效果图
页面布局
<a-transfer
:data-source="dataSource"
:list-style="{
width: '45%',
height: '420px',
marginTop: '20px',
marginBottom: '20px'
}"
:render="item => item.title"
:show-select-all="false"
:target-keys="targetKeys"
@change="onChangeTransfer"
>
<template
slot="children"
slot-scope="{
props: { direction, selectedKeys },
on: { itemSelect, itemSelectAll },
}"
>
<div v-if="direction === 'left'">
<a-checkbox
:checked="checkedLeftAll"
@change="(e) => {
onChangeLeftAll(e, [...selectedKeys], itemSelectAll, itemSelect)
}
"
>
全选
</a-checkbox>
<a-tree
class="transfer-tree"
blockNode
checkable
:checked-keys="leftCheckedKey"
:treeData="leftTreeData"
@check="
(_, props) => {
handleLeftChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect);
}
"
/>
</div>
<div v-else-if="direction==='right'">
<a-checkbox
:checked="checkedRightAll"
@change="(e) => {
onChangeRightAll(e, [...selectedKeys], itemSelectAll, itemSelect)
}
"
>
全选
</a-checkbox>
<a-tree
id="rightTree"
class="transfer-tree"
checkable
:auto-expand-parent="autoExpandParent"
:expanded-keys="rightExpandedKey"
:checked-keys="rightCheckedKey"
:treeData="rightTreeData"
@expand="onExpand"
@check="
(_, props) => {
handleRightChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect);
}
"
/>
</div>
</template>
</a-transfer>
js设置
<script lang="ts">
import {
cloneDeep,
flatten,
getDeepList,
getTreeKeys,
handleLeftTreeData,
handleRightTreeData,
isChecked,
uniqueTree
} from '@/utils/tree'
export default class AreaSelect extends Vue {
title = '地区'
treeData: any = [] // tree地区数据
dataSource: any[] = [] // 穿梭框数据
targetKeys: any = [] // 穿梭框已选的key
leftCheckedKey: any = [] // 左侧树选中 key 集合
leftHalfCheckedKeys: any = [] // 左侧半选集合
leftCheckedAllKey: any = [] // 左侧树选中的 key 集合,包括半选与全选
leftTreeData: any = [] // 左侧树
rightCheckedKey: any = [] // 左侧树选中 key 集合
rightHalfCheckedKeys: any = [] // 左侧半选集合
rightCheckedAllKey: any = [] // 左侧树选中的 key 集合,包括半选与全选
rightExpandedKey: any = [] // 右侧展开数集合
rightTreeData: any = [] // 左侧树
emitKeys: any = [] // 往父级组件传递的数据
deepList: any = [] // 深层列表
editKey: any = [] // 编辑的key,areaCode
areaCodes: any = '' // 编辑回传的地区code,全国'-1'或地区areaCode
checkedLeftAll: any = false // 左侧全选
checkedRightAll: any = false // 右侧全选
autoExpandParent: any = true
onExpand (expandedKeys: any) {
this.rightExpandedKey = expandedKeys
this.autoExpandParent = false
}
isChecked (selectedKeys: any, eventKey: any) {
return selectedKeys.indexOf(eventKey) !== -1
}
onChangeLeftAll (
e: any,
checkedKeys: string[],
itemSelectAll: (arg0: any, arg1: boolean) => void) {
this.checkedLeftAll = e.target.checked
let diffKeys: string[] = []
if (this.checkedLeftAll) {
this.leftCheckedAllKey = getTreeKeys(this.leftTreeData)
this.leftCheckedKey = this.leftCheckedAllKey
} else {
this.leftCheckedAllKey = []
this.leftCheckedKey = []
diffKeys = []
}
diffKeys = getTreeKeys(this.leftTreeData)
itemSelectAll(diffKeys, e.target.checked)
}
onChangeRightAll (e: any,
checkedKeys: string[],
itemSelectAll: (arg0: any, arg1: boolean) => void) {
this.checkedRightAll = e.target.checked
let diffKeys: string[] = []
if (this.checkedRightAll) {
diffKeys = getTreeKeys(this.rightTreeData)
this.rightCheckedKey = diffKeys
} else {
this.rightCheckedKey = []
diffKeys = []
}
itemSelectAll(diffKeys, e.target.checked)
}
// 左侧选择
handleLeftChecked (_: string[], {
node,
halfCheckedKeys
}: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void) {
this.leftCheckedKey = _
this.leftHalfCheckedKeys = [...new Set([...this.leftHalfCheckedKeys, ...halfCheckedKeys])]
this.leftCheckedAllKey = [...new Set([...this.leftHalfCheckedKeys, ...halfCheckedKeys, ..._])]
const { eventKey } = node
itemSelect(eventKey, !this.isChecked(checkedKeys, eventKey))
}
// 右侧选择
handleRightChecked (_: string[], {
node,
halfCheckedKeys
}: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void) {
this.rightCheckedKey = _
this.rightCheckedAllKey = [...new Set([...halfCheckedKeys, ..._])]
const { eventKey } = node
itemSelect(eventKey, this.isChecked(_, eventKey))
}
// 选项在两栏之间转移时的回调函数
onChangeTransfer (targetKeys: any, direction: string) {
if (direction === 'right') {
this.targetKeys = this.leftCheckedAllKey
this.rightCheckedKey = []
this.rightTreeData = handleRightTreeData(cloneDeep(this.treeData), this.leftCheckedAllKey, 'right')
this.leftTreeData = handleLeftTreeData(cloneDeep(this.treeData), this.leftCheckedKey, 'right')
this.checkedLeftAll = false
} else if (direction === 'left') {
this.rightTreeData = handleRightTreeData(this.rightTreeData, this.rightCheckedKey, 'left')
this.leftTreeData = handleLeftTreeData(this.leftTreeData, this.rightCheckedKey, 'left')
this.leftCheckedKey = this.leftCheckedKey.filter((item: any) => this.rightCheckedKey.indexOf(item) === -1)
this.targetKeys = this.targetKeys.filter((item: any) => this.rightCheckedKey.indexOf(item) === -1)
this.leftHalfCheckedKeys = this.leftHalfCheckedKeys.filter((item: any) => this.rightCheckedKey.indexOf(item) === -1)
this.rightCheckedKey = []
this.checkedRightAll = false
}
this.rightExpandedKey = getTreeKeys(this.rightTreeData)
this.emitKeys = this.rightExpandedKey
}
// 查看详情反显已选地区数据
detail (record: any) {
this.areaCodes = record.areaCodes || ''
this.queryAreaList()
}
// 获取tree地区数据
queryAreaList () {
this.dataSource = []
// 这里是测试数据,实际开发中这里是调接口请求数据
this.treeData = {
id: 16004,
key: '110000',
title: '北京市',
level: 1,
parentId: 0,
areaCode: '110000',
children: [{
id: 16038,
key: '110100',
title: '市辖区',
level: 2,
parentId: 16004,
areaCode: '110100'
},
{
id: 16039,
key: '110200',
title: '县',
level: 2,
parentId: 16004,
areaCode: '110200'
}]
}
this.getAreaCodes(this.treeData)
this.processTreeData()
}
// 根据实际情况处理数据
getAreaCodes (data: any) {
if (this.areaCodes !== '-1') {
this.editKey = this.areaCodes.split(',')
} else if (this.areaCodes === '-1') {
this.editKey = areaCodes // 全国的地区code
} else {
this.editKey = []
}
}
// 处理树数据
processTreeData () {
flatten(cloneDeep(this.treeData), this.dataSource)
if (this.editKey.length) {
this.processEditData()
} else {
this.leftTreeData = handleLeftTreeData(cloneDeep(this.treeData), this.leftCheckedKey)
}
}
// 处理编辑数据
processEditData () {
this.leftCheckedAllKey = this.editKey
this.rightExpandedKey = this.editKey
this.targetKeys = this.editKey
this.rightTreeData = handleRightTreeData(cloneDeep(this.treeData), this.editKey)
getDeepList(this.deepList, this.treeData)
this.leftCheckedKey = uniqueTree(this.editKey, this.deepList)
this.leftHalfCheckedKeys = this.leftCheckedAllKey.filter((item: any) => this.leftCheckedKey.indexOf(item) === -1)
if (this.areaCodes === '-1') {
this.leftCheckedKey = [...this.leftCheckedAllKey, ...this.leftHalfCheckedKeys]
} else {
this.leftCheckedKey = this.leftCheckedAllKey
}
this.leftTreeData = handleLeftTreeData(cloneDeep(this.treeData), this.leftCheckedKey)
this.emitKeys = this.rightExpandedKey
}
}
</script>
css样式设置
.transfer-tree {
height: 300px;
overflow-y: auto
}
utils/tree.ts
/**
* 根据parentId生成树结构
* @param array 要操作的数据
* @param childrenWrapName 包裹子数组的属性值
* @param isLeaf 是否为最终项(即无子数组了)
* @returns {boolean} 成功 push 返回 数组,不处理返回 []
*/
export function transferArrayToTree (data: any[], childrenWrapName = 'children', isLeafName = 'isLeaf') {
let idArray: any[] = []
let dealData: any[] = []
let children = childrenWrapName || 'children'
let isLeaf = isLeafName || 'isLeaf'
// 获取所有id
idArray = data.map(item => {
return item.id
})
if (dealData) {
// 如果允许不存在的parentId升为一级
dealData = data.map(item => {
if (!idArray.includes(item.parentId)) {
item.parentId = 0
}
return item
})
} else {
dealData = data
}
// 对源数据深度克隆
let cloneData: any[] = JSON.parse(JSON.stringify(dealData))
if (cloneData) {
return cloneData.filter(father => {
// 返回每一项的子级数组
let branchArr = cloneData.filter(child => {
if (father.id === child.parentId) {
return child
}
})
// 如果存在子级,则给父级添加一个children属性,并赋值
branchArr.length > 0 ? father[children] = branchArr : father[isLeaf] = true
// 返回第一层
return father.parentId === 0
})
} else {
return []
}
}
// 获取指定层级需要展开的树节点
export function getTreeExpandedKeysByLevel (arr: any[] = [], level = 1) {
let result: any[] = []
let func = (list: any[], number: number) => {
if (number <= level) {
list.forEach(item => {
result.push(item.key)
func(item.children || [], number + 1)
})
}
}
func(arr, 1)
return result
}
export interface TreeDataItem {
key: string;
title: string;
disabled?: boolean;
children?: TreeDataItem[];
[key: string]: any;
}
/**
* 深拷贝
* @param data
*/
export function cloneDeep<T> (data: T): T {
return JSON.parse(JSON.stringify(data))
}
/**
* 树转数组
* @param tree
* @param hasChildren
*/
export function treeToList (tree: TreeDataItem[], hasChildren = false): TreeDataItem[] {
let queen: TreeDataItem[] = []
const out: TreeDataItem[] = []
queen = queen.concat(JSON.parse(JSON.stringify(tree)))
while (queen.length) {
const first = queen.shift() as TreeDataItem
if (first?.children) {
queen = queen.concat(first.children)
if (!hasChildren) delete first.children
}
out.push(first)
}
return out
}
/**
* 数组转树
* @param list
* @param tree
* @param parentId
* @param key
*/
export function listToTree (list: TreeDataItem[], tree: TreeDataItem[], parentId = 0, key = 'parentId'): TreeDataItem[] {
list.forEach(item => {
if (item[key] === parentId) {
const child: TreeDataItem = {
...item,
children: []
}
listToTree(list, child.children as TreeDataItem[], item.id, key)
if (!child.children?.length) delete child.children
tree.push(child)
}
})
return tree
}
/**
* 获取树节点 key 列表
* @param treeData
*/
export function getTreeKeys (treeData: TreeDataItem[]): string[] {
const list = treeToList(treeData)
return list.map(item => item.key)
}
/**
* 循环遍历出最深层子节点,存放在一个数组中
* @param deepList
* @param treeData
*/
export function getDeepList (deepList: string[], treeData: TreeDataItem[]): string[] {
treeData.forEach(item => {
if (item.children && item.children.length) {
getDeepList(deepList, item.children)
} else {
deepList.push(item.key)
}
})
return deepList
}
/**
* 将后台返回的含有父节点的数组和第一步骤遍历的数组做比较,如果有相同值,将相同值取出来,push到一个新数组中
* @param uniqueArr
* @param arr
*/
export function uniqueTree (uniqueArr: string[], arr: string[]): string[] {
const uniqueChild = []
for (const i in arr) {
for (const k in uniqueArr) {
if (uniqueArr[k] === arr[i]) {
uniqueChild.push(uniqueArr[k])
}
}
}
return uniqueChild
}
/**
* 是否选中
* @param selectedKeys
* @param eventKey
*/
export function isChecked (selectedKeys: string[], eventKey: string): boolean {
return selectedKeys.indexOf(eventKey) !== -1
}
/**
* 处理左侧树数据
* @param data
* @param targetKeys
* @param direction
*/
export function handleLeftTreeData (data: TreeDataItem[], targetKeys: string[], direction = 'right'): TreeDataItem[] {
data.forEach(item => {
if (direction === 'right') {
item.disabled = targetKeys.includes(item.key)
} else if (direction === 'left') {
if (item.disabled && targetKeys.includes(item.key)) item.disabled = false
}
if (item.children) handleLeftTreeData(item.children, targetKeys, direction)
})
return data
}
/**
* 处理右侧树数据
* @param data
* @param targetKeys
* @param direction
*/
export function handleRightTreeData (data: TreeDataItem[], targetKeys: string[], direction = 'right'): TreeDataItem[] {
const list = treeToList(data)
const arr: TreeDataItem[] = []
const tree: TreeDataItem[] = []
list.forEach(item => {
if (direction === 'right') {
if (targetKeys.includes(item.key)) {
const content = { ...item }
if (content.children) delete content.children
arr.push({ ...content })
}
} else if (direction === 'left') {
if (!targetKeys.includes(item.key)) {
const content = { ...item }
if (content.children) delete content.children
arr.push({ ...content })
}
}
})
listToTree(arr, tree, 0)
return tree
}
/**
* 树数据展平
* @param list
* @param dataSource
*/
export function flatten (list: TreeDataItem[], dataSource: TreeDataItem[]): TreeDataItem[] {
list.forEach(item => {
dataSource.push(item)
if (item.children) flatten(item.children, dataSource)
})
return dataSource
}