refactor: new CountTo component with demo (#5551)

This commit is contained in:
Netfan 2025-02-17 21:16:10 +08:00 committed by GitHub
parent cddf71e600
commit 24bad09c74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 366 additions and 0 deletions

View 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>

View File

@ -0,0 +1,2 @@
export { default as CountTo } from './count-to.vue';
export * from './types';

View 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;
}

View File

@ -1,6 +1,7 @@
export * from './api-component';
export * from './captcha';
export * from './col-page';
export * from './count-to';
export * from './ellipsis-text';
export * from './icon-picker';
export * from './json-viewer';

View File

@ -273,6 +273,15 @@ const routes: RouteRecordRaw[] = [
title: 'Motion',
},
},
{
name: 'CountTo',
path: '/examples/count-to',
component: () => import('#/views/examples/count-to/index.vue'),
meta: {
icon: 'mdi:animation-play',
title: 'CountTo',
},
},
],
},
];

View 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>