mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-11-03 02:57:09 +08:00
feat(GiTable): 增强列设置功能
This commit is contained in:
@@ -33,35 +33,14 @@
|
|||||||
</a-doption>
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<a-popover
|
<ColumnSetting
|
||||||
v-if="showSettingColumnBtn" trigger="click" position="br"
|
v-if="showSettingColumnBtn"
|
||||||
:content-style="{ minWidth: '120px', padding: '6px 8px 10px' }"
|
ref="columnSettingRef"
|
||||||
>
|
v-model:columns="innerColumns"
|
||||||
<a-tooltip content="列设置">
|
:disabled-keys="disabledColumnKeys"
|
||||||
<a-button>
|
:table-id="tableId"
|
||||||
<template #icon>
|
@visible-columns-change="handleVisibleColumnsChange"
|
||||||
<icon-settings />
|
/>
|
||||||
</template>
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<template #content>
|
|
||||||
<div class="gi-table__draggable">
|
|
||||||
<VueDraggable v-model="settingColumnList">
|
|
||||||
<div v-for="item in settingColumnList" :key="item.title" class="drag-item">
|
|
||||||
<div class="drag-item__move"><icon-drag-dot-vertical /></div>
|
|
||||||
<a-checkbox v-model:model-value="item.show" :disabled="item.disabled">{{ item.title }}</a-checkbox>
|
|
||||||
</div>
|
|
||||||
</VueDraggable>
|
|
||||||
</div>
|
|
||||||
<a-divider :margin="6" />
|
|
||||||
<a-row justify="center">
|
|
||||||
<a-button type="primary" size="mini" long @click="resetSettingColumns">
|
|
||||||
<template #icon><icon-refresh /></template>
|
|
||||||
<template #default>重置</template>
|
|
||||||
</a-button>
|
|
||||||
</a-row>
|
|
||||||
</template>
|
|
||||||
</a-popover>
|
|
||||||
<a-tooltip content="全屏">
|
<a-tooltip content="全屏">
|
||||||
<a-button v-if="showFullscreenBtn" @click="toggleFullscreen">
|
<a-button v-if="showFullscreenBtn" @click="toggleFullscreen">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@@ -100,9 +79,9 @@
|
|||||||
<script setup lang="ts" generic="T extends TableData">
|
<script setup lang="ts" generic="T extends TableData">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import type { DropdownInstance, TableColumnData, TableData, TableInstance } from '@arco-design/web-vue'
|
import type { DropdownInstance, TableColumnData, TableData, TableInstance } from '@arco-design/web-vue'
|
||||||
import { VueDraggable } from 'vue-draggable-plus'
|
|
||||||
import { omit } from 'lodash-es'
|
import { omit } from 'lodash-es'
|
||||||
import type { TableProps } from './type'
|
import type { TableProps } from './type'
|
||||||
|
import ColumnSetting from './components/ColumnSetting.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'GiTable' })
|
defineOptions({ name: 'GiTable' })
|
||||||
|
|
||||||
@@ -117,6 +96,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
/** Emits 类型定义 */
|
/** Emits 类型定义 */
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'refresh'): void
|
(e: 'refresh'): void
|
||||||
|
(e: 'update:columns', columns: TableColumnData[]): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/** Slots 类型定义 */
|
/** Slots 类型定义 */
|
||||||
@@ -153,19 +133,16 @@ interface Props extends TableProps {
|
|||||||
disabledTools?: string[]
|
disabledTools?: string[]
|
||||||
/** 表格数据 */
|
/** 表格数据 */
|
||||||
data: T[]
|
data: T[]
|
||||||
|
/** 表格标识,用于存储列设置 */
|
||||||
|
tableId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
const attrs = useAttrs()
|
const attrs = useAttrs()
|
||||||
|
|
||||||
/** 表格属性计算 */
|
|
||||||
const tableProps = computed(() => ({
|
|
||||||
...omit(props, ['title', 'disabledColumnKeys', 'disabledTools']),
|
|
||||||
...attrs,
|
|
||||||
}))
|
|
||||||
|
|
||||||
/** 组件状态 */
|
/** 组件状态 */
|
||||||
const tableRef = useTemplateRef('tableRef')
|
const tableRef = useTemplateRef('tableRef')
|
||||||
|
const columnSettingRef = ref<InstanceType<typeof ColumnSetting> | null>(null)
|
||||||
const stripe = ref(false)
|
const stripe = ref(false)
|
||||||
const size = ref<TableInstance['size']>('large')
|
const size = ref<TableInstance['size']>('large')
|
||||||
const isBordered = ref(false)
|
const isBordered = ref(false)
|
||||||
@@ -205,66 +182,46 @@ const showSettingColumnBtn = computed(() => {
|
|||||||
return !props.disabledTools?.includes('setting') && Boolean(columns?.length)
|
return !props.disabledTools?.includes('setting') && Boolean(columns?.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 列设置项类型 */
|
/** 内部维护列数据 */
|
||||||
interface SettingColumnItem {
|
const innerColumns = ref<TableColumnData[]>([])
|
||||||
/** 列标题 */
|
|
||||||
title: string
|
|
||||||
/** 列标识 */
|
|
||||||
key: string
|
|
||||||
/** 是否显示 */
|
|
||||||
show: boolean
|
|
||||||
/** 是否禁用 */
|
|
||||||
disabled: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const settingColumnList = ref<SettingColumnItem[]>([])
|
/** 监听 props.columns 变化 */
|
||||||
|
watch(() => props.columns, (newColumns) => {
|
||||||
/** 重置列设置 */
|
if (newColumns && innerColumns.value.length === 0) {
|
||||||
const resetSettingColumns = () => {
|
innerColumns.value = [...newColumns]
|
||||||
if (!props.columns) {
|
|
||||||
settingColumnList.value = []
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
const columns = props.columns as TableColumnData[]
|
/** 实际显示的列(由ColumnSetting组件计算) */
|
||||||
settingColumnList.value = columns.map((column) => {
|
const tableColumns = ref<TableColumnData[]>([])
|
||||||
const key = column.dataIndex || (typeof column.title === 'string' ? column.title : '')
|
|
||||||
return {
|
/** 处理列设置组件的可见列变化 */
|
||||||
key,
|
const handleVisibleColumnsChange = (columns: TableColumnData[]) => {
|
||||||
title: typeof column.title === 'string' ? column.title : '',
|
tableColumns.value = columns
|
||||||
show: column.show ?? true,
|
|
||||||
disabled: props.disabledColumnKeys.includes(key),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听属性变化,重置列设置 */
|
/** 表格属性计算 */
|
||||||
watch(
|
const tableProps = computed(() => ({
|
||||||
() => props.columns,
|
...omit(props, ['title', 'disabledColumnKeys', 'disabledTools']),
|
||||||
() => resetSettingColumns(),
|
...attrs,
|
||||||
{ immediate: true },
|
}))
|
||||||
)
|
|
||||||
|
|
||||||
/** 计算显示的列 */
|
/** 计算显示的列 */
|
||||||
const visibleColumns = computed(() => {
|
const visibleColumns = computed(() => {
|
||||||
if (!props.columns) return []
|
// 如果tableColumns有值,使用tableColumns
|
||||||
|
if (tableColumns.value && tableColumns.value.length > 0) {
|
||||||
|
return tableColumns.value
|
||||||
|
}
|
||||||
|
|
||||||
const columns = props.columns as TableColumnData[]
|
// 否则使用原始的columns
|
||||||
const columnMap = new Map(
|
return props.columns?.filter((col) => col.show !== false) || []
|
||||||
columns.map((col) => [
|
|
||||||
col.dataIndex || (typeof col.title === 'string' ? col.title : ''),
|
|
||||||
col,
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 按照设置列表的顺序返回可见列
|
|
||||||
return settingColumnList.value
|
|
||||||
.filter((item) => item.show)
|
|
||||||
.map((item) => columnMap.get(item.key))
|
|
||||||
.filter(Boolean) as TableColumnData[]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({ tableRef })
|
defineExpose({
|
||||||
|
tableRef,
|
||||||
|
resetColumns: () => columnSettingRef.value?.resetColumns?.(),
|
||||||
|
saveColumns: () => columnSettingRef.value?.saveColumns?.(),
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
548
src/components/GiTable/src/components/ColumnSetting.vue
Normal file
548
src/components/GiTable/src/components/ColumnSetting.vue
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
<template>
|
||||||
|
<a-popover
|
||||||
|
v-model:open="popoverVisible"
|
||||||
|
trigger="click"
|
||||||
|
position="br"
|
||||||
|
:content-style="{ minWidth: '240px', padding: '6px 8px 10px' }"
|
||||||
|
@open="handleOpen"
|
||||||
|
>
|
||||||
|
<a-button>
|
||||||
|
<template #icon><icon-settings /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #content>
|
||||||
|
<!-- 顶部控制区域 -->
|
||||||
|
<div class="gi-table__setting-header">
|
||||||
|
<div class="gi-table__setting-select-all">
|
||||||
|
<a-checkbox
|
||||||
|
:model-value="isAllSelected"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@change="handleSelectAll"
|
||||||
|
>
|
||||||
|
全选
|
||||||
|
</a-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="gi-table__setting-reset">
|
||||||
|
<a-link @click="handleReset">重置</a-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-divider :margin="6" />
|
||||||
|
|
||||||
|
<!-- 列拖拽排序区域 -->
|
||||||
|
<div class="gi-table__draggable">
|
||||||
|
<VueDraggable v-model="localColumns" @end="handleDragEnd">
|
||||||
|
<div
|
||||||
|
v-for="item in localColumns"
|
||||||
|
:key="item.key"
|
||||||
|
class="gi-table__draggable-item"
|
||||||
|
>
|
||||||
|
<div class="gi-table__draggable-item-move">
|
||||||
|
<icon-drag-dot-vertical />
|
||||||
|
</div>
|
||||||
|
<a-checkbox
|
||||||
|
v-model="item.show"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
@change="() => handleColumnChange(item)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</a-checkbox>
|
||||||
|
<div class="gi-table__draggable-item-fixed">
|
||||||
|
<span
|
||||||
|
class="gi-table__fixed-icon"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'gi-table__fixed-icon--active': item.fixed === 'left',
|
||||||
|
'gi-table__fixed-icon--disabled': !item.show,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@click="handleFixedColumn(item, 'left')"
|
||||||
|
>
|
||||||
|
<icon-left />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="gi-table__fixed-icon"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'gi-table__fixed-icon--active': item.fixed === 'right',
|
||||||
|
'gi-table__fixed-icon--disabled': !item.show,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@click="handleFixedColumn(item, 'right')"
|
||||||
|
>
|
||||||
|
<icon-right />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VueDraggable>
|
||||||
|
</div>
|
||||||
|
<a-divider :margin="6" />
|
||||||
|
|
||||||
|
<!-- 底部保存按钮 -->
|
||||||
|
<a-row justify="center">
|
||||||
|
<a-button type="primary" long @click="handleSave">
|
||||||
|
保存
|
||||||
|
</a-button>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { VueDraggable } from 'vue-draggable-plus'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import type { TableColumnData } from '@arco-design/web-vue'
|
||||||
|
|
||||||
|
interface ColumnItem {
|
||||||
|
title: string
|
||||||
|
key: string
|
||||||
|
dataIndex?: string
|
||||||
|
show: boolean
|
||||||
|
disabled: boolean
|
||||||
|
fixed?: 'left' | 'right'
|
||||||
|
width?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
columns: TableColumnData[]
|
||||||
|
disabledKeys: string[]
|
||||||
|
tableId?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:columns', columns: TableColumnData[]): void
|
||||||
|
(e: 'visible-columns-change', columns: TableColumnData[]): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const popoverVisible = ref(false)
|
||||||
|
const localColumns = ref<ColumnItem[]>([])
|
||||||
|
// 保存原始列配置
|
||||||
|
const originalColumns = ref<TableColumnData[]>([])
|
||||||
|
|
||||||
|
// 提取列排序逻辑
|
||||||
|
const sortColumnsByLocalOrder = (columns: TableColumnData[]) => {
|
||||||
|
const keyOrderMap = new Map(
|
||||||
|
localColumns.value.map((col, index) => [col.key, index]),
|
||||||
|
)
|
||||||
|
|
||||||
|
return columns.sort((a, b) => {
|
||||||
|
const keyA = a.dataIndex || (typeof a.title === 'string' ? a.title : '')
|
||||||
|
const keyB = b.dataIndex || (typeof b.title === 'string' ? b.title : '')
|
||||||
|
const orderA = keyOrderMap.get(keyA) ?? 999
|
||||||
|
const orderB = keyOrderMap.get(keyB) ?? 999
|
||||||
|
return orderA - orderB
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新列变化
|
||||||
|
const emitColumnsChange = () => {
|
||||||
|
// 将当前列设置应用到原始列
|
||||||
|
const updatedColumns = props.columns.map((originalCol) => {
|
||||||
|
const key = originalCol.dataIndex || (typeof originalCol.title === 'string' ? originalCol.title : '')
|
||||||
|
const localCol = localColumns.value.find((col) => col.key === key)
|
||||||
|
|
||||||
|
if (localCol) {
|
||||||
|
return {
|
||||||
|
...originalCol,
|
||||||
|
show: localCol.show,
|
||||||
|
fixed: localCol.fixed,
|
||||||
|
width: localCol.width || originalCol.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalCol
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据拖拽后的顺序重新排序列
|
||||||
|
const sortedColumns = sortColumnsByLocalOrder(updatedColumns)
|
||||||
|
|
||||||
|
emit('update:columns', sortedColumns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存可选列
|
||||||
|
const selectableColumns = computed(() => {
|
||||||
|
return localColumns.value.filter((col) => !col.disabled)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算全选状态
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
if (selectableColumns.value.length === 0) return false
|
||||||
|
return selectableColumns.value.every((col) => col.show)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算半选状态
|
||||||
|
const isIndeterminate = computed(() => {
|
||||||
|
if (selectableColumns.value.length === 0) return false
|
||||||
|
const selectedCount = selectableColumns.value.filter((col) => col.show).length
|
||||||
|
return selectedCount > 0 && selectedCount < selectableColumns.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 全选/取消全选处理
|
||||||
|
const handleSelectAll = (checked: boolean) => {
|
||||||
|
selectableColumns.value.forEach((col) => {
|
||||||
|
col.show = checked
|
||||||
|
if (!checked) {
|
||||||
|
col.fixed = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
emitColumnsChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取存储键 */
|
||||||
|
const getStorageKey = computed(() => {
|
||||||
|
const pathKey = route.path.replace(/\//g, ':')
|
||||||
|
return props.tableId
|
||||||
|
? `table-columns-settings-${pathKey}:${props.tableId}`
|
||||||
|
: `table-columns-settings-${pathKey}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算可见列
|
||||||
|
const visibleColumns = computed(() => {
|
||||||
|
// 首先根据 show 属性过滤要显示的列
|
||||||
|
const showColumns = props.columns
|
||||||
|
.filter((col) => {
|
||||||
|
const key = col.dataIndex || (typeof col.title === 'string' ? col.title : '')
|
||||||
|
const localCol = localColumns.value.find((item) => item.key === key)
|
||||||
|
return localCol?.show !== false
|
||||||
|
})
|
||||||
|
.map((col) => {
|
||||||
|
const key = col.dataIndex || (typeof col.title === 'string' ? col.title : '')
|
||||||
|
const localCol = localColumns.value.find((item) => item.key === key)
|
||||||
|
|
||||||
|
if (localCol) {
|
||||||
|
return {
|
||||||
|
...col,
|
||||||
|
fixed: localCol.fixed,
|
||||||
|
width: localCol.width || col.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
})
|
||||||
|
|
||||||
|
// 根据拖拽后的顺序重新排序列
|
||||||
|
const sortedShowColumns = sortColumnsByLocalOrder(showColumns)
|
||||||
|
|
||||||
|
// 然后根据固定状态进行排序(左固定 -> 未固定 -> 右固定)
|
||||||
|
const leftFixedColumns = sortedShowColumns.filter((col) => col.fixed === 'left')
|
||||||
|
const unfixedColumns = sortedShowColumns.filter((col) => !col.fixed)
|
||||||
|
const rightFixedColumns = sortedShowColumns.filter((col) => col.fixed === 'right')
|
||||||
|
|
||||||
|
// 返回排序后的列
|
||||||
|
return [...leftFixedColumns, ...unfixedColumns, ...rightFixedColumns]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听本地列变化,发出可见列变化事件
|
||||||
|
watch([localColumns], () => {
|
||||||
|
if (localColumns.value.length > 0) {
|
||||||
|
emit('visible-columns-change', visibleColumns.value)
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
// 将列转换为本地列格式
|
||||||
|
const transformColumns = (columns = props.columns) => {
|
||||||
|
if (!columns || columns.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按固定位置分类列
|
||||||
|
const leftColumns: ColumnItem[] = []
|
||||||
|
const centerColumns: ColumnItem[] = []
|
||||||
|
const rightColumns: ColumnItem[] = []
|
||||||
|
|
||||||
|
columns.forEach((column) => {
|
||||||
|
const key = column.dataIndex || (typeof column.title === 'string' ? column.title : '')
|
||||||
|
const item: ColumnItem = {
|
||||||
|
key,
|
||||||
|
dataIndex: column.dataIndex as string,
|
||||||
|
title: typeof column.title === 'string' ? column.title : String(key),
|
||||||
|
show: column.show ?? true,
|
||||||
|
disabled: props.disabledKeys.includes(key),
|
||||||
|
fixed: typeof column.fixed === 'boolean' ? 'left' : column.fixed as 'left' | 'right' | undefined,
|
||||||
|
width: column.width as number,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.fixed === 'left') {
|
||||||
|
leftColumns.push(item)
|
||||||
|
} else if (item.fixed === 'right') {
|
||||||
|
rightColumns.push(item)
|
||||||
|
} else {
|
||||||
|
centerColumns.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组合列表:左固定 + 中间 + 右固定
|
||||||
|
return [...leftColumns, ...centerColumns, ...rightColumns]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从localStorage恢复设置
|
||||||
|
const loadSettingsFromStorage = () => {
|
||||||
|
try {
|
||||||
|
const settingsJson = localStorage.getItem(getStorageKey.value)
|
||||||
|
if (!settingsJson) return false
|
||||||
|
|
||||||
|
const settings = JSON.parse(settingsJson)
|
||||||
|
if (!settings || !settings.columns || !Array.isArray(settings.columns)) return false
|
||||||
|
|
||||||
|
// 获取原始列
|
||||||
|
const columnsMap = new Map(
|
||||||
|
props.columns.map((col) => [
|
||||||
|
col.dataIndex || (typeof col.title === 'string' ? col.title : ''),
|
||||||
|
col,
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 按固定位置分类列
|
||||||
|
const leftColumns: ColumnItem[] = []
|
||||||
|
const centerColumns: ColumnItem[] = []
|
||||||
|
const rightColumns: ColumnItem[] = []
|
||||||
|
|
||||||
|
settings.columns.forEach((item: ColumnItem) => {
|
||||||
|
const originalColumn = columnsMap.get(item.key)
|
||||||
|
if (originalColumn) {
|
||||||
|
const newItem: ColumnItem = {
|
||||||
|
...item,
|
||||||
|
title: typeof originalColumn.title === 'string' ? originalColumn.title : String(item.key),
|
||||||
|
dataIndex: originalColumn.dataIndex as string,
|
||||||
|
disabled: props.disabledKeys.includes(item.key),
|
||||||
|
width: item.width || originalColumn.width as number,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newItem.fixed === 'left') {
|
||||||
|
leftColumns.push(newItem)
|
||||||
|
} else if (newItem.fixed === 'right') {
|
||||||
|
rightColumns.push(newItem)
|
||||||
|
} else {
|
||||||
|
centerColumns.push(newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组合列表:左固定 + 中间 + 右固定
|
||||||
|
localColumns.value = [...leftColumns, ...centerColumns, ...rightColumns]
|
||||||
|
|
||||||
|
// 检查是否有新增的列,将它们添加到末尾
|
||||||
|
const existingKeys = new Set(localColumns.value.map((col) => col.key))
|
||||||
|
const newColumns = props.columns
|
||||||
|
.filter((col) => {
|
||||||
|
const key = col.dataIndex || (typeof col.title === 'string' ? col.title : '')
|
||||||
|
return !existingKeys.has(key)
|
||||||
|
})
|
||||||
|
.map((col) => {
|
||||||
|
const key = col.dataIndex || (typeof col.title === 'string' ? col.title : '')
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
dataIndex: col.dataIndex as string,
|
||||||
|
title: typeof col.title === 'string' ? col.title : String(key),
|
||||||
|
show: col.show ?? true,
|
||||||
|
disabled: props.disabledKeys.includes(key),
|
||||||
|
fixed: typeof col.fixed === 'boolean' ? 'left' : col.fixed as 'left' | 'right' | undefined,
|
||||||
|
width: col.width as number,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newColumns.length > 0) {
|
||||||
|
localColumns.value = [...localColumns.value, ...newColumns]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送更新事件
|
||||||
|
emitColumnsChange()
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load column settings from localStorage', e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化列表和原始列配置
|
||||||
|
watch(() => props.columns, (newColumns) => {
|
||||||
|
if (newColumns && newColumns.length > 0) {
|
||||||
|
// 第一次接收到非空列数据时,保存原始配置
|
||||||
|
if (originalColumns.value.length === 0) {
|
||||||
|
originalColumns.value = JSON.parse(JSON.stringify(newColumns))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果本地列数组为空,初始化本地列
|
||||||
|
if (localColumns.value.length === 0) {
|
||||||
|
localColumns.value = transformColumns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 处理popover打开事件
|
||||||
|
const handleOpen = () => {
|
||||||
|
if (!localColumns.value.length) {
|
||||||
|
localColumns.value = transformColumns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理拖拽结束
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
emitColumnsChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理单列变更
|
||||||
|
const handleColumnChange = (item: ColumnItem) => {
|
||||||
|
// 如果是隐藏列,取消固定
|
||||||
|
if (!item.show) {
|
||||||
|
item.fixed = undefined
|
||||||
|
}
|
||||||
|
emitColumnsChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理列固定
|
||||||
|
const handleFixedColumn = (item: ColumnItem, position: 'left' | 'right') => {
|
||||||
|
// 如果列没有显示,则不能固定
|
||||||
|
if (!item.show) return
|
||||||
|
|
||||||
|
// 检查列是否已经固定在当前位置,是则取消固定
|
||||||
|
const isCurrentlyFixed = item.fixed === position
|
||||||
|
|
||||||
|
// 只修改当前列的固定状态,不改变位置
|
||||||
|
item.fixed = isCurrentlyFixed ? undefined : position
|
||||||
|
|
||||||
|
// 如果被固定但没有宽度,设置默认宽度100
|
||||||
|
if (item.fixed && !item.width) {
|
||||||
|
item.width = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发出列变更事件
|
||||||
|
emitColumnsChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
try {
|
||||||
|
// 清除本地存储
|
||||||
|
localStorage.removeItem(getStorageKey.value)
|
||||||
|
|
||||||
|
// 使用原始列配置重新创建本地列
|
||||||
|
if (originalColumns.value.length > 0) {
|
||||||
|
// 将原始列配置重新应用(深拷贝避免共享引用)
|
||||||
|
const columnsToReset = JSON.parse(JSON.stringify(originalColumns.value))
|
||||||
|
|
||||||
|
// 使用父组件传入的列更新内部状态
|
||||||
|
emit('update:columns', columnsToReset)
|
||||||
|
|
||||||
|
// 更新本地列配置
|
||||||
|
localColumns.value = transformColumns(columnsToReset)
|
||||||
|
} else {
|
||||||
|
// 如果没有原始配置,则使用当前props
|
||||||
|
localColumns.value = transformColumns()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送更新事件
|
||||||
|
emitColumnsChange()
|
||||||
|
|
||||||
|
// 显示成功消息
|
||||||
|
Message.success('已重置表格列')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to reset column settings', e)
|
||||||
|
Message.error('重置表格列失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!getStorageKey.value) return
|
||||||
|
try {
|
||||||
|
const settings = {
|
||||||
|
columns: localColumns.value,
|
||||||
|
}
|
||||||
|
localStorage.setItem(getStorageKey.value, JSON.stringify(settings))
|
||||||
|
popoverVisible.value = false
|
||||||
|
Message.success('保存成功')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to save column settings', e)
|
||||||
|
Message.error('保存失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化时加载存储设置
|
||||||
|
onMounted(() => {
|
||||||
|
loadSettingsFromStorage()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 导出供外部使用
|
||||||
|
defineExpose({
|
||||||
|
visibleColumns,
|
||||||
|
resetColumns: handleReset,
|
||||||
|
saveColumns: handleSave,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.gi-table {
|
||||||
|
&__setting-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&-select-all {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-reset {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__draggable {
|
||||||
|
padding: 1px 0;
|
||||||
|
max-height: 250px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__draggable-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-move {
|
||||||
|
padding: 0 4px;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__fixed-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid var(--color-border-2);
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: rgb(var(--primary-6));
|
||||||
|
background-color: rgb(var(--primary-1));
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(&--disabled) {
|
||||||
|
border-color: rgb(var(--primary-6));
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user