Explorar el Código

feat: add countTo components

xiaoxian521 hace 4 años
padre
commit
42dfb536bd

+ 2 - 2
index.html

@@ -49,7 +49,7 @@
       .app-loading .app-loading-title {
         display: flex;
         margin-top: 30px;
-        font-size: 30px;
+        font-size: 1.2em;
         color: rgba(0, 0, 0, 0.85);
         justify-content: center;
         align-items: center;
@@ -61,7 +61,7 @@
         width: 48px;
         height: 48px;
         margin-top: 30px;
-        font-size: 32px;
+        font-size: 1.2em;
         transform: rotate(45deg);
         box-sizing: border-box;
         animation: antRotate 1.2s infinite linear;

+ 13 - 0
package-lock.json

@@ -895,6 +895,11 @@
       "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
       "dev": true
     },
+    "is-plain-object": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
+      "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g=="
+    },
     "json5": {
       "version": "1.0.1",
       "resolved": "http://192.168.250.101:4873/json5/-/json5-1.0.1.tgz",
@@ -1558,6 +1563,14 @@
       "resolved": "http://192.168.250.101:4873/vue-router/-/vue-router-4.0.4.tgz",
       "integrity": "sha1-rZtLe72tYiQHtP8YmxZG9IwekFM="
     },
+    "vue-types": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
+      "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
+      "requires": {
+        "is-plain-object": "3.0.1"
+      }
+    },
     "vuedraggable": {
       "version": "4.0.1",
       "resolved": "http://192.168.250.101:4873/vuedraggable/-/vuedraggable-4.0.1.tgz",

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "vue": "^3.0.10",
     "vue-i18n": "^9.0.0",
     "vue-router": "^4.0.4",
+    "vue-types": "^3.0.2",
     "vuedraggable": "^4.0.1",
     "vuex": "^4.0.0",
     "vxe-table": "^4.0.7-beta.4",

+ 172 - 0
src/components/countTo/src/index.vue

@@ -0,0 +1,172 @@
+<template>
+  <span>{{ displayValue }}</span>
+</template>
+<script lang="ts">
+import {
+  defineComponent,
+  reactive,
+  computed,
+  watch,
+  onMounted,
+  unref,
+  toRef
+} from "vue";
+import { countToProps } from "./props";
+import { isNumber } from "/@/utils/is";
+export default defineComponent({
+  name: "CountTo",
+  props: countToProps,
+  emits: ["mounted", "callback"],
+  setup(props, { emit }) {
+    const state = reactive<{
+      localStartVal: number;
+      printVal: number | null;
+      displayValue: string;
+      paused: boolean;
+      localDuration: number | null;
+      startTime: number | null;
+      timestamp: number | null;
+      rAF: any;
+      remaining: number | null;
+    }>({
+      localStartVal: props.startVal,
+      displayValue: formatNumber(props.startVal),
+      printVal: null,
+      paused: false,
+      localDuration: props.duration,
+      startTime: null,
+      timestamp: null,
+      remaining: null,
+      rAF: null
+    });
+
+    onMounted(() => {
+      if (props.autoplay) {
+        start();
+      }
+      emit("mounted");
+    });
+
+    const getCountDown = computed(() => {
+      return props.startVal > props.endVal;
+    });
+
+    watch([() => props.startVal, () => props.endVal], () => {
+      if (props.autoplay) {
+        start();
+      }
+    });
+
+    function start() {
+      const { startVal, duration } = props;
+      state.localStartVal = startVal;
+      state.startTime = null;
+      state.localDuration = duration;
+      state.paused = false;
+      state.rAF = requestAnimationFrame(count);
+    }
+
+    function pauseResume() {
+      if (state.paused) {
+        resume();
+        state.paused = false;
+      } else {
+        pause();
+        state.paused = true;
+      }
+    }
+
+    function pause() {
+      cancelAnimationFrame(state.rAF);
+    }
+
+    function resume() {
+      state.startTime = null;
+      state.localDuration = +(state.remaining as number);
+      state.localStartVal = +(state.printVal as number);
+      requestAnimationFrame(count);
+    }
+
+    function reset() {
+      state.startTime = null;
+      cancelAnimationFrame(state.rAF);
+      state.displayValue = formatNumber(props.startVal);
+    }
+
+    function count(timestamp: number) {
+      const { useEasing, easingFn, endVal } = props;
+      if (!state.startTime) state.startTime = timestamp;
+      state.timestamp = timestamp;
+      const progress = timestamp - state.startTime;
+      state.remaining = (state.localDuration as number) - progress;
+      if (useEasing) {
+        if (unref(getCountDown)) {
+          state.printVal =
+            state.localStartVal -
+            easingFn(
+              progress,
+              0,
+              state.localStartVal - endVal,
+              state.localDuration as number
+            );
+        } else {
+          state.printVal = easingFn(
+            progress,
+            state.localStartVal,
+            endVal - state.localStartVal,
+            state.localDuration as number
+          );
+        }
+      } else {
+        if (unref(getCountDown)) {
+          state.printVal =
+            state.localStartVal -
+            (state.localStartVal - endVal) *
+              (progress / (state.localDuration as number));
+        } else {
+          state.printVal =
+            state.localStartVal +
+            (endVal - state.localStartVal) *
+              (progress / (state.localDuration as number));
+        }
+      }
+      if (unref(getCountDown)) {
+        state.printVal = state.printVal < endVal ? endVal : state.printVal;
+      } else {
+        state.printVal = state.printVal > endVal ? endVal : state.printVal;
+      }
+      state.displayValue = formatNumber(state.printVal);
+      if (progress < (state.localDuration as number)) {
+        state.rAF = requestAnimationFrame(count);
+      } else {
+        emit("callback");
+      }
+    }
+
+    function formatNumber(num: number | string) {
+      const { decimals, decimal, separator, suffix, prefix } = props;
+      num = Number(num).toFixed(decimals);
+      num += "";
+      const x = num.split(".");
+      let x1 = x[0];
+      const x2 = x.length > 1 ? decimal + x[1] : "";
+      const rgx = /(\d+)(\d{3})/;
+      if (separator && !isNumber(separator)) {
+        while (rgx.test(x1)) {
+          x1 = x1.replace(rgx, "$1" + separator + "$2");
+        }
+      }
+      return prefix + x1 + x2 + suffix;
+    }
+
+    return {
+      count,
+      reset,
+      resume,
+      start,
+      pauseResume,
+      displayValue: toRef(state, "displayValue")
+    };
+  }
+});
+</script>

+ 27 - 0
src/components/countTo/src/props.ts

@@ -0,0 +1,27 @@
+import { PropType } from 'vue'
+import { propTypes } from '/@/utils/propTypes'
+export const countToProps = {
+  startVal: propTypes.number.def(0),
+  endVal: propTypes.number.def(2020),
+  duration: propTypes.number.def(1300),
+  autoplay: propTypes.bool.def(true),
+  decimals: {
+    type: Number as PropType<number>,
+    required: false,
+    default: 0,
+    validator(value: number) {
+      return value >= 0
+    },
+  },
+  decimal: propTypes.string.def('.'),
+  separator: propTypes.string.def(','),
+  prefix: propTypes.string.def(''),
+  suffix: propTypes.string.def(''),
+  useEasing: propTypes.bool.def(true),
+  easingFn: {
+    type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
+    default(t: number, b: number, c: number, d: number) {
+      return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
+    },
+  },
+}

+ 4 - 0
src/style/index.scss

@@ -104,4 +104,8 @@ ul {
     filter: url("data:image/svg+xml;utf8,#grayscale");
     filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
     -webkit-filter: grayscale(1);
+}
+
+.el-loading-mask {
+  z-index: -1;
 }

+ 94 - 0
src/utils/is.ts

@@ -0,0 +1,94 @@
+const toString = Object.prototype.toString
+
+export function is(val: unknown, type: string) {
+  return toString.call(val) === `[object ${type}]`
+}
+
+export function isDef<T = unknown>(val?: T): val is T {
+  return typeof val !== 'undefined'
+}
+
+export function isUnDef<T = unknown>(val?: T): val is T {
+  return !isDef(val)
+}
+
+export function isObject(val: any): val is Record<any, any> {
+  return val !== null && is(val, 'Object')
+}
+
+export function isEmpty<T = unknown>(val: T): val is T {
+  if (isArray(val) || isString(val)) {
+    return val.length === 0
+  }
+
+  if (val instanceof Map || val instanceof Set) {
+    return val.size === 0
+  }
+
+  if (isObject(val)) {
+    return Object.keys(val).length === 0
+  }
+
+  return false
+}
+
+export function isDate(val: unknown): val is Date {
+  return is(val, 'Date')
+}
+
+export function isNull(val: unknown): val is null {
+  return val === null
+}
+
+export function isNullAndUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) && isNull(val)
+}
+
+export function isNullOrUnDef(val: unknown): val is null | undefined {
+  return isUnDef(val) || isNull(val)
+}
+
+export function isNumber(val: unknown): val is number {
+  return is(val, 'Number')
+}
+
+export function isPromise<T = any>(val: unknown): val is Promise<T> {
+  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
+}
+
+export function isString(val: unknown): val is string {
+  return is(val, 'String')
+}
+
+export function isFunction(val: unknown): val is Function {
+  return typeof val === 'function'
+}
+
+export function isBoolean(val: unknown): val is boolean {
+  return is(val, 'Boolean')
+}
+
+export function isRegExp(val: unknown): val is RegExp {
+  return is(val, 'RegExp')
+}
+
+export function isArray(val: any): val is Array<any> {
+  return val && Array.isArray(val)
+}
+
+export function isWindow(val: any): val is Window {
+  return typeof window !== 'undefined' && is(val, 'Window')
+}
+
+export function isElement(val: unknown): val is Element {
+  return isObject(val) && !!val.tagName
+}
+
+export const isServer = typeof window === 'undefined'
+
+export const isClient = !isServer
+
+export function isUrl(path: string): boolean {
+  const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
+  return reg.test(path)
+}

+ 33 - 0
src/utils/propTypes.ts

@@ -0,0 +1,33 @@
+import { CSSProperties, VNodeChild } from 'vue'
+import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'
+
+export type VueNode = VNodeChild | JSX.Element
+
+type PropTypes = VueTypesInterface & {
+  readonly style: VueTypeValidableDef<CSSProperties>
+  readonly VNodeChild: VueTypeValidableDef<VueNode>
+}
+
+const propTypes = createTypes({
+  func: undefined,
+  bool: undefined,
+  string: undefined,
+  number: undefined,
+  object: undefined,
+  integer: undefined,
+}) as PropTypes
+
+propTypes.extend([
+  {
+    name: 'style',
+    getter: true,
+    type: [String, Object],
+    default: undefined,
+  },
+  {
+    name: 'VNodeChild',
+    getter: true,
+    type: undefined,
+  }
+])
+export { propTypes }

+ 28 - 0
src/utils/uuid.ts

@@ -0,0 +1,28 @@
+const hexList: string[] = []
+for (let i = 0; i <= 15; i++) {
+  hexList[i] = i.toString(16)
+}
+
+export function buildUUID(): string {
+  let uuid = ''
+  for (let i = 1; i <= 36; i++) {
+    if (i === 9 || i === 14 || i === 19 || i === 24) {
+      uuid += '-'
+    } else if (i === 15) {
+      uuid += 4
+    } else if (i === 20) {
+      uuid += hexList[(Math.random() * 4) | 8]
+    } else {
+      uuid += hexList[(Math.random() * 16) | 0]
+    }
+  }
+  return uuid.replace(/-/g, '')
+}
+
+let unique = 0
+export function buildShortUUID(prefix = ''): string {
+  const time = Date.now()
+  const random = Math.floor(Math.random() * 1000000000)
+  unique++
+  return prefix + '_' + random + unique + String(time)
+}

+ 6 - 3
src/views/welcome.vue

@@ -2,16 +2,19 @@
   <div class="welcome">
     <!-- <a title="欢迎Star" href="https://github.com/xiaoxian521/CURD-TS" target="_blank">点击打开仓库地址</a> -->
     <flop />
+    <CountTo prefix="$" :startVal="1" :endVal="200" />
   </div>
 </template>
 
 <script lang='ts'>
-import flop from "../components/flop/index.vue"
+import flop from "../components/flop/index.vue";
+import CountTo from "../components/countTo/src/index.vue";
 export default {
   name: "welcome",
   components: {
-    flop
-  },
+    flop,
+    CountTo
+  }
 };
 </script>