From 423fd1c82eb5733a69bf2e9a6cd8f54209e933dd Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 30 Nov 2021 17:23:21 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(statistic):=20'=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E6=95=B0=E5=80=BC=E7=BB=84=E4=BB=B6'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistic/__tests__/statistic.spec.ts | 8 + packages/devui-vue/devui/statistic/index.ts | 17 ++ .../devui/statistic/src/statistic-types.ts | 63 ++++++ .../devui/statistic/src/statistic.scss | 34 ++++ .../devui/statistic/src/statistic.tsx | 85 ++++++++ .../devui/statistic/src/utils/animation.ts | 129 ++++++++++++ .../devui/statistic/src/utils/easing.ts | 27 +++ .../devui/statistic/src/utils/separator.ts | 54 +++++ .../docs/components/statistic/index.md | 191 ++++++++++++++++++ .../docs/en-US/components/statistic/index.md | 191 ++++++++++++++++++ 10 files changed, 799 insertions(+) create mode 100644 packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts create mode 100644 packages/devui-vue/devui/statistic/index.ts create mode 100644 packages/devui-vue/devui/statistic/src/statistic-types.ts create mode 100644 packages/devui-vue/devui/statistic/src/statistic.scss create mode 100644 packages/devui-vue/devui/statistic/src/statistic.tsx create mode 100644 packages/devui-vue/devui/statistic/src/utils/animation.ts create mode 100644 packages/devui-vue/devui/statistic/src/utils/easing.ts create mode 100644 packages/devui-vue/devui/statistic/src/utils/separator.ts create mode 100644 packages/devui-vue/docs/components/statistic/index.md create mode 100644 packages/devui-vue/docs/en-US/components/statistic/index.md diff --git a/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts b/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts new file mode 100644 index 00000000..fb9c9e89 --- /dev/null +++ b/packages/devui-vue/devui/statistic/__tests__/statistic.spec.ts @@ -0,0 +1,8 @@ +import { mount } from '@vue/test-utils' +import { Statistic } from '../index' + +describe('statistic test', () => { + it('statistic init render', async () => { + // todo + }) +}) diff --git a/packages/devui-vue/devui/statistic/index.ts b/packages/devui-vue/devui/statistic/index.ts new file mode 100644 index 00000000..116831ee --- /dev/null +++ b/packages/devui-vue/devui/statistic/index.ts @@ -0,0 +1,17 @@ +import type { App } from 'vue' +import Statistic from './src/statistic' + +Statistic.install = function(app: App): void { + app.component(Statistic.name, Statistic) +} + +export { Statistic } + +export default { + title: 'Statistic 统计数值', + category: '数据展示', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + app.use(Statistic as any) + } +} diff --git a/packages/devui-vue/devui/statistic/src/statistic-types.ts b/packages/devui-vue/devui/statistic/src/statistic-types.ts new file mode 100644 index 00000000..db27965c --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic-types.ts @@ -0,0 +1,63 @@ +import type { PropType, ExtractPropTypes, CSSProperties } from 'vue' +import type { easingType } from './utils/animation' +export const statisticProps = { + title: { + type: String, + default: '' + }, + value: { + type: [Number, String] + }, + prefix: { + type: String + }, + suffix: { + type: String + }, + precision: { + type: Number + }, + groupSeparator: { + type: String, + default: ',' + }, + showGroupSeparator: { + type: Boolean, + default: false + }, + titleStyle: { + type: Object as PropType + }, + contentStyle: { + type: Object as PropType + }, + animationDuration: { + type: Number, + default: 2000 + }, + valueFrom: { + type: Number + }, + animation: { + type: Boolean, + default: false + }, + start: { + type: Boolean, + default: false + }, + extra: { + type: String, + default: '' + }, + easing: { + type: String as PropType, + default: 'easeOutCubic' + }, + delay: { + type: Number, + default: 0 + } +} as const + +export type StatisticProps = ExtractPropTypes diff --git a/packages/devui-vue/devui/statistic/src/statistic.scss b/packages/devui-vue/devui/statistic/src/statistic.scss new file mode 100644 index 00000000..a619e6ce --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic.scss @@ -0,0 +1,34 @@ +.devui-statistic { + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5715; + list-style: none; + + &-title { + margin-bottom: 4 px; + opacity: 0.7; + font-size: 14px; + } + + &-content { + font-size: 24px; + display: flex; + align-items: center; + vertical-align: center; + } + + &-prefix { + margin-right: 6px; + } + + &-suffix { + margin-left: 6px; + } + + &--value { + display: inline-block; + } +} diff --git a/packages/devui-vue/devui/statistic/src/statistic.tsx b/packages/devui-vue/devui/statistic/src/statistic.tsx new file mode 100644 index 00000000..e57ebf11 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/statistic.tsx @@ -0,0 +1,85 @@ +import { defineComponent, computed, ref, onMounted, watch } from 'vue' +import { statisticProps, StatisticProps } from './statistic-types' +import { analysisValueType } from './utils/separator' +import { Tween } from './utils/animation' +import './statistic.scss' + +export default defineComponent({ + name: 'DStatistic', + props: statisticProps, + inheritAttrs: false, + setup(props: StatisticProps, ctx) { + const innerValue = ref(props.valueFrom ?? props.value) + const tween = ref(null) + + const animation = ( + from: number = props.valueFrom ?? 0, + to: number = typeof props.value === 'number' ? props.value : Number(props.value) + ) => { + if (from !== to) { + tween.value = new Tween({ + from: { + value: from + }, + to: { + value: to + }, + delay: props.delay, + duration: props.animationDuration, + easing: props.easing, + onUpdate: (keys: any) => { + innerValue.value = keys.value + }, + onFinish: () => { + innerValue.value = to + } + }) + tween.value.start() + } + } + + const statisticValue = computed(() => { + return analysisValueType( + innerValue.value, + props.value, + props.groupSeparator, + props.precision, + props.showGroupSeparator, + props.animation + ) + }) + onMounted(() => { + if (props.animation && props.start) { + animation() + } + }) + + watch( + () => props.start, + (value) => { + if (value && !tween.value) { + animation() + } + } + ) + return () => { + return ( +
+
+ {ctx.slots.title?.() || props.title} +
+
+ {props.prefix || ctx.slots.prefix?.() ? ( + {ctx.slots.prefix?.() || props.prefix} + ) : null} + {statisticValue.value} + {props.suffix || ctx.slots.suffix?.() ? ( + {ctx.slots.suffix?.() || props.suffix} + ) : null} +
+ {ctx.slots.extra?.() || props.extra} +
+ ) + } + } +}) diff --git a/packages/devui-vue/devui/statistic/src/utils/animation.ts b/packages/devui-vue/devui/statistic/src/utils/animation.ts new file mode 100644 index 00000000..8b1ef695 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/animation.ts @@ -0,0 +1,129 @@ +import * as easing from './easing' + +export type easingType = 'easeOutCubic' | 'linear' | 'easeOutExpo' | 'easeInOutExpo' +export interface startFunc { + (key: number): number +} +export interface updateFunc { + (key: any): any +} +export interface finishFunc { + (key: any): any +} +export interface fromType { + value: number +} +export interface toType { + value: number +} +export interface AnimationOptions { + from: fromType + to: toType + duration?: number + delay?: number + easing?: easingType + onStart?: startFunc + onUpdate?: updateFunc + onFinish?: finishFunc +} + +export class Tween { + from: fromType + to: toType + duration?: number + delay?: number + easing?: easingType + onStart?: startFunc + onUpdate?: updateFunc + onFinish?: finishFunc + startTime?: number + started?: boolean + finished?: boolean + timer?: null | number + time?: number + elapsed?: number + keys?: any + constructor(options: AnimationOptions) { + const { from, to, duration, delay, easing, onStart, onUpdate, onFinish } = options + for (const key in from) { + if (to[key] === undefined) { + to[key] = from[key] + } + } + + for (const key in to) { + if (from[key] === undefined) { + from[key] = to[key] + } + } + + this.from = from + this.to = to + this.duration = duration + this.delay = delay + this.easing = easing || 'linear' + this.onStart = onStart + this.onUpdate = onUpdate || function () {} + this.onFinish = onFinish + this.startTime = Date.now() + this.delay + this.started = false + this.finished = false + this.timer = null + this.keys = {} + } + + update() { + this.time = Date.now() + // delay some time + if (this.time < this.startTime) { + return + } + if (this.finished) { + return + } + // finish animation + if (this.elapsed === this.duration) { + if (!this.finished) { + this.finished = true + this.onFinish && this.onFinish(this.keys) + } + return + } + // elapsed 时间 和 duration 时间比较 逝去光阴 + this.elapsed = this.time - this.startTime + // 防止 时间 一直 流逝 ~ + this.elapsed = this.elapsed > this.duration ? this.duration : this.elapsed + // 从0 到 1 elapsed time + for (const key in this.to) { + this.keys[key] = + this.from[key] + + (this.to[key] - this.from[key]) * easing[this.easing](this.elapsed / this.duration) + } + if (!this.started) { + this.onStart && this.onStart(this.keys) + this.started = true + } + this.onUpdate(this.keys) + } + + // 递归 重绘 + start() { + this.startTime = Date.now() + this.delay + const tick = () => { + this.update() + this.timer = requestAnimationFrame(tick) + + if (this.finished) { + // 在判断 update中 结束后 停止 重绘 + cancelAnimationFrame(this.timer) + this.timer = null + } + } + tick() + } + + stop() { + cancelAnimationFrame(this.timer) + this.timer = null + } +} diff --git a/packages/devui-vue/devui/statistic/src/utils/easing.ts b/packages/devui-vue/devui/statistic/src/utils/easing.ts new file mode 100644 index 00000000..6dfa4ca2 --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/easing.ts @@ -0,0 +1,27 @@ +// pow 返回 基数的指数次幂 t ** power +const pow = Math.pow +const sqrt = Math.sqrt + +export const easeOutCubic = function (x: number) { + return 1 - pow(1 - x, 3) +} +export const linear = (x) => x +export const easeOutExpo = function (x: number) { + return x === 1 ? 1 : 1 - pow(2, -10 * x) +} + +export const easeInOutExpo = function (x: number) { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? pow(2, 20 * x - 10) / 2 + : (2 - pow(2, -20 * x + 10)) / 2 +} +export const easeInExpo = function (x: number) { + return x === 0 ? 0 : pow(2, 10 * x - 10) +} +export const easeInOutCirc = function (x: number) { + return x < 0.5 ? (1 - sqrt(1 - pow(2 * x, 2))) / 2 : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2 +} diff --git a/packages/devui-vue/devui/statistic/src/utils/separator.ts b/packages/devui-vue/devui/statistic/src/utils/separator.ts new file mode 100644 index 00000000..91178bcc --- /dev/null +++ b/packages/devui-vue/devui/statistic/src/utils/separator.ts @@ -0,0 +1,54 @@ +export type valueType = string | number + +export const separator = ( + SeparatorString: string, // value + groupSeparator: string, // 千分位分隔符 + showGroupSeparator: boolean // 是否展示千分位分隔符 +): string => { + const res = SeparatorString.replace(/\d+/, function (n) { + // 先提取整数部分 + return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) { + return $1 + `${showGroupSeparator ? groupSeparator : ''}` + }) + }) + return res +} + +export const isHasDot = (value: number): boolean => { + if (!isNaN(value)) { + return (value + '').indexOf('.') !== -1 + } +} +export const analysisValueType = ( + value: valueType, // 动态value 值 + propsValue: valueType, // 用户传入value + groupSeparator: string, // 千位分隔符 + splitPrecisionNumber: number, // 分割精度, 小数点 + showGroupSeparator: boolean // 是否展示千分位分隔符 +): string => { + const fixedNumber = + propsValue.toString().indexOf('.') !== -1 + ? propsValue.toString().length - propsValue.toString().indexOf('.') - 1 + : 0 + if (typeof value === 'number') { + if (isHasDot(value)) { + return splitPrecisionNumber + ? separator( + value.toFixed(splitPrecisionNumber).toString(), + groupSeparator, + showGroupSeparator + ) + : separator(value.toFixed(fixedNumber).toString(), groupSeparator, showGroupSeparator) + } else { + return splitPrecisionNumber + ? separator( + value.toFixed(splitPrecisionNumber).toString(), + groupSeparator, + showGroupSeparator + ) + : separator(value.toString(), groupSeparator, showGroupSeparator) + } + } else { + return value + } +} diff --git a/packages/devui-vue/docs/components/statistic/index.md b/packages/devui-vue/docs/components/statistic/index.md new file mode 100644 index 00000000..32d4e886 --- /dev/null +++ b/packages/devui-vue/docs/components/statistic/index.md @@ -0,0 +1,191 @@ +# Statistic 统计数值 + +### 何时使用 + +当需要展示带描述的统计类数据时使用 + +### 基本用法 + +:::demo + +```vue + +``` + +::: + +### 在卡片中使用 + +在卡片中展示统计数值。 +:::demo + +```vue + +``` + +::: + +### 数值动画 + +我们可以通过设置 animation 属性 开启数值动画。可以在页面加载时开始动画,也可以手动控制 +:::demo + +```vue + + +``` + +::: + +### 插槽的使用 + +前缀和后缀插槽 +:::demo + +```vue + +``` + +::: + +### d-statistic + +| 参数 | 类型 | 默认 | 说明 | +| ------------------ | ------------------ | -------- | ---------------- | +| title | `string \| v-slot` | - | 数值的标题 | +| extra | `string \| v-slot` | - | 额外内容 | +| value | `number \| string` | - | 数值内容 | +| group-separator | `string` | , | 设置千分位标识符 | +| precision | `number` | - | 设置数值精度 | +| suffix | `string \| v-slot` | - | 设置数值的后缀 | +| prefix | `string \| v-slot` | - | 设置数值的前缀 | +| title-style | `style` | - | 标题样式 | +| content-style | `style` | - | 内容样式 | +| animation-duration | `number` | 2000 | 动画持续时间 | +| delay | `number` | 0 | 延迟进行动画时间 | +| value-from | `number` | 0 | 动画初始值 | +| animation | `boolean` | false | 是否开启动画 | +| easing | `string` | quartOut | 数字动画效果 | +| start | `boolean` | false | 是否开始动画 | + +d-statistic 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| ---- | ---- | ---- | --------- | +| | | | | +| | | | | +| | | | | diff --git a/packages/devui-vue/docs/en-US/components/statistic/index.md b/packages/devui-vue/docs/en-US/components/statistic/index.md new file mode 100644 index 00000000..e5b18357 --- /dev/null +++ b/packages/devui-vue/docs/en-US/components/statistic/index.md @@ -0,0 +1,191 @@ +# Statistic + +### When to use + +Used when it is necessary to display statistical data with description + +### Basic Usage + +:::demo + +```vue + +``` + +::: + +### Use in card + +Display statistics in cards. +:::demo + +```vue + +``` + +::: + +### Numerical animation + +We can start numerical animation by setting the animation attribute. You can start the animation when the page loads, or you can control it manually +:::demo + +```vue + + +``` + +::: + +### Use of slots + +Prefix and suffix slots +:::demo + +```vue + +``` + +::: + +### d-statistic + +| 参数 | 类型 | 默认 | 说明 | +| ------------------ | ------------------ | -------- | ---------------------------- | +| title | `string \| v-slot` | - | Title of value | +| extra | `string \| v-slot` | - | Extra content | +| value | `number \| string` | - | Value content | +| group-separator | `string` | , | Set group-separator | +| precision | `number` | - | Set numeric precision | +| suffix | `string \| v-slot` | - | Sets the suffix of the value | +| prefix | `string \| v-slot` | - | Sets the prefix of the value | +| title-style | `style` | - | Title Style | +| content-style | `style` | - | Content style | +| animation-duration | `number` | 2000 | Animation duration | +| delay | `number` | 0 | Delay animation time | +| value-from | `number` | 0 | Animation initial value | +| animation | `boolean` | false | Turn on animation | +| easing | `string` | quartOut | Digital animation effect | +| start | `boolean` | false | Start animation | + +d-statistic 事件 + +| 事件 | 类型 | 说明 | 跳转 Demo | +| ---- | ---- | ---- | --------- | +| | | | | +| | | | | +| | | | | -- Gitee From 777d28d633d62e4a28ae0ff1cf74b96affa92b00 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Tue, 30 Nov 2021 19:30:38 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(statistic):=20'=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=A4=9A=E4=BD=99=E5=86=97=E4=BD=99=E5=88=A4=E6=96=AD'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/devui-vue/devui/statistic/src/utils/animation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devui-vue/devui/statistic/src/utils/animation.ts b/packages/devui-vue/devui/statistic/src/utils/animation.ts index 8b1ef695..778f51d5 100644 --- a/packages/devui-vue/devui/statistic/src/utils/animation.ts +++ b/packages/devui-vue/devui/statistic/src/utils/animation.ts @@ -61,9 +61,9 @@ export class Tween { this.to = to this.duration = duration this.delay = delay - this.easing = easing || 'linear' + this.easing = easing this.onStart = onStart - this.onUpdate = onUpdate || function () {} + this.onUpdate = onUpdate this.onFinish = onFinish this.startTime = Date.now() + this.delay this.started = false -- Gitee From e6dc87d31944cb70077ec4a3d95314da0986a599 Mon Sep 17 00:00:00 2001 From: erkelost <1256029807@qq.com> Date: Wed, 1 Dec 2021 08:32:22 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(statistic):=20'=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=87=E6=A1=A3'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/devui-vue/docs/components/statistic/index.md | 6 ------ .../devui-vue/docs/en-US/components/statistic/index.md | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/devui-vue/docs/components/statistic/index.md b/packages/devui-vue/docs/components/statistic/index.md index 32d4e886..f3f020d6 100644 --- a/packages/devui-vue/docs/components/statistic/index.md +++ b/packages/devui-vue/docs/components/statistic/index.md @@ -182,10 +182,4 @@ export default { | easing | `string` | quartOut | 数字动画效果 | | start | `boolean` | false | 是否开始动画 | -d-statistic 事件 -| 事件 | 类型 | 说明 | 跳转 Demo | -| ---- | ---- | ---- | --------- | -| | | | | -| | | | | -| | | | | diff --git a/packages/devui-vue/docs/en-US/components/statistic/index.md b/packages/devui-vue/docs/en-US/components/statistic/index.md index e5b18357..e15adb35 100644 --- a/packages/devui-vue/docs/en-US/components/statistic/index.md +++ b/packages/devui-vue/docs/en-US/components/statistic/index.md @@ -164,7 +164,7 @@ Prefix and suffix slots ### d-statistic -| 参数 | 类型 | 默认 | 说明 | +| parameter | type | default | introduce | | ------------------ | ------------------ | -------- | ---------------------------- | | title | `string \| v-slot` | - | Title of value | | extra | `string \| v-slot` | - | Extra content | @@ -181,11 +181,3 @@ Prefix and suffix slots | animation | `boolean` | false | Turn on animation | | easing | `string` | quartOut | Digital animation effect | | start | `boolean` | false | Start animation | - -d-statistic 事件 - -| 事件 | 类型 | 说明 | 跳转 Demo | -| ---- | ---- | ---- | --------- | -| | | | | -| | | | | -| | | | | -- Gitee