302 lines
7.1 KiB
TypeScript
302 lines
7.1 KiB
TypeScript
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>
|
||
)
|
||
},
|
||
})
|