u
This commit is contained in:
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>
|
||||
+241
-25
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user