添加新页面,搜索展开右侧弹框

This commit is contained in:
扈兆增 2026-08-20 16:55:09 +08:00
parent 800a2665a0
commit dca960b986
31 changed files with 2834 additions and 314 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

File diff suppressed because one or more lines are too long

View 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>

View File

@ -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: all;
// 穿
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;
}
}

View File

@ -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();

View File

@ -1,25 +1,25 @@
<!-- developStatusChart/index.vue -->
<template>
<div class="basic_body">
<div ref="chartContainer" class="chart-container"></div>
<!-- 水电开发情况弹窗 -->
<a-modal
v-model:open="modalVisible"
title="水电开发情况"
width="80%"
:footer="null"
:destroy-on-close="true"
>
<ShuiDianKaiFQKTwoLayer
v-if="modalVisible"
:defaultJidiInfo="modalProps.defaultJidiInfo"
:seriesName="modalProps.seriesName"
:stateName="modalProps.stateName"
:defaultTab="modalProps.defaultTab"
/>
</a-modal>
</div>
<div class="basic_body">
<div ref="chartContainer" class="chart-container"></div>
<!-- 水电开发情况弹窗 -->
<a-modal
v-model:open="modalVisible"
title="水电开发情况"
width="80%"
:footer="null"
:destroy-on-close="true"
>
<ShuiDianKaiFQKTwoLayer
v-if="modalVisible"
:defaultJidiInfo="modalProps.defaultJidiInfo"
:seriesName="modalProps.seriesName"
:stateName="modalProps.stateName"
:defaultTab="modalProps.defaultTab"
/>
</a-modal>
</div>
</template>
<script lang="ts" setup>
@ -30,7 +30,7 @@ import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import ShuiDianKaiFQKTwoLayer from '@/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue';
import { useDraggable } from '@/utils/drag';
defineOptions({
name: 'developStatusChart'
name: 'developStatusChart'
});
const JidiSelectEventStore = useJidiSelectEventStore();
@ -43,267 +43,281 @@ const modalVisible = ref(false);
const modalProps = ref<any>({});
useDraggable(modalVisible, { boundary: true, resetOnOpen: true });
function openModal(props: any) {
modalProps.value = props;
modalVisible.value = true;
modalProps.value = props;
modalVisible.value = true;
}
defineExpose({
openModal,
dataLoading
openModal,
dataLoading
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
const transUnit = (value: number) => {
return value * 0.1;
return value * 0.1;
};
const getData = async () => {
if (!baseid.value) return;
if (!baseid.value) return;
const baseFilters: any[] = [
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' },
{ 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 });
const baseFilters: any[] = [
{ field: 'sttpCode', operator: 'eq', dataType: 'string', value: 'ENG' },
{
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
});
}
const params1 = {
filter: {
logic: 'and',
filters: [
...baseFilters,
{ field: 'ttpwr', operator: 'isnotnull' }
]
},
group: [{ dir: 'desc', field: 'bldsttCcode', aggregates: [{ field: 'ttpwr', aggregate: 'sum' }] }]
};
const params1 = {
filter: {
logic: 'and',
filters: [...baseFilters, { field: 'ttpwr', operator: 'isnotnull' }]
},
group: [
{
dir: 'desc',
field: 'bldsttCcode',
aggregates: [{ field: 'ttpwr', aggregate: 'sum' }]
}
]
};
const params2 = {
filter: {
logic: 'and',
filters: [...baseFilters]
},
group: [{ dir: 'desc', field: 'bldsttCcode' }]
};
const params2 = {
filter: {
logic: 'and',
filters: [...baseFilters]
},
group: [{ dir: 'desc', field: 'bldsttCcode' }]
};
try {
dataLoading.value = true;
const [res1, res2] = await Promise.all([
getVmsstbprptKendoList(params1),
getVmsstbprptKendoList(params2)
]);
try {
dataLoading.value = true;
const [res1, res2] = await Promise.all([
getVmsstbprptKendoList(params1),
getVmsstbprptKendoList(params2)
]);
const res1Data = res1?.data?.data || [];
const res2Data = res2?.data?.data || [];
const res1Data = res1?.data?.data || [];
const res2Data = res2?.data?.data || [];
if (res1Data.length > 0 || res2Data.length > 0) {
const ttpwrlist = res1Data.map((item: any) => ({
key: item.key,
ttpwr: transUnit(item.items[0].SUM_TTPWR)
}));
if (res1Data.length > 0 || res2Data.length > 0) {
const ttpwrlist = res1Data.map((item: any) => ({
key: item.key,
ttpwr: transUnit(item.items[0].SUM_TTPWR)
}));
const engTotalList = res2Data.map((item: any) => ({
key: item.key,
engTotal: item.count
}));
const engTotalList = res2Data.map((item: any) => ({
key: item.key,
engTotal: item.count
}));
const result: any = Object.values(
[...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.engTotal += engTotal;
existingItem.ttpwr += ttpwr;
return { ...result, [key]: existingItem };
}, {})
);
const result: any = Object.values(
[...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.engTotal += engTotal;
existingItem.ttpwr += ttpwr;
return { ...result, [key]: existingItem };
}, {})
);
result.sort((a: any, b: any) => {
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;
});
result.sort((a: any, b: any) => {
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;
});
const legendData: string[] = [];
const capData: { name: string; value: number }[] = [];
const countData: { name: string; value: number }[] = [];
const legendData: string[] = [];
const capData: { name: string; value: number }[] = [];
const countData: { name: string; value: number }[] = [];
result.forEach((item: any) => {
const name = item.bldstt;
if (name !== '未建') {
legendData.push(name);
countData.push({ name, value: Number(item.engTotal) });
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);
}
result.forEach((item: any) => {
const name = item.bldstt;
if (name !== '未建') {
legendData.push(name);
countData.push({ name, value: Number(item.engTotal) });
capData.push({ name, value: Number(item.ttpwr).toFixed(2) });
}
} catch (error) {
console.error('DevelopStatusChart getData error:', error);
} finally {
dataLoading.value = false;
});
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 {
dataLoading.value = false;
}
};
const initChart = () => {
if (!chartContainer.value) return;
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
chartInstance = echarts.init(chartContainer.value);
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} {b}: {c} ({d}%)',
position: 'right'
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} {b}: {c} ({d}%)',
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: {
data: ['已建', '在建'],
left: '60%',
orient: 'vertical',
top: 'center',
itemWidth: 20,
itemHeight: 12
label: { show: false },
labelLine: { show: false },
data: []
},
{
name: '数量(座)',
type: 'pie',
radius: ['0%', '50%'],
center: ['30%', '50%'],
itemStyle: {
borderRadius: 2,
borderColor: '#fff',
borderWidth: 2
},
color: ['#78c300', '#2196F3'],
series: [
{
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: []
}
]
};
label: { show: false },
labelLine: { show: false },
data: []
}
]
};
chartInstance.setOption(option);
chartInstance.setOption(option);
chartInstance.on('click', (params: any) => {
const currentJidiInfo = JidiSelectEventStore.selectedItem;
openModal({
defaultJidiInfo: {
baseid: currentJidiInfo?.wbsCode || 'all',
basename: currentJidiInfo?.wbsName || '全部基地'
},
seriesName: params.seriesName,
stateName: params.name,
defaultTab: params.name
});
chartInstance.on('click', (params: any) => {
const currentJidiInfo = JidiSelectEventStore.selectedItem;
openModal({
defaultJidiInfo: {
baseid: currentJidiInfo?.wbsCode || 'all',
basename: currentJidiInfo?.wbsName || '全部基地'
},
seriesName: params.seriesName,
stateName: params.name,
defaultTab: params.name
});
});
window.addEventListener('resize', () => {
chartInstance?.resize();
});
window.addEventListener('resize', () => {
chartInstance?.resize();
});
};
onMounted(() => {
if (chartContainer.value) {
initChart();
}
if (chartContainer.value) {
initChart();
}
});
watch(
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getData();
}
},
{ deep: true, immediate: true }
() => JidiSelectEventStore.selectedItem,
newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getData();
}
},
{ deep: true, immediate: true }
);
</script>
<style lang="scss" scoped>
.basic_body {
width: 100%;
height: 100%;
width: 100%;
height: 100%;
.chart-container {
width: 203px;
height: 100px;
}
.chart-container {
width: 203px;
height: 100px;
}
}
</style>

View File

@ -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);

View File

@ -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>

View 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);
};
// 0ECharts 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>

View 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>

View File

@ -14,14 +14,16 @@
<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]">
<span v-if="item.type == 'date'">
{{
info[item.value]
? dayjs(info[item.value]).format('YYYY-MM-DD')
: '-'
}}
</span>
<span v-else>{{ info[item.value] || '-' }} </span>
<a-tooltip :title="info[item.value]">
<span v-if="item.type == 'date'">
{{
info[item.value]
? dayjs(info[item.value]).format('YYYY-MM-DD')
: '-'
}}
</span>
<span v-else>{{ info[item.value] || '-' }} </span>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>

View File

@ -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>

View File

@ -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>

View File

@ -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('');

View File

@ -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(

View 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>

View 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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>