DSDSWeb/src/views/statistics/AuvStatistics.vue

470 lines
13 KiB
Vue
Raw Normal View History

<template>
<div class="statistics">
<div class="banner">
2026-04-03 02:05:29 +08:00
<div class="banner-title-picker">
2026-04-03 11:10:58 +08:00
<el-dropdown class="banner-type-dropdown" trigger="click" @command="handleCommand">
2026-04-03 02:05:29 +08:00
<span class="banner-select-trigger">
<span class="banner-select-text">{{ currentTitle }}</span>
2026-04-03 11:10:58 +08:00
<svg class="banner-select-chevron" viewBox="0 0 24 24" width="40" height="40" aria-hidden="true"
focusable="false">
2026-04-03 02:05:29 +08:00
<path fill="#ffffff" d="M7 10l5 5 5-5H7z" />
</svg>
</span>
<el-dropdown-menu slot="dropdown" class="banner-type-dropdown-popper">
<el-dropdown-item command="全部无人航行潜水器">全部无人航行潜水器</el-dropdown-item>
2026-04-03 11:10:58 +08:00
<el-dropdown-item v-for="item in titles" :key="item.tsy_id" :command="item.report_name">
2026-04-03 02:05:29 +08:00
{{ item.report_name }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<p class="banner-title-picker__hint">点击选择</p>
</div>
<div class="numbers">
<div v-for="(item, index) in numbers" :key="index" class="item">
<div class="value">{{ item.value }}<span class="unit">{{ item.unit }}</span></div>
<div class="label">{{ item.label }}</div>
</div>
</div>
</div>
<!-- 地图板块-->
<div class="map-module">
<div class="bezel">
<div class="map-title">下潜单位</div>
<div id="workUnitMap_auv" ref="workUnitMap_auv" style="height: 100%;width:100%;" />
</div>
<div class="bezel">
<div class="map-title">下潜地图</div>
<div id="voyageMap_auv" ref="voyageMap_auv" style="height: 100%;width:100%;" />
</div>
</div>
<!-- echarts板块-->
<div class="echarts-module">
<div class="bezel">
<BarChart :title="barData1.title" :chart-data="barData1.salesData" :categories="barData1.productCategories"
chart-type="single" />
</div>
<div class="bezel">
<BarChart :title="barData2.title" :chart-data="barData2.salesData" :categories="barData2.productCategories"
chart-type="stacked" stack-name="新增单位数" />
</div>
<div class="bezel">
<BarChart :title="barData3.title" :chart-data="barData3.salesData" :categories="barData3.productCategories" />
</div>
<div class="bezel">
<BarChart :title="barData4.title" :chart-data="barData4.salesData" :categories="barData4.productCategories" />
</div>
<div class="bezel">
<BarChart :title="barData5.title" :chart-data="barData5.salesData" :categories="barData5.productCategories" />
</div>
<div class="bezel">
<PieChart :chart-data="pieData" title="下潜单位类型" chart-type="ring" />
</div>
</div>
</div>
</template>
<script>
2026-04-03 02:05:29 +08:00
/**
2026-04-03 11:10:58 +08:00
* 无人航行潜水器汇总统计页请求 `/report/platform/auv` 系列接口交互与遥控潜水器汇总页一致ECharts
2026-04-03 02:05:29 +08:00
*/
import BarChart from './components/BarChart.vue'
import PieChart from './components/PieChart.vue'
2026-04-03 02:05:29 +08:00
import { reportList, platformAuv, auvUnit, auvUnitYear, auvDiving } from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
import { ColorGenerator } from '@/utils/color-generator';
export default {
components: {
BarChart,
PieChart
},
data() {
return {
currentTitle: '全部无人航行潜水器',
titles: [],
numbers: [
{
label: '潜次数',
value: '',
unit: '次'
},
{
label: '下潜人数',
value: '',
unit: '人'
},
{
label: '下潜单位',
value: '',
unit: '家'
},
{
label: '数据样品个数',
value: '',
unit: '个'
}
],
barData1: {},
barData2: {},
barData3: {},
barData4: {},
barData5: {},
pieData: []
}
},
created() {
this.getTypeList()
2026-04-03 02:05:29 +08:00
this.getAuvList()
this.getAuvUnit()
this.gitAuvDiving()
this.gitAuvUnitYear()
this.getChartBar({ year: '2025' })
},
mounted() {
initMapbox("workUnitMap_auv", {
tileKey: "gaode",
zoom: 3,
center: [110, 31],
minZoom: 2,
maxZoom: 29
});
initMapbox("voyageMap_auv", {
tileKey: "GEBCO_basemap_NCEI",
2026-04-03 11:10:58 +08:00
zoom: 3,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
2025-11-18 15:33:41 +08:00
},
beforeDestroy() {
2026-04-03 11:10:58 +08:00
},
methods: {
// 无人航行潜水器数据
getTypeList() {
reportList().then(res => {
this.titles = filterDictItems(res.array, '无人航行潜水器')
})
},
// 选择无人航行潜水器
handleCommand(command) {
this.currentTitle = command
this.removeSingleLineImg()
if (command === '全部无人航行潜水器') {
2026-04-03 02:05:29 +08:00
this.getAuvList()
this.gitAuvDiving()
this.gitAuvUnitYear()
this.getChartBar({ year: '2025' })
} else {
2026-04-03 02:05:29 +08:00
this.getAuvList({ platform_name: command })
this.gitAuvDiving({ platform_name: command })
this.gitAuvUnitYear({ platform_name: command })
this.getChartBar({ year: '2025', platform_name: command })
}
},
// 汇总统计数据
2026-04-03 02:05:29 +08:00
getAuvList(params = {}) {
platformAuv(params).then(res => {
const obj = res.array[0]
this.numbers[0].value = obj.diving_total
this.numbers[1].value = obj.driver_member_total
this.numbers[2].value = obj.unit_total
this.numbers[3].value = obj.dataset_total
})
},
// 下潜单位统计
2026-04-03 02:05:29 +08:00
getAuvUnit() {
auvUnit().then(res => {
let geojson = {
type: "FeatureCollection",
features: []
};
res.array.forEach(element => {
geojson.features.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.unit_lon, element.unit_lat]
},
properties: {
unit_name: element.unit_name,
unit_type: element.unit_type,
create_time: element.create_time,
tsy_id: element.tsy_id
}
});
});
loadJsonPointFeature("workUnit", "blue", geojson, "unit_name", "workUnitMap_auv");
2025-11-18 15:33:41 +08:00
const typeCount = res.array.reduce((acc, unit) => {
const type = unit.unit_type
acc[type] = (acc[type] || 0) + 1 // 如果类型已存在则计数+1否则初始化为1
return acc
}, {})
this.pieData = Object.entries(typeCount).map(([type, count]) => ({
name: type,
value: count
}))
})
},
2025-11-18 15:33:41 +08:00
// 潜次统计
2026-04-03 02:05:29 +08:00
gitAuvDiving(params = {}) {
auvDiving(params).then(res => {
let i = 0;
const colors = ColorGenerator.generateDivergingColors(res.array.length);
// 添加轨迹图层
res.array.forEach(item => {
// 加载轨迹图层
loadVectorLayer(item.diving_sn, ["line", "symbol"], [colors[i], "#FFFFFF"], "visible", "voyageMap_auv", "dsds");
i++;
})
})
},
// 参潜单位年度
2026-04-03 02:05:29 +08:00
gitAuvUnitYear(params = {}) {
auvUnitYear(params).then(res => {
const originalData = res.array
const sortedData = originalData.sort((a, b) => a.voyage_year - b.voyage_year)
this.barData2 = {
title: '年度参潜单位',
salesData: [
{
name: '参潜单位',
data: sortedData.map(item => item.unit_total),
stack: '新增单位数'
},
{
name: '新增单位',
data: sortedData.map(item => item.year_unit_total),
stack: '新增单位数'
}
],
productCategories: sortedData.map(item => item.voyage_year.toString())
}
})
},
// 柱状图数据
getChartBar(params) {
2026-04-03 02:05:29 +08:00
platformAuv(params).then(res => {
const originalData = res.array
const sortedData = originalData.sort((a, b) => a.year - b.year)
this.barData1 = {
title: '年度潜次数',
salesData: sortedData.map(item => item.diving_total),
productCategories: sortedData.map(item => item.year.toString())
}
this.barData3 = {
title: '样品集个数',
salesData: sortedData.map(item => item.dataset_total),
productCategories: sortedData.map(item => item.year.toString())
}
this.barData4 = {
title: '水中时间',
salesData: sortedData.map(item => item.water_total),
productCategories: sortedData.map(item => item.year.toString())
}
this.barData5 = {
title: '水中作业时间',
salesData: sortedData.map(item => item.job_total),
productCategories: sortedData.map(item => item.year.toString())
}
})
}
}
}
</script>
<style scoped lang="scss">
.statistics {
background-color: #ebf1f7;
.banner {
2026-04-03 11:17:11 +08:00
background-color: #012458;
background-image: url(../../../static/images/bannerny1.jpg);
2026-04-03 02:05:29 +08:00
background-repeat: no-repeat;
background-position: center;
2026-04-03 11:17:11 +08:00
background-size: contain;
width: 100%;
height: 480px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
2026-04-03 02:05:29 +08:00
.banner-title-picker {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40px;
2026-04-03 02:05:29 +08:00
padding: 12px 16px 0;
max-width: 100%;
}
.banner-type-dropdown {
display: inline-block;
width: auto;
max-width: 100%;
vertical-align: top;
}
.banner-select-trigger {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 2px 0;
border: none;
border-radius: 0;
background: transparent;
box-shadow: none;
cursor: pointer;
2026-04-03 02:05:29 +08:00
user-select: none;
outline: none;
&:hover .banner-select-text {
opacity: 0.92;
}
&:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.65);
outline-offset: 6px;
}
}
.banner-select-text {
font-size: 52px;
font-weight: bold;
color: #fff;
line-height: 1.15;
max-width: #{"min(90vw, 920px)"};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.banner-select-chevron {
flex-shrink: 0;
color: #ffffff;
}
.banner-title-picker__hint {
margin: 14px 0 0;
padding: 0;
font-size: 14px;
font-weight: 400;
letter-spacing: 0.12em;
color: rgba(255, 255, 255, 0.78);
line-height: 1.4;
}
.numbers {
display: flex;
justify-content: space-between;
gap: 80px;
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.value {
font-size: 56px;
font-weight: bold;
.unit {
font-size: 26px;
}
}
.label {
font-size: 18px;
}
}
}
}
.map-module {
padding: 50px 50px;
display: flex;
justify-content: space-between;
gap: 50px;
.bezel {
flex: 1;
width: 100%;
height: 520px;
border-radius: 10px;
overflow: hidden;
border: 1px solid #ccc;
position: relative;
.map-title {
position: absolute;
top: 20px;
left: 20px;
color: white;
z-index: 8888;
font-size: 22px;
}
.map-year {
position: absolute;
top: 20px;
right: 20px;
color: white;
z-index: 8888;
width: 100px;
}
}
}
.echarts-module {
padding: 0 50px 50px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 50px;
grid-auto-rows: 300px;
.bezel {
width: 100%;
height: 100%;
border-radius: 10px;
overflow: hidden;
border: 1px solid #ccc;
position: relative;
.map-title {
position: absolute;
top: 20px;
left: 20px;
color: white;
z-index: 8888;
font-size: 22px;
}
}
}
}
2026-04-03 02:05:29 +08:00
</style>
<style lang="scss">
.banner-type-dropdown-popper.el-dropdown-menu {
min-width: 240px;
max-height: #{"min(60vh, 420px)"};
overflow-y: auto;
padding: 6px 0;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
}
2026-04-03 02:05:29 +08:00
.banner-type-dropdown-popper.el-dropdown-menu .el-dropdown-menu__item {
font-size: 16px;
line-height: 22px;
padding: 14px 22px;
}
</style>