在vue3中完成表格可以展开,无限嵌套表格,展开箭头到下层表格之间有引导线,给出完整代码
时间: 2025-08-05 20:21:18 浏览: 5
<think>根据之前的代码和用户要求,我们创建一个完整的Vue3组件,使用ElementPlus的el-table,实现无限嵌套表格,并带有引导线。关键点:1.使用树形数据结构,通过递归组件或扁平化数据处理来渲染表格。2.每个节点有展开/折叠图标,点击图标可以展开或折叠子节点。3.在展开图标和子节点之间有引导线(连接线)来展示层级关系。由于用户强调“完整代码”,我们将提供一个独立的Vue单文件组件,包括模板、脚本和样式。注意:我们将使用CSS伪元素来绘制引导线,并根据层级计算位置。以下是完整代码:</think>
# Vue3 无限嵌套表格组件(带引导线)
下面是一个完整的 Vue3 无限嵌套表格实现,包含展开箭头、引导线和层级关系展示。这个实现基于 Element Plus 表格组件,并添加了自定义样式和交互逻辑。
```vue
<template>
<div class="tree-table-container">
<el-table
:data="flattenedData"
row-key="id"
style="width: 100%"
:row-class-name="setRowClass"
:row-style="setRowStyle"
>
<el-table-column prop="name" label="节点名称" min-width="250">
<template #default="{ row }">
<div class="node-container" :style="nodeIndentStyle(row)">
<!-- 展开/折叠图标 -->
<div
v-if="hasChildren(row)"
class="expand-icon"
@click="toggleExpand(row)"
>
<el-icon v-if="row.expanded" color="#409EFF" size="18">
<ArrowDownBold />
</el-icon>
<el-icon v-else color="#909399" size="18">
<ArrowRightBold />
</el-icon>
</div>
<div v-else class="placeholder-icon"></div>
<!-- 引导线 -->
<div v-if="row.level > 0" class="guide-lines">
<div
v-for="i in row.level"
:key="i"
class="guide-line"
:style="guideLineStyle(i)"
></div>
</div>
<!-- 节点内容 -->
<div class="node-content">
<div class="node-name">{{ row.name }}</div>
<div class="node-meta">
<span v-if="hasChildren(row)" class="children-count">
{{ row.expanded ? '展开中' : '折叠中' }} ({{ row.children.length }})
</span>
<el-tag v-if="row.type" size="small" :type="nodeTypeTag(row.type)">
{{ row.type }}
</el-tag>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" width="300">
<template #default="{ row }">
{{ row.description || '无描述信息' }}
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="120" align="right">
<template #default="{ row }">
{{ row.size || 'N/A' }}
</template>
</el-table-column>
</el-table>
<div class="controls">
<el-button size="small" @click="expandAll">
<el-icon><Expand /></el-icon> 展开所有
</el-button>
<el-button size="small" @click="collapseAll">
<el-icon><Fold /></el-icon> 折叠所有
</el-button>
<el-button size="small" @click="addRootNode" type="success">
<el-icon><Plus /></el-icon> 添加根节点
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import {
ArrowDownBold,
ArrowRightBold,
Expand,
Fold,
Plus
} from '@element-plus/icons-vue';
// 示例树形数据生成
const generateTreeData = (depth = 4, level = 0, parentId = null) => {
const types = ['folder', 'document', 'image', 'video'];
const descriptions = [
'主要目录结构',
'重要配置文件',
'系统图片资源',
'用户上传内容'
];
if (depth <= 0) return [];
const nodes = [];
const nodeCount = level === 0 ? 3 : Math.max(1, 4 - level);
for (let i = 1; i <= nodeCount; i++) {
const id = parentId ? `${parentId}-${i}` : `node-${level}-${i}`;
const type = level === 0 ? 'folder' :
depth === 1 ? types[Math.floor(Math.random() * 3) + 1] : 'folder';
const node = {
id,
name: `${type === 'folder' ? '📁' : ''} ${id}`,
type,
description: descriptions[level % descriptions.length],
size: type === 'folder' ? `${Math.pow(2, 4 - level) * 10} MB` :
type === 'image' ? `${Math.floor(Math.random() * 5) + 1} MB` :
`${Math.floor(Math.random() * 100)} KB`,
level,
expanded: level < 2,
children: [],
parentId
};
// 添加子节点
if (depth > 1 && type === 'folder') {
node.children = generateTreeData(depth - 1, level + 1, id);
}
nodes.push(node);
}
return nodes;
};
// 树形数据源
const treeData = ref([]);
// 初始化数据
onMounted(() => {
treeData.value = generateTreeData();
});
// 扁平化数据(用于表格渲染)
const flattenedData = computed(() => {
const flat = [];
const flatten = (nodes) => {
for (const node of nodes) {
flat.push(node);
if (node.expanded && node.children && node.children.length) {
flatten(node.children);
}
}
};
flatten(treeData.value);
return flat;
});
// 检查节点是否有子节点
const hasChildren = (row) => {
return row.children && row.children.length;
};
// 切换节点展开状态
const toggleExpand = (row) => {
row.expanded = !row.expanded;
};
// 展开所有节点
const expandAll = () => {
const expand = (nodes) => {
for (const node of nodes) {
node.expanded = true;
if (node.children && node.children.length) {
expand(node.children);
}
}
};
expand(treeData.value);
};
// 折叠所有节点
const collapseAll = () => {
const collapse = (nodes) => {
for (const node of nodes) {
node.expanded = false;
if (node.children && node.children.length) {
collapse(node.children);
}
}
};
collapse(treeData.value);
};
// 添加新的根节点
const addRootNode = () => {
const newCount = treeData.value.filter(n => n.parentId === null).length + 1;
treeData.value.push({
id: `root-${newCount}`,
name: `📁 新增根节点 ${newCount}`,
type: 'folder',
description: '新添加的根节点',
size: '0 KB',
level: 0,
expanded: true,
children: [],
parentId: null
});
};
// 设置行类名(用于样式处理)
const setRowClass = ({ row }) => {
const classes = [`level-${row.level}`];
if (hasChildren(row)) classes.push('has-children');
if (row.expanded) classes.push('expanded');
return classes.join(' ');
};
// 设置行样式(高度随内容变化)
const setRowStyle = ({ row }) => {
return {
height: row.expanded ? '60px' : '45px',
transition: 'height 0.3s ease'
};
};
// 设置节点缩进样式
const nodeIndentStyle = (row) => {
return {
paddingLeft: `${row.level * 20}px`
};
};
// 设置引导线样式
const guideLineStyle = (levelIndex) => {
// 最后一个层级不需要水平线
const isLastLevel = levelIndex === 1;
return {
left: `${(levelIndex - 1) * 20}px`,
height: isLastLevel ? 'calc(100% - 25px)' : '100%',
borderTop: isLastLevel ? 'none' : '1px solid #c0c4cc',
borderLeft: '1px solid #c0c4cc'
};
};
// 节点类型标签样式
const nodeTypeTag = (type) => {
const typeMap = {
folder: '',
document: 'info',
image: 'success',
video: 'warning'
};
return typeMap[type] || '';
};
</script>
<style scoped>
.tree-table-container {
padding: 20px;
background-color: #f8fafc;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
max-width: 1200px;
margin: 20px auto;
}
.node-container {
display: flex;
align-items: center;
position: relative;
min-height: 40px;
padding: 8px 0;
transition: all 0.3s;
}
.expand-icon {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
margin-right: 8px;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s;
z-index: 2;
flex-shrink: 0;
}
.expand-icon:hover {
background-color: #e6f4ff;
transform: scale(1.1);
}
.placeholder-icon {
width: 28px;
height: 28px;
margin-right: 8px;
flex-shrink: 0;
}
.guide-lines {
position: absolute;
top: 0;
bottom: 0;
left: 14px;
right: 0;
z-index: 0;
pointer-events: none;
}
.guide-line {
position: absolute;
top: 0;
width: 1px;
height: 100%;
border-left: 1px solid #e0e0e0;
}
.node-content {
z-index: 1;
background: rgba(255, 255, 255, 0.8);
padding: 0 8px;
border-radius: 4px;
flex-grow: 1;
}
.node-name {
font-weight: 600;
color: #333;
font-size: 15px;
margin-bottom: 4px;
}
.node-meta {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: #666;
}
.children-count {
background-color: #f0f7ff;
color: #409eff;
padding: 2px 8px;
border-radius: 10px;
}
/* 悬停效果 */
:deep(.el-table__row:hover) {
background-color: #f5f7fa !important;
}
:deep(.el-table__row:hover) .node-name {
color: #409eff;
}
:deep(.el-table__row:hover) .guide-line {
border-color: #409eff;
}
:deep(.el-table__row:hover) .expand-icon {
background-color: #e6f4ff;
}
/* 引导线动画 */
:deep(.el-table__row) {
transition: background-color 0.3s ease;
}
/* 折叠/展开动画 */
.node-enter-active, .node-leave-active {
transition: all 0.3s ease;
}
.node-enter-from, .node-leave-to {
opacity: 0;
transform: translateY(-10px);
}
.controls {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
```
## 实现功能说明
### 核心功能
1. **无限嵌套结构**:支持任意层级的表格嵌套
2. **展开/折叠功能**:点击箭头图标切换子节点的显示状态
3. **层级引导线**:直观展示节点之间的层级关系
4. **动画效果**:展开/折叠时的平滑过渡动画
5. **完整控制**:提供展开所有、折叠所有、添加根节点等控制功能
### 代码核心结构
1. **数据生成**
```javascript
// 生成树形结构数据
const generateTreeData = (depth, level, parentId) => {
// 创建带层级的节点
const node = {
id: unique_id,
name: '节点名称',
type: '节点类型',
level: current_level,
expanded: is_expanded, // 控制展开状态
children: [] // 子节点数组
};
// 递归生成子节点
if (depth > 1) {
node.children = generateTreeData(depth - 1, level + 1, node.id);
}
});
```
2. **扁平化处理**
```javascript
// 将树形数据转换为扁平结构
const flattenedData = computed(() => {
const flat = [];
const flatten = (nodes) => {
for (const node of nodes) {
flat.push(node);
if (node.expanded && node.children) {
flatten(node.children);
}
}
};
flatten(treeData.value);
return flat;
});
```
3. **引导线实现**
```vue
<!-- 引导线实现 -->
<div class="guide-lines">
<div
v-for="i in row.level"
:key="i"
class="guide-line"
:style="{
left: `${(i-1) * 20}px`,
height: i === 1 ? 'calc(100% - 25px)' : '100%'
}">
</div>
</div>
```
4. **交互控制**
```javascript
// 展开/折叠所有节点
const expandAll = () => {
const expand = (nodes) => {
for (const node of nodes) {
node.expanded = true;
if (node.children) expand(node.children);
}
};
expand(treeData.value);
};
```
## 使用说明
1. 将代码复制到 Vue3 项目中
2. 确保已安装 Element Plus 和图标库:
```bash
npm install element-plus @element-plus/icons-vue
```
3. 在组件中导入 Element Plus 样式
4. 替换 `generateTreeData` 方法为您的实际数据源
## 效果展示
这个实现会产生一个具有以下特点的表格:
- 树形层级结构,支持任意深度嵌套
- 每个层级都有清晰的视觉引导线
- 展开/折叠有平滑动画效果
- 节点类型区分显示(文件夹、文档等)
- 提供全局控制按钮(展开所有、折叠所有)
- 响应式设计,适应不同屏幕尺寸
阅读全文
相关推荐


















