特殊的穿梭框
先来看一个我自己封装的一个 Antd UI 组件库的一个穿梭框
这个穿梭框记得当时踩了一点坑
起初并没有做组件化,以为是很简单的一个数据穿梭框,没想到…
import { Transfer, Tree } from 'antd' import React, { useState, useEffect } from 'react' import { ImportTransferContainer } from './style' /** * * @param treeData 左侧用到的数据 * @param render 穿梭框返回的title * @param targetKeys 右侧选中的keys * @param dataSource 数据源 * @param rowKey 哪条该被选中 * @param generateTree 处理数据 * @param searchName 搜索名字 * @returns */ const TreeTransfer = ({ // 传参列表 treeData, dataSource, targetKeys, render, rowKey, generateTree, searchName = 'name', ...restProps }) => { const transferDataSource = [] const [data, setData] = useState([]) useEffect(() => { setData(generateTree(dataSource, targetKeys)) }, [dataSource, targetKeys]) const flatten = (list = []) => { list.forEach((item) => { transferDataSource.push(item) flatten(item.children) }) } flatten(dataSource) const isChecked = (selectedKeys, eventKey) => selectedKeys.includes(eventKey) const onSearch = (direction, value) => { if (direction === 'left') { if (value) { setData(generateTree(dataSource, targetKeys).filter(item => value.includes(item[searchName]))) } else { setData(generateTree(dataSource, targetKeys)) } } } return ( <ImportTransferContainer> <Transfer {...restProps} targetKeys={targetKeys} dataSource={transferDataSource} className="tree-transfer" titles={['源数据列表', '目的数据列表']} render={render} onSearch={onSearch} rowKey={rowKey} showSelectAll={false} > {({ direction, onItemSelect, selectedKeys }) => { if (direction === 'left') { const checkedKeys = [...selectedKeys, ...targetKeys] return ( <div> <Tree blockNode checkable checkStrictly defaultExpandAll checkedKeys={checkedKeys} treeData={data} onCheck={(_, { node: { key } }) => { onItemSelect(key, !isChecked(checkedKeys, key)) }} onSelect={(_, { node: { key } }) => { onItemSelect(key, !isChecked(checkedKeys, key)) }} /> </div> ) } return null }} </Transfer> </ImportTransferContainer> ) } export default TreeTransfer
不难发现,这是一个根据树形结构显示左侧数据源的一个穿梭框
可惜的是,我并没有用到
children
,因为我只是单层数据结构
看看我是如何使用它的
<VImportTransfer rowKey={(e) => e.id} // 每行的标识 render={(item) => item.name} // 显示名称 dataSource={systemData} // 包含左侧和右侧共同的数据[数据源] targetKeys={targetKeys} // 目标id数组,右侧数据 showSearch // 搜索框控制显隐 onChange={onChange} // change事件 generateTree={generateTree} // 处理 树组件数据方法 /> // 可能是技术水平有限,只能在这里写这个方法 const generateTree = () => { // 在数据源中,使用filter方法过滤掉了systemData中那些id包含在unitKeys数组中的对象 const treeData = systemData.filter(obj => !unitKeys.includes(obj.id)) // 使用map方法经过数据处理返回一个新的对象,我们给它加了几个参数 // 例如:disabled 有相同则置灰 return treeData.map((item) => ({ ...item, disabled: targetKeys.includes(item.id), title: item.name, key: item.id })) } // 从系统库中选择change事件 // 这里是我 onChange 作的各种判断~ // keys:当前选中的id数组 // direction:穿梭框方向移动(left/right) // movekeys:被移动的id数组 const onChange = (keys, direction, moveKeys) => { if (direction === 'right') { setDataResoure([...dataResource, ...moveKeys]) } if (direction === 'left') { if (moveKeys.length === 1) { dataResource.splice(moveKeys[0], 1) } else { moveKeys.forEach(element => { dataResource.splice(element, 1) }) } const unitFlag = unitList.map(i => moveKeys.includes(i.id)) if (unitFlag.includes(true)) { return messageApi.open({ type: 'warning', content: t('不能从模板库导入系统库') }) } const filteredMoveKeys = moveKeys.filter(key => !keys.includes(key)) moveKeys.splice(0, moveKeys.length, ...filteredMoveKeys) } setTargetKeys(keys) }
注意
: 这只是加强数据结构的记忆,这里的代码并不完善,甚至有一些数据的定义我在这里面并没有表明
奇怪的树形结构(真的很奇怪)
废话不多说,先整个图,如图所示,你很快就能看出来这是一个
用户权限
数据…
组建的封装代码
首先看这个组件的封装代码
import React, { useEffect, useRef, useState } from 'react' import { Tree } from 'antd' import { useTranslation } from 'react-i18next' import { TREE_DATA, CONVERSION_TYPE } from './contants' const UserPromiseTree = ({ value, powerKeys = [], setGetTree, setGetCheckKey, onChange }) => { const { t } = useTranslation() const treeData = useRef(TREE_DATA({ t })) const [checkedKeys, setCheckedKeys] = useState(powerKeys) const [treeParams, setTreeParams] = useState(value) useEffect(() => { if (value) { const tree = formatTreeData(treeData.current) setTreeParams(tree) } }, []) useEffect(() => { // getTree是根据 checkKeys选中勾选的,如果不手动勾选并拿不到真实的值, 只做为数据处理 setGetTree(treeParams) // getTree 根据 checkKeys 进行勾选渲染 setGetCheckKey(checkedKeys) }, [treeParams]) // 格式化树状结构数据 const formatTreeData = (props) => { let tree = {} props.forEach(item => { if (item.children?.length > 0) { tree = { ...tree, [item.key]: formatTreeData(item.children) } } else { tree = { ...tree, [item.key]: !!item.checked } } }) return tree } // 将选中数据转换为给接口的数据 const dataConversion = (data, checked, key, type = CONVERSION_TYPE.单个) => { return data.map(item => { if (item.key === key || type === CONVERSION_TYPE.全部) { if (item.children?.length > 0) { return { ...item, children: dataConversion(item.children, checked, key, CONVERSION_TYPE.全部) } } return { ...item, checked } } if (item.children?.length > 0) { return { ...item, children: dataConversion(item.children, checked, key) } } return item }) } // 点击复选框触发 const onCheck = (checkedKeysValue, { checked, node }) => { const { key } = node setCheckedKeys(checkedKeysValue) treeData.current = dataConversion(treeData.current, checked, key) const tree = formatTreeData(treeData.current) setTreeParams(tree) onChange(treeParams) } return ( <Tree checkable onCheck={onCheck} checkedKeys={checkedKeys} treeData={TREE_DATA({ t })} /> ) } export default UserPromiseTree
中间层结构,勾选判断作用
checkedKeys
是一个具有很多值的数组,若是你在树组件中勾选了它们,它们则会被添加到checkedKeys
这个数据结构在组件的
onCheck
事件中被添加
setCheckedKeys(checkedKeysValue) // onCheck [ "主页", "站管理器", "模板管理器", "项目管理器", "数据分析", "站管理器页", "新增", "编辑", "删除", "模板管理器页", "最近打开模板", "模板项目", "模板对比", "新增模板", "导入模板", "语言" ]
初始数据结构
这是
TREE_DATA, CONVERSION_TYPE
两个引入的数据结构
export const TREE_DATA = ({ t }) => [ { title: t('主页'), key: '主页', children: [ { title: t('站管理器'), key: '站管理器', children: [{ title: t('站管理器'), key: '站管理器页', children: [] }, { title: t('新增'), key: '新增', children: [] }, { title: t('编辑'), key: '编辑', children: [] }, { title: t('删除'), key: '删除', children: [] }] }, { title: t('模板管理器'), key: '模板管理器', children: [{ title: t('模板管理器'), key: '模板管理器页', children: [] }, { title: t('最近打开模板'), key: '最近打开模板', children: [] }, { title: t('模板项目'), key: '模板项目', children: [{ title: t('模板对比'), key: '模板对比', children: [] }, { title: t('新增模板'), key: '新增模板', children: [] }, { title: t('导入模板'), key: '导入模板', children: [] }] }] }, { title: t('项目管理器'), key: '项目管理器', children: [] }, { title: t('数据分析'), key: '数据分析', children: [] } ] }, { title: t('侧边栏'), key: '侧边栏', children: [ { title: t('用户'), key: '用户', children: [{ title: t('切换用户'), key: '切换用户', children: [] }, { title: t('修改密码'), key: '修改密码', children: [] }, { title: t('用户列表'), key: '用户列表', children: [{ title: t('用户列表'), key: '用户列表页', children: [] }, { title: t('新增'), key: '新增', children: [] }, { title: t('编辑'), key: '编辑', children: [] }, { title: t('删除'), key: '删除', children: [] }] }, { title: t('角色'), key: '角色', children: [] }] }, { title: t('语言'), key: '语言', children: [] }, { title: t('系统管理'), key: '系统管理', children: [{ title: t('单位管理'), key: '单位管理', children: [] }, { title: t('结果变量管理器'), key: '结果变量管理器', children: [] }, { title: t('语言管理'), key: '语言管理', children: [] }, { title: t('输入变量管理器'), key: '输入变量管理器', children: [] }, { title: t('信号变量管理器'), key: '信号变量管理器', children: [] }, { title: t('函数管理器'), key: '函数管理器', children: [] }, { title: t('试样设置'), key: '试样设置', children: [] }, { title: t('点检'), key: '点检', children: [] }, { title: t('首选项'), key: '首选项', children: [] }, { title: t('许可证管理'), key: '许可证管理', children: [] }] }, { title: t('软件配置'), key: '软件配置', children: [{ title: t('硬件管理器'), key: '硬件管理器', children: [] }, { title: t('软件管理器'), key: '软件管理器', children: [] }, { title: t('侧边栏站管理器'), key: '侧边栏站管理器', children: [] }, { title: t('映像管理器'), key: '映像管理器', children: [] }, { title: t('逻辑资源总览'), key: '逻辑资源总览', children: [] }, { title: t('总站管理器'), key: '总站管理器', children: [] }, { title: t('动作管理器'), key: '动作管理器', children: [] }, { title: t('图片管理器'), key: '图片管理器', children: [] }, { title: t('语音管理器'), key: '语音管理器', children: [] }, { title: t('视频管理器'), key: '视频管理器', children: [] }, { title: t('项目管理'), key: '项目管理', children: [] }, { title: t('模板管理'), key: '模板管理', children: [] }, { title: t('指令下发'), key: '指令下发', children: [] }] }, { title: t('信息'), key: '信息', children: [{ title: t('日志'), key: '日志', children: [] }] }, { title: t('帮助'), key: '帮助', children: [] } ] }, { title: t('工具栏'), key: '工具栏', children: [ { title: t('首页'), key: '首页', children: [] }, { title: t('关闭系统'), key: '关闭系统', children: [] }] }, { title: t('模板设置'), key: '模板设置', children: [ { title: t('试验流程'), key: '试验流程', children: [ { title: t('试验流程'), key: '试验流程页', children: [] }, { title: t('控件库'), key: '控件库', children: [] } ] }, { title: t('试验设置'), key: '试验设置', children: [{ title: t('试验设置'), key: '试验设置页', children: [] }, { title: t('向导'), key: '向导', children: [] }, { title: t('对话框'), key: '对话框', children: [] }, { title: t('参数'), key: '参数', children: [] }, { title: t('单位管理'), key: '单位管理', children: [] }, { title: t('前进'), key: '前进', children: [] }, { title: t('返回'), key: '返回', children: [] }] }, { title: t('运行测试'), key: '运行测试', children: [] }, { title: t('输出测试数据'), key: '输出测试数据', children: [] }] } ] export const CONVERSION_TYPE = { 单个: 'ONE', 全部: 'ALL' }
后端需要的数据结构
这里的数据是将
TREE_DATA
内的title
变为key
来进行数据的勾选,其实后端要的就是这种中文结构,它其实是转换成了这样
{ "模板设置": { "试验流程": { "控件库": true, "试验流程页": true }, "试验设置": { "参数": true, "向导": true, "单位管理": true, "试验设置页": true, "前进": true, "对话框": true, "返回": true }, "运行测试": true, "输出测试数据": true }, "工具栏": { "首页": true, "关闭系统": true }, "主页": { "站管理器": { "新增": true, "删除": true, "站管理器页": true, "编辑": true }, "项目管理器": true, "模板管理器": { "最近打开模板": true, "模板项目": { "模板对比": true, "新增模板": true, "导入模板": true }, "模板管理器页": true }, "数据分析": true }, "侧边栏": { "语言": true, "用户": { "修改密码": true, "用户列表": { "新增": true, "删除": true, "用户列表页": true, "编辑": true }, "切换用户": true, "角色": true }, "帮助": true, "信息": { "日志": true }, "软件配置": { "项目管理": true, "视频管理器": true, "软件管理器": true, "指令下发": true, "侧边栏站管理器": true, "图片管理器": true, "硬件管理器": true, "逻辑资源总览": true, "映像管理器": true, "总站管理器": true, "模板管理": true, "动作管理器": true, "语音管理器": true }, "系统管理": { "点检": true, "语言管理": true, "试样设置": true, "结果变量管理器": true, "首选项": true, "输入变量管理器": true, "许可证管理": true, "单位管理": true, "信号变量管理器": true, "函数管理器": true } } }
进行数据勾选,最后修改 true/false
/** * getTree 根据 checkKeys 进行判断. * 根据checkKeys数组的values与getTree的value进行判断. * 如果存在于getTree中则getTree中的false为true, 并且支持多层查值 */ const updateObj = (obj, array) => { const newObj = { ...obj } Object.keys(newObj).forEach(key => { if (typeof newObj[key] === 'object') { newObj[key] = updateObj(newObj[key], array) } else if (array.includes(key)) { newObj[key] = true } }) return newObj }
updateObj(getTree, getCheckKey) // 初始树组件数据,key数组数据
实时监听 权限树
, 很奇怪吧 O.o