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 = (
 | 
			
		||||
        <>
 | 
			
		||||
          <span key="int" class={`${prefixCls}-content-value-int`}>
 | 
			
		||||
            {negative}
 | 
			
		||||
            {int}
 | 
			
		||||
 | 
			
		||||
      valueNode = [
 | 
			
		||||
        <span key="int" class={`${prefixCls}-content-value-int`}>
 | 
			
		||||
          {negative}
 | 
			
		||||
          {int}
 | 
			
		||||
        </span>,
 | 
			
		||||
        decimal && (
 | 
			
		||||
          <span key="decimal" class={`${prefixCls}-content-value-decimal`}>
 | 
			
		||||
            {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