This commit is contained in:
oneao committed 2025-09-23 17:29:46 +08:00
1 parent 9c76e2769a
commit f285ff776f
16 files changed
+884 -89

No files matched your search

@@ -1,11 +1,13 @@
import type { PropType, VNode } from 'vue'
import { computed, defineComponent } from 'vue'
import { defineComponent } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import { getComponentInnerData } from '../_utils/vnode'
import Descriptions from './descriptions'
interface DescriptionsData {
label: string | VNode | (() => VNode)
content: string | VNode | (() => VNode)
span: number
label: string | VNode | (() => string | VNode)
content: string | VNode | (() => string | VNode)
span?: number
}
export default defineComponent({
@@ -15,13 +17,17 @@ export default defineComponent({
type: Array as PropType<DescriptionsData[]>,
default: () => [],
},
title: {
header: {
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
},
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium',
},
tableLayout: {
type: String as PropType<'fixed' | 'auto'>,
default: 'fixed',
},
layout: {
type: String as PropType<'row' | 'row-vertical' | 'column' | 'column-vertical'>,
default: 'row',
@@ -30,6 +36,28 @@ export default defineComponent({
type: Number,
default: 3,
},
border: {
type: Boolean,
default: true,
},
colon: {
type: Boolean,
default: false,
},
labelClass: {
type: [String, Array, Object],
},
labelStyle: {
type: Object,
default: () => ({}),
},
contentClass: {
type: [String, Array, Object],
},
contentStyle: {
type: Object,
default: () => ({}),
},
class: {
type: [String, Array, Object],
default: '',
@@ -40,36 +68,224 @@ export default defineComponent({
},
},
emits: [],
setup(props, { slots, emit }) {
const prefixCls = getPrefixCls('descriptions-group')
const renderHeader = () => {
const title = slots.title
? slots.title()
: typeof props.title === 'function'
? props.title()
: props.title
setup(props, { slots }) {
const prefixCls = getPrefixCls('descriptions')
return <div class={`${prefixCls}-header`}>{title}</div>
const renderLabel = (item: DescriptionsData) => {
if (typeof item.label === 'function')
return item.label()
return item.label
}
const childrenVNodes = computed(() => {
const nodes = slots.default?.() ?? []
return nodes.filter(node =>
node.type && (typeof node.type === 'string' || typeof node.type === 'object' || typeof node.type === 'function'),
)
})
const renderContent = (item: DescriptionsData) => {
if (typeof item.content === 'function')
return item.content()
return item.content
}
const groupedData = computed(() => {
c
})
const getRows = () => {
const dataList: DescriptionsData[] = []
// 1️⃣ 构建基础数据列表
if (props.data && props.data.length) {
props.data.forEach((item) => {
dataList.push({
label: item.label,
content: item.content,
span: item.span ?? 1, // 默认 span = 1
})
})
}
else if (slots.default) {
const childrenData = getComponentInnerData(slots.default(), Descriptions)
childrenData.forEach(({ item, slots }) => {
dataList.push({
label: slots.label ? slots.label() : item.label,
content: slots.content
? slots.content()
: slots.default
? slots.default()
: item.content,
span: item.span ?? 1,
})
})
}
// 2️⃣ 如果是列布局,直接一行返回
if (props.layout === 'column' || props.layout === 'column-vertical') {
return [dataList]
}
// 3️⃣ Row 布局按列分行
const res: DescriptionsData[][] = []
let temp: DescriptionsData[] = []
let currentSpan = 0
const addRow = () => {
if (temp.length) {
// 填充剩余列到最后一个 item
const restSpan = props.columns - currentSpan
if (restSpan > 0) {
const lastItem = temp[temp.length - 1]
lastItem.span = (lastItem.span ?? 1) + restSpan
}
res.push(temp)
}
}
dataList.forEach((item) => {
const span = Math.min(item.span ?? 1, props.columns)
if (currentSpan + span > props.columns) {
addRow()
temp = []
currentSpan = 0
}
temp.push({ ...item, span })
currentSpan += span
})
// 最后一行
addRow()
return res
}
const label = (node: DescriptionsData) => {
const labelClass = [`${prefixCls}-label`]
let colspan = 1
// 水平布局下
if (props.layout === 'row') {
colspan = 1
}
else if (props.layout === 'row-vertical') {
colspan = node.span ?? 1
}
return (
<td
colspan={colspan}
class={[labelClass, props.labelClass]}
style={props.labelStyle}
>
{renderLabel(node)}
{props.colon ? ':' : null}
</td>
)
}
const content = (node: DescriptionsData) => {
const contentClass = [`${prefixCls}-content`]
let colspan = 1
if (props.layout === 'row') {
const span = node.span ?? 1
colspan = span * 2 - 1
}
else if (props.layout === 'row-vertical') {
colspan = node.span ?? 1
}
return (
<td
colspan={colspan}
class={[contentClass, props.contentClass]}
style={props.contentStyle}
>
{renderContent(node)}
</td>
)
}
const row = (data: DescriptionsData[]) => {
return (
<tr>
{
data.map(node => (
<>
{label(node)}
{content(node)}
</>
))
}
</tr>
)
}
const rowVertical = (data: DescriptionsData[]) => {
return (
<>
<tr>{data.map(node => label(node))}</tr>
<tr>{data.map(node => content(node))}</tr>
</>
)
}
const column = (data: DescriptionsData[]) => {
return (
<>
{data.map(node => (
<tr>
{label(node)}
{content(node)}
</tr>
))}
</>
)
}
const columnVertical = (data: DescriptionsData[]) => {
return (
<>
{data.map(node => (
<>
<tr>{label(node)}</tr>
<tr>{content(node)}</tr>
</>
))}
</>
)
}
const renderHeader = () => {
const header = slots.header
? slots.header()
: typeof props.header === 'function'
? props.header()
: props.header
return <div class={`${prefixCls}-header`}>{header}</div>
}
const renderBody = () => {
return (
<table class={[
`${prefixCls}-body`,
`${prefixCls}-body-${props.tableLayout}`,
`${prefixCls}-size-${props.size}`,
{ [`${prefixCls}-body-border`]: props.border },
]}
>
<tbody>
{getRows().map((item) => {
if (props.layout === 'row') {
return row(item)
}
else if (props.layout === 'row-vertical') {
return rowVertical(item)
}
else if (props.layout === 'column') {
return column(item)
}
else if (props.layout === 'column-vertical') {
return columnVertical(item)
}
// 默认 fallback
return row(item)
})}
</tbody>
</table>
)
@@ -77,8 +293,8 @@ export default defineComponent({
return () => (
<div class={[prefixCls, props.class]} style={{ ...props.style }}>
{renderHeader}
{renderBody}
{renderHeader()}
{renderBody()}
</div>
)
},