refactor: new CountTo
component with demo (#5551)
This commit is contained in:
parent
cddf71e600
commit
24bad09c74
123
packages/effects/common-ui/src/components/count-to/count-to.vue
Normal file
123
packages/effects/common-ui/src/components/count-to/count-to.vue
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CountToProps } from './types';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import { TransitionPresets, useTransition } from '@vueuse/core';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CountToProps>(), {
|
||||||
|
startVal: 0,
|
||||||
|
duration: 2000,
|
||||||
|
separator: ',',
|
||||||
|
decimal: '.',
|
||||||
|
decimals: 0,
|
||||||
|
delay: 0,
|
||||||
|
transition: () => TransitionPresets.easeOutExpo,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['started', 'finished']);
|
||||||
|
|
||||||
|
const lastValue = ref(props.startVal);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
lastValue.value = props.endVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.endVal,
|
||||||
|
(val) => {
|
||||||
|
lastValue.value = val;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentValue = useTransition(lastValue, {
|
||||||
|
delay: computed(() => props.delay),
|
||||||
|
duration: computed(() => props.duration),
|
||||||
|
disabled: computed(() => props.disabled),
|
||||||
|
transition: computed(() => {
|
||||||
|
return isString(props.transition)
|
||||||
|
? TransitionPresets[props.transition]
|
||||||
|
: props.transition;
|
||||||
|
}),
|
||||||
|
onStarted() {
|
||||||
|
emit('started');
|
||||||
|
},
|
||||||
|
onFinished() {
|
||||||
|
emit('finished');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const numMain = computed(() => {
|
||||||
|
const result = currentValue.value
|
||||||
|
.toFixed(props.decimals)
|
||||||
|
.split('.')[0]
|
||||||
|
?.replaceAll(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const numDec = computed(() => {
|
||||||
|
return (
|
||||||
|
props.decimal + currentValue.value.toFixed(props.decimals).split('.')[1]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="count-to" v-bind="$attrs">
|
||||||
|
<slot name="prefix">
|
||||||
|
<div
|
||||||
|
class="count-to-prefix"
|
||||||
|
:style="prefixStyle"
|
||||||
|
:class="prefixClass"
|
||||||
|
v-if="prefix"
|
||||||
|
>
|
||||||
|
{{ prefix }}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
<div class="count-to-main" :class="mainClass" :style="mainStyle">
|
||||||
|
<span>{{ numMain }}</span>
|
||||||
|
<span
|
||||||
|
class="count-to-main-decimal"
|
||||||
|
v-if="decimals > 0"
|
||||||
|
:class="decimalClass"
|
||||||
|
:style="decimalStyle"
|
||||||
|
>
|
||||||
|
{{ numDec }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<slot name="suffix">
|
||||||
|
<div
|
||||||
|
class="count-to-suffix"
|
||||||
|
:style="suffixStyle"
|
||||||
|
:class="suffixClass"
|
||||||
|
v-if="suffix"
|
||||||
|
>
|
||||||
|
{{ suffix }}
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.count-to {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
&-prefix {
|
||||||
|
// font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-suffix {
|
||||||
|
// font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
// font-size: 1.5rem;
|
||||||
|
|
||||||
|
&-decimal {
|
||||||
|
// font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,2 @@
|
|||||||
|
export { default as CountTo } from './count-to.vue';
|
||||||
|
export * from './types';
|
53
packages/effects/common-ui/src/components/count-to/types.ts
Normal file
53
packages/effects/common-ui/src/components/count-to/types.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { CubicBezierPoints, EasingFunction } from '@vueuse/core';
|
||||||
|
|
||||||
|
import type { StyleValue } from 'vue';
|
||||||
|
|
||||||
|
import { TransitionPresets as TransitionPresetsData } from '@vueuse/core';
|
||||||
|
|
||||||
|
export type TransitionPresets = keyof typeof TransitionPresetsData;
|
||||||
|
|
||||||
|
export const TransitionPresetsKeys = Object.keys(
|
||||||
|
TransitionPresetsData,
|
||||||
|
) as TransitionPresets[];
|
||||||
|
|
||||||
|
export interface CountToProps {
|
||||||
|
/** 初始值 */
|
||||||
|
startVal?: number;
|
||||||
|
/** 当前值 */
|
||||||
|
endVal: number;
|
||||||
|
/** 是否禁用动画 */
|
||||||
|
disabled?: boolean;
|
||||||
|
/** 延迟动画开始的时间 */
|
||||||
|
delay?: number;
|
||||||
|
/** 持续时间 */
|
||||||
|
duration?: number;
|
||||||
|
/** 小数位数 */
|
||||||
|
decimals?: number;
|
||||||
|
/** 小数点 */
|
||||||
|
decimal?: string;
|
||||||
|
/** 分隔符 */
|
||||||
|
separator?: string;
|
||||||
|
/** 前缀 */
|
||||||
|
prefix?: string;
|
||||||
|
/** 后缀 */
|
||||||
|
suffix?: string;
|
||||||
|
/** 过渡效果 */
|
||||||
|
transition?: CubicBezierPoints | EasingFunction | TransitionPresets;
|
||||||
|
/** 整数部分的类名 */
|
||||||
|
mainClass?: string;
|
||||||
|
/** 小数部分的类名 */
|
||||||
|
decimalClass?: string;
|
||||||
|
/** 前缀部分的类名 */
|
||||||
|
prefixClass?: string;
|
||||||
|
/** 后缀部分的类名 */
|
||||||
|
suffixClass?: string;
|
||||||
|
|
||||||
|
/** 整数部分的样式 */
|
||||||
|
mainStyle?: StyleValue;
|
||||||
|
/** 小数部分的样式 */
|
||||||
|
decimalStyle?: StyleValue;
|
||||||
|
/** 前缀部分的样式 */
|
||||||
|
prefixStyle?: StyleValue;
|
||||||
|
/** 后缀部分的样式 */
|
||||||
|
suffixStyle?: StyleValue;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
export * from './api-component';
|
export * from './api-component';
|
||||||
export * from './captcha';
|
export * from './captcha';
|
||||||
export * from './col-page';
|
export * from './col-page';
|
||||||
|
export * from './count-to';
|
||||||
export * from './ellipsis-text';
|
export * from './ellipsis-text';
|
||||||
export * from './icon-picker';
|
export * from './icon-picker';
|
||||||
export * from './json-viewer';
|
export * from './json-viewer';
|
||||||
|
@ -273,6 +273,15 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: 'Motion',
|
title: 'Motion',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'CountTo',
|
||||||
|
path: '/examples/count-to',
|
||||||
|
component: () => import('#/views/examples/count-to/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'mdi:animation-play',
|
||||||
|
title: 'CountTo',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
178
playground/src/views/examples/count-to/index.vue
Normal file
178
playground/src/views/examples/count-to/index.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CountToProps, TransitionPresets } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
import { CountTo, Page, TransitionPresetsKeys } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
const props = reactive<CountToProps & { transition: TransitionPresets }>({
|
||||||
|
decimal: '.',
|
||||||
|
decimals: 2,
|
||||||
|
decimalStyle: {
|
||||||
|
fontSize: 'small',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
},
|
||||||
|
delay: 0,
|
||||||
|
disabled: false,
|
||||||
|
duration: 2000,
|
||||||
|
endVal: 100_000,
|
||||||
|
mainStyle: {
|
||||||
|
color: 'hsl(var(--primary))',
|
||||||
|
fontSize: 'xx-large',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
prefix: '¥',
|
||||||
|
prefixStyle: {
|
||||||
|
paddingRight: '0.5rem',
|
||||||
|
},
|
||||||
|
separator: ',',
|
||||||
|
startVal: 0,
|
||||||
|
suffix: '元',
|
||||||
|
suffixStyle: {
|
||||||
|
paddingLeft: '0.5rem',
|
||||||
|
},
|
||||||
|
transition: 'easeOutQuart',
|
||||||
|
});
|
||||||
|
|
||||||
|
function changeNumber() {
|
||||||
|
props.endVal =
|
||||||
|
Math.floor(Math.random() * 100_000_000) / 10 ** (props.decimals || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDocumentation() {
|
||||||
|
window.open('https://vueuse.org/core/useTransition/', '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStarted() {
|
||||||
|
message.loading({
|
||||||
|
content: '动画已开始',
|
||||||
|
duration: 0,
|
||||||
|
key: 'animator-info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFinished() {
|
||||||
|
message.success({
|
||||||
|
content: '动画已结束',
|
||||||
|
duration: 2,
|
||||||
|
key: 'animator-info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page title="CountTo" description="数字滚动动画组件。使用">
|
||||||
|
<template #description>
|
||||||
|
<span>
|
||||||
|
使用useTransition封装的数字滚动动画组件,每次改变当前值都会产生过渡动画。
|
||||||
|
</span>
|
||||||
|
<Button type="link" @click="openDocumentation">
|
||||||
|
查看useTransition文档
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<Card title="基本用法">
|
||||||
|
<div class="flex w-full items-center justify-center pb-4">
|
||||||
|
<CountTo v-bind="props" @started="onStarted" @finished="onFinished" />
|
||||||
|
</div>
|
||||||
|
<Form :model="props">
|
||||||
|
<Row :gutter="20">
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="初始值" name="startVal">
|
||||||
|
<InputNumber v-model:value="props.startVal" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="当前值" name="endVal">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="props.endVal"
|
||||||
|
class="w-full"
|
||||||
|
:precision="props.decimals"
|
||||||
|
>
|
||||||
|
<template #addonAfter>
|
||||||
|
<IconifyIcon
|
||||||
|
v-tippy="`设置一个随机值`"
|
||||||
|
class="size-5 cursor-pointer outline-none"
|
||||||
|
icon="ix:random-filled"
|
||||||
|
@click="changeNumber"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</InputNumber>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="禁用动画" name="disabled">
|
||||||
|
<Switch v-model:checked="props.disabled" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="延迟动画" name="delay">
|
||||||
|
<InputNumber v-model:value="props.delay" :min="0" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="持续时间" name="duration">
|
||||||
|
<InputNumber v-model:value="props.duration" :min="0" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="小数位数" name="decimals">
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="props.decimals"
|
||||||
|
:min="0"
|
||||||
|
:precision="0"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="分隔符" name="separator">
|
||||||
|
<Input v-model:value="props.separator" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="小数点" name="decimal">
|
||||||
|
<Input v-model:value="props.decimal" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="动画" name="transition">
|
||||||
|
<Select v-model:value="props.transition">
|
||||||
|
<Select.Option
|
||||||
|
v-for="preset in TransitionPresetsKeys"
|
||||||
|
:key="preset"
|
||||||
|
:value="preset"
|
||||||
|
>
|
||||||
|
{{ preset }}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="前缀" name="prefix">
|
||||||
|
<Input v-model:value="props.prefix" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<FormItem label="后缀" name="suffix">
|
||||||
|
<Input v-model:value="props.suffix" />
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
Loading…
Reference in New Issue
Block a user