diff --git a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss index 7e7e6e76..f1d21018 100644 --- a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss +++ b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_dark.scss @@ -76,7 +76,7 @@ --ao-bg-color-component: #393939; --ao-border-color-component: #5e5e5e; - --ao-bg-color-secondarycomponent: #4b4b4b; + --ao-bg-color-secondarycomponent: #2c2c2c; --ao-bg-color-secondarycomponent-hover: #5e5e5e; --ao-bg-color-mask: rgba(23, 23, 26, 0.6); diff --git a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss index c7fd1d57..6a0efac4 100644 --- a/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss +++ b/code/base-project/base-vue/packages/ao-design/src/_styles/theme/_light.scss @@ -77,7 +77,7 @@ --ao-bg-color-component: #e8e8e8; --ao-border-color-component: #ddd; - --ao-bg-color-secondarycomponent: #ddd; + --ao-bg-color-secondarycomponent: #f3f3f3; --ao-bg-color-secondarycomponent-hover: #c6c6c6; --ao-bg-color-mask: rgba(29, 33, 41, 0.6); diff --git a/code/base-project/base-vue/packages/ao-design/src/_utils/vnode.ts b/code/base-project/base-vue/packages/ao-design/src/_utils/vnode.ts new file mode 100644 index 00000000..ce7009e9 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/_utils/vnode.ts @@ -0,0 +1,69 @@ +import type { VNode, VNodeChild } from 'vue' +import { Fragment } from 'vue' + +/** + * 判断 vnode 是否匹配目标组件 + * @param vnode VNode + * @param componentType 组件对象 + * @param componentName 组件名称字符串(可选) + */ +function isTargetComponent(vnode: VNode, componentType: any, componentName?: string) { + if (!vnode) + return false + if (vnode.type === componentType) + return true + if (componentName && typeof vnode.type === 'object' && (vnode.type as any).name === componentName) { + return true + } + return false +} + +/** + * 递归获取指定组件的 props 和插槽函数(不执行函数 prop) + * @param nodes VNode 数组 + * @param componentType 目标组件对象 + * @param componentName 目标组件名称字符串(可选) + */ +export function getComponentInnerData( + nodes: VNode[], + componentType: any, + componentName?: string, +): { item: Record, slots: Record VNodeChild | VNodeChild[] | null> }[] { + const result: { item: Record, slots: Record VNodeChild | VNodeChild[] | null> }[] = [] + + function traverse(vnode: VNode) { + if (!vnode) + return + + if (vnode.type === Fragment && Array.isArray(vnode.children)) { + vnode.children.forEach(child => traverse(child as VNode)) + return + } + + if (isTargetComponent(vnode, componentType, componentName)) { + const item: Record = {} + const slots: Record VNodeChild | VNodeChild[] | null> = {} + + if (vnode.props) + Object.assign(item, vnode.props) + + if (vnode.children && typeof vnode.children === 'object' && !Array.isArray(vnode.children)) { + for (const key in vnode.children) { + const slotFn = vnode.children[key] + if (typeof slotFn === 'function') { + slots[key] = slotFn as () => VNodeChild | VNodeChild[] | null + } + } + } + + result.push({ item, slots }) + } + + if (Array.isArray(vnode.children)) { + vnode.children.forEach(child => traverse(child as VNode)) + } + } + + nodes.forEach(traverse) + return result +} diff --git a/code/base-project/base-vue/packages/ao-design/src/components.ts b/code/base-project/base-vue/packages/ao-design/src/components.ts index 9e95516d..a9b88f1a 100644 --- a/code/base-project/base-vue/packages/ao-design/src/components.ts +++ b/code/base-project/base-vue/packages/ao-design/src/components.ts @@ -6,10 +6,14 @@ export { default as Checkbox } from './checkbox/checkbox' export { default as CheckboxGroup } from './checkbox/checkbox-group' export { default as Collapse } from './collapse/collapse' export { default as CollapseGroup } from './collapse/collapse-group' +export { default as Descriptions } from './descriptions/descriptions' +export { default as DescriptionsGroup } from './descriptions/descriptions-group' export { default as Drawer } from './drawer' export { default as Group } from './group' export { default as Icon } from './icon' export { default as Input } from './input' +export { default as List } from './list/list' +export { default as ListItem } from './list/list-item' export { default as Modal } from './modal' export { default as Popconfirm } from './popconfirm' export { default as Popup } from './popup' diff --git a/code/base-project/base-vue/packages/ao-design/src/descriptions/demo.vue b/code/base-project/base-vue/packages/ao-design/src/descriptions/demo.vue new file mode 100644 index 00000000..a3ef5436 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/descriptions/demo.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions-group.tsx b/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions-group.tsx index af9b86ef..4b4ffedf 100644 --- a/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions-group.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions-group.tsx @@ -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, default: () => [], }, - title: { + header: { type: [String, Object, Function] as PropType 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
{title}
+ 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 ( + + {renderLabel(node)} + {props.colon ? ':' : null} + + ) + } + + 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 ( + + {renderContent(node)} + + ) + } + + const row = (data: DescriptionsData[]) => { + return ( + + { + data.map(node => ( + <> + {label(node)} + {content(node)} + + )) + } + + ) + } + + const rowVertical = (data: DescriptionsData[]) => { + return ( + <> + {data.map(node => label(node))} + {data.map(node => content(node))} + + ) + } + + const column = (data: DescriptionsData[]) => { + return ( + <> + {data.map(node => ( + + {label(node)} + {content(node)} + + ))} + + ) + } + + const columnVertical = (data: DescriptionsData[]) => { + return ( + <> + {data.map(node => ( + <> + {label(node)} + {content(node)} + + ))} + + ) + } + + const renderHeader = () => { + const header = slots.header + ? slots.header() + : typeof props.header === 'function' + ? props.header() + : props.header + + return
{header}
+ } const renderBody = () => { return ( + {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) + })}
) @@ -77,8 +293,8 @@ export default defineComponent({ return () => (
- {renderHeader} - {renderBody} + {renderHeader()} + {renderBody()}
) }, diff --git a/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions.tsx b/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions.tsx index 5cf3221b..c471c873 100644 --- a/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/descriptions/descriptions.tsx @@ -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 VNode)>, + type: [String, Object, Function] as PropType string | VNode)>, }, content: { - type: [String, Object, Function] as PropType VNode)>, - }, - default: { - type: [String, Object, Function] as PropType VNode)>, + type: [String, Object, Function] as PropType string | VNode)>, }, span: { type: Number, diff --git a/code/base-project/base-vue/packages/ao-design/src/descriptions/index.scss b/code/base-project/base-vue/packages/ao-design/src/descriptions/index.scss index 0a9006e6..5806fc7e 100644 --- a/code/base-project/base-vue/packages/ao-design/src/descriptions/index.scss +++ b/code/base-project/base-vue/packages/ao-design/src/descriptions/index.scss @@ -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; + } + } + } } diff --git a/code/base-project/base-vue/packages/ao-design/src/index.scss b/code/base-project/base-vue/packages/ao-design/src/index.scss index fdedf74f..3e3cf0ae 100644 --- a/code/base-project/base-vue/packages/ao-design/src/index.scss +++ b/code/base-project/base-vue/packages/ao-design/src/index.scss @@ -7,6 +7,7 @@ @use './collapse/index.scss' as *; @use './checkbox/index.scss' as *; @use './drawer/index.scss' as *; +@use './descriptions/index.scss' as *; @use './group/index.scss' as *; @use './icon/index.scss' as *; @use './input/index.scss' as *; @@ -23,6 +24,7 @@ @use './tag/index.scss' as *; @use './textarea/index.scss' as *; @use './tooltip/index.scss' as *; +@use './list/index.scss' as *; [class*='#{variables.$prefix}-'] { -webkit-box-sizing: border-box; diff --git a/code/base-project/base-vue/packages/ao-design/src/list/index.scss b/code/base-project/base-vue/packages/ao-design/src/list/index.scss new file mode 100644 index 00000000..49a0fee3 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/list/index.scss @@ -0,0 +1,65 @@ +@use '../_styles/_variables.scss' as *; +@use 'sass:color'; + +$list-prefix: '#{$prefix}-list'; + +.#{$list-prefix} { + font-size: 14px; + margin: 0; + padding: 0; + list-style: none; + color: $text-color-base; + background: $bg-color-container; + .#{$list-prefix}-header, + .#{$list-prefix}-footer { + background: $bg-color-container; + padding: 12px 16px; + } + + .#{$list-prefix}-body { + overflow: auto; + list-style: none; + padding: 0; + margin: 0; + } + + .#{$list-prefix}-item { + font-size: 14px; + padding: 12px 16px; + display: flex; + justify-content: space-between; + align-items: flex-start; + position: relative; + + .#{$list-prefix}-item-prefix { + margin-right: 20px; + flex: 0 0 auto; + } + + .#{$list-prefix}-item-content { + flex: 1 1 auto; + min-width: 0; + overflow-y: auto; + } + + .#{$list-prefix}-item-suffix { + margin-left: 20px; + flex: 0 0 auto; + } + + &::after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 1px; + } + } + + &.#{$list-prefix}-separator { + .#{$list-prefix}-item::after { + background: $separator-color; + } + } +} diff --git a/code/base-project/base-vue/packages/ao-design/src/list/list-item.tsx b/code/base-project/base-vue/packages/ao-design/src/list/list-item.tsx new file mode 100644 index 00000000..507531dd --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/list/list-item.tsx @@ -0,0 +1,18 @@ +import type { PropType, VNode } from 'vue' + +import { defineComponent } from 'vue' + +export default defineComponent({ + name: 'AoComponent', + props: { + prefix: { + type: [String, Object, Function] as PropType string | VNode)>, + }, + content: { + type: [String, Object, Function] as PropType string | VNode)>, + }, + suffix: { + type: [String, Object, Function] as PropType string | VNode)>, + }, + }, +}) diff --git a/code/base-project/base-vue/packages/ao-design/src/list/list.tsx b/code/base-project/base-vue/packages/ao-design/src/list/list.tsx new file mode 100644 index 00000000..d68329e5 --- /dev/null +++ b/code/base-project/base-vue/packages/ao-design/src/list/list.tsx @@ -0,0 +1,161 @@ +import type { PropType, VNode } from 'vue' +import { defineComponent } from 'vue' +import { getPrefixCls } from '../_utils/getPrefixCls' +import { getComponentInnerData } from '../_utils/vnode' +import ListItem from './list-item' + +interface ListItemData { + prefix: string | VNode | (() => string | VNode) + content: string | VNode | (() => string | VNode) + suffix: string | VNode | (() => string | VNode) +} + +export default defineComponent({ + name: 'List', + props: { + data: { + type: Array as PropType, + default: () => [], + }, + header: { + type: [String, Object, Function] as PropType VNode)>, + }, + content: { + type: [String, Object, Function] as PropType VNode)>, + }, + footer: { + type: [String, Object, Function] as PropType VNode)>, + }, + separator: { + type: Boolean, + default: true, + }, + onScroll: { + type: Function as PropType<(options: { e: Event, scrollTop: number, scrollBottom: number }) => void>, + }, + class: { + type: [String, Array, Object], + default: '', + }, + style: { + type: Object, + default: () => ({}), + }, + }, + emits: [], + setup(props, { slots }) { + const prefixCls = getPrefixCls('list') + + const getRows = () => { + const dataList: ListItemData[] = [] + + // 1️⃣ 构建基础数据列表 + if (props.data && props.data.length) { + props.data.forEach((item) => { + dataList.push({ + prefix: item.prefix, + content: item.content, + suffix: item.suffix, + }) + }) + } + else if (slots.default) { + const childrenData = getComponentInnerData(slots.default(), ListItem) + childrenData.forEach(({ item, slots }) => { + dataList.push({ + prefix: slots.prefix ? slots.prefix() : item.prefix, + content: slots.content + ? slots.content() + : slots.default + ? slots.default() + : item.content, + suffix: slots.suffix ? slots.suffix() : item.suffix, + }) + }) + } + + return dataList + } + + const renderItemPrefix = (item: ListItemData) => { + if (typeof item.prefix === 'function') + return item.prefix() + return item.prefix + } + + const renderItemContent = (item: ListItemData) => { + if (typeof item.content === 'function') + return item.content() + return item.content + } + + const renderItemSuffix = (item: ListItemData) => { + if (typeof item.suffix === 'function') + return item.suffix() + return item.suffix + } + + const renderHeader = () => { + const content = slots.header + ? slots.header() + : typeof props.header === 'function' + ? props.header() + : props.header + return content ?
{content}
: null + } + + const handleScroll = (e: Event) => { + const target = e.target as HTMLElement + const scrollTop = target.scrollTop + const scrollHeight = target.scrollHeight + const clientHeight = target.clientHeight + const scrollBottom = scrollHeight - scrollTop - clientHeight + + // 触发滚动回调 + props.onScroll?.({ e, scrollTop, scrollBottom }) + } + + const renderBody = () => { + return ( +
+
    + {getRows().map((item, index) => ( +
  • +
    {renderItemPrefix(item)}
    +
    {renderItemContent(item)}
    +
    {renderItemSuffix(item)}
    +
  • + ))} +
+
+ ) + } + + const renderFooter = () => { + const content = slots.footer + ? slots.footer() + : typeof props.footer === 'function' + ? props.footer() + : props.footer + return content ?
{content}
: null + } + + return () => ( +
+ {renderHeader()} + {renderBody()} + {renderFooter()} +
+ ) + }, +}) diff --git a/code/base-project/base-vue/packages/ao-design/src/spin/index.scss b/code/base-project/base-vue/packages/ao-design/src/spin/index.scss index ffd39ed5..b3efcf5c 100644 --- a/code/base-project/base-vue/packages/ao-design/src/spin/index.scss +++ b/code/base-project/base-vue/packages/ao-design/src/spin/index.scss @@ -43,3 +43,15 @@ $spin-prefix: #{$prefix}-spin; } } } +.#{$spin-prefix}-horizontal { + display: flex; + align-items: center; + gap: 8px; // 图标和 tip 之间间距 +} + +.#{$spin-prefix}-vertical { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; // 图标和 tip 之间间距 +} diff --git a/code/base-project/base-vue/packages/ao-design/src/spin/index.tsx b/code/base-project/base-vue/packages/ao-design/src/spin/index.tsx index 58d4408b..9f8c5d57 100644 --- a/code/base-project/base-vue/packages/ao-design/src/spin/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/spin/index.tsx @@ -1,24 +1,40 @@ import type { PropType, VNode } from 'vue' -import { defineComponent } from 'vue' +import { defineComponent, onUnmounted, ref } from 'vue' import { getPrefixCls } from '../_utils/getPrefixCls' import Icon from '../icon' export default defineComponent({ name: 'Spin', props: { - loading: { type: Boolean, default: false }, - opacity: { type: Number as PropType, default: 0.6 }, - rounded: { type: Boolean, default: true }, - icon: { type: String as PropType, default: 'mingcute:loading-line' }, - size: { type: Number as PropType, default: 16 }, - rotate: { type: Number as PropType, default: 360 }, + loading: { + type: Boolean, + default: false, + }, + icon: { + type: String as PropType, + default: 'mingcute:loading-fill', + }, + size: { + type: Number as PropType, + default: 16, + }, tip: { - type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode), + type: [String, Object, Function] as PropType VNode)>, default: '', }, + tipPosition: { + type: String as PropType<'top' | 'right' | 'bottom' | 'left'>, + default: 'bottom', + }, + delay: { + type: Number, + default: 0, + }, }, setup(props, { slots }) { const prefixCls = getPrefixCls('spin') + const showLoading = ref(false) + let timer: number | null = null const renderTip = () => { if (slots.tip) @@ -28,51 +44,72 @@ export default defineComponent({ return props.tip } - return () => { - const tipContent = renderTip() - - if (!props.loading || !slots.default) { - // loading 为 false 时的结构 - return ( -
- - {tipContent &&
{tipContent}
} -
- ) + // 每次渲染时处理 delay + const updateShowLoading = () => { + if (timer) + clearTimeout(timer) + if (props.loading) { + if (props.delay > 0) { + timer = window.setTimeout(() => { + showLoading.value = true + }, props.delay) + } + else { + showLoading.value = true + } } + else { + showLoading.value = false + } + } - // loading 为 true 时的结构 - return ( -
- {slots.default()} + onUnmounted(() => { + if (timer) + clearTimeout(timer) + }) -
-
- - {tipContent &&
{tipContent}
} + return () => { + // 更新延迟显示状态 + updateShowLoading() + + const tipContent = renderTip() + const isHorizontal = props.tipPosition === 'left' || props.tipPosition === 'right' + const containerClass = [ + prefixCls, + tipContent ? `${prefixCls}-has-tip` : '', + tipContent ? `${prefixCls}-tip-${props.tipPosition}` : '', + ] + + const tipNode = tipContent ?
{tipContent}
: null + const iconNode = + + const content = isHorizontal + ? ( +
+ {props.tipPosition === 'left' && tipNode} + {iconNode} + {props.tipPosition === 'right' && tipNode}
-
+ ) + : ( +
+ {props.tipPosition === 'top' && tipNode} + {iconNode} + {props.tipPosition === 'bottom' && tipNode} +
+ ) + return ( +
+ {/* 默认内容立即渲染 */} + {slots.default && slots.default()} + + {/* 延迟显示的 loading 遮罩 */} + {showLoading.value && ( +
+
{content}
+
+ )}
) } diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index 33e9e510..883fa0d8 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -1,17 +1,35 @@ diff --git a/code/base-project/base-vue/src/components.d.ts b/code/base-project/base-vue/src/components.d.ts index cbe446a3..7a4ca804 100644 --- a/code/base-project/base-vue/src/components.d.ts +++ b/code/base-project/base-vue/src/components.d.ts @@ -16,10 +16,15 @@ declare module 'vue' { ACollapse: typeof import('@ao/ao-design')['Collapse'] ACollapseGroup: typeof import('@ao/ao-design')['CollapseGroup'] AConfigProvider: typeof import('@ao/ao-design')['ConfigProvider'] + ADescriptions: typeof import('@ao/ao-design')['Descriptions'] + 'ADescriptions.Item': typeof import('@ao/ao-design')['Descriptions.Item'] + ADescriptionsGroup: typeof import('@ao/ao-design')['DescriptionsGroup'] ADrawer: typeof import('@ao/ao-design')['Drawer'] AGroup: typeof import('@ao/ao-design')['Group'] AIcon: typeof import('@ao/ao-design')['Icon'] AInput: typeof import('@ao/ao-design')['Input'] + AList: typeof import('@ao/ao-design')['List'] + AListItem: typeof import('@ao/ao-design')['ListItem'] ALoading: typeof import('@ao/ao-design')['Loading'] AMessage: typeof import('@ao/ao-design')['Message'] AModal: typeof import('@ao/ao-design')['Modal']