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

@@ -0,0 +1,107 @@
<script setup lang="ts">
import { ref } from 'vue'
// 示例数据
const demoData = ref([
{ label: '用户名', content: 'zhangsan', span: 1 },
{ label: '邮箱', content: 'zhangsan@example.com', span: 1 },
{ label: '手机号', content: '123456789', span: 1 },
{ label: '地址', content: '北京市海淀区中关村', span: 1 },
])
</script>
<template>
<div class="demo-container">
<!-- 1. Row 布局 -->
<div class="demo-block">
<h3>Row 布局</h3>
<ADescriptionsGroup
:data="demoData"
layout="row"
title="Row Layout"
:columns="3"
/>
</div>
<!-- 2. Row Vertical 布局 -->
<div class="demo-block">
<h3>Row Vertical 布局</h3>
<ADescriptionsGroup
:data="demoData"
layout="row-vertical"
title="Row Vertical Layout"
:columns="3"
/>
</div>
<!-- 3. Column 布局 -->
<div class="demo-block">
<h3>Column 布局</h3>
<ADescriptionsGroup
:data="demoData"
layout="column"
title="Column Layout"
:columns="3"
/>
</div>
<!-- 4. Column Vertical 布局 -->
<div class="demo-block">
<h3>Column Vertical 布局</h3>
<ADescriptionsGroup
:data="demoData"
layout="column-vertical"
title="Column Vertical Layout"
:columns="3"
/>
</div>
<!-- 5. 单独使用 ADescriptions -->
<div class="demo-block">
<h3>ADescriptions 单独使用</h3>
<ADescriptionsGroup title="单独 Descriptions" :columns="3">
<ADescriptions label="用户名">
zhangsan
</ADescriptions>
<ADescriptions label="邮箱">
zhangsan@example.com
</ADescriptions>
<ADescriptions label="手机号">
123456789
</ADescriptions>
<ADescriptions label="地址">
<div style="background-color: red;">
测试
</div>
</ADescriptions>
</ADescriptionsGroup>
</div>
</div>
</template>
<style>
.demo-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
gap: 40px;
}
.demo-block {
width: 80%;
max-width: 900px;
background: #fff;
padding: 20px 30px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.demo-block h3 {
margin-bottom: 16px;
font-weight: 600;
font-size: 18px;
color: #333;
}
</style>
@@ -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>
)
},
@@ -2,16 +2,13 @@ import type { PropType, VNode } from 'vue'
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Descriptions',
name: 'AoDescriptions',
props: {
label: {
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
type: [String, Object, Function] as PropType<string | VNode | (() => string | VNode)>,
},
content: {
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
},
default: {
type: [String, Object, Function] as PropType<string | VNode | (() => VNode)>,
type: [String, Object, Function] as PropType<string | VNode | (() => string | VNode)>,
},
span: {
type: Number,
@@ -4,4 +4,88 @@
$descriptions-prefix: '#{$prefix}-descriptions';
.#{$descriptions-prefix} {
font-size: 14px;
color: $text-color-base;
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
.#{$descriptions-prefix}-header {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
color: $text-color-base;
}
.#{$descriptions-prefix}-body {
border-collapse: collapse;
font-size: 14px;
width: 100%;
&.#{$descriptions-prefix}-body-fixed {
table-layout: fixed;
}
&.#{$descriptions-prefix}-body-auto {
table-layout: auto;
}
}
.#{$descriptions-prefix}-label {
color: $text-color-placeholder;
word-break: break-all;
}
.#{$descriptions-prefix}-content {
word-break: break-all;
}
.#{$descriptions-prefix}-size-small {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 8px 0;
}
}
.#{$descriptions-prefix}-size-medium {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 12px 0;
}
}
.#{$descriptions-prefix}-size-large {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 18px 0;
}
}
.#{$descriptions-prefix}-body-border {
> tbody > tr > td {
border: 1px solid $border-color-component;
}
.#{$descriptions-prefix}-label {
background-color: $bg-color-secondarycomponent;
}
&.#{$descriptions-prefix}-size-small {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 8px 16px;
}
}
&.#{$descriptions-prefix}-size-medium {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 12px 16px;
}
}
&.#{$descriptions-prefix}-size-large {
.#{$descriptions-prefix}-label,
.#{$descriptions-prefix}-content {
padding: 16px;
}
}
}
}