hldy_app/pages/watch/full.vue

779 lines
18 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="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}${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" v-show="!cameratarget.includes(5)" />
</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:
jumpopen.value = false;
uni.navigateBack()
break
case 6:
if (cameratarget.value.includes(index)) {
removeIndexOnce(index)
} else {
cameratarget.value.push(index)
}
break
case 7:
if (cameratarget.value.includes(index)) {
removeIndexOnce(index)
} else {
cameratarget.value.push(index)
}
break
case 8:
if (cameratarget.value.includes(index)) {
uni.$emit('fullmonitor:changeQuality');
removeIndexOnce(index)
} else {
uni.$emit('fullmonitor:changeQuality');
cameratarget.value.push(index)
}
break
case 9:
jumpopen.value = true
break
}
}
const clickjump = (index : number) => {
wheretarget.value = index;
if (index === 3) {
uni.$emit('fullmonitor:flipImage', 6)
removeIndexOnce(9)
} else {
uni.$emit('fullmonitor:flipImage', index)
cameratarget.value.push(9)
}
}
// 对 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: 1800rpx;
height: 1200rpx;
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: 820rpx;
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>