table.vue
<template>
<div class="drill-table">
<!-- 过滤区域 -->
<div class="filter-area">
<el-input
v-model="filters.keyword"
placeholder="搜索关键词"
class="filter-input"
@input="handleFilterChange"
>
<template #append>
<el-button @click="handleFilterChange">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
<el-select
v-model="filters.status"
placeholder="状态过滤"
class="filter-select"
@change="handleFilterChange"
>
<el-option label="全部" value=""></el-option>
<el-option label="活跃" value="active"></el-option>
<el-option label="待处理" value="pending"></el-option>
<el-option label="完成" value="completed"></el-option>
</el-select>
</div>
<!-- 数据表格 -->
<el-table
:data="currentData"
style="width: 100%"
row-key="id"
border
v-loading="loading"
>
<el-table-column
v-for="column in columns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
>
<template #default="scope">
<!-- 根据列的 drillable 属性决定是否可以下钻 -->
<div
:class="{'drillable': column.drillable}"
@click="column.drillable ? handleDrillDown(scope.row, column) : null"
>
{{ scope.row[column.prop] }}
<el-icon v-if="column.drillable" class="drill-icon">
<Right />
</el-icon>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页控件 -->
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</template>
<script setup>
import { computed, ref, watch, onMounted } from 'vue';
import { Search, Right } from '@element-plus/icons-vue';
import { useDrillStore } from '@/store/modules/drillStore';
import {
aboveStatistics,
tableHead,
statisticsList,
dateDifference,
weekInfo,
deptTree,
list
} from "@/api/main_sys/overview/index";
const drillStore = useDrillStore();
const loading = ref(false);
// 从store获取状态
const currentData = computed(() => drillStore.currentState.tableData);
const filters = computed(() => drillStore.currentState.filters);
const pagination = computed(() => drillStore.currentState.pagination);
// 列配置 (任何列都可设置为可下钻)
const columns = ref([
{ prop: 'name', label: '名称', drillable: true },
{ prop: 'category', label: '类别', drillable: true },
{ prop: 'status', label: '状态', drillable: true },
{ prop: 'value', label: '数值', drillable: false },
{ prop: 'date', label: '日期', drillable: true }
]);
// 处理下钻操作
const handleDrillDown = (row, column) => {
drillStore.drillDown({
title: `${column.label}: ${row[column.prop]}`,
dataRef: row.id
});
// 模拟获取新一级数据
fetchData();
};
// 处理过滤变化
const handleFilterChange = () => {
drillStore.updateFilters(filters.value);
drillStore.updatePagination({
page: 1,
pageSize: 10
});
fetchData();
};
// 处理分页大小变化
const handleSizeChange = (size) => {
drillStore.updatePagination({
...pagination.value,
page: 1,
pageSize: size
});
fetchData();
};
// 处理页码变化
const handlePageChange = (page) => {
drillStore.updatePagination({
...pagination.value,
page: page
});
fetchData();
};
// 模拟API获取数据
const fetchData = async () => {
loading.value = true;
// 实际项目中替换为真实的API调用
try {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 500));
const mockData = generateMockData(
pagination.value.page,
pagination.value.pageSize,
drillStore.currentState.level,
filters.value
);
drillStore.updateTableData(mockData.rows);
drillStore.updatePagination({
...pagination.value,
total: mockData.total
});
} finally {
loading.value = false;
}
};
// 监听层级变化
watch(() => drillStore.currentState.level, (newLevel) => {
if (newLevel !== 0 && drillStore.historyStack.length === 0) {
// 刚进入时的初始加载
fetchData();
}
});
// 初始化数据
onMounted(() => {
// 设置初始路径
if (drillStore.currentState.drillPath.length === 0) {
drillStore.drillDown({
title: '首页',
dataRef: '',
level: 0
});
}
fetchData();
});
// 模拟数据生成函数
const generateMockData = (page, pageSize, level, filters) => {
const statuses = ['active', 'pending', 'completed'];
const categories = ['A', 'B', 'C', 'D', 'E'];
const startIndex = (page - 1) * pageSize;
const data = [];
for (let i = 1; i <= pageSize; i++) {
const index = startIndex + i;
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
// 应用过滤器
if (filters.status && filters.status !== randomStatus) {
continue;
}
// 应用关键词过滤器
if (filters.keyword && !`${level}-${index}`.includes(filters.keyword)) {
continue;
}
data.push({
id: `${level}-${index}`,
name: `项目 ${level}-${index}`,
category: categories[Math.floor(Math.random() * categories.length)],
status: randomStatus,
value: Math.floor(Math.random() * 1000),
date: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toLocaleDateString()
});
}
// 总数据量模拟(实际应用中来自API)
const total = level * 1000 + 500 - Math.floor(Math.random() * 100);
return {
rows: data,
total: total > data.length ? total : data.length
};
};
</script>
<style scoped>
.drill-table {
padding: 15px;
}
.filter-area {
display: flex;
margin-bottom: 15px;
gap: 15px;
}
.filter-input {
width: 300px;
}
.el-pagination {
margin-top: 20px;
justify-content: flex-end;
}
.drillable {
cursor: pointer;
color: #409eff;
display: flex;
justify-content: space-between;
align-items: center;
}
.drillable:hover {
text-decoration: underline;
}
.drill-icon {
margin-left: 5px;
}
</style>
bre.vue
<template>
<el-breadcrumb separator="/" class="breadcrumb">
<el-breadcrumb-item
v-for="(item, index) in drillPath"
:key="item.level"
:class="{ 'current-level': index === drillPath.length - 1 }"
@click="goBack(item.level)"
>
{{ item.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { computed } from 'vue';
import { useDrillStore } from '@/store/modules/drillStore';
const drillStore = useDrillStore();
const drillPath = computed(() => drillStore.currentState.drillPath);
const goBack = (level) => {
drillStore.goBack(level);
};
</script>
<style scoped>
.breadcrumb {
margin-bottom: 20px;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
}
.el-breadcrumb-item {
cursor: pointer;
}
.el-breadcrumb-item:not(.current-level):hover {
color: #409eff;
text-decoration: underline;
}
.current-level {
color: #909399;
cursor: default;
}
</style>
store/modules/drillStore.js
import { defineStore } from 'pinia';
export const useDrillStore = defineStore('drill', {
state: () => ({
historyStack: [], // 保存历史状态
currentState: {
level: 0, // 当前层级
tableData: [], // 当前表格数据
filters: {},
pagination: {
page: 1,
pageSize: 10,
total: 0
},
drillPath: [] // 下钻路径 [{level: 0, title: '首页', dataRef: ''}]
}
}),
actions: {
// 保存当前状态并推进历史堆栈
saveState() {
this.historyStack.push({
...JSON.parse(JSON.stringify(this.currentState))
});
},
// 下钻操作
drillDown(payload) {
this.saveState();
// 更新当前状态
this.currentState.level = payload.level || this.currentState.level + 1;
this.currentState.drillPath.push({
level: this.currentState.level,
title: payload.title,
dataRef: payload.dataRef
});
// 重置为初始状态(保留钻取级别)
this.currentState.filters = { };
this.currentState.pagination = {
page: 1,
pageSize: 10,
total: 0
};
},
// 返回操作
goBack(toLevel) {
if (this.historyStack.length === 0) return;
// 查找目标状态
const targetState = this.historyStack.find(state => state.level === toLevel);
if (targetState) {
// 应用状态
this.currentState = targetState;
// 清理历史堆栈中更深的状态
this.historyStack = this.historyStack.filter(
state => state.level <= toLevel
);
// 更新钻取路径
this.currentState.drillPath = this.currentState.drillPath.filter(
path => path.level <= toLevel
);
}
},
// 更新表格数据
updateTableData(data) {
this.currentState.tableData = data;
},
// 更新分页信息
updatePagination(pagination) {
this.currentState.pagination = { ...pagination };
},
// 更新过滤条件
updateFilters(filters) {
this.currentState.filters = { ...filters };
}
}
});
页面使用
<template>
<div class="container">
<el-card>
<!-- 面包屑导航 -->
<BreadcrumbNav />
<!-- 数据表格 -->
<DrillTable />
</el-card>
</div>
</template>
<script setup>
import BreadcrumbNav from '@/components/bre.vue';
import DrillTable from '@/components/demo/table.vue';
onMounted(async () => {
})
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
</style>
需求截图
返回第一页
数据是自己mock的 可以替换为接口!