hldy_app/pages/watch/settings/leida.vue

907 lines
20 KiB
Vue
Raw Normal View History

2025-08-21 16:51:53 +08:00
<template>
<view class="index-content-other">
<view class="index-content-right" @click="goback">
<image class="back-img" :src="`/static/index/settings/back.png`" />
返回
</view>
<view class="left-contain">
<view class="blue-bgc">
<image class="all-img" src="/static/index/leida/leftbgc.png" />
<image class="all-img" style="width: 90%;height: 90%;margin-top: 5%;margin-left: 5%;"
src="/static/index/leida/bigball.png" />
<!-- 旋转雷达部分 -->
<view class="inset-img" :style="insetStyle">
<image class="all-img" style="z-index: 2;" src="/static/index/leida/biao.png" />
<image class="all-img" style="z-index: 1;" src="/static/index/leida/ball.png" />
<image class="all-img" src="/static/index/leida/shallow.png" />
</view>
<!-- 小球层 -->
<view class="ball-layer">
<view v-for="ball in balls" :key="ball.id" class="ball" :style="{
width: ball.size + 'px',
height: ball.size + 'px',
top: ball.top + 'px',
left: ball.left + 'px',
backgroundColor: ball.color,
opacity: ball.opacity
}"></view>
</view>
</view>
<!-- 进度条 -->
<view style="width: 100%;display: flex;flex-direction: column;align-items: center;">
<view class="blue-button" style="margin-top: 0;" :class="targetNumber==-1?'zerotarget':''" @click="cantoggleRun">
{{ count===100?`扫描`: `扫描` }}
</view>
<view class="progress-wrap">
<view class="progress-inner" :style="{ width: count + '%' }"></view>
</view>
<view class="progress-text">{{ count }}%</view>
</view>
</view>
<view class="other">
<scroll-view ref="scrollViewRef" scroll-y class="other-father" :scroll-top="scrollTop">
<view class="card-father">
<view class="card" v-for="(allitem, allindex) in cardarray" :key="allindex"
:class="[ targetNumber===allindex &&infoNumber===-1 ? 'zerotarget' : '', { 'fade-in': allitem.isNew } ]"
@animationend="onAnimEnd(allitem)">
<!-- <view class="card-title">
<view style="font-size: 25rpx;">NUID{{allitem.nuId}}</view>
<view class="right-box">护理单元</view>
</view> -->
<!-- <view class="card-bottom">
{{allitem.nuName}}
<image class="bottom-img" src="@/static/index/leida/rename.png"
@click="renamenummber=allindex;savevalue=allitem.nuName" />
</view> -->
<view class="main-title">
<view class="main-title-font" style="font-weight: 600;font-size: 40rpx;">
{{allitem.nuName}}
</view>
<view class="video-father" :class="targetNumber===allindex && infoNumber===0 ? 'zerotarget' : ''">
<image class="edit-img" @click=""
src="/static/index/leida/play.png" />
</view>
</view>
<view class="blue-button" style="width: 200rpx;height: 70rpx;margin-top: 50rpx;"
:class="targetNumber===allindex && infoNumber===1 ? 'zerotarget' : ''"
@click="rename(allindex,allitem) ">
重命名
</view>
<!-- <view class="main-title">
<view class="main-title-font" style="margin-top: 20rpx;">
NUID:{{allitem.nuId}}
</view>
</view> -->
<view class="card-tags">
<view style="margin-left: 80rpx;">
NUID:{{allitem.nuId}}
<!-- {{ allitem.areaFlagName?allitem.areaFlagName:"护理单元" }} -->
</view>
</view>
<view class="play-img">
护理单元
</view>
<!-- <image class="play-img" src="/static/index/leida/play.png" /> -->
</view>
<!-- 弹出层 -->
<view v-if="renamenummber!==-1" class="popup-any" :style="isdonghua?{opacity:1}:{opacity:0}">
<view class="mask" @click="renamenummber=-1"></view>
<view class="rename-father">
<view class="rename-title">
重命名
</view>
<view style="width: 100%;margin-left: 5rpx;margin-bottom: 20rpx;">
NUID:{{savenuId}}
<!-- {{ allitem.areaFlagName?allitem.areaFlagName:"护理单元" }} -->
</view>
<view class="rename-input">
<!-- <image class="left-img" src="@/static/index/click.png" /> -->
<input class="uni-input" placeholder="请重新命名" v-model="savevalue" />
<image v-show="savevalue" class="right-img" src="@/static/index/quxiao.png"
@click="savevalue=``" />
</view>
<view class="blue-button" style="width: 200rpx;height: 70rpx;margin-top: 40rpx;"
@click="changename(savevalue,renamenummber)">
确定
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="index-content-down">
长春市朝阳区久泰开运养老服务有限公司
</view>
<arrowkeys @movecard="movecard" />
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { getCardList } from "@/component/Initialization/api.js"
const goback = () => {
uni.navigateBack()
}
/* ------------------ 旋转逻辑 ------------------ */
const running = ref(true)
const centerAngle = ref(0)
const speed = ref(100)
const renamenummber = ref(-1)
const savevalue = ref("");
const savenuId = ref();
const isdonghua = ref(false)
// const status = ref(0);
const rightbutton = ref([
{ url: '/static/index/leida/00.png', targetUrl: '/static/index/leida/01.png', target: false },
{ url: '/static/index/leida/10.png', targetUrl: '/static/index/leida/11.png', target: false },
{ url: '/static/index/leida/20.png', targetUrl: '/static/index/leida/21.png', target: false },
{ url: '/static/index/leida/30.png', targetUrl: '/static/index/leida/31.png', target: false }
])
const cardarray = ref<Array<{ menu : any; isNew : boolean; nuName : string }>>([])
const relArray = ref([]);
const scrollTop = ref(0);
let rafId : any = null
function raf(cb : FrameRequestCallback) {
if (typeof (globalThis as any).requestAnimationFrame === 'function') {
return (globalThis as any).requestAnimationFrame(cb)
} else {
return setTimeout(() => cb(Date.now() as unknown as DOMHighResTimeStamp), 16)
}
}
function caf(id : any) {
if (typeof (globalThis as any).cancelAnimationFrame === 'function') {
(globalThis as any).cancelAnimationFrame(id)
} else {
clearTimeout(id)
}
}
function nowTime(ts ?: number) {
if (typeof ts === 'number') return ts
if ((globalThis as any).performance?.now) {
return (globalThis as any).performance.now()
}
return Date.now()
}
let lastT : number | null = null
function loop(ts ?: number) {
const now = nowTime(ts)
if (!lastT) lastT = now
const dt = (now - lastT) / 1000
lastT = now
if (running.value) {
centerAngle.value = (centerAngle.value + speed.value * dt) % 360
}
rafId = raf(loop)
}
// 监听动画结束,清除标记
function onAnimEnd(item : { isNew : boolean }) {
item.isNew = false
}
function cantoggleRun() {
if (count.value === 100) {
startCounter()
toggleRun()
}
}
function toggleRun() {
running.value = !running.value
if (running.value) {
lastT = nowTime()
startBallGeneration()
} else {
stopBallGeneration()
}
}
const changename = (name, index) => {
if (name) {
cardarray.value[index].nuName = name;
}
renamenummber.value = -1
}
const insetStyle = computed(() => ({
transform: `rotate(${centerAngle.value}deg)`
}))
/* ------------------ 小球逻辑 ------------------ */
interface Ball {
id : number
size : number
top : number
left : number
color : string
opacity : number
}
const balls = ref<Ball[]>([])
let ballTimer : any = null
let ballId = 0
function createBall() {
const size = Math.random() * 1 + 5
const left = Math.random() * (200 - size)
const top = Math.random() * (200 - size)
const id = ballId++
const ball : Ball = {
id,
size,
left,
top,
color: "#02a9ff",
opacity: 0
}
balls.value.push(ball)
setTimeout(() => {
ball.opacity = 1
}, 20)
setTimeout(() => {
ball.opacity = 0
setTimeout(() => {
balls.value = balls.value.filter(b => b.id !== id)
}, 500)
}, 2000)
}
function startBallGeneration() {
if (ballTimer) return
ballTimer = setInterval(() => {
createBall()
}, 500)
}
function stopBallGeneration() {
clearInterval(ballTimer)
ballTimer = null
}
/* ------------------ 进度条逻辑 ------------------ */
const count = ref(0)
function startCounter() {
let start = Date.now()
let totalTime = 4000
let pausePoint = Math.floor(Math.random() * 30) + 30 // 30~60
let paused = false
let pauseDuration = Math.floor(Math.random() * 500) + 300 // 300~800ms
let pauseStart = 0
function update() {
let elapsed = Date.now() - start
// 到停顿点
if (!paused && count.value >= pausePoint) {
paused = true
pauseStart = Date.now()
}
// 停顿中
if (paused) {
if (Date.now() - pauseStart >= pauseDuration) {
paused = false
} else {
setTimeout(update, 16)
return
}
}
let progress = Math.min(elapsed / totalTime, 1)
count.value = Math.floor(progress * 100)
if (count.value === 100) {
toggleRun()
}
if (count.value < 100) {
setTimeout(update, 16)
}
}
getCardList().then(res => {
res.result.records.forEach((element : any) => {
element.menu = JSON.parse(JSON.stringify(rightbutton.value))
element.isNew = true
})
relArray.value = []
relArray.value.push(JSON.parse(JSON.stringify((res.result.records[0]))))
relArray.value.push(JSON.parse(JSON.stringify((res.result.records[0]))))
relArray.value.push(JSON.parse(JSON.stringify((res.result.records[0]))))
relArray.value.push(JSON.parse(JSON.stringify((res.result.records[0]))))
relArray.value.push(JSON.parse(JSON.stringify((res.result.records[0]))))
suiji()
})
setTimeout(update, 16)
}
// 随机生成并逐个添加卡片
function suiji() {
const n = relArray.value.length
cardarray.value = []
const randomWeights = Array.from({ length: n }, () => Math.random())
const totalWeight = randomWeights.reduce((a, b) => a + b, 0)
const intervals = randomWeights.map((w) => (w / totalWeight) * 4000)
let cumulative = 0
for (let i = 0; i < n; i++) {
cumulative += intervals[i]
setTimeout(async () => {
cardarray.value.push(relArray.value[i])
// 当卡片总数为 5,7,9,... 时滚动到底部
const len = cardarray.value.length
}, cumulative)
}
}
const rename = (index : number, item : any) => {
renamenummber.value = index;
savevalue.value = item.nuName;
savenuId.value = item.nuId
isdonghua.value = false;
setTimeout(() => {
isdonghua.value = true;
}, 50)
}
/* ------------------ 生命周期 ------------------ */
onMounted(() => {
init()
})
const init = () => {
lastT = nowTime()
rafId = raf(loop)
startBallGeneration()
startCounter()
}
const targetNumber = ref(-1)
const infoNumber = ref(-1)
const movecard = (where : number) => {
if (count.value !== 100) {
return
}
switch (where) {
case 0:
if (infoNumber.value === 0) {
infoNumber.value = 1
return
} else if (infoNumber.value === 1) {
infoNumber.value = 0
return
}
if (targetNumber.value - 2 < -1) {
targetNumber.value = -1
} else {
targetNumber.value = targetNumber.value - 2
ensureVisible(targetNumber.value)
}
break
case 1:
if (infoNumber.value === 0) {
infoNumber.value = 1
return
} else if (infoNumber.value === 1) {
infoNumber.value = 0
return
}
if (targetNumber.value + 1 > cardarray.value.length - 1) {
} else {
targetNumber.value++
ensureVisible(targetNumber.value)
}
break
case 2:
if (infoNumber.value === 0) {
infoNumber.value = 1
return
} else if (infoNumber.value === 1) {
infoNumber.value = 0
return
}
if (targetNumber.value + 1 === cardarray.value.length - 1 || targetNumber.value === -1) {
targetNumber.value++
ensureVisible(targetNumber.value)
}
else if (targetNumber.value + 2 > cardarray.value.length - 1) {
} else {
targetNumber.value = targetNumber.value + 2
ensureVisible(targetNumber.value)
}
break
case 3:
if (infoNumber.value === 0) {
infoNumber.value = 1
return
} else if (infoNumber.value === 1) {
infoNumber.value = 0
return
}
if (targetNumber.value !== -1) {
targetNumber.value--
ensureVisible(targetNumber.value)
}
break
case 4:
if(infoNumber.value ===1){
rename(targetNumber.value,cardarray.value[targetNumber.value])
return
}
if (targetNumber.value === -1) {
cantoggleRun()
} else {
infoNumber.value = 0;
}
break
case 5:
if(infoNumber.value !==-1){
infoNumber.value = -1
}else{
goback()
}
break
}
}
onBeforeUnmount(() => {
if (rafId != null) caf(rafId)
stopBallGeneration()
})
function ensureVisible(index : number) {
const count = cardarray.value.length
const COLS = 2
const ROW_HEIGHT = 245
const SCROLL_HEIGHT = 475 // scroll-view 固定高度
if (index < 0) index = 0
if (index >= count) index = count - 1
nextTick(() => {
const rowIndex = Math.floor(index / COLS)
const rowTop = rowIndex * ROW_HEIGHT
const rowBottom = rowTop + ROW_HEIGHT
const visibleTop = scrollTop.value
const visibleBottom = visibleTop + SCROLL_HEIGHT
let newTop = scrollTop.value
if (rowTop < visibleTop) {
// 目标行在可视区上方 -> 向上滚到该行顶部
newTop = rowTop
} else if (rowBottom > visibleBottom) {
// 目标行在可视区下方 -> 向下滚到让该行显示在底部可见
newTop = rowBottom - SCROLL_HEIGHT
} // 否则已经可见,不变
// 限制范围,避免越界
const totalRows = Math.ceil(count / COLS)
const totalHeight = totalRows * ROW_HEIGHT
const maxTop = Math.max(0, totalHeight - SCROLL_HEIGHT)
if (newTop < 0) newTop = 0
if (newTop > maxTop) newTop = maxTop
scrollTop.value = Math.round(newTop)
})
}
</script>
<style scoped lang="less">
.index-content-other {
width: 100%;
height: 100%;
position: relative;
background-color: #EFF0F4;
display: flex;
justify-content: center;
align-items: center;
}
.index-content-down {
position: absolute;
bottom: 40rpx;
left: 50%;
transform: translateX(-50%);
}
.index-content-right {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
align-items: center;
padding-top: 100rpx;
font-size: 32rpx;
}
.back-img {
width: 30rpx;
height: 30rpx;
margin-left: 100rpx;
margin-right: 20rpx;
}
.all-img {
position: absolute;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
}
.blue-bgc {
width: 600rpx;
height: 600rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
flex-direction: column;
}
.inset-img {
width: 400rpx;
height: 400rpx;
position: relative;
}
.ball-layer {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
transform: translate(-50%, -50%);
z-index: 999;
}
.ball {
position: absolute;
border-radius: 50%;
transition: opacity 0.5s ease;
z-index: 10;
}
/* 进度条样式 */
.progress-wrap {
width: 60%;
height: 13px;
background-color: #E6E9EE;
border-radius: 9999px;
overflow: hidden;
margin-top: 30px;
}
.progress-inner {
height: 100%;
background-image: linear-gradient(90deg, #0097FF 0%, #007CFF 100%);
border-radius: inherit;
transition: width 0.2s ease;
}
.progress-text {
margin-top: 8px;
font-size: 28rpx;
color: #007CFF;
}
.blue-button {
margin-top: 30rpx;
width: 250rpx;
height: 90rpx;
border-radius: 40rpx;
display: flex;
justify-content: center;
align-items: center;
color: #007CFF;
font-size: 30rpx;
background-color: #ddf0ff;
border: 1rpx solid #007CFF;
}
.other {
width: 50%;
height: 100%;
margin-left: 100rpx;
}
.other-father {
margin-top: 200rpx;
height: 950rpx;
width: 100%;
}
.card-father {
width: 100%;
display: flex;
flex-wrap: wrap;
}
.card {
width: 45%;
margin-left: 4%;
height: 450rpx;
box-shadow: 3rpx 6rpx 12rpx 3rpx rgba(206, 206, 206, 0.5);
background-color: #f4f5f7;
border-radius: 30rpx;
margin-top: 5rpx;
margin-bottom: 35rpx;
padding: 0 25rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
overflow: hidden;
}
.card-title {
width: 100%;
height: 130rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
/* 旋转动画 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes spinx {
from {
transform: rotate(-70deg);
}
to {
transform: rotate(290deg);
}
}
/* 新卡片淡入动画 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.4s ease-out forwards;
}
.index-content-title {
position: absolute;
top: 60rpx;
left: 60rpx;
display: flex;
align-items: center;
.shu {
width: 20rpx;
height: 50rpx;
background: linear-gradient(to right, #0052C2, #00B4FF);
border-radius: 20rpx;
margin-right: 30rpx;
}
.shu-font {
color: #415273;
font-size: 35rpx;
}
}
.right-box {
background: rgb(0, 171, 255);
width: 160rpx;
height: 65rpx;
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
.card-bottom {
margin-top: 17rpx;
margin-left: 10rpx;
display: flex;
.bottom-img {
width: 38rpx;
height: 38rpx;
margin-left: 30rpx;
}
}
.left-contain {
margin-right: 80rpx;
}
.main-title {
display: flex;
align-items: center;
margin-top: 70rpx;
margin-bottom: 10rpx;
}
.edit-img {
width: 40rpx;
height: 30rpx;
}
.card-tags {
position: absolute;
top: 50rpx;
left: 0rpx;
// background: linear-gradient(to right,#57B8FF,#0097FF);
width: 200rpx;
height: 65rpx;
font-size: 25rpx;
// color: #fff;
// border-radius: 35rpx;
display: flex;
justify-content: center;
align-items: center;
}
.play-img {
position: absolute;
top: 50rpx;
right: 30rpx;
width: 130rpx;
height: 55rpx;
font-size: 26rpx;
border-radius: 35rpx;
border: 2rpx solid #999;
display: flex;
justify-content: center;
align-items: center;
}
.rename-father {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600rpx;
height: 420rpx;
border-radius: 30rpx;
box-shadow: 2rpx 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.3);
background-color: #fff;
display: flex;
// justify-content: center;
flex-direction: column;
align-items: center;
padding: 0 30rpx;
z-index: 999;
.rename-title {
width: 100%;
height: 80rpx;
// border-bottom: 2rpx solid rgb(245, 245, 245);
display: flex;
justify-content: center;
align-items: center;
// margin: 30rpx 0;
margin-top: 30rpx;
margin-bottom: 20rpx;
font-size: 32rpx;
}
.rename-gray {
width: 100%;
height: 80rpx;
display: flex;
// justify-content: center;
color: rgb(167, 167, 167);
align-items: center;
}
.rename-input {
width: 100%;
height: 80rpx;
display: flex;
background-color: rgb(245, 246, 250);
border-radius: 20rpx;
color: rgb(167, 167, 167);
align-items: center;
padding: 0 20rpx;
position: relative;
.uni-input {
font-size: 25rpx;
width: 100%;
}
.left-img {
width: 50rpx;
height: 50rpx;
margin-right: 15rpx;
}
.right-img {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
width: 30rpx;
height: 30rpx;
}
}
}
.popup-any {
position: fixed;
inset: 0;
z-index: 999;
/* 初始透明度 */
opacity: 0;
/* 播放动画:名称 fadeIn时长 0.5s,缓动函数 ease保持最后状态 */
transition: opacity 0.5s ease;
backdrop-filter: blur(1rpx);
background-color: rgba(236, 237, 241, 0.4);
/* 添加毛玻璃效果 */
z-index: 999;
}
.mask {
position: absolute;
inset: 0;
}
.video-father{
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
margin-left: 15rpx;
}
.zerotarget {
--color: #99C9FD;
--thick: 2px;
--radius: 30rpx;
--outline-offset: 0rpx;
/* 外扩多少 */
/* 内层虚线(你现在用的) */
border-radius: var(--radius);
background-color: white;
/* 内部背景 */
animation: scalePulse 360ms cubic-bezier(.2, .8, .2, 1);
/* 外层虚线:放在 outline不会影响元素尺寸 */
outline: var(--thick) dashed var(--color);
outline-offset: var(--outline-offset);
/* 保证文本 / 子元素在最上层 */
position: relative;
z-index: 0;
}
</style>