feat: add countTo components

This commit is contained in:
xiaoxian521 2021-04-03 15:04:28 +08:00
parent f3d206da43
commit 42dfb536bd
10 changed files with 380 additions and 5 deletions

View File

@ -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
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

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

View File

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

View File

@ -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
src/utils/is.ts Normal file
View File

@ -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
src/utils/propTypes.ts Normal file
View File

@ -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
src/utils/uuid.ts Normal file
View File

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

View File

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