194 lines
5.3 KiB
TypeScript
194 lines
5.3 KiB
TypeScript
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,
|
|
}
|
|
}
|