From 49fda0f7c8a1ae65c023bb3033a3062e3b73a5b7 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 17 Oct 2020 11:51:17 +0800 Subject: [PATCH] fix: refactor statistic to ts --- components/statistic/Countdown.tsx | 103 ++++++++++++++++------------- components/statistic/Number.tsx | 48 +++++++------- components/statistic/Statistic.tsx | 80 ++++++++++++---------- components/statistic/index.ts | 2 +- components/statistic/utils.ts | 32 +++++++-- 5 files changed, 152 insertions(+), 113 deletions(-) diff --git a/components/statistic/Countdown.tsx b/components/statistic/Countdown.tsx index e09d34038..ce4be77ae 100644 --- a/components/statistic/Countdown.tsx +++ b/components/statistic/Countdown.tsx @@ -1,79 +1,88 @@ +import { defineComponent } from 'vue'; import moment from 'moment'; import interopDefault from '../_util/interopDefault'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; import Statistic, { StatisticProps } from './Statistic'; -import { formatCountdown } from './utils'; -import { defineComponent, ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'; -import PropTypes from '../_util/vue-types/index'; +import { formatCountdown, countdownValueType, FormatConfig } from './utils'; const REFRESH_INTERVAL = 1000 / 30; -function getTime(value) { + +function getTime(value?: countdownValueType) { return interopDefault(moment)(value).valueOf(); } -const StatisticCountdown = defineComponent({ +export default defineComponent({ name: 'AStatisticCountdown', - props: { - ...StatisticProps, - format: PropTypes.string.def('HH:mm:ss'), + props: initDefaultProps(StatisticProps, { + format: 'HH:mm:ss', + }), + setup() { + return { + countdownId: undefined, + } as { countdownId: number }; + }, + mounted() { + this.syncTimer(); }, - emits: ['finish'], - setup(props, { emit }) { - let countdownId: number | undefined = undefined; - const renderKey = ref(0); - const syncTimer = () => { - const timestamp = getTime(props.value); + updated() { + this.syncTimer(); + }, + + beforeUnmount() { + this.stopTimer(); + }, + + methods: { + syncTimer() { + const { value } = this.$props; + const timestamp = getTime(value); if (timestamp >= Date.now()) { - startTimer(); + this.startTimer(); } else { - stopTimer(); + this.stopTimer(); } - }; + }, - const startTimer = () => { - if (countdownId) return; - countdownId = window.setInterval(() => { - renderKey.value++; - syncTimer(); + startTimer() { + if (this.countdownId) return; + this.countdownId = window.setInterval(() => { + (this.$refs.statistic as any).$forceUpdate(); + this.syncTimer(); }, REFRESH_INTERVAL); - }; + }, - const stopTimer = () => { - if (countdownId) { - clearInterval(countdownId); - countdownId = undefined; + stopTimer() { + const { value } = this.$props; + if (this.countdownId) { + clearInterval(this.countdownId); + this.countdownId = undefined; - const timestamp = getTime(props.value); + const timestamp = getTime(value); if (timestamp < Date.now()) { - emit('finish'); + this.$emit('finish'); } } - }; - - onMounted(() => { - syncTimer(); - }); + }, - onUpdated(() => { - syncTimer(); - }); + formatCountdown({ value, config }: { value: countdownValueType; config: FormatConfig }) { + const { format } = this.$props; + return formatCountdown(value, { ...config, format }); + }, - onBeforeUnmount(() => { - stopTimer(); - }); + valueRenderHtml: node => node, + }, - return () => ( + render() { + return ( node, - formatter: ({ value }) => formatCountdown(value, { format: props.format }), + ...this.$props, + valueRender: this.valueRenderHtml, + formatter: this.formatCountdown, }} /> ); }, }); - -export default StatisticCountdown; diff --git a/components/statistic/Number.tsx b/components/statistic/Number.tsx index 040e1567d..eb7d65a7e 100644 --- a/components/statistic/Number.tsx +++ b/components/statistic/Number.tsx @@ -1,19 +1,23 @@ import padEnd from 'lodash-es/padEnd'; import { FunctionalComponent, VNodeTypes } from 'vue'; -import { StatisticProps, StatisticPropsType } from './Statistic'; -import PropTypes from '../_util/vue-types/index'; +import { FormatConfig, valueType } from './utils'; -const StatisticNumber: FunctionalComponent & { - formatter?: VNodeTypes; -}> = props => { - const { formatter, value, groupSeparator, precision, decimalSeparator, prefixCls } = props; +interface NumberProps extends FormatConfig { + value: valueType; +} + +const Number: FunctionalComponent = props => { + const { value, formatter, precision, decimalSeparator, groupSeparator = '', prefixCls } = props; let valueNode: VNodeTypes; - if (formatter) { - valueNode = formatter; + if (typeof formatter === 'function') { + // Customize formatter + valueNode = formatter({ value }); } else { + // Internal formatter const val = String(value); const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/); + // Process if illegal number if (!cells) { valueNode = val; } else { @@ -29,25 +33,21 @@ const StatisticNumber: FunctionalComponent if (decimal) { decimal = `${decimalSeparator}${decimal}`; } - valueNode = ( - <> - - {negative} - {int} + + valueNode = [ + + {negative} + {int} + , + decimal && ( + + {decimal} - {decimal && ( - - {decimal} - - )} - - ); + ), + ]; } } return {valueNode}; }; - -StatisticNumber.props = { ...StatisticProps, formatter: PropTypes.VNodeChild }; - -export default StatisticNumber; +export default Number; diff --git a/components/statistic/Statistic.tsx b/components/statistic/Statistic.tsx index e7d4892f9..1af9cae53 100644 --- a/components/statistic/Statistic.tsx +++ b/components/statistic/Statistic.tsx @@ -1,63 +1,71 @@ -import { defineComponent, inject, VNodeTypes, computed } from 'vue'; -import { VueTypeValidableDef } from 'vue-types'; -import { defaultConfigProvider } from '../config-provider'; -import { getComponent } from '../_util/props-util'; +import { defineComponent, inject, PropType } from 'vue'; import PropTypes from '../_util/vue-types'; +import { getComponent } from '../_util/props-util'; +import initDefaultProps from '../_util/props-util/initDefaultProps'; +import { defaultConfigProvider } from '../config-provider'; import StatisticNumber from './Number'; +import { countdownValueType } from './utils'; export const StatisticProps = { - decimalSeparator: PropTypes.string.def('.'), - formatter: PropTypes.func as VueTypeValidableDef<(params: { value: unknown }) => VNodeTypes>, - groupSeparator: PropTypes.string.def(','), + prefixCls: PropTypes.string, + decimalSeparator: PropTypes.string, + groupSeparator: PropTypes.string, + format: PropTypes.string, + value: { + type: [String, Number, Object] as PropType, + }, + valueStyle: PropTypes.style, + valueRender: PropTypes.any, + formatter: PropTypes.any, precision: PropTypes.number, prefix: PropTypes.VNodeChild, suffix: PropTypes.VNodeChild, title: PropTypes.VNodeChild, - value: PropTypes.any, - valueStyle: PropTypes.style, - prefixCls: PropTypes.string, - valueRender: PropTypes.func as VueTypeValidableDef<(node: VNodeTypes) => VNodeTypes>, + onFinish: PropTypes.func, }; -export type StatisticPropsType = Parameters['0']; - -const Statistic = defineComponent({ +export default defineComponent({ name: 'AStatistic', - props: StatisticProps, - setup(props) { - const configProvider = inject('configProvider', defaultConfigProvider); - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = computed(() => getPrefixCls('statistic', props.prefixCls)); - return { prefixCls }; + props: initDefaultProps(StatisticProps, { + decimalSeparator: '.', + groupSeparator: ',', + }), + + setup() { + return { + configProvider: inject('configProvider', defaultConfigProvider), + }; }, - render() { - const { prefixCls, valueStyle, valueRender, value } = this; - const valueNode = ( - - ); + render() { + const { prefixCls: customizePrefixCls, value = 0, valueStyle, valueRender } = this.$props; + const { getPrefixCls } = this.configProvider; + const prefixCls = getPrefixCls('statistic', customizePrefixCls); const title = getComponent(this, 'title'); - const prefix = getComponent(this, 'prefix'); - const suffix = getComponent(this, 'suffix'); + let prefix = getComponent(this, 'prefix'); + let suffix = getComponent(this, 'suffix'); + const formatter = getComponent(this, 'formatter', {}, false); + const props = { + ...this.$props, + prefixCls, + value, + formatter, + }; + let valueNode = ; + if (valueRender) { + valueNode = valueRender(valueNode); + } return (
{title &&
{title}
}
{prefix && {prefix}} - {valueRender ? valueRender(valueNode) : valueNode} + {valueNode} {suffix && {suffix}}
); }, }); - -export default Statistic; diff --git a/components/statistic/index.ts b/components/statistic/index.ts index c7c5eb123..5028a5e61 100644 --- a/components/statistic/index.ts +++ b/components/statistic/index.ts @@ -1,6 +1,6 @@ -import { App } from 'vue'; import Statistic from './Statistic'; import Countdown from './Countdown'; +import { App } from 'vue'; Statistic.Countdown = Countdown; /* istanbul ignore next */ diff --git a/components/statistic/utils.ts b/components/statistic/utils.ts index f862244b9..e0f69618e 100644 --- a/components/statistic/utils.ts +++ b/components/statistic/utils.ts @@ -1,10 +1,32 @@ +import { VNodeTypes } from 'vue'; import moment from 'moment'; import padStart from 'lodash-es/padStart'; import interopDefault from '../_util/interopDefault'; +export type valueType = number | string; +export type countdownValueType = valueType | string; + +export type Formatter = + | false + | 'number' + | 'countdown' + | (({ value, config }: { value: valueType; config?: FormatConfig }) => VNodeTypes); + +export interface FormatConfig { + formatter?: Formatter; + decimalSeparator?: string; + groupSeparator?: string; + precision?: number; + prefixCls?: string; +} + +export interface CountdownFormatConfig extends FormatConfig { + format?: string; +} + // Countdown -const timeUnits = [ +const timeUnits: [string, number][] = [ ['Y', 1000 * 60 * 60 * 24 * 365], // years ['M', 1000 * 60 * 60 * 24 * 30], // months ['D', 1000 * 60 * 60 * 24], // days @@ -12,10 +34,10 @@ const timeUnits = [ ['m', 1000 * 60], // minutes ['s', 1000], // seconds ['S', 1], // million seconds -] as const; +]; -export function formatTimeStr(duration, format) { - let leftDuration = duration; +export function formatTimeStr(duration: number, format: string) { + let leftDuration: number = duration; const escapeRegex = /\[[^\]]*\]/g; const keepList = (format.match(escapeRegex) || []).map(str => str.slice(1, -1)); @@ -41,7 +63,7 @@ export function formatTimeStr(duration, format) { }); } -export function formatCountdown(value, config) { +export function formatCountdown(value: countdownValueType, config: CountdownFormatConfig) { const { format = '' } = config; const target = interopDefault(moment)(value).valueOf(); const current = interopDefault(moment)().valueOf();