第一版差
This commit is contained in:
parent
0b23ae04bb
commit
f615b15783
@ -120,6 +120,13 @@ export const robotApi = {
|
||||
limit: params.limit
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取事件类型下拉框
|
||||
getEventTypes: () => {
|
||||
return service({
|
||||
url: '/api/v1/events/getEtypeNameList',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
403
src/components/AlarmStatistics copy.vue
Normal file
403
src/components/AlarmStatistics copy.vue
Normal file
@ -0,0 +1,403 @@
|
||||
<template>
|
||||
<div class="alarm-statistics">
|
||||
<div class="time-filter">
|
||||
<div
|
||||
v-for="period in periods"
|
||||
:key="period.value"
|
||||
:class="['filter-item', { active: currentPeriod === period.value }]"
|
||||
@click="handleManualSelect(period.value)"
|
||||
>
|
||||
{{ period.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container" ref="chartRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch, computed } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { homeApi } from '../api/index';
|
||||
import emptyList from '../assets/img/empty_list.png';
|
||||
|
||||
const chartRef = ref(null);
|
||||
let chart = null;
|
||||
let autoSwitchTimer = null;
|
||||
let checkInterval = null;
|
||||
const isLoading = ref(false);
|
||||
const isManualMode = ref(false); // 是否处于手动选择模式
|
||||
|
||||
// 告警事件类型映射
|
||||
const eventTypeMap = {
|
||||
'0': '读表告警',
|
||||
'1': '高温感知报警',
|
||||
'2': '吸烟报警',
|
||||
'3': '长时间滞留报警',
|
||||
'4': '空气质量报警',
|
||||
'5': '急停按下',
|
||||
'6': '语音未接通',
|
||||
'7': '日常巡检'
|
||||
};
|
||||
|
||||
// 存储API返回的数据
|
||||
const apiData = ref({
|
||||
'1': [], // 天
|
||||
'7': [], // 周
|
||||
'30': [], // 月
|
||||
});
|
||||
|
||||
// 判断当前时间段是否有数据
|
||||
const hasData = computed(() => {
|
||||
return apiData.value[currentPeriod.value] && apiData.value[currentPeriod.value].length > 0;
|
||||
});
|
||||
|
||||
// 转换API数据为图表格式
|
||||
const transformData = (data) => {
|
||||
const xAxisData = [];
|
||||
const seriesData = [];
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return { xAxis: [], series: [] };
|
||||
}
|
||||
|
||||
// 按事件类型分组并排序
|
||||
data.forEach(item => {
|
||||
const eventTypeName = eventTypeMap[item.eventType] || `未知类型(${item.eventType})`;
|
||||
xAxisData.push(eventTypeName);
|
||||
seriesData.push(item.count);
|
||||
});
|
||||
|
||||
return {
|
||||
xAxis: xAxisData,
|
||||
series: seriesData
|
||||
};
|
||||
};
|
||||
|
||||
const periods = [
|
||||
{ label: "天", value: "1" },
|
||||
{ label: "周", value: "7" },
|
||||
{ label: "月", value: "30" },
|
||||
];
|
||||
|
||||
const currentPeriod = ref("1");
|
||||
|
||||
// 获取指定时间段的告警统计数据
|
||||
const fetchAlarmStatistics = async (day) => {
|
||||
console.log(`获取${day}天的数据`);
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const res = await homeApi.getAlarmStatistics(day);
|
||||
if (res.code === 200) {
|
||||
apiData.value[day] = res.data || [];
|
||||
console.log(`获取${day}天告警统计数据成功:`, res.data);
|
||||
} else {
|
||||
console.error(`获取${day}天告警统计数据失败:`, res);
|
||||
apiData.value[day] = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`获取${day}天告警统计数据错误:`, err);
|
||||
apiData.value[day] = [];
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
|
||||
// 确保当前还是这个时间段才更新图表
|
||||
if (currentPeriod.value === day) {
|
||||
updateChart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 更新图表数据
|
||||
const updateChart = () => {
|
||||
if (!chartRef.value || !chart) return;
|
||||
|
||||
console.log(`更新图表数据,当前时间段: ${currentPeriod.value}`);
|
||||
|
||||
// 获取当前时间段的数据
|
||||
const chartData = transformData(apiData.value[currentPeriod.value]);
|
||||
|
||||
// 基础配置,无论是否有数据都会显示
|
||||
const option = {
|
||||
grid: {
|
||||
top: "10%",
|
||||
left: "5%",
|
||||
right: "4%",
|
||||
bottom: "15%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
// 如果没有数据,使用空数组但仍显示坐标轴
|
||||
data: chartData.xAxis.length > 0 ? chartData.xAxis : ['暂无数据'],
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "rgba(185, 232, 255, 0.1)",
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#B9E8FF",
|
||||
fontSize: 12,
|
||||
interval: 0,
|
||||
rotate: 45,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
name: "数量: 次",
|
||||
nameTextStyle: {
|
||||
color: "#B9E8FF",
|
||||
fontSize: 12,
|
||||
padding: [0, 30, 0, 0],
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "rgba(185, 232, 255, 0.1)",
|
||||
type: "dashed",
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#B9E8FF",
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// 当没有数据时,显示一个空数据点
|
||||
data: chartData.series.length > 0 ? chartData.series : [0],
|
||||
type: "line",
|
||||
smooth: true,
|
||||
symbol: "circle",
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: "#FF8A00",
|
||||
},
|
||||
lineStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#FF8A00",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "rgba(255, 138, 0, 0)",
|
||||
},
|
||||
],
|
||||
},
|
||||
width: 3,
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "rgba(255, 138, 0, 0.3)",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "rgba(255, 138, 0, 0)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#FF8A00',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
console.log('设置图表选项');
|
||||
chart.setOption(option, true); // 使用true参数完全覆盖之前的配置
|
||||
};
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
}
|
||||
|
||||
chart = echarts.init(chartRef.value);
|
||||
updateChart(); // 初始更新图表
|
||||
};
|
||||
|
||||
// 开始自动轮播
|
||||
const startAutoSwitch = () => {
|
||||
stopAutoSwitch(); // 先停止现有的定时器
|
||||
|
||||
autoSwitchTimer = setInterval(async () => {
|
||||
// 如果处于手动模式,不自动切换
|
||||
if (isManualMode.value) return;
|
||||
|
||||
const currentIndex = periods.findIndex(p => p.value === currentPeriod.value);
|
||||
const nextIndex = (currentIndex + 1) % periods.length;
|
||||
const nextPeriod = periods[nextIndex].value;
|
||||
|
||||
console.log(`自动切换到: ${periods[nextIndex].label}(${nextPeriod})`);
|
||||
|
||||
// 先切换时间段
|
||||
currentPeriod.value = nextPeriod;
|
||||
|
||||
// 然后获取数据
|
||||
await fetchAlarmStatistics(nextPeriod);
|
||||
}, 10000); // 每10秒切换一次
|
||||
};
|
||||
|
||||
// 停止自动轮播
|
||||
const stopAutoSwitch = () => {
|
||||
if (autoSwitchTimer) {
|
||||
clearInterval(autoSwitchTimer);
|
||||
autoSwitchTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理手动选择
|
||||
const handleManualSelect = async (period) => {
|
||||
console.log(`手动选择时间段: ${period}`);
|
||||
|
||||
// 进入手动模式
|
||||
isManualMode.value = true;
|
||||
|
||||
// 如果点击的是当前已选择的时间段,不做任何操作
|
||||
if (currentPeriod.value === period) return;
|
||||
|
||||
// 更新当前选中时间段
|
||||
currentPeriod.value = period;
|
||||
|
||||
// 获取该时间段的数据
|
||||
await fetchAlarmStatistics(period);
|
||||
|
||||
// 5秒后恢复自动切换
|
||||
setTimeout(() => {
|
||||
console.log('恢复自动模式');
|
||||
isManualMode.value = false;
|
||||
}, 30000); // 30秒后恢复自动模式
|
||||
};
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
chart && chart.resize();
|
||||
};
|
||||
|
||||
// 监听当前选中的时间段变化
|
||||
watch(currentPeriod, (newPeriod) => {
|
||||
console.log(`当前选中时间段变为: ${newPeriod}`);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('组件挂载');
|
||||
|
||||
// 初始化时获取默认数据(1天)
|
||||
await fetchAlarmStatistics('1');
|
||||
|
||||
// 然后初始化其他时间段数据
|
||||
Promise.all([
|
||||
fetchAlarmStatistics('7'),
|
||||
fetchAlarmStatistics('30')
|
||||
]);
|
||||
|
||||
initChart();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// 启动自动轮播
|
||||
console.log('开始自动轮播');
|
||||
startAutoSwitch();
|
||||
|
||||
// 定期检查轮播是否还在进行
|
||||
checkInterval = setInterval(() => {
|
||||
if (!autoSwitchTimer) {
|
||||
console.log('检测到轮播已停止,重新启动');
|
||||
startAutoSwitch();
|
||||
}
|
||||
}, 20000); // 每20秒检查一次
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理定时器
|
||||
stopAutoSwitch();
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
checkInterval = null;
|
||||
}
|
||||
|
||||
// 销毁图表
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
chart = null;
|
||||
}
|
||||
|
||||
window.removeEventListener("resize", handleResize);
|
||||
console.log('组件卸载,停止自动轮播');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alarm-statistics {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.time-filter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 5px;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
color: #b9e8ff;
|
||||
cursor: pointer;
|
||||
padding: 2px 10px;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-item.active {
|
||||
padding: 2px 10px;
|
||||
background: url("../assets/img/alarm_tri.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -175,33 +175,18 @@ const updateChart = () => {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// 当没有数据时,显示一个空数据点
|
||||
data: chartData.series.length > 0 ? chartData.series : [0],
|
||||
type: "line",
|
||||
smooth: true,
|
||||
smooth: false,
|
||||
symbol: "circle",
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: "#FF8A00",
|
||||
color: "#fff", // 圆点白色
|
||||
borderColor: '#FF8A00', // 橙色描边
|
||||
borderWidth: 1,
|
||||
},
|
||||
lineStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#FF8A00",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "rgba(255, 138, 0, 0)",
|
||||
},
|
||||
],
|
||||
},
|
||||
color: "#FF8A00", // 线条橙色
|
||||
width: 3,
|
||||
},
|
||||
areaStyle: {
|
||||
@ -212,50 +197,18 @@ const updateChart = () => {
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "rgba(255, 138, 0, 0.3)",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "rgba(255, 138, 0, 0)",
|
||||
},
|
||||
{ offset: 0, color: "rgba(255, 138, 0, 0.3)" },
|
||||
{ offset: 1, color: "rgba(255, 138, 0, 0)" },
|
||||
],
|
||||
},
|
||||
},
|
||||
markPoint: chartData.series.length > 0 ? {
|
||||
symbol: "circle",
|
||||
symbolSize: 50,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: "radial",
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
r: 0.5,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "rgba(255, 138, 0, 0.2)",
|
||||
},
|
||||
{
|
||||
offset: 0.8,
|
||||
color: "rgba(255, 138, 0, 0.1)",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "rgba(255, 138, 0, 0)",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
type: "max",
|
||||
name: "最高频率",
|
||||
label: { show: true, color: "#FF8A00", fontSize: 12 },
|
||||
},
|
||||
],
|
||||
} : null,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
color: '#FF8A00',
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 视角指示器 -->
|
||||
<div class="view-indicators" v-if="hasValidViews">
|
||||
<!-- <div class="view-indicators" v-if="hasValidViews">
|
||||
<div
|
||||
v-for="(view, index) in validViews"
|
||||
:key="index"
|
||||
@ -34,7 +34,7 @@
|
||||
:class="{ active: currentViewIndex === validViewsIndices[index] }"
|
||||
@click="setView(validViewsIndices[index])"
|
||||
></div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 视角指示器 -->
|
||||
<div class="view-indicators" v-if="hasValidViews">
|
||||
<!-- <div class="view-indicators" v-if="hasValidViews">
|
||||
<div
|
||||
v-for="(view, index) in validViews"
|
||||
:key="index"
|
||||
@ -32,7 +32,7 @@
|
||||
:class="{ active: currentViewIndex === validViewsIndices[index] }"
|
||||
@click="setView(validViewsIndices[index])"
|
||||
></div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -19,6 +19,24 @@
|
||||
<div class="loading-text">加载中...</div>
|
||||
</div>
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<div class="control-buttons">
|
||||
<img
|
||||
src="../../assets/img/fullscreen-icon.png"
|
||||
alt="全屏"
|
||||
class="control-icon fullscreen-icon"
|
||||
@click="toggleFullscreen"
|
||||
v-if="!isFullscreen"
|
||||
/>
|
||||
<img
|
||||
src="../../assets/img/snapshot-icon.png"
|
||||
alt="退出全屏"
|
||||
class="control-icon snapshot-icon"
|
||||
@click="exitFullscreen"
|
||||
v-if="isFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -50,6 +68,57 @@ const videoRef = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const error = ref('');
|
||||
const pc = ref(null); // WebRTC连接
|
||||
const isFullscreen = ref(false); // 是否全屏
|
||||
|
||||
// 切换全屏模式
|
||||
const toggleFullscreen = () => {
|
||||
const container = videoRef.value?.parentElement || videoRef.value;
|
||||
|
||||
if (!container) return;
|
||||
|
||||
if (!document.fullscreenElement) {
|
||||
// 进入全屏模式
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen().then(() => {
|
||||
isFullscreen.value = true;
|
||||
}).catch(err => {
|
||||
console.error('全屏模式出错:', err);
|
||||
});
|
||||
} else if (container.webkitRequestFullscreen) { // Safari
|
||||
container.webkitRequestFullscreen();
|
||||
isFullscreen.value = true;
|
||||
} else if (container.msRequestFullscreen) { // IE11
|
||||
container.msRequestFullscreen();
|
||||
isFullscreen.value = true;
|
||||
}
|
||||
} else {
|
||||
exitFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
// 退出全屏模式
|
||||
const exitFullscreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen().then(() => {
|
||||
isFullscreen.value = false;
|
||||
}).catch(err => {
|
||||
console.error('退出全屏模式出错:', err);
|
||||
});
|
||||
} else if (document.webkitExitFullscreen) { // Safari
|
||||
document.webkitExitFullscreen();
|
||||
isFullscreen.value = false;
|
||||
} else if (document.msExitFullscreen) { // IE11
|
||||
document.msExitFullscreen();
|
||||
isFullscreen.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听全屏状态变化
|
||||
const handleFullscreenChange = () => {
|
||||
isFullscreen.value = !!document.fullscreenElement ||
|
||||
!!document.webkitFullscreenElement ||
|
||||
!!document.msFullscreenElement;
|
||||
};
|
||||
|
||||
// 初始化WebRTC连接
|
||||
const initWebRTC = async () => {
|
||||
@ -249,80 +318,63 @@ const cleanupWebRTC = () => {
|
||||
error.value = '';
|
||||
};
|
||||
|
||||
// 监听流URL变化
|
||||
// 监听streamUrl变化
|
||||
watch(() => props.streamUrl, (newUrl) => {
|
||||
if (newUrl) {
|
||||
console.log('流URL变化,重新初始化WebRTC:', newUrl);
|
||||
console.log('streamUrl变化,重新初始化WebRTC:', newUrl);
|
||||
initWebRTC();
|
||||
} else {
|
||||
console.log('流URL为空,清理WebRTC连接');
|
||||
console.log('streamUrl为空,清理WebRTC连接');
|
||||
cleanupWebRTC();
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
onMounted(() => {
|
||||
console.log('CustomWebRTCPlayer组件挂载');
|
||||
if (props.streamUrl) {
|
||||
initWebRTC();
|
||||
}
|
||||
});
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
if (props.streamUrl) {
|
||||
console.log('组件挂载,初始化WebRTC:', props.streamUrl);
|
||||
initWebRTC();
|
||||
}
|
||||
|
||||
// 添加全屏变化事件监听
|
||||
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
||||
document.addEventListener('msfullscreenchange', handleFullscreenChange);
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
console.log('CustomWebRTCPlayer组件卸载');
|
||||
console.log('组件卸载,清理WebRTC连接');
|
||||
cleanupWebRTC();
|
||||
|
||||
// 移除全屏变化事件监听
|
||||
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
|
||||
document.removeEventListener('msfullscreenchange', handleFullscreenChange);
|
||||
|
||||
// 确保退出全屏
|
||||
if (isFullscreen.value) {
|
||||
exitFullscreen();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.webrtc-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #033347;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-empty-state {
|
||||
background: #033347;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.empty-icon) {
|
||||
height: 46px !important;
|
||||
}
|
||||
|
||||
:deep(.empty-title) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.empty-subtitle) {
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
background-color: #033347;
|
||||
}
|
||||
|
||||
.player-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-element {
|
||||
@ -337,44 +389,88 @@ onUnmounted(() => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 4px solid #00a8ff;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 3px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
background: rgba(255, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
.loading-text {
|
||||
color: #ffffff;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #ff4d4f;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
max-width: 80%;
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-empty-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 控制按钮样式 */
|
||||
.control-buttons {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.control-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.control-icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@ -472,6 +472,7 @@ onMounted(() => {
|
||||
/* background: rgba(0, 21, 31, 0.5); */
|
||||
background: #033347;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.camera-feed img {
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
|
||||
<div class="center-panel">
|
||||
<div class="thumbnail-container">
|
||||
<div class="thumbnail-container1">
|
||||
<div class="thumbnail-title">主摄像头</div>
|
||||
<div class="camera-feed">
|
||||
<CustomWebRTCPlayer
|
||||
@ -96,9 +96,7 @@
|
||||
class="search-icon arrow-icon"
|
||||
/>
|
||||
<ul v-if="selectOpen" class="alarm-select-dropdown">
|
||||
<li @click.stop="selectType('全部告警')">全部告警</li>
|
||||
<li @click.stop="selectType('事件告警')">事件告警</li>
|
||||
<li @click.stop="selectType('仪表识别')">仪表识别</li>
|
||||
<li v-for="type in eventTypeOptions" :key="type" @click.stop="selectType(type)">{{ type }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -559,12 +557,19 @@ const handleViewAll = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// 使用新API获取告警事件列表,每页10条
|
||||
const res = await robotApi.getAlarmDetailList({
|
||||
// 构建接口参数
|
||||
const params = {
|
||||
number: robotId.value,
|
||||
offset: 0,
|
||||
limit: 10
|
||||
});
|
||||
offset: (currentPage.value - 1) * pageSize.value,
|
||||
limit: pageSize.value
|
||||
};
|
||||
// 如果不是全部告警,添加etypeName参数
|
||||
if (selectedType.value && selectedType.value !== '全部告警') {
|
||||
params.etypeName = selectedType.value;
|
||||
}
|
||||
|
||||
// 使用新API获取告警事件列表
|
||||
const res = await robotApi.getAlarmDetailList(params);
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
console.log('获取告警事件列表成功:', res.data);
|
||||
@ -1055,14 +1060,18 @@ const getAlarmEventList = async () => {
|
||||
|
||||
// 过滤当前Tab下的告警
|
||||
const filteredEventAlarms = computed(() => {
|
||||
let list = eventAlarms.value;
|
||||
if (selectedType.value && selectedType.value !== '全部告警') {
|
||||
// 按类型过滤,匹配etypeName或content字段
|
||||
list = list.filter(a => a.etypeName === selectedType.value || a.content === selectedType.value);
|
||||
}
|
||||
if (alarmTab.value === "pending") {
|
||||
// 未处理标签下显示待处理和超时未处理的告警
|
||||
return eventAlarms.value.filter((a) => a.status === "pending" || a.status === "timeout");
|
||||
return list.filter((a) => a.status === "pending" || a.status === "timeout");
|
||||
}
|
||||
if (alarmTab.value === "done") {
|
||||
return eventAlarms.value.filter((a) => a.status === "done");
|
||||
return list.filter((a) => a.status === "done");
|
||||
}
|
||||
return eventAlarms.value;
|
||||
return list;
|
||||
});
|
||||
|
||||
const filteredMeterAlarms = computed(() => {
|
||||
@ -1172,6 +1181,17 @@ onMounted(async () => {
|
||||
} catch (error) {
|
||||
console.error('初始化告警数据失败:', error);
|
||||
}
|
||||
|
||||
// 获取告警类型
|
||||
try {
|
||||
// 获取告警类型
|
||||
const res = await robotApi.getEventTypes();
|
||||
if (res.code === 200 && Array.isArray(res.data)) {
|
||||
eventTypeOptions.value = ['全部告警', ...res.data];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取告警类型失败', e);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -1194,6 +1214,10 @@ const closeSelect = () => {
|
||||
const selectType = (type) => {
|
||||
selectedType.value = type;
|
||||
selectOpen.value = false;
|
||||
// 如果"查看全部"弹窗已打开,自动刷新
|
||||
if (showViewAllModal.value) {
|
||||
handleViewAll();
|
||||
}
|
||||
};
|
||||
|
||||
// 告警详情弹窗相关
|
||||
@ -1909,6 +1933,24 @@ const handleAlarmEvent = async (alarm) => {
|
||||
// 在RobotDetail.vue中添加eventListData变量,用于存储完整的告警事件列表数据
|
||||
// 在currentEvent和monitorList之后添加
|
||||
const eventListData = ref([]);
|
||||
|
||||
// 告警类型选项
|
||||
const eventTypeOptions = ref(['全部告警']);
|
||||
|
||||
// 页面加载时获取告警类型
|
||||
onMounted(async () => {
|
||||
// ... existing code ...
|
||||
try {
|
||||
// 获取告警类型
|
||||
const res = await robotApi.getEventTypes();
|
||||
if (res.code === 200 && Array.isArray(res.data)) {
|
||||
eventTypeOptions.value = ['全部告警', ...res.data];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取告警类型失败', e);
|
||||
}
|
||||
// ... existing code ...
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -1962,6 +2004,7 @@ const eventListData = ref([]);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
height: 100%; /* 确保占满整个高度 */
|
||||
}
|
||||
|
||||
/* 右侧面板 */
|
||||
@ -2264,7 +2307,14 @@ const eventListData = ref([]);
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.thumbnail-container1{
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
}
|
||||
.camera-title {
|
||||
font-size: 14px;
|
||||
color: #b9e8ff;
|
||||
@ -2275,7 +2325,7 @@ const eventListData = ref([]);
|
||||
}
|
||||
|
||||
.camera-feed {
|
||||
height: 60vh;
|
||||
height: 55vh; /* 从60vh改为70vh */
|
||||
background: rgba(0, 21, 31, 0.3);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(185, 232, 255, 0.3);
|
||||
@ -2295,23 +2345,25 @@ const eventListData = ref([]);
|
||||
right: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.camera-thumbnails {
|
||||
/* height: 150px; */
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 10px;
|
||||
/* 移除固定高度,使用flex布局 */
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
width: 100%;
|
||||
background: rgba(0, 21, 31, 0.3);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden; */
|
||||
position: relative;
|
||||
display: flex; /* 添加flex布局 */
|
||||
flex-direction: column; /* 垂直方向flex */
|
||||
flex: 1; /* 添加flex:1使其自适应高度 */
|
||||
}
|
||||
|
||||
.thumbnail-container.active {
|
||||
@ -2331,9 +2383,11 @@ const eventListData = ref([]);
|
||||
|
||||
.thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 150px;
|
||||
flex: 1; /* 使用flex:1代替固定高度 */
|
||||
|
||||
background: rgba(0, 21, 31, 0.3);
|
||||
display: flex; /* 添加flex布局 */
|
||||
flex-direction: column; /* 垂直方向flex */
|
||||
}
|
||||
|
||||
.thumbnail img {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user