This commit is contained in:
guochunsi
2026-01-06 16:39:57 +08:00
parent 7f91263205
commit 9e583c3c30
32 changed files with 2830 additions and 481 deletions

View File

@@ -0,0 +1,411 @@
import { ref, computed, watch, onMounted, nextTick, type Ref } from 'vue'
import type { TableInstance } from 'element-plus'
export interface ColumnConfig {
prop?: string
label: string
width?: number | string
minWidth?: number | string
fixed?: boolean | 'left' | 'right'
alwaysShow?: boolean
[key: string]: any
}
/**
* 自动从 el-table 提取列配置的 composable
* @param tableRef el-table 的 ref
* @param storageKey localStorage 存储的 key用于持久化
* @param options 额外配置选项
*/
export function useTableColumns(
tableRef: Ref<TableInstance | undefined> | any, // 支持多种类型的 ref
storageKey?: string,
options?: {
// 默认隐藏的列prop 或 label
defaultHidden?: string[]
// 始终显示的列prop 或 label
alwaysShow?: string[]
// 列配置映射(用于自定义列的显示名称等)
columnMap?: Record<string, Partial<ColumnConfig>>
}
) {
const columns = ref<ColumnConfig[]>([])
const visibleColumns = ref<string[]>([])
// 获取表格实例的辅助函数
const getTableInstance = (): TableInstance | null => {
// 方法1: 直接从 tableRef.value 获取
if (tableRef?.value) {
return tableRef.value
}
// 方法2: 如果 tableRef 本身有 value 属性(可能是直接的 ref 对象)
if ((tableRef as any).value) {
return (tableRef as any).value
}
// 方法3: 如果 tableRef 本身就是表格实例(不应该发生,但作为备用)
if ((tableRef as any).$el) {
return tableRef as any
}
return null
}
// 从 el-table 提取列配置
const extractColumns = (): ColumnConfig[] => {
console.log('[useTableColumns] extractColumns 开始执行')
const tableInstance = getTableInstance()
if (!tableInstance) {
console.warn('[useTableColumns] tableRef.value 为空')
console.warn('[useTableColumns] tableRef:', tableRef)
console.warn('[useTableColumns] tableRef?.value:', tableRef?.value)
return []
}
const table = tableInstance
console.log('[useTableColumns] tableInstance 存在:', table)
const extracted: ColumnConfig[] = []
try {
// 方法1: 从 table.store 中提取Element Plus 内部实现)
const store = (table as any).store
console.log('[useTableColumns] store:', store)
if (store) {
// 尝试多种路径访问列数据
let tableColumns: any[] = []
if (store.states && store.states.columns) {
tableColumns = store.states.columns.value || []
} else if (store.columns) {
tableColumns = Array.isArray(store.columns) ? store.columns : (store.columns.value || [])
} else if ((table as any).columns) {
tableColumns = Array.isArray((table as any).columns) ? (table as any).columns : ((table as any).columns.value || [])
}
if (tableColumns.length > 0) {
tableColumns.forEach((col: any) => {
// 跳过序号列和没有 label 的列
if (!col.label || col.type === 'index') return
const config: ColumnConfig = {
prop: col.property || col.prop || '',
label: col.label || '',
width: col.width,
minWidth: col.minWidth,
fixed: col.fixed,
}
// 应用自定义映射
if (options?.columnMap && config.prop) {
const mapped = options.columnMap[config.prop]
if (mapped) {
Object.assign(config, mapped)
}
}
// 应用 alwaysShow 配置
if (options?.alwaysShow) {
const key = config.prop || config.label
if (key && options.alwaysShow.includes(key)) {
config.alwaysShow = true
}
}
extracted.push(config)
})
if (extracted.length > 0) {
return extracted
}
}
}
// 方法2: 从 DOM 中提取(备用方案,更可靠)
const tableEl = (table as any).$el
console.log('[useTableColumns] tableEl:', tableEl)
if (tableEl) {
// 尝试多种选择器来查找表头
let columnHeaders: NodeListOf<HTMLElement> | null = null
// 方法2.1: 从主表格头部查找
const headerWrapper = tableEl.querySelector('.el-table__header-wrapper')
console.log('[useTableColumns] headerWrapper:', headerWrapper)
if (headerWrapper) {
columnHeaders = headerWrapper.querySelectorAll('th')
console.log('[useTableColumns] 从 headerWrapper 找到列数:', columnHeaders?.length || 0)
}
// 方法2.2: 如果找不到,从整个表格查找
if (!columnHeaders || columnHeaders.length === 0) {
columnHeaders = tableEl.querySelectorAll('th')
console.log('[useTableColumns] 从整个表格找到列数:', columnHeaders?.length || 0)
}
// 方法2.3: 如果还是找不到,尝试查找固定列
if (!columnHeaders || columnHeaders.length === 0) {
const fixedLeft = tableEl.querySelector('.el-table__fixed-left')
console.log('[useTableColumns] fixedLeft:', fixedLeft)
if (fixedLeft) {
columnHeaders = fixedLeft.querySelectorAll('th')
console.log('[useTableColumns] 从 fixedLeft 找到列数:', columnHeaders?.length || 0)
}
}
if (!columnHeaders || columnHeaders.length === 0) {
console.warn('[useTableColumns] 未找到任何列头元素')
return []
}
console.log('[useTableColumns] 最终找到列数量:', columnHeaders.length)
// 创建一个映射,通过列索引匹配 prop
const propMap = new Map<number, string>()
// 尝试从表格的 slot 或配置中获取 prop
// 通过检查表格的列定义来匹配
if (store && store.states && store.states.columns) {
const tableColumns = store.states.columns.value || []
tableColumns.forEach((col: any, idx: number) => {
if (col.property || col.prop) {
propMap.set(idx, col.property || col.prop)
}
})
}
columnHeaders.forEach((th: HTMLElement, index: number) => {
const label = th.textContent?.trim() || ''
console.log(`[useTableColumns] 列 ${index}: label="${label}"`)
// 排除序号列和空列
if (!label || label === '序号') {
console.log(`[useTableColumns] 列 ${index} 被跳过(序号列或空列)`)
return
}
// 检查是否是固定列
let fixed: 'left' | 'right' | undefined
const thParent = th.closest('table')
if (thParent) {
if (thParent.classList.contains('el-table__fixed-left') || th.closest('.el-table__fixed-left')) {
fixed = 'left'
} else if (thParent.classList.contains('el-table__fixed-right') || th.closest('.el-table__fixed-right')) {
fixed = 'right'
} else if (th.classList.contains('is-left')) {
fixed = 'left'
} else if (th.classList.contains('is-right')) {
fixed = 'right'
}
}
// 尝试从属性中获取宽度
const widthAttr = th.getAttribute('width') || th.style.width
let width: number | string | undefined
if (widthAttr) {
const numWidth = parseInt(widthAttr)
width = isNaN(numWidth) ? widthAttr : numWidth
}
// 尝试从对应的 body cell 中获取 data-key 或其他属性来匹配 prop
let prop = propMap.get(index)
// 如果没有找到 prop尝试从 label 推断(简单匹配)
if (!prop) {
// 对于常见的列,可以通过 label 推断 prop
const labelToPropMap: Record<string, string> = {
'是否退休': 'tied',
'姓名/工号': 'nameNo',
'性别': 'sex',
'部门': 'deptName',
'学历学位': 'dgreeName',
'职称等级': 'professionalTitle',
'岗位级别': 'stationLevelName',
'职业资格等级': 'levelName',
'职业资格工种': 'workName',
'用工性质': 'employmentNatureName',
'手机': 'telPhone',
'家庭住址': 'homeAddress',
'授课类型': 'teacherCate',
'操作': 'action',
}
prop = labelToPropMap[label]
}
const columnConfig = {
prop: prop || `column_${index}`,
label,
width,
fixed,
}
console.log(`[useTableColumns] 提取到列配置:`, columnConfig)
extracted.push(columnConfig)
})
console.log('[useTableColumns] 总共提取到列数:', extracted.length)
if (extracted.length > 0) {
console.log('[useTableColumns] 提取到的所有列:', extracted)
return extracted
}
}
} catch (error) {
// 提取失败,输出错误信息
console.error('[useTableColumns] 提取列配置时出错:', error)
}
console.warn('[useTableColumns] 提取失败,返回空数组')
return []
}
// 初始化列配置
const initColumns = async () => {
// 等待多个渲染周期,确保表格完全渲染
await nextTick()
await new Promise(resolve => setTimeout(resolve, 300))
await nextTick()
let extracted = extractColumns()
// 如果第一次提取失败,多次重试
if (extracted.length === 0) {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 200))
extracted = extractColumns()
if (extracted.length > 0) break
}
}
if (extracted.length > 0) {
columns.value = extracted
// 初始化可见列
if (storageKey) {
const saved = localStorage.getItem(storageKey)
if (saved) {
try {
visibleColumns.value = JSON.parse(saved)
// 验证保存的列是否仍然存在
const validColumns = columns.value
.filter(col => !col.alwaysShow && col.fixed === undefined)
.map(col => col.prop || col.label)
visibleColumns.value = visibleColumns.value.filter(col => validColumns.includes(col))
} catch (e) {
initDefaultVisibleColumns()
}
} else {
initDefaultVisibleColumns()
}
} else {
initDefaultVisibleColumns()
}
} else {
console.warn('[useTableColumns] initColumns: 提取失败,未设置 columns.value')
}
}
// 初始化默认可见列
const initDefaultVisibleColumns = () => {
const defaultHidden = options?.defaultHidden || []
// 默认显示所有列(除了默认隐藏的列)
visibleColumns.value = columns.value
.filter(col => {
const key = col.prop || col.label
return !col.alwaysShow &&
col.fixed === undefined &&
!defaultHidden.includes(key)
})
.map(col => col.prop || col.label)
// 如果所有列都被隐藏了,至少显示所有非固定列
if (visibleColumns.value.length === 0 && columns.value.length > 0) {
visibleColumns.value = columns.value
.filter(col => !col.alwaysShow && col.fixed === undefined)
.map(col => col.prop || col.label)
}
}
// 判断列是否可见
const isColumnVisible = (propOrLabel: string): boolean => {
// 如果列配置还没有提取完成,默认显示所有列(避免初始渲染时所有列被隐藏)
if (columns.value.length === 0) {
return true
}
const column = columns.value.find(col =>
(col.prop || col.label) === propOrLabel
)
// 如果找不到对应的列配置,默认显示(可能是新添加的列)
if (!column) {
return true
}
// 固定列和始终显示的列始终显示
if (column.fixed !== undefined || column.alwaysShow) {
return true
}
// 如果可见列列表为空,默认显示所有列(初始状态)
if (visibleColumns.value.length === 0) {
return true
}
// 检查是否在可见列列表中
return visibleColumns.value.includes(propOrLabel)
}
// 更新可见列
const updateVisibleColumns = (newColumns: string[]) => {
visibleColumns.value = newColumns
if (storageKey) {
localStorage.setItem(storageKey, JSON.stringify(newColumns))
}
}
// 监听表格变化,重新提取列配置
watch(() => {
// 尝试多种方式获取 tableRef.value
if (tableRef?.value) {
return tableRef.value
}
if ((tableRef as any).value) {
return (tableRef as any).value
}
return null
}, (newVal, oldVal) => {
if (newVal && newVal !== oldVal) {
console.log('[useTableColumns] tableRef.value 变化,开始提取列配置')
// 延迟一下,确保表格已经渲染
setTimeout(() => {
initColumns()
}, 200)
} else if (newVal && !oldVal) {
// 如果从 undefined 变为有值,也触发提取
console.log('[useTableColumns] tableRef.value 从 undefined 变为有值,开始提取列配置')
setTimeout(() => {
initColumns()
}, 200)
}
}, { immediate: true }) // 改为 true立即检查一次
// 组件挂载后初始化
onMounted(() => {
// 延迟初始化,确保表格已经渲染
// 如果 tableRef.value 已经有值,立即初始化;否则等待 watch 触发
const tableInstance = getTableInstance()
if (tableInstance) {
setTimeout(() => {
initColumns()
}, 300)
} else {
console.log('[useTableColumns] onMounted: tableRef.value 还未就绪,等待 watch 触发')
}
})
return {
columns: computed(() => columns.value),
visibleColumns: computed(() => visibleColumns.value),
isColumnVisible,
updateVisibleColumns,
refreshColumns: initColumns,
}
}