hldy_app_mini/pages/watch/full.vue

780 lines
18 KiB
Vue
Raw Normal View History

2025-11-05 15:59:48 +08:00
<template>
<view class="view">
<view class="view-right">
<view class="big-bgc">
<view
style="width: 100%;height: 100%;z-index: 1;display: flex;justify-content: center;align-items: center;background-color: rgba(226, 227, 231, 0.5);">
<view style="position: relative;">
<image style="width: 600rpx;height: 600rpx;" src="/static/nocamera.png" />
<view
style="color: #4D5561;font-size: 32rpx;position: absolute;left: 50%;bottom: 100rpx;transform: translateX(-50%);">
暂无视频内容显示
</view>
</view>
</view>
</view>
<view class="picture">
<view class="picture-card" v-for="(item,index) in downpicture" :key="index"
:style="{color:downtarget===index?'#1486EB':''}" @click="clickDownCard(index)">
<view class="bgc-card" :style="{background:downtarget===index?'#E2EDFC':''}"
:class="{target: index === bottomTargetIndex}">
<image style="width: 90rpx;height: 90rpx;"
:src="`/static/index/picture/${index}${downtarget===index?1:0}.png`" />
</view>
{{item}}
</view>
</view>
<view class="right-right">
<!-- <view class="red-kuang">
<image style="width: 90rpx;height: 90rpx;margin-left: 40rpx;"
:src="`${uni.getStorageSync('serverUrl')}/sys/common/static/${rightmessage.fzrHeadPath}` " />
<view class="">
<view
style="margin-left: 20rpx;display: flex;align-items: center;margin-bottom: 5rpx;height: 60rpx;">
<view class="font-weight">
{{ rightmessage.fzr }}
</view>
</view>
<view class="font-small" style="margin-left: 20rpx;">
{{ rightmessage.fzrTel }}
</view>
</view>
</view> -->
<view class="bottom-view">
<view v-for="(item,index) in cameraArray" class="button-father" @click="clickcamera(index)">
<view class="bottom-button"
:style="{backgroundColor:cameratarget.includes(index)?`#DDE9F0`:`` }">
<image style="width: 70%;height: 70%"
:src="`/static/index/camera/${index==9?5:(index>4?index+1:index)}${cameratarget.includes(index)?1:0}.png`" />
</view>
<view style="font-size: 25rpx;" :style="{color:cameratarget.includes(index)?`#0E86EA`:`` }">
{{item}}
</view>
</view>
<view class="jump-white" v-show="jumpopen">
<view
style="display: flex;justify-content: space-around;height: 100rpx;width: 100%;align-items: center;">
<view class="">
翻转
</view>
<image style="width: 40rpx;height: 40rpx" src="/static/index/camera/back.png"
@click="jumpopen=false" />
</view>
<view class="jump-item" v-for="(item,index) in where" :key="index" @click="clickjump(index)"
:style="{backgroundColor:wheretarget===index?'#E5E8EE':''}">
<image style="width: 50rpx;height: 50rpx;margin-right: 10rpx;margin-left:-10rpx"
:src="wheretarget===index?item.target:item.url" />
<view :style="{color:wheretarget===index?'#0E86EA':''}">
{{item.name}}
</view>
</view>
</view>
</view>
</view>
</view>
<joysticknew @movecard="movecamera" />
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed, reactive } from 'vue';
import { onShow, onLoad, onHide } from "@dcloudio/uni-app"
import { movedirection, queryPadPageList } from './api/lunpan.js'
import joysticknew from '@/component/public/newgame/joysticknew.vue';
const cameraArray = ref([])
const rightmessage = ref({
fzrHeadPath:"",
fzr:"",
fzrTel:""
});
onLoad(() => {
cameraArray.value = cameraBig;
//子组件加载慢
setTimeout(()=>{
const item = uni.getStorageSync('NUall')
rightmessage.value = item
uni.$emit('fullmonitor:killView');
if (item.cameraInfo !== null && item.cameraInfo[0].deviceName) {
uni.$emit('fullmonitor:isshow', true);
// 加点延迟吧
setTimeout(() => {
uni.$emit('fullmonitor:changeinit', item.cameraInfo[0].deviceIndex);
}, 100)
} else {
uni.$emit('fullmonitor:isshow', false)
}
},500)
})
const menuIndex = ref(-1);
const typeNow = ref(-1);
const photoplay = ref(false);
const getblue = ref(false);
const gobackdrawer = ref(null);
const wheelRef = ref(null);
const topnum = ref(0); // scroll-top (px)
const itemHeight = 100; // 每项高度 (px)
const containerHeight = 400; // scroll-view 高度 (px)
const wheretarget = ref(3);
const where = [
{
name: "左右翻转",
url: "/static/index/camera/800.png",
target: "/static/index/camera/801.png",
},
{
name: "上下翻转",
url: "/static/index/camera/810.png",
target: "/static/index/camera/811.png",
},
{
name: "中心翻转",
url: "/static/index/camera/820.png",
target: "/static/index/camera/821.png",
},
{
name: "不翻转",
url: "/static/index/camera/830.png",
target: "/static/index/camera/831.png",
},
]
const downpicture = [
"原图",
"全景拉伸",
"四分屏",
"180°全景",
"360°全景"
]
const cameratarget = ref([]);
const cameraBig = [
"截图",
"录制",
"对讲",
"静音",
"预警",
"图片",
"视频",
"清晰度",
"翻转",
"返回",
]
const cameraSmall = [
"截图",
"录制",
"对讲",
"静音",
"预警",
"展开",
]
const downtarget = ref(0)
const clickDownCard = (index : number) => {
bottomTargetIndex.value = index;
downtarget.value = index;
switch (index) {
case 0:
uni.$emit('fullmonitor:switchDisplay', 0)
break
case 1:
uni.$emit('fullmonitor:switchDisplay', 4)
break
case 2:
uni.$emit('fullmonitor:switchDisplay', 1)
break
case 3:
uni.$emit('fullmonitor:switchDisplay', 2)
break
case 4:
uni.$emit('fullmonitor:switchDisplay', 3)
break
}
}
const first = ref(5);
const second = ref(0);
function parseToMinutes(t : string) : number {
const [h, m] = t.split(':').map(Number)
return h * 60 + m
}
function findClosestItem(list : any[], nowStr : string) {
const now = parseToMinutes(nowStr)
let closest = null
let minDiff = Infinity
list.forEach((element) => {
const val = parseToMinutes(element.startTime)
// 计算差值,取 24 小时范围内的最小绝对差
let diff = Math.abs(val - now)
if (diff > 720) diff = 1440 - diff // 跨天时比如23:50和00:10
if (diff < minDiff) {
minDiff = diff
closest = element
}
})
return closest
}
const serveritem = ref({
startTime: "",
endTime: "",
typeName: "",
immediateFile: ""
})
function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
// 通用的生成函数
function genPaths(base, prefix, count, ext = 'png', startIndex = 0, pad = false) {
return Array.from({ length: count }, (_, i) => {
const idx = pad
? String(i + startIndex).padStart(2, '0')
: i + startIndex
return `${base}/${prefix}${idx}.${ext}`
})
}
onMounted(() => {
// 触发监听
menuIndex.value = 0;
typeNow.value = 0;
photoplay.value = true;
getblue.value = true;
})
const leftTargetIndex = ref(0);
const topTargetIndex = ref(-1);
const bottomTargetIndex = ref(-1)
const shezhi = ref(false)
const movecamera = (type : number) => {
handleKey(type)
}
const savetypeNow = ref(0);
const removeTimers = new Map<number, ReturnType<typeof setTimeout>>();
onBeforeUnmount(() => {
for (const t of removeTimers.values()) {
clearTimeout(t)
}
removeTimers.clear()
})
const removeIndexOnce = (index : number) => {
const pos = cameratarget.value.indexOf(index)
if (pos !== -1) {
cameratarget.value.splice(pos, 1) // 从 pos 开始删除 1 个元素
}
}
const jumpopen = ref(false);
const clickcamera = (index : number) => {
switch (index) {
case 0:
// 触发快照事件
uni.$emit('fullmonitor:doSnapshot')
// 如果数组里不存在就 push避免重复
if (!cameratarget.value.includes(index)) {
cameratarget.value.push(index)
}
// 如果之前有定时器,先清掉(实现“重新计时”)
if (removeTimers.has(index)) {
clearTimeout(removeTimers.get(index)!)
}
// 新建一个 1s 后移除的定时器
const timerId = setTimeout(() => {
const pos = cameratarget.value.indexOf(index)
if (pos !== -1) {
cameratarget.value.splice(pos, 1)
}
removeTimers.delete(index)
}, 500)
removeTimers.set(index, timerId)
break
case 1:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:stopRecord');
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:startRecord');
cameratarget.value.push(index)
}
break
case 2:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:stopTalk');
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:openTalk');
cameratarget.value.push(index)
}
break
case 3:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:toggleVolume');
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:toggleVolume');
cameratarget.value.push(index)
}
break
case 4:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:stopAlarm')
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:startAlarm')
cameratarget.value.push(index)
}
break
case 5:
if (cameratarget.value.includes(index)) {
removeIndexOnce(index)
} else {
cameratarget.value.push(index)
}
break
case 6:
if (cameratarget.value.includes(index)) {
removeIndexOnce(index)
} else {
cameratarget.value.push(index)
}
break
case 7:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:changeQuality');
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:changeQuality');
cameratarget.value.push(index)
}
break
case 8:
jumpopen.value = true
break
case 9:
jumpopen.value = false;
uni.navigateBack()
break
}
}
const clickjump = (index : number) => {
wheretarget.value = index;
if (index === 3) {
uni.$emit('fullmonitor:flipImage', 6)
removeIndexOnce(8)
} else {
uni.$emit('fullmonitor:flipImage', index)
cameratarget.value.push(8)
}
}
// 对 moveFirstUp / moveFirstDown 包装防抖
const moveUpDebounced = useThrottle(() => wheelRef.value?.moveFirstUp(), 400)
const moveDownDebounced = useThrottle(() => wheelRef.value?.moveFirstDown(), 400)
const moveUpsecond = useThrottle(() => wheelRef.value?.moveSecondUp(), 400)
const moveDownsecond = useThrottle(() => wheelRef.value?.moveSecondDown(), 400)
const clickDownsecond = useThrottle(() => doSomething(), 700)
const gaoqing = ref(0);
const yuntai = ref(false);
const savefirst = ref(-1);
function doSomething() {
wheelRef.value?.startchange()
if (first.value === 0) {
if (second.value) {
uni.$emit('fullmonitor:toggleVolume');
} else {
uni.$emit('fullmonitor:toggleVolume');
}
}
if (first.value === 1) {
if (second.value) {
uni.$emit('fullmonitor:stopTalk');
} else {
uni.$emit('fullmonitor:openTalk');
}
}
if (first.value === 2) {
uni.$emit('fullmonitor:doSnapshot');
}
if (first.value === 3) {
if (second.value) {
uni.$emit('fullmonitor:stopRecord');
} else {
uni.$emit('fullmonitor:startRecord');
}
}
if (first.value === 4) {
if (!second.value) {
savefirst.value = first.value
first.value = -1;
yuntai.value = true;
} else {
}
}
if (first.value === 5) {
if (gaoqing.value !== second.value) {
gaoqing.value = second.value
uni.$emit('fullmonitor:changeQuality'); // 发起截图请求,不关心回调
}
}
if (first.value === 6) {
uni.$emit('fullmonitor:switchDisplay', second.value)
}
if (first.value === 7) {
if (second.value === 3) {
uni.$emit('fullmonitor:flipImage', 6)
} else {
uni.$emit('fullmonitor:flipImage', second.value)
}
}
if (first.value === 8) {
if (second.value) {
uni.$emit('fullmonitor:stopAlarm')
} else {
uni.$emit('fullmonitor:startAlarm')
}
}
}
function useThrottle(fn : () => void, delay = 1000) {
let throttling = false
return () => {
if (throttling) return
fn()
throttling = true
setTimeout(() => {
throttling = false
}, delay)
}
}
const pad = (n) => String(n).padStart(2, '0')
// 当前活跃的方向0,1,2,3
// -1 表示没有活跃方向
let activeDir = -1
// 定时器 mapkey = 方向
const stopTimers : Record<number, any> = {}
// 核心逻辑
function handleKey(type : number) {
switch (type) {
case 0: runDirection(1, 0); break // 上
case 1: runDirection(5, 1); break // 右
case 2: runDirection(7, 2); break // 下
case 3: runDirection(3, 3); break // 左
case 4: // 确定
first.value = savefirst.value
yuntai.value = false
savefirst.value = -1
moveUpsecond()
clickDownsecond()
break
case 5: // 取消
first.value = savefirst.value
yuntai.value = false
savefirst.value = -1
moveUpsecond()
clickDownsecond()
break
}
}
// 管理方向的开始/停止
function runDirection(dirCode : number, type : number) {
// 如果换方向,先停掉旧的
if (activeDir !== -1 && activeDir !== type) {
stopDirection(activeDir)
}
activeDir = type
// 执行开始
movedirection(dirCode, 1)
// 重置 stop 计时器
if (stopTimers[type]) clearTimeout(stopTimers[type])
stopTimers[type] = setTimeout(() => {
stopDirection(type)
}, 550) // 如果 550ms 内没有新指令 → 自动停止
}
// 停止动作
function stopDirection(type : number) {
if (type === -1) return
let dirCode = 0
switch (type) {
case 0: dirCode = 1; break
case 1: dirCode = 5; break
case 2: dirCode = 7; break
case 3: dirCode = 3; break
}
movedirection(dirCode, 0).then((res : any) => { })
clearTimeout(stopTimers[type])
stopTimers[type] = null
if (activeDir === type) activeDir = -1
}
// 简单的宽松解析函数
function parseDateFlexible(s) {
if (s instanceof Date) return s
if (typeof s !== 'string') throw new Error('birth must be string or Date')
// 支持 "YYYY-MM-DD" 或 "YYYY年MM月DD日" 或 "YYYY/MM/DD"
const m = s.match(/(\d{4})\D+(\d{1,2})\D+(\d{1,2})/)
if (!m) throw new Error('无法解析的日期格式')
return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
}
</script>
<style lang="less" scoped>
.view {
background-color: #eff0f4;
width: 100%;
height: 100vh;
display: flex;
.view-right {
height: 100%;
width: 100%;
position: relative;
padding: 60rpx;
.scroll-vi {
height: 100rpx;
width: 100%;
margin-left: 0rpx;
margin-top: 80rpx;
position: relative;
display: flex;
.menu {
flex: 0 0 auto; // 👈 关键点
height: 90rpx;
width: 240rpx;
border-radius: 50rpx;
margin-left: 20rpx;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
padding-top: 9rpx;
.menu-img {
width: 55rpx;
height: 55rpx;
margin-right: 15rpx;
}
.menu-font {
margin-top: 18rpx;
font-size: 25rpx;
}
}
}
}
}
.big-bgc {
margin-left: -15rpx;
margin-top: 20rpx;
width: 1600rpx;
2025-11-19 16:52:02 +08:00
height: 1000rpx;
2025-11-05 15:59:48 +08:00
border-radius: 55rpx;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
@keyframes glowFlash {
0%,
100% {
box-shadow:
0 0 4rpx #f1d7da,
0 0 8rpx #f1d7da,
0 0 12rpx #f1d7da;
}
50% {
box-shadow:
0 0 10rpx #f1d7da,
0 0 20rpx #f1d7da,
0 0 30rpx #f1d7da;
}
}
.right-right {
position: absolute;
right: -20rpx;
top: 80rpx;
height: calc(100% - 250rpx);
width: 570rpx;
.red-kuang {
// margin-top: 0rpx;
margin-left: 90rpx;
width: 430rpx;
height: 120rpx;
border-radius: 30rpx;
position: relative;
display: flex;
align-items: center;
background-color: rgba(226, 227, 231, 0.5);
.blue-bgc {
width: 120rpx;
height: 50rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
font-size: 23rpx;
background-color: rgba(248, 249, 250, 0.5);
color: #017DE9;
border-radius: 20rpx;
// margin-left: 30rpx;
}
.font-weight {
font-size: 30rpx;
font-weight: 600;
}
.font-small {
font-size: 22rpx;
}
}
}
.bottom-view {
margin-left: 90rpx;
margin-top: 20rpx;
width: 440rpx;
height: 800rpx;
border-radius: 50rpx;
background-color: rgba(226, 227, 231, 0.5);
position: relative;
display: flex;
flex-wrap: wrap;
overflow: hidden;
align-items: flex-start;
align-content: flex-start;
.button-father {
margin-top: 20rpx;
margin-bottom: 0;
margin-left: 28rpx;
text-align: center;
.bottom-button {
background-color: #F2F2F4;
display: flex;
justify-content: center;
align-items: center;
width: 110rpx;
height: 110rpx;
border: 1rpx solid #CDD3DD;
border-radius: 35rpx;
margin-bottom: 5rpx;
}
}
}
.target {
--color: #99C9FD;
--thick: 2px;
--radius: 60rpx;
--outline-offset: 0rpx;
/* 内层虚线(你现在用的) */
border-radius: var(--radius);
background-color: #ddf0ff;
/* 内部背景 */
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: 1;
}
.picture {
display: flex;
margin-top: 40rpx;
.picture-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-left: 20rpx;
margin-right: 10rpx;
width: 200rpx;
.bgc-card {
width: 100%;
height: 130rpx;
background-color: rgba(226, 227, 231, 0.5);
border-radius: 30rpx;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10rpx;
}
}
}
.jump-white {
position: absolute;
bottom: 20rpx;
left: 50rpx;
width: 300rpx;
height: 400rpx;
background-color: #fff;
border-radius: 30rpx;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.1)
}
.jump-item {
margin: 10rpx 10rpx;
height: 60rpx;
justify-content: center;
width: 93%;
display: flex;
border-radius: 20rpx;
align-items: center;
}
</style>