DSDSWeb/src/views/statistics/AuvStatistics.vue

487 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="statistics">
<div class="banner">
<div class="banner-title-picker">
<el-dropdown class="banner-type-dropdown" trigger="click" @command="handleCommand">
<span class="banner-select-trigger">
<span class="banner-select-text">{{ currentTitle }}</span>
<svg class="banner-select-chevron" viewBox="0 0 24 24" width="40" height="40" aria-hidden="true"
focusable="false">
<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>
<el-dropdown-item v-for="item in titles" :key="item.tsy_id" :command="item.report_name">
{{ 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>
import Vue from 'vue'
/**
* 无人航行潜水器汇总统计页,请求 `/report/platform/auv` 系列接口交互与遥控潜水器汇总页一致ECharts
*/
import BarChart from './components/BarChart.vue'
import PieChart from './components/PieChart.vue'
import { reportList, platformAuv, auvUnit, auvUnitYear, auvDiving } from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
import { initMapbox } from "@/utils/mapbox-utils";
import { loadVectorLayer, loadJsonPointFeature,showUnitReport } 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()
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",
zoom: 3,
center: [113, 15],
minZoom: 2,
maxZoom: 29
});
},
beforeDestroy() {
const unitMap = Vue.config.maps && Vue.config.maps['workUnitMap_auv']
if (unitMap) {
unitMap.remove()
delete Vue.config.maps['workUnitMap_auv']
}
const voyageMap = Vue.config.maps && Vue.config.maps['voyageMap_auv']
if (voyageMap) {
voyageMap.remove()
delete Vue.config.maps['voyageMap_auv']
}
},
methods: {
// 无人航行潜水器数据
getTypeList() {
reportList().then(res => {
this.titles = filterDictItems(res.array, '无人航行潜水器')
})
},
// 选择无人航行潜水器
handleCommand(command) {
this.currentTitle = command
this.removeSingleLineImg()
if (command === '全部无人航行潜水器') {
this.getAuvList()
this.gitAuvDiving()
this.gitAuvUnitYear()
this.getChartBar({ year: '2025' })
} else {
this.getAuvList({ platform_name: command })
this.gitAuvDiving({ platform_name: command })
this.gitAuvUnitYear({ platform_name: command })
this.getChartBar({ year: '2025', platform_name: command })
}
},
// 汇总统计数据
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
})
},
// 下潜单位统计
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");
Vue.config.maps['workUnitMap_auv'].on('click', 'workUnit', async (e) => {
if (!e.features || e.features.length <= 0)
return;
// 地图点选弹窗显示单位信息
showUnitReport('workUnitMap_auv', e.features[0].properties.unit_name, e.features[0].geometry.coordinates);
});
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
}))
})
},
// 潜次统计
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++;
})
})
},
// 参潜单位年度
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) {
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 {
background-color: #012458;
background-image: url(../../../static/images/bannerny1.jpg);
background-repeat: no-repeat;
background-position: center;
background-size: contain;
width: 100%;
height: 480px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
.banner-title-picker {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40px;
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;
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;
}
}
}
}
</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);
}
.banner-type-dropdown-popper.el-dropdown-menu .el-dropdown-menu__item {
font-size: 16px;
line-height: 22px;
padding: 14px 22px;
}
</style>