refactor: statistic and countdown to ts (#2997)

feat-dayjs
孙运天 2020-10-17 11:23:31 +08:00 committed by GitHub
parent 4d63b7cab6
commit 01096b556d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 198 additions and 204 deletions

View File

@ -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,
}}
/>
);
},
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
);
},
};

View File

@ -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;

View File

@ -1,9 +1,10 @@
import { App } from 'vue';
import Statistic from './Statistic';
import Countdown from './Countdown';
Statistic.Countdown = Countdown;
/* istanbul ignore next */
Statistic.install = function(app) {
Statistic.install = function(app: App) {
app.component(Statistic.name, Statistic);
app.component(Statistic.Countdown.name, Statistic.Countdown);
return app;

View File

@ -12,7 +12,7 @@ const timeUnits = [
['m', 1000 * 60], // minutes
['s', 1000], // seconds
['S', 1], // million seconds
];
] as const;
export function formatTimeStr(duration, format) {
let leftDuration = duration;