feat: 空天地大屏

This commit is contained in:
nightdays 2025-11-17 17:56:57 +08:00
parent 972e35f278
commit e57b9b4865
16 changed files with 951 additions and 0 deletions

View File

@ -0,0 +1,45 @@
@use '@/styles/mixins.scss' as *;
.air-sky-land {
width: 100%;
height: 100%;
background-color: rgb(17, 38, 61);
}
.map-section {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.left-section {
position: absolute;
left: vw(45);
top: vw(25);
bottom: vw(25);
display: flex;
flex-direction: column;
justify-content: space-between;
}
// 遥感监控区
.remote-monitor-section {
color: #fff;
}
// 无人机监控区
.aircraft-monitor-section {
color: #fff;
}
// 常规监控区
.common-monitor-section {
position: absolute;
right: vw(45);
top: 25px;
color: #fff;
}

View File

@ -0,0 +1,38 @@
<template>
<div class="air-sky-land">
<!-- 地图 -->
<div class="map-section">
<MapCenter />
</div>
<div class="left-section">
<div class="remote-monitor-section">
<SectionTitle title="遥感监控(空)" />
<!-- 遥感监控 -->
<RemoteMonitor />
</div>
<div class="aircraft-monitor-section">
<SectionTitle title="无人机检测(天)" />
<!-- 无人机检测 -->
<AircraftMonitor />
</div>
</div>
<div class="common-monitor-section">
<SectionTitle title="常规检测(地)" />
<!-- 常规检测 -->
<CommonMonitor />
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import SectionTitle from './components/SectionTitle/SectionTitle.vue';
import RemoteMonitor from './components/RemoteMonitor/RemoteMonitor.vue';
import CommonMonitor from './components/CommonMonitor/CommonMonitor.vue';
import AircraftMonitor from './components/AircraftMonitor/AircraftMonitor.vue';
import MapCenter from './components/MapCenter/MapCenter.vue';
</script>
<style scoped lang="scss" src="./AirSkyLand.scss"></style>

View File

@ -0,0 +1,3 @@
# 空天地-一体化智能检测-大屏展示
#### 该页面来自于WuRenJi 项目的YLZG页面对其进行vue化的改造。

View File

@ -0,0 +1,7 @@
import { request } from '@shared/utils/request'
export function getBaseMapLayer() {
return request({
url: '/api/v1/DataDirectory/QueryCatalog',
method: 'get'
})
}

View File

@ -0,0 +1,19 @@
<!-- 无人机监测 -->
<template>
<div class="aircraft-monitor-section">
<!-- 每日快速巡检 -->
<QuickInspection />
<!-- 关键点经常性检查 -->
<KeyInspection />
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import QuickInspection from './QuickInspection.vue';
import KeyInspection from './KeyInspection.vue';
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
</style>

View File

@ -0,0 +1,104 @@
<!-- 每日巡检 -->
<template>
<div class="key-inspection">
<div class="title-block">
<div class="title-text">关键点经常性检查</div>
<div class="date-text">巡查月份: {{ date }}</div>
</div>
<div class="content-block">
<div class="item-box-list">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="left-side">
<div class="main-field-item-box">
<img />
<div class="right-side">
<div class="number-label-text">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
</div>
<div class="right-side">
<div class="sub-field-item-box">
<img />
<div class="right-side">
<div class="number-label-text">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
<div class="sub-field-item-box">
<img />
<div class="right-side">
<div class="number-label-text">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const date = ref('2025/10/10')
const columnConfig = ref([
{
label: '边坡总数',
name: '',
icon: '',
completeLabel: '完成率',
completeIcon: '',
completeValue: '',
countLabel: '派单数',
countIcon: '',
countValue: ''
},
{
label: '边坡总数',
name: '',
icon: '',
completeLabel: '完成率',
completeIcon: '',
completeValue: '',
countLabel: '派单数',
countIcon: '',
countValue: ''
},
])
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.key-inspection {}
.item-box-list {
display: flex;
align-items: center;
.item-box {
display: flex;
align-items: center;
}
}
.left-field-item-box {
display: flex;
align-items: center;
.number-label-text {}
.label-text {}
}
</style>

View File

@ -0,0 +1,79 @@
<!-- 每日快速巡检 -->
<template>
<div class="everyday-inspection">
<div class="title-block">
<div class="title-text">每日快速巡检</div>
<div class="date-text">巡检日期: {{ date }}</div>
</div>
<div class="content-block">
<div class="item-box-list">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="left-side">
<img />
</div>
<div class="right-side">
<div class="number-label-text">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const date = ref('2025/10/10')
const columnConfig = ref([
{
label: '路线总里程 (公里)',
icon: '',
name: '',
},
{
label: '完成率',
icon: '',
name: '',
},
{
label: '复核率',
icon: '',
name: '',
},
{
label: '派单数',
icon: '',
name: '',
}
])
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.everyday-inspection {}
.item-box-list {
display: flex;
align-items: center;
.item-box {
display: flex;
align-items: center;
.number-label-text {}
.label-text {}
}
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<div class="common-monitor-section">
<div class="common-monitor-area">
<div class="area-title">日常巡检</div>
<StateRoad />
<CountryRoad />
</div>
<div class="common-monitor-area">
<div class="area-title">智能巡查</div>
<SmartInspection />
</div>
<div class="common-monitor-area">
<div class="area-title">智能监测</div>
<SmartMonitor />
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import StateRoad from './StateRoad.vue';
import CountryRoad from './CountryRoad.vue';
import SmartMonitor from './SmartMonitor.vue';
import SmartInspection from './SmartInspection.vue'
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,82 @@
<!-- 农村公路路障数值化履职 -->
<template>
<div class="country-road">
<div class="top-side">
<div class="info-item-box">
<img class="icon" />
<div class="right-side">
<div class="number-label-text">{{ '' }}<span class="special"></span></div>
<div class="label-text">{{ '农村公路巡检总人数' }}</div>
</div>
</div>
</div>
<div class="bottom-side">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="bottom-item-box">
<img class="icon" />
<div class="right-side">
<div class="number-label-text" :class="{special: item.special}">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const columnConfig = ref([
{
label: '事件总数 (件)',
special: true,
icon: '',
name: '',
},
{
label: '处置数 (件)',
icon: '',
name: '',
},
{
label: '处置中 (件)',
icon: '',
name: '',
}
])
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.info-item-box {
display: flex;
align-items: center;
.icon {}
.special {}
.number-label-text {}
.label-text {}
}
.bottom-item-box {
display: flex;
align-items: center;
.icon {}
.special {}
.number-label-text {}
.label-text {}
}
</style>

View File

@ -0,0 +1,78 @@
<!-- 智能巡查 -->
<template>
<div class="smart-inspection">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="left-side">
<img class="icon" />
</div>
<div class="right-side">
<div class="field-item title">
<div class="label-text">{{ item.label }}</div>
</div>
<div class="field-item expection">
<div class="label-text">异常数</div>
<div class="value-text">{{ data[item.expection] }}</div>
</div>
<div class="field-item dispatch">
<div class="label-text">派单数</div>
<div class="value-text">{{ data[item.dispatch] }}</div>
</div>
<div class="field-item online">
<div class="label-text">总数 / 在线数</div>
<div class="value-text">{{ data[item.online] }}</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const columnConfig = ref([
{
label: '视频巡查',
icon: '',
expection: '',
dispatch: '',
online: ''
},
{
label: '公交巡查',
icon: '',
expection: '',
dispatch: '',
online: ''
},
{
label: '专业巡查',
icon: '',
expection: '',
dispatch: '',
online: ''
}
])
</script>
<style scoped lang="scss">
.smart-inspection {
display: flexx;
}
.item-box {
display: flex;
align-items: center;
.icon {}
.number-label-text {}
.label-text {}
}
</style>

View File

@ -0,0 +1,67 @@
<!-- 智能监测 -->
<template>
<div class="smart-monitor">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="left-side">
<img class="icon" />
</div>
<div class="right-side">
<div class="field-item title">
<div class="label-text">{{ item.label }}</div>
</div>
<div class="count-item">
{{ data[item.count] }}
</div>
<div class="field-item alert">
<div class="label-text">预警数</div>
<div class="value-text">{{ data[item.alert] }}</div>
</div>
<div class="field-item dispatch">
<div class="label-text">派单数</div>
<div class="value-text">{{ data[item.dispatch] }}</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const columnConfig = ref([
{
label: '桥梁监测',
icon: '',
count: '',
countSubfix: '座',
alert: '',
dispatch: '',
},
{
label: '隧道监测',
icon: '',
count: '',
countSubfix: '座',
alert: '',
dispatch: '',
},
{
label: '边坡监测',
icon: '',
count: '',
countSubfix: '处',
alert: '',
dispatch: '',
}
])
</script>
<style scoped lang="scss">
.smart-monitor {}
</style>

View File

@ -0,0 +1,82 @@
<!-- 国省通风险管控 -->
<template>
<div class="state-road">
<div class="top-bar">
<div class="top-item-box">
<img class="icon" />
<div class="right-side">
<div class="number-label-text">{{ data }}</div>
<div class="label-text">国道巡检总人数</div>
</div>
</div>
<div class="top-item-box">
<img class="icon" />
<div class="right-side">
<div class="number-label-text">{{ data }}%</div>
<div class="label-text">履职率</div>
</div>
</div>
</div>
<div class="bottom-bar">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="top-side">{{ item['title'] }}</div>
<div class="bottom-side">
<div>
<div class="person-block">{{ data[item.person] }} <span class="person-text"></span></div>
</div>
<div class="flex">
<img />
<div class="">巡查履职完成率</div>
<div>
<span class="percent-text">{{ data[item.percent] }}%</span>{{ item.percentSubfix }}
</div>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const columnConfig = ref([
{
title: '交通主管部门责任人',
icon: '',
person: '',
percent: '',
percentSubfix: '1次/半年',
},
{
title: '公路机构责任人',
icon: '',
person: '',
percent: '',
percentSubfix: '1次/1月',
},
{
title: '养护站(道班)责任人',
icon: '',
person: '',
percent: '',
percentSubfix: '1次/半年',
},
{
title: '路段护路员',
icon: '',
person: '',
percent: '',
percentSubfix: '1次/2周',
}
])
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,10 @@
<template>
<div class="">
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
//["", "线", "", "", "", "", "", "", ""]
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,208 @@
<template>
<div class="map-center">
<div class="map-container" ref="mapContainer" />
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getBaseMapLayer } from '../../api'
import * as Cesium from 'cesium'
import { useMapStore } from '../../../../map/index.js'
const emit = defineEmits()
const mapStore = useMapStore()
const ionToken = import.meta.env.VITE_CESIUM_ION_TOKEN
if (ionToken) {
Cesium.Ion.defaultAccessToken = ionToken
}
const mapContainer = ref(null);
const viewerRef = ref(null);
const initCesium = () => {
if (!mapContainer.value) return;
if (viewerRef.value) return
try {
viewerRef.value = new Cesium.Viewer(mapContainer.value, {
//
timeline: false,
animation: false,
fullscreenButton: false,
vrButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: true,
selectionIndicator: false,
shadows: true,
shouldAnimate: true,
navigationHelpButton: false,
imageryProvider: false,
baseLayerPicker: false,
sceneModePicker: false,
// 使
useBrowserRecommendedResolution: false,
maximumScreenSpaceError: 2,
//
// imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
// url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
// })
})
viewerRef.value.cesiumWidget.creditContainer.style.display = 'none'
mapStore.destroy()
mapStore.init(viewerRef.value)
loadBaseMap()
} catch (e) {
console.error('初始化 Cesium 失败:', e)
}
}
const setViewToCoordinates = (longitude, latitude, height) => {
if (!viewerRef.value) return
viewerRef.value.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
orientation: {
heading: Cesium.Math.toRadians(0), //
pitch: Cesium.Math.toRadians(-90), // -90
roll: 0 //
}
})
clearAllEntities()
// addDeviceMarker(longitude, latitude, 0, {
// label: device.value.DName
// })
}
//
// const addDeviceMarker = (longitude, latitude, height = 0, options = {}) => {
// if (!viewerRef.value) return null;
// const entity = viewerRef.value.entities.add({
// id: options.id || `device-${Date.now()}`,
// position: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
// billboard: {
// image: '/src/assets/images/device.png',
// width: 42,
// height: 42,
// horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
// verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
// heightReference: options.clampToGround
// ? Cesium.HeightReference.CLAMP_TO_GROUND
// : Cesium.HeightReference.NONE,
// disableDepthTestDistance: Number.POSITIVE_INFINITY, //
// color: options.color ? Cesium.Color.fromCssColorString(options.color) : Cesium.Color.WHITE
// },
// label: options.label ? {
// text: options.label,
// font: options.font || '14px sans-serif',
// fillColor: Cesium.Color.BLACK,
// outlineWidth: 0,
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// verticalOrigin: Cesium.VerticalOrigin.TOP,
// pixelOffset: new Cesium.Cartesian2(0, 2),
// showBackground: true,
// backgroundColor: Cesium.Color.WHITE,
// backgroundPadding: new Cesium.Cartesian2(5, 8),
// distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0.0, 200000.0) //
// } : undefined
// });
// return entity;
// };
//
const clearAllEntities = () => {
if (!viewerRef.value) return;
entityMap = {}
//
viewerRef.value.entities.removeAll();
};
const setDevice = (_device) => {
device.value = _device
const { Lng, Lat } = _device
setViewToCoordinates(Lng, Lat, 1600)
};
//
async function loadBaseMap() {
const { layer } = mapStore.services()
try {
const currentGroupId = mapStore.getCurrentBaseMapGroupId()
console.log('当前底图组ID', currentGroupId)
if (!currentGroupId) return
for (const group of mapStore.baseMapGroups) {
const groupId = group.Attribute?.rid || group.Rid
const visible = groupId === currentGroupId
const layers = mapStore.getBaseMapLayersForGroup(groupId)
for (const cfg of layers) {
await layer.addLayer({
id: cfg.id,
type: cfg.type,
url: cfg.url,
options: { visible },
meta: cfg.meta
})
}
}
} catch (e) {
console.warn('底图加载失败', e)
}
}
onMounted(async () => {
await fetchBaseMapLayers()
initCesium()
})
const fetchBaseMapLayers = async () => {
try {
const params = {
pcatalog: 'DDT'
}
const res = await getBaseMapLayer(params)
// const response = await axiosApi('/api/v1/DataDirectory/QueryCatalog', 'GET', {
// pcatalog: 'DDT'
// })
// if (!response?.data) {
// console.error('')
// return createDefaultResult()
// }
// const loadBaseMap = response.data.length > 0 ? response.data : defaultBaseMapLayers
// const layerConfigs = processLayerConfigs(collectLayerConfigs(loadBaseMap))
// return { layerConfigs, rawBaseMapData: loadBaseMap }
} catch (error) {
console.error('获取底图图层时出错:', error)
// return createDefaultResult()
}
}
</script>
<style scoped lang="scss">
.map-center {
width: 100%;
height: 100%;
.map-container {
overflow: hidden;
border-radius: 6px;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,66 @@
<!-- 遥感监测 -->
<template>
<div class="remote-monitor">
<div class="item-box-list">
<template v-for="(item, index) in columnConfig" :key="index">
<div class="item-box">
<div class="left-side">
<img class="icon" />
</div>
<div class="right-side">
<div class="number-label-text">{{ data[item.name] }}</div>
<div class="label-text">{{ item['label'] }}</div>
</div>
</div>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
})
const columnConfig = ref([
{
label: '当月隐患点总数',
icon: '',
name: '',
},
{
label: '区县确认数',
icon: '',
name: '',
},
{
label: '派单数',
icon: '',
name: '',
}
])
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.item-box-list {
display: flex;
align-items: center;
.item-box {
display: flex;
align-items: center;
.icon {
}
.number-label-text {}
.label-text {}
}
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="section-title">
<span class="title-text">{{ title }}</span>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const props = defineProps({
title: {
type: String,
default: ''
}
})
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.section-title {
display: flex;
align-items: center;
position: relative;
background-image: url("../../assets/img/common-panel-header-bg.png");
background-repeat: no-repeat;
background-size: cover;
width: vw(327);
height: vw(55);
padding-left: vw(40);
}
.title-text {
font-size: vw(24);
font-weight: 700;
color: #fff;
}
</style>