add fiel
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="top-right-btn" :style="style">
|
||||
<el-row>
|
||||
<el-row :gutter="0">
|
||||
<!-- 搜索框控制 -->
|
||||
<el-tooltip
|
||||
class="item"
|
||||
@@ -103,6 +103,25 @@ const isExport = () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-right-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.el-row) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tooltip) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-transfer__button) {
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
|
||||
272
src/components/TableColumnControl/INTEGRATION_GUIDE.md
Normal file
272
src/components/TableColumnControl/INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# TableColumnControl 集成指南
|
||||
|
||||
## 概述
|
||||
`TableColumnControl` 是一个通用的表格列显隐控制组件,可以为任何表格页面添加列显示/隐藏和排序功能。
|
||||
|
||||
## 集成步骤
|
||||
|
||||
### 1. 导入必要的依赖
|
||||
|
||||
```typescript
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { Menu } from '@element-plus/icons-vue'
|
||||
```
|
||||
|
||||
### 2. 定义表格列配置
|
||||
|
||||
```typescript
|
||||
// 表格列配置
|
||||
const tableColumns = [
|
||||
{ prop: 'schoolYear', label: '学年' },
|
||||
{ prop: 'schoolTerm', label: '学期' },
|
||||
{ prop: 'title', label: '标题' },
|
||||
{ prop: 'author', label: '作者' },
|
||||
{ prop: '操作', label: '操作', alwaysShow: true, fixed: true }
|
||||
]
|
||||
|
||||
// 列配置映射(用于图标,可选)
|
||||
const columnConfigMap: Record<string, { icon: any }> = {
|
||||
schoolYear: { icon: Calendar },
|
||||
schoolTerm: { icon: Clock },
|
||||
title: { icon: Document },
|
||||
author: { icon: User }
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加状态变量
|
||||
|
||||
```typescript
|
||||
const route = useRoute()
|
||||
const columnControlRef = ref<any>()
|
||||
|
||||
// 当前显示的列
|
||||
const visibleColumns = ref<string[]>([])
|
||||
// 列排序顺序
|
||||
const columnOrder = ref<string[]>([])
|
||||
```
|
||||
|
||||
### 4. 添加配置加载和保存逻辑
|
||||
|
||||
```typescript
|
||||
// 立即从 localStorage 加载配置
|
||||
const loadSavedConfig = () => {
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}`
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
const validColumns = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
|
||||
|
||||
visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns
|
||||
} catch (e) {
|
||||
console.error('解析列配置失败:', e)
|
||||
visibleColumns.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
} else {
|
||||
visibleColumns.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
|
||||
// 加载列排序配置
|
||||
const orderKey = `${storageKey}-order`
|
||||
const savedOrder = localStorage.getItem(orderKey)
|
||||
if (savedOrder) {
|
||||
try {
|
||||
const parsedOrder = JSON.parse(savedOrder)
|
||||
const validColumns = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
columnOrder.value = parsedOrder.filter((key: string) => validColumns.includes(key))
|
||||
|
||||
validColumns.forEach(key => {
|
||||
if (!columnOrder.value.includes(key)) {
|
||||
columnOrder.value.push(key)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('解析列排序失败:', e)
|
||||
columnOrder.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
} else {
|
||||
columnOrder.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
}
|
||||
|
||||
// 立即加载保存的配置
|
||||
loadSavedConfig()
|
||||
```
|
||||
|
||||
### 5. 添加排序后的表格列计算属性
|
||||
|
||||
```typescript
|
||||
// 排序后的表格列
|
||||
const sortedTableColumns = computed(() => {
|
||||
const columns = tableColumns.filter(col => {
|
||||
const key = col.prop || col.label
|
||||
return col.alwaysShow || col.fixed || visibleColumns.value.includes(key)
|
||||
})
|
||||
|
||||
if (columnOrder.value.length > 0) {
|
||||
const orderedColumns: typeof tableColumns = []
|
||||
const unorderedColumns: typeof tableColumns = []
|
||||
|
||||
columnOrder.value.forEach(key => {
|
||||
const col = columns.find(c => (c.prop || c.label) === key)
|
||||
if (col) {
|
||||
orderedColumns.push(col)
|
||||
}
|
||||
})
|
||||
|
||||
columns.forEach(col => {
|
||||
const key = col.prop || col.label
|
||||
if (!columnOrder.value.includes(key)) {
|
||||
unorderedColumns.push(col)
|
||||
}
|
||||
})
|
||||
|
||||
return [...orderedColumns, ...unorderedColumns]
|
||||
}
|
||||
|
||||
return columns
|
||||
})
|
||||
```
|
||||
|
||||
### 6. 添加列显示控制函数
|
||||
|
||||
```typescript
|
||||
// 列显示控制函数
|
||||
const checkColumnVisible = (prop: string): boolean => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
return true
|
||||
}
|
||||
return visibleColumns.value.includes(prop)
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 添加事件处理函数
|
||||
|
||||
```typescript
|
||||
// 监听列变化
|
||||
const handleColumnChange = (columns: string[]) => {
|
||||
visibleColumns.value = columns
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}`
|
||||
const selectableColumns = columns.filter(col => {
|
||||
const column = tableColumns.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
|
||||
}
|
||||
|
||||
// 监听列排序变化
|
||||
const handleColumnOrderChange = (order: string[]) => {
|
||||
columnOrder.value = order
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}-order`
|
||||
localStorage.setItem(storageKey, JSON.stringify(order))
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 在模板中添加 TableColumnControl 组件
|
||||
|
||||
在 `right-toolbar` 组件内添加:
|
||||
|
||||
```vue
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right;"
|
||||
@queryTable="getDataList">
|
||||
<TableColumnControl
|
||||
ref="columnControlRef"
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-type="default"
|
||||
trigger-circle
|
||||
@change="handleColumnChange"
|
||||
@order-change="handleColumnOrderChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</TableColumnControl>
|
||||
</right-toolbar>
|
||||
```
|
||||
|
||||
### 9. 修改表格列渲染方式
|
||||
|
||||
将原来的静态 `el-table-column` 替换为动态渲染:
|
||||
|
||||
```vue
|
||||
<el-table-column type="index" label="序号" width="60" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-for="col in sortedTableColumns" :key="col.prop || col.label">
|
||||
<el-table-column
|
||||
v-if="checkColumnVisible(col.prop || '')"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-icon><component :is="columnConfigMap[col.prop]?.icon || Calendar" /></el-icon>
|
||||
<span style="margin-left: 4px">{{ col.label }}</span>
|
||||
</template>
|
||||
<!-- 特殊列的模板可以在这里添加 -->
|
||||
<template v-if="col.prop === 'schoolTerm'" #default="scope">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ scope.row.schoolTerm === 1 ? '上学期' : scope.row.schoolTerm === 2 ? '下学期' : scope.row.schoolTerm }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<!-- 操作列内容 -->
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
### 10. 在 onMounted 中确保配置已加载
|
||||
|
||||
```typescript
|
||||
onMounted(() => {
|
||||
// 其他初始化代码...
|
||||
|
||||
// 确保配置已同步
|
||||
nextTick(() => {
|
||||
if (visibleColumns.value.length === 0) {
|
||||
loadSavedConfig()
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **列配置中的 prop 必须与 el-table-column 的 prop 属性一致**
|
||||
2. **操作列应该标记为 `alwaysShow: true` 和 `fixed: true`**
|
||||
3. **序号列(type="index")不需要添加到 tableColumns 中**
|
||||
4. **localStorage 的 key 会根据路由自动生成,确保每个页面都有独立的配置**
|
||||
|
||||
## 示例
|
||||
|
||||
参考 `src/views/stuwork/weekPlan/index.vue` 和 `src/views/stuwork/classroomhygienemonthly/index.vue` 的完整实现。
|
||||
|
||||
293
src/components/TableColumnControl/QUICK_START.md
Normal file
293
src/components/TableColumnControl/QUICK_START.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# TableColumnControl 快速开始指南
|
||||
|
||||
## 简介
|
||||
|
||||
`TableColumnControl` 是一个通用的表格列控制组件,支持:
|
||||
- ✅ 列的显示/隐藏控制
|
||||
- ✅ 列的拖拽排序
|
||||
- ✅ 配置的自动保存和恢复(基于路由)
|
||||
- ✅ 两种使用方式:自动提取或手动配置
|
||||
|
||||
## 快速集成(3 步)
|
||||
|
||||
### 步骤 1: 在页面中引入组件
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
|
||||
const tableRef = ref<TableInstance>()
|
||||
</script>
|
||||
```
|
||||
|
||||
### 步骤 2: 添加列设置按钮
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<!-- 列设置按钮(放在表格上方) -->
|
||||
<right-toolbar>
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
trigger-circle
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</TableColumnControl>
|
||||
</right-toolbar>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table ref="tableRef" :data="tableData">
|
||||
<el-table-column prop="name" label="姓名" />
|
||||
<el-table-column prop="age" label="年龄" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 步骤 3: 完成!
|
||||
|
||||
就这么简单!组件会自动:
|
||||
- 从表格中提取列配置
|
||||
- 根据当前路由自动生成存储 key
|
||||
- 保存和恢复用户的列设置
|
||||
|
||||
## 手动配置方式(可选)
|
||||
|
||||
如果你的表格列是动态生成的,或者需要自定义列配置,可以使用手动配置:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<right-toolbar>
|
||||
<TableColumnControl
|
||||
:columns="tableColumns"
|
||||
v-model="visibleColumns"
|
||||
trigger-circle
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip content="列设置" placement="top">
|
||||
<el-button circle>
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</TableColumnControl>
|
||||
</right-toolbar>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
v-for="col in sortedTableColumns"
|
||||
v-if="checkColumnVisible(col.prop || '')"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const visibleColumns = ref<string[]>([])
|
||||
const columnOrder = ref<string[]>([])
|
||||
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名' },
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'email', label: '邮箱' },
|
||||
{ prop: 'action', label: '操作', fixed: true, alwaysShow: true }
|
||||
]
|
||||
|
||||
// 检查列是否可见
|
||||
const checkColumnVisible = (prop: string): boolean => {
|
||||
if (visibleColumns.value.length === 0) return true
|
||||
return visibleColumns.value.includes(prop)
|
||||
}
|
||||
|
||||
// 排序后的列
|
||||
const sortedTableColumns = computed(() => {
|
||||
if (columnOrder.value.length === 0) return tableColumns
|
||||
|
||||
const ordered: typeof tableColumns = []
|
||||
const unordered: typeof tableColumns = []
|
||||
|
||||
columnOrder.value.forEach(key => {
|
||||
const col = tableColumns.find(c => (c.prop || c.label) === key)
|
||||
if (col) ordered.push(col)
|
||||
})
|
||||
|
||||
tableColumns.forEach(col => {
|
||||
const key = col.prop || col.label
|
||||
if (!columnOrder.value.includes(key)) {
|
||||
unordered.push(col)
|
||||
}
|
||||
})
|
||||
|
||||
return [...ordered, ...unordered]
|
||||
})
|
||||
|
||||
// 加载保存的配置
|
||||
const loadSavedConfig = () => {
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}`
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
const validColumns = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const filteredSaved = savedColumns.filter((col: string) => validColumns.includes(col))
|
||||
|
||||
if (filteredSaved.length > 0) {
|
||||
visibleColumns.value = filteredSaved
|
||||
} else {
|
||||
visibleColumns.value = validColumns
|
||||
}
|
||||
} catch (e) {
|
||||
visibleColumns.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
} else {
|
||||
visibleColumns.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
|
||||
// 加载排序
|
||||
const orderKey = `${storageKey}-order`
|
||||
const savedOrder = localStorage.getItem(orderKey)
|
||||
if (savedOrder) {
|
||||
try {
|
||||
columnOrder.value = JSON.parse(savedOrder)
|
||||
} catch (e) {
|
||||
columnOrder.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
} else {
|
||||
columnOrder.value = tableColumns
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听列变化
|
||||
const handleColumnChange = (columns: string[]) => {
|
||||
visibleColumns.value = columns
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}`
|
||||
const selectableColumns = columns.filter(col => {
|
||||
const column = tableColumns.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(storageKey, JSON.stringify(selectableColumns))
|
||||
}
|
||||
|
||||
// 监听列排序变化
|
||||
const handleColumnOrderChange = (order: string[]) => {
|
||||
columnOrder.value = order
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
const storageKey = `table-columns-${routePath}-order`
|
||||
localStorage.setItem(storageKey, JSON.stringify(order))
|
||||
}
|
||||
|
||||
// 立即加载配置
|
||||
loadSavedConfig()
|
||||
</script>
|
||||
```
|
||||
|
||||
## 使用 Composable(推荐)
|
||||
|
||||
为了简化手动配置方式,可以使用 `useTableColumnControl` composable:
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import { useTableColumnControl } from '/@/composables/useTableColumnControl'
|
||||
|
||||
const tableColumns = [
|
||||
{ prop: 'name', label: '姓名' },
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'email', label: '邮箱' },
|
||||
{ prop: 'action', label: '操作', fixed: true, alwaysShow: true }
|
||||
]
|
||||
|
||||
const {
|
||||
visibleColumns,
|
||||
columnOrder,
|
||||
checkColumnVisible,
|
||||
saveColumnConfig,
|
||||
saveColumnOrder
|
||||
} = useTableColumnControl({
|
||||
columns: tableColumns
|
||||
})
|
||||
|
||||
const sortedTableColumns = computed(() => {
|
||||
// ... 排序逻辑
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何自定义存储 key?
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
storage-key="my-custom-key"
|
||||
/>
|
||||
```
|
||||
|
||||
### Q: 如何设置始终显示的列?
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
:auto-extract-options="{
|
||||
alwaysShow: ['name', 'action']
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### Q: 如何设置默认隐藏的列?
|
||||
|
||||
```vue
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
:auto-extract-options="{
|
||||
defaultHidden: ['remark', 'description']
|
||||
}"
|
||||
/>
|
||||
```
|
||||
|
||||
### Q: 固定列会自动隐藏吗?
|
||||
|
||||
不会。使用 `fixed="left"` 或 `fixed="right"` 的列会自动标记为不可隐藏。
|
||||
|
||||
## 完整示例
|
||||
|
||||
查看 `src/views/stuwork/classroomhygienemonthly/index.vue` 了解完整的使用示例。
|
||||
|
||||
@@ -1,16 +1,65 @@
|
||||
# TableColumnControl 表格列显隐控制组件
|
||||
# TableColumnControl 表格列控制组件
|
||||
|
||||
一个通用的表格列显示/隐藏控制组件,支持动态控制表格列的显示状态。
|
||||
一个**完全通用**的表格列控制组件,支持列的显示/隐藏控制和拖拽排序,配置会自动保存到 localStorage,并在页面重新加载时自动恢复。
|
||||
|
||||
## 功能特性
|
||||
## ✨ 功能特性
|
||||
|
||||
- ✅ 动态控制表格列的显示/隐藏
|
||||
- ✅ 支持全选/全不选
|
||||
- ✅ 支持重置为默认值
|
||||
- ✅ 支持 localStorage 持久化
|
||||
- ✅ 支持固定列(不可隐藏)
|
||||
- ✅ 支持始终显示的列
|
||||
- ✅ 可自定义触发按钮样式
|
||||
- ✅ **列的显示/隐藏控制**:用户可以自定义显示哪些列
|
||||
- ✅ **列的拖拽排序**:用户可以自定义列的显示顺序
|
||||
- ✅ **自动保存和恢复**:配置自动保存到 localStorage,基于路由自动生成存储 key
|
||||
- ✅ **两种使用方式**:支持自动提取或手动配置
|
||||
- ✅ **完全通用**:可以在任何页面使用,无需额外配置
|
||||
- ✅ **固定列保护**:固定列(fixed)和始终显示的列(alwaysShow)自动不可隐藏
|
||||
|
||||
## 🚀 快速开始(3 步集成)
|
||||
|
||||
### 步骤 1: 引入组件
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
|
||||
const tableRef = ref<TableInstance>()
|
||||
</script>
|
||||
```
|
||||
|
||||
### 步骤 2: 添加列设置按钮
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<right-toolbar>
|
||||
<TableColumnControl
|
||||
:table-ref="tableRef"
|
||||
trigger-circle
|
||||
>
|
||||
<template #trigger>
|
||||
<el-tooltip content="列设置" placement="top">
|
||||
<el-button circle style="margin-left: 0;">
|
||||
<el-icon><Menu /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</TableColumnControl>
|
||||
</right-toolbar>
|
||||
|
||||
<el-table ref="tableRef" :data="tableData">
|
||||
<el-table-column prop="name" label="姓名" />
|
||||
<el-table-column prop="age" label="年龄" />
|
||||
<el-table-column label="操作" fixed="right" />
|
||||
</el-table>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 步骤 3: 完成!
|
||||
|
||||
就这么简单!组件会自动:
|
||||
- ✅ 从表格中提取列配置
|
||||
- ✅ 根据当前路由自动生成存储 key
|
||||
- ✅ 保存和恢复用户的列设置
|
||||
|
||||
> 📖 更多使用方式请查看 [快速开始指南](./QUICK_START.md)
|
||||
|
||||
## 使用方法
|
||||
|
||||
|
||||
@@ -16,28 +16,30 @@
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="列显隐设置"
|
||||
:width="dialogWidth"
|
||||
:width="props.dialogWidth"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="column-control-content">
|
||||
<div class="column-control-body">
|
||||
<el-checkbox-group v-model="checkedColumns" @change="handleColumnChange" class="column-checkbox-group">
|
||||
<div class="column-switch-group" ref="sortableContainerRef">
|
||||
<div
|
||||
v-for="column in actualColumns"
|
||||
v-for="column in sortedColumns"
|
||||
:key="column.prop || column.label"
|
||||
class="column-item"
|
||||
:data-key="column.prop || column.label"
|
||||
>
|
||||
<el-checkbox
|
||||
:label="column.prop || column.label"
|
||||
<div class="column-drag-handle">
|
||||
<el-icon class="drag-icon"><Rank /></el-icon>
|
||||
</div>
|
||||
<span class="column-label">{{ column.label }}</span>
|
||||
<el-switch
|
||||
v-model="columnVisibleMap[column.prop || column.label]"
|
||||
:disabled="!!column.fixed || column.alwaysShow"
|
||||
>
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
<!-- <el-tag v-if="column.fixed !== undefined" size="small" type="info">
|
||||
{{ column.fixed === 'left' ? '固定左侧' : column.fixed === 'right' ? '固定右侧' : '固定' }}
|
||||
</el-tag> -->
|
||||
@change="handleSwitchChange(column.prop || column.label, $event)"
|
||||
/>
|
||||
</div>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -46,11 +48,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, nextTick, useSlots, type Ref } from 'vue'
|
||||
import { Menu } from '@element-plus/icons-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Menu, Rank } from '@element-plus/icons-vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
import { useTableColumns, type ColumnConfig } from '/@/composables/useTableColumns'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
const slots = useSlots()
|
||||
const route = useRoute()
|
||||
|
||||
interface Column {
|
||||
prop?: string
|
||||
@@ -85,20 +90,40 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
triggerCircle: false,
|
||||
triggerText: '',
|
||||
triggerLink: false,
|
||||
dialogWidth: '800px'
|
||||
dialogWidth: '500px'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string[]]
|
||||
'change': [value: string[]]
|
||||
'order-change': [value: string[]]
|
||||
}>()
|
||||
|
||||
const visible = ref(false)
|
||||
const checkedColumns = ref<string[]>([])
|
||||
const columnVisibleMap = ref<Record<string, boolean>>({})
|
||||
const sortableContainerRef = ref<HTMLElement>()
|
||||
const columnOrder = ref<string[]>([])
|
||||
let sortableInstance: any = null
|
||||
|
||||
// 根据路由自动生成 storageKey(如果没有提供)
|
||||
const getStorageKey = (): string => {
|
||||
if (props.storageKey) {
|
||||
return props.storageKey
|
||||
}
|
||||
// 使用路由路径生成 key,移除开头的 / 并替换 / 为 -
|
||||
const routePath = route.path.replace(/^\//, '').replace(/\//g, '-')
|
||||
return `table-columns-${routePath}`
|
||||
}
|
||||
|
||||
// 获取排序配置的 storageKey
|
||||
const getOrderStorageKey = (): string => {
|
||||
return `${getStorageKey()}-order`
|
||||
}
|
||||
|
||||
// 如果提供了 tableRef,使用自动提取;否则使用手动配置的 columns
|
||||
const tableColumnsResult = props.tableRef
|
||||
? useTableColumns(props.tableRef, props.storageKey, props.autoExtractOptions)
|
||||
? useTableColumns(props.tableRef, getStorageKey(), props.autoExtractOptions)
|
||||
: {
|
||||
columns: computed(() => []),
|
||||
visibleColumns: computed(() => []),
|
||||
@@ -123,6 +148,41 @@ const actualColumns = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
// 排序后的列配置(排除序号和操作列)
|
||||
const sortedColumns = computed(() => {
|
||||
const columns = actualColumns.value.filter(col => {
|
||||
const key = col.prop || col.label
|
||||
// 排除序号列(type === 'index')和操作列(固定右侧或 alwaysShow)
|
||||
return col.type !== 'index' && !(col.fixed === 'right' && col.alwaysShow) && key !== '操作'
|
||||
})
|
||||
|
||||
// 如果有保存的排序顺序,按照顺序排序
|
||||
if (columnOrder.value.length > 0) {
|
||||
const orderedColumns: ColumnConfig[] = []
|
||||
const unorderedColumns: ColumnConfig[] = []
|
||||
|
||||
// 先按照保存的顺序添加列
|
||||
columnOrder.value.forEach(key => {
|
||||
const col = columns.find(c => (c.prop || c.label) === key)
|
||||
if (col) {
|
||||
orderedColumns.push(col)
|
||||
}
|
||||
})
|
||||
|
||||
// 添加未在排序列表中的列(新增的列)
|
||||
columns.forEach(col => {
|
||||
const key = col.prop || col.label
|
||||
if (!columnOrder.value.includes(key)) {
|
||||
unorderedColumns.push(col)
|
||||
}
|
||||
})
|
||||
|
||||
return [...orderedColumns, ...unorderedColumns]
|
||||
}
|
||||
|
||||
return columns
|
||||
})
|
||||
|
||||
// 获取所有列(包括固定列和 alwaysShow 列)
|
||||
const getAllColumns = (): string[] => {
|
||||
return actualColumns.value.map(col => col.prop || col.label)
|
||||
@@ -130,6 +190,37 @@ const getAllColumns = (): string[] => {
|
||||
|
||||
// 初始化选中的列
|
||||
const initCheckedColumns = () => {
|
||||
// 如果 actualColumns 为空,无法初始化
|
||||
if (actualColumns.value.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// 优先从 localStorage 读取配置
|
||||
const storageKey = getStorageKey()
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
const allSelectableColumns = actualColumns.value
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
|
||||
// 使用保存的列配置(即使数量少于所有列,也使用保存的配置)
|
||||
// 但确保包含固定列和 alwaysShow 列
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...savedColumns, ...fixedAndAlwaysShow])]
|
||||
// 同步到外部 modelValue
|
||||
emit('update:modelValue', checkedColumns.value)
|
||||
return // 已从 localStorage 加载,直接返回
|
||||
} catch (e) {
|
||||
// 如果解析失败,继续使用其他方式初始化
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 localStorage 中没有保存的配置,使用其他方式初始化
|
||||
if (props.modelValue && props.modelValue.length > 0) {
|
||||
checkedColumns.value = [...props.modelValue]
|
||||
} else if (props.tableRef && autoVisibleColumns.value.length > 0) {
|
||||
@@ -151,60 +242,118 @@ const initCheckedColumns = () => {
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...autoVisibleColumns.value, ...fixedAndAlwaysShow])]
|
||||
}
|
||||
} else if (props.storageKey) {
|
||||
// 从 localStorage 读取
|
||||
const saved = localStorage.getItem(props.storageKey)
|
||||
if (saved) {
|
||||
try {
|
||||
const savedColumns = JSON.parse(saved)
|
||||
const allSelectableColumns = actualColumns.value
|
||||
.filter(col => !col.alwaysShow && !col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
|
||||
// 如果保存的列数量少于所有列,默认全部选中
|
||||
if (savedColumns.length < allSelectableColumns.length) {
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...allSelectableColumns, ...fixedAndAlwaysShow])]
|
||||
} else {
|
||||
// 使用保存的列,但确保包含固定列和 alwaysShow 列
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
checkedColumns.value = [...new Set([...savedColumns, ...fixedAndAlwaysShow])]
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果解析失败,使用默认值(所有列)
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
} else {
|
||||
// 首次使用,默认全部选中
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
} else {
|
||||
// 没有 storageKey,默认全部选中
|
||||
checkedColumns.value = getAllColumns()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 actualColumns 变化,更新选中状态
|
||||
watch(actualColumns, (newColumns) => {
|
||||
if (newColumns.length > 0 && checkedColumns.value.length === 0) {
|
||||
// 如果列数据已加载但选中列表为空,初始化选中所有列
|
||||
initCheckedColumns()
|
||||
} else if (newColumns.length > 0) {
|
||||
// 确保固定列和 alwaysShow 列始终在选中列表中
|
||||
const fixedAndAlwaysShow = newColumns
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const currentChecked = checkedColumns.value
|
||||
const missingFixed = fixedAndAlwaysShow.filter(col => !currentChecked.includes(col))
|
||||
if (missingFixed.length > 0) {
|
||||
checkedColumns.value = [...currentChecked, ...missingFixed]
|
||||
// 初始化列可见性映射
|
||||
const initColumnVisibleMap = () => {
|
||||
const map: Record<string, boolean> = {}
|
||||
actualColumns.value.forEach(col => {
|
||||
const key = col.prop || col.label
|
||||
// 固定列和 alwaysShow 列始终为 true
|
||||
if (col.fixed || col.alwaysShow) {
|
||||
map[key] = true
|
||||
} else {
|
||||
// 其他列根据 checkedColumns 判断
|
||||
map[key] = checkedColumns.value.includes(key)
|
||||
}
|
||||
})
|
||||
columnVisibleMap.value = map
|
||||
}
|
||||
|
||||
// 初始化列排序顺序
|
||||
const initColumnOrder = () => {
|
||||
const storageKey = getOrderStorageKey()
|
||||
|
||||
// 先获取所有可排序的列(不依赖 sortedColumns computed)
|
||||
const allSortableColumns = actualColumns.value.filter(col => {
|
||||
const key = col.prop || col.label
|
||||
return col.type !== 'index' && !(col.fixed === 'right' && col.alwaysShow) && key !== '操作'
|
||||
})
|
||||
const validColumns = allSortableColumns.map(col => col.prop || col.label)
|
||||
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
if (saved) {
|
||||
try {
|
||||
const savedOrder = JSON.parse(saved)
|
||||
// 验证保存的列是否仍然存在
|
||||
columnOrder.value = savedOrder.filter((key: string) => validColumns.includes(key))
|
||||
|
||||
// 添加新列到排序列表末尾
|
||||
validColumns.forEach(key => {
|
||||
if (!columnOrder.value.includes(key)) {
|
||||
columnOrder.value.push(key)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
// 如果解析失败,使用默认顺序
|
||||
columnOrder.value = [...validColumns]
|
||||
}
|
||||
} else {
|
||||
// 首次使用,使用默认顺序
|
||||
columnOrder.value = [...validColumns]
|
||||
}
|
||||
}
|
||||
|
||||
// 保存列排序顺序
|
||||
const saveColumnOrder = () => {
|
||||
const storageKey = getOrderStorageKey()
|
||||
if (columnOrder.value.length > 0) {
|
||||
localStorage.setItem(storageKey, JSON.stringify(columnOrder.value))
|
||||
}
|
||||
}, { deep: true })
|
||||
}
|
||||
|
||||
// 初始化拖拽排序
|
||||
const initSortable = () => {
|
||||
if (!sortableContainerRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 销毁旧的实例
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy()
|
||||
sortableInstance = null
|
||||
}
|
||||
|
||||
// 等待 DOM 更新完成
|
||||
nextTick(() => {
|
||||
if (!sortableContainerRef.value) return
|
||||
|
||||
// 创建新的拖拽实例(整行可拖拽)
|
||||
sortableInstance = Sortable.create(sortableContainerRef.value, {
|
||||
animation: 150,
|
||||
ghostClass: 'column-item-ghost',
|
||||
dragClass: 'column-item-drag',
|
||||
forceFallback: false,
|
||||
filter: '.el-switch', // 排除开关元素,避免拖拽时触发开关
|
||||
preventOnFilter: false,
|
||||
onEnd: (evt) => {
|
||||
const { newIndex, oldIndex } = evt
|
||||
if (newIndex !== undefined && oldIndex !== undefined && newIndex !== oldIndex) {
|
||||
// 获取当前排序后的列
|
||||
const currentOrder = sortedColumns.value.map(col => col.prop || col.label)
|
||||
|
||||
// 更新列顺序
|
||||
const movedKey = currentOrder[oldIndex]
|
||||
currentOrder.splice(oldIndex, 1)
|
||||
currentOrder.splice(newIndex, 0, movedKey)
|
||||
|
||||
// 更新 columnOrder
|
||||
columnOrder.value = currentOrder
|
||||
|
||||
// 保存排序顺序
|
||||
saveColumnOrder()
|
||||
|
||||
// 触发 change 事件,通知父组件列顺序已改变
|
||||
emit('order-change', [...columnOrder.value])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 监听弹窗打开,触发列配置重新提取
|
||||
watch(visible, (newVal) => {
|
||||
@@ -225,6 +374,22 @@ watch(visible, (newVal) => {
|
||||
checkedColumns.value = [...currentChecked, ...missingFixed]
|
||||
}
|
||||
}
|
||||
// 更新列可见性映射
|
||||
initColumnVisibleMap()
|
||||
// 初始化列排序顺序(只在 columnOrder 为空时初始化,避免覆盖已保存的配置)
|
||||
if (columnOrder.value.length === 0) {
|
||||
initColumnOrder()
|
||||
}
|
||||
// 初始化拖拽排序
|
||||
nextTick(() => {
|
||||
initSortable()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 弹窗关闭时销毁拖拽实例
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy()
|
||||
sortableInstance = null
|
||||
}
|
||||
}
|
||||
if (newVal && props.tableRef) {
|
||||
@@ -345,7 +510,51 @@ defineExpose({
|
||||
refreshColumns: refreshAutoColumns
|
||||
})
|
||||
|
||||
// 列变化处理(实时生效)
|
||||
// 开关变化处理
|
||||
const handleSwitchChange = (columnKey: string, value: boolean) => {
|
||||
// 更新 columnVisibleMap
|
||||
columnVisibleMap.value[columnKey] = value
|
||||
|
||||
// 更新 checkedColumns
|
||||
if (value) {
|
||||
// 开启:添加到选中列表
|
||||
if (!checkedColumns.value.includes(columnKey)) {
|
||||
checkedColumns.value.push(columnKey)
|
||||
}
|
||||
} else {
|
||||
// 关闭:从选中列表移除
|
||||
checkedColumns.value = checkedColumns.value.filter(col => col !== columnKey)
|
||||
}
|
||||
|
||||
// 确保固定列和 alwaysShow 列始终在选中列表中
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
.filter(col => col.alwaysShow || !!col.fixed)
|
||||
.map(col => col.prop || col.label)
|
||||
const finalValue = [...new Set([...checkedColumns.value, ...fixedAndAlwaysShow])]
|
||||
|
||||
// 立即应用更改
|
||||
emit('update:modelValue', finalValue)
|
||||
emit('change', finalValue)
|
||||
|
||||
// 如果使用自动提取,同步更新(只更新可选择的列)
|
||||
if (props.tableRef) {
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
updateAutoVisibleColumns(selectableValue)
|
||||
} else {
|
||||
// 保存到 localStorage(只保存可选择的列)
|
||||
const storageKey = getStorageKey()
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(storageKey, JSON.stringify(selectableValue))
|
||||
}
|
||||
}
|
||||
|
||||
// 列变化处理(实时生效)- 保留用于兼容性
|
||||
const handleColumnChange = (value: string[]) => {
|
||||
// 确保固定列和 alwaysShow 列始终在选中列表中
|
||||
const fixedAndAlwaysShow = actualColumns.value
|
||||
@@ -366,26 +575,64 @@ const handleColumnChange = (value: string[]) => {
|
||||
updateAutoVisibleColumns(selectableValue)
|
||||
} else {
|
||||
// 保存到 localStorage(只保存可选择的列)
|
||||
if (props.storageKey) {
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(props.storageKey, JSON.stringify(selectableValue))
|
||||
}
|
||||
const storageKey = getStorageKey()
|
||||
const selectableValue = finalValue.filter(col => {
|
||||
const column = actualColumns.value.find(c => (c.prop || c.label) === col)
|
||||
return column && !column.alwaysShow && !column.fixed
|
||||
})
|
||||
localStorage.setItem(storageKey, JSON.stringify(selectableValue))
|
||||
}
|
||||
}
|
||||
|
||||
// 监听外部 modelValue 变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
// 只有在外部传入的值与当前值不同时才更新,避免覆盖已加载的配置
|
||||
if (newVal && newVal.length > 0) {
|
||||
checkedColumns.value = [...newVal]
|
||||
const currentSorted = [...checkedColumns.value].sort()
|
||||
const newSorted = [...newVal].sort()
|
||||
if (JSON.stringify(currentSorted) !== JSON.stringify(newSorted)) {
|
||||
// 检查是否是从 localStorage 加载的配置
|
||||
const storageKey = getStorageKey()
|
||||
const saved = localStorage.getItem(storageKey)
|
||||
// 如果 checkedColumns 为空,或者没有保存的配置,才使用外部传入的值
|
||||
if (checkedColumns.value.length === 0 || !saved) {
|
||||
checkedColumns.value = [...newVal]
|
||||
initColumnVisibleMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
}, { immediate: false })
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
initCheckedColumns()
|
||||
// 等待 actualColumns 准备好
|
||||
nextTick(() => {
|
||||
// 优先从 localStorage 加载配置
|
||||
if (actualColumns.value.length > 0) {
|
||||
initCheckedColumns()
|
||||
initColumnVisibleMap()
|
||||
initColumnOrder()
|
||||
|
||||
// 如果外部传入了 modelValue,且 checkedColumns 为空,才使用外部传入的值
|
||||
if (props.modelValue && props.modelValue.length > 0 && checkedColumns.value.length === 0) {
|
||||
checkedColumns.value = [...props.modelValue]
|
||||
initColumnVisibleMap()
|
||||
}
|
||||
} else {
|
||||
// 如果 actualColumns 为空,等待它准备好
|
||||
// 使用 nextTick 确保所有函数都已定义
|
||||
nextTick(() => {
|
||||
const unwatch = watch(actualColumns, (newColumns) => {
|
||||
if (newColumns.length > 0) {
|
||||
initCheckedColumns()
|
||||
initColumnVisibleMap()
|
||||
initColumnOrder()
|
||||
unwatch() // 停止监听
|
||||
}
|
||||
}, { immediate: true })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -396,31 +643,84 @@ onMounted(() => {
|
||||
|
||||
.column-control-content {
|
||||
.column-control-body {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.column-checkbox-group {
|
||||
.column-switch-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 16px;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 120px;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
cursor: move;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
&.sortable-ghost {
|
||||
opacity: 0.5;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
&.sortable-drag {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.column-drag-handle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
pointer-events: none; // 让图标不阻止拖拽
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
padding-left: 8px;
|
||||
.drag-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.column-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none; // 让文本不阻止拖拽
|
||||
}
|
||||
|
||||
// 开关元素可以正常交互
|
||||
.el-switch {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.column-item-ghost {
|
||||
opacity: 0.5;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
|
||||
.column-item-drag {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user