Ant Design Vue 实现左右树穿梭框
Ant Design Vue 实现左右树穿梭框最近碰见一个需求,要实现权限树左右树穿梭的功能。Ant Design Vue官网中有穿梭框 Transfer 组件,但提供的示例是左侧是树,右侧则是平铺列表。只能在其基础上进行修改实现了。效果图Options API 实现新建文件 options-transfer.vue<template><a-transfer:data-sourc
·
最近碰见一个需求,要实现权限树左右树穿梭的功能。Ant Design Vue
官网中有穿梭框 Transfer
组件,但提供的示例是左侧是树,右侧则是平铺列表。只能在其基础上进行修改实现了。
效果图
Options API 实现
新建文件 options-transfer.vue
<template>
<a-transfer
:data-source="dataSource"
:target-keys="targetKeys"
:render="item => item.title"
:show-select-all="false"
@change="onChange"
>
<template #children="{ direction, selectedKeys, onItemSelect }">
<template v-if="direction==='left'">
<a-tree
v-if="leftTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="leftTreeData"
:checked-keys="leftCheckedKey"
@check="(_, props) => { handleLeftChecked(_, props, [...selectedKeys, ...targetKeys], onItemSelect) }"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
<template v-else-if="direction==='right'">
<a-tree
v-if="rightTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="rightTreeData"
v-model:checked-keys="rightCheckedKey"
v-model:expanded-keys="rightExpandedKey"
@check="(_, props) => { handleRightChecked(_, props, [...selectedKeys, ...targetKeys], onItemSelect) }"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
</template>
</a-transfer>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import {
cloneDeep,
flatten,
getDeepList,
getTreeKeys,
handleLeftTreeData,
handleRightTreeData,
isChecked,
uniqueTree
} from '@/utils'
import type { TreeDataItem } from '@/types'
export default defineComponent({
name: 'OptionsTransfer',
props: {
/** 树数据 */
treeData: {
type: Array as PropType<TreeDataItem[]>,
default: () => []
},
/** 编辑 key */
editKey: {
type: Array as PropType<string[]>,
default: () => []
}
},
data () {
return {
targetKeys: [] as string[], // 显示在右侧框数据的 key 集合
dataSource: [] as TreeDataItem[], // 数据源,其中的数据将会被渲染到左边一栏
leftCheckedKey: [] as string[], // 左侧树选中 key 集合
leftHalfCheckedKeys: [] as string[], // 左侧半选集合
leftCheckedAllKey: [] as string[], // 左侧树选中的 key 集合,包括半选与全选
leftTreeData: [] as TreeDataItem[], // 左侧树
rightCheckedKey: [] as string[], // 右侧树选中集合
rightCheckedAllKey: [] as string[], // 右侧树选中集合,包括半选与全选
rightExpandedKey: [] as string[], // 右侧展开数集合
rightTreeData: [] as TreeDataItem[], // 右侧树
emitKeys: [] as string[], // 往父级组件传递的数据
deepList: [] as string[] // 深层列表
}
},
watch: {
treeData: {
deep: true,
handler (val) {
this.processTreeData()
}
},
editKey: {
deep: true,
handler (val) {
this.processTreeData()
}
}
},
created () {
this.processTreeData()
},
methods: {
// 处理树数据
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 => this.leftCheckedKey.indexOf(item) === -1)
this.leftTreeData = handleLeftTreeData(cloneDeep(this.treeData), this.leftCheckedKey)
this.emitKeys = this.rightExpandedKey
},
// 穿梭更改
onChange (targetKeys: string[], 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')
} 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 => this.rightCheckedKey.indexOf(item) === -1)
this.targetKeys = this.targetKeys.filter(item => this.rightCheckedKey.indexOf(item) === -1)
this.leftHalfCheckedKeys = this.leftHalfCheckedKeys.filter(item => this.rightCheckedKey.indexOf(item) === -1)
this.rightCheckedKey = []
}
this.rightExpandedKey = getTreeKeys(this.rightTreeData)
this.emitKeys = this.rightExpandedKey
},
// 左侧选择
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, !isChecked(checkedKeys, eventKey))
},
// 右侧选择
handleRightChecked (_: string[], {
node,
halfCheckedKeys
}: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void) {
this.rightCheckedKey = _
this.rightCheckedAllKey = [...halfCheckedKeys, ..._]
const { eventKey } = node
itemSelect(eventKey, isChecked(_, eventKey))
}
}
})
</script>
<style scoped lang="less">
.ant-transfer {
::v-deep(.ant-transfer-list) {
width: 300px;
}
}
</style>
Composition API 实现
新建 use-tree-transfer.ts
文件
import { onMounted, reactive, watch } from 'vue'
import {
cloneDeep,
flatten,
getDeepList,
getTreeKeys,
handleLeftTreeData,
handleRightTreeData,
isChecked,
uniqueTree
} from '@/utils'
import type { TreeDataItem } from '@/types'
interface Props {
/** 树数据 */
treeData: TreeDataItem[];
/** 编辑 key */
editKey: string[];
}
interface State {
/** 显示在右侧框数据的 key 集合 */
targetKeys: string[];
/** 数据源,其中的数据将会被渲染到左边一栏 */
dataSource: TreeDataItem[];
/** 左侧树选中 key 集合 */
leftCheckedKey: string[];
/** 左侧半选集合 */
leftHalfCheckedKeys: string[];
/** 左侧树选中的 key 集合,包括半选与全选 */
leftCheckedAllKey: string[];
/** 左侧树 */
leftTreeData: TreeDataItem[];
/** 右侧树选中集合 */
rightCheckedKey: string[];
/** 右侧树选中集合,包括半选与全选 */
rightCheckedAllKey: string[];
/** 右侧展开数集合 */
rightExpandedKey: string[];
/** 右侧树 */
rightTreeData: TreeDataItem[];
/** 往父级组件传递的数据 */
emitKeys: string[];
/** 深层列表 */
deepList: string[];
}
interface UseTreeTransfer {
state: State;
/** 穿梭更改 */
onChange: (targetKeys: string[], direction: string) => void;
/** 左侧选择 */
handleLeftChecked: (_: string[], { node, halfCheckedKeys }: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void) => void;
/** 右侧选择 */
handleRightChecked: (_: string[], { node, halfCheckedKeys }: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void) => void;
}
export function useTreeTransfer (props: Props): UseTreeTransfer {
const state = reactive<State>({
targetKeys: [],
dataSource: [],
leftCheckedKey: [],
leftHalfCheckedKeys: [],
leftCheckedAllKey: [],
leftTreeData: [],
rightCheckedKey: [],
rightCheckedAllKey: [],
rightExpandedKey: [],
rightTreeData: [],
emitKeys: [],
deepList: []
})
watch(props, () => {
processEditData()
})
onMounted(() => {
processTreeData()
})
/** 处理树数据 */
function processTreeData (): void {
flatten(cloneDeep(props.treeData), state.dataSource)
if (props.editKey.length) {
processEditData()
} else {
state.leftTreeData = handleLeftTreeData(cloneDeep(props.treeData), state.leftCheckedKey)
}
}
/** 处理编辑数据 */
function processEditData (): void {
state.leftCheckedAllKey = props.editKey
state.rightExpandedKey = props.editKey
state.targetKeys = props.editKey
state.rightTreeData = handleRightTreeData(cloneDeep(props.treeData), props.editKey)
getDeepList(state.deepList, props.treeData)
state.leftCheckedKey = uniqueTree(props.editKey, state.deepList)
state.leftHalfCheckedKeys = state.leftCheckedAllKey.filter(item => state.leftCheckedKey.indexOf(item) === -1)
state.leftTreeData = handleLeftTreeData(cloneDeep(props.treeData), state.leftCheckedKey)
state.emitKeys = state.rightExpandedKey
}
/** 穿梭更改 */
function onChange (targetKeys: string[], direction: string) {
if (direction === 'right') {
state.targetKeys = state.leftCheckedAllKey
state.rightCheckedKey = []
state.rightTreeData = handleRightTreeData(cloneDeep(props.treeData), state.leftCheckedAllKey, 'right')
state.leftTreeData = handleLeftTreeData(cloneDeep(props.treeData), state.leftCheckedKey, 'right')
} else if (direction === 'left') {
state.rightTreeData = handleRightTreeData(state.rightTreeData, state.rightCheckedKey, 'left')
state.leftTreeData = handleLeftTreeData(state.leftTreeData, state.rightCheckedKey, 'left')
state.leftCheckedKey = state.leftCheckedKey.filter(item => state.rightCheckedKey.indexOf(item) === -1)
state.targetKeys = state.targetKeys.filter(item => state.rightCheckedKey.indexOf(item) === -1)
state.leftHalfCheckedKeys = state.leftHalfCheckedKeys.filter(item => state.rightCheckedKey.indexOf(item) === -1)
state.rightCheckedKey = []
}
state.rightExpandedKey = getTreeKeys(state.rightTreeData)
state.emitKeys = state.rightExpandedKey
}
/** 左侧选择 */
function handleLeftChecked (_: string[], { node, halfCheckedKeys }: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void): void {
state.leftCheckedKey = _
state.leftHalfCheckedKeys = [...new Set([...state.leftHalfCheckedKeys, ...halfCheckedKeys])]
state.leftCheckedAllKey = [...new Set([...state.leftHalfCheckedKeys, ...halfCheckedKeys, ..._])]
const { eventKey } = node
itemSelect(eventKey, !isChecked(checkedKeys, eventKey))
}
/** 右侧选择 */
function handleRightChecked (_: string[], { node, halfCheckedKeys }: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void): void {
state.rightCheckedKey = _
state.rightCheckedAllKey = [...halfCheckedKeys, ..._]
const { eventKey } = node
itemSelect(eventKey, isChecked(_, eventKey))
}
return {
state,
onChange,
handleLeftChecked,
handleRightChecked
}
}
新建 composition-transfer.vue
文件
<template>
<a-transfer
:data-source="dataSource"
:target-keys="targetKeys"
:render="item => item.title"
:show-select-all="false"
@change="onChange"
>
<template #children="{ direction, selectedKeys, onItemSelect }">
<template v-if="direction==='left'">
<a-tree
v-if="leftTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="leftTreeData"
:checked-keys="leftCheckedKey"
@check="(_, props) => { handleLeftChecked(_, props, [...selectedKeys, ...targetKeys], onItemSelect) }"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
<template v-else-if="direction==='right'">
<a-tree
v-if="rightTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="rightTreeData"
v-model:checked-keys="rightCheckedKey"
v-model:expanded-keys="rightExpandedKey"
@check="(_, props) => { handleRightChecked(_, props, [...selectedKeys, ...targetKeys], onItemSelect) }"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
</template>
</a-transfer>
</template>
<script lang="ts">
import { defineComponent, PropType, toRefs } from 'vue'
import { useTreeTransfer } from './use-tree-transfer'
import type { TreeDataItem } from '@/types'
export default defineComponent({
name: 'CompositionTransfer',
props: {
/** 树数据 */
treeData: {
type: Array as PropType<TreeDataItem[]>,
default: () => []
},
/** 编辑 key */
editKey: {
type: Array as PropType<string[]>,
default: () => []
}
},
setup (props) {
const { state, onChange, handleLeftChecked, handleRightChecked } = useTreeTransfer(props)
return {
...toRefs(state),
onChange,
handleLeftChecked,
handleRightChecked
}
}
})
</script>
<style scoped lang="less">
.ant-transfer {
::v-deep(.ant-transfer-list) {
width: 300px;
}
}
</style>
页面中使用组件
部分代码:
<template>
<div class="transfer-wrapper">
<transfer
ref="transferRef"
:tree-data="treeData"
:edit-key="editKey"
/>
</div>
<a-space>
<a-button
type="primary"
@click="getValue"
>
获取值
</a-button>
<a-button
@click="setValue"
>
设置值
</a-button>
</a-space>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
import Transfer from '@/components/composition-transfer.vue'
import data from '@/assets/data'
import type { TreeDataItem } from '@/types'
interface State {
/** 组件实例 */
transferRef: any;
/** 树数据 */
treeData: TreeDataItem[];
/** 编辑 key */
editKey: string[];
}
export default defineComponent({
name: 'Composition',
components: {
Transfer
},
setup () {
const state = reactive<State>({
transferRef: undefined,
treeData: data.treeData,
editKey: []
})
/** 设置值 */
function getValue () {
console.log(state.transferRef.emitKeys)
}
/** 获取值 */
function setValue () {
state.editKey = data.editKey
}
return {
...toRefs(state),
getValue,
setValue
}
}
})
</script>
源码地址
更多推荐
已为社区贡献19条内容
所有评论(0)