feat(number-animation): Add NumberAnimation component (#3301)
* feat(number-animation): [number-animation] Add NumberAnimation component * feat(number-animation): [number-animation] Add NumberAnimation component * feat(number-animation): [number-animation] Add NumberAnimation component * feat(number-animation): [number-animation] Add NumberAnimation component * feat(number-animation): [number-animation] Add NumberAnimation component * feat(number-animation): [number-animation] Add NumberAnimation component
This commit is contained in:
parent
f98b9367d7
commit
b76c76d183
|
@ -0,0 +1,104 @@
|
|||
export default {
|
||||
mode: ['pc'],
|
||||
apis: [
|
||||
{
|
||||
name: 'number-animation',
|
||||
type: 'component',
|
||||
props: [
|
||||
{
|
||||
name: 'active',
|
||||
type: 'boolean',
|
||||
defaultValue: 'true',
|
||||
desc: {
|
||||
'zh-CN': '是否开始动画',
|
||||
'en-US': 'Whether or not start animation'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'basic-usage'
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
type: 'number',
|
||||
defaultValue: '3000',
|
||||
desc: {
|
||||
'zh-CN': '动画持续时间',
|
||||
'en-US': 'Animation duration'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'basic-usage'
|
||||
},
|
||||
{
|
||||
name: 'from',
|
||||
type: 'number',
|
||||
defaultValue: '0',
|
||||
desc: {
|
||||
'zh-CN': '数值动画起始值',
|
||||
'en-US': 'Starting value of numerical animation'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'basic-usage'
|
||||
},
|
||||
{
|
||||
name: 'to',
|
||||
type: 'number',
|
||||
defaultValue: '',
|
||||
desc: {
|
||||
'zh-CN': '目标值',
|
||||
'en-US': 'Target value'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'basic-usage'
|
||||
},
|
||||
{
|
||||
name: 'precision',
|
||||
type: 'number',
|
||||
defaultValue: '0',
|
||||
desc: {
|
||||
'zh-CN': '精度,保留小数点后几位',
|
||||
'en-US': 'Precision, rounded to a few decimal places.'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'precision'
|
||||
},
|
||||
{
|
||||
name: 'separator',
|
||||
type: 'string',
|
||||
defaultValue: ',',
|
||||
desc: {
|
||||
'zh-CN': '千分位分隔符',
|
||||
'en-US': 'Thousandth separator'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'separator'
|
||||
}
|
||||
],
|
||||
events: [
|
||||
{
|
||||
name: 'finish',
|
||||
type: '() => void',
|
||||
defaultValue: '',
|
||||
desc: {
|
||||
'zh-CN': '动画结束后的回调',
|
||||
'en-US': 'The callback after the animation ends.'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'finish-events'
|
||||
}
|
||||
],
|
||||
methods: [
|
||||
{
|
||||
name: 'play',
|
||||
type: '() => void',
|
||||
defaultValue: '',
|
||||
desc: {
|
||||
'zh-CN': '播放动画',
|
||||
'en-US': 'Play Animation'
|
||||
},
|
||||
mode: ['pc'],
|
||||
pcDemo: 'finish-events'
|
||||
}
|
||||
],
|
||||
slots: []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
const numberAnimationRef = ref(null)
|
||||
const fromVal = ref(0)
|
||||
const toVal = ref(12309)
|
||||
function handleClick() {
|
||||
numberAnimationRef.value?.play()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,8 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('基本用法', async ({ page }) => {
|
||||
page.on('pageerror', (exception) => expect(exception).toBeNull()) // 断言页面上不出现错误
|
||||
await page.goto('number-animation#basic-usage') // 要测试的示例的相对地址
|
||||
await page.waitForTimeout(1000)
|
||||
await page.locator('#basic-usage').getByText('12,039')
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script lang="jsx">
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyButton,
|
||||
TinyNumberAnimation
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fromVal: 0,
|
||||
toVal: 12039
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$refs.numberAnimationRef.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" @finish="handleFinish" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref } from 'vue'
|
||||
import { TinyButton, TinyNumberAnimation, TinyModal } from '@opentiny/vue'
|
||||
|
||||
const numberAnimationRef = ref(null)
|
||||
const fromVal = ref(0)
|
||||
const toVal = ref(900)
|
||||
|
||||
function handleClick() {
|
||||
numberAnimationRef.value?.play()
|
||||
}
|
||||
function handleFinish() {
|
||||
TinyModal.message({ message: '动画结束了', status: 'info' })
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,11 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('动画播放完成', async ({ page }) => {
|
||||
page.on('pageerror', (exception) => expect(exception).toBeNull())
|
||||
await page.goto('number-animation#finish-events')
|
||||
await page.getByRole('button', { name: '播放' }).click()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.locator('#finish-events').getByText('900')
|
||||
const messageLocator = page.locator('.tiny-modal__box').filter({ hasText: '动画结束了' })
|
||||
await expect(messageLocator).toBeVisible()
|
||||
})
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" @finish="handleFinish" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script lang="jsx">
|
||||
import { TinyButton, TinyNumberAnimation, TinyModal } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyButton,
|
||||
TinyNumberAnimation
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fromVal: 0,
|
||||
toVal: 900
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$refs.numberAnimationRef.play()
|
||||
},
|
||||
handleFinish() {
|
||||
TinyModal.message({ message: '动画结束了', status: 'info' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" :precision="4" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref } from 'vue'
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
const numberAnimationRef = ref(null)
|
||||
const fromVal = ref(0.0)
|
||||
const toVal = ref(24)
|
||||
function handleClick() {
|
||||
numberAnimationRef.value?.play()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('精度', async ({ page }) => {
|
||||
page.on('pageerror', (exception) => expect(exception).toBeNull()) // 断言页面上不出现错误
|
||||
await page.goto('number-animation#precision') // 要测试的示例的相对地址
|
||||
await page.getByRole('button', { name: /播放/ }).click()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.locator('#precision').getByText('24.0000')
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" :precision="4" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script lang="jsx">
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyButton,
|
||||
TinyNumberAnimation
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fromVal: 0.0,
|
||||
toVal: 24
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$refs.numberAnimationRef.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { ref } from 'vue'
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
const numberAnimationRef = ref(null)
|
||||
const fromVal = ref(0)
|
||||
const toVal = ref(100000000)
|
||||
|
||||
function handleClick() {
|
||||
numberAnimationRef.value?.play()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('分隔符', async ({ page }) => {
|
||||
page.on('pageerror', (exception) => expect(exception).toBeNull()) // 断言页面上不出现错误
|
||||
await page.goto('number-animation#separator') // 要测试的示例的相对地址
|
||||
await page.getByRole('button', { name: '播放', exact: true }).click()
|
||||
await page.waitForTimeout(1000)
|
||||
await page.locator('#separator').getByText('100,000,000')
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<tiny-number-animation ref="numberAnimationRef" :from="fromVal" :to="toVal" :active="false" />
|
||||
<tiny-button @click="handleClick">播放</tiny-button>
|
||||
</template>
|
||||
|
||||
<script lang="jsx">
|
||||
import { TinyButton, TinyNumberAnimation } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyButton,
|
||||
TinyNumberAnimation
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fromVal: 0,
|
||||
toVal: 100000000
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$refs.numberAnimationRef.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title:
|
||||
---
|
||||
|
||||
# Number Animation 数值动画
|
||||
|
||||
<div>数值播放动画</div>
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title:
|
||||
---
|
||||
|
||||
# Number Animation 数值动画
|
||||
|
||||
<div>Numerical playback animation</div>
|
|
@ -0,0 +1,55 @@
|
|||
export default {
|
||||
column: '1',
|
||||
owner: '',
|
||||
demos: [
|
||||
{
|
||||
demoId: 'basic-usage',
|
||||
name: {
|
||||
'zh-CN': '基本用法',
|
||||
'en-US': 'Basic Usage'
|
||||
},
|
||||
desc: {
|
||||
'zh-CN': '通过 <code>from</code> 设置数值动画起始值;<code>to</code>设置目标值。',
|
||||
'en-US': 'Set the starting value of numerical animation through<code>from</code>, <code>to</code>设置目标值.'
|
||||
},
|
||||
codeFiles: ['basic-usage.vue']
|
||||
},
|
||||
{
|
||||
demoId: 'precision',
|
||||
name: {
|
||||
'zh-CN': '精度',
|
||||
'en-US': 'Precision Mode'
|
||||
},
|
||||
desc: {
|
||||
'zh-CN': '通过 <code>precision</code> 设置 设定精度。',
|
||||
'en-US': 'Set precision through<code>precision</code>.'
|
||||
},
|
||||
codeFiles: ['precision.vue']
|
||||
},
|
||||
{
|
||||
demoId: 'separator',
|
||||
name: {
|
||||
'zh-CN': '分隔符',
|
||||
'en-US': 'Separator Mode'
|
||||
},
|
||||
desc: {
|
||||
'zh-CN': '通过 <code>separator</code> 设置分隔符。',
|
||||
'en-US': 'Set delimiter through<code>separator</code>.'
|
||||
},
|
||||
codeFiles: ['separator.vue']
|
||||
},
|
||||
|
||||
{
|
||||
demoId: 'finish-events',
|
||||
name: {
|
||||
'zh-CN': '动画结束事件',
|
||||
'en-US': 'Finish Event'
|
||||
},
|
||||
desc: {
|
||||
'zh-CN': '通过 <code>finish</code> 自定义动画结束后的事件',
|
||||
'en-US': 'Customize the events after the animation ends through<code>finish</code>.'
|
||||
},
|
||||
codeFiles: ['finish-events.vue']
|
||||
}
|
||||
]
|
||||
}
|
|
@ -249,7 +249,8 @@ export const cmpMenus = [
|
|||
}
|
||||
},
|
||||
{ 'nameCn': '用户头像', 'name': 'UserHead', 'key': 'user-head' },
|
||||
{ 'nameCn': '流程图', 'name': 'Wizard', 'key': 'wizard' }
|
||||
{ 'nameCn': '流程图', 'name': 'Wizard', 'key': 'wizard' },
|
||||
{ 'nameCn': '数值动画', 'name': 'NumberAnimation', key: 'number-animation' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1708,6 +1708,19 @@
|
|||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"NumberAnimation": {
|
||||
"path": "vue/src/number-animation/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false,
|
||||
"mode": [
|
||||
"pc"
|
||||
]
|
||||
},
|
||||
"NumberAnimationPc": {
|
||||
"path": "vue/src/number-animation/src/pc.vue",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"Option": {
|
||||
"path": "vue/src/option/index.ts",
|
||||
"type": "component",
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
export const onFinish =
|
||||
({ emit, props, state }) =>
|
||||
() => {
|
||||
state.value = props.to
|
||||
state.animating = false
|
||||
emit('finish')
|
||||
}
|
||||
|
||||
const easeOut = (t: number): number => 1 - (1 - t) ** 5
|
||||
|
||||
export const play =
|
||||
({ props, state, api }) =>
|
||||
() => {
|
||||
animate(state, props, api)
|
||||
}
|
||||
export const animate = (state, props, api) => {
|
||||
state.animating = true
|
||||
state.value = props.from
|
||||
if (props.from !== props.to) {
|
||||
const startTime = performance.now()
|
||||
|
||||
const tick = () => {
|
||||
const current = performance.now()
|
||||
const elapsedTime = Math.min(current - startTime, props.duration)
|
||||
const currentValue = props.from + (props.to - props.from) * easeOut(elapsedTime / props.duration)
|
||||
if (elapsedTime === props.duration) {
|
||||
api.onFinish()
|
||||
return
|
||||
}
|
||||
state.value = currentValue
|
||||
requestAnimationFrame(tick)
|
||||
}
|
||||
tick()
|
||||
}
|
||||
}
|
||||
|
||||
export const formattedValue =
|
||||
({ state, props }) =>
|
||||
() => {
|
||||
// 类型检查
|
||||
if (typeof state.value !== 'number' && typeof state.value !== 'string') return
|
||||
if (typeof props.precision !== 'number') return
|
||||
const numValue = Number(state.value)
|
||||
if (isNaN(numValue) || !isFinite(numValue)) return
|
||||
if (numValue === 0) {
|
||||
return numValue.toFixed(props.precision)
|
||||
}
|
||||
let formatValue = numValue.toFixed(props.precision)
|
||||
if (typeof props.separator === 'string' && props.separator !== '') {
|
||||
const [integerPart, decimalPart] = formatValue.split('.')
|
||||
formatValue =
|
||||
integerPart.replace(/(\d)(?=(\d{3})+$)/g, '$1' + props.separator) + (decimalPart ? '.' + decimalPart : '')
|
||||
}
|
||||
return formatValue
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { play, formattedValue, onFinish } from './index'
|
||||
|
||||
export const api = ['state', 'play', 'formattedValue', 'onFinish']
|
||||
export const renderless = (props, { onMounted, computed, reactive }, { emit }) => {
|
||||
const api = {}
|
||||
|
||||
const state = reactive({
|
||||
animating: true,
|
||||
value: props.from,
|
||||
showValue: computed(() => api.formattedValue(state, props))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.active) {
|
||||
api.play(props, state)
|
||||
}
|
||||
})
|
||||
Object.assign(api, {
|
||||
state,
|
||||
play: play({ props, state, api }),
|
||||
formattedValue: formattedValue({ state, props }),
|
||||
onFinish: onFinish({ emit, props, state })
|
||||
})
|
||||
|
||||
return api
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
@import '../custom.less';
|
||||
@import './vars.less';
|
||||
|
||||
@number-animation-item-prefix-cls: ~'@{css-prefix}number-animation';
|
||||
|
||||
.@{number-animation-item-prefix-cls} {
|
||||
.inject-NumberAnimation-vars();
|
||||
font-size: var(--tv-NumberAnimation-font-size);
|
||||
font-weight: var(--tv-NumberAnimation-font-weight);
|
||||
margin-bottom: var(--tv-NumberAnimation-margin-bottom);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.inject-NumberAnimation-vars() {
|
||||
// 数字内容下间距
|
||||
--tv-NumberAnimation-margin-bottom: 20px;
|
||||
// 数字内容字体粗细
|
||||
--tv-NumberAnimation-font-weight: var(--tv-font-weight-regular);
|
||||
// 数字内容字体
|
||||
--tv-NumberAnimation-font-size: var(--tv-font-size-heading-lg);
|
||||
}
|
|
@ -118,7 +118,7 @@
|
|||
font-size: var(--tv-Table-icon-font-size);
|
||||
border-radius: var(--tv-Table-check-icon-border-radius);
|
||||
& path:last-child{
|
||||
fill: var(--tv-Table-border-color);
|
||||
fill: var(--tv-Table-icon-border-color);
|
||||
}
|
||||
|
||||
& path:first-child {
|
||||
|
|
|
@ -28,7 +28,9 @@
|
|||
// 表格单元格字体大小
|
||||
--tv-Table-td-font-size: var(--tv-font-size-default, 14px);
|
||||
// 表格边框颜色
|
||||
--tv-Table-border-color: var(--tv-color-border-divider, #f0f0f0);
|
||||
--tv-Table-border-color: var(--tv-color-border-divider);
|
||||
// 表格复选框边框颜色
|
||||
--tv-Table-icon-border-color: var(--tv-color-border);
|
||||
// 表头背景颜色
|
||||
--tv-Table-thead-bg-color: var(--tv-color-bg-header, #f5f5f5);
|
||||
// 表格图标字体大小
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
"@opentiny/vue-month-table": "workspace:~",
|
||||
"@opentiny/vue-nav-menu": "workspace:~",
|
||||
"@opentiny/vue-notify": "workspace:~",
|
||||
"@opentiny/vue-number-animation": "workspace:~",
|
||||
"@opentiny/vue-numeric": "workspace:~",
|
||||
"@opentiny/vue-option": "workspace:~",
|
||||
"@opentiny/vue-option-group": "workspace:~",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { describe } from 'vitest'
|
||||
|
||||
describe('PC Mode', () => {})
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Copyright (c) 2022 - present TinyVue Authors.
|
||||
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
import NumberAnimation from './src/index'
|
||||
import '@opentiny/vue-theme/number-animation/index.less'
|
||||
import { version } from './package.json'
|
||||
|
||||
/* istanbul ignore next */
|
||||
NumberAnimation.install = function (Vue) {
|
||||
Vue.component(NumberAnimation.name, NumberAnimation)
|
||||
}
|
||||
|
||||
NumberAnimation.version = version
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (process.env.BUILD_TARGET === 'runtime') {
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
NumberAnimation.install(window.Vue)
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberAnimation
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "@opentiny/vue-number-animation",
|
||||
"type": "module",
|
||||
"version": "3.20.0",
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"main": "lib/index.js",
|
||||
"module": "index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm -w build:ui $npm_package_name",
|
||||
"//postversion": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/vue-button": "workspace:~",
|
||||
"@opentiny/vue-common": "workspace:~",
|
||||
"@opentiny/vue-modal": "workspace:~",
|
||||
"@opentiny/vue-renderless": "workspace:~",
|
||||
"@opentiny/vue-statistic": "workspace:~",
|
||||
"@opentiny/vue-theme": "workspace:~"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentiny-internal/vue-test-utils": "workspace:*",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
|
||||
import template from 'virtual-template?pc'
|
||||
|
||||
export const $constants = {
|
||||
PREFIX: 'tiny-number-animation'
|
||||
}
|
||||
|
||||
export const numberAnimationProps = {
|
||||
...$props,
|
||||
_constants: {
|
||||
type: Object,
|
||||
default: () => $constants
|
||||
},
|
||||
to: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
precision: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
default: ','
|
||||
},
|
||||
from: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'NumberAnimation',
|
||||
props: numberAnimationProps,
|
||||
setup(props, context) {
|
||||
return $setup({ props, context, template })
|
||||
}
|
||||
})
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
* Copyright (c) 2022 - present TinyVue Authors.
|
||||
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
-->
|
||||
<template>
|
||||
<div class="tiny-number-animation" ref="numberAnimationInstRef">
|
||||
{{ state.showValue }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { renderless, api } from '@opentiny/vue-renderless/number-animation/vue'
|
||||
import { props, setup, defineComponent } from '@opentiny/vue-common'
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['finish'],
|
||||
props: [...props, 'to', 'precision', 'separator', 'from', 'duration', 'active'],
|
||||
setup(props, context) {
|
||||
return setup({ props, context, renderless, api })
|
||||
}
|
||||
})
|
||||
</script>
|
Loading…
Reference in New Issue