更新修复
This commit is contained in:
parent
727c204ae7
commit
fa5a6f2b63
|
|
@ -1,4 +1,10 @@
|
|||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
node_modules/
|
||||
/dist/
|
||||
npm-debug.log*
|
||||
|
|
@ -8,9 +14,14 @@ yarn-error.log*
|
|||
/test/e2e/reports/
|
||||
selenium-debug.log
|
||||
|
||||
# 根目录本地环境变量(避免把密钥提交入库);构建仍依赖的 config/*.env.js 保持跟踪
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.code-workspace
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ module.exports = merge(prodEnv, {
|
|||
// VUE_APP_API_URL: '"http://10.10.150.237/"'
|
||||
|
||||
VUE_APP_BASE_API: '"/api"',
|
||||
/** 仅参航科学家报表;与生产一致直连 https(服务端 CORS 已放开,避免本地代理 404) */
|
||||
VUE_APP_UNIT_MEMBER_REPORT_BASE: '"https://weixin.bdsmart.cn/ds1"',
|
||||
// VUE_APP_BASE_APIURL: '"http://ds2.hzzxq.com/api2"',
|
||||
VUE_APP_BASE_APIURL: '"http://test.gopmas.com/ds"',
|
||||
VUE_APP_FULL_API: '"http://10.10.151.5:5555"',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,18 @@ module.exports = {
|
|||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
/**
|
||||
* 参航科学家接口单独转发到 weixin(须写在通用 /api 之前,避免被旧后端吞掉)。
|
||||
* 与 unitMember 使用 /ds1-report-proxy 或 /api 时的兜底一致。
|
||||
*/
|
||||
'/api/report/unit/member.htm': {
|
||||
target: 'https://weixin.bdsmart.cn',
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/ds1'
|
||||
}
|
||||
},
|
||||
'/api': {
|
||||
// target: 'http://120.48.105.88', // 线上
|
||||
// target: 'http://10.0.90.70', // 测试环境
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ module.exports = {
|
|||
// VUE_APP_BASE_API: '"/api"',
|
||||
// VUE_APP_BASE_APIURL: '"http://ds.hzzxq.com/api"',
|
||||
VUE_APP_BASE_API: '"./"',
|
||||
/** 仅参航科学家报表 report/unit/member.htm 使用,与全局 VUE_APP_BASE_API 解耦 */
|
||||
VUE_APP_UNIT_MEMBER_REPORT_BASE: '"https://weixin.bdsmart.cn/ds1"',
|
||||
VUE_APP_BASE_APIURL: '"./"',
|
||||
VUE_APP_FULL_API: '"http://10.10.151.3:5555"',
|
||||
VUE_APP_BASE_API_FRONT: '"http://10.10.150.128:8080/satellitePub/pub/index.html"',
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,28 @@
|
|||
diff --git a/node_modules/http-deceiver/lib/deceiver.js b/node_modules/http-deceiver/lib/deceiver.js
|
||||
index 33e14cb..cec712f 100644
|
||||
--- a/node_modules/http-deceiver/lib/deceiver.js
|
||||
+++ b/node_modules/http-deceiver/lib/deceiver.js
|
||||
@@ -19,12 +19,17 @@ var kOnHeadersComplete;
|
||||
var kOnMessageComplete;
|
||||
var kOnBody;
|
||||
if (mode === 'normal' || mode === 'modern') {
|
||||
- HTTPParser = process.binding('http_parser').HTTPParser;
|
||||
- methods = HTTPParser.methods;
|
||||
-
|
||||
- // v6
|
||||
- if (!methods)
|
||||
- methods = process.binding('http_parser').methods;
|
||||
+ try {
|
||||
+ var binding = process.binding('http_parser');
|
||||
+ HTTPParser = binding.HTTPParser;
|
||||
+ methods = HTTPParser.methods;
|
||||
+ if (!methods) {
|
||||
+ methods = binding.methods;
|
||||
+ }
|
||||
+ } catch (err) {
|
||||
+ HTTPParser = require('http-parser-js').HTTPParser;
|
||||
+ methods = HTTPParser.methods;
|
||||
+ }
|
||||
|
||||
reverseMethods = {};
|
||||
|
||||
|
|
@ -100,6 +100,36 @@ export function platformAuv(data) {
|
|||
})
|
||||
}
|
||||
|
||||
/** 无人航行潜水器潜次统计 */
|
||||
export function auvDiving(data) {
|
||||
return request({
|
||||
url: '/report/platform/auv/diving.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 无人航行潜水器参航单位统计 */
|
||||
export function auvUnit(data) {
|
||||
return request({
|
||||
url: '/report/platform/auv/unit.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 无人航行潜水器参航单位统计(年度) */
|
||||
export function auvUnitYear(data) {
|
||||
return request({
|
||||
url: '/report/platform/auv/unit/year.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 遥控潜水器潜水器统计
|
||||
export function platformRov(data) {
|
||||
return request({
|
||||
|
|
@ -109,3 +139,78 @@ export function platformRov(data) {
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 遥控潜水器潜次统计 */
|
||||
export function rovDiving(data) {
|
||||
return request({
|
||||
url: '/report/platform/rov/diving.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 遥控潜水器参航单位统计 */
|
||||
export function rovUnit(data) {
|
||||
return request({
|
||||
url: '/report/platform/rov/unit.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 遥控潜水器参航单位统计(年度) */
|
||||
export function rovUnitYear(data) {
|
||||
return request({
|
||||
url: '/report/platform/rov/unit/year.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* ds1 报表独立域名(参航科学家/单位汇总等),与全局 VUE_APP_BASE_API 解耦。
|
||||
* @returns {string}
|
||||
*/
|
||||
function getDs1ReportBase() {
|
||||
return (
|
||||
process.env.VUE_APP_UNIT_MEMBER_REPORT_BASE ||
|
||||
'https://weixin.bdsmart.cn/ds1'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位维度参航科学家明细(按科学家汇总)。
|
||||
* @param {Object} data 请求体,需包含 unit(单位标识)
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function unitMember(data) {
|
||||
return request({
|
||||
baseURL: getDs1ReportBase(),
|
||||
url: '/report/unit/member.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
/** 跨域且服务端返回 Access-Control-Allow-Origin: * 时不可带 cookie,否则浏览器会拦截 */
|
||||
withCredentials: false,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位维度汇总统计(按单位一行,含航次、天数、人数、深潜等)。
|
||||
* 与 unitMember 共用 ds1 baseURL。
|
||||
* @param {Object} [data] 可选 unit 等筛选参数;空对象表示全部单位
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function unitTotal(data = {}) {
|
||||
return request({
|
||||
baseURL: getDs1ReportBase(),
|
||||
url: '/report/unit/total.htm',
|
||||
method: 'post',
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
withCredentials: false,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,15 +379,39 @@ export const constantRoutes = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'uuv',
|
||||
name: 'uuv',
|
||||
component: () => import('@/views/statistics/UuvStatistics.vue'),
|
||||
path: 'auv',
|
||||
name: 'auv',
|
||||
component: () => import('@/views/statistics/AuvStatistics.vue'),
|
||||
meta: {
|
||||
title: '无人潜水器汇总统计',
|
||||
icon: 'peoples',
|
||||
loginAuthority: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'uuv',
|
||||
redirect: '/container/auv'
|
||||
},
|
||||
{
|
||||
path: 'uuvMember',
|
||||
name: 'uuvMember',
|
||||
component: () => import('@/views/statistics/UuvMemberStatistics.vue'),
|
||||
meta: {
|
||||
title: '参航科学家统计',
|
||||
icon: 'peoples',
|
||||
loginAuthority: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'uuvUnit',
|
||||
name: 'uuvUnit',
|
||||
component: () => import('@/views/statistics/UuvUnitStatistics.vue'),
|
||||
meta: {
|
||||
title: '参航单位统计',
|
||||
icon: 'peoples',
|
||||
loginAuthority: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'AboutUs',
|
||||
name: 'AboutUs',
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@
|
|||
<el-radio label="航次">航次</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="left4" v-if="radio == '数据样品集'">
|
||||
<div class="left4-sample-wrap" v-if="radio == '数据样品集'">
|
||||
<p class="rule-text">编号规则:潜次号_平台_设备类型_样品数据类型</p>
|
||||
<div class="left4">
|
||||
<div v-for="(item, index) in dataList" :key="index" :id="item.tsy_id" class="left4_1" >
|
||||
<div class="left4_top">
|
||||
<p class="rule-text">编号规则:航次号_潜次号_设备类型_数据样品类型</p>
|
||||
<p @click="openList(item, 'list')" style="font-weight: 400;font-size: 14px;cursor: pointer;color: #409eff" >{{ item.dataset_name }} <span class="mileage">{{ item.file_total }}</span></p>
|
||||
<span class="org">{{ item.create_time }}</span>
|
||||
</div>
|
||||
|
|
@ -75,7 +76,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="left4" v-if="radio == '航次'">
|
||||
<div v-for="(item, index) in dataList" :key="index" class="left4_1" style="cursor: pointer" :id="item.tsy_id">
|
||||
|
|
@ -806,6 +807,7 @@ export default {
|
|||
.left1 {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
|
|
@ -846,27 +848,60 @@ export default {
|
|||
padding: 10px 0;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据样品集:编号说明固定在滚动区外,仅列表在 .left4 内滚动
|
||||
*/
|
||||
.left4-sample-wrap {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .rule-text {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
> .left4 {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 筛选结果列表:占满左侧剩余高度并独立滚动 */
|
||||
.left4 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #c0c4cc #f0f0f0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
/* 保持一个最小的宽度 */
|
||||
background-color: transparent;
|
||||
/* 透明背景 */
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
/* 滚动条滑块也设置为透明 */
|
||||
background-color: #c0c4cc;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background-color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.left4_1 {
|
||||
|
|
@ -989,6 +1024,7 @@ export default {
|
|||
color: #333333;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
.tag {
|
||||
color: #333333;
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">数据样品类型录入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="data_name" label="数据样品类型名称" />
|
||||
<el-table-column prop="data_eng" label="英文名称" />
|
||||
<el-table-column prop="data_alias" label="别名" width="100" />
|
||||
<el-table-column prop="data_order" label="排序" width="100" />
|
||||
<el-table-column prop="data_group" label="分组" width="120" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" />
|
||||
<el-table-column prop="data_name" label="数据样品类型名称" sortable="custom" />
|
||||
<el-table-column prop="data_eng" label="英文名称" sortable="custom" />
|
||||
<el-table-column prop="data_alias" label="别名" width="100" sortable="custom" />
|
||||
<el-table-column prop="data_order" label="排序" width="100" sortable="custom" />
|
||||
<el-table-column prop="data_group" label="分组" width="120" sortable="custom" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -91,7 +91,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -133,6 +135,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:分页请求携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -211,6 +231,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">设备类型录入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="equipment_name" label="设备类型名称" />
|
||||
<el-table-column prop="equipment_eng" label="英文名称" />
|
||||
<el-table-column prop="equipment_alias" label="别名" width="100" />
|
||||
<el-table-column prop="equipment_order" label="排序" width="100" />
|
||||
<el-table-column prop="equipment_group" label="分组" width="120" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" />
|
||||
<el-table-column prop="equipment_name" label="设备类型名称" sortable="custom" />
|
||||
<el-table-column prop="equipment_eng" label="英文名称" sortable="custom" />
|
||||
<el-table-column prop="equipment_alias" label="别名" width="100" sortable="custom" />
|
||||
<el-table-column prop="equipment_order" label="排序" width="100" sortable="custom" />
|
||||
<el-table-column prop="equipment_group" label="分组" width="120" sortable="custom" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -91,7 +91,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -133,6 +135,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:分页请求携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -211,6 +231,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">资助机构录入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="funding_name" label="资助机构名称" />
|
||||
<el-table-column prop="funding_eng" label="英文名称" />
|
||||
<el-table-column prop="funding_order" label="排序" width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" />
|
||||
<el-table-column prop="funding_name" label="资助机构名称" sortable="custom" />
|
||||
<el-table-column prop="funding_eng" label="英文名称" sortable="custom" />
|
||||
<el-table-column prop="funding_order" label="排序" width="100" sortable="custom" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -80,7 +80,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -116,6 +118,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:分页请求携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -191,6 +211,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">平台类型录入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="platform_name" label="平台类型名称" />
|
||||
<el-table-column prop="platform_eng" label="英文名称" />
|
||||
<el-table-column prop="platform_alias" label="别名" width="100" />
|
||||
<el-table-column prop="platform_order" label="排序" width="100" />
|
||||
<el-table-column prop="platform_group" label="分组" width="120" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" />
|
||||
<el-table-column prop="platform_name" label="平台类型名称" sortable="custom" />
|
||||
<el-table-column prop="platform_eng" label="英文名称" sortable="custom" />
|
||||
<el-table-column prop="platform_alias" label="别名" width="100" sortable="custom" />
|
||||
<el-table-column prop="platform_order" label="排序" width="100" sortable="custom" />
|
||||
<el-table-column prop="platform_group" label="分组" width="120" sortable="custom" />
|
||||
<el-table-column prop="create_time" label="创建时间" width="160" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -91,7 +91,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -133,6 +135,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:分页请求携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -211,6 +231,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">共享单位录入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="unit_name" label="共享单位名称" width="300" />
|
||||
<el-table-column prop="unit_type" label="单位类型" width="200">
|
||||
<el-table-column prop="unit_name" label="共享单位名称" width="300" sortable="custom" />
|
||||
<el-table-column prop="unit_type" label="单位类型" width="200" sortable="custom">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.unit_type ? '单位类型' + scope.row.unit_type : '' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="link_man" label="单位负责人" width="100" />
|
||||
<el-table-column prop="link_tel" label="联系电话" width="150" />
|
||||
<el-table-column prop="unit_addr" label="单位地址" />
|
||||
<el-table-column prop="link_man" label="单位负责人" width="100" sortable="custom" />
|
||||
<el-table-column prop="link_tel" label="联系电话" width="150" sortable="custom" />
|
||||
<el-table-column prop="unit_addr" label="单位地址" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -107,7 +107,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -156,6 +158,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:分页请求携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -236,6 +256,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
<el-button type="primary" size="medium" @click="handleAdd">新增参航单位</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
||||
<el-table ref="tableData" v-loading="loading" stripe border :data="tableData" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" @sort-change="handleSortChange">
|
||||
<el-table-column type="index" label="#" width="50"/>
|
||||
<el-table-column prop="unit_name" label="参航单位名称" />
|
||||
<el-table-column prop="unit_type" label="参航单位类型" width="200" />
|
||||
<el-table-column prop="unit_lon" label="经度" width="200" />
|
||||
<el-table-column prop="unit_lat" label="纬度" width="200" />
|
||||
<el-table-column prop="unit_name" label="参航单位名称" sortable="custom" />
|
||||
<el-table-column prop="unit_type" label="参航单位类型" width="200" sortable="custom" />
|
||||
<el-table-column prop="unit_lon" label="经度" width="200" sortable="custom" />
|
||||
<el-table-column prop="unit_lat" label="纬度" width="200" sortable="custom" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip class="item" effect="dark" content="修改" placement="top-end">
|
||||
|
|
@ -89,7 +89,9 @@ export default {
|
|||
start: 0,
|
||||
limit: 10,
|
||||
group: 10,
|
||||
query: ''
|
||||
query: '',
|
||||
order: undefined,
|
||||
type: undefined
|
||||
},
|
||||
pageNum: 1,
|
||||
// 列表数据
|
||||
|
|
@ -131,6 +133,24 @@ export default {
|
|||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 表头排序:请求 `/dict/unit/sail/page.htm` 时携带 order(字段名)、type(ASC|DESC)
|
||||
* @param {{ column?: object, prop?: string, order?: string | null }} sort
|
||||
*/
|
||||
handleSortChange(sort) {
|
||||
const prop = sort.prop
|
||||
const order = sort.order
|
||||
if (!order || !prop) {
|
||||
this.$delete(this.params, 'order')
|
||||
this.$delete(this.params, 'type')
|
||||
} else {
|
||||
this.params.order = prop
|
||||
this.params.type = order === 'ascending' ? 'ASC' : 'DESC'
|
||||
}
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
// 获取列表数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
|
|
@ -208,6 +228,8 @@ export default {
|
|||
},
|
||||
handleSizeChange(val) {
|
||||
this.params.limit = val
|
||||
this.pageNum = 1
|
||||
this.params.start = 0
|
||||
this.getData()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="titles">{{ currentTitle }}<i class="el-icon-arrow-down el-icon--right" /></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<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"
|
||||
|
|
@ -14,6 +31,8 @@
|
|||
</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>
|
||||
|
|
@ -25,11 +44,11 @@
|
|||
<div class="map-module">
|
||||
<div class="bezel">
|
||||
<div class="map-title">下潜单位</div>
|
||||
<div id="baiduCesiumContainer" ref="baiduCesiumContainer" style="height: 100%;width:100%;" />
|
||||
<div id="auvBaiduCesiumContainer" ref="auvBaiduCesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<div class="map-title">下潜地图</div>
|
||||
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
|
||||
<div id="auvCesiumContainer" ref="auvCesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- echarts板块-->
|
||||
|
|
@ -84,10 +103,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 无人航行潜水器汇总统计页,请求 `/report/platform/auv` 系列接口;交互与遥控潜水器汇总页一致(Cesium + ECharts)。
|
||||
*/
|
||||
import BarChart from './components/BarChart.vue'
|
||||
import PieChart from './components/PieChart.vue'
|
||||
import { setImageryViewModels } from '../../utils/common'
|
||||
import { reportList, platformHov, hovUnit, hovUnitYear, hovDiving } from '../../api/statistics'
|
||||
import { reportList, platformAuv, auvUnit, auvUnitYear, auvDiving } from '../../api/statistics'
|
||||
import { filterDictItems } from '../../utils/index'
|
||||
const Cesium = window.Cesium
|
||||
|
||||
|
|
@ -139,10 +161,10 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getTypeList()
|
||||
this.getHovList()
|
||||
this.getHovUnit()
|
||||
this.gitHovDiving()
|
||||
this.gitHovUnitYear()
|
||||
this.getAuvList()
|
||||
this.getAuvUnit()
|
||||
this.gitAuvDiving()
|
||||
this.gitAuvUnitYear()
|
||||
this.getChartBar({ year: '2025' })
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -171,20 +193,20 @@ export default {
|
|||
this.currentTitle = command
|
||||
this.removeSingleLineImg()
|
||||
if (command === '全部无人航行潜水器') {
|
||||
this.getHovList()
|
||||
this.gitHovDiving()
|
||||
this.gitHovUnitYear()
|
||||
this.getAuvList()
|
||||
this.gitAuvDiving()
|
||||
this.gitAuvUnitYear()
|
||||
this.getChartBar({ year: '2025' })
|
||||
} else {
|
||||
this.getHovList({ platform_name: command })
|
||||
this.gitHovDiving({ platform_name: command })
|
||||
this.gitHovUnitYear({ platform_name: command })
|
||||
this.getAuvList({ platform_name: command })
|
||||
this.gitAuvDiving({ platform_name: command })
|
||||
this.gitAuvUnitYear({ platform_name: command })
|
||||
this.getChartBar({ year: '2025', platform_name: command })
|
||||
}
|
||||
},
|
||||
// 汇总统计数据
|
||||
getHovList(params = {}) {
|
||||
platformHov(params).then(res => {
|
||||
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
|
||||
|
|
@ -193,8 +215,8 @@ export default {
|
|||
})
|
||||
},
|
||||
// 下潜单位统计
|
||||
getHovUnit() {
|
||||
hovUnit().then(res => {
|
||||
getAuvUnit() {
|
||||
auvUnit().then(res => {
|
||||
// 清除之前的标记
|
||||
this.clearBaiduCesiumMarkers()
|
||||
|
||||
|
|
@ -261,8 +283,8 @@ export default {
|
|||
}
|
||||
},
|
||||
// 潜次统计
|
||||
gitHovDiving(params = {}) {
|
||||
hovDiving(params).then(res => {
|
||||
gitAuvDiving(params = {}) {
|
||||
auvDiving(params).then(res => {
|
||||
// 添加轨迹图层
|
||||
res.array.forEach(item => {
|
||||
this.addSingleLineImg(item)
|
||||
|
|
@ -271,7 +293,7 @@ export default {
|
|||
},
|
||||
// 初始化Cesium - 参航单位地图
|
||||
initBaiduCesium() {
|
||||
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.baiduCesiumContainer, {
|
||||
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.auvBaiduCesiumContainer, {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
|
|
@ -307,7 +329,7 @@ export default {
|
|||
// 初始化cesium
|
||||
initCesium() {
|
||||
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||
this.viewer = new Cesium.Viewer('cesiumContainer', {
|
||||
this.viewer = new Cesium.Viewer(this.$refs.auvCesiumContainer, {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
|
|
@ -367,8 +389,8 @@ export default {
|
|||
}
|
||||
},
|
||||
// 参潜单位年度
|
||||
gitHovUnitYear(params = {}) {
|
||||
hovUnitYear(params).then(res => {
|
||||
gitAuvUnitYear(params = {}) {
|
||||
auvUnitYear(params).then(res => {
|
||||
const originalData = res.array
|
||||
const sortedData = originalData.sort((a, b) => a.voyage_year - b.voyage_year)
|
||||
this.barData2 = {
|
||||
|
|
@ -391,7 +413,7 @@ export default {
|
|||
},
|
||||
// 柱状图数据
|
||||
getChartBar(params) {
|
||||
platformHov(params).then(res => {
|
||||
platformAuv(params).then(res => {
|
||||
const originalData = res.array
|
||||
const sortedData = originalData.sort((a, b) => a.year - b.year)
|
||||
this.barData1 = {
|
||||
|
|
@ -426,6 +448,9 @@ export default {
|
|||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
|
|
@ -434,12 +459,69 @@ export default {
|
|||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.titles {
|
||||
.banner-title-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
font-size: 56px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
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 {
|
||||
|
|
@ -523,8 +605,21 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px
|
||||
<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>
|
||||
|
|
@ -1,9 +1,26 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="titles">{{ currentTitle }}<i class="el-icon-arrow-down el-icon--right" /></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<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"
|
||||
|
|
@ -14,6 +31,8 @@
|
|||
</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>
|
||||
|
|
@ -426,6 +445,9 @@ export default {
|
|||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
|
|
@ -434,12 +456,69 @@ export default {
|
|||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.titles {
|
||||
.banner-title-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
font-size: 56px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
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 {
|
||||
|
|
@ -523,8 +602,21 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="titles">{{ currentTitle }}<i class="el-icon-arrow-down el-icon--right" /></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<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"
|
||||
|
|
@ -14,6 +31,8 @@
|
|||
</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>
|
||||
|
|
@ -25,11 +44,11 @@
|
|||
<div class="map-module">
|
||||
<div class="bezel">
|
||||
<div class="map-title">下潜单位</div>
|
||||
<div id="baiduCesiumContainer" ref="baiduCesiumContainer" style="height: 100%;width:100%;" />
|
||||
<div id="rovBaiduCesiumContainer" ref="rovBaiduCesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<div class="map-title">下潜地图</div>
|
||||
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
|
||||
<div id="rovCesiumContainer" ref="rovCesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- echarts板块-->
|
||||
|
|
@ -84,10 +103,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 遥控潜水器汇总统计页,请求 `/report/platform/rov` 系列接口。
|
||||
*/
|
||||
import BarChart from './components/BarChart.vue'
|
||||
import PieChart from './components/PieChart.vue'
|
||||
import { setImageryViewModels } from '../../utils/common'
|
||||
import { reportList, platformHov, hovUnit, hovUnitYear, hovDiving } from '../../api/statistics'
|
||||
import { reportList, platformRov, rovUnit, rovUnitYear, rovDiving } from '../../api/statistics'
|
||||
import { filterDictItems } from '../../utils/index'
|
||||
const Cesium = window.Cesium
|
||||
|
||||
|
|
@ -139,10 +161,10 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getTypeList()
|
||||
this.getHovList()
|
||||
this.getHovUnit()
|
||||
this.gitHovDiving()
|
||||
this.gitHovUnitYear()
|
||||
this.getRovList()
|
||||
this.getRovUnit()
|
||||
this.gitRovDiving()
|
||||
this.gitRovUnitYear()
|
||||
this.getChartBar({ year: '2025' })
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -171,20 +193,20 @@ export default {
|
|||
this.currentTitle = command
|
||||
this.removeSingleLineImg()
|
||||
if (command === '全部遥控潜水器') {
|
||||
this.getHovList()
|
||||
this.gitHovDiving()
|
||||
this.gitHovUnitYear()
|
||||
this.getRovList()
|
||||
this.gitRovDiving()
|
||||
this.gitRovUnitYear()
|
||||
this.getChartBar({ year: '2025' })
|
||||
} else {
|
||||
this.getHovList({ platform_name: command })
|
||||
this.gitHovDiving({ platform_name: command })
|
||||
this.gitHovUnitYear({ platform_name: command })
|
||||
this.getRovList({ platform_name: command })
|
||||
this.gitRovDiving({ platform_name: command })
|
||||
this.gitRovUnitYear({ platform_name: command })
|
||||
this.getChartBar({ year: '2025', platform_name: command })
|
||||
}
|
||||
},
|
||||
// 汇总统计数据
|
||||
getHovList(params = {}) {
|
||||
platformHov(params).then(res => {
|
||||
getRovList(params = {}) {
|
||||
platformRov(params).then(res => {
|
||||
const obj = res.array[0]
|
||||
this.numbers[0].value = obj.diving_total
|
||||
this.numbers[1].value = obj.driver_member_total
|
||||
|
|
@ -193,8 +215,8 @@ export default {
|
|||
})
|
||||
},
|
||||
// 下潜单位统计
|
||||
getHovUnit() {
|
||||
hovUnit().then(res => {
|
||||
getRovUnit() {
|
||||
rovUnit().then(res => {
|
||||
// 清除之前的标记
|
||||
this.clearBaiduCesiumMarkers()
|
||||
|
||||
|
|
@ -261,8 +283,8 @@ export default {
|
|||
}
|
||||
},
|
||||
// 潜次统计
|
||||
gitHovDiving(params = {}) {
|
||||
hovDiving(params).then(res => {
|
||||
gitRovDiving(params = {}) {
|
||||
rovDiving(params).then(res => {
|
||||
// 添加轨迹图层
|
||||
res.array.forEach(item => {
|
||||
this.addSingleLineImg(item)
|
||||
|
|
@ -271,7 +293,7 @@ export default {
|
|||
},
|
||||
// 初始化Cesium - 参航单位地图
|
||||
initBaiduCesium() {
|
||||
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.baiduCesiumContainer, {
|
||||
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.rovBaiduCesiumContainer, {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
|
|
@ -307,7 +329,7 @@ export default {
|
|||
// 初始化cesium
|
||||
initCesium() {
|
||||
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||
this.viewer = new Cesium.Viewer('cesiumContainer', {
|
||||
this.viewer = new Cesium.Viewer(this.$refs.rovCesiumContainer, {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
|
|
@ -367,8 +389,8 @@ export default {
|
|||
}
|
||||
},
|
||||
// 参潜单位年度
|
||||
gitHovUnitYear(params = {}) {
|
||||
hovUnitYear(params).then(res => {
|
||||
gitRovUnitYear(params = {}) {
|
||||
rovUnitYear(params).then(res => {
|
||||
const originalData = res.array
|
||||
const sortedData = originalData.sort((a, b) => a.voyage_year - b.voyage_year)
|
||||
this.barData2 = {
|
||||
|
|
@ -391,7 +413,7 @@ export default {
|
|||
},
|
||||
// 柱状图数据
|
||||
getChartBar(params) {
|
||||
platformHov(params).then(res => {
|
||||
platformRov(params).then(res => {
|
||||
const originalData = res.array
|
||||
const sortedData = originalData.sort((a, b) => a.year - b.year)
|
||||
this.barData1 = {
|
||||
|
|
@ -426,6 +448,9 @@ export default {
|
|||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
|
|
@ -434,12 +459,69 @@ export default {
|
|||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.titles {
|
||||
.banner-title-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
font-size: 56px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
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 {
|
||||
|
|
@ -523,8 +605,21 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,26 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="titles">{{ currentTitle }}<i class="el-icon-arrow-down el-icon--right" /></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<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"
|
||||
|
|
@ -14,6 +31,8 @@
|
|||
</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>
|
||||
|
|
@ -29,15 +48,19 @@
|
|||
</div>
|
||||
<div class="bezel">
|
||||
<div class="map-title">航次地图</div>
|
||||
<div class="map-year-panel">
|
||||
<span class="map-year-label">年份</span>
|
||||
<el-date-picker
|
||||
v-model="voyageYear"
|
||||
type="year"
|
||||
size="mini"
|
||||
placeholder="选择年"
|
||||
size="small"
|
||||
placeholder="全部"
|
||||
class="map-year"
|
||||
clearable
|
||||
format="yyyy"
|
||||
@change="selectVoyageYear"
|
||||
/>
|
||||
</div>
|
||||
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -51,6 +74,14 @@
|
|||
chart-type="single"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barDataDays.title"
|
||||
:chart-data="barDataDays.salesData"
|
||||
:categories="barDataDays.productCategories"
|
||||
chart-type="single"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barData2.title"
|
||||
|
|
@ -126,7 +157,7 @@ export default {
|
|||
{
|
||||
label: '累计里程',
|
||||
value: '',
|
||||
unit: 'KM'
|
||||
unit: 'km'
|
||||
}
|
||||
],
|
||||
// Cesium相关 - 参航单位地图
|
||||
|
|
@ -138,6 +169,8 @@ export default {
|
|||
trackLineLayers: [],
|
||||
voyageYear: '',
|
||||
barData1: {},
|
||||
/** 年度航次天数(与 barData1 同源接口按年) */
|
||||
barDataDays: {},
|
||||
barData2: {},
|
||||
barData3: {},
|
||||
barData4: {},
|
||||
|
|
@ -420,6 +453,11 @@ export default {
|
|||
salesData: sortedData.map(item => item.member_total),
|
||||
productCategories: sortedData.map(item => item.year.toString())
|
||||
}
|
||||
this.barDataDays = {
|
||||
title: '年度航次天数',
|
||||
salesData: sortedData.map(item => item.days_total),
|
||||
productCategories: sortedData.map(item => item.year.toString())
|
||||
}
|
||||
this.barData3 = {
|
||||
title: '样品集个数',
|
||||
salesData: sortedData.map(item => item.dataset_total),
|
||||
|
|
@ -442,6 +480,9 @@ export default {
|
|||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
|
|
@ -450,12 +491,69 @@ export default {
|
|||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.titles {
|
||||
.banner-title-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
font-size: 56px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
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 {
|
||||
|
|
@ -503,13 +601,78 @@ export default {
|
|||
z-index: 8888;
|
||||
font-size: 22px;
|
||||
}
|
||||
.map-year {
|
||||
|
||||
.map-year-panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
color: white;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 8888;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 10px 6px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.68);
|
||||
border: 1px solid rgba(255, 255, 255, 0.55);
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.14);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
border-color 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
transform 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border-color: rgba(255, 255, 255, 0.92);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.map-year-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgba(28, 42, 58, 0.78);
|
||||
letter-spacing: 0.04em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.map-year {
|
||||
width: 120px;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding-left: 30px;
|
||||
padding-right: 28px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 50, 90, 0.12);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1c2d3f;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner:hover,
|
||||
::v-deep .el-input__inner:focus {
|
||||
border-color: rgba(0, 90, 150, 0.42);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
box-shadow: 0 2px 8px rgba(0, 50, 100, 0.1);
|
||||
}
|
||||
|
||||
::v-deep .el-input__prefix {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
::v-deep .el-input__suffix {
|
||||
right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -539,8 +702,21 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,377 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<div class="banner-select-text banner-static-title">{{ currentTitle }}</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="echarts-module echarts-module--single">
|
||||
<div class="bezel bezel--single chart-panel-with-unit">
|
||||
<div class="chart-unit-toolbar">
|
||||
<span class="chart-unit-label">统计单位</span>
|
||||
<el-select
|
||||
v-model="currentUnitKey"
|
||||
class="chart-unit-select"
|
||||
filterable
|
||||
clearable
|
||||
size="small"
|
||||
placeholder="请选择单位"
|
||||
@change="onUnitChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="unit in unitOptions"
|
||||
:key="unitOptionKey(unit)"
|
||||
:label="unit.unit_name"
|
||||
:value="unitOptionKey(unit)"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<BarChart
|
||||
:title="memberMixChart.title"
|
||||
:chart-data="memberMixChart.series"
|
||||
:categories="memberMixChart.categories"
|
||||
chart-type="mixed"
|
||||
:dual-y-axis="true"
|
||||
left-axis-name="次数"
|
||||
right-axis-name="天数"
|
||||
:height="420"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BarChart from './components/BarChart.vue'
|
||||
import { hovUnit, unitMember } from '../../api/statistics'
|
||||
|
||||
/**
|
||||
* @param {*} v 原始值
|
||||
* @returns {number}
|
||||
*/
|
||||
function num(v) {
|
||||
if (v == null || v === '') return 0
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 深潜次数字段(接口可能为 drving_total 或 diving_total)。
|
||||
* @param {Object} row 行数据
|
||||
* @returns {number}
|
||||
*/
|
||||
function diveTotal(row) {
|
||||
if (!row || typeof row !== 'object') return 0
|
||||
const raw = row.drving_total != null ? row.drving_total : row.diving_total
|
||||
return num(raw)
|
||||
}
|
||||
|
||||
/**
|
||||
* 无路由 query.unit 时默认选中的统计单位名称(与 hovUnit 返回的 unit_name 一致)。
|
||||
* @type {string}
|
||||
*/
|
||||
const DEFAULT_STAT_UNIT_NAME = '中国科学院深海科学与工程研究所'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BarChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTitle: '参航科学家统计',
|
||||
unitOptions: [],
|
||||
/** 当前统计单位键(与 report/unit/member.htm 的 unit 参数一致) */
|
||||
currentUnitKey: '',
|
||||
numbers: [
|
||||
{
|
||||
label: '科学家人数',
|
||||
value: '',
|
||||
unit: '人'
|
||||
},
|
||||
{
|
||||
label: '累计参加航次',
|
||||
value: '',
|
||||
unit: '次'
|
||||
},
|
||||
{
|
||||
label: '累计参航天数',
|
||||
value: '',
|
||||
unit: '天'
|
||||
}
|
||||
],
|
||||
/**
|
||||
* 混合图:横轴科学家姓名;左轴次数(参航次数、深潜次数柱),右轴天数(参航天数折线)。
|
||||
* @type {{ title: string, categories: string[], series: Array<{ name: string, type: string, data: number[], yAxisIndex: number }> }}
|
||||
*/
|
||||
memberMixChart: {
|
||||
title: '参航科学家统计',
|
||||
categories: [],
|
||||
series: [
|
||||
{ name: '参航次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '深潜次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: [], yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.query.unit': {
|
||||
handler() {
|
||||
this.syncUnitFromRoute()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getHovUnit()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 下拉选项 value(优先 unit_id,否则 unit_name)。
|
||||
* @param {Object} unit 单位行
|
||||
* @returns {string|number}
|
||||
*/
|
||||
unitOptionKey(unit) {
|
||||
if (!unit) return ''
|
||||
return unit.unit_id != null && unit.unit_id !== '' ? unit.unit_id : unit.unit_name
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据路由 query.unit 同步当前选中单位。
|
||||
* @returns {void}
|
||||
*/
|
||||
syncUnitFromRoute() {
|
||||
const q = this.$route.query.unit
|
||||
if (q == null || q === '') return
|
||||
const match = (this.unitOptions || []).find(
|
||||
u => String(this.unitOptionKey(u)) === String(q) || u.unit_name === q
|
||||
)
|
||||
if (match) {
|
||||
this.currentUnitKey = this.unitOptionKey(match)
|
||||
this.loadUnitMemberData()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 单位变更时重新拉取科学家统计。
|
||||
* @returns {void}
|
||||
*/
|
||||
onUnitChange() {
|
||||
this.loadUnitMemberData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 调用参航科学家接口并刷新 Banner 与图表。
|
||||
* @returns {void}
|
||||
*/
|
||||
loadUnitMemberData() {
|
||||
if (this.currentUnitKey === '' || this.currentUnitKey == null) {
|
||||
this.resetMemberStats()
|
||||
return
|
||||
}
|
||||
unitMember({ unit: this.currentUnitKey })
|
||||
.then(res => {
|
||||
const list = res && Array.isArray(res.array) ? res.array : []
|
||||
this.applyMemberStats(list)
|
||||
})
|
||||
.catch(() => {
|
||||
this.resetMemberStats()
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 无单位或未选单位时的空态。
|
||||
* @returns {void}
|
||||
*/
|
||||
resetMemberStats() {
|
||||
this.numbers[0].value = ''
|
||||
this.numbers[1].value = ''
|
||||
this.numbers[2].value = ''
|
||||
this.memberMixChart = {
|
||||
title: '参航科学家统计',
|
||||
categories: [],
|
||||
series: [
|
||||
{ name: '参航次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '深潜次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: [], yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据 report/unit/member.htm 返回列表更新 Banner 与混合图。
|
||||
* @param {Object[]} list 科学家行列表(member_name、voyage_total、days_total、drving_total 等)
|
||||
* @returns {void}
|
||||
*/
|
||||
applyMemberStats(list) {
|
||||
const n = list.length
|
||||
const sumVoyage = list.reduce((s, m) => s + num(m.voyage_total), 0)
|
||||
const sumDays = list.reduce((s, m) => s + num(m.days_total), 0)
|
||||
this.numbers[0].value = n
|
||||
this.numbers[1].value = sumVoyage
|
||||
this.numbers[2].value = sumDays
|
||||
|
||||
const sorted = [...list].sort((a, b) => num(b.voyage_total) - num(a.voyage_total))
|
||||
const names = sorted.map(m => m.member_name || '—')
|
||||
const voyage = sorted.map(m => num(m.voyage_total))
|
||||
const days = sorted.map(m => num(m.days_total))
|
||||
const dives = sorted.map(m => diveTotal(m))
|
||||
|
||||
this.memberMixChart = {
|
||||
title: '参航科学家统计',
|
||||
categories: names,
|
||||
series: [
|
||||
{ name: '参航次数', type: 'bar', data: voyage, yAxisIndex: 0 },
|
||||
{ name: '深潜次数', type: 'bar', data: dives, yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: days, yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 参航单位列表(用于统计单位下拉)
|
||||
getHovUnit() {
|
||||
hovUnit().then(res => {
|
||||
const map = new Map()
|
||||
;(res.array || []).forEach(unit => {
|
||||
const k = unit.unit_id || unit.unitId || unit.unit_name
|
||||
if (!map.has(k)) map.set(k, unit)
|
||||
})
|
||||
this.unitOptions = Array.from(map.values())
|
||||
const q = this.$route.query.unit
|
||||
let initial = null
|
||||
if (q != null && q !== '') {
|
||||
initial = this.unitOptions.find(
|
||||
u => String(this.unitOptionKey(u)) === String(q) || u.unit_name === q
|
||||
)
|
||||
}
|
||||
if (!initial && this.unitOptions.length) {
|
||||
initial =
|
||||
this.unitOptions.find(u => u.unit_name === DEFAULT_STAT_UNIT_NAME) ||
|
||||
this.unitOptions[0]
|
||||
}
|
||||
this.currentUnitKey = initial ? this.unitOptionKey(initial) : ''
|
||||
this.loadUnitMemberData()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.statistics {
|
||||
background-color: #ebf1f7;
|
||||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.banner-static-title {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.echarts-module {
|
||||
padding: 50px 50px 50px;
|
||||
|
||||
&.echarts-module--single {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.bezel {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
padding: 12px 8px 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.bezel--single {
|
||||
min-height: 460px;
|
||||
}
|
||||
|
||||
&.chart-panel-with-unit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-unit-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: flex-end;
|
||||
flex-shrink: 0;
|
||||
gap: 10px;
|
||||
margin: 0 4px 6px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.chart-unit-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
letter-spacing: 0.04em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chart-unit-select {
|
||||
width: #{"min(56vw, 360px)"};
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -0,0 +1,881 @@
|
|||
<template>
|
||||
<div class="statistics">
|
||||
<div class="banner">
|
||||
<div class="banner-select-text banner-static-title">{{ currentTitle }}</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="unit-toolbar">
|
||||
<div class="unit-toolbar-inner">
|
||||
<div class="unit-filter-box">
|
||||
<span class="banner-unit-label">统计单位</span>
|
||||
<el-select
|
||||
v-model="selectedUnits"
|
||||
class="banner-unit-select"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
size="small"
|
||||
placeholder="请选择单位(不选表示全部)"
|
||||
value-key="unit_name"
|
||||
collapse-tags
|
||||
@change="onBannerUnitsChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="unit in unitOptions"
|
||||
:key="unit.unit_id || unit.unit_name"
|
||||
:label="unit.unit_name"
|
||||
:value="unit"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 地图板块-->
|
||||
<div class="map-module">
|
||||
<div class="bezel">
|
||||
<div class="map-title">参航单位</div>
|
||||
<div id="baiduCesiumContainer" ref="baiduCesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<div class="map-title">航次地图</div>
|
||||
<div class="map-year-panel">
|
||||
<span class="map-year-label">年份</span>
|
||||
<el-date-picker
|
||||
v-model="voyageYear"
|
||||
type="year"
|
||||
size="small"
|
||||
placeholder="全部"
|
||||
class="map-year"
|
||||
clearable
|
||||
format="yyyy"
|
||||
@change="selectVoyageYear"
|
||||
/>
|
||||
</div>
|
||||
<div id="cesiumContainer" ref="cesiumContainer" style="height: 100%;width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- echarts板块-->
|
||||
<div class="echarts-module">
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barVoyageCount.title"
|
||||
:chart-data="barVoyageCount.salesData"
|
||||
:categories="barVoyageCount.productCategories"
|
||||
chart-type="single"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barVoyageDays.title"
|
||||
:chart-data="barVoyageDays.salesData"
|
||||
:categories="barVoyageDays.productCategories"
|
||||
chart-type="single"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barPeopleDiving.title"
|
||||
:chart-data="barPeopleDiving.salesData"
|
||||
:categories="barPeopleDiving.productCategories"
|
||||
chart-type="multiple"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="barFirstPeopleDiving.title"
|
||||
:chart-data="barFirstPeopleDiving.salesData"
|
||||
:categories="barFirstPeopleDiving.productCategories"
|
||||
chart-type="multiple"
|
||||
/>
|
||||
</div>
|
||||
<div class="bezel">
|
||||
<BarChart
|
||||
:title="mixedMileage.title"
|
||||
:chart-data="mixedMileage.series"
|
||||
:categories="mixedMileage.categories"
|
||||
chart-type="mixed"
|
||||
:dual-y-axis="true"
|
||||
left-axis-name="次数"
|
||||
right-axis-name="天数"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BarChart from './components/BarChart.vue'
|
||||
import { setImageryViewModels } from '../../utils/common'
|
||||
import { unitTotal, hovUnit, hovDiving } from '../../api/statistics'
|
||||
import { getYearRange } from '../../utils/index'
|
||||
const Cesium = window.Cesium
|
||||
|
||||
/**
|
||||
* @param {*} v 原始值
|
||||
* @returns {number}
|
||||
*/
|
||||
function num(v) {
|
||||
if (v == null || v === '') return 0
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BarChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTitle: '参航单位统计',
|
||||
voyageYear: '',
|
||||
unitOptions: [],
|
||||
/** 与 Banner 多选绑定:筛选单位汇总图表与航次地图轨迹(空表示全部) */
|
||||
selectedUnits: [],
|
||||
/** unitTotal 全量缓存,便于按 Banner 选择做前端过滤 */
|
||||
unitTotalRawList: [],
|
||||
// 缓存 hovDiving 返回数据,便于多选单位时仅做前端过滤与重绘
|
||||
hovDivingItems: [],
|
||||
numbers: [
|
||||
{
|
||||
label: '参航单位数量',
|
||||
value: '',
|
||||
unit: '家'
|
||||
},
|
||||
{
|
||||
label: '参航人数',
|
||||
value: '',
|
||||
unit: '人'
|
||||
},
|
||||
{
|
||||
label: '深潜次数',
|
||||
value: '',
|
||||
unit: '次'
|
||||
}
|
||||
],
|
||||
// Cesium相关 - 参航单位地图
|
||||
baiduCesiumViewer: null,
|
||||
baiduCesiumEntities: [], // 存储参航单位标记实体
|
||||
viewer: null,
|
||||
// 地图图层基础地址
|
||||
CesiumHostAddr: `${window.location.protocol}//${window.location.hostname}:${window.location.port}/`,
|
||||
trackLineLayers: [],
|
||||
/** 航行次数(柱状) */
|
||||
barVoyageCount: { title: '航行次数', salesData: [], productCategories: [] },
|
||||
/** 参航天数(柱状) */
|
||||
barVoyageDays: { title: '参航天数', salesData: [], productCategories: [] },
|
||||
/** 参航人员数、深潜次数(柱状,多系列) */
|
||||
barPeopleDiving: {
|
||||
title: '参航人员数、深潜次数',
|
||||
salesData: [
|
||||
{ name: '参航人员数', type: 'bar', data: [] },
|
||||
{ name: '深潜次数', type: 'bar', data: [] }
|
||||
],
|
||||
productCategories: []
|
||||
},
|
||||
/** 首次参航、首次深潜(柱状,多系列) */
|
||||
barFirstPeopleDiving: {
|
||||
title: '首次参航人数、首次深潜人数',
|
||||
salesData: [
|
||||
{ name: '首次参航人数', type: 'bar', data: [] },
|
||||
{ name: '首次深潜人数', type: 'bar', data: [] }
|
||||
],
|
||||
productCategories: []
|
||||
},
|
||||
/** 航行次数(柱)+ 参航天数(折) */
|
||||
mixedMileage: {
|
||||
title: '航行次数与参航天数',
|
||||
categories: [],
|
||||
series: [
|
||||
{ name: '航行次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: [], yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadUnitTotalFromApi()
|
||||
this.getHovUnit()
|
||||
this.gitHovDiving()
|
||||
},
|
||||
mounted() {
|
||||
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
|
||||
this.initCesium() // 初始化航次地图
|
||||
this.initBaiduCesium() // 初始化参航单位地图
|
||||
this.refreshTrackLines()
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理Cesium资源
|
||||
if (this.baiduCesiumViewer) {
|
||||
this.baiduCesiumViewer.destroy()
|
||||
}
|
||||
if (this.viewer) {
|
||||
this.viewer.destroy()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 刷新航次地图轨迹图层(按单位多选进行前端过滤)。
|
||||
* @returns {void}
|
||||
*/
|
||||
refreshTrackLines() {
|
||||
if (!this.viewer) return
|
||||
this.removeSingleLineImg()
|
||||
|
||||
const selectedNameSet = new Set(
|
||||
(this.selectedUnits || [])
|
||||
.map(u => u && u.unit_name)
|
||||
.filter(Boolean)
|
||||
)
|
||||
const selectedIdSet = new Set(
|
||||
(this.selectedUnits || [])
|
||||
.map(u => (u && (u.unit_id || u.unitId)) || null)
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const unitFilterEnabled = selectedNameSet.size > 0 || selectedIdSet.size > 0
|
||||
const itemsToShow = unitFilterEnabled
|
||||
? (this.hovDivingItems || []).filter(item => {
|
||||
const itemName = item.unit_name || item.unitName || item.unit_name_cn || null
|
||||
const itemId = item.unit_id || item.unitId || item.unit_code || null
|
||||
return selectedNameSet.has(itemName) || selectedIdSet.has(itemId)
|
||||
})
|
||||
: (this.hovDivingItems || [])
|
||||
|
||||
itemsToShow.forEach(item => {
|
||||
this.addSingleLineImg(item)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Banner 统计单位多选变更:刷新汇总图表与航次地图轨迹。
|
||||
* @returns {void}
|
||||
*/
|
||||
onBannerUnitsChange() {
|
||||
this.applyFilteredUnitTotal()
|
||||
this.refreshTrackLines()
|
||||
},
|
||||
|
||||
/**
|
||||
* 按 Banner 选中单位过滤 unitTotal 行,再写入 Banner 与图表。
|
||||
* @returns {void}
|
||||
*/
|
||||
applyFilteredUnitTotal() {
|
||||
const raw = this.unitTotalRawList || []
|
||||
if (!raw.length) {
|
||||
this.applyUnitTotalRows([])
|
||||
return
|
||||
}
|
||||
const sel = this.selectedUnits || []
|
||||
if (!sel.length) {
|
||||
this.applyUnitTotalRows(raw)
|
||||
return
|
||||
}
|
||||
const nameSet = new Set(sel.map(u => u && u.unit_name).filter(Boolean))
|
||||
const filtered = raw.filter(row => nameSet.has(row.unit_name))
|
||||
this.applyUnitTotalRows(filtered)
|
||||
},
|
||||
|
||||
/**
|
||||
* 拉取「单位汇总」报表(/report/unit/total.htm),驱动 Banner 与各柱状图。
|
||||
* @returns {void}
|
||||
*/
|
||||
loadUnitTotalFromApi() {
|
||||
unitTotal({})
|
||||
.then(res => {
|
||||
const list = res && Array.isArray(res.array) ? res.array : []
|
||||
this.unitTotalRawList = list
|
||||
this.applyFilteredUnitTotal()
|
||||
})
|
||||
.catch(() => {
|
||||
this.unitTotalRawList = []
|
||||
this.applyUnitTotalRows([])
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 将 unitTotal 返回的按单位行写入 Banner 与图表。
|
||||
* @param {Object[]} list 接口 array
|
||||
* @returns {void}
|
||||
*/
|
||||
applyUnitTotalRows(list) {
|
||||
if (!list.length) {
|
||||
this.numbers[0].value = ''
|
||||
this.numbers[1].value = ''
|
||||
this.numbers[2].value = ''
|
||||
this.barVoyageCount = { title: '航行次数', salesData: [], productCategories: [] }
|
||||
this.barVoyageDays = { title: '参航天数', salesData: [], productCategories: [] }
|
||||
this.barPeopleDiving = {
|
||||
title: '参航人员数、深潜次数',
|
||||
salesData: [
|
||||
{ name: '参航人员数', type: 'bar', data: [] },
|
||||
{ name: '深潜次数', type: 'bar', data: [] }
|
||||
],
|
||||
productCategories: []
|
||||
}
|
||||
this.barFirstPeopleDiving = {
|
||||
title: '首次参航人数、首次深潜人数',
|
||||
salesData: [
|
||||
{ name: '首次参航人数', type: 'bar', data: [] },
|
||||
{ name: '首次深潜人数', type: 'bar', data: [] }
|
||||
],
|
||||
productCategories: []
|
||||
}
|
||||
this.mixedMileage = {
|
||||
title: '航行次数与参航天数',
|
||||
categories: [],
|
||||
series: [
|
||||
{ name: '航行次数', type: 'bar', data: [], yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: [], yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const sorted = [...list].sort((a, b) => num(b.voyage_total) - num(a.voyage_total))
|
||||
const names = sorted.map(r => r.unit_name || '—')
|
||||
const voyage = sorted.map(r => num(r.voyage_total))
|
||||
const days = sorted.map(r => num(r.voyage_days))
|
||||
const member = sorted.map(r => num(r.member_total))
|
||||
const drving = sorted.map(r => num(r.drving_total))
|
||||
const fVoyage = sorted.map(r => num(r.f_voyage_total))
|
||||
const fDrving = sorted.map(r => num(r.f_drving_total))
|
||||
|
||||
this.numbers[0].value = sorted.length
|
||||
this.numbers[1].value = sorted.reduce((s, r) => s + num(r.member_total), 0)
|
||||
this.numbers[2].value = sorted.reduce((s, r) => s + num(r.drving_total), 0)
|
||||
|
||||
this.barVoyageCount = {
|
||||
title: '航行次数',
|
||||
salesData: voyage,
|
||||
productCategories: names
|
||||
}
|
||||
this.barVoyageDays = {
|
||||
title: '参航天数',
|
||||
salesData: days,
|
||||
productCategories: names
|
||||
}
|
||||
this.barPeopleDiving = {
|
||||
title: '参航人员数、深潜次数',
|
||||
salesData: [
|
||||
{ name: '参航人员数', type: 'bar', data: member },
|
||||
{ name: '深潜次数', type: 'bar', data: drving }
|
||||
],
|
||||
productCategories: names
|
||||
}
|
||||
this.barFirstPeopleDiving = {
|
||||
title: '首次参航人数、首次深潜人数',
|
||||
salesData: [
|
||||
{ name: '首次参航人数', type: 'bar', data: fVoyage },
|
||||
{ name: '首次深潜人数', type: 'bar', data: fDrving }
|
||||
],
|
||||
productCategories: names
|
||||
}
|
||||
this.mixedMileage = {
|
||||
title: '航行次数与参航天数',
|
||||
categories: names,
|
||||
series: [
|
||||
{ name: '航行次数', type: 'bar', data: voyage, yAxisIndex: 0 },
|
||||
{ name: '参航天数', type: 'line', data: days, yAxisIndex: 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
// 下潜单位统计
|
||||
getHovUnit() {
|
||||
hovUnit().then(res => {
|
||||
// 初始化单位下拉选项(去重)
|
||||
const map = new Map()
|
||||
;(res.array || []).forEach(unit => {
|
||||
const k = unit.unit_id || unit.unitId || unit.unit_name
|
||||
if (!map.has(k)) map.set(k, unit)
|
||||
})
|
||||
this.unitOptions = Array.from(map.values())
|
||||
|
||||
// 清除之前的标记
|
||||
this.clearBaiduCesiumMarkers()
|
||||
|
||||
// 创建参航单位标记
|
||||
this.createBaiduCesiumMarkers(res.array || [])
|
||||
})
|
||||
},
|
||||
// 创建参航单位Cesium标记点
|
||||
createBaiduCesiumMarkers(units) {
|
||||
if (!this.baiduCesiumViewer) return
|
||||
|
||||
const entities = []
|
||||
|
||||
units.forEach((item, index) => {
|
||||
// 创建标记实体
|
||||
const entity = this.baiduCesiumViewer.entities.add({
|
||||
position: Cesium.Cartesian3.fromDegrees(item.unit_lon, item.unit_lat),
|
||||
billboard: {
|
||||
image: '../../../static/img/01.png', // 正常状态的图标
|
||||
width: 32,
|
||||
height: 32,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
horizontalOrigin: Cesium.HorizontalOrigin.CENTER
|
||||
},
|
||||
label: {
|
||||
text: item.unit_name,
|
||||
font: '14pt monospace',
|
||||
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
||||
outlineWidth: 2,
|
||||
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
||||
pixelOffset: new Cesium.Cartesian2(0, -40), // 标签在标记下方
|
||||
fillColor: Cesium.Color.WHITE,
|
||||
outlineColor: Cesium.Color.BLACK
|
||||
},
|
||||
unitData: item, // 存储原始数据
|
||||
index: index // 存储索引
|
||||
})
|
||||
entities.push(entity)
|
||||
})
|
||||
|
||||
this.baiduCesiumEntities = entities
|
||||
|
||||
// 调整视角以显示所有标记
|
||||
if (entities.length > 0) {
|
||||
this.baiduCesiumViewer.zoomTo(entities)
|
||||
}
|
||||
},
|
||||
// 清除参航单位标记
|
||||
clearBaiduCesiumMarkers() {
|
||||
if (this.baiduCesiumViewer && this.baiduCesiumEntities.length > 0) {
|
||||
this.baiduCesiumEntities.forEach(entity => {
|
||||
this.baiduCesiumViewer.entities.remove(entity)
|
||||
})
|
||||
this.baiduCesiumEntities = []
|
||||
}
|
||||
},
|
||||
// 潜次统计
|
||||
gitHovDiving(params = {}) {
|
||||
hovDiving(params).then(res => {
|
||||
this.hovDivingItems = res.array || []
|
||||
this.refreshTrackLines()
|
||||
})
|
||||
},
|
||||
// 初始化Cesium - 参航单位地图
|
||||
initBaiduCesium() {
|
||||
this.baiduCesiumViewer = new Cesium.Viewer(this.$refs.baiduCesiumContainer, {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
baseLayerPicker: false,
|
||||
imageryProviderViewModels: this.imageryViewModels,
|
||||
terrainProviderViewModels: [],
|
||||
fullscreenButton: false,
|
||||
selectionIndicator: false,
|
||||
homeButton: false,
|
||||
sceneModePicker: false,
|
||||
animation: false,
|
||||
infoBox: false,
|
||||
geocoder: false,
|
||||
navigationHelpButton: false
|
||||
})
|
||||
this.baiduCesiumViewer._cesiumWidget._creditContainer.style.display = 'none'
|
||||
this.baiduCesiumViewer.scene.sun.show = false
|
||||
this.baiduCesiumViewer.scene.moon.show = false
|
||||
this.baiduCesiumViewer.scene.fog.enabled = false
|
||||
this.baiduCesiumViewer.scene.skyAtmosphere.show = false
|
||||
this.baiduCesiumViewer.scene.sun.show = false
|
||||
this.baiduCesiumViewer.scene.skyBox.show = false
|
||||
this.baiduCesiumViewer.scene.globe.enableLighting = false
|
||||
this.baiduCesiumViewer.shadowMap.darkness = 0.8
|
||||
this.baiduCesiumViewer.scene._sunBloom = false
|
||||
this.baiduCesiumViewer.scene.globe.showGroundAtmosphere = false
|
||||
this.baiduCesiumViewer.scene.postProcessStages.fxaa.enabled = true
|
||||
|
||||
this.baiduCesiumViewer.camera.setView({
|
||||
destination: Cesium.Cartesian3.fromDegrees(114.4040, 30.5196, 4000000)
|
||||
})
|
||||
},
|
||||
// 初始化cesium
|
||||
initCesium() {
|
||||
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||
this.viewer = new Cesium.Viewer('cesiumContainer', {
|
||||
sceneMode: Cesium.SceneMode.SCENE2D,
|
||||
shadows: false,
|
||||
timeline: false,
|
||||
baseLayerPicker: false,
|
||||
// 设置自定义底图
|
||||
imageryProviderViewModels: this.imageryViewModels,
|
||||
// 关闭默认的地形底图
|
||||
terrainProviderViewModels: [],
|
||||
fullscreenButton: false,
|
||||
selectionIndicator: false,
|
||||
homeButton: false,
|
||||
sceneModePicker: false,
|
||||
animation: false,
|
||||
infoBox: false,
|
||||
geocoder: false,
|
||||
navigationHelpButton: false
|
||||
})
|
||||
this.viewer._cesiumWidget._creditContainer.style.display = 'none'
|
||||
this.viewer.scene.sun.show = false
|
||||
this.viewer.scene.moon.show = false
|
||||
this.viewer.scene.fog.enabled = false
|
||||
this.viewer.scene.skyAtmosphere.show = false
|
||||
this.viewer.scene.sun.show = false
|
||||
this.viewer.scene.skyBox.show = false
|
||||
this.viewer.scene.globe.enableLighting = false
|
||||
this.viewer.shadowMap.darkness = 0.8
|
||||
this.viewer.scene._sunBloom = false
|
||||
this.viewer.scene.globe.showGroundAtmosphere = false
|
||||
this.viewer.scene.postProcessStages.fxaa.enabled = true
|
||||
|
||||
this.viewer.camera.setView({
|
||||
// 指定相机初始位置
|
||||
destination: Cesium.Cartesian3.fromDegrees(112, 18, 4000000)
|
||||
})
|
||||
},
|
||||
// 添加轨迹图层
|
||||
addSingleLineImg(item) {
|
||||
const trackLine = new Cesium.WebMapTileServiceImageryProvider(
|
||||
{
|
||||
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${item.diving_sn}/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
|
||||
layer: `dsds:${item.diving_sn}`,
|
||||
style: 'default',
|
||||
format: 'image/png',
|
||||
tileMatrixSetID: 'EPSG:4326',
|
||||
tilingScheme: new Cesium.GeographicTilingScheme()
|
||||
}
|
||||
)
|
||||
const trackLineLayer = this.viewer.imageryLayers.addImageryProvider(trackLine)
|
||||
this.trackLineLayers.push(trackLineLayer)
|
||||
},
|
||||
// 移除轨迹图层
|
||||
removeSingleLineImg() {
|
||||
if (this.viewer && this.trackLineLayers.length) {
|
||||
this.trackLineLayers.forEach(line => {
|
||||
this.viewer.imageryLayers.remove(line)
|
||||
})
|
||||
}
|
||||
// 清空引用,避免重复 remove 或无限增长
|
||||
this.trackLineLayers = []
|
||||
},
|
||||
// 选择航次地图年份
|
||||
selectVoyageYear(val) {
|
||||
this.removeSingleLineImg()
|
||||
if (val) {
|
||||
const params = getYearRange(val)
|
||||
this.gitHovDiving({ ...params })
|
||||
} else {
|
||||
this.gitHovDiving({})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.statistics {
|
||||
background-color: #ebf1f7;
|
||||
|
||||
.banner {
|
||||
background-image: url(../../../static/images/bannerny1.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.banner-topic {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 18px;
|
||||
text-shadow: 0 6px 22px rgba(0, 0, 0, 0.25);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.banner-type-dropdown {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.banner-static-title {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.banner-select-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 24px 14px 28px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.88);
|
||||
border-radius: 12px;
|
||||
background: rgba(8, 40, 72, 0.42);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.14);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
border-color 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
transform 0.25s ease;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
background: rgba(12, 55, 95, 0.58);
|
||||
border-color: rgba(255, 255, 255, 0.98);
|
||||
transform: translateY(-3px);
|
||||
box-shadow:
|
||||
0 12px 36px rgba(0, 0, 0, 0.38),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 3px solid rgba(255, 255, 255, 0.85);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
opacity: 0.95;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Banner 下方:统计单位筛选条,整行容器 + 右侧对齐独立框 */
|
||||
.unit-toolbar {
|
||||
width: 100%;
|
||||
padding: 8px 50px 8px;
|
||||
box-sizing: border-box;
|
||||
background-color: #ebf1f7;
|
||||
|
||||
.unit-toolbar-inner {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.unit-filter-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(0, 50, 90, 0.12);
|
||||
box-shadow: 0 2px 10px rgba(0, 40, 80, 0.06);
|
||||
}
|
||||
|
||||
.banner-unit-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
color: rgba(28, 45, 63, 0.88);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.banner-unit-select {
|
||||
width: #{"min(72vw, 520px)"};
|
||||
min-width: 200px;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
min-height: 34px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 50, 90, 0.14);
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1c2d3f;
|
||||
}
|
||||
|
||||
::v-deep .el-select__tags {
|
||||
max-width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map-module {
|
||||
padding: 20px 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-panel {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 8888;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 6px 10px 6px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.68);
|
||||
border: 1px solid rgba(255, 255, 255, 0.55);
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.14);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
border-color 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
transform 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border-color: rgba(255, 255, 255, 0.92);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.map-year-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgba(28, 42, 58, 0.78);
|
||||
letter-spacing: 0.04em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.map-year {
|
||||
width: 120px;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding-left: 30px;
|
||||
padding-right: 28px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 50, 90, 0.12);
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1c2d3f;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
::v-deep .el-input__inner:hover,
|
||||
::v-deep .el-input__inner:focus {
|
||||
border-color: rgba(0, 90, 150, 0.42);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
box-shadow: 0 2px 8px rgba(0, 50, 100, 0.1);
|
||||
}
|
||||
|
||||
::v-deep .el-input__prefix {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
::v-deep .el-input__suffix {
|
||||
right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div ref="chartContainer" style="width: 100%; height: 300px;"></div>
|
||||
<div
|
||||
ref="chartContainer"
|
||||
class="bar-chart-host"
|
||||
:style="chartHostStyle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -34,6 +38,21 @@ export default {
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 双 Y 轴时左侧轴名称 */
|
||||
leftAxisName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 双 Y 轴时右侧轴名称 */
|
||||
rightAxisName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 图表区域高度(像素) */
|
||||
height: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
// 新增:堆叠组名称(用于stacked模式)
|
||||
stackName: {
|
||||
type: String,
|
||||
|
|
@ -45,6 +64,12 @@ export default {
|
|||
myChart: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
chartHostStyle() {
|
||||
const h = typeof this.height === 'number' ? `${this.height}px` : '300px';
|
||||
return { width: '100%', height: h };
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart();
|
||||
},
|
||||
|
|
@ -99,7 +124,10 @@ export default {
|
|||
name: series.name || (series.type === 'line' ? '折线' : '柱状') + (index + 1),
|
||||
type: series.type || (index === 0 ? 'bar' : 'line'),
|
||||
data: series.data || [],
|
||||
yAxisIndex: series.yAxisIndex || (this.dualYAxis && index > 0 ? 1 : 0)
|
||||
yAxisIndex:
|
||||
series.yAxisIndex !== undefined && series.yAxisIndex !== null
|
||||
? series.yAxisIndex
|
||||
: (this.dualYAxis && index > 0 ? 1 : 0)
|
||||
}));
|
||||
}
|
||||
} else if (this.chartType === 'stacked') {
|
||||
|
|
@ -162,7 +190,7 @@ export default {
|
|||
return [
|
||||
{
|
||||
type: 'value',
|
||||
name: '左轴',
|
||||
name: this.leftAxisName || '左轴',
|
||||
position: 'left',
|
||||
axisLine: {
|
||||
show: true,
|
||||
|
|
@ -180,7 +208,7 @@ export default {
|
|||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '右轴',
|
||||
name: this.rightAxisName || '右轴',
|
||||
position: 'right',
|
||||
axisLine: {
|
||||
show: true,
|
||||
|
|
@ -201,6 +229,13 @@ export default {
|
|||
|
||||
// 获取系列配置
|
||||
getSeriesConfig(processedSeries) {
|
||||
const firstBarIndex = processedSeries.findIndex(s => s.type === 'bar');
|
||||
const barSeriesCount = processedSeries.filter(s => s.type === 'bar').length;
|
||||
const tightBarGroup =
|
||||
this.chartType !== 'stacked' &&
|
||||
barSeriesCount >= 2 &&
|
||||
(this.chartType === 'mixed' || this.chartType === 'multiple');
|
||||
|
||||
return processedSeries.map((series, index) => {
|
||||
const isLine = series.type === 'line';
|
||||
const isStacked = this.chartType === 'stacked';
|
||||
|
|
@ -252,6 +287,11 @@ export default {
|
|||
barConfig.itemStyle.borderRadius = [2, 2, 0, 0]; // 顶部圆角
|
||||
}
|
||||
|
||||
// 同一类目下多根柱(如混合图双柱):柱间距为 0,使两柱紧贴(barGap 写在首个 bar 系列上)
|
||||
if (tightBarGroup && index === firstBarIndex) {
|
||||
barConfig.barGap = '0%';
|
||||
}
|
||||
|
||||
return barConfig;
|
||||
}
|
||||
});
|
||||
|
|
@ -425,6 +465,23 @@ export default {
|
|||
this.renderChart();
|
||||
}
|
||||
},
|
||||
leftAxisName() {
|
||||
if (this.myChart) {
|
||||
this.renderChart();
|
||||
}
|
||||
},
|
||||
rightAxisName() {
|
||||
if (this.myChart) {
|
||||
this.renderChart();
|
||||
}
|
||||
},
|
||||
height() {
|
||||
this.$nextTick(() => {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize();
|
||||
}
|
||||
});
|
||||
},
|
||||
stackName() {
|
||||
if (this.myChart && this.chartType === 'stacked') {
|
||||
this.renderChart();
|
||||
|
|
|
|||
|
|
@ -9,96 +9,224 @@
|
|||
<!-- 科考船汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
class="stat-card stat-card--nav stat-card--ship"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('ships')"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-ship"></i>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 18h18v2H3v-2zm1-2h16l-2-9H6l-2 9z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
<path d="M8 7V5a2 2 0 012-2h4a2 2 0 012 2v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">科考船汇总统计</h3>
|
||||
<p class="card-count">{{ ship.length }}艘</p>
|
||||
<p class="card-desc"><span v-for="(item, index) in ship" :key="index">{{ item.report_name }}<span v-if="index !== ship.length - 1">、</span></span></p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">科考船汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ ship.length }} 艘在册</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in shipMetrics" :key="'s'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="shipNamesJoined">{{ shipNamesJoined }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 载人潜水器汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
class="stat-card stat-card--nav stat-card--hov"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('hov')"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-watermelon"></i>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="12" cy="12" rx="7" ry="4" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M5 12h14M12 8v8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
<circle cx="12" cy="12" r="2" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">载人潜水器汇总统计</h3>
|
||||
<p class="card-count">{{ hov.length }}艘</p>
|
||||
<p class="card-desc"><span v-for="(item, index) in hov" :key="index">{{ item.report_name }}<span v-if="index !== hov.length - 1">、</span></span> </p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">载人潜水器汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ hov.length }} 艘在册</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in platformMetrics(hovSummary)" :key="'h'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="hovNamesJoined">{{ hovNamesJoined }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 遥控潜水器汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
class="stat-card stat-card--nav stat-card--rov"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('rov')"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-cpu"></i>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="5" y="9" width="14" height="10" rx="2" stroke="currentColor" stroke-width="1.5" />
|
||||
<path d="M9 14h6M12 11v6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
<path d="M19 13v4M3 13v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">遥控潜水器汇总统计</h3>
|
||||
<p class="card-count">{{ rov.length }}艘</p>
|
||||
<p class="card-desc"><span v-for="(item, index) in rov" :key="index">{{ item.report_name }}<span v-if="index !== rov.length - 1">、</span></span> </p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">遥控潜水器汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ rov.length }} 艘在册</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in platformMetrics(rovSummary)" :key="'r'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="rovNamesJoined">{{ rovNamesJoined }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 无人航行潜水器汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
@click.native="goToDetail('uuv')"
|
||||
class="stat-card stat-card--nav stat-card--uuv"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('auv')"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-help"></i>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 12c2-4 6-6 8-6s6 2 8 6c-2 4-6 6-8 6s-6-2-8-6z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
<circle cx="12" cy="12" r="2" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">无人航行潜水器汇总统计</h3>
|
||||
<p class="card-count">{{ uuv.length }}艘</p>
|
||||
<p class="card-desc"><span v-for="(item, index) in uuv" :key="index">{{ item.report_name }}<span v-if="index !== uuv.length - 1">、</span></span> </p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">无人航行潜水器汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ uuv.length }} 艘在册</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in platformMetrics(uuvSummary)" :key="'u'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="uuvNamesJoined">{{ uuvNamesJoined }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 参航单位统计(无人航行潜水器) -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card stat-card--nav stat-card--uuv-unit"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('uuvUnit')"
|
||||
>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 8l9-5 9 5-9 5-9-5z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
<path d="M3 8v8l9 5 9-5V8" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">参航单位统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ uuvUnitBadge }} 家</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in uuvUnitCardMetrics" :key="'uu'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="uuvNamesJoined">单位汇总与参航单位详情页一致 · 点击查看</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 参行科学家统计(无人航行潜水器) -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card stat-card--nav stat-card--uuv-scientist"
|
||||
shadow="never"
|
||||
@click.native="goToDetail('uuvMember')"
|
||||
>
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 21h6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
<path d="M10 17l-2-2 2-6h4l2 6-2 2" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
<path d="M9 9V7a3 3 0 016 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">参航科学家统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge">{{ uuvScientistBadge }} 人</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
|
||||
<div v-for="(m, idx) in uuvScientistMetrics" :key="'ms'+idx" class="stat-metric">
|
||||
<span class="stat-metric__value">{{ m.display }}</span>
|
||||
<span class="stat-metric__label">{{ m.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="stat-card__fleet" :title="uuvNamesJoined">默认单位:中国科学院深海科学与工程研究所 · 点击查看详情</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 基站汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-office-building"></i>
|
||||
<el-card class="stat-card stat-card--placeholder stat-card--station" shadow="never">
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V7l8-4z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
|
||||
<path d="M12 8v5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">基站汇总统计</h3>
|
||||
<p class="card-count no-data">暂无数据</p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">基站汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge stat-card__badge--muted">敬请期待</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-placeholder">
|
||||
<span class="stat-placeholder__text">暂无数据</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
@ -106,18 +234,24 @@
|
|||
|
||||
<!-- 着陆器汇总统计 -->
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card
|
||||
class="stat-card"
|
||||
shadow="hover"
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="card-icon">
|
||||
<i class="el-icon-place"></i>
|
||||
<el-card class="stat-card stat-card--placeholder stat-card--lander" shadow="never">
|
||||
<div class="stat-card__inner">
|
||||
<div class="stat-card__top">
|
||||
<div class="stat-card__icon-wrap" aria-hidden="true">
|
||||
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 21s7-4.5 7-11a7 7 0 10-14 0c0 6.5 7 11 7 11z" stroke="currentColor" stroke-width="1.5" />
|
||||
<circle cx="12" cy="10" r="2.5" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<h3 class="card-title">着陆器汇总统计</h3>
|
||||
<p class="card-count no-data">暂无数据</p>
|
||||
<p class="card-desc"></p>
|
||||
<div class="stat-card__head-text">
|
||||
<h3 class="stat-card__title">着陆器汇总统计</h3>
|
||||
<p class="stat-card__meta">
|
||||
<span class="stat-card__badge stat-card__badge--muted">敬请期待</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-placeholder">
|
||||
<span class="stat-placeholder__text">暂无数据</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
@ -127,9 +261,56 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { reportList } from '../../api/statistics'
|
||||
import {
|
||||
reportList,
|
||||
shipTotal,
|
||||
platformHov,
|
||||
platformRov,
|
||||
platformAuv,
|
||||
hovUnit,
|
||||
unitMember,
|
||||
unitTotal
|
||||
} from '../../api/statistics'
|
||||
import { filterDictItems } from '../../utils/index'
|
||||
|
||||
/**
|
||||
* 与 UuvMemberStatistics 默认统计单位一致
|
||||
* @type {string}
|
||||
*/
|
||||
const DEFAULT_MEMBER_STAT_UNIT_NAME = '中国科学院深海科学与工程研究所'
|
||||
|
||||
/**
|
||||
* @param {*} v 原始值
|
||||
* @returns {number}
|
||||
*/
|
||||
function num(v) {
|
||||
if (v == null || v === '') return 0
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 unitTotal 按单位行聚合为卡片用汇总(与 UuvUnitStatistics 全量口径一致)。
|
||||
* @param {Object[]} list - report/unit/total.htm 的 array
|
||||
* @returns {{ unitCount: number, voyageSum: number, daysSum: number, memberSum: number, drvingSum: number }|null}
|
||||
*/
|
||||
function aggregateUnitTotalForCard(list) {
|
||||
if (!Array.isArray(list) || !list.length) return null
|
||||
return list.reduce(
|
||||
(acc, row) => ({
|
||||
unitCount: acc.unitCount + 1,
|
||||
voyageSum: acc.voyageSum + num(row.voyage_total),
|
||||
daysSum: acc.daysSum + num(row.voyage_days),
|
||||
memberSum: acc.memberSum + num(row.member_total),
|
||||
drvingSum: acc.drvingSum + num(row.drving_total)
|
||||
}),
|
||||
{ unitCount: 0, voyageSum: 0, daysSum: 0, memberSum: 0, drvingSum: 0 }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计分析入口:展示各平台类型入口卡片及与子页一致的汇总指标
|
||||
*/
|
||||
export default {
|
||||
name: 'MainDashboard',
|
||||
data() {
|
||||
|
|
@ -137,15 +318,125 @@ export default {
|
|||
ship: [],
|
||||
hov: [],
|
||||
rov: [],
|
||||
uuv: []
|
||||
uuv: [],
|
||||
/** 科考船汇总(与 ShipStatistics 顶部 banner 同源) */
|
||||
shipSummary: null,
|
||||
/** 载人潜水器汇总(与 HovStatistics 同源接口) */
|
||||
hovSummary: null,
|
||||
/** 遥控潜水器汇总 */
|
||||
rovSummary: null,
|
||||
/** 无人航行潜水器汇总 */
|
||||
uuvSummary: null,
|
||||
/** 参航科学家卡片:unitMember 聚合(默认单位) */
|
||||
memberScientistCard: null,
|
||||
/** 参航单位统计卡片:unitTotal 全表聚合(与 UuvUnitStatistics 同源) */
|
||||
uuvUnitCardAgg: null,
|
||||
summaryLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shipNamesJoined() {
|
||||
return this.joinReportNames(this.ship)
|
||||
},
|
||||
hovNamesJoined() {
|
||||
return this.joinReportNames(this.hov)
|
||||
},
|
||||
rovNamesJoined() {
|
||||
return this.joinReportNames(this.rov)
|
||||
},
|
||||
uuvNamesJoined() {
|
||||
return this.joinReportNames(this.uuv)
|
||||
},
|
||||
/** 科考船四项核心指标展示行 */
|
||||
shipMetrics() {
|
||||
const s = this.shipSummary
|
||||
if (!s) {
|
||||
return [
|
||||
{ label: '航次', display: '—' },
|
||||
{ label: '航次天数', display: '—' },
|
||||
{ label: '参航单位', display: '—' },
|
||||
{ label: '数据样品', display: '—' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ label: '航次', display: this.formatMetric(s.member_total) + ' 次' },
|
||||
{ label: '航次天数', display: this.formatMetric(s.days_total) + ' 天' },
|
||||
{ label: '参航单位', display: this.formatMetric(s.unit_total) + ' 家' },
|
||||
{ label: '数据样品', display: this.formatMetric(s.dataset_total) + ' 个' }
|
||||
]
|
||||
},
|
||||
/** 无人航行潜水器:参航单位数量(汇总接口兜底) */
|
||||
uuvUnitValue() {
|
||||
if (!this.uuvSummary) return '—'
|
||||
return this.formatMetric(this.uuvSummary.unit_total)
|
||||
},
|
||||
/** 参航单位卡片角标:优先 unitTotal 家数,否则 platformAuv 汇总 */
|
||||
uuvUnitBadge() {
|
||||
if (this.uuvUnitCardAgg != null) {
|
||||
return this.formatMetric(this.uuvUnitCardAgg.unitCount)
|
||||
}
|
||||
return this.uuvUnitValue
|
||||
},
|
||||
/**
|
||||
* 参航单位统计卡片四项指标(与参航单位详情页 Banner/汇总一致)
|
||||
* @returns {Array<{ label: string, display: string }>}
|
||||
*/
|
||||
uuvUnitCardMetrics() {
|
||||
const a = this.uuvUnitCardAgg
|
||||
if (!a) {
|
||||
return [
|
||||
{ label: '参航单位', display: '—' },
|
||||
{ label: '参航人数', display: '—' },
|
||||
{ label: '深潜次数', display: '—' },
|
||||
{ label: '航行次数', display: '—' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ label: '参航单位', display: this.formatMetric(a.unitCount) + ' 家' },
|
||||
{ label: '参航人数', display: this.formatMetric(a.memberSum) + ' 人' },
|
||||
{ label: '深潜次数', display: this.formatMetric(a.drvingSum) + ' 次' },
|
||||
{ label: '航行次数', display: this.formatMetric(a.voyageSum) + ' 次' }
|
||||
]
|
||||
},
|
||||
/** 无人航行潜水器:参航科学家数量(汇总接口兜底) */
|
||||
uuvScientistValue() {
|
||||
if (!this.uuvSummary) return '—'
|
||||
return this.formatMetric(this.uuvSummary.driver_member_total)
|
||||
},
|
||||
/** 卡片角标:优先 unitMember 科学家人数,否则汇总接口人数 */
|
||||
uuvScientistBadge() {
|
||||
if (this.memberScientistCard && this.memberScientistCard.scientistCount != null) {
|
||||
return this.formatMetric(this.memberScientistCard.scientistCount)
|
||||
}
|
||||
return this.uuvScientistValue
|
||||
},
|
||||
/**
|
||||
* 参航科学家卡片三项指标(与 Uuv 详情页 banner 一致)
|
||||
* @returns {Array<{ label: string, display: string }>}
|
||||
*/
|
||||
uuvScientistMetrics() {
|
||||
const c = this.memberScientistCard
|
||||
if (!c) {
|
||||
return [
|
||||
{ label: '科学家人数', display: '—' },
|
||||
{ label: '累计参加航次', display: '—' },
|
||||
{ label: '累计参航天数', display: '—' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ label: '科学家人数', display: this.formatMetric(c.scientistCount) + ' 人' },
|
||||
{ label: '累计参加航次', display: this.formatMetric(c.sumVoyage) + ' 次' },
|
||||
{ label: '累计参航天数', display: this.formatMetric(c.sumDays) + ' 天' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getTypeList()
|
||||
this.loadSummaries()
|
||||
},
|
||||
methods: {
|
||||
goToDetail(type) {
|
||||
this.$router.push(`/container/${type}`);
|
||||
this.$router.push(`/container/${type}`)
|
||||
},
|
||||
getTypeList() {
|
||||
reportList().then(res => {
|
||||
|
|
@ -155,32 +446,125 @@ export default {
|
|||
this.uuv = filterDictItems(res.array, '无人航行潜水器')
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 并行拉取各子页汇总接口,用于卡片内核心指标展示
|
||||
*/
|
||||
loadSummaries() {
|
||||
this.summaryLoading = true
|
||||
const pMemberCard = hovUnit()
|
||||
.then(res => {
|
||||
const list = res.array || []
|
||||
const unit =
|
||||
list.find(u => u.unit_name === DEFAULT_MEMBER_STAT_UNIT_NAME) || list[0]
|
||||
if (!unit) return null
|
||||
const key = unit.unit_id != null && unit.unit_id !== '' ? unit.unit_id : unit.unit_name
|
||||
return unitMember({ unit: key })
|
||||
})
|
||||
.then(memberRes => {
|
||||
if (!memberRes || !Array.isArray(memberRes.array)) return null
|
||||
const arr = memberRes.array
|
||||
return {
|
||||
scientistCount: arr.length,
|
||||
sumVoyage: arr.reduce((s, m) => s + num(m.voyage_total), 0),
|
||||
sumDays: arr.reduce((s, m) => s + num(m.days_total), 0)
|
||||
}
|
||||
})
|
||||
.catch(() => null)
|
||||
|
||||
const pUnitTotalCard = unitTotal({})
|
||||
.then(res => aggregateUnitTotalForCard(res && res.array ? res.array : []))
|
||||
.catch(() => null)
|
||||
|
||||
Promise.all([
|
||||
shipTotal({}),
|
||||
platformHov({}),
|
||||
platformRov({}),
|
||||
platformAuv({}),
|
||||
pMemberCard,
|
||||
pUnitTotalCard
|
||||
])
|
||||
.then(([shipRes, hovRes, rovRes, uuvRes, memberAgg, unitAgg]) => {
|
||||
this.shipSummary = (shipRes.array && shipRes.array[0]) || null
|
||||
this.hovSummary = (hovRes.array && hovRes.array[0]) || null
|
||||
this.rovSummary = (rovRes.array && rovRes.array[0]) || null
|
||||
this.uuvSummary = (uuvRes.array && uuvRes.array[0]) || null
|
||||
this.memberScientistCard = memberAgg
|
||||
this.uuvUnitCardAgg = unitAgg
|
||||
})
|
||||
.catch(() => {
|
||||
this.shipSummary = null
|
||||
this.hovSummary = null
|
||||
this.rovSummary = null
|
||||
this.uuvSummary = null
|
||||
this.memberScientistCard = null
|
||||
this.uuvUnitCardAgg = null
|
||||
})
|
||||
.finally(() => {
|
||||
this.summaryLoading = false
|
||||
})
|
||||
},
|
||||
joinReportNames(list) {
|
||||
if (!list || !list.length) return '—'
|
||||
return list.map(i => i.report_name).join('、')
|
||||
},
|
||||
/**
|
||||
* 载人/遥控/无人平台卡片指标(与子页 banner 字段一致)
|
||||
* @param {object|null} row - 接口 array[0]
|
||||
* @returns {Array<{ label: string, display: string }>}
|
||||
*/
|
||||
platformMetrics(row) {
|
||||
if (!row) {
|
||||
return [
|
||||
{ label: '潜次数', display: '—' },
|
||||
{ label: '下潜人数', display: '—' },
|
||||
{ label: '下潜单位', display: '—' },
|
||||
{ label: '数据样品', display: '—' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ label: '潜次数', display: this.formatMetric(row.diving_total) + ' 次' },
|
||||
{ label: '下潜人数', display: this.formatMetric(row.driver_member_total) + ' 人' },
|
||||
{ label: '下潜单位', display: this.formatMetric(row.unit_total) + ' 家' },
|
||||
{ label: '数据样品', display: this.formatMetric(row.dataset_total) + ' 个' }
|
||||
]
|
||||
},
|
||||
/**
|
||||
* @param {string|number|null|undefined} v
|
||||
* @returns {string}
|
||||
*/
|
||||
formatMetric(v) {
|
||||
if (v === null || v === undefined || v === '') return '—'
|
||||
return String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.main-dashboard {
|
||||
padding: 20px;
|
||||
background-color: #f0f7ff;
|
||||
padding: 24px 20px 32px;
|
||||
min-height: calc(100vh - 130px);
|
||||
background: #e9eef4;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #1a365d;
|
||||
color: #0f172a;
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
margin: 0 0 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #4a5568;
|
||||
color: #475569;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
|
|
@ -189,98 +573,213 @@ export default {
|
|||
|
||||
.stat-card {
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 12px;
|
||||
background-color: #ebf8ff;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: #fff;
|
||||
min-height: 280px;
|
||||
transition:
|
||||
box-shadow 0.22s ease,
|
||||
border-color 0.22s ease,
|
||||
transform 0.22s ease;
|
||||
|
||||
::v-deep .el-card__body {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card--nav {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: #cbd5e1;
|
||||
box-shadow: 0 14px 32px -12px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card--placeholder {
|
||||
cursor: default;
|
||||
opacity: 0.92;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card__inner {
|
||||
padding: 18px 18px 16px;
|
||||
position: relative;
|
||||
min-height: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 移除顶部描边(border-top) */
|
||||
|
||||
.stat-card__top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-card__icon-wrap {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-icon i {
|
||||
font-size: 36px;
|
||||
color: #3182ce;
|
||||
.stat-card--ship .stat-card__icon-wrap {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
.stat-card--hov .stat-card__icon-wrap {
|
||||
background: #f0fdfa;
|
||||
color: #0d9488;
|
||||
}
|
||||
|
||||
.stat-card--rov .stat-card__icon-wrap {
|
||||
background: #f5f3ff;
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
.stat-card--uuv .stat-card__icon-wrap {
|
||||
background: #fffbeb;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.stat-card--uuv-unit .stat-card__icon-wrap {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.stat-card--uuv-scientist .stat-card__icon-wrap {
|
||||
background: #f5f3ff;
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
.stat-card--station .stat-card__icon-wrap,
|
||||
.stat-card--lander .stat-card__icon-wrap {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.stat-card__icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.stat-card__head-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-card__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
margin: 0 0 6px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.stat-card__meta {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stat-card__badge {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #475569;
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
background: #f1f5f9;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.stat-card__badge--muted {
|
||||
color: #94a3b8;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.stat-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2d3748;
|
||||
margin-bottom: 8px;
|
||||
.stat-metrics--loading .stat-metric {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.card-count {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #3182ce;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.loading {
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
color: #e53e3e;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
.stat-metric {
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #f1f5f9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.stat-metric__value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
line-height: 1.2;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.stat-metric__label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.stat-card__fleet {
|
||||
margin: 12px 0 0;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
line-height: 1.45;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-placeholder {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
margin-top: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed #cbd5e1;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.stat-placeholder__text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
|
|
@ -289,13 +788,42 @@ export default {
|
|||
}
|
||||
|
||||
.stat-card {
|
||||
animation: fadeInUp 0.5s ease forwards;
|
||||
animation: fadeInUp 0.45s ease forwards;
|
||||
}
|
||||
|
||||
.el-col:nth-child(1) .stat-card { animation-delay: 0.1s; }
|
||||
.el-col:nth-child(2) .stat-card { animation-delay: 0.2s; }
|
||||
.el-col:nth-child(3) .stat-card { animation-delay: 0.3s; }
|
||||
.el-col:nth-child(4) .stat-card { animation-delay: 0.4s; }
|
||||
.el-col:nth-child(5) .stat-card { animation-delay: 0.5s; }
|
||||
.el-col:nth-child(6) .stat-card { animation-delay: 0.6s; }
|
||||
.el-col:nth-child(1) .stat-card {
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
.el-col:nth-child(2) .stat-card {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.el-col:nth-child(3) .stat-card {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
.el-col:nth-child(4) .stat-card {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.el-col:nth-child(5) .stat-card {
|
||||
animation-delay: 0.25s;
|
||||
}
|
||||
.el-col:nth-child(6) .stat-card {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.el-col:nth-child(7) .stat-card {
|
||||
animation-delay: 0.35s;
|
||||
}
|
||||
.el-col:nth-child(8) .stat-card {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.stat-card__inner {
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue