Files
workspace/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions-group.tsx
T
2025-09-23 17:29:46 +08:00

302 lines
7.1 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 type { PropType, VNode } 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 | (() => string | VNode)
content: string | VNode | (() => string | VNode)
span?: number
}
export default defineComponent({
name: 'DescriptionsGroup',
props: {
data: {
type: Array as PropType<DescriptionsData[]>,
default: () => [],
},
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',
},
columns: {
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: '',
},
style: {
type: Object,
default: () => ({}),
},
},
emits: [],
setup(props, { slots }) {
const prefixCls = getPrefixCls('descriptions')
const renderLabel = (item: DescriptionsData) => {
if (typeof item.label === 'function')
return item.label()
return item.label
}
const renderContent = (item: DescriptionsData) => {
if (typeof item.content === 'function')
return item.content()
return item.content
}
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>
)
}
return () => (
<div class={[prefixCls, props.class]} style={{ ...props.style }}>
{renderHeader()}
{renderBody()}
</div>
)
},
})