Browse Source

功能调整

gr 4 weeks ago
parent
commit
9fa5e4e1f7

+ 47 - 0
src/assets/styles/loading2.scss

@@ -0,0 +1,47 @@
+/* HTML: <div class="loader"></div> */
+.loader-spin {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 99;
+	background: rgba(255, 255, 255, 0.2);
+	// background: rgba(0, 0, 0, 0.25);
+	display: flex;
+	justify-content: center;
+	align-items: center;
+
+	/* HTML: <div class="loader"></div> */
+	.loader {
+		width: 50px;
+		aspect-ratio: 1;
+		display: grid;
+		border: 4px solid #0000;
+		border-radius: 50%;
+		border-color: #ccc #0000;
+		animation: l16 1.6s infinite linear;
+	}
+	.loader::before,
+	.loader::after {
+		content: '';
+		grid-area: 1/1;
+		margin: 2px;
+		border: inherit;
+		border-radius: 50%;
+	}
+	.loader::before {
+		border-color: #f03355 #0000;
+		animation: inherit;
+		animation-duration: 0.8s;
+		animation-direction: reverse;
+	}
+	.loader::after {
+		margin: 8px;
+	}
+	@keyframes l16 {
+		100% {
+			transform: rotate(1turn);
+		}
+	}
+}

+ 106 - 0
src/components/ScrollTable.vue

@@ -0,0 +1,106 @@
+<template>
+	<table class="table-default mb-2">
+		<thead>
+			<slot name="headRow"></slot>
+		</thead>
+		<tbody class="tbody-scroll" ref="listRef">
+			<slot name="bodyRow" :rows="tableData_render"></slot>
+		</tbody>
+	</table>
+</template>
+
+<script setup>
+import { nextTick, watch, ref } from 'vue'
+
+const props = defineProps({
+	tableData: {
+		type: Array,
+		required: true,
+	},
+	// 显示的行数,超出滚动
+	rowNum: {
+		type: Number,
+		default: 3,
+	},
+	// 滚动的帧间隔。滚动频率 = 浏览器刷新率 / (interval + 1)
+	interval: {
+		type: Number,
+		default: 1,
+	},
+	// 每次滚动的像素数
+	speed: {
+		type: Number,
+		default: 1,
+	},
+})
+
+const listRef = ref(null)
+
+const tableData_render = ref([])
+
+watch(
+	() => props.tableData,
+	(val) => {
+		tableData_render.value = JSON.parse(JSON.stringify(val))
+		nextTick(() => {
+			initScroll()
+		})
+	},
+	{ immediate: true, deep: true }
+)
+
+let animationId = null
+
+function initScroll() {
+	const ele = listRef.value
+	ele.style.maxHeight = 50 * props.rowNum + 'px'
+	let stopFlag = false
+	if (animationId !== null) {
+		stopFlag = true
+		ele.scrollTo(0, 0)
+	}
+	if (tableData_render.value.length <= props.rowNum) {
+		window.cancelAnimationFrame(animationId)
+		ele.onmouseenter = null
+		ele.onmouseleave = null
+		return
+	}
+	let itemH = ele.scrollHeight / Math.ceil(tableData_render.value.length)
+	let intervalLast = props.interval
+	const scroll = (isResume = false) => {
+		if (ele.scrollTop >= itemH) {
+			ele.scrollTo(0, 0)
+			tableData_render.value.push(tableData_render.value.shift())
+		}
+		if (intervalLast > 0) {
+			intervalLast--
+		} else {
+			ele.scrollTo(0, ele.scrollTop + props.speed)
+			intervalLast = props.interval
+		}
+		if (stopFlag || isResume) {
+			window.cancelAnimationFrame(animationId)
+			stopFlag = false
+		}
+		animationId = window.requestAnimationFrame(scroll)
+	}
+	scroll()
+	ele.onmouseenter = () => {
+		window.cancelAnimationFrame(animationId)
+	}
+	ele.onmouseleave = () => {
+		scroll(true)
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.tbody-scroll {
+	overflow-y: auto;
+
+	&::-webkit-scrollbar {
+		width: 0;
+		height: 0;
+	}
+}
+</style>

+ 28 - 35
src/views/home/Home.vue

@@ -42,21 +42,17 @@
 			</ul>
 
 			<div class="title-main rtl">实时飞行动态</div>
-			<table class="table-default mb-2">
-				<thead>
+			<ScrollTable :table-data="panelData.flightActivity">
+				<template #headRow>
 					<tr>
 						<th>无人机编号</th>
 						<th>飞行单位/人</th>
 						<th>航线名称</th>
 						<th>飞行状态</th>
 					</tr>
-				</thead>
-				<TransitionGroup name="table-insert" tag="tbody" class="tbody-row3">
-					<tr
-						v-for="item in panelData.flightActivity"
-						:key="item.uavCode"
-						@click="CheckUav(item)"
-						class="cursor-pointer">
+				</template>
+				<template #bodyRow="{ rows }">
+					<tr v-for="item in rows" :key="item.uavCode" @click="CheckUav(item)" class="cursor-pointer">
 						<td>{{ item.uavCode }}</td>
 						<td>{{ item.unit }}</td>
 						<td>{{ item.routeName }}</td>
@@ -68,25 +64,21 @@
 							>
 						</td>
 					</tr>
-				</TransitionGroup>
-			</table>
+				</template>
+			</ScrollTable>
 
-			<div class="title-main rtl" @dblclick="testAddFlightWarning">飞行告警信息</div>
-			<table class="table-default mb-2">
-				<thead>
+			<div class="title-main rtl">飞行告警信息</div>
+			<ScrollTable :tableData="panelData.flightWarning">
+				<template #headRow>
 					<tr>
 						<th>时间</th>
 						<th>无人机编号</th>
 						<th>飞行单位/人</th>
 						<th>告警类型</th>
 					</tr>
-				</thead>
-				<TransitionGroup name="table-insert" tag="tbody" class="tbody-row3">
-					<tr
-						v-for="item in panelData.flightWarning"
-						:key="item.uavCode"
-						@click="CheckUav(item)"
-						class="cursor-pointer">
+				</template>
+				<template #bodyRow="{ rows }">
+					<tr v-for="item in rows" :key="item.uavCode" @click="CheckUav(item)" class="cursor-pointer">
 						<td>{{ item.time }}</td>
 						<td>{{ item.uavCode }}</td>
 						<td>{{ item.unit }}</td>
@@ -94,8 +86,8 @@
 							<span class="tag-text red">{{ item.type }}</span>
 						</td>
 					</tr>
-				</TransitionGroup>
-			</table>
+				</template>
+			</ScrollTable>
 
 			<div class="title-main rtl">各类预警类型占比次数</div>
 			<div class="b-wgzb">
@@ -148,7 +140,7 @@
 
 		<!-- gis 态势监视-选项面板 -->
 		<Transition name="emerge-right">
-			<FloatPanelTsjsGis v-if="layoutStore.floatPanels.tsjs_gis" />
+			<FloatPanelTsjsGis v-if="layoutStore.floatPanels.tsjs_gis && layoutStore.sceneType === 'gis'" />
 		</Transition>
 
 		<!-- ue 态势监视 -->
@@ -179,6 +171,7 @@ import FloatPanelCube from './cpns/FloatPanelCube.vue'
 import FloatPanelTsjs from './cpns/FloatPanelTsjs.vue'
 import NumberScroll from '@/components/NumberScroll.vue'
 import FloatPanelTsjsGis from './cpns/FloatPanelTsjsGis.vue'
+import ScrollTable from '@/components/ScrollTable.vue'
 
 const layoutStore = useLayoutStore()
 
@@ -191,27 +184,27 @@ const panelData = reactive({
 		{ uavCode: 'BM11344738556', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '飞行中' },
 		{ uavCode: 'BM11344744657', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '已降落' },
 		{ uavCode: 'BM11342293853', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '计划终止' },
+		{ uavCode: 'BM11344738559', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '飞行中' },
+		{ uavCode: 'BM11344738563', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '飞行中' },
+		{ uavCode: 'BM11344738561', unit: '美团无人机', routeName: '合生汇-黄兴公园', status: '已降落' },
 	],
 	flightWarning: [
-		{ time: '11:20:09', uavCode: 'BM11344738556', unit: '美团无人机', type: '无人机碰撞' },
-		{ time: '11:03:24', uavCode: 'BM11399847563', unit: '美团无人机', type: '无人机碰撞' },
-		{ time: '10:36:02', uavCode: 'BM11302123870', unit: '美团无人机', type: '无人机碰撞' },
+		{ time: '11:20:09', uavCode: 'BM11344738556', unit: '美团无人机1', type: '无人机碰撞' },
+		{ time: '11:08:23', uavCode: 'BM11344738559', unit: '美团无人机4', type: '电量低' },
+		{ time: '10:52:45', uavCode: 'BM11344738561', unit: '美团无人机6', type: '偏离航线' },
+		{ time: '10:49:24', uavCode: 'BM11344738557', unit: '美团无人机2', type: '无人机碰撞' },
+		{ time: '10:36:02', uavCode: 'BM11344738558', unit: '美团无人机3', type: '电量低' },
+		{ time: '10:11:45', uavCode: 'BM11344738562', unit: '美团无人机7', type: '天气预警' },
+		{ time: '10:08:08', uavCode: 'BM11344738560', unit: '美团无人机5', type: '电量低' },
 	],
 	violationSummary: [
 		{ label: '无人机碰撞', count: 2 },
 		{ label: '电量低', count: 3 },
 		{ label: '偏离航线', count: 1 },
-		{ label: '天气预警', count: 10 },
+		{ label: '天气预警', count: 1 },
 	],
 })
 
-function testAddFlightWarning() {
-	// panelData.flightWarning.pop()
-	// panelData.flightWarning.unshift({
-	//   time: '11:24:05', uavCode: new Date().getTime(), unit: '美团无人机', type: '无人机碰撞'
-	// })
-}
-
 function CheckUav(item) {
 	layoutStore.toggleFloatPanel('uav', true)
 }

+ 123 - 120
src/views/home/cpns/FloatPanelCube.vue

@@ -1,152 +1,155 @@
 <template>
-  <Transition name="fade">
-    <div class="f-panel-left panel-cube" :class="{ 'collapse': layoutStore.leftCollapse }">
-      <i @click="handleClose" class="absolute right-4 top-4 text-white size-6 cursor-pointer hover:scale-110">
-        <img src="@/assets/images/svg/close.svg" alt="">
-      </i>
-
-      <h2 class="c-title">{{ mapStore.gridCode || '-' }}</h2>
-
-      <div class="flex flex-1">
-        <div id="c-chart"></div>
-
-        <ul class="c-list ml-4 flex-1 flex flex-wrap justify-between content-between">
-          <li v-for="(item, index) in list" class=" flex w-1/2">
-            <img :src="getAssetsFile(`page/icon-cube-${index + 1}.png`)" alt="">
-            <div class="ml-2 pl-2 pt-1 flex-1 flex flex-col justify-evenly">
-              <span>{{ item.label }}</span>
-              <div>
-                <span v-if="index === 2" :title="item.value.join(',')">{{ item.value.length === 0 ? '-' : item.value.length > 1 ?
-                  item.value[0] + '...' : item.value[0] }}</span>
-                <span v-else>{{ item.value || '-' }}</span>
-                <span v-if="item.value" class="ml-1">{{ item.unit }}</span>
-              </div>
-            </div>
-          </li>
-        </ul>
-      </div>
-    </div>
-  </Transition>
+	<Transition name="fade">
+		<div class="f-panel-left panel-cube" :class="{ collapse: layoutStore.leftCollapse }">
+			<i @click="handleClose" class="absolute right-4 top-4 text-white size-6 cursor-pointer hover:scale-110">
+				<img src="@/assets/images/svg/close.svg" alt="" />
+			</i>
+
+			<h2 class="c-title">{{ mapStore.gridCode || '-' }}</h2>
+
+			<div class="flex flex-1">
+				<div id="c-chart"></div>
+
+				<ul class="c-list ml-4 flex-1 flex flex-wrap justify-between content-between">
+					<li v-for="(item, index) in list" class="flex w-1/2">
+						<img :src="getAssetsFile(`page/icon-cube-${index + 1}.png`)" alt="" />
+						<div class="ml-2 pl-2 pt-1 flex-1 flex flex-col justify-evenly">
+							<span>{{ item.label }}</span>
+							<div>
+								<span v-if="index === 2" :title="item.value.join(',')">{{
+									item.value.length === 0 ? '-' : item.value.length > 1 ? item.value[0] + '...' : item.value[0]
+								}}</span>
+								<span v-else>{{ item.value || '-' }}</span>
+								<span v-if="item.value" class="ml-1">{{ item.unit }}</span>
+							</div>
+						</div>
+					</li>
+				</ul>
+			</div>
+		</div>
+	</Transition>
 </template>
 
 <script setup>
-import { setAqxs } from '@/echarts/options';
-import { queryGridByCode } from "@/service/map.js";
-import useLayoutStore from '@/store/layout';
-import { useMapStore } from "@/store/map.js";
-import { getAssetsFile } from '@/utils/require';
-import { onMounted, ref, watch } from 'vue';
-
-const mapStore = useMapStore();
+import { setAqxs } from '@/echarts/options'
+import { queryGridByCode } from '@/service/map.js'
+import useLayoutStore from '@/store/layout'
+import { useMapStore } from '@/store/map.js'
+import { getAssetsFile } from '@/utils/require'
+import { onMounted, ref, watch } from 'vue'
+
+const mapStore = useMapStore()
 const layoutStore = useLayoutStore()
 
 const list = ref([
-  { label: '高度', value: '', unit: '米' },
-  { label: '是否占用', value: '', unit: '' },
-  { label: '占用类型', value: [], unit: '' },
-  { label: '人口', value: '', unit: '人' },
-  { label: '河流上方', value: '', unit: '' },
-  { label: '道路上方', value: '', unit: '' },
+	{ label: '真高', value: '', unit: '米' },
+	{ label: '是否占用', value: '', unit: '' },
+	{ label: '占用类型', value: [], unit: '' },
+	{ label: '人口', value: '', unit: '人' },
+	{ label: '河流上方', value: '', unit: '' },
+	{ label: '道路上方', value: '', unit: '' },
 ])
 
 function handleClose() {
-  layoutStore.toggleFloatPanel('cube', false)
+	layoutStore.toggleFloatPanel('cube', false)
 }
 
 function getGridData() {
-  queryGridByCode({
-    code: mapStore.gridCode
-  }).then(res => {
-    if (res.data.code == "200") {
-      const { data } = res.data
-      const { element } = data
-      setAqxs(document.getElementById('c-chart'), data.weightValue || 0)
-      list.value[0].value = data.z.toFixed(2)
-      list.value[4].value = element.river ? '是' : '否'
-      list.value[5].value = element.road ? '是' : '否'
-      list.value[3].value = element.demographics
-
-      const occupyTypes = {
-        clearZone: '净空区',
-        clearZoneBuffer: '净空区缓冲区',
-        noFlyZone: '禁飞区',
-        noFlyZoneBuffer: '禁飞区缓冲区',
-        collision: '建筑物',
-        collisionBuffer: '建筑物缓冲区',
-        expand1: '起降场',
-        expand2: '航线',
-      }
-
-      list.value[1].value = '否'
-      let occupys = []
-      for (const [k, v] of Object.entries(occupyTypes)) {
-        if (element[k]) {
-          list.value[1].value = '是'
-          occupys.push(v)
-        }
-      }
-      list.value[2].value = occupys
-
-    } else {
-      console.log("网格信息查询失败")
-    }
-  })
+	queryGridByCode({
+		code: mapStore.gridCode,
+	}).then((res) => {
+		if (res.data.code == '200') {
+			const { data } = res.data
+			const { element } = data
+			setAqxs(document.getElementById('c-chart'), data.weightValue || 0)
+			list.value[0].value = data.z.toFixed(2)
+			list.value[4].value = element.river ? '是' : '否'
+			list.value[5].value = element.road ? '是' : '否'
+			list.value[3].value = element.demographics
+
+			const occupyTypes = {
+				clearZone: '净空区',
+				clearZoneBuffer: '净空区缓冲区',
+				noFlyZone: '禁飞区',
+				noFlyZoneBuffer: '禁飞区缓冲区',
+				collision: '建筑物',
+				collisionBuffer: '建筑物缓冲区',
+				expand1: '起降场',
+				expand2: '航线',
+			}
+
+			list.value[1].value = '否'
+			let occupys = []
+			for (const [k, v] of Object.entries(occupyTypes)) {
+				if (element[k]) {
+					list.value[1].value = '是'
+					occupys.push(v)
+				}
+			}
+			list.value[2].value = occupys
+		} else {
+			console.log('网格信息查询失败')
+		}
+	})
 }
 
 onMounted(() => {
-  getGridData()
+	getGridData()
 })
 
-watch(() => mapStore.gridCode, (val) => {
-  getGridData()
-}, {
-  deep: true
-})
+watch(
+	() => mapStore.gridCode,
+	(val) => {
+		getGridData()
+	},
+	{
+		deep: true,
+	}
+)
 </script>
 
 <style lang="scss" scoped>
 .panel-cube {
-  width: 624px;
-  height: 307px;
-  padding: 20px 20px 35px;
-  display: flex;
-  flex-direction: column;
-  background: url('../../../assets/images/page/bg-dialog-wide.png');
-  background-size: 100% 100% !important;
+	width: 624px;
+	height: 307px;
+	padding: 20px 20px 35px;
+	display: flex;
+	flex-direction: column;
+	background: url('../../../assets/images/page/bg-dialog-wide.png');
+	background-size: 100% 100% !important;
 }
 
 .c-title {
-  width: 90%;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  margin-bottom: 5px;
-  font-size: 26px;
-  background: linear-gradient(186deg, rgba(128, 155, 237, 1) 0%, rgba(255, 255, 255, 1) 100%);
-  -webkit-background-clip: text;
-  -webkit-text-fill-color: transparent;
+	width: 90%;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	margin-bottom: 5px;
+	font-size: 26px;
+	background: linear-gradient(186deg, rgba(128, 155, 237, 1) 0%, rgba(255, 255, 255, 1) 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
 }
 
 #c-chart {
-  width: 210px;
-  height: 100%;
+	width: 210px;
+	height: 100%;
 }
 
 .c-list {
-  li {
-    height: 57px;
-
-    &>div {
-      background: url('../../../assets/images/page/bg-des.png');
-      background-size: 100% 100%;
-
-      &>span {
-        background: linear-gradient(0deg, #7F9FFF 30%, #FFFFFF 100%);
-        -webkit-background-clip: text;
-        -webkit-text-fill-color: transparent;
-      }
-
-    }
-  }
+	li {
+		height: 57px;
+
+		& > div {
+			background: url('../../../assets/images/page/bg-des.png');
+			background-size: 100% 100%;
+
+			& > span {
+				background: linear-gradient(0deg, #7f9fff 30%, #ffffff 100%);
+				-webkit-background-clip: text;
+				-webkit-text-fill-color: transparent;
+			}
+		}
+	}
 }
-</style>
+</style>

+ 4 - 0
src/views/home/cpns/FloatPanelKysg.vue

@@ -184,6 +184,10 @@ watch(
 			form.value.meshLevel = val
 			const maxHeight = Number(DenseData.find((d) => d.level == val).des.slice(1, -2))
 			form.value.meshHeights = [0, Math.min(600, maxHeight)]
+			const toolsTips = document.getElementsByClassName('range-tooltip')
+			for (let i = 0; i < toolsTips.length; i++) {
+				toolsTips[i].style.visibility = 'visible'
+			}
 		}
 	}
 )

+ 10 - 6
src/views/home/cpns/FloatPanelTsjs.vue

@@ -2,6 +2,7 @@
 	<div class="panel-tsjs">
 		<ul class="type-list">
 			<li v-for="item in listData" @click="handleDemo(item)">
+				<div class="loader-spin" v-if="currentDemo === item.value"><div class="loader"></div></div>
 				<img :src="getAssetsFile(`page/alert-bg-${item.pic}.png`)" alt="" />
 				<span>{{ item.label }}</span>
 			</li>
@@ -65,10 +66,10 @@ const listData = ref([
 	{ label: '电量预警', value: 'Electricity', pic: 'dl' },
 ])
 
-const currentDemo = ref({})
+const currentDemo = ref('')
 
 async function handleDemo(item) {
-	currentDemo.value = item
+	currentDemo.value = item.value
 	layoutStore.sceneLoading = true
 	ueStore.toggleGlobalUav(false)
 	uavInfoShow.value = false
@@ -184,11 +185,11 @@ function handleCloseInfo() {
 	uavInfoShow.value = false
 	initSceneDemo()
 	panelStore.setWeather('Clear Skies')
-	if (currentDemo.value.value === 'BreakInto') {
+	if (currentDemo.value === 'BreakInto') {
 		renderBreakIntoArea(false)
 	}
 	showSingleLine.value = false
-	currentDemo.value = false
+	currentDemo.value = ''
 }
 
 let currentLineId
@@ -287,11 +288,11 @@ onBeforeMount(() => {
 			}
 			case 'FlightOver': {
 				if (currentDemo.value) {
-					if (currentDemo.value.value === 'BreakInto') {
+					if (currentDemo.value === 'BreakInto') {
 						renderBreakIntoArea(false)
 					}
 					handleCloseInfo()
-					currentDemo.value = null
+					currentDemo.value = ''
 				}
 				break
 			}
@@ -308,6 +309,8 @@ onBeforeUnmount(() => {
 </script>
 
 <style lang="scss" scoped>
+@use '../../../assets/styles/loading2.scss';
+
 .panel-tsjs {
 	position: absolute;
 	left: var(--panel-gap);
@@ -346,6 +349,7 @@ onBeforeUnmount(() => {
 		& > span {
 			font-size: 22px;
 			line-height: 22px;
+			color: #e5e5e5;
 		}
 	}
 }

+ 1 - 1
src/views/home/cpns/PanelSjwg.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="panel-sjwg flex flex-col aside-left-inner">
-		<div class="title-main shrink-0">数据网格可视化</div>
+		<div class="title-main shrink-0">低空数字地图</div>
 
 		<div class="title-sub my-4 shrink-0">
 			底板数据

+ 5 - 4
src/views/layout/cpns/Header.vue

@@ -9,7 +9,7 @@
 		</div>
 		<div class="h-title">
 			<img src="@/assets/images/layout/logo.png" alt="" @click="handleReset" />
-			<h1 class="title-text">低空飞行综合监管服务系统</h1>
+			<h1 class="title-text">上海市低空飞行综合监管服务平台</h1>
 		</div>
 		<div class="h-right flex justify-end items-center shrink-0">
 			<img
@@ -136,11 +136,12 @@ function handleReset() {
 		}
 
 		.title-text {
-			padding-bottom: 12px;
+			margin-right: 10px;
+			padding-bottom: 6px;
 			font-family: ShuHei;
-			font-size: 30px;
+			font-size: 26px;
 			line-height: 30px;
-			letter-spacing: 2px;
+			letter-spacing: 1px;
 			background-image: linear-gradient(0deg, #cedeff 0%, #ffffff 100%);
 			-webkit-background-clip: text;
 			-webkit-text-fill-color: transparent;