马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本文先容了基于 ElementPlus 的 el-tree 组件进行二次封装的 TreeView 组件,使用 Vue3 和 JavaScript 实现。TreeView 组件通过 props 接收树形数据、设置项等,支持懒加载、节点展开/收起、节点点击、删除、编辑等操纵。组件内部通过 ref 管理树实例,并提供了 clearCurrentNode、setCurrentKey、setExpandedKeys 等方法供父组件调用。renderContent 方法用于自界说节点内容,支持根据设置表现删除和编辑按钮。事件处理函数通过 emit 将节点操纵通报给父组件,实现了组件与父组件的交互。样式部分通过 scoped 样式隔离,确保组件样式独立。
准备组件 TreeView treeUtils方法
- <template>
- <div class="tree-container">
- <div v-if="isShowHeader" class="tree-header">
- <slot name="header"></slot>
- </div>
- <el-tree
- ref="treeRef"
- :data="treeData"
- :props="treeProps"
- highlight-current
- node-key="id"
- :render-content="renderContent"
- :lazy="lazy" :load="lazy ? loadNode : undefined"
- :default-expanded-keys="expandedKeys"
- :show-checkbox="showCheckbox"
- :check-strictly="checkStrictly"
- @node-click="handleNodeClick"
- @node-expand="handleNodeExpand"
- @node-collapse="handleNodeCollapse"
- @check="handleCheck" />
- </div>
- </template>
- <script setup>
- import { defineProps, defineEmits, ref } from 'vue'
- import { Delete, Edit } from '@element-plus/icons-vue'
- import { handleNodeExpand as handleNodeExpandUtil, handleNodeCollapse as handleNodeCollapseUtil } from '@/utils/treeUtils'
- // 接收父组件传来的数据
- const props = defineProps({
- treeData: {
- type: Array,
- required: true,
- },
- treeProps: {
- type: Object,
- default: () => ({
- children: 'children',
- label: 'label',
- isLeaf: 'isLeaf'
- })
- },
- showDelete: {
- type: Boolean,
- default: false
- },
- showEdit: {
- type: Boolean,
- default: false
- },
- lazy: {
- type: Boolean,
- default: false
- },
- isShowHeader: {
- type: Boolean,
- default: false
- },
- showCheckbox: {
- type: Boolean,
- default: false
- },
- checkStrictly: {
- type: Boolean,
- default: false
- }
- })
- const applicationYear = ref('')
- // 接收父组件传来的事件
- const emit = defineEmits([
- 'nodeClick',
- 'loadChildren',
- 'deleteNode',
- 'nodeExpand',
- 'nodeCollapse',
- 'check'
- ])
- // 使用props中的treeProps
- const { treeProps } = props
- // 添加treeRef
- const treeRef = ref(null)
- // 展开的节点keys
- const expandedKeys = ref([])
- // 添加取消选中节点的方法
- const clearCurrentNode = () => {
- if (treeRef.value) {
- treeRef.value.setCurrentKey(null)
- }
- }
- // 设置当前选中的节点
- const setCurrentKey = (key) => {
- if (treeRef.value) {
- treeRef.value.setCurrentKey(key)
- }
- }
- // 设置展开的节点
- const setExpandedKeys = (keys) => {
- expandedKeys.value = [...keys]
- }
- // 获取当前展开的节点
- const getExpandedKeys = () => {
- return expandedKeys.value
- }
- // 处理复选框选中事件
- const handleCheck = (data, { checkedKeys, checkedNodes }) => {
- emit('check', {
- data,
- checkedKeys,
- checkedNodes
- })
- }
- // 获取选中的节点
- const getCheckedKeys = () => {
- return treeRef.value?.getCheckedKeys() || []
- }
- // 获取半选中的节点
- const getHalfCheckedKeys = () => {
- return treeRef.value?.getHalfCheckedKeys() || []
- }
- // 设置选中的节点
- const setCheckedKeys = (keys) => {
- treeRef.value?.setCheckedKeys(keys)
- }
- // 暴露方法给父组件
- defineExpose({
- clearCurrentNode,
- setCurrentKey,
- setExpandedKeys,
- getExpandedKeys,
- getCheckedKeys,
- getHalfCheckedKeys,
- setCheckedKeys
- })
- const renderContent = (hFn, { node, data }) => {
- const content = [
- hFn('span', data[props.treeProps.label] || data.label)
- ]
- // 根据showDelete配置决定是否显示删除按钮
- if (props.showDelete) {
- content.push(
- hFn(
- 'el-button',
- {
- type: 'danger',
- size: 'small',
- class: 'delete-btn',
- onClick: () => handleDeleteNode(node, data),
- },
- [
- hFn(Delete)
- ]
- )
- )
- }
- // 根据showDelete配置决定是否显示修改按钮
- if (props.showEdit) {
- content.push(
- hFn(
- 'el-button',
- {
- type: 'danger',
- size: 'small',
- class: 'edit-btn',
- onClick: () => handleEditNode(data),
- },
- [
- hFn(Edit)
- ]
- )
- )
- }
- return hFn(
- 'div',
- { class: 'tree-node' },
- content
- )
- }
- // 加载子节点数据
- const loadNode = (node, resolve) => {
- if (!props.lazy) {
- return resolve([])
- }
- if (node.level === 0) {
- // 根节点直接返回初始数据
- return resolve(props.treeData)
- }
- // 触发父组件的事件来获取子节点数据
- emit('loadChildren', {
- node,
- resolve: (children) => {
- // 确保children是数组
- const childNodes = Array.isArray(children) ? children : []
- // 将子节点数据设置到当前节点的children属性中
- if (node.data) {
- node.data.children = childNodes
- }
- resolve(childNodes)
- }
- })
- }
- // 处理节点点击事件
- const handleNodeClick = (data, node) => {
- emit('nodeClick', data)
- }
- // 处理删除节点事件
- const handleDeleteNode = (node, data) => {
- emit('deleteNode', { node, data })
- }
- // 处理修改节点事件
- const handleEditNode = (nodeData) => {
- emit('editNode', nodeData)
- }
- // 处理节点展开
- const handleNodeExpand = (data, node) => {
- expandedKeys.value = handleNodeExpandUtil({
- data,
- node,
- expandedKeys: expandedKeys.value,
- onExpand: (data) => emit('nodeExpand', data)
- })
- }
- // 处理节点收起
- const handleNodeCollapse = (data, node) => {
- expandedKeys.value = handleNodeCollapseUtil({
- data,
- expandedKeys: expandedKeys.value,
- onCollapse: (data) => emit('nodeCollapse', data)
- })
- }
- </script>
- <style scoped>
- .tree-container {
- height: 100%;
- border: 1px solid #e4e7ed;
- padding: 10px;
- overflow: auto;
- }
- ::v-deep(.tree-node .delete-btn) {
- display: none !important;
- }
- ::v-deep(.tree-node .edit-btn) {
- display: none !important;
- }
- ::v-deep(.tree-node:hover) {
- color: skyblue;
- }
- ::v-deep(.tree-node:hover .delete-btn) {
- width: 14px;
- display: inline-block !important;
- color: red;
- margin-left: 5px;
- transform: translateY(2px);
- }
- ::v-deep(.tree-node:hover .edit-btn) {
- width: 14px;
- display: inline-block !important;
- color: rgb(17, 0, 255);
- margin-left: 5px;
- transform: translateY(2px);
- }
- .tree-header {
- border-bottom: 1px solid #e4e7ed;
- margin-bottom: 10px;
- }
- </style>
复制代码- import { nextTick } from 'vue'
- /**
- * 处理树节点展开
- * @param {Object} options 配置选项
- * @param {Object} options.data 节点数据
- * @param {Object} options.node 节点对象
- * @param {Array} options.expandedKeys 展开节点数组
- * @param {Function} options.onExpand 展开回调函数
- * @returns {Array} 更新后的展开节点数组
- */
- export const handleNodeExpand = ({
- data,
- node,
- expandedKeys,
- onExpand
- }) => {
- // 如果节点ID不在展开数组中,则添加
- if (!expandedKeys.includes(data.id)) {
- expandedKeys.push(data.id)
- }
-
- // 确保父节点也保持展开状态
- let parent = node.parent
- while (parent && parent.data && parent.data.id) {
- if (!expandedKeys.includes(parent.data.id)) {
- expandedKeys.push(parent.data.id)
- }
- parent = parent.parent
- }
-
- // 调用展开回调
- if (onExpand) {
- onExpand(data)
- }
-
- return expandedKeys
- }
- /**
- * 处理树节点收起
- * @param {Object} options 配置选项
- * @param {Object} options.data 节点数据
- * @param {Array} options.expandedKeys 展开节点数组
- * @param {Function} options.onCollapse 收起回调函数
- * @returns {Array} 更新后的展开节点数组
- */
- export const handleNodeCollapse = ({
- data,
- expandedKeys,
- onCollapse
- }) => {
- // 从展开数组中移除节点ID
- const index = expandedKeys.indexOf(data.id)
- if (index > -1) {
- expandedKeys.splice(index, 1)
- }
-
- // 调用收起回调
- if (onCollapse) {
- onCollapse(data)
- }
-
- return expandedKeys
- }
- /**
- * 处理树节点删除后的展开状态
- * @param {Object} options 配置选项
- * @param {Object} options.node 要删除的节点
- * @param {Object} options.data 节点数据
- * @param {Array} options.treeData 树数据
- * @param {Function} options.getExpandedKeys 获取展开节点的方法
- * @param {Function} options.setExpandedKeys 设置展开节点的方法
- * @param {Function} options.clearCurrentNode 清除当前选中节点的方法
- * @returns {Promise<void>}
- */
- export const handleTreeDelete = async ({
- node,
- data,
- treeData,
- getExpandedKeys,
- setExpandedKeys,
- clearCurrentNode
- }) => {
- const parent = node.parent
- const children = parent.data.children || parent.data
- const index = children.findIndex((d) => d.id === data.id)
-
- // 获取当前展开的节点
- const currentExpandedKeys = getExpandedKeys()
-
- // 删除节点
- children.splice(index, 1)
-
- // 强制刷新treeData
- treeData.value = JSON.parse(JSON.stringify(treeData.value))
-
- // 重新设置展开状态
- await nextTick()
- // 确保父节点保持展开状态
- if (parent && parent.data && parent.data.id) {
- if (!currentExpandedKeys.includes(parent.data.id)) {
- currentExpandedKeys.push(parent.data.id)
- }
- }
- clearCurrentNode()
- setExpandedKeys(currentExpandedKeys)
-
- return currentExpandedKeys
- }
复制代码 父组件使用
- import TreeView from '@/components/basicComponents/TreeView'
复制代码- <TreeView
- ref="treeViewRef"
- :treeData="treeData"
- :treeProps="customTreeProps"
- :showDelete="true"
- :lazy="true"
- :default-expanded-keys="expandedKeys"
- @nodeClick="handleNodeClick"
- @deleteNode="handleNodeDelete"
- @loadChildren="handleLoadChildren"
- @nodeExpand="handleNodeExpand"
- @nodeCollapse="handleNodeCollapse"
- />
复制代码- // 定义treeViewRef
- const treeViewRef = ref(null)
- const treeData = ref([]) //树数据
- const expandedKeys = ref([]) // 添加展开节点的key数组
- // 自定义树形配置
- const customTreeProps = {
- children: 'children', // 子节点字段名
- label: 'label', // 使用label字段作为显示文本
- isLeaf: 'isLeaf' // 是否为叶子节点字段名
- }
- const handleLoadChildren = async ({ node, resolve }) => {
- try {
- const children = await fetchTreeChildrenData(node.data.id)
- resolve(children)
- } catch (error) {
- console.error('加载子节点失败:', error)
- resolve([]) // 加载失败时返回空数组
- }
- }
- // 获取树子节点数据 懒加载 格式化数据
- const fetchTreeChildrenData = async (id = '') => {
- const { data } = await getZhuangBeiCategory( id )
- const formattedChildren = data.map(item => ({
- id: item.id,
- label: item.label, // 添加label字段
- isLeaf: item.sonNum > 0 ? false : true, // 修正isLeaf的逻辑
- children: [] // 初始化为空数组,等待后续加载
- }))
- if(id) return formattedChildren
- treeData.value = formattedChildren
- }
- //删除子节点
- const handleNodeDelete = ({node, data}) => {
- ElMessageBox.confirm(
- `<div style="text-align: center;">确定要删除【${data.label}】吗?</div>
- '提示',
- {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- }
- ).then(async() => {
- try{
- await deleteZhuangBeiCategory(data.id)
- ElMessage({ type: 'success', message: '删除成功!'})
- await handleTreeDelete({
- node,
- data,
- treeData,
- getExpandedKeys: () => treeViewRef.value.getExpandedKeys(),
- setExpandedKeys: (keys) => treeViewRef.value.setExpandedKeys(keys),
- clearCurrentNode: () => treeViewRef.value.clearCurrentNode()
- })
- }catch{
- ElMessage({ type: 'error', message: '删除失败!'})
- }
-
-
- }).catch(() => {
- // 取消了,不做处理
- })
- }
- // 处理节点展开
- const handleNodeExpand = (data) => {
- if (!expandedKeys.value.includes(data.id)) {
- expandedKeys.value.push(data.id)
- }
- }
- // 处理节点收起
- const handleNodeCollapse = (data) => {
- const index = expandedKeys.value.indexOf(data.id)
- if (index > -1) {
- expandedKeys.value.splice(index, 1)
- }
- }
- // 处理节点点击
- const handleNodeClick = (nodeData) => {
- }
复制代码
- 其他方法好比复选框,编辑不在示例,感爱好的可以去试试
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |