feat: spin support cssvar
parent
c9443e038b
commit
86c9b4cc9a
|
@ -0,0 +1,101 @@
|
||||||
|
import { defineComponent, ref, computed, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
export interface ProgressProps {
|
||||||
|
prefixCls: string;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewSize = 100;
|
||||||
|
const borderWidth = viewSize / 5;
|
||||||
|
const radius = viewSize / 2 - borderWidth / 2;
|
||||||
|
const circumference = radius * 2 * Math.PI;
|
||||||
|
const position = 50;
|
||||||
|
|
||||||
|
const CustomCircle = defineComponent({
|
||||||
|
compatConfig: { MODE: 3 },
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
dotClassName: String,
|
||||||
|
style: Object,
|
||||||
|
hasCircleCls: Boolean,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const cStyle = computed(() => props.style || {});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<circle
|
||||||
|
class={[
|
||||||
|
`${props.dotClassName}-circle`,
|
||||||
|
{
|
||||||
|
[`${props.dotClassName}-circle-bg`]: props.hasCircleCls,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
r={radius}
|
||||||
|
cx={position}
|
||||||
|
cy={position}
|
||||||
|
stroke-width={borderWidth}
|
||||||
|
style={cStyle.value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
compatConfig: { MODE: 3 },
|
||||||
|
name: 'Progress',
|
||||||
|
inheritAttrs: false,
|
||||||
|
props: {
|
||||||
|
percent: Number,
|
||||||
|
prefixCls: String,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const dotClassName = `${props.prefixCls}-dot`;
|
||||||
|
const holderClassName = `${dotClassName}-holder`;
|
||||||
|
const hideClassName = `${holderClassName}-hidden`;
|
||||||
|
|
||||||
|
const render = ref(false);
|
||||||
|
|
||||||
|
// ==================== Visible =====================
|
||||||
|
watchEffect(() => {
|
||||||
|
if (props.percent !== 0) {
|
||||||
|
render.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Progress ====================
|
||||||
|
const safePtg = computed(() => Math.max(Math.min(props.percent, 100), 0));
|
||||||
|
|
||||||
|
const circleStyle = computed(() => ({
|
||||||
|
strokeDashoffset: `${circumference / 4}`,
|
||||||
|
strokeDasharray: `${(circumference * safePtg.value) / 100} ${
|
||||||
|
(circumference * (100 - safePtg.value)) / 100
|
||||||
|
}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ===================== Render =====================
|
||||||
|
return () => {
|
||||||
|
if (!render.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class={[holderClassName, `${dotClassName}-progress`, safePtg.value <= 0 && hideClassName]}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox={`0 0 ${viewSize} ${viewSize}`}
|
||||||
|
{...({
|
||||||
|
role: 'progressbar',
|
||||||
|
'aria-valuemin': 0,
|
||||||
|
'aria-valuemax': 100,
|
||||||
|
'aria-valuenow': safePtg.value,
|
||||||
|
} as any)}
|
||||||
|
>
|
||||||
|
<CustomCircle dotClassName={dotClassName} hasCircleCls={true} />
|
||||||
|
<CustomCircle dotClassName={dotClassName} style={circleStyle.value} />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,11 +1,22 @@
|
||||||
import type { VNode, ExtractPropTypes, PropType } from 'vue';
|
import type { VNode, ExtractPropTypes, PropType } from 'vue';
|
||||||
import { onBeforeUnmount, cloneVNode, isVNode, defineComponent, shallowRef, watch } from 'vue';
|
import {
|
||||||
|
onBeforeUnmount,
|
||||||
|
cloneVNode,
|
||||||
|
isVNode,
|
||||||
|
defineComponent,
|
||||||
|
shallowRef,
|
||||||
|
watch,
|
||||||
|
computed,
|
||||||
|
} from 'vue';
|
||||||
import { debounce } from 'throttle-debounce';
|
import { debounce } from 'throttle-debounce';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { filterEmpty, getPropsSlot } from '../_util/props-util';
|
import { filterEmpty, getPropsSlot } from '../_util/props-util';
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||||
import useStyle from './style';
|
import useStyle from './style';
|
||||||
import useConfigInject from '../config-provider/hooks/useConfigInject';
|
import useConfigInject from '../config-provider/hooks/useConfigInject';
|
||||||
|
import useCSSVarCls from '../config-provider/hooks/useCssVarCls';
|
||||||
|
import Progress from './Progress';
|
||||||
|
import usePercent from './usePercent';
|
||||||
|
|
||||||
export type SpinSize = 'small' | 'default' | 'large';
|
export type SpinSize = 'small' | 'default' | 'large';
|
||||||
export const spinProps = () => ({
|
export const spinProps = () => ({
|
||||||
|
@ -16,6 +27,8 @@ export const spinProps = () => ({
|
||||||
tip: PropTypes.any,
|
tip: PropTypes.any,
|
||||||
delay: Number,
|
delay: Number,
|
||||||
indicator: PropTypes.any,
|
indicator: PropTypes.any,
|
||||||
|
fullscreen: Boolean,
|
||||||
|
percent: [Number, String] as PropType<number | 'auto'>,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type SpinProps = Partial<ExtractPropTypes<ReturnType<typeof spinProps>>>;
|
export type SpinProps = Partial<ExtractPropTypes<ReturnType<typeof spinProps>>>;
|
||||||
|
@ -40,11 +53,16 @@ export default defineComponent({
|
||||||
size: 'default',
|
size: 'default',
|
||||||
spinning: true,
|
spinning: true,
|
||||||
wrapperClassName: '',
|
wrapperClassName: '',
|
||||||
|
fullscreen: false,
|
||||||
}),
|
}),
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
const { prefixCls, size, direction } = useConfigInject('spin', props);
|
const { prefixCls, size, direction } = useConfigInject('spin', props);
|
||||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
const rootCls = useCSSVarCls(prefixCls);
|
||||||
|
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||||
const sSpinning = shallowRef(props.spinning && !shouldDelay(props.spinning, props.delay));
|
const sSpinning = shallowRef(props.spinning && !shouldDelay(props.spinning, props.delay));
|
||||||
|
|
||||||
|
const mergedPercent = computed(() => usePercent(sSpinning.value, props.percent));
|
||||||
|
|
||||||
let updateSpinning: any;
|
let updateSpinning: any;
|
||||||
watch(
|
watch(
|
||||||
[() => props.spinning, () => props.delay],
|
[() => props.spinning, () => props.delay],
|
||||||
|
@ -63,6 +81,7 @@ export default defineComponent({
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
updateSpinning?.cancel();
|
updateSpinning?.cancel();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const { class: cls, ...divProps } = attrs;
|
const { class: cls, ...divProps } = attrs;
|
||||||
const { tip = slots.tip?.() } = props;
|
const { tip = slots.tip?.() } = props;
|
||||||
|
@ -78,8 +97,11 @@ export default defineComponent({
|
||||||
[cls as string]: !!cls,
|
[cls as string]: !!cls,
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderIndicator(prefixCls: string) {
|
function renderIndicator(prefixCls: string, percent: number) {
|
||||||
const dotClassName = `${prefixCls}-dot`;
|
const dotClassName = `${prefixCls}-dot`;
|
||||||
|
const holderClassName = `${dotClassName}-holder`;
|
||||||
|
const hideClassName = `${holderClassName}-hidden`;
|
||||||
|
|
||||||
let indicator = getPropsSlot(slots, props, 'indicator');
|
let indicator = getPropsSlot(slots, props, 'indicator');
|
||||||
// should not be render default indicator when indicator value is null
|
// should not be render default indicator when indicator value is null
|
||||||
if (indicator === null) {
|
if (indicator === null) {
|
||||||
|
@ -89,43 +111,87 @@ export default defineComponent({
|
||||||
indicator = indicator.length === 1 ? indicator[0] : indicator;
|
indicator = indicator.length === 1 ? indicator[0] : indicator;
|
||||||
}
|
}
|
||||||
if (isVNode(indicator)) {
|
if (isVNode(indicator)) {
|
||||||
return cloneVNode(indicator, { class: dotClassName });
|
return cloneVNode(indicator, { class: dotClassName, percent });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultIndicator && isVNode(defaultIndicator())) {
|
if (defaultIndicator && isVNode(defaultIndicator())) {
|
||||||
return cloneVNode(defaultIndicator(), { class: dotClassName });
|
return cloneVNode(defaultIndicator(), { class: dotClassName, percent });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span class={`${dotClassName} ${prefixCls}-dot-spin`}>
|
<>
|
||||||
<i class={`${prefixCls}-dot-item`} />
|
<span class={[holderClassName, percent > 0 && hideClassName]}>
|
||||||
<i class={`${prefixCls}-dot-item`} />
|
<span class={[dotClassName, `${prefixCls}-dot-spin`]}>
|
||||||
<i class={`${prefixCls}-dot-item`} />
|
{[1, 2, 3, 4].map(i => (
|
||||||
<i class={`${prefixCls}-dot-item`} />
|
<i class={`${prefixCls}-dot-item`} key={i} />
|
||||||
</span>
|
))}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{props.percent && <Progress prefixCls={prefixCls} percent={percent} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const spinElement = (
|
const spinElement = (
|
||||||
<div {...divProps} class={spinClassName} aria-live="polite" aria-busy={sSpinning.value}>
|
<div
|
||||||
{renderIndicator(prefixCls.value)}
|
{...divProps}
|
||||||
{tip ? <div class={`${prefixCls.value}-text`}>{tip}</div> : null}
|
key="loading"
|
||||||
|
class={[spinClassName, rootCls.value, cssVarCls.value]}
|
||||||
|
aria-live="polite"
|
||||||
|
aria-busy={sSpinning.value}
|
||||||
|
>
|
||||||
|
{renderIndicator(prefixCls.value, mergedPercent.value.value)}
|
||||||
|
{tip ? (
|
||||||
|
<div class={[`${prefixCls.value}-text`, hashId.value, rootCls.value, cssVarCls.value]}>
|
||||||
|
{tip}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (children && filterEmpty(children).length) {
|
if (children && filterEmpty(children).length && !props.fullscreen) {
|
||||||
const containerClassName = {
|
const containerClassName = {
|
||||||
[`${prefixCls.value}-container`]: true,
|
[`${prefixCls.value}-container`]: true,
|
||||||
[`${prefixCls.value}-blur`]: sSpinning.value,
|
[`${prefixCls.value}-blur`]: sSpinning.value,
|
||||||
|
[rootCls.value]: true,
|
||||||
|
[cssVarCls.value]: true,
|
||||||
|
[hashId.value]: true,
|
||||||
};
|
};
|
||||||
return wrapSSR(
|
return wrapCSSVar(
|
||||||
<div class={[`${prefixCls.value}-nested-loading`, props.wrapperClassName, hashId.value]}>
|
<div
|
||||||
{sSpinning.value && <div key="loading">{spinElement}</div>}
|
class={[
|
||||||
|
`${prefixCls.value}-nested-loading`,
|
||||||
|
props.wrapperClassName,
|
||||||
|
hashId.value,
|
||||||
|
rootCls.value,
|
||||||
|
cssVarCls.value,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{sSpinning.value && spinElement}
|
||||||
<div class={containerClassName} key="container">
|
<div class={containerClassName} key="container">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return wrapSSR(spinElement);
|
|
||||||
|
if (props.fullscreen) {
|
||||||
|
return wrapCSSVar(
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
`${prefixCls.value}-fullscreen`,
|
||||||
|
{
|
||||||
|
[`${prefixCls.value}-fullscreen-show`]: sSpinning.value,
|
||||||
|
},
|
||||||
|
hashId.value,
|
||||||
|
rootCls.value,
|
||||||
|
cssVarCls.value,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{spinElement}
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapCSSVar(spinElement);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,15 +17,38 @@ Use custom loading indicator.
|
||||||
</docs>
|
</docs>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-spin :indicator="indicator" />
|
<a-flex align="center" gap="middle">
|
||||||
|
<a-spin :indicator="smallIndicator" />
|
||||||
|
<a-spin :indicator="indicator" />
|
||||||
|
<a-spin :indicator="largeIndicator" />
|
||||||
|
<a-spin :indicator="customIndicator" />
|
||||||
|
</a-flex>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
const smallIndicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '16px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
const indicator = h(LoadingOutlined, {
|
const indicator = h(LoadingOutlined, {
|
||||||
style: {
|
style: {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
},
|
},
|
||||||
spin: true,
|
spin: true,
|
||||||
});
|
});
|
||||||
|
const largeIndicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '36px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
|
const customIndicator = h(LoadingOutlined, {
|
||||||
|
style: {
|
||||||
|
fontSize: '48px',
|
||||||
|
},
|
||||||
|
spin: true,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<docs>
|
||||||
|
---
|
||||||
|
order: 8
|
||||||
|
title:
|
||||||
|
zh-CN: 全屏
|
||||||
|
en-US: fullscreen
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
`fullscreen` 属性非常适合创建流畅的页面加载器。它添加了半透明覆盖层,并在其中心放置了一个旋转加载符号。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
The `fullscreen` mode is perfect for creating page loaders. It adds a dimmed overlay with a centered spinner.
|
||||||
|
|
||||||
|
</docs>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a-button @click="showLoader">Show fullscreen</a-button>
|
||||||
|
<a-spin :spinning="spinning" :percent="percent" fullscreen />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onUnmounted } from 'vue';
|
||||||
|
|
||||||
|
const spinning = ref(false);
|
||||||
|
const percent = ref(0);
|
||||||
|
let interval = null;
|
||||||
|
|
||||||
|
const showLoader = () => {
|
||||||
|
spinning.value = true;
|
||||||
|
let ptg = -10;
|
||||||
|
|
||||||
|
interval = setInterval(() => {
|
||||||
|
ptg += 5;
|
||||||
|
percent.value = ptg;
|
||||||
|
|
||||||
|
if (ptg > 120) {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
spinning.value = false;
|
||||||
|
percent.value = 0;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件卸载时清理定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -7,6 +7,8 @@
|
||||||
<tip />
|
<tip />
|
||||||
<delay />
|
<delay />
|
||||||
<custom-indicator />
|
<custom-indicator />
|
||||||
|
<fullscreen />
|
||||||
|
<percent />
|
||||||
</demo-sort>
|
</demo-sort>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -16,6 +18,8 @@ import Inside from './inside.vue';
|
||||||
import Nested from './nested.vue';
|
import Nested from './nested.vue';
|
||||||
import Tip from './tip.vue';
|
import Tip from './tip.vue';
|
||||||
import Delay from './delay.vue';
|
import Delay from './delay.vue';
|
||||||
|
import Fullscreen from './fullscreen.vue';
|
||||||
|
import Percent from './percent.vue';
|
||||||
import CustomIndicator from './custom-indicator.vue';
|
import CustomIndicator from './custom-indicator.vue';
|
||||||
import CN from '../index.zh-CN.md';
|
import CN from '../index.zh-CN.md';
|
||||||
import US from '../index.en-US.md';
|
import US from '../index.en-US.md';
|
||||||
|
@ -31,6 +35,8 @@ export default defineComponent({
|
||||||
Tip,
|
Tip,
|
||||||
Delay,
|
Delay,
|
||||||
CustomIndicator,
|
CustomIndicator,
|
||||||
|
Fullscreen,
|
||||||
|
Percent,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<docs>
|
||||||
|
---
|
||||||
|
order: 7
|
||||||
|
title:
|
||||||
|
zh-CN: 进度
|
||||||
|
en-US: progress
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
展示进度,当设置 `percent="auto"` 时会预估一个永远不会停止的进度条。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Show the progress. When `percent="auto"` is set, an indeterminate progress will be displayed.
|
||||||
|
|
||||||
|
</docs>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="display: flex; align-items: center; gap: 16px">
|
||||||
|
<!-- 开关组件 -->
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="auto"
|
||||||
|
checked-children="Auto"
|
||||||
|
un-checked-children="Auto"
|
||||||
|
@change="toggleAuto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 加载动画组件 -->
|
||||||
|
<a-spin :percent="mergedPercent" size="small" />
|
||||||
|
<a-spin :percent="mergedPercent" />
|
||||||
|
<a-spin :percent="mergedPercent" size="large" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onUnmounted, onMounted, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
|
const auto = ref(false); // 是否启用自动模式
|
||||||
|
const percent = ref(-50); // 当前加载百分比
|
||||||
|
let interval = null;
|
||||||
|
|
||||||
|
const mergedPercent = computed(() => (auto.value ? 'auto' : percent.value));
|
||||||
|
|
||||||
|
const toggleAuto = checked => {
|
||||||
|
auto.value = checked;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
percent.value = percent.value + 5;
|
||||||
|
if (percent.value > 150) {
|
||||||
|
percent.value = -50;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped></style>
|
|
@ -1,6 +1,6 @@
|
||||||
<docs>
|
<docs>
|
||||||
---
|
---
|
||||||
order: 4
|
order: 4
|
||||||
title:
|
title:
|
||||||
zh-CN: 自定义描述文案
|
zh-CN: 自定义描述文案
|
||||||
en-US: Customized description
|
en-US: Customized description
|
||||||
|
@ -17,10 +17,23 @@ Customized description content.
|
||||||
</docs>
|
</docs>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-spin tip="Loading...">
|
<a-flex gap="middle" vertical>
|
||||||
<a-alert
|
<a-flex gap="middle">
|
||||||
message="Alert message title"
|
<a-spin tip="Loading" size="small">
|
||||||
description="Further details about the context of this alert."
|
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
|
||||||
></a-alert>
|
</a-spin>
|
||||||
</a-spin>
|
<a-spin tip="Loading">
|
||||||
|
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
|
||||||
|
</a-spin>
|
||||||
|
<a-spin tip="Loading" size="large">
|
||||||
|
<div style="padding: 50px; background: rgba(0, 0, 0, 0.05); border-radius: 4px"></div>
|
||||||
|
</a-spin>
|
||||||
|
</a-flex>
|
||||||
|
<a-spin tip="Loading...">
|
||||||
|
<a-alert
|
||||||
|
message="Alert message title"
|
||||||
|
description="Further details about the context of this alert."
|
||||||
|
></a-alert>
|
||||||
|
</a-spin>
|
||||||
|
</a-flex>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -17,7 +17,9 @@ When part of the page is waiting for asynchronous data or during a rendering pro
|
||||||
| Property | Description | Type | Default Value | Version |
|
| Property | Description | Type | Default Value | Version |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| delay | specifies a delay in milliseconds for loading state (prevent flush) | number (milliseconds) | - | |
|
| delay | specifies a delay in milliseconds for loading state (prevent flush) | number (milliseconds) | - | |
|
||||||
|
| fullscreen | Display a backdrop with the `Spin` component | boolean | false | |
|
||||||
| indicator | vue node of the spinning indicator | vNode \|slot | - | |
|
| indicator | vue node of the spinning indicator | vNode \|slot | - | |
|
||||||
|
| percent | The progress percentage, when set to `auto`, it will be an indeterminate progress | number \| 'auto' | - | |
|
||||||
| size | size of Spin, options: `small`, `default` and `large` | string | `default` | |
|
| size | size of Spin, options: `small`, `default` and `large` | string | `default` | |
|
||||||
| spinning | whether Spin is visible | boolean | true | |
|
| spinning | whether Spin is visible | boolean | true | |
|
||||||
| tip | customize description content when Spin has children | string \| slot | - | slot 3.0 |
|
| tip | customize description content when Spin has children | string \| slot | - | slot 3.0 |
|
||||||
|
|
|
@ -18,7 +18,9 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*i43_ToFrL8YAAA
|
||||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - | |
|
| delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - | |
|
||||||
|
| fullscreen | 显示带有`Spin`组件的背景 | boolean | false | |
|
||||||
| indicator | 加载指示符 | vNode \| slot | - | |
|
| indicator | 加载指示符 | vNode \| slot | - | |
|
||||||
|
| percent | 展示进度,当设置`percent="auto"`时会预估一个永远不会停止的进度 | number \| 'auto' | - | |
|
||||||
| size | 组件大小,可选值为 `small` `default` `large` | string | `default` | |
|
| size | 组件大小,可选值为 `small` `default` `large` | string | `default` | |
|
||||||
| spinning | 是否为加载中状态 | boolean | true | |
|
| spinning | 是否为加载中状态 | boolean | true | |
|
||||||
| tip | 当作为包裹元素时,可以自定义描述文案 | string \| slot | - | slot 3.0 |
|
| tip | 当作为包裹元素时,可以自定义描述文案 | string \| slot | - | slot 3.0 |
|
||||||
|
|
|
@ -1,18 +1,34 @@
|
||||||
import type { CSSObject } from '../../_util/cssinjs';
|
import type { CSSObject } from '../../_util/cssinjs';
|
||||||
import { Keyframes } from '../../_util/cssinjs';
|
import { Keyframes } from '../../_util/cssinjs';
|
||||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||||
import { resetComponent } from '../../style';
|
import { resetComponent } from '../../style';
|
||||||
|
|
||||||
export interface ComponentToken {
|
export interface ComponentToken {
|
||||||
contentHeight: number;
|
/**
|
||||||
|
* @desc 内容区域高度
|
||||||
|
* @descEN Height of content area
|
||||||
|
*/
|
||||||
|
contentHeight: number | string;
|
||||||
|
/**
|
||||||
|
* @desc 加载图标尺寸
|
||||||
|
* @descEN Loading icon size
|
||||||
|
*/
|
||||||
|
dotSize: number;
|
||||||
|
/**
|
||||||
|
* @desc 小号加载图标尺寸
|
||||||
|
* @descEN Small loading icon size
|
||||||
|
*/
|
||||||
|
dotSizeSM: number;
|
||||||
|
/**
|
||||||
|
* @desc 大号加载图标尺寸
|
||||||
|
* @descEN Large loading icon size
|
||||||
|
*/
|
||||||
|
dotSizeLG: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpinToken extends FullToken<'Spin'> {
|
interface SpinToken extends FullToken<'Spin'> {
|
||||||
spinDotDefault: string;
|
spinDotDefault: string;
|
||||||
spinDotSize: number;
|
|
||||||
spinDotSizeSM: number;
|
|
||||||
spinDotSizeLG: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const antSpinMove = new Keyframes('antSpinMove', {
|
const antSpinMove = new Keyframes('antSpinMove', {
|
||||||
|
@ -23,219 +39,309 @@ const antRotate = new Keyframes('antRotate', {
|
||||||
to: { transform: 'rotate(405deg)' },
|
to: { transform: 'rotate(405deg)' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => ({
|
const genSpinStyle: GenerateStyle<SpinToken> = (token: SpinToken): CSSObject => {
|
||||||
[`${token.componentCls}`]: {
|
const { componentCls, calc } = token;
|
||||||
...resetComponent(token),
|
return {
|
||||||
position: 'absolute',
|
[componentCls]: {
|
||||||
display: 'none',
|
...resetComponent(token),
|
||||||
color: token.colorPrimary,
|
position: 'absolute',
|
||||||
textAlign: 'center',
|
display: 'none',
|
||||||
verticalAlign: 'middle',
|
color: token.colorPrimary,
|
||||||
opacity: 0,
|
fontSize: 0,
|
||||||
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
opacity: 0,
|
||||||
|
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOutCirc}`,
|
||||||
|
|
||||||
'&-spinning': {
|
'&-spinning': {
|
||||||
position: 'static',
|
position: 'relative',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
'&-nested-loading': {
|
[`${componentCls}-text`]: {
|
||||||
position: 'relative',
|
fontSize: token.fontSize,
|
||||||
[`> div > ${token.componentCls}`]: {
|
paddingTop: calc(calc(token.dotSize).sub(token.fontSize)).div(2).add(2).equal(),
|
||||||
position: 'absolute',
|
},
|
||||||
top: 0,
|
|
||||||
insetInlineStart: 0,
|
|
||||||
zIndex: 4,
|
|
||||||
display: 'block',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
maxHeight: token.contentHeight,
|
|
||||||
|
|
||||||
[`${token.componentCls}-dot`]: {
|
'&-fullscreen': {
|
||||||
position: 'absolute',
|
position: 'fixed',
|
||||||
top: '50%',
|
width: '100vw',
|
||||||
insetInlineStart: '50%',
|
height: '100vh',
|
||||||
margin: -token.spinDotSize / 2,
|
backgroundColor: token.colorBgMask,
|
||||||
|
zIndex: token.zIndexPopupBase,
|
||||||
|
inset: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
opacity: 0,
|
||||||
|
visibility: 'hidden',
|
||||||
|
transition: `all ${token.motionDurationMid}`,
|
||||||
|
'&-show': {
|
||||||
|
opacity: 1,
|
||||||
|
visibility: 'visible',
|
||||||
},
|
},
|
||||||
|
|
||||||
[`${token.componentCls}-text`]: {
|
[componentCls]: {
|
||||||
position: 'absolute',
|
[`${componentCls}-dot-holder`]: {
|
||||||
top: '50%',
|
color: token.colorWhite,
|
||||||
width: '100%',
|
|
||||||
paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2,
|
|
||||||
textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
|
|
||||||
},
|
|
||||||
|
|
||||||
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
|
|
||||||
marginTop: -(token.spinDotSize / 2) - 10,
|
|
||||||
},
|
|
||||||
|
|
||||||
'&-sm': {
|
|
||||||
[`${token.componentCls}-dot`]: {
|
|
||||||
margin: -token.spinDotSizeSM / 2,
|
|
||||||
},
|
},
|
||||||
[`${token.componentCls}-text`]: {
|
[`${componentCls}-text`]: {
|
||||||
paddingTop: (token.spinDotSizeSM - token.fontSize) / 2 + 2,
|
color: token.colorTextLightSolid,
|
||||||
},
|
|
||||||
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
|
|
||||||
marginTop: -(token.spinDotSizeSM / 2) - 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'&-lg': {
|
|
||||||
[`${token.componentCls}-dot`]: {
|
|
||||||
margin: -(token.spinDotSizeLG / 2),
|
|
||||||
},
|
|
||||||
[`${token.componentCls}-text`]: {
|
|
||||||
paddingTop: (token.spinDotSizeLG - token.fontSize) / 2 + 2,
|
|
||||||
},
|
|
||||||
[`&${token.componentCls}-show-text ${token.componentCls}-dot`]: {
|
|
||||||
marginTop: -(token.spinDotSizeLG / 2) - 10,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`${token.componentCls}-container`]: {
|
'&-nested-loading': {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
transition: `opacity ${token.motionDurationSlow}`,
|
[`> ${componentCls}`]: {
|
||||||
|
|
||||||
'&::after': {
|
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
insetInlineEnd: 0,
|
|
||||||
bottom: 0,
|
|
||||||
insetInlineStart: 0,
|
insetInlineStart: 0,
|
||||||
zIndex: 10,
|
zIndex: 4,
|
||||||
|
display: 'block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: token.colorBgContainer,
|
maxHeight: token.contentHeight,
|
||||||
opacity: 0,
|
[`${componentCls}-dot`]: {
|
||||||
transition: `all ${token.motionDurationSlow}`,
|
position: 'absolute',
|
||||||
content: '""',
|
top: '50%',
|
||||||
|
insetInlineStart: '50%',
|
||||||
|
margin: calc(token.dotSize).mul(-1).div(2).equal(),
|
||||||
|
},
|
||||||
|
|
||||||
|
[`${componentCls}-text`]: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
width: '100%',
|
||||||
|
// paddingTop: (token.spinDotSize - token.fontSize) / 2 + 2,
|
||||||
|
textShadow: `0 1px 2px ${token.colorBgContainer}`, // FIXME: shadow
|
||||||
|
},
|
||||||
|
|
||||||
|
[`&${componentCls}-show-text ${componentCls}-dot`]: {
|
||||||
|
marginTop: calc(token.dotSize).div(2).mul(-1).sub(10).equal(),
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-sm': {
|
||||||
|
[`${componentCls}-dot`]: {
|
||||||
|
margin: calc(token.dotSizeSM).mul(-1).div(2).equal(),
|
||||||
|
},
|
||||||
|
[`${componentCls}-text`]: {
|
||||||
|
paddingTop: calc(calc(token.dotSizeSM).sub(token.fontSize)).div(2).add(2).equal(),
|
||||||
|
},
|
||||||
|
[`&${componentCls}-show-text ${componentCls}-dot`]: {
|
||||||
|
marginTop: calc(token.dotSizeSM).div(2).mul(-1).sub(10).equal(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-lg': {
|
||||||
|
[`${componentCls}-dot`]: {
|
||||||
|
margin: calc(token.dotSizeLG).mul(-1).div(2).equal(),
|
||||||
|
},
|
||||||
|
[`${componentCls}-text`]: {
|
||||||
|
paddingTop: calc(calc(token.dotSizeLG).sub(token.fontSize)).div(2).add(2).equal(),
|
||||||
|
},
|
||||||
|
[`&${componentCls}-show-text ${componentCls}-dot`]: {
|
||||||
|
marginTop: calc(token.dotSizeLG).div(2).mul(-1).sub(10).equal(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`${componentCls}-container`]: {
|
||||||
|
position: 'relative',
|
||||||
|
transition: `opacity ${token.motionDurationSlow}`,
|
||||||
|
|
||||||
|
'&::after': {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
bottom: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
zIndex: 10,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background: token.colorBgContainer,
|
||||||
|
opacity: 0,
|
||||||
|
transition: `all ${token.motionDurationSlow}`,
|
||||||
|
content: '""',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`${componentCls}-blur`]: {
|
||||||
|
clear: 'both',
|
||||||
|
opacity: 0.5,
|
||||||
|
userSelect: 'none',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
|
||||||
|
[`&::after`]: {
|
||||||
|
opacity: 0.4,
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`${token.componentCls}-blur`]: {
|
// tip
|
||||||
clear: 'both',
|
// ------------------------------
|
||||||
opacity: 0.5,
|
[`&-tip`]: {
|
||||||
userSelect: 'none',
|
color: token.spinDotDefault,
|
||||||
pointerEvents: 'none',
|
|
||||||
|
|
||||||
[`&::after`]: {
|
|
||||||
opacity: 0.4,
|
|
||||||
pointerEvents: 'auto',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
// tip
|
// holder
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
[`&-tip`]: {
|
[`${componentCls}-dot-holder`]: {
|
||||||
color: token.spinDotDefault,
|
width: '1em',
|
||||||
},
|
height: '1em',
|
||||||
|
fontSize: token.dotSize,
|
||||||
// dots
|
display: 'inline-block',
|
||||||
// ------------------------------
|
transition: `transform ${token.motionDurationSlow} ease, opacity ${token.motionDurationSlow} ease`,
|
||||||
[`${token.componentCls}-dot`]: {
|
|
||||||
position: 'relative',
|
|
||||||
display: 'inline-block',
|
|
||||||
fontSize: token.spinDotSize,
|
|
||||||
width: '1em',
|
|
||||||
height: '1em',
|
|
||||||
|
|
||||||
'&-item': {
|
|
||||||
position: 'absolute',
|
|
||||||
display: 'block',
|
|
||||||
width: (token.spinDotSize - token.marginXXS / 2) / 2,
|
|
||||||
height: (token.spinDotSize - token.marginXXS / 2) / 2,
|
|
||||||
backgroundColor: token.colorPrimary,
|
|
||||||
borderRadius: '100%',
|
|
||||||
transform: 'scale(0.75)',
|
|
||||||
transformOrigin: '50% 50%',
|
transformOrigin: '50% 50%',
|
||||||
opacity: 0.3,
|
lineHeight: 1,
|
||||||
animationName: antSpinMove,
|
color: token.colorPrimary,
|
||||||
animationDuration: '1s',
|
|
||||||
animationIterationCount: 'infinite',
|
|
||||||
animationTimingFunction: 'linear',
|
|
||||||
animationDirection: 'alternate',
|
|
||||||
|
|
||||||
'&:nth-child(1)': {
|
'&-hidden': {
|
||||||
top: 0,
|
transform: 'scale(0.3)',
|
||||||
insetInlineStart: 0,
|
opacity: 0,
|
||||||
},
|
|
||||||
|
|
||||||
'&:nth-child(2)': {
|
|
||||||
top: 0,
|
|
||||||
insetInlineEnd: 0,
|
|
||||||
animationDelay: '0.4s',
|
|
||||||
},
|
|
||||||
|
|
||||||
'&:nth-child(3)': {
|
|
||||||
insetInlineEnd: 0,
|
|
||||||
bottom: 0,
|
|
||||||
animationDelay: '0.8s',
|
|
||||||
},
|
|
||||||
|
|
||||||
'&:nth-child(4)': {
|
|
||||||
bottom: 0,
|
|
||||||
insetInlineStart: 0,
|
|
||||||
animationDelay: '1.2s',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'&-spin': {
|
// progress
|
||||||
transform: 'rotate(45deg)',
|
// ------------------------------
|
||||||
animationName: antRotate,
|
[`${componentCls}-dot-progress`]: {
|
||||||
animationDuration: '1.2s',
|
position: 'absolute',
|
||||||
animationIterationCount: 'infinite',
|
inset: 0,
|
||||||
animationTimingFunction: 'linear',
|
},
|
||||||
|
|
||||||
|
// dots
|
||||||
|
// ------------------------------
|
||||||
|
[`${componentCls}-dot`]: {
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-block',
|
||||||
|
fontSize: token.dotSize,
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
|
||||||
|
'&-item': {
|
||||||
|
position: 'absolute',
|
||||||
|
display: 'block',
|
||||||
|
width: calc(token.dotSize).sub(calc(token.marginXXS).div(2)).div(2).equal(),
|
||||||
|
height: calc(token.dotSize).sub(calc(token.marginXXS).div(2)).div(2).equal(),
|
||||||
|
backgroundColor: token.colorPrimary,
|
||||||
|
borderRadius: '100%',
|
||||||
|
transform: 'scale(0.75)',
|
||||||
|
transformOrigin: '50% 50%',
|
||||||
|
opacity: 0.3,
|
||||||
|
animationName: antSpinMove,
|
||||||
|
animationDuration: '1s',
|
||||||
|
animationIterationCount: 'infinite',
|
||||||
|
animationTimingFunction: 'linear',
|
||||||
|
animationDirection: 'alternate',
|
||||||
|
|
||||||
|
'&:nth-child(1)': {
|
||||||
|
top: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:nth-child(2)': {
|
||||||
|
top: 0,
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
animationDelay: '0.4s',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:nth-child(3)': {
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
bottom: 0,
|
||||||
|
animationDelay: '0.8s',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:nth-child(4)': {
|
||||||
|
bottom: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
animationDelay: '1.2s',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-spin': {
|
||||||
|
transform: 'rotate(45deg)',
|
||||||
|
animationName: antRotate,
|
||||||
|
animationDuration: '1.2s',
|
||||||
|
animationIterationCount: 'infinite',
|
||||||
|
animationTimingFunction: 'linear',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-circle': {
|
||||||
|
strokeLinecap: 'round',
|
||||||
|
transition: ['stroke-dashoffset', 'stroke-dasharray', 'stroke', 'stroke-width', 'opacity']
|
||||||
|
.map(item => `${item} ${token.motionDurationSlow} ease`)
|
||||||
|
.join(','),
|
||||||
|
fillOpacity: 0,
|
||||||
|
stroke: 'currentcolor',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&-circle-bg': {
|
||||||
|
stroke: token.colorFillSecondary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
[`&-sm ${componentCls}-dot`]: {
|
||||||
|
'&, &-holder': {
|
||||||
|
fontSize: token.dotSizeSM,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// small
|
||||||
|
[`&-sm ${componentCls}-dot-holder`]: {
|
||||||
|
i: {
|
||||||
|
width: calc(calc(token.dotSizeSM).sub(calc(token.marginXXS).div(2)))
|
||||||
|
.div(2)
|
||||||
|
.equal(),
|
||||||
|
height: calc(calc(token.dotSizeSM).sub(calc(token.marginXXS).div(2)))
|
||||||
|
.div(2)
|
||||||
|
.equal(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// large
|
||||||
|
[`&-lg ${componentCls}-dot`]: {
|
||||||
|
'&, &-holder': {
|
||||||
|
fontSize: token.dotSizeLG,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[`&-lg ${componentCls}-dot-holder`]: {
|
||||||
|
i: {
|
||||||
|
width: calc(calc(token.dotSizeLG).sub(token.marginXXS)).div(2).equal(),
|
||||||
|
height: calc(calc(token.dotSizeLG).sub(token.marginXXS)).div(2).equal(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[`&${componentCls}-show-text ${componentCls}-text`]: {
|
||||||
|
display: 'block',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Sizes
|
export const prepareComponentToken: GetDefaultToken<'Spin'> = token => {
|
||||||
// ------------------------------
|
const { controlHeightLG, controlHeight } = token;
|
||||||
|
return {
|
||||||
// small
|
contentHeight: 400,
|
||||||
[`&-sm ${token.componentCls}-dot`]: {
|
dotSize: controlHeightLG / 2,
|
||||||
fontSize: token.spinDotSizeSM,
|
dotSizeSM: controlHeightLG * 0.35,
|
||||||
|
dotSizeLG: controlHeight,
|
||||||
i: {
|
};
|
||||||
width: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
|
};
|
||||||
height: (token.spinDotSizeSM - token.marginXXS / 2) / 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// large
|
|
||||||
[`&-lg ${token.componentCls}-dot`]: {
|
|
||||||
fontSize: token.spinDotSizeLG,
|
|
||||||
|
|
||||||
i: {
|
|
||||||
width: (token.spinDotSizeLG - token.marginXXS) / 2,
|
|
||||||
height: (token.spinDotSizeLG - token.marginXXS) / 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
[`&${token.componentCls}-show-text ${token.componentCls}-text`]: {
|
|
||||||
display: 'block',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================== Export ==============================
|
// ============================== Export ==============================
|
||||||
export default genComponentStyleHook(
|
export default genStyleHooks(
|
||||||
'Spin',
|
'Spin',
|
||||||
token => {
|
token => {
|
||||||
const spinToken = mergeToken<SpinToken>(token, {
|
const spinToken = mergeToken<SpinToken>(token, {
|
||||||
spinDotDefault: token.colorTextDescription,
|
spinDotDefault: token.colorTextDescription,
|
||||||
spinDotSize: token.controlHeightLG / 2,
|
|
||||||
spinDotSizeSM: token.controlHeightLG * 0.35,
|
|
||||||
spinDotSizeLG: token.controlHeight,
|
|
||||||
});
|
});
|
||||||
return [genSpinStyle(spinToken)];
|
return [genSpinStyle(spinToken)];
|
||||||
},
|
},
|
||||||
{
|
prepareComponentToken,
|
||||||
contentHeight: 400,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { ref, computed, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
const AUTO_INTERVAL = 200;
|
||||||
|
const STEP_BUCKETS: [limit: number, stepPtg: number][] = [
|
||||||
|
[30, 0.05],
|
||||||
|
[70, 0.03],
|
||||||
|
[96, 0.01],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function usePercent(spinning: boolean, percent?: number | 'auto') {
|
||||||
|
const mockPercent = ref(0);
|
||||||
|
const mockIntervalRef = ref<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|
||||||
|
const isAuto = ref(percent === 'auto');
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
// 清除现有定时器
|
||||||
|
if (mockIntervalRef.value || !isAuto.value || !spinning) {
|
||||||
|
clearInterval(mockIntervalRef.value);
|
||||||
|
mockIntervalRef.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAuto.value && spinning) {
|
||||||
|
mockPercent.value = 0;
|
||||||
|
|
||||||
|
mockIntervalRef.value = setInterval(() => {
|
||||||
|
mockPercent.value = calculateNextPercent(mockPercent.value);
|
||||||
|
}, AUTO_INTERVAL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return computed(() => (isAuto.value ? mockPercent.value : +percent));
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateNextPercent(prev: number): number {
|
||||||
|
const restPTG = 100 - prev;
|
||||||
|
|
||||||
|
for (let i = 0; i < STEP_BUCKETS.length; i += 1) {
|
||||||
|
const [limit, stepPtg] = STEP_BUCKETS[i];
|
||||||
|
if (prev <= limit) {
|
||||||
|
return prev + restPTG * stepPtg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}
|
Loading…
Reference in New Issue