mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-06 00:18:51 +08:00
feat: add countTo components
This commit is contained in:
parent
f3d206da43
commit
42dfb536bd
@ -49,7 +49,7 @@
|
|||||||
.app-loading .app-loading-title {
|
.app-loading .app-loading-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
font-size: 30px;
|
font-size: 1.2em;
|
||||||
color: rgba(0, 0, 0, 0.85);
|
color: rgba(0, 0, 0, 0.85);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -61,7 +61,7 @@
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
font-size: 32px;
|
font-size: 1.2em;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
animation: antRotate 1.2s infinite linear;
|
animation: antRotate 1.2s infinite linear;
|
||||||
|
13
package-lock.json
generated
13
package-lock.json
generated
@ -895,6 +895,11 @@
|
|||||||
"integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
|
"integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=",
|
||||||
"dev": true
|
"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": {
|
"json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "http://192.168.250.101:4873/json5/-/json5-1.0.1.tgz",
|
"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",
|
"resolved": "http://192.168.250.101:4873/vue-router/-/vue-router-4.0.4.tgz",
|
||||||
"integrity": "sha1-rZtLe72tYiQHtP8YmxZG9IwekFM="
|
"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": {
|
"vuedraggable": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "http://192.168.250.101:4873/vuedraggable/-/vuedraggable-4.0.1.tgz",
|
"resolved": "http://192.168.250.101:4873/vuedraggable/-/vuedraggable-4.0.1.tgz",
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"vue": "^3.0.10",
|
"vue": "^3.0.10",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
"vue-router": "^4.0.4",
|
"vue-router": "^4.0.4",
|
||||||
|
"vue-types": "^3.0.2",
|
||||||
"vuedraggable": "^4.0.1",
|
"vuedraggable": "^4.0.1",
|
||||||
"vuex": "^4.0.0",
|
"vuex": "^4.0.0",
|
||||||
"vxe-table": "^4.0.7-beta.4",
|
"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: url("data:image/svg+xml;utf8,#grayscale");
|
||||||
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
|
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
|
||||||
-webkit-filter: 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">
|
<div class="welcome">
|
||||||
<!-- <a title="欢迎Star" href="https://github.com/xiaoxian521/CURD-TS" target="_blank">点击打开仓库地址</a> -->
|
<!-- <a title="欢迎Star" href="https://github.com/xiaoxian521/CURD-TS" target="_blank">点击打开仓库地址</a> -->
|
||||||
<flop />
|
<flop />
|
||||||
|
<CountTo prefix="$" :startVal="1" :endVal="200" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang='ts'>
|
<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 {
|
export default {
|
||||||
name: "welcome",
|
name: "welcome",
|
||||||
components: {
|
components: {
|
||||||
flop
|
flop,
|
||||||
},
|
CountTo
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user