添加新页面,搜索展开右侧弹框
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>
|
||||
<div class="global-right-drawer">
|
||||
<div
|
||||
class="global-right-drawer"
|
||||
:class="{ 'is-open': uiStore.globalDrawerOpen }"
|
||||
>
|
||||
<!-- 仅搜索状态下且全局抽屉被收起时,提供重新展开入口 -->
|
||||
<div
|
||||
@click="handleToggle"
|
||||
class="drawerController1"
|
||||
v-if="!uiStore.globalDrawerOpen"
|
||||
v-if="uiStore.isSearchActive && !uiStore.globalDrawerOpen"
|
||||
>
|
||||
<img src="../../assets/components/arrow-left.png" alt="" />
|
||||
</div>
|
||||
@ -51,7 +55,12 @@ const handleToggle = () => {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
// 收起时不拦截点击,点击穿透到页面局部抽屉;展开时恢复交互
|
||||
pointer-events: none;
|
||||
|
||||
&.is-open {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.drawerController1 {
|
||||
width: 18px;
|
||||
@ -61,13 +70,14 @@ const handleToggle = () => {
|
||||
z-index: 10;
|
||||
height: 88px;
|
||||
line-height: 88px;
|
||||
top: 45%;
|
||||
top: 44%;
|
||||
vertical-align: middle;
|
||||
background-image: url(../../assets/components/bg-toggle.e1dabcf3.svg);
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.drawerController {
|
||||
@ -86,6 +96,7 @@ const handleToggle = () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -243,6 +243,7 @@ const columns = [
|
||||
}
|
||||
}
|
||||
];
|
||||
const planFtp = ref('');
|
||||
const queryBtn = () => {
|
||||
console.log('5');
|
||||
const filter = {
|
||||
@ -292,30 +293,30 @@ const queryBtn = () => {
|
||||
nextTick(() => {
|
||||
tableRef.value?.getList(filter);
|
||||
});
|
||||
// const params = {
|
||||
// filter: {
|
||||
// logic: 'and',
|
||||
// filters: [
|
||||
// {
|
||||
// field: 'stcd',
|
||||
// operator: 'eq',
|
||||
// dataType: 'string',
|
||||
// value: modelStore.params.stcd
|
||||
// },
|
||||
// {
|
||||
// field: 'yr',
|
||||
// operator: 'eq',
|
||||
// dataType: 'string',
|
||||
// value: searchParams.value.tm.format('YYYY')
|
||||
// },
|
||||
// { field: 'direction', operator: 'eq', dataType: 'string', value: '0' }
|
||||
// ]
|
||||
// }
|
||||
// };
|
||||
// getRunStateSecondPlan(params).then(res => {
|
||||
// console.log(res);
|
||||
// planFtp.value = res.data.ftp || '暂无数据';
|
||||
// });
|
||||
const params = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
{
|
||||
field: 'stcd',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: modelStore.params.stcd
|
||||
},
|
||||
{
|
||||
field: 'yr',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: searchParams.value.tm.format('YYYY')
|
||||
},
|
||||
{ field: 'direction', operator: 'eq', dataType: 'string', value: '0' }
|
||||
]
|
||||
}
|
||||
};
|
||||
getRunStateSecondPlan(params).then(res => {
|
||||
console.log(res);
|
||||
planFtp.value = res.data.ftp || '暂无数据';
|
||||
});
|
||||
};
|
||||
const initData = async () => {
|
||||
queryBtn();
|
||||
|
||||
@ -68,21 +68,34 @@ const getData = async () => {
|
||||
|
||||
const baseFilters: any[] = [
|
||||
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' },
|
||||
{ field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }
|
||||
{
|
||||
field: 'prsc',
|
||||
operator: 'in',
|
||||
dataType: 'string',
|
||||
value: ['1', '2', '3']
|
||||
}
|
||||
];
|
||||
if (baseid.value !== 'all') {
|
||||
baseFilters.unshift({ field: 'baseId', operator: 'eq', dataType: 'string', value: baseid.value });
|
||||
baseFilters.unshift({
|
||||
field: 'baseId',
|
||||
operator: 'eq',
|
||||
dataType: 'string',
|
||||
value: baseid.value
|
||||
});
|
||||
}
|
||||
|
||||
const params1 = {
|
||||
filter: {
|
||||
logic: 'and',
|
||||
filters: [
|
||||
...baseFilters,
|
||||
{ field: 'ttpwr', operator: 'isnotnull' }
|
||||
]
|
||||
filters: [...baseFilters, { field: 'ttpwr', operator: 'isnotnull' }]
|
||||
},
|
||||
group: [{ dir: 'desc', field: 'bldsttCcode', aggregates: [{ field: 'ttpwr', aggregate: 'sum' }] }]
|
||||
group: [
|
||||
{
|
||||
dir: 'desc',
|
||||
field: 'bldsttCcode',
|
||||
aggregates: [{ field: 'ttpwr', aggregate: 'sum' }]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const params2 = {
|
||||
@ -118,7 +131,8 @@ const getData = async () => {
|
||||
[...engTotalList, ...ttpwrlist].reduce((result: any, item: any) => {
|
||||
const { key, engTotal = 0, ttpwr = 0 } = item;
|
||||
const existingItem = result[key] || { key, engTotal: 0, ttpwr: 0 };
|
||||
existingItem.bldstt = item.key == 0 ? '未建' : item.key == 1 ? '在建' : '已建';
|
||||
existingItem.bldstt =
|
||||
item.key == 0 ? '未建' : item.key == 1 ? '在建' : '已建';
|
||||
existingItem.engTotal += engTotal;
|
||||
existingItem.ttpwr += ttpwr;
|
||||
return { ...result, [key]: existingItem };
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="map-filter-container" v-show="!uiStore.isRoaming">
|
||||
<div
|
||||
class="map-filter-container"
|
||||
v-show="!uiStore.isRoaming && !uiStore.isSmartEquipment"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<a-form :model="formModel">
|
||||
<a-row :gutter="10">
|
||||
@ -151,7 +154,8 @@ const DRAWER_STTP_CODES = [
|
||||
'ZQ',
|
||||
'VP',
|
||||
'VA',
|
||||
'EQ'
|
||||
'EQ',
|
||||
'FB'
|
||||
];
|
||||
const siteRangePicker = [
|
||||
{ label: '全部', value: 'all' },
|
||||
@ -244,6 +248,7 @@ watch(
|
||||
anchorPointSelect: null,
|
||||
fishSurveyZhuanZhi: false
|
||||
};
|
||||
uiStore.setIsSearchActive(false);
|
||||
uiStore.setGlobalDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setDrawerOpen(true);
|
||||
@ -615,6 +620,7 @@ watch(
|
||||
return;
|
||||
}
|
||||
formModel.value.anchorPointSelect = null;
|
||||
uiStore.setIsSearchActive(false);
|
||||
uiStore.setGlobalDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setDrawerOpen(true);
|
||||
@ -660,6 +666,7 @@ watch(
|
||||
|
||||
const handleCapacityChange = (value: string) => {
|
||||
formModel.value.anchorPointSelect = null;
|
||||
uiStore.setIsSearchActive(false);
|
||||
uiStore.setGlobalDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setDrawerOpen(true);
|
||||
@ -691,7 +698,8 @@ const focusSelectedAnchorPoint = () => {
|
||||
// 抽屉是否弹出由选中点位的 sttpCode 决定(命中 DRAWER_STTP_CODES 才弹)。
|
||||
const handleAnchorPointChange = (value: string) => {
|
||||
if (value == null) {
|
||||
// 清空:先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
||||
// 清空:退出搜索状态,先收起全局抽屉,等完全关闭后再恢复页面自带抽屉
|
||||
uiStore.setIsSearchActive(false);
|
||||
uiStore.setGlobalDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setDrawerOpen(true);
|
||||
@ -705,15 +713,17 @@ const handleAnchorPointChange = (value: string) => {
|
||||
const sttpCode = String(
|
||||
matched?.sttpCode || matched?.sttp || ''
|
||||
).toUpperCase();
|
||||
// 非抽屉图层:不弹抽屉,收起全局抽屉后恢复页面自带抽屉
|
||||
// 非抽屉图层:不弹抽屉,退出搜索状态,收起全局抽屉后恢复页面自带抽屉
|
||||
if (!DRAWER_STTP_CODES.includes(sttpCode)) {
|
||||
uiStore.setIsSearchActive(false);
|
||||
uiStore.setGlobalDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setDrawerOpen(true);
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
// 需要抽屉:先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
||||
// 需要抽屉:进入搜索状态,先收起页面自带抽屉,等完全关闭后再展示全局抽屉
|
||||
uiStore.setIsSearchActive(true);
|
||||
uiStore.setDrawerOpen(false);
|
||||
setTimeout(() => {
|
||||
uiStore.setGlobalDrawerOpen(true);
|
||||
|
||||
@ -34,7 +34,6 @@ const isZhiNengPeiTu = computed(() => {
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<!-- 全局抽屉:承载锚点搜索内容(基本信息 + 监测表格),由锚点下拉 sttpCode 驱动打开 -->
|
||||
<div class="global-drawer-wrap">
|
||||
<GlobalRightDrawer>
|
||||
<basicInfoMod />
|
||||
@ -76,6 +75,7 @@ const isZhiNengPeiTu = computed(() => {
|
||||
width: 450px;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
pointer-events: all;
|
||||
// 收起时整体不拦截点击,不遮挡页面局部抽屉;展开状态由 GlobalRightDrawer 内部 is-open 控制
|
||||
pointer-events: none;
|
||||
}
|
||||
</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,6 +14,7 @@
|
||||
<a-row class="px-2">
|
||||
<a-col :span="item.col" v-for="(item, index) in formDiv" :key="index">
|
||||
<a-form-item :label="item.label" :title="info[item.value]">
|
||||
<a-tooltip :title="info[item.value]">
|
||||
<span v-if="item.type == 'date'">
|
||||
{{
|
||||
info[item.value]
|
||||
@ -22,6 +23,7 @@
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{ info[item.value] || '-' }} </span>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</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>
|
||||
<div>
|
||||
<!--
|
||||
1.增殖放流 接口
|
||||
2. 生态调查 陆生 水生
|
||||
-->
|
||||
<!-- 电站运行过程线 -->
|
||||
<monitorInfoMod v-if="sttpValue == 'ENG'" />
|
||||
<!-- 水温监控 -->
|
||||
@ -21,7 +25,17 @@
|
||||
<!-- 生态流量泄放设施 EQ -->
|
||||
<FlowDischarge v-if="sttpValue == 'EQ'" />
|
||||
<!-- 鱼类增殖站 -->
|
||||
<NormalOperationData v-if="sttpValue == 'FB'" />
|
||||
<!-- 实时视频 -->
|
||||
<VideoInfo
|
||||
title="实时视频"
|
||||
placeholder="请选择视频"
|
||||
v-if="sttpValue == 'FB'"
|
||||
/>
|
||||
|
||||
<!-- 生态调查 -->
|
||||
<!-- <WaterTemperature v-if="sttpValue == 'WE'" /> -->
|
||||
<!-- 生态调查 陆生 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -38,6 +52,8 @@ import FlowMeasure from '@/modules/rightSearchDrawer/monitoringTable/components/
|
||||
import Plant from '@/modules/rightSearchDrawer/monitoringTable/components/Plant.vue';
|
||||
import Animal from '@/modules/rightSearchDrawer/monitoringTable/components/Animal.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 sttpValue = ref('');
|
||||
|
||||
@ -324,7 +324,9 @@ const getSelectData = async () => {
|
||||
|
||||
// 监听电站变化(优先使用父组件 provide 的电站上下文,兜底 jidiStore)
|
||||
const injectedStation = inject<any>('dianZhanStation', null);
|
||||
const stationSource = computed(() => injectedStation?.value || JidiSelectEventStore.selectedItem);
|
||||
const stationSource = computed(
|
||||
() => injectedStation?.value || JidiSelectEventStore.selectedItem
|
||||
);
|
||||
|
||||
// ==================== 监听基地变化 ====================
|
||||
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);
|
||||
// 3d 流域三维漫游
|
||||
const isRoaming = ref(false);
|
||||
// 智慧装备
|
||||
const isSmartEquipment = ref(false);
|
||||
// dim 切换时跳过漫游相机还原(由地图模式切换自行处理)
|
||||
const skipRoamCameraRestore = ref(false);
|
||||
// 搜索抽屉状态
|
||||
const searchDrawerOpen = ref(false);
|
||||
// 全局兜底抽屉状态(AppMain 通用抽屉,给未自带抽屉的页面用)
|
||||
const globalDrawerOpen = ref(false);
|
||||
// 是否处于锚点搜索状态(选中命中 DRAWER_STTP_CODES 的锚点时 true;清空/切页等 false)
|
||||
const isSearchActive = ref(false);
|
||||
|
||||
// 切换抽屉状态
|
||||
const toggleDrawer = () => {
|
||||
@ -31,6 +35,10 @@ export const useUiStore = defineStore('ui', () => {
|
||||
const setGlobalDrawerOpen = (open: boolean) => {
|
||||
globalDrawerOpen.value = open;
|
||||
};
|
||||
// 设置锚点搜索状态
|
||||
const setIsSearchActive = (active: boolean) => {
|
||||
isSearchActive.value = active;
|
||||
};
|
||||
|
||||
const markMapSwitchCompleted = () => {
|
||||
mapSwitchCompletedTick.value += 1;
|
||||
@ -39,6 +47,7 @@ export const useUiStore = defineStore('ui', () => {
|
||||
return {
|
||||
drawerOpen,
|
||||
isRoaming,
|
||||
isSmartEquipment,
|
||||
skipRoamCameraRestore,
|
||||
toggleDrawer,
|
||||
setDrawerOpen,
|
||||
@ -46,6 +55,8 @@ export const useUiStore = defineStore('ui', () => {
|
||||
searchDrawerOpen,
|
||||
globalDrawerOpen,
|
||||
setGlobalDrawerOpen,
|
||||
isSearchActive,
|
||||
setIsSearchActive,
|
||||
mapType,
|
||||
mapSwitchCompletedTick,
|
||||
markMapSwitchCompleted
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
<template>
|
||||
<div class="moduleContent" style="color: red; z-index: 1000">
|
||||
时序模型管理
|
||||
</div>
|
||||
<div class="shiXuMoXingGuanLi-page">时序模型管理</div>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<div class="moduleContent" style="color: red; z-index: 1000">三维仿真</div>
|
||||
<div class="sanWeiFangZhen-page">三维仿真</div>
|
||||
</template>
|
||||
|
||||
<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 RightDrawer from '@/components/RightDrawer/index.vue';
|
||||
import SSSTJCQKTT from '@/modules/shuishengshengtaijiance/index.vue';
|
||||
import Sstdcdm from '@/modules/shuishengtaidiaochaduanmian/index.vue';
|
||||
import SSSTDCQK from '@/modules/shuiShengShengTaiDiaoCha/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 { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
import { useDraggable } from '@/utils/drag';
|
||||
@ -26,9 +28,12 @@ const handleOpenModal = () => {
|
||||
</div>
|
||||
<div class="rightContent">
|
||||
<RightDrawer>
|
||||
<SSSTJCQKTT />
|
||||
<!-- <SSSTJCQKTT />
|
||||
<SSSTDCQK />
|
||||
<AlongFishMod />
|
||||
<AlongFishMod /> -->
|
||||
<Sstdcdm />
|
||||
<VideoInfo title="声呐(45个)" placeholder="请选择声呐" />
|
||||
<VideoInfo title="水下摄像头(10个)" placeholder="请选择水下摄像头" />
|
||||
</RightDrawer>
|
||||
</div>
|
||||
<div class="center">
|
||||
|
||||
@ -1,31 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useUiStore } from '@/store/modules/ui';
|
||||
import basicInfoMod from '@/modules/rightSearchDrawer/basicInfo/index.vue';
|
||||
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
|
||||
import RightDrawer from '@/components/RightDrawer/index.vue';
|
||||
import ZhiWuYuanJianSheJiJieRuQingKuangBar from '@/modules/ZhenXiZhiWuYuanMod/ZhiWuYuanJianSheJiJieRuQingKuangBar/index.vue'; // 植物园建设及接入情况
|
||||
import Dwjzqk from '@/modules/ZhenXiZhiWuYuanMod/Dwjzqk/index.vue';
|
||||
import JZZQKJS from '@/modules/DongWuJiuZhuZhan/JiuZhuZhanQingKuangJieShao.vue'; // 救助站情况介绍
|
||||
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
|
||||
const JidiSelectEventStore = useJidiSelectEventStore();
|
||||
import EquipmentStatistics from '@/modules/EquipmentStatistics/index.vue'; //设备统计
|
||||
import BaseStatistics from '@/modules/BaseStatistics/index.vue'; //基础统计
|
||||
const uiStore = useUiStore();
|
||||
const wbsCode = ref('all');
|
||||
watch(
|
||||
() => JidiSelectEventStore.selectedItem,
|
||||
newVal => {
|
||||
console.log(newVal);
|
||||
if (newVal.wbsCode == 'all') {
|
||||
wbsCode.value = 'all';
|
||||
} else {
|
||||
wbsCode.value = newVal.wbsCode;
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
onMounted(() => {
|
||||
uiStore.isSmartEquipment = true;
|
||||
});
|
||||
onUnmounted(() => {
|
||||
uiStore.isSmartEquipment = false;
|
||||
});
|
||||
const filter2 = ref('jinshajiang');
|
||||
const filter3 = ref('lianghekou');
|
||||
const filter4 = ref('station');
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<div class="moduleContent" style="color: red; z-index: 1000">
|
||||
智能报告生成
|
||||
</div>
|
||||
<div class="zhiNengBaoGaoShengCheng-page">智能报告生成</div>
|
||||
</template>
|
||||
|
||||
<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