水温接口模拟,视频监控添加
This commit is contained in:
parent
b11a2ddea6
commit
c960b57e0b
@ -3,7 +3,7 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV='development'
|
||||
|
||||
VITE_APP_TITLE = '水电水利建设项目全过程环 境管理信息平台'
|
||||
VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
||||
VITE_APP_PORT = 3000
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
# 本地环境
|
||||
@ -12,14 +12,12 @@ VITE_APP_BASE_API = '/dev-api'
|
||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||
# VITE_APP_BASE_URL = 'http://172.16.21.142:8096'
|
||||
# 汤伟
|
||||
VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||
# VITE_APP_BASE_URL = 'http://10.84.121.21:8093'
|
||||
VITE_APP_BASE_URL = 'http://192.168.1.162:8093'
|
||||
|
||||
# 测试环境线上
|
||||
VITE_APP_TEST_ONLINE_URL = 'https://211.99.26.225:12122'
|
||||
|
||||
# 线上服务名称
|
||||
VITE_APP_LYGK_SERVER = '/api/dec-lygk-base-server'
|
||||
|
||||
|
||||
|
||||
|
||||
@ -27,3 +25,4 @@ VITE_APP_LYGK_SERVER = '/api/dec-lygk-base-server'
|
||||
VITE_APP_BASE_API_URL = 'http://10.84.121.21:8093'
|
||||
## 开发环境预览 图片视频地址
|
||||
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
||||
# ?menu=systemManageMenu&page=disposeManage
|
||||
4
frontend/.lingma/settings.local.json
Normal file
4
frontend/.lingma/settings.local.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"permissions": {},
|
||||
"outputStyle": "Vibe"
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
// src/api/config.ts
|
||||
// 获取环境变量的辅助函数,兼容 Vite 和 Webpack
|
||||
const getEnvVar = (key: string) => {
|
||||
// Vite 使用 import.meta.env
|
||||
if (import.meta.env) {
|
||||
return import.meta.env[key];
|
||||
}
|
||||
};
|
||||
|
||||
export const SERVICE_URLS = {
|
||||
// ABC 服务基础地址
|
||||
lygk: getEnvVar('VITE_APP_LYGK_SERVER'),
|
||||
|
||||
// XYZ 服务基础地址
|
||||
eng: getEnvVar('VITE_APP_eng_SERVER'),
|
||||
|
||||
// 默认服务(如果有)
|
||||
DEFAULT: ''
|
||||
};
|
||||
8
frontend/src/api/sw/index.ts
Normal file
8
frontend/src/api/sw/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request';
|
||||
export function getKendoListCust(data: any) {
|
||||
return request({
|
||||
url: '/sw/alongList/qgc/GetKendoListCust',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="baselayer-switcher" :style="{ right: drawerOpen ? '480px' : '12px' }">
|
||||
<div
|
||||
class="baselayer-switcher"
|
||||
:style="{ right: drawerOpen ? '480px' : '12px' }"
|
||||
v-if="uiStore.mapType == '2D'"
|
||||
>
|
||||
<div
|
||||
class="switcher-item"
|
||||
v-for="item in layers"
|
||||
@ -11,7 +15,7 @@
|
||||
<div class="label">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="nineSectionsImg">
|
||||
<img :src="nineSectionsImg[activeKey]" alt="" />
|
||||
<img :src="nineSectionsImg[activeKey]" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -29,7 +33,7 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
});
|
||||
const uiStore = useUiStore();
|
||||
const drawerOpen = ref(uiStore.drawerOpen);
|
||||
|
||||
@ -45,13 +49,17 @@ const layers = [
|
||||
{ key: "BASEMAP-white", name: "地形", img: dixingImg },
|
||||
{ key: "BASEMAP-img", name: "影像", img: yingxiangImg },
|
||||
];
|
||||
const nineSectionsImg:any = {'s_province_boundaries':nineSectionsShiliangImg,'BASEMAP-white':nineSectionsDixingImg,'BASEMAP-img':nineSectionsYingxiangImg,}
|
||||
const nineSectionsImg: any = {
|
||||
s_province_boundaries: nineSectionsShiliangImg,
|
||||
"BASEMAP-white": nineSectionsDixingImg,
|
||||
"BASEMAP-img": nineSectionsYingxiangImg,
|
||||
};
|
||||
|
||||
const activeKey = ref(layers[0].key);
|
||||
const handleSwitch = (key: string) => {
|
||||
activeKey.value = key;
|
||||
props.map.baseLayerSwitcher(key);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
mapList="{mapList}"
|
||||
loading="{loading}"
|
||||
pointData="{pointData}" -->
|
||||
<!-- 地图图例 -->
|
||||
<MapLegend :setLegendDataMap="updateLegendDataMap" />
|
||||
<!-- 地图筛选器 -->
|
||||
<MapFilter />
|
||||
|
||||
@ -40,7 +40,6 @@ watch(
|
||||
);
|
||||
|
||||
const isFullScreen = ref(false);
|
||||
const mapType = ref("2D");
|
||||
|
||||
// 响应式的控制器配置
|
||||
const controllers: any = computed(() => [
|
||||
@ -78,7 +77,7 @@ const controllers: any = computed(() => [
|
||||
{
|
||||
name: "3D",
|
||||
key: "dim",
|
||||
icon: mapType.value === "2D" ? "a-3D" : "a-2D",
|
||||
icon: uiStore.mapType === "2D" ? "a-3D" : "a-2D",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -91,7 +90,7 @@ const controllers: any = computed(() => [
|
||||
},
|
||||
],
|
||||
},
|
||||
mapType.value === "2D"
|
||||
uiStore.mapType === "2D"
|
||||
? {
|
||||
children: [
|
||||
{
|
||||
@ -102,7 +101,7 @@ const controllers: any = computed(() => [
|
||||
],
|
||||
}
|
||||
: {},
|
||||
mapType.value === "2D"
|
||||
uiStore.mapType === "2D"
|
||||
? {
|
||||
children: [
|
||||
{
|
||||
@ -113,7 +112,7 @@ const controllers: any = computed(() => [
|
||||
],
|
||||
}
|
||||
: {},
|
||||
mapType.value === "3D"
|
||||
uiStore.mapType === "3D"
|
||||
? {
|
||||
children: [
|
||||
{
|
||||
@ -124,7 +123,7 @@ const controllers: any = computed(() => [
|
||||
],
|
||||
}
|
||||
: {},
|
||||
mapType.value === "3D"
|
||||
uiStore.mapType === "3D"
|
||||
? {
|
||||
children: [
|
||||
{
|
||||
@ -175,13 +174,13 @@ const handleControllerClick = (item: any) => {
|
||||
map.zoomToggle("out");
|
||||
break;
|
||||
case "dim":
|
||||
mapType.value = mapType.value === "2D" ? "3D" : "2D";
|
||||
if (mapType.value === "2D") {
|
||||
uiStore.mapType = uiStore.mapType === "2D" ? "3D" : "2D";
|
||||
if (uiStore.mapType === "2D") {
|
||||
uiStore.drawerOpen = true;
|
||||
} else {
|
||||
uiStore.drawerOpen = false;
|
||||
}
|
||||
props.onClick("dim", mapType.value);
|
||||
props.onClick("dim", uiStore.mapType);
|
||||
break;
|
||||
case "rightDrawer":
|
||||
// 使用全局事件切换右侧面板
|
||||
|
||||
@ -12,7 +12,6 @@ import Antd from 'ant-design-vue';
|
||||
import 'ant-design-vue/dist/reset.css'; // Ant Design 全局样式重置
|
||||
import dayjs from 'dayjs'; // ant 中文语言
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
||||
// @ts-ignore
|
||||
import 'virtual:svg-icons-register';
|
||||
// 3d地图
|
||||
|
||||
@ -1,106 +1,138 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="水温监测工作开展情况">
|
||||
<div class="facility-grid" >
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||
<div class="facility-icon">
|
||||
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||
</div>
|
||||
</div>
|
||||
<SidePanelItem title="水温监测工作开展情况">
|
||||
<div class="facility-grid">
|
||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||
<div
|
||||
style="
|
||||
width: 60px;
|
||||
height: 62px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<div class="facility-icon">
|
||||
<i
|
||||
style="color: #fff"
|
||||
:class="facility.icon"
|
||||
type="icon-shengtailiuliang2"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count }}</span><span>个</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
<div class="facility-info">
|
||||
<div class="facility-name">{{ facility.name }}</div>
|
||||
<div style="font-size: 16px">
|
||||
<span class="facility-count">{{ facility.count }}</span
|
||||
><span>个</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { ref, onMounted } from "vue";
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
import { getKendoListCust } from "@/api/sw";
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'shuiwenjiancegongzuokaizhangqingkuang'
|
||||
name: "shuiwenjiancegongzuokaizhangqingkuang",
|
||||
});
|
||||
|
||||
// 设施数据
|
||||
const facilities = ref([
|
||||
{
|
||||
name: '表层水温',
|
||||
count: 145,
|
||||
icon: 'icon iconfont icon-shuizhijiancezhan'
|
||||
},
|
||||
{
|
||||
name: '垂向水温',
|
||||
count: 24,
|
||||
icon: 'icon iconfont icon-diwenshuijianhuan'
|
||||
},
|
||||
{
|
||||
name: "表层水温",
|
||||
count: 145,
|
||||
icon: "icon iconfont icon-shuizhijiancezhan",
|
||||
},
|
||||
{
|
||||
name: "垂向水温",
|
||||
count: 24,
|
||||
icon: "icon iconfont icon-diwenshuijianhuan",
|
||||
},
|
||||
]);
|
||||
const init = async () => {
|
||||
const params = {
|
||||
filter: {
|
||||
logic: "and",
|
||||
filters: [
|
||||
{ field: "rvcd", operator: "eq", dataType: "string", value: "SJLY1U" },
|
||||
{ field: "tm", operator: "gte", dataType: "date", value: "2026-04-17 00:00:00" },
|
||||
{ field: "tm", operator: "lte", dataType: "date", value: "2026-05-17 23:00:00" },
|
||||
],
|
||||
},
|
||||
sort: [{ field: "sort", dir: "asc" }],
|
||||
};
|
||||
await getKendoListCust(params);
|
||||
};
|
||||
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => {
|
||||
|
||||
// init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.facility-grid {
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||
width: 406px;
|
||||
flex-flow: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial,
|
||||
Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
|
||||
Noto Color Emoji;
|
||||
}
|
||||
|
||||
.facility-card {
|
||||
width: 200px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
width: 200px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0px;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.facility-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: #2f6b98;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
// margin-right: 8px;
|
||||
background: #2f6b98;
|
||||
border-radius: 50%;
|
||||
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.facility-info {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.facility-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
// margin-bottom: 4px;
|
||||
// font-weight: 500;
|
||||
}
|
||||
|
||||
.facility-count {
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #2f6b98;
|
||||
// font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,69 +1,59 @@
|
||||
<!-- SidePanelItem.vue -->
|
||||
<template>
|
||||
<SidePanelItem title="沿程水温变化" :prompt="prompts" :select="select" :datetimePicker="datetimePicker">
|
||||
<SidePanelItem
|
||||
title="沿程水温变化"
|
||||
:prompt="prompts"
|
||||
:select="select"
|
||||
:datetimePicker="datetimePicker"
|
||||
>
|
||||
<div ref="chartRef" class="chart-container"></div>
|
||||
</SidePanelItem>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { ECharts } from 'echarts';
|
||||
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import type { ECharts } from "echarts";
|
||||
import SidePanelItem from "@/components/SidePanelItem/index.vue";
|
||||
import { getKendoListCust } from "@/api/sw";
|
||||
|
||||
// 定义组件名(便于调试和递归)
|
||||
defineOptions({
|
||||
name: 'yanchengshuiwenChangeMod'
|
||||
name: "yanchengshuiwenChangeMod",
|
||||
});
|
||||
// 选择器配置
|
||||
const select = ref({
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined
|
||||
show: true,
|
||||
value: undefined,
|
||||
options: [],
|
||||
picker: undefined,
|
||||
format: undefined,
|
||||
});
|
||||
|
||||
// 日期选择器配置
|
||||
const datetimePicker = ref({
|
||||
show: true,
|
||||
value: '2026-05-15 15',
|
||||
format: 'YYYY-MM-DD HH',
|
||||
picker: 'date' as const,
|
||||
options: []
|
||||
show: true,
|
||||
value: "2026-05-15 15",
|
||||
format: "YYYY-MM-DD HH",
|
||||
picker: "date" as const,
|
||||
options: [],
|
||||
});
|
||||
const prompts = ref({
|
||||
show: true,
|
||||
value: '注:最新数据时间为2026-04-08 23',
|
||||
value: "注:最新数据时间为2026-04-08 23",
|
||||
});
|
||||
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
let chartInstance: ECharts | null = null;
|
||||
|
||||
// 静态数据 - 站点名称
|
||||
const stationNames = ref([
|
||||
'班多',
|
||||
'龙羊峡',
|
||||
'拉西瓦',
|
||||
'李家峡',
|
||||
'公伯峡',
|
||||
'苏只',
|
||||
'积石峡'
|
||||
]);
|
||||
const stationNames = ref([]);
|
||||
|
||||
// 静态数据 - 水温值 (°C),班多无数据设为 null
|
||||
const waterTemperatures = ref([
|
||||
null,
|
||||
5.7,
|
||||
5.7,
|
||||
6.5,
|
||||
7.2,
|
||||
7.8,
|
||||
8.4
|
||||
]);
|
||||
const waterTemperatures = ref([]);
|
||||
|
||||
// 静态时间数据
|
||||
const currentTime = '2026-04-08 23';
|
||||
const currentTime = "2026-04-08 23";
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
@ -73,17 +63,17 @@ const initChart = () => {
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '水温(°C)',
|
||||
text: "水温(°C)",
|
||||
left: 5,
|
||||
top: 0,
|
||||
textStyle: {
|
||||
color: '#000000',
|
||||
color: "#000000",
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
}
|
||||
fontWeight: "normal",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
trigger: "axis",
|
||||
formatter: (params: any) => {
|
||||
if (params && params.length > 0) {
|
||||
const data = params[0];
|
||||
@ -92,31 +82,31 @@ const initChart = () => {
|
||||
return `${currentTime}<br/>${data.name}:${data.value}°C`;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
left: "3%",
|
||||
right: "4%",
|
||||
bottom: "3%",
|
||||
top: "15%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
type: "category",
|
||||
data: stationNames.value,
|
||||
boundaryGap: true,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#333',
|
||||
color: "#333",
|
||||
fontSize: 12,
|
||||
margin: 8,
|
||||
interval: 0,
|
||||
@ -125,79 +115,79 @@ const initChart = () => {
|
||||
// 奇数索引(1, 3, 5)的标签在下方
|
||||
// 通过添加空行实现上下错位
|
||||
if (index % 2 === 0) {
|
||||
return `${value}\n `; // 上方标签:文字 + 换行 + 空格占位
|
||||
return `${value}\n `; // 上方标签:文字 + 换行 + 空格占位
|
||||
} else {
|
||||
return ` \n${value}`; // 下方标签:空格占位 + 换行 + 文字
|
||||
return ` \n${value}`; // 下方标签:空格占位 + 换行 + 文字
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
color: "#e0e0e0",
|
||||
type: "solid",
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
type: "value",
|
||||
min: 0,
|
||||
max: 10,
|
||||
// max: 10,
|
||||
interval: 2,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
length: 3,
|
||||
lineStyle: {
|
||||
color: '#8f8f8f'
|
||||
}
|
||||
color: "#8f8f8f",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#333',
|
||||
fontSize: 12
|
||||
color: "#333",
|
||||
fontSize: 12,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#e0e0e0',
|
||||
type: 'solid'
|
||||
}
|
||||
}
|
||||
color: "#e0e0e0",
|
||||
type: "solid",
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '水温',
|
||||
type: 'line',
|
||||
name: "水温",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
color: '#6ca4f7',
|
||||
width: 2
|
||||
color: "#6ca4f7",
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#6ca4f7'
|
||||
color: "#6ca4f7",
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(108, 164, 247, 0.3)'
|
||||
color: "rgba(108, 164, 247, 0.3)",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(108, 164, 247, 0.05)'
|
||||
}
|
||||
])
|
||||
color: "rgba(108, 164, 247, 0.05)",
|
||||
},
|
||||
]),
|
||||
},
|
||||
data: waterTemperatures.value
|
||||
}
|
||||
]
|
||||
data: waterTemperatures.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
chartInstance.setOption(option);
|
||||
@ -208,23 +198,65 @@ const handleResize = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保 DOM 渲染完成
|
||||
const init = async () => {
|
||||
const params = {
|
||||
filter: {
|
||||
logic: "and",
|
||||
filters: [
|
||||
{ field: "rvcd", operator: "eq", dataType: "string", value: "SJLY1U" },
|
||||
{ field: "tm", operator: "gte", dataType: "date", value: "2026-04-17 00:00:00" },
|
||||
{ field: "tm", operator: "lte", dataType: "date", value: "2026-05-17 23:00:00" },
|
||||
],
|
||||
},
|
||||
sort: [{ field: "sort", dir: "asc" }],
|
||||
};
|
||||
let res = await getKendoListCust(params);
|
||||
stationNames.value = res.data.data
|
||||
.filter((item: any) => item.sttp == "1")
|
||||
.map((item: any) => item.stnm);
|
||||
console.log(stationNames.value);
|
||||
|
||||
waterTemperatures.value = res.data.data
|
||||
.filter((item: any) => item.sttp == "2")
|
||||
.map((item: any) => item.temperature);
|
||||
// waterTemperatures.value = res.data.data.map((item: any) => item.temperature);
|
||||
console.log(waterTemperatures.value);
|
||||
|
||||
// 数据获取完成后,初始化或更新图表
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
if (!chartInstance) {
|
||||
initChart();
|
||||
} else {
|
||||
// 如果图表已存在,只更新数据
|
||||
chartInstance.setOption({
|
||||
xAxis: {
|
||||
data: stationNames.value,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: waterTemperatures.value,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
// 强制重绘,确保尺寸正确
|
||||
setTimeout(() => {
|
||||
chartInstance?.resize();
|
||||
}, 100);
|
||||
}, 50);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
// 页面加载时执行的逻辑
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保 DOM 渲染完成
|
||||
init();
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
// 组件卸载前清理
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
chartInstance?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -4,6 +4,7 @@ import { ref } from 'vue';
|
||||
export const useUiStore = defineStore('ui', () => {
|
||||
// 右侧抽屉状态
|
||||
const drawerOpen = ref(true);
|
||||
const mapType = ref('2D');
|
||||
|
||||
// 切换抽屉状态
|
||||
const toggleDrawer = () => {
|
||||
@ -19,5 +20,6 @@ export const useUiStore = defineStore('ui', () => {
|
||||
drawerOpen,
|
||||
toggleDrawer,
|
||||
setDrawerOpen,
|
||||
mapType
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,503 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>视频监控</h2>
|
||||
<div class="shiPinJianKongZhuanTi-page">
|
||||
<a-tabs
|
||||
v-model:activeKey="activeTab"
|
||||
type="card"
|
||||
:tabBarGutter="8"
|
||||
class="custom-tabs"
|
||||
>
|
||||
<template #tabBarExtraContent>
|
||||
<a-button type="primary" size="small" @click="handleAction">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加监控点
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-tab-pane v-for="tab in tabs" :key="tab.key" :tab="tab.title" />
|
||||
</a-tabs>
|
||||
|
||||
<div class="tabs-content">
|
||||
<!-- 顶部标题和图标选择区域 -->
|
||||
<div class="content-header">
|
||||
<div class="header-left">
|
||||
<span class="title">监控列表</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<a-radio-group v-model:value="viewLayout" button-style="solid">
|
||||
<a-radio-button value="1">
|
||||
<AppstoreOutlined />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="4">
|
||||
<LayoutOutlined />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="6">
|
||||
<BorderOutlined />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="9">
|
||||
<GridOutlined />
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体内容区域 -->
|
||||
<div class="content-body">
|
||||
<!-- 左侧监控列表 -->
|
||||
<div class="left-panel">
|
||||
<!-- Tab切换:实时视频/录像 -->
|
||||
<a-tabs v-model:activeKey="monitorType" size="small" class="monitor-tabs">
|
||||
<a-tab-pane key="live" tab="实时视频" />
|
||||
<a-tab-pane key="record" tab="录像" />
|
||||
</a-tabs>
|
||||
|
||||
<!-- 查询和状态筛选 -->
|
||||
<div class="search-bar">
|
||||
<a-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索监控点"
|
||||
size="small"
|
||||
style="flex: 1"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-button-group size="small" class="status-filter">
|
||||
<a-button
|
||||
:type="statusFilter === 'online' ? 'primary' : 'default'"
|
||||
@click="statusFilter = 'online'"
|
||||
>
|
||||
在线
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="statusFilter === 'offline' ? 'primary' : 'default'"
|
||||
@click="statusFilter = 'offline'"
|
||||
>
|
||||
离线
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
|
||||
<!-- 树形结构 -->
|
||||
<div class="tree-container">
|
||||
<a-tree
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="{ title: 'title', key: 'key', children: 'children' }"
|
||||
block-node
|
||||
show-icon
|
||||
>
|
||||
<template #switcherIcon="{ switcherCls }">
|
||||
<DownOutlined :class="switcherCls" />
|
||||
</template>
|
||||
<template #title="{ title }">
|
||||
<span>{{ title }}</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间视频内容区域 -->
|
||||
<div class="center-panel">
|
||||
<div class="video-grid" :class="`layout-${viewLayout}`">
|
||||
<div v-for="index in parseInt(viewLayout)" :key="index" class="video-item">
|
||||
<div class="video-placeholder">
|
||||
<VideoCameraOutlined class="video-icon" />
|
||||
<span class="video-label">摄像头 {{ index }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧回放面板(仅录像模式显示) -->
|
||||
<div v-if="monitorType === 'record'" class="right-panel">
|
||||
<div class="replay-header">
|
||||
<span class="replay-title">回放</span>
|
||||
</div>
|
||||
|
||||
<!-- 时间选择 -->
|
||||
<div class="date-picker">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 录像列表 -->
|
||||
<div class="record-list">
|
||||
<a-list :data-source="recordList" :loading="recordLoading" size="small">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item class="record-item">
|
||||
<div class="record-info">
|
||||
<div class="record-name">{{ item.name }}</div>
|
||||
<div class="record-time">{{ item.time }}</div>
|
||||
</div>
|
||||
<a-button size="small" type="link">
|
||||
<PlayCircleOutlined />
|
||||
</a-button>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue";
|
||||
// import {
|
||||
// PlusOutlined,
|
||||
// AppstoreOutlined,
|
||||
// LayoutOutlined,
|
||||
// BorderOutlined,
|
||||
// GridOutlined,
|
||||
// DownOutlined,
|
||||
// VideoCameraOutlined,
|
||||
// PlayCircleOutlined
|
||||
// } from '@ant-design/icons-vue'
|
||||
import type { Dayjs } from "dayjs";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
// Tab相关
|
||||
const tabs = ref([
|
||||
{ key: "1", title: "实时监控" },
|
||||
{ key: "2", title: "录像回放" },
|
||||
{ key: "3", title: "告警记录" },
|
||||
]);
|
||||
const activeTab = ref("1");
|
||||
|
||||
// 视图布局选择(1/4/6/9宫格)
|
||||
const viewLayout = ref("1");
|
||||
|
||||
// 监控类型(实时视频/录像)
|
||||
const monitorType = ref("live");
|
||||
|
||||
// 搜索文本
|
||||
const searchText = ref("");
|
||||
|
||||
// 状态筛选(在线/离线)
|
||||
const statusFilter = ref<"online" | "offline">("online");
|
||||
|
||||
// 树形数据
|
||||
const expandedKeys = ref<string[]>(["0-0", "0-1"]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
const treeData = [
|
||||
{
|
||||
title: "区域一",
|
||||
key: "0-0",
|
||||
children: [
|
||||
{ title: "摄像头01", key: "0-0-0", isLeaf: true },
|
||||
{ title: "摄像头02", key: "0-0-1", isLeaf: true },
|
||||
{ title: "摄像头03", key: "0-0-2", isLeaf: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "区域二",
|
||||
key: "0-1",
|
||||
children: [
|
||||
{ title: "摄像头04", key: "0-1-0", isLeaf: true },
|
||||
{ title: "摄像头05", key: "0-1-1", isLeaf: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "区域三",
|
||||
key: "0-2",
|
||||
children: [
|
||||
{ title: "摄像头06", key: "0-2-0", isLeaf: true },
|
||||
{ title: "摄像头07", key: "0-2-1", isLeaf: true },
|
||||
{ title: "摄像头08", key: "0-2-2", isLeaf: true },
|
||||
{ title: "摄像头09", key: "0-2-3", isLeaf: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 日期范围(默认前7天)
|
||||
const dateRange = ref<[Dayjs, Dayjs]>([dayjs().subtract(7, "day"), dayjs()]);
|
||||
|
||||
// 录像列表
|
||||
const recordLoading = ref(false);
|
||||
const recordList = ref([
|
||||
{ name: "录像文件01", time: "2026-05-17 10:00:00 - 10:30:00" },
|
||||
{ name: "录像文件02", time: "2026-05-17 09:00:00 - 09:30:00" },
|
||||
{ name: "录像文件03", time: "2026-05-16 14:00:00 - 14:30:00" },
|
||||
{ name: "录像文件04", time: "2026-05-16 10:00:00 - 10:30:00" },
|
||||
{ name: "录像文件05", time: "2026-05-15 16:00:00 - 16:30:00" },
|
||||
{ name: "录像文件06", time: "2026-05-15 10:00:00 - 10:30:00" },
|
||||
{ name: "录像文件07", time: "2026-05-14 11:00:00 - 11:30:00" },
|
||||
{ name: "录像文件08", time: "2026-05-13 15:00:00 - 15:30:00" },
|
||||
{ name: "录像文件09", time: "2026-05-12 09:00:00 - 09:30:00" },
|
||||
{ name: "录像文件10", time: "2026-05-11 14:00:00 - 14:30:00" },
|
||||
]);
|
||||
|
||||
// 处理按钮点击
|
||||
const handleAction = () => {
|
||||
console.log("添加监控点");
|
||||
};
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = (value: string) => {
|
||||
console.log("搜索:", value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.shiPinJianKongZhuanTi-page {
|
||||
position: relative;
|
||||
z-index: 900;
|
||||
pointer-events: all;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.custom-tabs {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
|
||||
&::before {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab) {
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-tab-active) {
|
||||
background-color: #fff;
|
||||
border-color: #d9d9d9;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
flex: 1;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 顶部标题和图标选择区域
|
||||
.content-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
|
||||
.header-left {
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
:deep(.ant-radio-group) {
|
||||
.ant-radio-button-wrapper {
|
||||
padding: 4px 12px;
|
||||
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主体内容区域
|
||||
.content-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 左侧面板
|
||||
.left-panel {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
.monitor-tabs {
|
||||
padding: 8px 8px 0;
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.status-filter {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
|
||||
:deep(.ant-tree) {
|
||||
.ant-tree-node-content-wrapper {
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-node-selected {
|
||||
.ant-tree-node-content-wrapper {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 中间视频区域
|
||||
.center-panel {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.video-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
|
||||
&.layout-1 {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
&.layout-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&.layout-6 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&.layout-9 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.video-item {
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
min-height: 150px;
|
||||
|
||||
.video-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
|
||||
.video-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.video-label {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧回放面板
|
||||
.right-panel {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
.replay-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
|
||||
.replay-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
|
||||
.record-item {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.record-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.record-name {
|
||||
font-size: 13px;
|
||||
color: #262626;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 11px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user