添加新页面,搜索展开右侧弹框
This commit is contained in:
parent
800a2665a0
commit
dca960b986
6
frontend/src/assets/svg/equipment1.svg
Normal file
6
frontend/src/assets/svg/equipment1.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment2.svg
Normal file
6
frontend/src/assets/svg/equipment2.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment3.svg
Normal file
6
frontend/src/assets/svg/equipment3.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment4.svg
Normal file
6
frontend/src/assets/svg/equipment4.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment5.svg
Normal file
6
frontend/src/assets/svg/equipment5.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment6.svg
Normal file
6
frontend/src/assets/svg/equipment6.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment7.svg
Normal file
6
frontend/src/assets/svg/equipment7.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1492 -438 )">
|
||||||
|
<path d="M 16.2118524271846 19.0291262135922 L 19.9417475728155 19.0291262135922 L 19.1133514563107 15.799518446602 L 19.9417475728155 12.5242718446602 L 16.2103611650484 12.5242718446602 C 15.7547805825243 12.4854368932039 15.1180116504854 12.7491417475728 14.7961475728155 13.071254368932 L 13.239518446602 14.6276349514563 L 12.4126135922331 13.8009786407767 C 10.9837359223302 12.3723495145631 9.1099650485437 11.6567922330097 7.23470291262129 11.6567922330097 C 5.35919223300971 11.6567922330097 3.48542135922321 12.3723495145631 2.05654368932028 13.8009786407767 L 0.0582524271844704 15.799518446602 L 2.05654368932028 17.7975611650485 C 3.48542135922321 19.2256932038835 5.35919223300971 19.9417475728155 7.23470291262129 19.9417475728155 C 9.17715584383541 19.9441270685663 11.0405140870726 19.1725055275301 12.4126135922331 17.7975611650485 L 13.239518446602 16.9709048543689 L 14.7978873786408 18.5277825242719 C 15.1197514563107 18.8496466019417 15.7565203883496 19.1131029126213 16.2118524271846 19.0291262135922 Z M 7.47572815533977 5.02912621359224 L 4.61480388349514 7.96116504854371 L 5.85711125164357 7.96116504854382 C 5.83999200538392 8.63258049587466 5.29034577336438 9.17160388349515 4.61480388349514 9.17160388349515 C 3.92846943710356 9.17160388349515 3.37208543689326 8.61521988328485 3.39805825242729 7.92888543689321 L 3.39805825242729 4.87701747572817 C 4.33494368932043 4.53477281553398 5.02912621359224 3.62460582524272 5.02912621359224 2.54368932038835 C 5.02912621359224 1.1707339805825 3.91664466019415 0.0582524271844704 2.54368932038847 0.0582524271844704 C 1.17073398058255 0.0582524271844704 0.0582524271844704 1.1707339805825 0.0582524271844704 2.54368932038835 C 0.0582524271844704 3.62460582524272 0.75243495145628 4.53477281553398 1.74757281553389 4.87701747572817 L 1.74757281553389 7.92863689320387 C 1.71529320388345 9.52975533980583 3.01393398058258 10.8283961165048 4.61480388349514 10.8283961165048 C 6.2159223300971 10.8286446601942 7.51456310679623 9.53000388349517 7.47572815533977 7.92888543689321 L 7.47572815533977 5.02912621359224 Z M 7.23470291262129 13.3140815533981 C 8.74833398058263 13.3140815533981 10.1714951456311 13.9028815533981 11.2409786407768 14.9723650485437 L 12.0678834951457 15.799518446602 L 11.2409786407768 16.6261747572815 C 10.1804526139158 17.6916197749018 8.73799039517485 18.2888648178868 7.23470291262129 18.2849553398058 C 5.73112006657107 18.2890006705935 4.2884127325492 17.6913332030626 3.2281786407766 16.6251805825243 L 2.40127378640773 15.799518446602 L 3.2281786407766 14.9723650485437 C 4.29791067961173 13.9028815533981 5.72082330097078 13.3140815533981 7.23470291262129 13.3140815533981 Z M 16.2411805825243 14.1747572815534 L 17.8199300970873 14.1747572815534 L 17.4056077669902 15.799518446602 L 17.8196815533981 17.3786407766991 L 16.2118524271846 17.3786407766991 C 16.1673631067961 17.4481087378641 16.0065553398058 17.3819961165049 15.9466563106796 17.3350213592233 L 14.4109048543689 15.799518446602 L 15.9677825242718 14.242640776699 C 16.0048155339805 14.2167922330097 16.1658718446602 14.1504310679612 16.2411805825243 14.1747572815534 Z M 2.52934089944642 1.71541747572815 C 2.53412397550369 1.71541747572815 2.53890696186431 1.71545890122997 2.54368932038847 1.71554174757279 C 2.99553837939152 1.72336927490284 3.35773701595144 2.09189673892234 3.35773701595144 2.54381359223299 C 3.35773701595144 2.9957304455437 2.99553837939152 3.36425790956321 2.54368932038847 3.3720854368932 C 2.53890696186431 3.37216828323602 2.53412397550369 3.37220970873784 2.52934089944642 3.37220970873784 C 2.07183035748176 3.37220970873784 1.70094478294163 3.00132413419772 1.70094478294163 2.54381359223299 C 1.70094478294163 2.08630305026833 2.07183035748176 1.71541747572815 2.52934089944642 1.71541747572815 Z M 5.85752233009703 7.96116504854371 C 5.85738484075614 7.95043141406916 5.85752233009703 7.93967448165927 5.85752233009703 7.92888543689321 C 5.85752233009703 7.92888543689321 5.85752233009703 7.92888543689321 5.85752233009703 7.96116504854371 Z M 6.2718449960978 15.7987726661015 C 6.2718449960978 15.3415366579154 5.89993858473554 14.9708734503359 5.44270257654944 14.9708734503359 C 4.98546656836334 14.9708734503359 4.61480373386075 15.3427801230764 4.61480373386075 15.8000161312625 C 4.61480373386075 16.2571148725594 4.98709558293785 16.6276663554221 5.44419432423456 16.6276663554221 C 5.90129306553149 16.6276663554221 6.2718449960978 16.2558714073984 6.2718449960978 15.7987726661015 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1492 438 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
6
frontend/src/assets/svg/equipment8.svg
Normal file
6
frontend/src/assets/svg/equipment8.svg
Normal file
File diff suppressed because one or more lines are too long
6
frontend/src/assets/svg/equipment9.svg
Normal file
6
frontend/src/assets/svg/equipment9.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="matrix(1 0 0 1 -1491 -524 )">
|
||||||
|
<path d="M 17.2111111111111 14.8111111111111 L 17.2111111111111 11.4422222222222 L 1.98888888888888 11.4422222222222 L 1.98888888888888 14.8133333333333 L 7.39333333333332 14.8133333333333 L 7.52666666666664 14.84 L 9.64666666666665 15.76 L 11.6377777777778 14.8422222222222 L 11.7777777777778 14.8111111111111 L 17.2111111111111 14.8111111111111 Z M 17.2111111111111 8.72000000000003 L 17.2111111111111 6 L 1.98888888888888 6 L 1.98888888888888 8.72222222222217 L 7.39333333333332 8.72222222222217 L 7.52666666666664 8.74888888888893 L 9.64666666666665 9.66888888888889 L 11.6377777777778 8.75111111111107 L 11.7777777777778 8.72000000000003 L 17.2111111111111 8.72000000000003 Z M 17.2111111111111 2.99333333333334 L 17.2111111111111 1.44000000000005 C 17.2111111111111 0.695555555555529 17.7644444444445 0.0777777777777828 18.4666666666666 0.0777777777777828 C 18.64 0.0777777777777828 18.8088888888889 0.115555555555602 18.9644444444444 0.188888888888869 C 19.0822222222222 0.244444444444412 19.1577777777778 0.362222222222272 19 0.491111111111081 L 19 19.5822222222222 C 19.1577777777778 19.7111111111111 19.0822222222222 19.8311111111111 18.9644444444444 19.8844444444444 C 18.8088888888889 19.9577777777778 18.64 19.9955555555556 18.4666666666666 19.9955555555556 C 17.7644444444445 19.9955555555556 17.2111111111111 19.3777777777777 17.2111111111111 18.6333333333333 L 17.2111111111111 17.5333333333333 L 1.98888888888888 17.5333333333333 L 1.98888888888888 18.6133333333333 C 1.98888888888888 19.3577777777778 1.43555555555554 19.9755555555555 0.733333333333348 19.9755555555555 C 0.560000000000002 19.9755555555555 0.391111111111115 19.9377777777778 0.23555555555555 19.8644444444444 C 0.117777777777803 19.8088888888889 0.0422222222222217 19.6911111111111 0 19.5622222222222 L 0 0.493333333333339 C 0.0422222222222217 0.364444444444416 0.117777777777803 0.244444444444412 0.23555555555555 0.191111111111127 C 0.391111111111115 0.117777777777746 0.560000000000002 0.0800000000000409 0.733333333333348 0.0800000000000409 C 1.43555555555554 0.0800000000000409 1.98888888888888 0.697777777777787 1.98888888888888 1.4422222222222 L 1.98888888888888 2.99333333333334 L 7.39333333333332 2.99333333333334 L 7.5577777777778 3.03555555555556 L 9.64666666666665 4.20666666666671 L 11.6066666666667 3.03999999999996 L 11.7777777777778 2.99333333333334 L 17.2111111111111 2.99333333333334 Z " fill-rule="nonzero" fill="#ffffff" stroke="none" transform="matrix(1 0 0 1 1491 524 )" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
@ -1,10 +1,14 @@
|
|||||||
<!-- 全局右侧抽屉:由 AppMain 承载,展示锚点搜索内容(基本信息 + 监测表格),受 uiStore.globalDrawerOpen 控制 -->
|
<!-- 全局右侧抽屉:由 AppMain 承载,展示锚点搜索内容(基本信息 + 监测表格),仅搜索时展示 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="global-right-drawer">
|
<div
|
||||||
|
class="global-right-drawer"
|
||||||
|
:class="{ 'is-open': uiStore.globalDrawerOpen }"
|
||||||
|
>
|
||||||
|
<!-- 仅搜索状态下且全局抽屉被收起时,提供重新展开入口 -->
|
||||||
<div
|
<div
|
||||||
@click="handleToggle"
|
@click="handleToggle"
|
||||||
class="drawerController1"
|
class="drawerController1"
|
||||||
v-if="!uiStore.globalDrawerOpen"
|
v-if="uiStore.isSearchActive && !uiStore.globalDrawerOpen"
|
||||||
>
|
>
|
||||||
<img src="../../assets/components/arrow-left.png" alt="" />
|
<img src="../../assets/components/arrow-left.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +55,12 @@ const handleToggle = () => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
pointer-events: all;
|
// 收起时不拦截点击,点击穿透到页面局部抽屉;展开时恢复交互
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
.drawerController1 {
|
.drawerController1 {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
@ -61,13 +70,14 @@ const handleToggle = () => {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
height: 88px;
|
height: 88px;
|
||||||
line-height: 88px;
|
line-height: 88px;
|
||||||
top: 45%;
|
top: 44%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawerController {
|
.drawerController {
|
||||||
@ -86,6 +96,7 @@ const handleToggle = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -243,6 +243,7 @@ const columns = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const planFtp = ref('');
|
||||||
const queryBtn = () => {
|
const queryBtn = () => {
|
||||||
console.log('5');
|
console.log('5');
|
||||||
const filter = {
|
const filter = {
|
||||||
@ -292,30 +293,30 @@ const queryBtn = () => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
tableRef.value?.getList(filter);
|
tableRef.value?.getList(filter);
|
||||||
});
|
});
|
||||||
// const params = {
|
const params = {
|
||||||
// filter: {
|
filter: {
|
||||||
// logic: 'and',
|
logic: 'and',
|
||||||
// filters: [
|
filters: [
|
||||||
// {
|
{
|
||||||
// field: 'stcd',
|
field: 'stcd',
|
||||||
// operator: 'eq',
|
operator: 'eq',
|
||||||
// dataType: 'string',
|
dataType: 'string',
|
||||||
// value: modelStore.params.stcd
|
value: modelStore.params.stcd
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// field: 'yr',
|
field: 'yr',
|
||||||
// operator: 'eq',
|
operator: 'eq',
|
||||||
// dataType: 'string',
|
dataType: 'string',
|
||||||
// value: searchParams.value.tm.format('YYYY')
|
value: searchParams.value.tm.format('YYYY')
|
||||||
// },
|
},
|
||||||
// { field: 'direction', operator: 'eq', dataType: 'string', value: '0' }
|
{ field: 'direction', operator: 'eq', dataType: 'string', value: '0' }
|
||||||
// ]
|
]
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
// getRunStateSecondPlan(params).then(res => {
|
getRunStateSecondPlan(params).then(res => {
|
||||||
// console.log(res);
|
console.log(res);
|
||||||
// planFtp.value = res.data.ftp || '暂无数据';
|
planFtp.value = res.data.ftp || '暂无数据';
|
||||||
// });
|
});
|
||||||
};
|
};
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
queryBtn();
|
queryBtn();
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
<!-- developStatusChart/index.vue -->
|
<!-- developStatusChart/index.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="basic_body">
|
<div class="basic_body">
|
||||||
<div ref="chartContainer" class="chart-container"></div>
|
<div ref="chartContainer" class="chart-container"></div>
|
||||||
|
|
||||||
<!-- 水电开发情况弹窗 -->
|
<!-- 水电开发情况弹窗 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:open="modalVisible"
|
v-model:open="modalVisible"
|
||||||
title="水电开发情况"
|
title="水电开发情况"
|
||||||
width="80%"
|
width="80%"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
>
|
>
|
||||||
<ShuiDianKaiFQKTwoLayer
|
<ShuiDianKaiFQKTwoLayer
|
||||||
v-if="modalVisible"
|
v-if="modalVisible"
|
||||||
:defaultJidiInfo="modalProps.defaultJidiInfo"
|
:defaultJidiInfo="modalProps.defaultJidiInfo"
|
||||||
:seriesName="modalProps.seriesName"
|
:seriesName="modalProps.seriesName"
|
||||||
:stateName="modalProps.stateName"
|
:stateName="modalProps.stateName"
|
||||||
:defaultTab="modalProps.defaultTab"
|
:defaultTab="modalProps.defaultTab"
|
||||||
/>
|
/>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -30,7 +30,7 @@ import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
|||||||
import ShuiDianKaiFQKTwoLayer from '@/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue';
|
import ShuiDianKaiFQKTwoLayer from '@/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'developStatusChart'
|
name: 'developStatusChart'
|
||||||
});
|
});
|
||||||
|
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
@ -43,267 +43,281 @@ const modalVisible = ref(false);
|
|||||||
const modalProps = ref<any>({});
|
const modalProps = ref<any>({});
|
||||||
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
function openModal(props: any) {
|
function openModal(props: any) {
|
||||||
modalProps.value = props;
|
modalProps.value = props;
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openModal,
|
openModal,
|
||||||
dataLoading
|
dataLoading
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (chartInstance) {
|
if (chartInstance) {
|
||||||
chartInstance.dispose();
|
chartInstance.dispose();
|
||||||
chartInstance = null;
|
chartInstance = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const transUnit = (value: number) => {
|
const transUnit = (value: number) => {
|
||||||
return value * 0.1;
|
return value * 0.1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
if (!baseid.value) return;
|
if (!baseid.value) return;
|
||||||
|
|
||||||
const baseFilters: any[] = [
|
const baseFilters: any[] = [
|
||||||
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' },
|
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' },
|
||||||
{ field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }
|
{
|
||||||
];
|
field: 'prsc',
|
||||||
if (baseid.value !== 'all') {
|
operator: 'in',
|
||||||
baseFilters.unshift({ field: 'baseId', operator: 'eq', dataType: 'string', value: baseid.value });
|
dataType: 'string',
|
||||||
|
value: ['1', '2', '3']
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
if (baseid.value !== 'all') {
|
||||||
|
baseFilters.unshift({
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const params1 = {
|
const params1 = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [
|
filters: [...baseFilters, { field: 'ttpwr', operator: 'isnotnull' }]
|
||||||
...baseFilters,
|
},
|
||||||
{ field: 'ttpwr', operator: 'isnotnull' }
|
group: [
|
||||||
]
|
{
|
||||||
},
|
dir: 'desc',
|
||||||
group: [{ dir: 'desc', field: 'bldsttCcode', aggregates: [{ field: 'ttpwr', aggregate: 'sum' }] }]
|
field: 'bldsttCcode',
|
||||||
};
|
aggregates: [{ field: 'ttpwr', aggregate: 'sum' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
const params2 = {
|
const params2 = {
|
||||||
filter: {
|
filter: {
|
||||||
logic: 'and',
|
logic: 'and',
|
||||||
filters: [...baseFilters]
|
filters: [...baseFilters]
|
||||||
},
|
},
|
||||||
group: [{ dir: 'desc', field: 'bldsttCcode' }]
|
group: [{ dir: 'desc', field: 'bldsttCcode' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dataLoading.value = true;
|
dataLoading.value = true;
|
||||||
const [res1, res2] = await Promise.all([
|
const [res1, res2] = await Promise.all([
|
||||||
getVmsstbprptKendoList(params1),
|
getVmsstbprptKendoList(params1),
|
||||||
getVmsstbprptKendoList(params2)
|
getVmsstbprptKendoList(params2)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const res1Data = res1?.data?.data || [];
|
const res1Data = res1?.data?.data || [];
|
||||||
const res2Data = res2?.data?.data || [];
|
const res2Data = res2?.data?.data || [];
|
||||||
|
|
||||||
if (res1Data.length > 0 || res2Data.length > 0) {
|
if (res1Data.length > 0 || res2Data.length > 0) {
|
||||||
const ttpwrlist = res1Data.map((item: any) => ({
|
const ttpwrlist = res1Data.map((item: any) => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
ttpwr: transUnit(item.items[0].SUM_TTPWR)
|
ttpwr: transUnit(item.items[0].SUM_TTPWR)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const engTotalList = res2Data.map((item: any) => ({
|
const engTotalList = res2Data.map((item: any) => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
engTotal: item.count
|
engTotal: item.count
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const result: any = Object.values(
|
const result: any = Object.values(
|
||||||
[...engTotalList, ...ttpwrlist].reduce((result: any, item: any) => {
|
[...engTotalList, ...ttpwrlist].reduce((result: any, item: any) => {
|
||||||
const { key, engTotal = 0, ttpwr = 0 } = item;
|
const { key, engTotal = 0, ttpwr = 0 } = item;
|
||||||
const existingItem = result[key] || { key, engTotal: 0, ttpwr: 0 };
|
const existingItem = result[key] || { key, engTotal: 0, ttpwr: 0 };
|
||||||
existingItem.bldstt = item.key == 0 ? '未建' : item.key == 1 ? '在建' : '已建';
|
existingItem.bldstt =
|
||||||
existingItem.engTotal += engTotal;
|
item.key == 0 ? '未建' : item.key == 1 ? '在建' : '已建';
|
||||||
existingItem.ttpwr += ttpwr;
|
existingItem.engTotal += engTotal;
|
||||||
return { ...result, [key]: existingItem };
|
existingItem.ttpwr += ttpwr;
|
||||||
}, {})
|
return { ...result, [key]: existingItem };
|
||||||
);
|
}, {})
|
||||||
|
);
|
||||||
|
|
||||||
result.sort((a: any, b: any) => {
|
result.sort((a: any, b: any) => {
|
||||||
if (a.bldstt === '已建' && b.bldstt !== '已建') return -1;
|
if (a.bldstt === '已建' && b.bldstt !== '已建') return -1;
|
||||||
else if (a.bldstt !== '已建' && b.bldstt === '已建') return 1;
|
else if (a.bldstt !== '已建' && b.bldstt === '已建') return 1;
|
||||||
else if (a.bldstt === '在建' && b.bldstt === '未建') return -1;
|
else if (a.bldstt === '在建' && b.bldstt === '未建') return -1;
|
||||||
else if (a.bldstt === '未建' && b.bldstt === '在建') return 1;
|
else if (a.bldstt === '未建' && b.bldstt === '在建') return 1;
|
||||||
else return 0;
|
else return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const legendData: string[] = [];
|
const legendData: string[] = [];
|
||||||
const capData: { name: string; value: number }[] = [];
|
const capData: { name: string; value: number }[] = [];
|
||||||
const countData: { name: string; value: number }[] = [];
|
const countData: { name: string; value: number }[] = [];
|
||||||
|
|
||||||
result.forEach((item: any) => {
|
result.forEach((item: any) => {
|
||||||
const name = item.bldstt;
|
const name = item.bldstt;
|
||||||
if (name !== '未建') {
|
if (name !== '未建') {
|
||||||
legendData.push(name);
|
legendData.push(name);
|
||||||
countData.push({ name, value: Number(item.engTotal) });
|
countData.push({ name, value: Number(item.engTotal) });
|
||||||
capData.push({ name, value: Number(item.ttpwr).toFixed(2) });
|
capData.push({ name, value: Number(item.ttpwr).toFixed(2) });
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const colors = ['#78c300', '#2196F3'];
|
|
||||||
|
|
||||||
const option = {
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'item',
|
|
||||||
formatter: '{b}电站{a}: {c}',
|
|
||||||
position: 'right'
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
data: legendData,
|
|
||||||
left: '60%',
|
|
||||||
orient: 'vertical',
|
|
||||||
top: 'center',
|
|
||||||
itemWidth: 20,
|
|
||||||
itemHeight: 12
|
|
||||||
},
|
|
||||||
color: colors,
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '数量(座)',
|
|
||||||
type: 'pie',
|
|
||||||
radius: [0, '55%'],
|
|
||||||
label: { show: false },
|
|
||||||
labelLine: { show: false },
|
|
||||||
data: countData,
|
|
||||||
center: ['30%', '50%'],
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 2,
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '装机容量(万kW)',
|
|
||||||
type: 'pie',
|
|
||||||
radius: ['70%', '90%'],
|
|
||||||
labelLine: { length: 30 },
|
|
||||||
label: { show: false },
|
|
||||||
data: capData,
|
|
||||||
center: ['30%', '50%'],
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 2,
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.setOption(option, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
});
|
||||||
console.error('DevelopStatusChart getData error:', error);
|
|
||||||
} finally {
|
const colors = ['#78c300', '#2196F3'];
|
||||||
dataLoading.value = false;
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b}电站{a}: {c}',
|
||||||
|
position: 'right'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: legendData,
|
||||||
|
left: '60%',
|
||||||
|
orient: 'vertical',
|
||||||
|
top: 'center',
|
||||||
|
itemWidth: 20,
|
||||||
|
itemHeight: 12
|
||||||
|
},
|
||||||
|
color: colors,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '数量(座)',
|
||||||
|
type: 'pie',
|
||||||
|
radius: [0, '55%'],
|
||||||
|
label: { show: false },
|
||||||
|
labelLine: { show: false },
|
||||||
|
data: countData,
|
||||||
|
center: ['30%', '50%'],
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '装机容量(万kW)',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['70%', '90%'],
|
||||||
|
labelLine: { length: 30 },
|
||||||
|
label: { show: false },
|
||||||
|
data: capData,
|
||||||
|
center: ['30%', '50%'],
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.setOption(option, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DevelopStatusChart getData error:', error);
|
||||||
|
} finally {
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
if (!chartContainer.value) return;
|
if (!chartContainer.value) return;
|
||||||
|
|
||||||
chartInstance = echarts.init(chartContainer.value);
|
chartInstance = echarts.init(chartContainer.value);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
formatter: '{a} {b}: {c} ({d}%)',
|
formatter: '{a} {b}: {c} ({d}%)',
|
||||||
position: 'right'
|
position: 'right'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: ['已建', '在建'],
|
||||||
|
left: '60%',
|
||||||
|
orient: 'vertical',
|
||||||
|
top: 'center',
|
||||||
|
itemWidth: 20,
|
||||||
|
itemHeight: 12
|
||||||
|
},
|
||||||
|
color: ['#78c300', '#2196F3'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '装机容量(万kW)',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['70%', '90%'],
|
||||||
|
center: ['30%', '50%'],
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
legend: {
|
label: { show: false },
|
||||||
data: ['已建', '在建'],
|
labelLine: { show: false },
|
||||||
left: '60%',
|
data: []
|
||||||
orient: 'vertical',
|
},
|
||||||
top: 'center',
|
{
|
||||||
itemWidth: 20,
|
name: '数量(座)',
|
||||||
itemHeight: 12
|
type: 'pie',
|
||||||
|
radius: ['0%', '50%'],
|
||||||
|
center: ['30%', '50%'],
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 2,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
},
|
},
|
||||||
color: ['#78c300', '#2196F3'],
|
label: { show: false },
|
||||||
series: [
|
labelLine: { show: false },
|
||||||
{
|
data: []
|
||||||
name: '装机容量(万kW)',
|
}
|
||||||
type: 'pie',
|
]
|
||||||
radius: ['70%', '90%'],
|
};
|
||||||
center: ['30%', '50%'],
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 2,
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 2
|
|
||||||
},
|
|
||||||
label: { show: false },
|
|
||||||
labelLine: { show: false },
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '数量(座)',
|
|
||||||
type: 'pie',
|
|
||||||
radius: ['0%', '50%'],
|
|
||||||
center: ['30%', '50%'],
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 2,
|
|
||||||
borderColor: '#fff',
|
|
||||||
borderWidth: 2
|
|
||||||
},
|
|
||||||
label: { show: false },
|
|
||||||
labelLine: { show: false },
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
chartInstance.setOption(option);
|
chartInstance.setOption(option);
|
||||||
|
|
||||||
chartInstance.on('click', (params: any) => {
|
chartInstance.on('click', (params: any) => {
|
||||||
const currentJidiInfo = JidiSelectEventStore.selectedItem;
|
const currentJidiInfo = JidiSelectEventStore.selectedItem;
|
||||||
openModal({
|
openModal({
|
||||||
defaultJidiInfo: {
|
defaultJidiInfo: {
|
||||||
baseid: currentJidiInfo?.wbsCode || 'all',
|
baseid: currentJidiInfo?.wbsCode || 'all',
|
||||||
basename: currentJidiInfo?.wbsName || '全部基地'
|
basename: currentJidiInfo?.wbsName || '全部基地'
|
||||||
},
|
},
|
||||||
seriesName: params.seriesName,
|
seriesName: params.seriesName,
|
||||||
stateName: params.name,
|
stateName: params.name,
|
||||||
defaultTab: params.name
|
defaultTab: params.name
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
chartInstance?.resize();
|
chartInstance?.resize();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (chartContainer.value) {
|
if (chartContainer.value) {
|
||||||
initChart();
|
initChart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => JidiSelectEventStore.selectedItem,
|
() => JidiSelectEventStore.selectedItem,
|
||||||
newVal => {
|
newVal => {
|
||||||
if (newVal && newVal.wbsCode) {
|
if (newVal && newVal.wbsCode) {
|
||||||
baseid.value = newVal.wbsCode;
|
baseid.value = newVal.wbsCode;
|
||||||
getData();
|
getData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.basic_body {
|
.basic_body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 203px;
|
width: 203px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="map-filter-container" v-show="!uiStore.isRoaming">
|
<div
|
||||||
|
class="map-filter-container"
|
||||||
|
v-show="!uiStore.isRoaming && !uiStore.isSmartEquipment"
|
||||||
|
>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<a-form :model="formModel">
|
<a-form :model="formModel">
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
@ -151,7 +154,8 @@ const DRAWER_STTP_CODES = [
|
|||||||
'ZQ',
|
'ZQ',
|
||||||
'VP',
|
'VP',
|
||||||
'VA',
|
'VA',
|
||||||
'EQ'
|
'EQ',
|
||||||
|
'FB'
|
||||||
];
|
];
|
||||||
const siteRangePicker = [
|
const siteRangePicker = [
|
||||||
{ label: '全部', value: 'all' },
|
{ label: '全部', value: 'all' },
|
||||||
@ -244,6 +248,7 @@ watch(
|
|||||||
anchorPointSelect: null,
|
anchorPointSelect: null,
|
||||||
fishSurveyZhuanZhi: false
|
fishSurveyZhuanZhi: false
|
||||||
};
|
};
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setDrawerOpen(true);
|
uiStore.setDrawerOpen(true);
|
||||||
@ -615,6 +620,7 @@ watch(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formModel.value.anchorPointSelect = null;
|
formModel.value.anchorPointSelect = null;
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setDrawerOpen(true);
|
uiStore.setDrawerOpen(true);
|
||||||
@ -660,6 +666,7 @@ watch(
|
|||||||
|
|
||||||
const handleCapacityChange = (value: string) => {
|
const handleCapacityChange = (value: string) => {
|
||||||
formModel.value.anchorPointSelect = null;
|
formModel.value.anchorPointSelect = null;
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setDrawerOpen(true);
|
uiStore.setDrawerOpen(true);
|
||||||
@ -691,7 +698,8 @@ const focusSelectedAnchorPoint = () => {
|
|||||||
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
||||||
const handleAnchorPointChange = (value: string) => {
|
const handleAnchorPointChange = (value: string) => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
// 清空:先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
// 清空:退出搜索状态,先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setDrawerOpen(true);
|
uiStore.setDrawerOpen(true);
|
||||||
@ -705,15 +713,17 @@ const handleAnchorPointChange = (value: string) => {
|
|||||||
const sttpCode = String(
|
const sttpCode = String(
|
||||||
matched?.sttpCode || matched?.sttp || ''
|
matched?.sttpCode || matched?.sttp || ''
|
||||||
).toUpperCase();
|
).toUpperCase();
|
||||||
// 非抽屉图层:不弹抽屉,收起全局抽屉后恢复页面自带抽屉
|
// 非抽屉图层:不弹抽屉,退出搜索状态,收起全局抽屉后恢复页面自带抽屉
|
||||||
if (!DRAWER_STTP_CODES.includes(sttpCode)) {
|
if (!DRAWER_STTP_CODES.includes(sttpCode)) {
|
||||||
|
uiStore.setIsSearchActive(false);
|
||||||
uiStore.setGlobalDrawerOpen(false);
|
uiStore.setGlobalDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setDrawerOpen(true);
|
uiStore.setDrawerOpen(true);
|
||||||
}, 300);
|
}, 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 需要抽屉:先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
// 需要抽屉:进入搜索状态,先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
||||||
|
uiStore.setIsSearchActive(true);
|
||||||
uiStore.setDrawerOpen(false);
|
uiStore.setDrawerOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uiStore.setGlobalDrawerOpen(true);
|
uiStore.setGlobalDrawerOpen(true);
|
||||||
|
|||||||
@ -34,7 +34,6 @@ const isZhiNengPeiTu = computed(() => {
|
|||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 全局抽屉:承载锚点搜索内容(基本信息 + 监测表格),由锚点下拉 sttpCode 驱动打开 -->
|
|
||||||
<div class="global-drawer-wrap">
|
<div class="global-drawer-wrap">
|
||||||
<GlobalRightDrawer>
|
<GlobalRightDrawer>
|
||||||
<basicInfoMod />
|
<basicInfoMod />
|
||||||
@ -76,6 +75,7 @@ const isZhiNengPeiTu = computed(() => {
|
|||||||
width: 450px;
|
width: 450px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
pointer-events: all;
|
// 收起时整体不拦截点击,不遮挡页面局部抽屉;展开状态由 GlobalRightDrawer 内部 is-open 控制
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
98
frontend/src/modules/BaseStatistics/index.vue
Normal file
98
frontend/src/modules/BaseStatistics/index.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem title="各基地设备统计">
|
||||||
|
<div ref="chartRef" style="width: 100%; height: calc(100vh - 520px)"></div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
// 图表实例
|
||||||
|
const chartRef = ref(null);
|
||||||
|
let chartInstance = null;
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
const option = {
|
||||||
|
color: ['#5B8FF9', '#5AD8A6', '#5D7092'],
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: {
|
||||||
|
data: [
|
||||||
|
{ name: '鱼类调查装置' },
|
||||||
|
{ name: '鱼类智能识别装置' },
|
||||||
|
{ name: '动物智能识别装置' },
|
||||||
|
{ name: '鸟类智能识别装置' },
|
||||||
|
{ name: '河道违章智能识别装置' },
|
||||||
|
{ name: '智能分拣装置' },
|
||||||
|
{ name: '钓鱼' },
|
||||||
|
{ name: '漂浮物识别装置' },
|
||||||
|
{ name: '叠梁门运行识别装置' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
grid: { left: 0, right: 30, top: 60, bottom: 40, containLabel: true },
|
||||||
|
xAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
// offset: -60,
|
||||||
|
data: [
|
||||||
|
'金沙江干流',
|
||||||
|
'雅磨江干流',
|
||||||
|
'大渡河干流',
|
||||||
|
'乌江干流',
|
||||||
|
'长江上游干流',
|
||||||
|
'湘西',
|
||||||
|
'黄河上游干流',
|
||||||
|
'黄河中游干流',
|
||||||
|
'南盘江-红水河',
|
||||||
|
'雅鲁藏布江干流'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '鱼类调查装置',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: [12.3, 10, 8.8, 10, 7.2, 6, 15, 11, 16, 8.6, 18, 15, 20.2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鱼类智能识别装置',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: [12.3, 10, 8.8, 10, 7.2, 6, 15, 11, 16, 8.6, 18, 15, 20.2]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
markLine: {
|
||||||
|
silent: true,
|
||||||
|
data: [{ xAxis: '05-14', lineStyle: { color: 'red', type: 'dashed' } }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 容器尺寸自适应(抽屉展开动画导致初始宽度为0时,ECharts 默认100px,展开完成后自动纠正)
|
||||||
|
let resizeObserver = null;
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart();
|
||||||
|
resizeObserver = new ResizeObserver(handleResize);
|
||||||
|
if (chartRef.value) {
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
resizeObserver?.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
chartInstance?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
166
frontend/src/modules/EquipmentStatistics/index.vue
Normal file
166
frontend/src/modules/EquipmentStatistics/index.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem title="设备统计">
|
||||||
|
<a-spin :spinning="loading" tip="加载中...">
|
||||||
|
<div class="facility-grid">
|
||||||
|
<div
|
||||||
|
v-for="facility in facilities"
|
||||||
|
:key="facility.name"
|
||||||
|
class="facility-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 60px;
|
||||||
|
height: 62px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="facility-icon">
|
||||||
|
<img :src="facility.icon" alt="设备图标" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="facility-info">
|
||||||
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
|
<div style="font-size: 16px">
|
||||||
|
<span class="facility-count">{{ facility.count }}</span
|
||||||
|
><span>个</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import equipment1 from '@/assets/svg/equipment1.svg';
|
||||||
|
import equipment2 from '@/assets/svg/equipment2.svg';
|
||||||
|
import equipment3 from '@/assets/svg/equipment3.svg';
|
||||||
|
import equipment4 from '@/assets/svg/equipment4.svg';
|
||||||
|
import equipment5 from '@/assets/svg/equipment5.svg';
|
||||||
|
import equipment6 from '@/assets/svg/equipment6.svg';
|
||||||
|
import equipment7 from '@/assets/svg/equipment7.svg';
|
||||||
|
import equipment8 from '@/assets/svg/equipment8.svg';
|
||||||
|
import equipment9 from '@/assets/svg/equipment9.svg';
|
||||||
|
|
||||||
|
// Loading 状态
|
||||||
|
const loading = ref(false);
|
||||||
|
// 设施数据
|
||||||
|
const facilities = ref([
|
||||||
|
{
|
||||||
|
name: '鱼类调查装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment1,
|
||||||
|
key: '26'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鱼类智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment2,
|
||||||
|
key: '27'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '动物智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment3,
|
||||||
|
key: '28'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '鸟类智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment4,
|
||||||
|
key: '29'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '河道违章智能识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment5,
|
||||||
|
key: '29'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '智能分拣装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment6,
|
||||||
|
key: '29'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '钓鱼',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment7,
|
||||||
|
key: '30'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '漂浮物识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment8,
|
||||||
|
key: '30'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '叠梁门运行识别装置',
|
||||||
|
count: 0,
|
||||||
|
icon: equipment9,
|
||||||
|
key: '31'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.facility-grid {
|
||||||
|
width: 406px;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||||
|
Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji,
|
||||||
|
Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-card {
|
||||||
|
width: 200px;
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 4px 0px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: #2f6b98;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-count {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #2f6b98;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -14,14 +14,16 @@
|
|||||||
<a-row class="px-2">
|
<a-row class="px-2">
|
||||||
<a-col :span="item.col" v-for="(item, index) in formDiv" :key="index">
|
<a-col :span="item.col" v-for="(item, index) in formDiv" :key="index">
|
||||||
<a-form-item :label="item.label" :title="info[item.value]">
|
<a-form-item :label="item.label" :title="info[item.value]">
|
||||||
<span v-if="item.type == 'date'">
|
<a-tooltip :title="info[item.value]">
|
||||||
{{
|
<span v-if="item.type == 'date'">
|
||||||
info[item.value]
|
{{
|
||||||
? dayjs(info[item.value]).format('YYYY-MM-DD')
|
info[item.value]
|
||||||
: '-'
|
? dayjs(info[item.value]).format('YYYY-MM-DD')
|
||||||
}}
|
: '-'
|
||||||
</span>
|
}}
|
||||||
<span v-else>{{ info[item.value] || '-' }} </span>
|
</span>
|
||||||
|
<span v-else>{{ info[item.value] || '-' }} </span>
|
||||||
|
</a-tooltip>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|||||||
@ -0,0 +1,254 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem title="增殖计划完成情况" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="selectedYear"
|
||||||
|
picker="year"
|
||||||
|
format="YYYY"
|
||||||
|
:allowClear="false"
|
||||||
|
:disabled-date="disabledYear"
|
||||||
|
size="small"
|
||||||
|
placeholder=" "
|
||||||
|
class="w-[90px]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="chart-row">
|
||||||
|
<div
|
||||||
|
v-for="(cfg, index) in chartConfigs"
|
||||||
|
:key="cfg.key"
|
||||||
|
class="pie-chart-container"
|
||||||
|
>
|
||||||
|
<div class="title">{{ cfg.title }}</div>
|
||||||
|
<div :ref="el => setChartRef(el, index)" class="chart"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import type { ECharts } from 'echarts';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
interface PieChartConfig {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
unit: string;
|
||||||
|
colors: string[];
|
||||||
|
data: Array<{ name: string; value: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 图表数据(先写死,后续接入接口) ====================
|
||||||
|
const chartConfigs: PieChartConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'count',
|
||||||
|
title: '数量完成情况',
|
||||||
|
unit: '计划完成总数 \n(万尾)',
|
||||||
|
unitLegend: '万尾',
|
||||||
|
colors: ['#5470C6', '#91CC75'],
|
||||||
|
data: [
|
||||||
|
{ name: '未完成数量', value: 92.4 },
|
||||||
|
{ name: '已完成数量', value: 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
title: '种类完成情况',
|
||||||
|
unit: '计划完成种类 \n(种)',
|
||||||
|
unitLegend: '种',
|
||||||
|
colors: ['#FAC858', '#EE6666'],
|
||||||
|
data: [
|
||||||
|
{ name: '未完成种类', value: 3 },
|
||||||
|
{ name: '已完成种类', value: 5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 年份选择(默认今年) ====================
|
||||||
|
const selectedYear = ref<Dayjs>(dayjs());
|
||||||
|
const disabledYear = (current: Dayjs) =>
|
||||||
|
current && current.isAfter(dayjs(), 'year');
|
||||||
|
|
||||||
|
// ==================== 图表 ====================
|
||||||
|
const chartInstances = ref<(ECharts | null)[]>([]);
|
||||||
|
const chartRefs = ref<(HTMLElement | null)[]>([]);
|
||||||
|
|
||||||
|
const setChartRef = (el: any, index: number) => {
|
||||||
|
if (el) chartRefs.value[index] = el;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新单个图表配置(样式参考 GYZLLB)
|
||||||
|
const renderChart = (index: number) => {
|
||||||
|
const instance = chartInstances.value[index];
|
||||||
|
const cfg = chartConfigs[index];
|
||||||
|
if (!instance || !cfg) return;
|
||||||
|
|
||||||
|
const total = cfg.data.reduce((sum, item) => sum + item.value, 0);
|
||||||
|
|
||||||
|
const option: echarts.EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: `{b}: {c} ${cfg.unit} ({d}%)`,
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: `${total}`,
|
||||||
|
subtext: `${cfg.unit}`,
|
||||||
|
left: '47%',
|
||||||
|
top: '30%',
|
||||||
|
textAlign: 'center',
|
||||||
|
textVerticalAlign: 'middle',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#333'
|
||||||
|
},
|
||||||
|
subtextStyle: {
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
grid: {
|
||||||
|
top: '0%',
|
||||||
|
bottom: '10%',
|
||||||
|
left: '0%',
|
||||||
|
right: '0%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
orient: 'vertical',
|
||||||
|
left: 5,
|
||||||
|
bottom: '0',
|
||||||
|
itemGap: 8,
|
||||||
|
pageIconColor: '#333',
|
||||||
|
pageIconInactiveColor: '#ccc',
|
||||||
|
pageIconSize: 12,
|
||||||
|
pageFormatter: '{current}/{total}',
|
||||||
|
formatter: function (name: string) {
|
||||||
|
const found = cfg.data.find(item => item.name === name);
|
||||||
|
const value = found ? found.value : '-';
|
||||||
|
return `${name} ${value} ${cfg.unitLegend}`;
|
||||||
|
},
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
top: '-24%',
|
||||||
|
radius: ['50%', '70%'],
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.3)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: cfg.data.map((item, idx) => ({
|
||||||
|
...item,
|
||||||
|
itemStyle: {
|
||||||
|
color: cfg.colors[idx]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.setOption(option, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化单个图表(容器尺寸为 0 时延迟重试)
|
||||||
|
const initChart = (index: number) => {
|
||||||
|
const chartRef = chartRefs.value[index];
|
||||||
|
if (!chartRef) return;
|
||||||
|
|
||||||
|
const rect = chartRef.getBoundingClientRect();
|
||||||
|
if (rect.width === 0 || rect.height === 0) {
|
||||||
|
setTimeout(() => initChart(index), 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = echarts.init(chartRef);
|
||||||
|
chartInstances.value[index] = instance;
|
||||||
|
renderChart(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 窗口大小变化时自适应
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstances.value.forEach(instance => {
|
||||||
|
if (instance) instance.resize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
chartConfigs.forEach((_, index) => initChart(index));
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
chartInstances.value.forEach(instance => {
|
||||||
|
if (instance) instance.dispose();
|
||||||
|
});
|
||||||
|
chartInstances.value = [];
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-row {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.pie-chart-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 260px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #2c2c2c;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,189 @@
|
|||||||
|
<template>
|
||||||
|
<SidePanelItem :title="title" :shrink="false">
|
||||||
|
<template #title-right-content>
|
||||||
|
<a-select
|
||||||
|
v-model:value="currentStcd"
|
||||||
|
:options="videoOptions"
|
||||||
|
:loading="loading"
|
||||||
|
show-search
|
||||||
|
:placeholder="placeholder"
|
||||||
|
size="small"
|
||||||
|
style="width: 200px"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
@change="handleSelectVideo"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="video-wrapper">
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<LiveVideoBox
|
||||||
|
v-if="currentVideo"
|
||||||
|
:video-data="currentVideo"
|
||||||
|
monitor-type="live"
|
||||||
|
:show-title="false"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
<a-empty
|
||||||
|
v-else-if="!loading"
|
||||||
|
description="暂无数据"
|
||||||
|
class="flex align-center justify-center h-full flex-col"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { getVideoList } from '@/api/mapModal';
|
||||||
|
import { useModelStore } from '@/store/modules/model';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import LiveVideoBox from '@/views/shiPinJianKong/components/LiveVideoBox.vue';
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const videoList = ref<any[]>([]);
|
||||||
|
const currentStcd = ref<string | undefined>(undefined);
|
||||||
|
const currentVideo = ref<any>(null);
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '实时视频'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择视频'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 下拉框选项:label 显示站名,value 使用 stcd
|
||||||
|
const videoOptions = computed(() =>
|
||||||
|
videoList.value.map(item => ({ label: item.stnm, value: item.stcd }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterOption = (input: string, option?: any) => {
|
||||||
|
return (option?.label ?? '').toLowerCase().includes(input.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组装 LiveVideoBox 需要的视频数据
|
||||||
|
const buildVideoData = (item: any) => ({
|
||||||
|
flnm: item.stnm,
|
||||||
|
stnm: item.stnm,
|
||||||
|
url: item.url,
|
||||||
|
key: item.stcd,
|
||||||
|
...item
|
||||||
|
});
|
||||||
|
|
||||||
|
// 拉取视频列表(查询参数取 modelStore.selectedAnchorPoint)
|
||||||
|
const fetchVideoList = async () => {
|
||||||
|
const stcd = modelStore.selectedAnchorPoint?.stcd;
|
||||||
|
if (!stcd) {
|
||||||
|
videoList.value = [];
|
||||||
|
currentVideo.value = null;
|
||||||
|
currentStcd.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{ field: 'sttpCode', operator: 'startswith', value: 'VD_' },
|
||||||
|
{
|
||||||
|
logic: 'or',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'fhstcd',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'stCode',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: stcd
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
select: ['stcd', 'stnm', 'sttpCode', 'sttpName', 'playWay', 'url'],
|
||||||
|
sort: [{ field: 'stnm', dir: 'asc' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
const res: any = await getVideoList(params);
|
||||||
|
const list = res?.data?.data || [];
|
||||||
|
videoList.value = list;
|
||||||
|
|
||||||
|
// 默认选中第一条并播放
|
||||||
|
if (list.length > 0) {
|
||||||
|
const first = list[0];
|
||||||
|
currentStcd.value = first.stcd;
|
||||||
|
currentVideo.value = buildVideoData(first);
|
||||||
|
} else {
|
||||||
|
currentStcd.value = undefined;
|
||||||
|
currentVideo.value = null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取视频列表失败:', error);
|
||||||
|
videoList.value = [];
|
||||||
|
currentVideo.value = null;
|
||||||
|
currentStcd.value = undefined;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下拉框切换视频
|
||||||
|
const handleSelectVideo = (stcd: string) => {
|
||||||
|
const item = videoList.value.find(v => v.stcd === stcd);
|
||||||
|
if (item) currentVideo.value = buildVideoData(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选中点位变化时重新拉取视频列表;初始已有点位时立即加载
|
||||||
|
watch(
|
||||||
|
() => modelStore.selectedAnchorPoint?.stcd,
|
||||||
|
(newStcd, oldStcd) => {
|
||||||
|
if (!newStcd) {
|
||||||
|
videoList.value = [];
|
||||||
|
currentVideo.value = null;
|
||||||
|
currentStcd.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newStcd !== oldStcd) fetchVideoList();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.video-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 240px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
:deep(.ant-spin-nested-loading) {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.ant-spin-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!--
|
||||||
|
1.增殖放流 接口
|
||||||
|
2. 生态调查 陆生 水生
|
||||||
|
-->
|
||||||
<!-- 电站运行过程线 -->
|
<!-- 电站运行过程线 -->
|
||||||
<monitorInfoMod v-if="sttpValue == 'ENG'" />
|
<monitorInfoMod v-if="sttpValue == 'ENG'" />
|
||||||
<!-- 水温监控 -->
|
<!-- 水温监控 -->
|
||||||
@ -21,7 +25,17 @@
|
|||||||
<!-- 生态流量泄放设施 EQ -->
|
<!-- 生态流量泄放设施 EQ -->
|
||||||
<FlowDischarge v-if="sttpValue == 'EQ'" />
|
<FlowDischarge v-if="sttpValue == 'EQ'" />
|
||||||
<!-- 鱼类增殖站 -->
|
<!-- 鱼类增殖站 -->
|
||||||
|
<NormalOperationData v-if="sttpValue == 'FB'" />
|
||||||
|
<!-- 实时视频 -->
|
||||||
|
<VideoInfo
|
||||||
|
title="实时视频"
|
||||||
|
placeholder="请选择视频"
|
||||||
|
v-if="sttpValue == 'FB'"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 生态调查 -->
|
<!-- 生态调查 -->
|
||||||
|
<!-- <WaterTemperature v-if="sttpValue == 'WE'" /> -->
|
||||||
|
<!-- 生态调查 陆生 -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -38,6 +52,8 @@ import FlowMeasure from '@/modules/rightSearchDrawer/monitoringTable/components/
|
|||||||
import Plant from '@/modules/rightSearchDrawer/monitoringTable/components/Plant.vue';
|
import Plant from '@/modules/rightSearchDrawer/monitoringTable/components/Plant.vue';
|
||||||
import Animal from '@/modules/rightSearchDrawer/monitoringTable/components/Animal.vue';
|
import Animal from '@/modules/rightSearchDrawer/monitoringTable/components/Animal.vue';
|
||||||
import FlowDischarge from '@/modules/rightSearchDrawer/monitoringTable/components/FlowDischarge.vue';
|
import FlowDischarge from '@/modules/rightSearchDrawer/monitoringTable/components/FlowDischarge.vue';
|
||||||
|
import NormalOperationData from '@/modules/rightSearchDrawer/monitoringTable/components/NormalOperationData.vue';
|
||||||
|
import VideoInfo from '@/modules/rightSearchDrawer/monitoringTable/components/VideoInfo.vue';
|
||||||
|
|
||||||
const modelStore = useModelStore();
|
const modelStore = useModelStore();
|
||||||
const sttpValue = ref('');
|
const sttpValue = ref('');
|
||||||
|
|||||||
@ -324,7 +324,9 @@ const getSelectData = async () => {
|
|||||||
|
|
||||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
const injectedStation = inject<any>('dianZhanStation', null);
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
// ==================== 监听基地变化 ====================
|
// ==================== 监听基地变化 ====================
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
363
frontend/src/modules/shuishengtaidiaochaduanmian/SsstdcgkTk.vue
Normal file
363
frontend/src/modules/shuishengtaidiaochaduanmian/SsstdcgkTk.vue
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ssst-dcgk-table">
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<a-form layout="inline" class="search-form">
|
||||||
|
<a-form-item label="年份">
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="yearValue"
|
||||||
|
picker="year"
|
||||||
|
format="YYYY"
|
||||||
|
:allow-clear="false"
|
||||||
|
:show-time="false"
|
||||||
|
style="width: 150px"
|
||||||
|
@change="handleYearChange"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="调查批次">
|
||||||
|
<a-select
|
||||||
|
v-model:value="dcpcValue"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="width: 150px"
|
||||||
|
:options="dcpcOptions"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="调查鱼种类">
|
||||||
|
<a-input
|
||||||
|
v-model:value="nameValue"
|
||||||
|
placeholder="请输入鱼种类"
|
||||||
|
allow-clear
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-space>
|
||||||
|
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||||
|
<a-button @click="handleReset">重置</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<BasicTable
|
||||||
|
ref="tableRef"
|
||||||
|
:columns="columns"
|
||||||
|
:scroll-y="500"
|
||||||
|
:scroll-x="800"
|
||||||
|
:list-url="listUrl"
|
||||||
|
:search-params="searchParams"
|
||||||
|
:transform-data="customTransform"
|
||||||
|
>
|
||||||
|
<template #stnm="{ record }">
|
||||||
|
<a @click="handleViewDetail(record)" class="text-link">
|
||||||
|
{{ record.stnm }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template #tm="{ record }">
|
||||||
|
{{ record.tm ? dayjs(record.tm).format('YYYY-MM-DD') : '-' }}
|
||||||
|
</template>
|
||||||
|
<template #fid="{ record }">
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
style="padding: 4px"
|
||||||
|
:disabled="record.fid"
|
||||||
|
@click="handleViewAttachment(record.fid)"
|
||||||
|
>
|
||||||
|
查看附件
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CommonAttachmentModal
|
||||||
|
v-model:open="attachmentModalVisible"
|
||||||
|
:fid="currentAttachmentFid"
|
||||||
|
title="查看附件"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
|
import BasicTable from '@/components/BasicTable/index.vue';
|
||||||
|
import { getWeStaticsSecond } from '@/api/stdc';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useModelStore } from "@/store/modules/model";
|
||||||
|
|
||||||
|
const modelStore = useModelStore();
|
||||||
|
// ==================== Props ====================
|
||||||
|
const props = defineProps<{
|
||||||
|
selectDateTime?: any; // 选中的日期
|
||||||
|
stcd?: string; // 断面编码
|
||||||
|
all?: boolean; // 是否不限制断面
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// ==================== 搜索状态 ====================
|
||||||
|
const tableRef = ref();
|
||||||
|
const yearValue = ref(props.selectDateTime ? dayjs(props.selectDateTime) : dayjs());
|
||||||
|
const dcpcValue = ref('');
|
||||||
|
const nameValue = ref('');
|
||||||
|
const dcpcOptions = ref<Array<{ label: string; value: string }>>([
|
||||||
|
{ label: '全部', value: '' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ==================== 弹框状态 ====================
|
||||||
|
const attachmentModalVisible = ref(false);
|
||||||
|
const currentAttachmentFid = ref('');
|
||||||
|
// ==================== 接口URL ====================
|
||||||
|
const listUrl = getWeStaticsSecond;
|
||||||
|
|
||||||
|
// ==================== 搜索参数 ====================
|
||||||
|
const searchParams = computed(() => ({
|
||||||
|
sort: [
|
||||||
|
{
|
||||||
|
field: 'tm',
|
||||||
|
dir: 'desc'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
group: [],
|
||||||
|
select: [],
|
||||||
|
groupResultFlat: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
key: 'stnm',
|
||||||
|
title: '断面名称',
|
||||||
|
dataIndex: 'stnm',
|
||||||
|
fixed: 'left' as const,
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
slots: { customRender: 'stnm' },
|
||||||
|
merge: true,
|
||||||
|
mergeDeps: ['stnm']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tm',
|
||||||
|
title: '调查日期',
|
||||||
|
dataIndex: 'tm',
|
||||||
|
fixed: 'left' as const,
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
slots: { customRender: 'tm' },
|
||||||
|
merge: true,
|
||||||
|
mergeDeps: ['stnm']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'dcpc',
|
||||||
|
title: '调查批次',
|
||||||
|
dataIndex: 'dcpc',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
merge: true,
|
||||||
|
mergeDeps: ['stnm']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ftpName',
|
||||||
|
title: '调查鱼种类',
|
||||||
|
dataIndex: 'ftpName',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fcnt',
|
||||||
|
title: '调查鱼总数',
|
||||||
|
dataIndex: 'fcnt',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fid',
|
||||||
|
title: '证明材料',
|
||||||
|
dataIndex: 'fid',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
slots: { customRender: 'fid' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== 过滤条件构建 ====================
|
||||||
|
const getFilter = () => {
|
||||||
|
const filters: any[] = [
|
||||||
|
{
|
||||||
|
field: 'year',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [dayjs(yearValue.value).format('YYYY')]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 断面筛选
|
||||||
|
if (!props.all && props.stcd) {
|
||||||
|
filters.push({
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: props.stcd
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调查批次筛选
|
||||||
|
if (dcpcValue.value) {
|
||||||
|
filters.push({
|
||||||
|
field: 'dcpc',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: dcpcValue.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鱼种类搜索
|
||||||
|
if (nameValue.value) {
|
||||||
|
filters.push({
|
||||||
|
field: 'ftpName',
|
||||||
|
operator: 'contains',
|
||||||
|
dataType: 'string',
|
||||||
|
value: nameValue.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
logic: 'and',
|
||||||
|
filters: filters.filter((el: any) => el != null)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 数据转换 ====================
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
return {
|
||||||
|
records: res?.data?.data || [],
|
||||||
|
total: res?.data?.total || 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 获取调查批次选项 ====================
|
||||||
|
const getDcpcOptions = async () => {
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
...(props.all
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
field: 'rstcd',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: props.stcd
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
field: 'year',
|
||||||
|
operator: 'in',
|
||||||
|
dataType: 'string',
|
||||||
|
value: [dayjs(yearValue.value).format('YYYY')]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const group = [
|
||||||
|
{
|
||||||
|
dir: 'des',
|
||||||
|
field: 'dcpc'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const res = await getWeStaticsSecond( { filter: params, group });
|
||||||
|
// debugger
|
||||||
|
|
||||||
|
if (res?.data?.data?.length) {
|
||||||
|
const options = res.data.data.map((item: any) => ({
|
||||||
|
label: item.key,
|
||||||
|
value: item.key
|
||||||
|
}));
|
||||||
|
dcpcOptions.value = [{ label: '全部', value: '' }, ...options];
|
||||||
|
} else {
|
||||||
|
dcpcOptions.value = [{ label: '全部', value: '' }];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取调查批次选项失败:', error);
|
||||||
|
dcpcOptions.value = [{ label: '全部', value: '' }];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 搜索与重置 ====================
|
||||||
|
const handleSearch = () => {
|
||||||
|
const filter = getFilter();
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
yearValue.value = props.selectDateTime ? dayjs(props.selectDateTime) : dayjs();
|
||||||
|
dcpcValue.value = '';
|
||||||
|
nameValue.value = '';
|
||||||
|
handleSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleYearChange = () => {
|
||||||
|
dcpcValue.value = '';
|
||||||
|
getDcpcOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情(占位)
|
||||||
|
const handleViewDetail = (record: any) => {
|
||||||
|
modelStore.modalVisible = true;
|
||||||
|
modelStore.params = record;
|
||||||
|
modelStore.params.sttp = "WE";
|
||||||
|
modelStore.title = record.stnm;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看附件(占位)
|
||||||
|
const handleViewAttachment = (fid: string) => {
|
||||||
|
currentAttachmentFid.value = fid;
|
||||||
|
attachmentModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 监听 selectDateTime 变化 ====================
|
||||||
|
watch(
|
||||||
|
() => props.selectDateTime,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
yearValue.value = dayjs(newVal);
|
||||||
|
getDcpcOptions();
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 挂载时执行 ====================
|
||||||
|
onMounted(() => {
|
||||||
|
getDcpcOptions();
|
||||||
|
handleSearch();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ssst-dcgk-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
padding: 16px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
color: #2f6b98;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-placeholder,
|
||||||
|
.attachment-placeholder {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
319
frontend/src/modules/shuishengtaidiaochaduanmian/index.vue
Normal file
319
frontend/src/modules/shuishengtaidiaochaduanmian/index.vue
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem :title="title" @update-values="handlePanelChange">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
</SidePanelItem>
|
||||||
|
|
||||||
|
<!-- 水生生态调查概况弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
title="水生生态调查概况"
|
||||||
|
width="1536px"
|
||||||
|
:footer="null"
|
||||||
|
@cancel="handleModalClose"
|
||||||
|
>
|
||||||
|
<SsstdcgkTk
|
||||||
|
v-if="modalVisible"
|
||||||
|
:selectDateTime="datetimePicker.value"
|
||||||
|
:all="true"
|
||||||
|
/>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
inject,
|
||||||
|
defineOptions,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue';
|
||||||
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
import SsstdcgkTk from './SsstdcgkTk.vue';
|
||||||
|
import { wbsbGetKendoList, getDftYear } from '@/api/stdc';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Label } from 'cesium';
|
||||||
|
import { useDraggable } from '@/utils/drag';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
// 定义组件名
|
||||||
|
defineOptions({
|
||||||
|
name: 'shuishengshengtaijiance'
|
||||||
|
});
|
||||||
|
|
||||||
|
const title = ref('水生态生态调查断面 (23)个');
|
||||||
|
|
||||||
|
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||||
|
const baseid = ref('');
|
||||||
|
|
||||||
|
// ==================== 弹框状态 ====================
|
||||||
|
const modalVisible = ref(false);
|
||||||
|
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
|
||||||
|
// ==================== 响应式变量定义 ====================
|
||||||
|
// 选择器配置
|
||||||
|
const select = ref({
|
||||||
|
show: true,
|
||||||
|
value: undefined,
|
||||||
|
options: [],
|
||||||
|
picker: undefined,
|
||||||
|
format: undefined,
|
||||||
|
width: '120px'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日期选择器配置
|
||||||
|
const datetimePicker = ref({
|
||||||
|
show: true,
|
||||||
|
value: '',
|
||||||
|
format: 'YYYY',
|
||||||
|
picker: 'year' as const,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 图表(柱状图) ====================
|
||||||
|
const chartRef = ref<HTMLElement>();
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
|
// 柱状图数据(先写死,后续接入接口)
|
||||||
|
const chartData = [
|
||||||
|
{ name: '长江流域', value: 5 },
|
||||||
|
{ name: '黄河流域', value: 8 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 渲染柱状图
|
||||||
|
const renderChart = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
chartInstance.setOption(
|
||||||
|
{
|
||||||
|
legend: {
|
||||||
|
data: ['数量'],
|
||||||
|
top: '0',
|
||||||
|
left: 'center',
|
||||||
|
textStyle: { fontSize: 12, color: '#666' }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
textStyle: { color: '#fff', fontSize: 12 }
|
||||||
|
},
|
||||||
|
grid: { left: 40, right: 20, top: 40, bottom: 30 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: chartData.map(item => item.name),
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLabel: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLine: { lineStyle: { color: '#000000' } },
|
||||||
|
splitLine: { lineStyle: { color: '#bfbfbf', type: 'solid' } },
|
||||||
|
axisLabel: { fontSize: 12 }
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '数量',
|
||||||
|
type: 'bar',
|
||||||
|
data: chartData.map(item => item.value),
|
||||||
|
barWidth: 30,
|
||||||
|
itemStyle: { color: '#6395FA', borderRadius: [4, 4, 0, 0] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
if (chartInstance) chartInstance.dispose();
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
// 点击图表打开概况弹框(保留原卡片点击行为)
|
||||||
|
chartInstance.on('click', handleCardClick);
|
||||||
|
renderChart();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initChart();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
}, 200);
|
||||||
|
if (!resizeObserver && chartRef.value) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (chartInstance) chartInstance.resize();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(chartRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
chartInstance = null;
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
const handlePanelChange = (data: any) => {
|
||||||
|
console.log('当前所有控件状态:', data);
|
||||||
|
if (data.select) {
|
||||||
|
select.value.value = data.select;
|
||||||
|
}
|
||||||
|
if (data.datetime) {
|
||||||
|
datetimePicker.value.value = data.datetime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCardClick = () => {
|
||||||
|
console.log('卡片被点击,准备打开弹框');
|
||||||
|
console.log('modalVisible:', datetimePicker.value.value);
|
||||||
|
modalVisible.value = true;
|
||||||
|
console.log('modalVisible:', modalVisible.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
modalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 获取默认年份 ====================
|
||||||
|
const getDefaultYear = async () => {
|
||||||
|
if (!baseid.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// const params = {
|
||||||
|
// filter: { baseId: baseid.value },
|
||||||
|
// group: [{ dir: 'des', field: 'yr' }]
|
||||||
|
// };
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'baseId',
|
||||||
|
operator: 'eq',
|
||||||
|
dataType: 'string',
|
||||||
|
value: baseid.value
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
dir: 'des',
|
||||||
|
field: 'yr'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const res = await getDftYear(params);
|
||||||
|
const data = res?.data?.data?.[0] || {};
|
||||||
|
const allYears = Object.values(data);
|
||||||
|
|
||||||
|
if (allYears?.length) {
|
||||||
|
const last = (allYears as any[]).flat().sort().pop();
|
||||||
|
datetimePicker.value.value = last;
|
||||||
|
} else {
|
||||||
|
datetimePicker.value.value = moment().format('YYYY');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取默认年份失败:', error);
|
||||||
|
datetimePicker.value.value = moment().format('YYYY');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 获取选择器数据 ====================
|
||||||
|
const getSelectData = async () => {
|
||||||
|
let params = {
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: 'wbsType',
|
||||||
|
operator: 'eq',
|
||||||
|
value: 'PSB_RVCD'
|
||||||
|
},
|
||||||
|
baseid.value != 'all'
|
||||||
|
? {
|
||||||
|
field: 'objId',
|
||||||
|
operator: 'eq',
|
||||||
|
value: '水生生态调查情况'
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
].filter(Boolean)
|
||||||
|
},
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'orderIndex'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsCode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'wbsName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dir: 'asc',
|
||||||
|
field: 'objid'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groupResultFlat: true
|
||||||
|
};
|
||||||
|
const res = await wbsbGetKendoList(params);
|
||||||
|
let data = res?.data?.data;
|
||||||
|
select.value.options = data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
value: item.wbsCode,
|
||||||
|
label: item.wbsName
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||||
|
const injectedStation = inject<any>('dianZhanStation', null);
|
||||||
|
const stationSource = computed(
|
||||||
|
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== 监听基地变化 ====================
|
||||||
|
watch(
|
||||||
|
() => stationSource.value,
|
||||||
|
async newVal => {
|
||||||
|
if (newVal && newVal.wbsCode) {
|
||||||
|
baseid.value = newVal.wbsCode;
|
||||||
|
getSelectData();
|
||||||
|
getDefaultYear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -8,12 +8,16 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
const mapSwitchCompletedTick = ref(0);
|
const mapSwitchCompletedTick = ref(0);
|
||||||
// 3d 流域三维漫游
|
// 3d 流域三维漫游
|
||||||
const isRoaming = ref(false);
|
const isRoaming = ref(false);
|
||||||
|
// 智慧装备
|
||||||
|
const isSmartEquipment = ref(false);
|
||||||
// dim 切换时跳过漫游相机还原(由地图模式切换自行处理)
|
// dim 切换时跳过漫游相机还原(由地图模式切换自行处理)
|
||||||
const skipRoamCameraRestore = ref(false);
|
const skipRoamCameraRestore = ref(false);
|
||||||
// 搜索抽屉状态
|
// 搜索抽屉状态
|
||||||
const searchDrawerOpen = ref(false);
|
const searchDrawerOpen = ref(false);
|
||||||
// 全局兜底抽屉状态(AppMain 通用抽屉,给未自带抽屉的页面用)
|
// 全局兜底抽屉状态(AppMain 通用抽屉,给未自带抽屉的页面用)
|
||||||
const globalDrawerOpen = ref(false);
|
const globalDrawerOpen = ref(false);
|
||||||
|
// 是否处于锚点搜索状态(选中命中 DRAWER_STTP_CODES 的锚点时 true;清空/切页等 false)
|
||||||
|
const isSearchActive = ref(false);
|
||||||
|
|
||||||
// 切换抽屉状态
|
// 切换抽屉状态
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
@ -31,6 +35,10 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
const setGlobalDrawerOpen = (open: boolean) => {
|
const setGlobalDrawerOpen = (open: boolean) => {
|
||||||
globalDrawerOpen.value = open;
|
globalDrawerOpen.value = open;
|
||||||
};
|
};
|
||||||
|
// 设置锚点搜索状态
|
||||||
|
const setIsSearchActive = (active: boolean) => {
|
||||||
|
isSearchActive.value = active;
|
||||||
|
};
|
||||||
|
|
||||||
const markMapSwitchCompleted = () => {
|
const markMapSwitchCompleted = () => {
|
||||||
mapSwitchCompletedTick.value += 1;
|
mapSwitchCompletedTick.value += 1;
|
||||||
@ -39,6 +47,7 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
return {
|
return {
|
||||||
drawerOpen,
|
drawerOpen,
|
||||||
isRoaming,
|
isRoaming,
|
||||||
|
isSmartEquipment,
|
||||||
skipRoamCameraRestore,
|
skipRoamCameraRestore,
|
||||||
toggleDrawer,
|
toggleDrawer,
|
||||||
setDrawerOpen,
|
setDrawerOpen,
|
||||||
@ -46,6 +55,8 @@ export const useUiStore = defineStore('ui', () => {
|
|||||||
searchDrawerOpen,
|
searchDrawerOpen,
|
||||||
globalDrawerOpen,
|
globalDrawerOpen,
|
||||||
setGlobalDrawerOpen,
|
setGlobalDrawerOpen,
|
||||||
|
isSearchActive,
|
||||||
|
setIsSearchActive,
|
||||||
mapType,
|
mapType,
|
||||||
mapSwitchCompletedTick,
|
mapSwitchCompletedTick,
|
||||||
markMapSwitchCompleted
|
markMapSwitchCompleted
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">
|
<div class="shiXuMoXingGuanLi-page">时序模型管理</div>
|
||||||
时序模型管理
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.shiXuMoXingGuanLi-page {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
z-index: 2000;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">三维仿真</div>
|
<div class="sanWeiFangZhen-page">三维仿真</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.sanWeiFangZhen-page {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
z-index: 2000;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import { ref, computed } from 'vue';
|
|||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import SSSTJCQKTT from '@/modules/shuishengshengtaijiance/index.vue';
|
import SSSTJCQKTT from '@/modules/shuishengshengtaijiance/index.vue';
|
||||||
|
import Sstdcdm from '@/modules/shuishengtaidiaochaduanmian/index.vue';
|
||||||
import SSSTDCQK from '@/modules/shuiShengShengTaiDiaoCha/index.vue'; // 水生生态监测情况模块
|
import SSSTDCQK from '@/modules/shuiShengShengTaiDiaoCha/index.vue'; // 水生生态监测情况模块
|
||||||
import AlongFishMod from '@/modules/alongFishMod/index.vue'; //沿程鱼类变化情况
|
import AlongFishMod from '@/modules/alongFishMod/index.vue'; //沿程鱼类变化情况
|
||||||
|
import VideoInfo from '@/modules/rightSearchDrawer/monitoringTable/components/VideoInfo.vue';
|
||||||
import SSSTDCDB from './shuiShengShengTaiDiaoCha/SSSTDCDB.vue';
|
import SSSTDCDB from './shuiShengShengTaiDiaoCha/SSSTDCDB.vue';
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||||
import { useDraggable } from '@/utils/drag';
|
import { useDraggable } from '@/utils/drag';
|
||||||
@ -26,9 +28,12 @@ const handleOpenModal = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<SSSTJCQKTT />
|
<!-- <SSSTJCQKTT />
|
||||||
<SSSTDCQK />
|
<SSSTDCQK />
|
||||||
<AlongFishMod />
|
<AlongFishMod /> -->
|
||||||
|
<Sstdcdm />
|
||||||
|
<VideoInfo title="声呐(45个)" placeholder="请选择声呐" />
|
||||||
|
<VideoInfo title="水下摄像头(10个)" placeholder="请选择水下摄像头" />
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
|
|||||||
@ -1,31 +1,59 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
import { useUiStore } from '@/store/modules/ui';
|
import { useUiStore } from '@/store/modules/ui';
|
||||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
|
||||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||||
import ZhiWuYuanJianSheJiJieRuQingKuangBar from '@/modules/ZhenXiZhiWuYuanMod/ZhiWuYuanJianSheJiJieRuQingKuangBar/index.vue'; // 植物园建设及接入情况
|
import EquipmentStatistics from '@/modules/EquipmentStatistics/index.vue'; //设备统计
|
||||||
import Dwjzqk from '@/modules/ZhenXiZhiWuYuanMod/Dwjzqk/index.vue';
|
import BaseStatistics from '@/modules/BaseStatistics/index.vue'; //基础统计
|
||||||
import JZZQKJS from '@/modules/DongWuJiuZhuZhan/JiuZhuZhanQingKuangJieShao.vue'; // 救助站情况介绍
|
|
||||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
|
||||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const wbsCode = ref('all');
|
onMounted(() => {
|
||||||
watch(
|
uiStore.isSmartEquipment = true;
|
||||||
() => JidiSelectEventStore.selectedItem,
|
});
|
||||||
newVal => {
|
onUnmounted(() => {
|
||||||
console.log(newVal);
|
uiStore.isSmartEquipment = false;
|
||||||
if (newVal.wbsCode == 'all') {
|
});
|
||||||
wbsCode.value = 'all';
|
const filter2 = ref('jinshajiang');
|
||||||
} else {
|
const filter3 = ref('lianghekou');
|
||||||
wbsCode.value = newVal.wbsCode;
|
const filter4 = ref('station');
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 100">888123</div>
|
<div class="moduleContent">
|
||||||
|
<div class="leftContent">
|
||||||
|
<JidiSelectorMod />
|
||||||
|
</div>
|
||||||
|
<div class="selector">
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center">
|
||||||
|
<a-select v-model:value="filter2" style="width: 160px">
|
||||||
|
<a-select-option value="jinshajiang">金沙江干流</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filter3" style="width: 140px">
|
||||||
|
<a-select-option value="lianghekou">两河口</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filter4" style="width: 200px">
|
||||||
|
<a-select-option value="station">两河口库尾水温站</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rightContent">
|
||||||
|
<RightDrawer>
|
||||||
|
<EquipmentStatistics />
|
||||||
|
<BaseStatistics />
|
||||||
|
</RightDrawer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.moduleContent {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.selector {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 200px;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,544 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">过鱼设施</div>
|
<div class="guoYuSheShiYunXingXiaoGuoFenXi-page">
|
||||||
|
<!-- 顶部筛选栏 -->
|
||||||
|
<div class="top-bar">
|
||||||
|
<a-select v-model:value="filterVal1" style="width: 180px">
|
||||||
|
<a-select-option value="jsj">金沙江干流</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filterVal2" style="width: 140px">
|
||||||
|
<a-select-option value="lianghekou">两河口</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filterVal3" style="width: 180px">
|
||||||
|
<a-select-option value="fishlift">两河口升鱼机</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="dateRange"
|
||||||
|
picker="day"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区:两行各占 50% -->
|
||||||
|
<div class="rows-wrap">
|
||||||
|
<!-- 第一行 3栏 -->
|
||||||
|
<a-row :gutter="12" class="row-item">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<a-col :span="10" class="w-full h-full">
|
||||||
|
<a-card title="基本信息">
|
||||||
|
<div class="base-info">
|
||||||
|
<div class="base-info-img">
|
||||||
|
<img
|
||||||
|
src="https://picsum.photos/id/1039/220/160"
|
||||||
|
alt="设施图片"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="base-info-list">
|
||||||
|
<p>
|
||||||
|
<span class="label">设施名称:</span
|
||||||
|
><span class="value">盐锅峡入库水温站点</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">设施类系:</span
|
||||||
|
><span class="value">升鱼机</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">地址:</span
|
||||||
|
><span class="value">吉林市市辖区</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">所属流域:</span
|
||||||
|
><span class="value"
|
||||||
|
>长江流域-金沙江干流-雅砻江干流-雅砻江干流中游</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">建成日期:</span
|
||||||
|
><span class="value">2026-01-12</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">过鱼对象:</span
|
||||||
|
><span class="value"
|
||||||
|
>黄河裸裂尻鱼、花斑裸鲤、厚唇裸重唇鱼、极边扁咽齿鱼、骨唇黄河鱼、拟鲶高原鳅</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="label">过鱼规模:</span
|
||||||
|
><span class="value">4万尾</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 分析结论 -->
|
||||||
|
<a-col :span="6" class="w-full h-full">
|
||||||
|
<a-card title="分析结论">
|
||||||
|
<div class="analysis-conclusion">
|
||||||
|
<p>
|
||||||
|
两河口升鱼机共过鱼多少种多少尾,其中主要过鱼对象多少种多少尾。两河口升鱼机有效(大多数过鱼对象,过鱼数量达设计目标
|
||||||
|
/总体有效(有过鱼对象,过鱼数量未达设计目标)/无效(无过鱼对象)。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
两河口升鱼机过鱼效率为多少,过鱼效率较高(>50%)、一般(30%至50%)或较低(小于30%)。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
|
||||||
|
<!-- AI识别过鱼设施运行 -->
|
||||||
|
<a-col :span="8" class="w-full h-full">
|
||||||
|
<a-card title="AI识别过鱼设施运行">
|
||||||
|
<div class="run-grid">
|
||||||
|
<div class="run-item" v-for="(item, idx) in runList" :key="idx">
|
||||||
|
<div class="run-icon">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</div>
|
||||||
|
<div class="run-info">
|
||||||
|
<div class="run-title">{{ item.name }}</div>
|
||||||
|
<div class="run-count">
|
||||||
|
<span class="run-num">{{ item.count }}</span>
|
||||||
|
<span class="run-unit">次</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<!-- 第二行 4图表 -->
|
||||||
|
<a-row :gutter="12" class="row-item">
|
||||||
|
<!-- 过鱼种类分析 饼图 -->
|
||||||
|
<a-col :span="6" class="w-full h-full">
|
||||||
|
<a-card title="过鱼种类分析">
|
||||||
|
<div ref="pieChartRef" class="chart-box"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<!-- 过鱼对象分析 横向柱状 -->
|
||||||
|
<a-col :span="6" class="w-full h-full">
|
||||||
|
<a-card title="过鱼对象分析">
|
||||||
|
<div ref="barHChartRef" class="chart-box"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<!-- 过鱼节律分析 堆叠柱状 -->
|
||||||
|
<a-col :span="6" class="w-full h-full">
|
||||||
|
<a-card title="过鱼节律分析">
|
||||||
|
<div ref="stackBarRef" class="chart-box"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<!-- 过鱼规格分析 柱状图 -->
|
||||||
|
<a-col :span="6" class="w-full h-full">
|
||||||
|
<a-card title="过鱼规格分析">
|
||||||
|
<div ref="sizeBarRef" class="chart-box"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
FilterOutlined,
|
||||||
|
RocketOutlined,
|
||||||
|
CarOutlined,
|
||||||
|
// TruckOutlined,
|
||||||
|
SendOutlined,
|
||||||
|
CloudUploadOutlined
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
const filterVal1 = ref('金沙江干流');
|
||||||
|
const filterVal2 = ref('两河口');
|
||||||
|
const filterVal3 = ref('两河口升鱼机');
|
||||||
|
const dateRange = ref([
|
||||||
|
dayjs().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
dayjs().endOf('month').format('YYYY-MM-DD')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 运行统计数据
|
||||||
|
const runList = ref([
|
||||||
|
{ name: '赶鱼栅运行', count: 45, icon: FilterOutlined },
|
||||||
|
{ name: '升鱼斗运行', count: 45, icon: RocketOutlined },
|
||||||
|
{ name: 'AGV小车', count: 45, icon: CarOutlined },
|
||||||
|
{ name: '运鱼车运行', count: 45, icon: CarOutlined },
|
||||||
|
{ name: '运鱼船出港', count: 45, icon: SendOutlined },
|
||||||
|
{ name: '鱼道放流', count: 45, icon: CloudUploadOutlined }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 图表DOM
|
||||||
|
const pieChartRef = ref(null);
|
||||||
|
const barHChartRef = ref(null);
|
||||||
|
const stackBarRef = ref(null);
|
||||||
|
const sizeBarRef = ref(null);
|
||||||
|
|
||||||
|
let pieIns, barHIns, stackIns, sizeBarIns;
|
||||||
|
|
||||||
|
// 饼图 - 过鱼种类分析
|
||||||
|
const initPie = () => {
|
||||||
|
pieIns = echarts.init(pieChartRef.value);
|
||||||
|
pieIns.setOption({
|
||||||
|
tooltip: { trigger: 'item' },
|
||||||
|
legend: {
|
||||||
|
top: 10,
|
||||||
|
left: 'center',
|
||||||
|
data: ['过鱼对象种类', '非过鱼对象种类']
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: '65%',
|
||||||
|
data: [
|
||||||
|
{ name: '过鱼对象种类', value: 7, itemStyle: { color: '#5488eb' } },
|
||||||
|
{ name: '非过鱼对象种类', value: 6, itemStyle: { color: '#fab530' } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 横向柱状图 过鱼对象分析
|
||||||
|
const initHBar = () => {
|
||||||
|
barHIns = echarts.init(barHChartRef.value);
|
||||||
|
barHIns.setOption({
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { top: 10, left: 'center', data: ['上行数量', '下行数量'] },
|
||||||
|
grid: { left: 60, right: 30, top: 60, bottom: 30 },
|
||||||
|
xAxis: { type: 'value', max: 50000 },
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [
|
||||||
|
'短须颌须鮈',
|
||||||
|
'中华细鲫',
|
||||||
|
'湘华鲮',
|
||||||
|
'厚唇鲂',
|
||||||
|
'南汀爬鳅',
|
||||||
|
'武义吻虾虎鱼'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '上行数量',
|
||||||
|
type: 'bar',
|
||||||
|
data: [22000, 8000, 30000, 46000, 7000, 48000],
|
||||||
|
color: '#5488eb'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '下行数量',
|
||||||
|
type: 'bar',
|
||||||
|
data: [3000, 4000, 16000, 26000, 2000, 29000],
|
||||||
|
color: '#37c8c4'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 堆叠柱状 过鱼节律分析
|
||||||
|
const initStackBar = () => {
|
||||||
|
stackIns = echarts.init(stackBarRef.value);
|
||||||
|
stackIns.setOption({
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { top: 10, left: 'center', data: ['上行数量', '下行数量'] },
|
||||||
|
grid: { left: 40, right: 20, top: 60, bottom: 30 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [
|
||||||
|
'1月',
|
||||||
|
'2月',
|
||||||
|
'3月',
|
||||||
|
'4月',
|
||||||
|
'5月',
|
||||||
|
'6月',
|
||||||
|
'7月',
|
||||||
|
'8月',
|
||||||
|
'9月',
|
||||||
|
'10月',
|
||||||
|
'11月',
|
||||||
|
'12月'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '上行数量',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: [7, 8, 11, 14, 18, 22, 27, 29, 24, 18, 12, 9],
|
||||||
|
color: '#f26551'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '下行数量',
|
||||||
|
type: 'bar',
|
||||||
|
stack: 'total',
|
||||||
|
data: [4, 5, 7, 9, 12, 15, 16, 16, 14, 11, 6, 4],
|
||||||
|
color: '#46bc75'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 柱状图 过鱼规格分析
|
||||||
|
const initSizeBar = () => {
|
||||||
|
sizeBarIns = echarts.init(sizeBarRef.value);
|
||||||
|
sizeBarIns.setOption({
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: { top: 10, left: 'center', data: ['占比'] },
|
||||||
|
grid: { left: 40, right: 20, top: 60, bottom: 30 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: ['5', '10', '15', '20', '25', '30', '35', '40', '45', '50']
|
||||||
|
},
|
||||||
|
yAxis: { type: 'value', name: '(mm)' },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '占比',
|
||||||
|
type: 'bar',
|
||||||
|
data: [12, 2, 11, 8, 15, 21, 5, 18, 2, 6],
|
||||||
|
color: '#5488eb'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图表随容器尺寸自适应(高分辨率屏幕、窗口缩放均跟随)
|
||||||
|
const handleResize = () => {
|
||||||
|
pieIns?.resize();
|
||||||
|
barHIns?.resize();
|
||||||
|
stackIns?.resize();
|
||||||
|
sizeBarIns?.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
let resizeObservers = [];
|
||||||
|
const bindResize = () => {
|
||||||
|
const list = [
|
||||||
|
{ el: pieChartRef.value, ins: pieIns },
|
||||||
|
{ el: barHChartRef.value, ins: barHIns },
|
||||||
|
{ el: stackBarRef.value, ins: stackIns },
|
||||||
|
{ el: sizeBarRef.value, ins: sizeBarIns }
|
||||||
|
];
|
||||||
|
list.forEach(({ el, ins }) => {
|
||||||
|
if (!el || !ins) return;
|
||||||
|
const observer = new ResizeObserver(() => ins.resize());
|
||||||
|
observer.observe(el);
|
||||||
|
resizeObservers.push(observer);
|
||||||
|
});
|
||||||
|
// 窗口缩放兜底
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
initPie();
|
||||||
|
initHBar();
|
||||||
|
initStackBar();
|
||||||
|
initSizeBar();
|
||||||
|
bindResize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 销毁释放
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
resizeObservers.forEach(observer => observer.disconnect());
|
||||||
|
resizeObservers = [];
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
pieIns?.dispose();
|
||||||
|
barHIns?.dispose();
|
||||||
|
stackIns?.dispose();
|
||||||
|
sizeBarIns?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.guoYuSheShiYunXingXiaoGuoFenXi-page {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
z-index: 2000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.top-bar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.rows-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.row-item {
|
||||||
|
flex: 4;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
:deep(.ant-card) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.row-item + .row-item {
|
||||||
|
margin-top: 16px;
|
||||||
|
flex: 6;
|
||||||
|
}
|
||||||
|
.base-info {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.base-info-img {
|
||||||
|
max-width: 320px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.base-info-list {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 2;
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #666;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 80px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.analysis-conclusion {
|
||||||
|
background: #fff7e6;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.7;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p + p {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.run-grid {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
// grid-template-columns: repeat(3, 1fr);
|
||||||
|
// grid-auto-rows: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.run-item {
|
||||||
|
width: calc(33.33% - 12px);
|
||||||
|
height: 90px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 0;
|
||||||
|
background: #f3f5fa;
|
||||||
|
}
|
||||||
|
.run-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #e2e9f8;
|
||||||
|
color: #1677ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.run-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.run-title {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.run-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
.run-num {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000000;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-unit {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chart-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.ant-card-head) {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 10px;
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
left: 0;
|
||||||
|
top: 6px;
|
||||||
|
width: 4px;
|
||||||
|
height: 14px;
|
||||||
|
background: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,417 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">预测预警</div>
|
<div
|
||||||
|
class="yuCeYuJing-page"
|
||||||
|
style="padding: 16px; background: #f5f7fa; min-height: 100vh"
|
||||||
|
>
|
||||||
|
<!-- 顶部标签 + 筛选栏 -->
|
||||||
|
<div class="top-bar">
|
||||||
|
<a-tabs v-model:activeKey="activeTab">
|
||||||
|
<a-tab-pane key="waterTemp" tab="水温预测" />
|
||||||
|
<a-tab-pane key="verticalTemp" tab="垂向水温预测" />
|
||||||
|
<a-tab-pane key="waterQuality" tab="水质预测" />
|
||||||
|
<a-tab-pane key="algae" tab="水华预测" />
|
||||||
|
</a-tabs>
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center">
|
||||||
|
<a-select v-model:value="filter1" style="width: 160px">
|
||||||
|
<a-select-option value="hydropower">水电基地</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filter2" style="width: 160px">
|
||||||
|
<a-select-option value="jinshajiang">金沙江干流</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filter3" style="width: 140px">
|
||||||
|
<a-select-option value="lianghekou">两河口</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model:value="filter4" style="width: 200px">
|
||||||
|
<a-select-option value="station">两河口库尾水温站</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第一行三栏 -->
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-card title="基本信息" :bordered="false">
|
||||||
|
<div class="info-item">
|
||||||
|
<a-row :gutter="8" class="mb-2">
|
||||||
|
<a-col :span="8">
|
||||||
|
<div>测站名称:</div>
|
||||||
|
<span>盐锅峡入库水温站点</span></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="16">
|
||||||
|
<div>所属流域:</div>
|
||||||
|
<span
|
||||||
|
>长江流域-金沙江干流-雅磨江干流-雅磨江干流中游</span
|
||||||
|
></a-col
|
||||||
|
>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="8" class="my-2">
|
||||||
|
<a-col :span="8">
|
||||||
|
<div>所属电站:</div>
|
||||||
|
<span>两河口</span></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="16">
|
||||||
|
<div>站址:</div>
|
||||||
|
<span>甘肃省临夏回族自治州永靖县</span></a-col
|
||||||
|
>
|
||||||
|
</a-row>
|
||||||
|
<a-row :gutter="8" class="my-2">
|
||||||
|
<a-col :span="8">
|
||||||
|
<div>建成日期:</div>
|
||||||
|
<span>2023-07-01</span></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="8">
|
||||||
|
<div>测站类型:</div>
|
||||||
|
<span>河道水温</span></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="8">
|
||||||
|
<div>监测方式:</div>
|
||||||
|
<span>自动</span></a-col
|
||||||
|
>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<!-- 算法信息 -->
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-card title="算法信息" :bordered="false">
|
||||||
|
<div style="min-height: 90px"></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24" class="mt-4">
|
||||||
|
<!-- 预测结果图表区域 -->
|
||||||
|
<a-card title="预测结果" :bordered="false">
|
||||||
|
<div class="chart-container">
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center">
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="dateRange"
|
||||||
|
picker="day"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
<a-tabs v-model:activeKey="activeTabTime">
|
||||||
|
<a-tab-pane key="7day" tab="未来7天"></a-tab-pane>
|
||||||
|
<a-tab-pane key="15day" tab="未来15天"></a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<a-button type="primary">开始预测</a-button>
|
||||||
|
</div>
|
||||||
|
<a-button type="primary">历史预测</a-button>
|
||||||
|
</div>
|
||||||
|
<!-- echarts容器 -->
|
||||||
|
<div
|
||||||
|
ref="chartRef"
|
||||||
|
style="width: 100%; height: calc(100vh - 506px)"
|
||||||
|
></div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
<!-- 预测分析 -->
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-card
|
||||||
|
title="预测分析"
|
||||||
|
:body-style="{
|
||||||
|
padding: '10px 24px',
|
||||||
|
height: 'calc(100vh - 260px)',
|
||||||
|
overflowY: 'auto'
|
||||||
|
}"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<div style="font-size: 13px; line-height: 1.7">
|
||||||
|
<h4
|
||||||
|
style="
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #005293;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
结果分析
|
||||||
|
</h4>
|
||||||
|
<p><strong>1.技术工艺与设施设备</strong></p>
|
||||||
|
<p>
|
||||||
|
人工繁育技术瓶颈:检查是否存在珍稀特有鱼类人工驯养和繁育技术不成熟的问题。部分鱼类的繁殖条件(如性腺发育、产卵行为)可能未被完全掌握。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
设施设备与工艺缺陷:检查生产设备是否老化。同时,需评估温度控制系统、供水供电方案、循环水处理工艺等关键设施是否满足要求,是否存在设计缺陷。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
养殖用水与模式:核查传统流水养殖模式是否效率低下、耗水过多,评估循环水养殖系统处理工艺的针对性是否不足。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>2.运行管理与操作规范</strong></p>
|
||||||
|
<p>
|
||||||
|
亲本数量与质量:检查亲鱼(种鱼)数量是否充足、质量是否健康。亲本不足会直接影响苗种产量。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
生产过程控制:核查苗种培育、鱼病防治、野化驯养等关键环节。温度和饵料控制不当、疾病爆发或鱼苗未经过野化训练,都会严重影响成活率和放流后的适应能力。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4
|
||||||
|
style="
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #005293;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
意见建议
|
||||||
|
</h4>
|
||||||
|
<p><strong>1.加强科研攻关与技术升级</strong></p>
|
||||||
|
<p>
|
||||||
|
针对繁育技术瓶颈,联动科研单位加大投入进行攻关。同时,对老化或低效的生产设备进行更新换代,例如引入更高效的循环水处理系统。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>2.优化运行管理与操作规范</strong></p>
|
||||||
|
<p>
|
||||||
|
强化亲本培育,确保有足够数量和质量健康的种鱼。细化生产流程管理,严格控制水温、饵料、疾病防治等关键环节。建立健全监测评估体系,对放流后的鱼苗进行标记回捕、跟踪监测,用数据指导后续工作。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>3.推动外部环境改善与流域协同</strong></p>
|
||||||
|
<p>
|
||||||
|
积极与相关部门沟通,确保生态流量的足额下泄。在更大范围内,推动流域性的统筹规划,与上下游电站协同开展生态保护。同时,科学评估并修复或改善鱼类栖息地。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
|
// 使用原生echarts引入方式
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const activeTab = ref('waterTemp');
|
||||||
|
const filter1 = ref('水电基地');
|
||||||
|
const filter2 = ref('金沙江干流');
|
||||||
|
const filter3 = ref('两河口');
|
||||||
|
const filter4 = ref('两河口库尾水温站');
|
||||||
|
const dateRange = ref([
|
||||||
|
dayjs().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
dayjs().endOf('month').format('YYYY-MM-DD')
|
||||||
|
]);
|
||||||
|
const activeTabTime = ref('7day');
|
||||||
|
|
||||||
|
const chartRef = ref(null);
|
||||||
|
let chartInstance = null;
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
const option = {
|
||||||
|
tooltip: { trigger: 'axis' },
|
||||||
|
legend: {
|
||||||
|
data: [{ name: '历史水温' }, { name: '预测水温' }]
|
||||||
|
},
|
||||||
|
grid: { left: 40, right: 30, top: 60, bottom: 40 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: [
|
||||||
|
'05-01',
|
||||||
|
'05-02',
|
||||||
|
'05-03',
|
||||||
|
'05-04',
|
||||||
|
'05-05',
|
||||||
|
'05-06',
|
||||||
|
'05-07',
|
||||||
|
'05-08',
|
||||||
|
'05-09',
|
||||||
|
'05-10',
|
||||||
|
'05-11',
|
||||||
|
'05-12',
|
||||||
|
'05-13',
|
||||||
|
'05-14',
|
||||||
|
'05-15',
|
||||||
|
'05-16',
|
||||||
|
'05-17',
|
||||||
|
'05-18',
|
||||||
|
'05-19',
|
||||||
|
'05-20'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 20
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '历史水温',
|
||||||
|
type: 'line',
|
||||||
|
data: [
|
||||||
|
12.3,
|
||||||
|
10,
|
||||||
|
8.8,
|
||||||
|
10,
|
||||||
|
7.2,
|
||||||
|
6,
|
||||||
|
15,
|
||||||
|
11,
|
||||||
|
16,
|
||||||
|
8.6,
|
||||||
|
18,
|
||||||
|
15,
|
||||||
|
20.2,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
lineStyle: { color: '#4086e8' },
|
||||||
|
itemStyle: { color: '#4086e8' },
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '预测水温',
|
||||||
|
type: 'line',
|
||||||
|
data: [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
18.2,
|
||||||
|
17.5,
|
||||||
|
16.2,
|
||||||
|
18.5,
|
||||||
|
18.6,
|
||||||
|
11,
|
||||||
|
16.3
|
||||||
|
],
|
||||||
|
lineStyle: { color: '#4086e8', type: 'dashed' },
|
||||||
|
itemStyle: { color: '#4086e8' },
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
markLine: {
|
||||||
|
silent: true,
|
||||||
|
data: [{ xAxis: '05-14', lineStyle: { color: 'red', type: 'dashed' } }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 窗口缩放自适应
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
initChart();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 销毁实例防止内存泄漏
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.yuCeYuJing-page {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
.top-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
:deep(.ant-tabs-nav) {
|
||||||
|
height: 32px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 0px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.ant-tabs-tab-active {
|
||||||
|
background: #ffffff !important;
|
||||||
|
}
|
||||||
|
.ant-tabs-ink-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ant-tabs-nav-wrap {
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
background: #f0f2f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.ant-card-head) {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 10px;
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
left: 0;
|
||||||
|
top: 6px;
|
||||||
|
width: 4px;
|
||||||
|
height: 14px;
|
||||||
|
background: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.info-item {
|
||||||
|
.ant-col {
|
||||||
|
display: flex;
|
||||||
|
div {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 72px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(148, 148, 148);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(54, 54, 54);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chart-container {
|
||||||
|
position: absolute;
|
||||||
|
left: 20%;
|
||||||
|
top: 10px;
|
||||||
|
width: 80%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moduleContent" style="color: red; z-index: 1000">
|
<div class="zhiNengBaoGaoShengCheng-page">智能报告生成</div>
|
||||||
智能报告生成
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.zhiNengBaoGaoShengCheng-page {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: all;
|
||||||
|
z-index: 2000;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user