hldy_app/pages/watch/settings/leida.vue

907 lines
20 KiB
Vue
Raw 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.

<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>