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

@@ -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 之间间距
}
@@ -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<number>, default: 0.6 },
rounded: { type: Boolean, default: true },
icon: { type: String as PropType<string>, default: 'mingcute:loading-line' },
size: { type: Number as PropType<number>, default: 16 },
rotate: { type: Number as PropType<number>, default: 360 },
loading: {
type: Boolean,
default: false,
},
icon: {
type: String as PropType<string>,
default: 'mingcute:loading-fill',
},
size: {
type: Number as PropType<number>,
default: 16,
},
tip: {
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
type: [String, Object, Function] as PropType<string | VNode | (() => 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 (
<div
class={[
prefixCls,
tipContent ? `${prefixCls}-has-tip` : '',
]}
>
<Icon
icon={props.icon}
size={props.size}
class={`${prefixCls}-icon`}
spin
/>
{tipContent && <div class={`${prefixCls}-tip`}>{tipContent}</div>}
</div>
)
// 每次渲染时处理 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 (
<div
class={[
prefixCls,
`${prefixCls}-loading`,
]}
>
{slots.default()}
onUnmounted(() => {
if (timer)
clearTimeout(timer)
})
<div class={`${prefixCls}-mask`}>
<div class={`${prefixCls}-mask-inner`}>
<Icon
icon={props.icon}
size={props.size}
spin
class={`${prefixCls}-icon`}
/>
{tipContent && <div class={`${prefixCls}-tip`}>{tipContent}</div>}
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 ? <div class={`${prefixCls}-tip`}>{tipContent}</div> : null
const iconNode = <Icon icon={props.icon} size={props.size} spin class={`${prefixCls}-icon`} />
const content = isHorizontal
? (
<div class={`${prefixCls}-horizontal`}>
{props.tipPosition === 'left' && tipNode}
{iconNode}
{props.tipPosition === 'right' && tipNode}
</div>
</div>
)
: (
<div class={`${prefixCls}-vertical`}>
{props.tipPosition === 'top' && tipNode}
{iconNode}
{props.tipPosition === 'bottom' && tipNode}
</div>
)
return (
<div class={containerClass} style={{ position: 'relative' }}>
{/* 默认内容立即渲染 */}
{slots.default && slots.default()}
{/* 延迟显示的 loading 遮罩 */}
{showLoading.value && (
<div class={`${prefixCls}-mask`}>
<div class={`${prefixCls}-mask-inner`}>{content}</div>
</div>
)}
</div>
)
}