refactor: statistic and countdown to ts (#2997)
parent
4d63b7cab6
commit
01096b556d
|
@ -1,87 +0,0 @@
|
||||||
import moment from 'moment';
|
|
||||||
import interopDefault from '../_util/interopDefault';
|
|
||||||
import { initDefaultProps } from '../_util/props-util';
|
|
||||||
import Statistic, { StatisticProps } from './Statistic';
|
|
||||||
import { formatCountdown } from './utils';
|
|
||||||
|
|
||||||
const REFRESH_INTERVAL = 1000 / 30;
|
|
||||||
|
|
||||||
function getTime(value) {
|
|
||||||
return interopDefault(moment)(value).valueOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AStatisticCountdown',
|
|
||||||
props: initDefaultProps(StatisticProps, {
|
|
||||||
format: 'HH:mm:ss',
|
|
||||||
}),
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.countdownId = undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.syncTimer();
|
|
||||||
},
|
|
||||||
|
|
||||||
updated() {
|
|
||||||
this.syncTimer();
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount() {
|
|
||||||
this.stopTimer();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
syncTimer() {
|
|
||||||
const { value } = this.$props;
|
|
||||||
const timestamp = getTime(value);
|
|
||||||
if (timestamp >= Date.now()) {
|
|
||||||
this.startTimer();
|
|
||||||
} else {
|
|
||||||
this.stopTimer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
startTimer() {
|
|
||||||
if (this.countdownId) return;
|
|
||||||
this.countdownId = window.setInterval(() => {
|
|
||||||
this.$refs.statistic.$forceUpdate();
|
|
||||||
this.syncTimer();
|
|
||||||
}, REFRESH_INTERVAL);
|
|
||||||
},
|
|
||||||
|
|
||||||
stopTimer() {
|
|
||||||
const { value } = this.$props;
|
|
||||||
if (this.countdownId) {
|
|
||||||
clearInterval(this.countdownId);
|
|
||||||
this.countdownId = undefined;
|
|
||||||
|
|
||||||
const timestamp = getTime(value);
|
|
||||||
if (timestamp < Date.now()) {
|
|
||||||
this.$emit('finish');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatCountdown({ value, config }) {
|
|
||||||
const { format } = this.$props;
|
|
||||||
return formatCountdown(value, { ...config, format });
|
|
||||||
},
|
|
||||||
|
|
||||||
valueRenderHtml: node => node,
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Statistic
|
|
||||||
ref="statistic"
|
|
||||||
{...{
|
|
||||||
...this.$props,
|
|
||||||
valueRender: this.valueRenderHtml,
|
|
||||||
formatter: this.formatCountdown,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import moment from 'moment';
|
||||||
|
import interopDefault from '../_util/interopDefault';
|
||||||
|
import Statistic, { StatisticProps } from './Statistic';
|
||||||
|
import { formatCountdown } from './utils';
|
||||||
|
import { defineComponent, ref, onMounted, onUpdated, onBeforeUnmount } from 'vue';
|
||||||
|
import PropTypes from '../_util/vue-types/index';
|
||||||
|
|
||||||
|
const REFRESH_INTERVAL = 1000 / 30;
|
||||||
|
function getTime(value) {
|
||||||
|
return interopDefault(moment)(value).valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatisticCountdown = defineComponent({
|
||||||
|
name: 'AStatisticCountdown',
|
||||||
|
props: {
|
||||||
|
...StatisticProps,
|
||||||
|
format: PropTypes.string.def('HH:mm:ss'),
|
||||||
|
},
|
||||||
|
emits: ['finish'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
let countdownId: number | undefined = undefined;
|
||||||
|
const renderKey = ref(0);
|
||||||
|
|
||||||
|
const syncTimer = () => {
|
||||||
|
const timestamp = getTime(props.value);
|
||||||
|
if (timestamp >= Date.now()) {
|
||||||
|
startTimer();
|
||||||
|
} else {
|
||||||
|
stopTimer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startTimer = () => {
|
||||||
|
if (countdownId) return;
|
||||||
|
countdownId = window.setInterval(() => {
|
||||||
|
renderKey.value++;
|
||||||
|
syncTimer();
|
||||||
|
}, REFRESH_INTERVAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopTimer = () => {
|
||||||
|
if (countdownId) {
|
||||||
|
clearInterval(countdownId);
|
||||||
|
countdownId = undefined;
|
||||||
|
|
||||||
|
const timestamp = getTime(props.value);
|
||||||
|
if (timestamp < Date.now()) {
|
||||||
|
emit('finish');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
syncTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
syncTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopTimer();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Statistic
|
||||||
|
data-key={renderKey.value}
|
||||||
|
ref="statistic"
|
||||||
|
{...{
|
||||||
|
...props,
|
||||||
|
valueRender: node => node,
|
||||||
|
formatter: ({ value }) => formatCountdown(value, { format: props.format }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default StatisticCountdown;
|
|
@ -1,48 +0,0 @@
|
||||||
import padEnd from 'lodash-es/padEnd';
|
|
||||||
import { createVNode } from 'vue';
|
|
||||||
|
|
||||||
const Number = (_, { attrs }) => {
|
|
||||||
const { value, formatter, precision, decimalSeparator, groupSeparator = '', prefixCls } = attrs;
|
|
||||||
let valueNode;
|
|
||||||
|
|
||||||
if (typeof formatter === 'function') {
|
|
||||||
// Customize formatter
|
|
||||||
valueNode = formatter({ value, h: createVNode });
|
|
||||||
} else {
|
|
||||||
// Internal formatter
|
|
||||||
const val = String(value);
|
|
||||||
const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/);
|
|
||||||
// Process if illegal number
|
|
||||||
if (!cells) {
|
|
||||||
valueNode = val;
|
|
||||||
} else {
|
|
||||||
const negative = cells[1];
|
|
||||||
let int = cells[2] || '0';
|
|
||||||
let decimal = cells[4] || '';
|
|
||||||
|
|
||||||
int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator);
|
|
||||||
if (typeof precision === 'number') {
|
|
||||||
decimal = padEnd(decimal, precision, '0').slice(0, precision);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decimal) {
|
|
||||||
decimal = `${decimalSeparator}${decimal}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
valueNode = [
|
|
||||||
<span key="int" class={`${prefixCls}-content-value-int`}>
|
|
||||||
{negative}
|
|
||||||
{int}
|
|
||||||
</span>,
|
|
||||||
decimal && (
|
|
||||||
<span key="decimal" class={`${prefixCls}-content-value-decimal`}>
|
|
||||||
{decimal}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span class={`${prefixCls}-content-value`}>{valueNode}</span>;
|
|
||||||
};
|
|
||||||
export default Number;
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import padEnd from 'lodash-es/padEnd';
|
||||||
|
import { FunctionalComponent, VNodeTypes } from 'vue';
|
||||||
|
import { StatisticProps, StatisticPropsType } from './Statistic';
|
||||||
|
import PropTypes from '../_util/vue-types/index';
|
||||||
|
|
||||||
|
const StatisticNumber: FunctionalComponent<Omit<StatisticPropsType, 'formatter'> & {
|
||||||
|
formatter?: VNodeTypes;
|
||||||
|
}> = props => {
|
||||||
|
const { formatter, value, groupSeparator, precision, decimalSeparator, prefixCls } = props;
|
||||||
|
let valueNode: VNodeTypes;
|
||||||
|
|
||||||
|
if (formatter) {
|
||||||
|
valueNode = formatter;
|
||||||
|
} else {
|
||||||
|
const val = String(value);
|
||||||
|
const cells = val.match(/^(-?)(\d*)(\.(\d+))?$/);
|
||||||
|
if (!cells) {
|
||||||
|
valueNode = val;
|
||||||
|
} else {
|
||||||
|
const negative = cells[1];
|
||||||
|
let int = cells[2] || '0';
|
||||||
|
let decimal = cells[4] || '';
|
||||||
|
|
||||||
|
int = int.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator);
|
||||||
|
if (typeof precision === 'number') {
|
||||||
|
decimal = padEnd(decimal, precision, '0').slice(0, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimal) {
|
||||||
|
decimal = `${decimalSeparator}${decimal}`;
|
||||||
|
}
|
||||||
|
valueNode = (
|
||||||
|
<>
|
||||||
|
<span key="int" class={`${prefixCls}-content-value-int`}>
|
||||||
|
{negative}
|
||||||
|
{int}
|
||||||
|
</span>
|
||||||
|
{decimal && (
|
||||||
|
<span key="decimal" class={`${prefixCls}-content-value-decimal`}>
|
||||||
|
{decimal}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span class={`${prefixCls}-content-value`}>{valueNode}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
StatisticNumber.props = { ...StatisticProps, formatter: PropTypes.VNodeChild };
|
||||||
|
|
||||||
|
export default StatisticNumber;
|
|
@ -1,67 +0,0 @@
|
||||||
import { inject } from 'vue';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
|
||||||
import { getComponent, initDefaultProps } from '../_util/props-util';
|
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
|
||||||
import StatisticNumber from './Number';
|
|
||||||
|
|
||||||
export const StatisticProps = {
|
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
decimalSeparator: PropTypes.string,
|
|
||||||
groupSeparator: PropTypes.string,
|
|
||||||
format: PropTypes.string,
|
|
||||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
|
|
||||||
valueStyle: PropTypes.any,
|
|
||||||
valueRender: PropTypes.any,
|
|
||||||
formatter: PropTypes.any,
|
|
||||||
precision: PropTypes.number,
|
|
||||||
prefix: PropTypes.any,
|
|
||||||
suffix: PropTypes.any,
|
|
||||||
title: PropTypes.any,
|
|
||||||
onFinish: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AStatistic',
|
|
||||||
props: initDefaultProps(StatisticProps, {
|
|
||||||
decimalSeparator: '.',
|
|
||||||
groupSeparator: ',',
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { prefixCls: customizePrefixCls, value = 0, valueStyle, valueRender } = this.$props;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('statistic', customizePrefixCls);
|
|
||||||
|
|
||||||
const title = getComponent(this, 'title');
|
|
||||||
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 = <StatisticNumber {...props} />;
|
|
||||||
if (valueRender) {
|
|
||||||
valueNode = valueRender(valueNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={prefixCls}>
|
|
||||||
{title && <div class={`${prefixCls}-title`}>{title}</div>}
|
|
||||||
<div style={valueStyle} class={`${prefixCls}-content`}>
|
|
||||||
{prefix && <span class={`${prefixCls}-content-prefix`}>{prefix}</span>}
|
|
||||||
{valueNode}
|
|
||||||
{suffix && <span class={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { defineComponent, inject, VNodeTypes, computed } from 'vue';
|
||||||
|
import { VueTypeValidableDef } from 'vue-types';
|
||||||
|
import { defaultConfigProvider } from '../config-provider';
|
||||||
|
import { getComponent } from '../_util/props-util';
|
||||||
|
import PropTypes from '../_util/vue-types';
|
||||||
|
import StatisticNumber from './Number';
|
||||||
|
|
||||||
|
export const StatisticProps = {
|
||||||
|
decimalSeparator: PropTypes.string.def('.'),
|
||||||
|
formatter: PropTypes.func as VueTypeValidableDef<(params: { value: unknown }) => VNodeTypes>,
|
||||||
|
groupSeparator: PropTypes.string.def(','),
|
||||||
|
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>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StatisticPropsType = Parameters<typeof Statistic['setup']>['0'];
|
||||||
|
|
||||||
|
const Statistic = 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 };
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { prefixCls, valueStyle, valueRender, value } = this;
|
||||||
|
|
||||||
|
const valueNode = (
|
||||||
|
<StatisticNumber
|
||||||
|
{...{
|
||||||
|
...this.$props,
|
||||||
|
prefixCls: this.prefixCls,
|
||||||
|
formatter: getComponent(this, 'formatter', { value }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const title = getComponent(this, 'title');
|
||||||
|
const prefix = getComponent(this, 'prefix');
|
||||||
|
const suffix = getComponent(this, 'suffix');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={prefixCls}>
|
||||||
|
{title && <div class={`${prefixCls}-title`}>{title}</div>}
|
||||||
|
<div style={valueStyle} class={`${prefixCls}-content`}>
|
||||||
|
{prefix && <span class={`${prefixCls}-content-prefix`}>{prefix}</span>}
|
||||||
|
{valueRender ? valueRender(valueNode) : valueNode}
|
||||||
|
{suffix && <span class={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Statistic;
|
|
@ -1,9 +1,10 @@
|
||||||
|
import { App } from 'vue';
|
||||||
import Statistic from './Statistic';
|
import Statistic from './Statistic';
|
||||||
import Countdown from './Countdown';
|
import Countdown from './Countdown';
|
||||||
|
|
||||||
Statistic.Countdown = Countdown;
|
Statistic.Countdown = Countdown;
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
Statistic.install = function(app) {
|
Statistic.install = function(app: App) {
|
||||||
app.component(Statistic.name, Statistic);
|
app.component(Statistic.name, Statistic);
|
||||||
app.component(Statistic.Countdown.name, Statistic.Countdown);
|
app.component(Statistic.Countdown.name, Statistic.Countdown);
|
||||||
return app;
|
return app;
|
|
@ -12,7 +12,7 @@ const timeUnits = [
|
||||||
['m', 1000 * 60], // minutes
|
['m', 1000 * 60], // minutes
|
||||||
['s', 1000], // seconds
|
['s', 1000], // seconds
|
||||||
['S', 1], // million seconds
|
['S', 1], // million seconds
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
export function formatTimeStr(duration, format) {
|
export function formatTimeStr(duration, format) {
|
||||||
let leftDuration = duration;
|
let leftDuration = duration;
|
Loading…
Reference in New Issue