mirror of
https://gitee.com/yiming_chang/vue-pure-admin.git
synced 2024-11-29 17:57:37 +08:00
feat: add countTo components
This commit is contained in:
parent
f3d206da43
commit
42dfb536bd
@ -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
13
package-lock.json
generated
@ -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",
|
||||
|
@ -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
src/components/countTo/src/index.vue
Normal file
172
src/components/countTo/src/index.vue
Normal 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>
|
27
src/components/countTo/src/props.ts
Normal file
27
src/components/countTo/src/props.ts
Normal 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
|
||||
},
|
||||
},
|
||||
}
|
@ -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
94
src/utils/is.ts
Normal 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
33
src/utils/propTypes.ts
Normal 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
28
src/utils/uuid.ts
Normal 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)
|
||||
}
|
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user