fix: refactor statistic to ts
parent
8eb6afb9b0
commit
49fda0f7c8
|
@ -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();
|
||||
});
|
||||
formatCountdown({ value, config }: { value: countdownValueType; config: FormatConfig }) {
|
||||
const { format } = this.$props;
|
||||
return formatCountdown(value, { ...config, format });
|
||||
},
|
||||
|
||||
onUpdated(() => {
|
||||
syncTimer();
|
||||
});
|
||||
valueRenderHtml: node => node,
|
||||
},
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
return () => (
|
||||
render() {
|
||||
return (
|
||||
<Statistic
|
||||
data-key={renderKey.value}
|
||||
ref="statistic"
|
||||
{...{
|
||||
...props,
|
||||
valueRender: node => node,
|
||||
formatter: ({ value }) => formatCountdown(value, { format: props.format }),
|
||||
...this.$props,
|
||||
valueRender: this.valueRenderHtml,
|
||||
formatter: this.formatCountdown,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default StatisticCountdown;
|
||||
|
|
|
@ -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<Omit<StatisticPropsType, 'formatter'> & {
|
||||
formatter?: VNodeTypes;
|
||||
}> = props => {
|
||||
const { formatter, value, groupSeparator, precision, decimalSeparator, prefixCls } = props;
|
||||
interface NumberProps extends FormatConfig {
|
||||
value: valueType;
|
||||
}
|
||||
|
||||
const Number: FunctionalComponent<NumberProps> = 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<Omit<StatisticPropsType, 'formatter'>
|
|||
if (decimal) {
|
||||
decimal = `${decimalSeparator}${decimal}`;
|
||||
}
|
||||
valueNode = (
|
||||
<>
|
||||
|
||||
valueNode = [
|
||||
<span key="int" class={`${prefixCls}-content-value-int`}>
|
||||
{negative}
|
||||
{int}
|
||||
</span>
|
||||
{decimal && (
|
||||
</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;
|
||||
export default Number;
|
||||
|
|
|
@ -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<countdownValueType>,
|
||||
},
|
||||
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<typeof Statistic['setup']>['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 };
|
||||
},
|
||||
render() {
|
||||
const { prefixCls, valueStyle, valueRender, value } = this;
|
||||
props: initDefaultProps(StatisticProps, {
|
||||
decimalSeparator: '.',
|
||||
groupSeparator: ',',
|
||||
}),
|
||||
|
||||
const valueNode = (
|
||||
<StatisticNumber
|
||||
{...{
|
||||
...this.$props,
|
||||
prefixCls: this.prefixCls,
|
||||
formatter: getComponent(this, 'formatter', { value }),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
|
||||
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 = <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>}
|
||||
{valueRender ? valueRender(valueNode) : valueNode}
|
||||
{valueNode}
|
||||
{suffix && <span class={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default Statistic;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue