Files
jordium-gantt-vue3/src/components/TaskList/composables/useTaskListColumns.ts
2025-12-16 18:28:19 +08:00

194 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { computed, type VNode, type Slots, type VNodeChild, type ComputedRef } from 'vue'
import type { Task } from '../../../models/classes/Task'
import type { TaskListColumnConfig } from '../../../models/configs/TaskListConfig'
/**
* 声明式列配置接口
* 从 task-list-column 组件解析出的列配置
*/
export interface DeclarativeColumnConfig {
prop?: string
label?: string
width?: number | string
align?: 'left' | 'center' | 'right'
cssClass?: string
// slot 函数
headerSlot?: () => VNodeChild
defaultSlot?: (scope: { row: Task; $index: number }) => VNodeChild
}
/**
* 从 VNode 中提取 task-list-column 的配置
*/
export function extractColumnConfig(vnode: VNode): DeclarativeColumnConfig | null {
// 检查是否是 task-list-column 组件
// 支持多种识别方式
let isTaskListColumn = false
if (typeof vnode.type === 'object') {
const component = vnode.type as any
// 检查组件名称的多种可能性
isTaskListColumn =
component.name === 'TaskListColumn' ||
component.__name === 'TaskListColumn' ||
// 检查文件名setup script 组件)
(component.__file && component.__file.includes('TaskListColumn'))
}
if (!isTaskListColumn) {
return null
}
// 提取 props
const props = vnode.props || {}
type ChildrenType = {
header?: () => VNodeChild
default?: (scope: { row: Task; $index: number }) => VNodeChild
} | null
const children = vnode.children as ChildrenType
// 提取 slots
const headerSlot = children?.header
const defaultSlot = children?.default
return {
prop: props.prop,
label: props.label,
width: props.width,
align: props.align || 'left',
cssClass: props.cssClass,
headerSlot,
defaultSlot,
}
}
/**
* 解析声明式列配置
* 从默认 slot 的 VNode 中提取所有 task-list-column 的配置
*/
export function parseDeclarativeColumns(slots: Slots): DeclarativeColumnConfig[] {
const defaultSlot = slots.default?.()
if (!defaultSlot || !Array.isArray(defaultSlot)) {
return []
}
const columns: DeclarativeColumnConfig[] = []
// 递归提取所有 task-list-column
const extractColumns = (vnodes: VNode[]) => {
for (const vnode of vnodes) {
// 跳过注释节点和文本节点,但继续处理 Fragment
if (!vnode) {
continue
}
// 如果是 Fragment 或其他 Symbol 类型,直接处理子节点
if (typeof vnode.type === 'symbol') {
if (vnode.children && Array.isArray(vnode.children)) {
extractColumns(vnode.children as VNode[])
}
continue
}
const config = extractColumnConfig(vnode)
if (config) {
columns.push(config)
}
// 递归处理子节点
if (vnode.children && Array.isArray(vnode.children)) {
extractColumns(vnode.children as VNode[])
}
}
}
extractColumns(defaultSlot)
return columns
}/**
* Task List Columns Composable
* 用于管理任务列表的列配置(支持声明式和配置式)
*/
export function useTaskListColumns(
renderMode: ComputedRef<'default' | 'declarative'> | 'default' | 'declarative',
slots: Slots,
defaultColumns?: TaskListColumnConfig[],
) {
// 声明式列配置
const declarativeColumns = computed(() => {
const mode = typeof renderMode === 'string' ? renderMode : renderMode.value
if (mode !== 'declarative') {
return []
}
return parseDeclarativeColumns(slots)
})
// 最终使用的列配置
const finalColumns = computed(() => {
const mode = typeof renderMode === 'string' ? renderMode : renderMode.value
if (mode === 'declarative') {
return declarativeColumns.value
}
return defaultColumns || []
})
// 获取列宽度样式
const getColumnWidthStyle = (
column: { width?: number | string },
containerWidth?: number,
) => {
if (!column.width) return {}
let widthPx: string
// 如果是百分比,转换为像素
if (typeof column.width === 'string' && column.width.includes('%')) {
if (containerWidth && containerWidth > 0) {
const percentage = parseFloat(column.width) / 100
const pixels = Math.floor(containerWidth * percentage)
widthPx = `${pixels}px`
} else {
return {} // 容器宽度未知时返回空
}
} else {
// 像素值:处理数字或字符串数字
if (typeof column.width === 'number') {
widthPx = `${column.width}px`
} else if (typeof column.width === 'string') {
// 如果字符串已经包含单位px, rem, em等直接使用
if (/\d+(px|rem|em|%)/.test(column.width)) {
widthPx = column.width
} else {
// 纯数字字符串,添加 px 单位
widthPx = `${column.width}px`
}
} else {
widthPx = String(column.width)
}
}
return {
flex: `0 0 ${widthPx}`,
minWidth: widthPx,
maxWidth: widthPx,
}
}
// 获取列对齐样式
const getColumnAlignStyle = (align?: 'left' | 'center' | 'right') => {
if (!align || align === 'left') return {}
return {
justifyContent: align === 'center' ? 'center' : 'flex-end',
textAlign: align,
}
}
return {
declarativeColumns,
finalColumns,
getColumnWidthStyle,
getColumnAlignStyle,
}
}