committed by
GitHub
6 changed files with 366 additions and 0 deletions
@ -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'; |
|||
@ -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; |
|||
} |
|||
@ -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