某个公共组件的
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { formatTime } from '@/utils'
const props = defineProps({
argList: {
type: Array,
default: () => [{ name: '--', value: '--', unit: '--', url: '' }],
},
noteName:{
type: String
}
})
const emit = defineEmits<{
(event: 'toDeviceDetail', item: { value: string | number; unit: string; name: string }): void
}>()
const tickerArr = ref([])
// 存储当前显示的值的索引
const displayValues = ref<
Record<number, { current: string | number; prev: string | number; showPrev: boolean }>
>({})
// 新增:用于强制刷新的计数器
const refreshCounter = ref(0)
// 初始化 displayValues
onMounted(() => {
props.argList.forEach((item, index) => {
displayValues.value[index] = {
current: item.value,
prev: item.prevValue,
showPrev: false,
}
})
// 每秒递增计数器,触发视图更新
const timer = setInterval(() => refreshCounter.value++, 1000)
const instance = ref(getCurrentInstance())
nextTick(()=>{
// 先尝试从本地读取缓存
const cachedWidth = uni.getStorageSync('tickerContainerW')
if (cachedWidth && cachedWidth > 0) {
console.log('✅ 读取本地缓存宽度:', cachedWidth)
return
}
const query = uni.createSelectorQuery().in(instance.value.proxy)
if (!instance.value) {
console.warn('getCurrentInstance 为 null')
return
}
query.selectAll('#tickerContainer').boundingClientRect(rect => {
console.log(' = noteName tickerContainer', rect)
if(rect && rect[0] && rect[0].width > 0){
uni.setStorageSync('tickerContainerW', rect[0].width)
}
}).exec()
// 各个数值
props.argList.forEach((els, index) => {
const query1 = uni.createSelectorQuery().in(instance.value.proxy)
query1.select('#ticker' + props.noteName + index).boundingClientRect(rect => {
console.log("++++++++++++++++++++++")
console.log(' = props.noteName =', props.noteName)
console.log('rect.width : tickerContainerW',rect.width,' : ',uni.getStorageSync('tickerContainerW'))
console.log("++++++++++++++++++++++")
if (rect) {
if (rect.width > uni.getStorageSync('tickerContainerW')) {
tickerArr.value[index] = true
} else {
tickerArr.value[index] = false
}
}
}).exec();
})
})
onBeforeUnmount(() => clearInterval(timer))
})
// 监听 argList 变化
watch(
() => props.argList,
(newVal) => {
newVal.forEach((item, index) => {
if (item.prevValue && item.value !== item.prevValue) {
startAnimation(index, item.value, item.prevValue)
} else {
displayValues.value[index] = {
current: item.value,
prev: item.prevValue ? item.value : item.prevValue,
showPrev: false,
}
}
})
// tickerChange()
},
{ deep: true },
)
const tickerChange = () => {
// let tickerContainerW = 0
//
// const instance = getCurrentInstance()
// const query = uni.createSelectorQuery().in(instance.proxy)
// if (!instance) {
// console.warn('getCurrentInstance 为 null')
// return
// }
//
// query.selectAll('#tickerContainer').boundingClientRect(rect => {
// console.log(' = noteName tickerContainer', rect)
// if (rect && tickerContainerW === 0) {
// tickerContainerW = rect[0].width
// console.log("= tickerContainerW width",tickerContainerW)
// }
// }).exec();
//
// console.log("= 固定宽 tickerContainerW width",tickerContainerW)
//
// props.argList.forEach((els, index) => {
// const instance = getCurrentInstance()
// const query = uni.createSelectorQuery().in(instance.proxy)
// query.select('#ticker' + props.noteName + index).boundingClientRect(rect => {
// console.log("++++++++++++++++++++++")
// console.log(' = props.noteName =', props.noteName)
// console.log('rect.width : tickerContainerW',rect.width,' : ',tickerContainerW)
// console.log("++++++++++++++++++++++")
// if (rect) {
// if (rect.width > tickerContainerW) {
// tickerArr.value[index] = true
// } else {
// tickerArr.value[index] = false
// }
// }
//
// }).exec();
// })
//
// console.log('noteName tickerArr', tickerArr.value)
}
const tickerContainer = ref("tickerContainer")
// 开始动画
const startAnimation = (
index: number,
currentValue: string | number,
prevValue: string | number,
) => {
displayValues.value[index] = {
current: currentValue,
prev: prevValue,
showPrev: true,
}
let count = 0
const maxCount = 10 // 10秒 / 1秒 = 10次
const interval = setInterval(() => {
count++
if (count >= maxCount) {
clearInterval(interval)
displayValues.value[index].showPrev = false
return
}
displayValues.value[index].showPrev = !displayValues.value[index].showPrev
}, 1000)
}
const toDetail = (item: { value: string | number; unit: string; name: string }) => {
emit('toDeviceDetail', item)
}
const dynamicFormatTime = (timestamp: number) => {
refreshCounter.value // 依赖计数器,强制重新计算
return formatTime(timestamp) // 调用原始方法
}
</script>
<template>
<wd-row custom-class="device-body" >
<wd-col :span="12" custom-class="mt-4 arg-top" class="mt-4" v-for="(item, key) in props.argList" :key="key">
<view
class="arg-item mt-4"
:class="{ _box: item.value !== item.prevValue }"
@click="toDetail(item)"
>
<wd-row style="display: flex">
<wd-col :span="19">
<view class="arg-name">
<p>{{ $te('element.' + item.yeuSign) ? $t('element.' + item.yeuSign) : $t('element.default') }}</p>
</view>
<view :id="tickerContainer" class="arg-value">
<view :class="tickerArr[key]==true?'active-arg-value-text':''" :id="'ticker'+props.noteName+key" class="value-container">
<view class="value-animation" :class="{
'prev-value': displayValues[key]?.showPrev,
'current-value': !displayValues[key]?.showPrev,
}">
<view>
{{
displayValues[key]?.showPrev
? displayValues[key]?.prev
: displayValues[key]?.current
}}
</view>
</view>
<view class="value-unit">{{ item.unit || item.elementUnit || item.yeuUnit }}</view>
</view>
</view>
<view class="arg-datetime">{{ dynamicFormatTime(item.time) }}</view>
</wd-col>
<wd-col
:span="5"
custom-class="arg-url"
>
<view v-if="item.yeuUrl" style="width: 100%; display: flex; align-items: center; justify-content: flex-end; height: 100%">
<img
style="width: 36rpx; height: 36rpx; object-fit: contain"
:src="item.yeuUrl"
alt=""
srcset=""
/>
</view>
<view v-else>--</view>
</wd-col>
</wd-row>
</view>
</wd-col>
</wd-row>
</template>
<style scoped lang="scss">
:deep(.device-body) {
display: flex!important;
flex-wrap: wrap!important;
justify-content: space-between!important;
}
::v-deep {
.device-body {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}
::v-deep {
.arg-top {
margin-top: 6px!important;
}
}
::v-deep {
.arg-url {
display: flex!important;
align-items: center!important;
justify-content: flex-end!important;
height: 100%!important;
}
}
:deep(.wd-col__12.data-v-ce06229d) {
margin-top: 6px!important;
}
.wd-row::after {
content: none !important;
}
.device-body {
display: flex;
flex-wrap: wrap;
gap: 10rpx;
justify-content: space-between;
}
.arg-item {
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
width: 326rpx;
min-height: 160rpx;
padding: 15rpx;
margin: 0 auto;
background-color: #ffffff;
border-radius: 20rpx;
}
:deep(.arg-datetime) {
width: 280rpx;
padding: 8rpx 0;
font-size: 21rpx;
font-weight: 300;
color: #565656;
}
:deep(.arg-name) {
font-size: 25rpx;
font-weight: 400;
color: #000000;
}
:deep(.arg-value) {
font-size: 42rpx;
font-weight: bold;
font-family: monospace;
color: #000000;
position: relative;
height: 50rpx;
overflow: hidden;
.value-container {
position: absolute;
display: flex;
align-items: center;
}
.value-animation {
transition: all 0.3s ease;
white-space: nowrap;
}
.active-arg-value-text {
animation: scroll-left 5s linear infinite;
}
@keyframes scroll-left {
0% {
left: 100%;
}
100% {
left: -100%;
}
}
.value-unit {
margin-left: 4rpx;
color: #000000;
font-size: 26rpx;
font-weight: normal;
}
.prev-value {
color: red;
}
.current-value {
color: inherit;
}
}
:deep(.arg-icon) {
width: 69rpx;
height: 69rpx;
background: rgba(154, 200, 251, 0.1);
border-radius: 50%;
}
._box {
animation: breathe 1000ms ease-in-out 10 alternate;
}
@keyframes breathe {
30% {
box-shadow: 0rpx 1rpx 10rpx 1rpx #8eceac;
}
50% {
box-shadow: 0rpx 1rpx 10rpx 1rpx #5ece96;
}
100% {
box-shadow: 0rpx 1rpx 10rpx 1rpx #2dce89;
}
}
</style>