refactor: statistic
parent
fc6c358e9a
commit
7624645d58
|
@ -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,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue