refactor: statistic

pull/4199/head
tangjinzhou 2021-06-10 23:04:57 +08:00
parent fc6c358e9a
commit 7624645d58
5 changed files with 129 additions and 110 deletions

View File

@ -1,9 +1,9 @@
import { defineComponent } from 'vue'; import { defineComponent, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue';
import moment from 'moment'; import moment from 'moment';
import interopDefault from '../_util/interopDefault'; import interopDefault from '../_util/interopDefault';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import Statistic, { StatisticProps } from './Statistic'; import Statistic, { StatisticProps } from './Statistic';
import { formatCountdown, countdownValueType, FormatConfig } from './utils'; import { formatCountdown as formatCD, countdownValueType, FormatConfig } from './utils';
const REFRESH_INTERVAL = 1000 / 30; const REFRESH_INTERVAL = 1000 / 30;
@ -16,73 +16,76 @@ export default defineComponent({
props: initDefaultProps(StatisticProps, { props: initDefaultProps(StatisticProps, {
format: 'HH:mm:ss', format: 'HH:mm:ss',
}), }),
setup() { emits: ['finish', 'change'],
return { setup(props, { emit }) {
countdownId: undefined, const countdownId = ref<number>();
} as { countdownId: number }; const statistic = ref();
}, const syncTimer = () => {
mounted() { const { value } = props;
this.syncTimer();
},
updated() {
this.syncTimer();
},
beforeUnmount() {
this.stopTimer();
},
methods: {
syncTimer() {
const { value } = this.$props;
const timestamp = getTime(value); const timestamp = getTime(value);
if (timestamp >= Date.now()) { if (timestamp >= Date.now()) {
this.startTimer(); startTimer();
} else { } else {
this.stopTimer(); stopTimer();
} }
}, };
startTimer() { const startTimer = () => {
if (this.countdownId) return; if (countdownId.value) return;
this.countdownId = window.setInterval(() => { const timestamp = getTime(props.value);
(this.$refs.statistic as any).$forceUpdate(); countdownId.value = window.setInterval(() => {
this.syncTimer(); statistic.value.$forceUpdate();
if (timestamp > Date.now()) {
emit('change', timestamp - Date.now());
}
}, REFRESH_INTERVAL); }, REFRESH_INTERVAL);
}, };
stopTimer() { const stopTimer = () => {
const { value } = this.$props; const { value } = props;
if (this.countdownId) { if (countdownId) {
clearInterval(this.countdownId); clearInterval(countdownId.value);
this.countdownId = undefined; countdownId.value = undefined;
const timestamp = getTime(value); const timestamp = getTime(value);
if (timestamp < Date.now()) { if (timestamp < Date.now()) {
this.$emit('finish'); emit('finish');
} }
} }
}, };
formatCountdown({ value, config }: { value: countdownValueType; config: FormatConfig }) { const formatCountdown = ({
const { format } = this.$props; value,
return formatCountdown(value, { ...config, format }); config,
}, }: {
value: countdownValueType;
config: FormatConfig;
}) => {
const { format } = props;
return formatCD(value, { ...config, format });
};
valueRenderHtml: node => node, const valueRenderHtml = (node: any) => node;
}, onMounted(() => {
syncTimer();
render() { });
onUpdated(() => {
syncTimer();
});
onBeforeUnmount(() => {
stopTimer();
});
return () => {
return ( return (
<Statistic <Statistic
ref="statistic" ref={statistic}
{...{ {...{
...this.$props, ...props,
valueRender: this.valueRenderHtml, valueRender: valueRenderHtml,
formatter: this.formatCountdown, formatter: formatCountdown,
}} }}
/> />
); );
};
}, },
}); });

View File

@ -1,10 +1,10 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getComponent } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import { defaultConfigProvider } from '../config-provider';
import StatisticNumber from './Number'; import StatisticNumber from './Number';
import { countdownValueType } from './utils'; import { countdownValueType } from './utils';
import Skeleton from '../skeleton/Skeleton';
import useConfigInject from '../_util/hooks/useConfigInject';
export const StatisticProps = { export const StatisticProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
@ -22,6 +22,7 @@ export const StatisticProps = {
suffix: PropTypes.VNodeChild, suffix: PropTypes.VNodeChild,
title: PropTypes.VNodeChild, title: PropTypes.VNodeChild,
onFinish: PropTypes.func, onFinish: PropTypes.func,
loading: PropTypes.looseBool,
}; };
export default defineComponent({ export default defineComponent({
@ -29,45 +30,41 @@ export default defineComponent({
props: initDefaultProps(StatisticProps, { props: initDefaultProps(StatisticProps, {
decimalSeparator: '.', decimalSeparator: '.',
groupSeparator: ',', groupSeparator: ',',
loading: false,
}), }),
slots: ['title', 'prefix', 'suffix', 'formatter'],
setup() { setup(props, { slots }) {
return { const { prefixCls, direction } = useConfigInject('statistic', props);
configProvider: inject('configProvider', defaultConfigProvider), return () => {
}; const { value = 0, valueStyle, valueRender } = props;
}, const pre = prefixCls.value;
const title = props.title ?? slots.title?.();
render() { const prefix = props.prefix ?? slots.prefix?.();
const { prefixCls: customizePrefixCls, value = 0, valueStyle, valueRender } = this.$props; const suffix = props.suffix ?? slots.suffix?.();
const { getPrefixCls } = this.configProvider; const formatter = props.formatter ?? slots.formatter;
const prefixCls = getPrefixCls('statistic', customizePrefixCls);
const title = getComponent(this, 'title');
const prefix = getComponent(this, 'prefix');
const suffix = getComponent(this, 'suffix');
const formatter = getComponent(this, 'formatter', {}, false);
const props = {
...this.$props,
prefixCls,
value,
formatter,
};
// data-for-update just for update component // data-for-update just for update component
// https://github.com/vueComponent/ant-design-vue/pull/3170 // https://github.com/vueComponent/ant-design-vue/pull/3170
let valueNode = <StatisticNumber data-for-update={Date.now()} {...props} />; let valueNode = (
<StatisticNumber
data-for-update={Date.now()}
{...{ ...props, prefixCls: pre, value, formatter }}
/>
);
if (valueRender) { if (valueRender) {
valueNode = valueRender(valueNode); valueNode = valueRender(valueNode);
} }
return ( return (
<div class={prefixCls}> <div class={[pre, { [`${pre}-rtl`]: direction.value === 'rtl' }]}>
{title && <div class={`${prefixCls}-title`}>{title}</div>} {title && <div class={`${pre}-title`}>{title}</div>}
<div style={valueStyle} class={`${prefixCls}-content`}> <Skeleton paragraph={false} loading={props.loading}>
{prefix && <span class={`${prefixCls}-content-prefix`}>{prefix}</span>} <div style={valueStyle} class={`${pre}-content`}>
{prefix && <span class={`${pre}-content-prefix`}>{prefix}</span>}
{valueNode} {valueNode}
{suffix && <span class={`${prefixCls}-content-suffix`}>{suffix}</span>} {suffix && <span class={`${pre}-content-suffix`}>{suffix}</span>}
</div> </div>
</Skeleton>
</div> </div>
); );
};
}, },
}); });

View File

@ -7,7 +7,7 @@
.reset-component(); .reset-component();
&-title { &-title {
margin-bottom: 4px; margin-bottom: @margin-xss;
color: @text-color-secondary; color: @text-color-secondary;
font-size: @statistic-title-font-size; font-size: @statistic-title-font-size;
} }
@ -18,9 +18,8 @@
font-family: @statistic-font-family; font-family: @statistic-font-family;
&-value { &-value {
&-decimal { display: inline-block;
font-size: @statistic-unit-font-size; direction: ltr;
}
} }
&-prefix, &-prefix,
@ -34,7 +33,8 @@
&-suffix { &-suffix {
margin-left: 4px; margin-left: 4px;
font-size: @statistic-unit-font-size;
} }
} }
} }
@import './rtl';

View File

@ -0,0 +1,21 @@
.@{statistic-prefix-cls} {
&-rtl {
direction: rtl;
}
&-content {
&-prefix {
.@{statistic-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 4px;
}
}
&-suffix {
.@{statistic-prefix-cls}-rtl & {
margin-right: 4px;
margin-left: 0;
}
}
}
}

View File

@ -1,9 +1,6 @@
import { VNodeTypes } from 'vue'; import { VNodeTypes } from 'vue';
import moment from 'moment';
import padStart from 'lodash-es/padStart'; import padStart from 'lodash-es/padStart';
import interopDefault from '../_util/interopDefault';
export type valueType = number | string; export type valueType = number | string;
export type countdownValueType = valueType | string; export type countdownValueType = valueType | string;
@ -39,15 +36,15 @@ const timeUnits: [string, number][] = [
export function formatTimeStr(duration: number, format: string) { export function formatTimeStr(duration: number, format: string) {
let leftDuration: number = duration; let leftDuration: number = duration;
const escapeRegex = /\[[^\]]*\]/g; const escapeRegex = /\[[^\]]*]/g;
const keepList = (format.match(escapeRegex) || []).map(str => str.slice(1, -1)); const keepList: string[] = (format.match(escapeRegex) || []).map(str => str.slice(1, -1));
const templateText = format.replace(escapeRegex, '[]'); const templateText = format.replace(escapeRegex, '[]');
const replacedText = timeUnits.reduce((current, [name, unit]) => { const replacedText = timeUnits.reduce((current, [name, unit]) => {
if (current.indexOf(name) !== -1) { if (current.indexOf(name) !== -1) {
const value = Math.floor(leftDuration / unit); const value = Math.floor(leftDuration / unit);
leftDuration -= value * unit; leftDuration -= value * unit;
return current.replace(new RegExp(`${name}+`, 'g'), match => { return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => {
const len = match.length; const len = match.length;
return padStart(value.toString(), len, '0'); return padStart(value.toString(), len, '0');
}); });
@ -65,8 +62,9 @@ export function formatTimeStr(duration: number, format: string) {
export function formatCountdown(value: countdownValueType, config: CountdownFormatConfig) { export function formatCountdown(value: countdownValueType, config: CountdownFormatConfig) {
const { format = '' } = config; const { format = '' } = config;
const target = interopDefault(moment)(value).valueOf(); const target = new Date(value).getTime();
const current = interopDefault(moment)().valueOf(); const current = Date.now();
const diff = Math.max(target - current, 0); const diff = Math.max(target - current, 0);
return formatTimeStr(diff, format); return formatTimeStr(diff, format);
} }