Files
2026-01-05 12:47:14 +08:00

576 lines
16 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, defineComponent, nextTick, onMounted, onUnmounted, reactive, watch } from 'vue'
import { PREFIX } from '../_constants'
import { useExtend } from '../_hooks'
import { cloneDeep, getMainClass } from '../_utils'
import type { IData } from './countup'
import { countupEmits, countupProps } from './countup'
const props = defineProps(countupProps)
const emit = defineEmits(countupEmits)
defineExpose({ machineLuck })
const classes = computed(() => {
return getMainClass(props, componentName)
})
const data = reactive<IData>({
valFlag: false,
current: 0,
sortFlag: 'add',
initDigit1: 0,
initDigit2: 0,
to0_10: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
to10_0: [0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1],
timer: null,
totalCount: 0, // 正整数
pointNum: 0, // 小数位
numberVal: 0, // 数字
num_total_len: 0, // 数字长度
relNum: 0, // 去除小数点
customNumber: 1,
prizeLevelTrun: 0,
prizeY: [],
prizeYPrev: [],
// machineTransition: 'none',
finshMachine: 0,
notPrize: [],
typeMachine: '',
})
const { startFlag, scrolling, customBgImg, type } = reactive(props)
watch(
() => props.customChangeNum,
(count, prevCount) => {
clearIntervalTime()
// data.customNumber = count;
countGo(0)
},
)
watch(
() => props.machinePrizeLevel,
(count, prevCount) => {
data.prizeLevelTrun = count
},
)
watch(
() => props.initNum,
(count, prevCount) => {
data.current = count
data.valFlag = false
valChange()
},
)
watch(
() => props.endNum,
(count, prevCount) => {
data.current = props.initNum
data.valFlag = false
valChange()
},
)
function valChange() {
if (data.valFlag)
return false
if (startFlag) {
if (scrolling || customBgImg) {
if (type !== 'machine')
countGo()
}
else {
countChange()
setTimeout(() => {
data.valFlag = true
}, 300)
}
}
}
// 清空定时器
function clearIntervalTime() {
clearInterval(Number(data.timer))
data.timer = null
}
// 精确计算
function calculation(num1: number, num2: number, type: string) {
const num1Digits = (num1.toString().split('.')[1] || '').length
const num2Digits = (num2.toString().split('.')[1] || '').length
const baseNum = 10 ** Math.max(num1Digits, num2Digits)
if (type === '-') {
const n = Number((num1 * baseNum - num2 * baseNum).toFixed(0))
return n / baseNum
}
else {
const m = Number((num1 * baseNum + num2 * baseNum).toFixed(0))
return m / baseNum
}
}
// 数字滚动-top值
function topNumber(index: number) {
const { num_total_len, pointNum, initDigit1, initDigit2, sortFlag } = data
const idx1
= sortFlag === 'add' || sortFlag === 'equal'
? String(initDigit2)[index - (num_total_len - pointNum)]
: 10 - Number(String(initDigit2)[index - (num_total_len - pointNum)])
const idx2
= sortFlag === 'add' || sortFlag === 'equal' ? String(initDigit1)[index] : 10 - Number(String(initDigit1)[index])
let num
= index > num_total_len - pointNum - 1
? `${-idx1 * 100}%`
: index <= String(initDigit1).length - 1
? `${-idx2 * 100}%`
: 0
if (num === '-1000%')
num = 0
return num
}
// 数字滚动-到哪里了
function turnNumber(index: number) {
const { num_total_len, pointNum, initDigit1, initDigit2, sortFlag } = data
const idx1 = String(initDigit2)[index - (num_total_len - pointNum)]
const num
= index > num_total_len - pointNum - 1
? idx1 || 0
: index <= String(initDigit1).length - 1
? String(initDigit1)[index]
: 0
return num
}
// 基础用法
function countChange() {
const { endNum, initNum, speed, toFixed } = props
const countTimer = setInterval(() => {
if (initNum > endNum) {
// 减少
if (data.current <= endNum || data.current <= speed) {
// 数字减小有可能导致current小于speed
data.current = Number(endNum.toFixed(toFixed))
clearInterval(countTimer)
emit('scrollEnd')
data.valFlag = false
}
else {
data.current = Number((Number.parseFloat(String(data.current)) - Number.parseFloat(String(speed))).toFixed(toFixed))
}
}
else {
// 增加
if (data.current >= endNum) {
data.current = Number(endNum.toFixed(toFixed))
clearInterval(countTimer)
emit('scrollEnd')
data.valFlag = false
}
else {
data.current = Number((Number.parseFloat(String(data.current)) + Number.parseFloat(String(speed))).toFixed(toFixed))
}
}
}, props.during)
}
function countGo(flag?: number): void {
let { initNum, endNum, toFixed, customBgImg } = props
if (customBgImg)
initNum = props.customChangeNum
// --------------
let startNumber1, startNumber2, endNumber1, endNumber2
if (initNum !== 0) {
if (toFixed !== 0)
initNum = Number(initNum.toFixed(toFixed))
if (String(initNum).includes('.')) {
startNumber1 = String(initNum).split('.')[0].length
startNumber2 = String(initNum).split('.')[1].length
}
else {
startNumber1 = String(initNum).length
startNumber2 = 0
}
}
else {
startNumber1 = 1
startNumber2 = 0
}
if (endNum !== 0) {
if (toFixed !== 0)
endNum = Number(endNum.toFixed(toFixed))
if (String(endNum).includes('.')) {
endNumber1 = String(endNum).split('.')[0].length
endNumber2 = String(endNum).split('.')[1].length
}
else {
endNumber1 = String(endNum).length
endNumber2 = 0
}
}
else {
endNumber1 = 1
endNumber2 = 0
}
const len1 = startNumber1 >= endNumber1 ? startNumber1 : endNumber1
const len2 = startNumber2 >= endNumber2 ? startNumber2 : endNumber2
data.num_total_len = len1 + len2
data.pointNum = len2
// --------------
if (initNum > endNum) {
// 减少
data.sortFlag = 'reduce'
data.to0_10 = [0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
data.totalCount = calculation(initNum, endNum, '-')
data.numberVal = Number(String(initNum))
}
else if (initNum < endNum) {
// 增加
data.sortFlag = 'add'
data.to0_10 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
data.totalCount = calculation(endNum, initNum, '-')
data.numberVal = Number(String(endNum))
}
else {
data.sortFlag = 'equal'
}
// 将小数位数计算后补0
let unit = 1
for (let i = 0; i < data.pointNum; i++)
unit *= 10
const rel_big = data.numberVal * unit // 去除小数点后的数unit几个零表示有几个小数
data.relNum = rel_big
// this.totalCount = rel_big;
if (toFixed !== 0) {
// 计算小数点后的位数,小数位
data.pointNum = String(data.numberVal).split('.')[1] ? String(data.numberVal).split('.')[1].length : 0
// 数字长度
data.num_total_len = String(rel_big).length
}
if (String(initNum).includes('.')) {
const n = String(initNum).split('.')
data.initDigit1 = Number(n[0])
data.initDigit2 = Number(n[1])
}
else {
data.initDigit1 = initNum
data.initDigit2 = 0
}
if (scrolling && !customBgImg) {
// #ifdef H5
// 数字都是从小加到大的所以我们循环转动最后一个数字传入最后一个数字的DOM
nextTick(() => {
if (data.sortFlag === 'equal')
return false
const refsDom: HTMLCollectionOf<Element> = document.getElementsByClassName('nut-countup__number-item')
const element = refsDom[data.num_total_len - 1]
runTurn(element)
})
// #endif
}
else {
if (flag !== 0)
imgNumberScroll()
}
}
function runTurn(el: Element) {
clearIntervalTime()
let m = 1
if (data.pointNum !== 0)
m = 1 / 10 ** data.pointNum;
// 设置定时器
(data.timer as any) = setInterval(() => {
runStep(el)
data.totalCount = calculation(data.totalCount, m, '-')
// that.totalCount--;
if (data.totalCount <= 0) {
clearIntervalTime()
emit('scrollEnd')
data.valFlag = false
}
}, props.during)
}
function runStep(el: any) {
// let currentTurn = el.getAttribute('turn-number');
const currentTurn = el.style.all
let turningNum: number
if (data.sortFlag === 'add')
turningNum = Number.parseInt(String(currentTurn)) + 1
else
turningNum = Number.parseInt(String(currentTurn)) - 1 >= 0 ? Number.parseInt(String(currentTurn)) - 1 : 9
// el.setAttribute('turn-number', String(turningNum));
el.style.all = String(turningNum)
if (el.style.transition === 'none' || turningNum === 1 || !el.style.transition)
el.style.transition = `all linear ${props.during}ms`
if (turningNum === 10 || (data.sortFlag === 'reduce' && turningNum === 0)) {
let timeOut: any = null
// el.style.top = `-${turningNum * 100}%`;
el.style.top = `-${data.sortFlag === 'add' ? turningNum * 100 : (10 - turningNum) * 100}%`
// el.setAttribute('turn-number', '0');
el.style.all = '0'
timeOut = setTimeout(() => {
timeOut && clearTimeout(timeOut)
el.style.transition = 'none'
el.style.top = '0'
// 前面数字的滚动,用于递增
if (turningNum === 10) {
if (el.previousSibling)
runStep(el.previousSibling as HTMLElement)
}
}, 0.975 * props.during)
}
else {
// el.style.top = `-${(10-turningNum)*100}%`;
el.style.top = `-${data.sortFlag === 'add' ? turningNum * 100 : (10 - turningNum) * 100}%`
}
// 用于递减的时候
if (el.style.top === '-100%' && data.sortFlag === 'reduce')
runStep(el.previousSibling as HTMLElement)
}
// 自定义图片
function imgNumberScroll() {
let m = 1
if (data.pointNum !== 0)
m = 10 ** data.pointNum
// #ifdef H5
nextTick(() => {
const f = document.getElementsByClassName('nut-countup__numberimg')[0]
// setTimeout(() => {
// data.relNum = calculation(data.relNum, m * props.speed, '+');
// }, props.during);
f.addEventListener('webkitTransitionEnd', () => {
emit('scrollEnd')
data.valFlag = false
// setTimeout(() => {
// data.relNum = calculation(data.relNum, m * props.speed, '+');
// }, props.during);
})
})
// #endif
}
// 不中奖设置随机数
function generateRandom() {
data.notPrize = []
while (data.notPrize.length < 3) {
const rand: number = Math.floor(Math.random() * props.machinePrizeNum + 1)
if (!data.notPrize.includes(rand))
data.notPrize.push(rand)
}
}
// 抽奖
function machineLuck() {
const machineTurnMoreNum = props.machineTurnMore < 0 ? 0 : props.machineTurnMore
const distance = props.numHeight * props.machinePrizeNum // 所有奖品的高度,雪碧图的高度
if (data.prizeLevelTrun < 0)
generateRandom()
for (let i = 0; i < props.machineNum; i++) {
setTimeout(() => {
const turn = distance * (i + 1 + Number.parseFloat(String(machineTurnMoreNum)))
if (data.prizeYPrev.length !== 0) {
// this.machineTransition = 'none';
// console.log(this.prizeYPrev[i]-(this.numHeight * this.machinePrizeNum));
// this.$set(data.prizeY, i, data.prizeYPrev[i]);
data.prizeY[i] = data.prizeYPrev[i]
}
const local = data.prizeYPrev[i] ? data.prizeYPrev[i] : 0
let newLocation
= turn + local + (props.machinePrizeNum - data.prizeLevelTrun + 1) * props.numHeight + (distance - local)
if (data.prizeLevelTrun < 0)
newLocation += props.numHeight * data.notPrize[i]
scrollTime(
i,
// parseFloat((this.machinePrizeNum-(this.prizeLevelTrun-1))*this.numHeight + turn + local),
newLocation,
local,
)
}, 500 * i)
}
}
useExtend({ machineLuck })
function scrollTime(index: number, total: number, num: number) {
// this.machineTransition = `all linear ${this.during/this.machinePrizeNum}ms`;
let t: any = setInterval(() => {
if (num <= total) {
num += 10
data.prizeY[index] = Number.parseFloat(String(num))
}
else {
clearInterval(t)
t = null
data.finshMachine += 1
data.prizeY[index] = total
if (data.finshMachine === props.machineNum) {
const distance = props.numHeight * props.machinePrizeNum
data.prizeYPrev = []
const prevAry = cloneDeep(data.prizeY)
prevAry.forEach((item: any) => {
let n = item
while (n > distance)
n -= distance
data.prizeYPrev.push(n)
})
setTimeout(() => {
data.finshMachine = 0
if (data.prizeLevelTrun < 0) {
emit('scrollEnd', false)
data.valFlag = false
}
else {
emit('scrollEnd', true)
data.valFlag = false
}
}, 130)
}
}
}, 30)
}
onMounted(() => {
data.current = props.initNum
nextTick(() => {
valChange()
})
})
onUnmounted(() => {
clearIntervalTime()
data.timer = null
})
</script>
<script lang="ts">
const componentName = `${PREFIX}-countup`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="customStyle">
<template v-if="customBgImg !== ''">
<template v-if="type === 'machine'">
<view class="nut-countup__machine" :style="{ height: `${numHeight}px` }">
<view
v-for="(val, index) of machineNum"
:key="`mImg${index}`"
class="nut-countup__machine-item"
:style="{
width: `${numWidth}px`,
height: `${numHeight}px`,
background: `url(${customBgImg}) `,
backgroundPosition: `0 ${data.prizeY[index]}px`,
}"
/>
</view>
</template>
<template v-else>
<view class="nut-countup__numberimg" :style="{ height: `${numHeight}px` }">
<view
v-for="(val, index) of data.num_total_len"
:key="`cImg${index}`"
class="nut-countup__numberimg__item"
:style="{
width: `${numWidth}px`,
height: `${numHeight}px`,
left:
`${numWidth
* (index > data.num_total_len - data.pointNum - 1
? index === data.num_total_len - data.pointNum
? index * 1.5
: index * 1.3
: index)
}px`,
backgroundImage: `url(${customBgImg})`,
backgroundPosition:
`0 ${-(+String(data.relNum)[index] * numHeight + customSpacNum * +String(data.relNum)[index])}px`,
transition: `all linear ${during / 10}ms`,
}"
/>
<view
v-if="data.pointNum > 0"
class="nut-countup-pointstyl"
:style="{
width: `${numWidth / 2}px`,
bottom: 0,
left: `${numWidth * (data.num_total_len - data.pointNum) * 1.1}px`,
fontSize: '30px',
}"
>
.
</view>
</view>
</template>
</template>
<template v-else>
<view
v-if="scrolling"
class="nut-countup__number"
:style="{
width: `${numWidth * data.num_total_len + numWidth / 3}px`,
height: `${numHeight}px`,
lineHeight: `${numHeight}px`,
}"
>
<view
v-for="(val, index) of data.num_total_len"
:key="val"
class="nut-countup__number-item"
:style="{
all: turnNumber(index) as any,
top: topNumber(index),
left: `${numWidth * (index > data.num_total_len - data.pointNum - 1 ? index * 1.1 : index)}px`,
}"
:turn-number="turnNumber(index)"
>
<view
v-for="(item, idx) of data.to0_10"
:key="`dote${idx}`"
class="nut-countup__number-item__span"
:style="{
width: `${numWidth}px`,
height: `${numHeight}px`,
lineHeight: `${numHeight}px`,
}"
>
{{ item }}
</view>
</view>
<view
v-if="data.pointNum > 0"
class="nut-countup-pointstyl"
:style="{
width: `${numWidth / 3}px`,
height: `${numHeight}px`,
lineHeight: `${numHeight}px`,
top: 0,
left: `${numWidth * (data.num_total_len - data.pointNum)}px`,
}"
>
.
</view>
</view>
<template v-else>
{{ data.current }}
</template>
</template>
</view>
</template>
<style lang="scss">
@import './index';
</style>