生态调查-线上接口

This commit is contained in:
王兴凯 2026-06-26 17:54:38 +08:00
commit c7a9ff346f
15 changed files with 3177 additions and 693 deletions

View File

@ -1,4 +1,12 @@
import request from '@/utils/request'; import request from '@/utils/request';
//水电基地数据获取
export function wbsbGetKendoList(data: any) {
return request({
url: '/env/hydrobase/wbsb/GetKendoList',
method: 'post',
data
});
}
// 基地下拉列表 // 基地下拉列表
export function getBaseDropdown(data: any) { export function getBaseDropdown(data: any) {

View File

@ -0,0 +1,139 @@
import request from '@/utils/request';
//水生生态调查情况-下拉选择框
export function wbsbGetKendoList(data: any) {
return request({
url: '/api/dec-lygk-base-server/base/wbsb/GetKendoList',
method: 'post',
data
});
}
// 水生生态调查情况-获取默认年份
export function getDftYear(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/year/qgc/GetKendoListCust',
method: 'post',
data
});
}
// 水生生态调查情况-获取统计数据
export function getWeFisht(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/statics/GetKendoListCust',
method: 'post',
data
});
}
// 水生生态调查概况-表格数据接口
export function getWeStaticsSecond(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/statics/getWeStaticsSecond',
method: 'post',
data
});
}
// 沿程鱼类变化-图表数据接口
export function fishChanges2(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/stat/GetKendoListCust',
method: 'post',
data
});
}
// 沿程鱼类变化-详情表格接口
export function fishDetail(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/detail/GetKendoListCust',
method: 'post',
data
});
}
// 沿程鱼类变化-Tab数量统计接口
export function getTabNum(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/statTab/GetKendoListCust',
method: 'post',
data
});
}
// 沿程鱼类变化-获取断面列表
export function getStcdList(data: any) {
return request({
url: '/api/dec-lygk-base-server/base/vmsstbprpt/noWqDtinType0Auth/GetKendoList',
method: 'post',
data: {
filter: data,
orders: JSON.stringify({ siteStepSort: 'asc' })
}
});
}
// 沿程鱼类变化-基地模式详情表格接口
export function fishQgcDetail(data: any) {
return request({
url: '/api/wmp-env-server/env/we/fisht/qgcDetail/GetKendoListCust',
method: 'post',
data
});
}
// 陆生生态监测情况-获取统计数据
export function getEvnmAutoMonitor(data: any) {
return request({
url: '/api/dec-lygk-base-server/base/evnmAutoMonitor/GetKendoListCust',
method: 'post',
data
});
}
// 陆生生态监测-红外相机信息表格
export function getVmsstbprptList(data: any) {
return request({
url: '/api/dec-lygk-base-server/base/vmsstbprpt/GetKendoList',
method: 'post',
data
});
}
// 陆生生态监测-重点陆生动物表格
export function getTetSpecList(data: any) {
return request({
url: '/api/wmp-env-server/te/tet/spec/GetKendoListCust',
method: 'post',
data
});
}
// 野生动物监测-获取运行数据
export function getWvaData(data: any) {
return request({
url: '/api/wmp-env-server/env/wva/GetKendoListCust',
method: 'post',
data
});
}
// 野生动物监测-获取默认数据
export function getWvaDftData(data: any) {
return request({
url: '/api/wmp-env-server/env/wva/default/GetKendoListCust',
method: 'post',
data
});
}
// 野生动物监测-获取默认年份列表
export function getWvaDftTm(data: any) {
return request({
url: '/api/wmp-env-server/env/wva/year/GetKendoListCust',
method: 'post',
data
});
}

View File

@ -596,12 +596,18 @@ const enhancedColumns = computed(() => {
const { slots, ...restCol } = enhancedCol; const { slots, ...restCol } = enhancedCol;
return { return {
...restCol, ...restCol,
customCell: createMergeCellHandler(col),
_slotName: slots.customRender, _slotName: slots.customRender,
...(props.enableEllipsis ? { ellipsis: true } : {}) ...(props.enableEllipsis ? { ellipsis: true } : {})
}; };
} }
return enhancedCol; // slots customCell
return {
...enhancedCol,
customCell: createMergeCellHandler(col),
...(props.enableEllipsis ? { ellipsis: true } : {})
};
} }
// slots.customRender slots // slots.customRender slots

View File

@ -47,26 +47,7 @@
</span> </span>
</div> </div>
<div class="title_right"> <div class="title_right">
<div v-if="select.show">
<a-select
v-model:value="selectValue"
show-search
placeholder="请选择"
:size="'small'"
:style="{ width: select.width || '80px' }"
:dropdown-style="{
maxHeight: '400px',
overflow: 'auto',
minWidth: '200px'
}"
:options="select.options"
:filter-option="filterOption"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
@select="handleTreeSelect1"
></a-select>
</div>
<div v-if="shrink" class="title_shrink" @click="isExpand = !isExpand"> <div v-if="shrink" class="title_shrink" @click="isExpand = !isExpand">
<img v-if="isExpand" src="@/assets/components/arrow-up.png" alt="" /> <img v-if="isExpand" src="@/assets/components/arrow-up.png" alt="" />
<img v-else src="@/assets/components/arrow-down.png" alt="" /> <img v-else src="@/assets/components/arrow-down.png" alt="" />
@ -96,6 +77,26 @@
@expand="handleTreeExpand" @expand="handleTreeExpand"
> >
</a-tree-select> </a-tree-select>
</div>
<div v-if="select.show">
<a-select
v-model:value="selectValue"
show-search
placeholder="请选择"
:size="'small'"
:style="{ width: select.width || '80px' }"
:dropdown-style="{
maxHeight: '400px',
overflow: 'auto',
minWidth: '200px'
}"
:options="select.options"
:filter-option="filterOption"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
@select="handleTreeSelect1"
></a-select>
</div> </div>
<div v-if="datetimePicker.show"> <div v-if="datetimePicker.show">
<!-- 添加 locale 属性来设置语言 --> <!-- 添加 locale 属性来设置语言 -->

View File

@ -48,7 +48,6 @@ app
.use(router) .use(router)
.use(ElementPlus) .use(ElementPlus)
.use(Antd) .use(Antd)
.use(Antd)
.use(WujieVue) .use(WujieVue)
.use(i18n) .use(i18n)
.mount('#app'); .mount('#app');

View File

@ -1,164 +1,184 @@
<!-- SidePanelItem.vue --> <!-- 野生动物监测 -->
<template> <template>
<SidePanelItem title="野生动物监测" :select="select" :datetimePicker="datetimePicker" :tabs="tabs" <SidePanelItem
@tab-change="handleTabChange"> title="野生动物监测"
<div v-if="tabsa == 'one'" class="container" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> :select="select"
<!-- 跑马灯轨道容器 --> :datetimePicker="datetimePicker"
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }" :tabs="tabs"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"> @tab-change="handleTabChange"
<!-- 遍历所有媒体项包含克隆项 --> @update-values="handlePanelChange"
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item"> >
<!-- 图片 --> <a-spin :spinning="loading" tip="加载中...">
<a-image :src="item.url" /> <div v-if="originalMediaData.length === 0" class="empty-wrapper">
<!-- 说明文字 --> <a-empty description="暂无数据" />
</div>
<template v-else>
<div
v-if="tabsa == 'one'"
class="container"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
<div
class="carousel-track"
:class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
>
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item"
@click="handleItemClick(item)"
>
<img :src="item.url" />
<div class="text">{{ item.text }}</div> <div class="text">{{ item.text }}</div>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="container video-carousel-container" @mouseenter="handleMouseEnter" <div
@mouseleave="handleMouseLeave"> v-else
<!-- 跑马灯轨道容器 --> class="container video-carousel-container"
<div class="carousel-track" :class="{ 'no-transition': isTransitioning }" @mouseenter="handleMouseEnter"
:style="{ transform: `translateX(-${currentIndex * 30}%)` }"> @mouseleave="handleMouseLeave"
<!-- 遍历所有媒体项包含克隆项 --> >
<div v-for="(item, index) in renderMediaData" :key="index" class="carousel-item1"> <div
<!-- 图片 --> class="carousel-track"
<a-image :src="item.url" /> :class="{ 'no-transition': isTransitioning }"
:style="{ transform: `translateX(-${currentIndex * 30}%)` }"
>
<div
v-for="(item, index) in renderMediaData"
:key="index"
class="carousel-item1"
@click="handleItemClick(item)"
>
<img :src="item.url" />
<div class="biaozji"> <div class="biaozji">
<span class="icon iconfont icon-play"></span> <span class="icon iconfont icon-play"></span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template>
</a-spin>
</SidePanelItem> </SidePanelItem>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted, watch } from 'vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import { getVmsstbprptList, getWvaData, getWvaDftTm } from '@/api/stdc';
import { useModelStore } from '@/store/modules/model';
// 便
defineOptions({ defineOptions({
name: 'ZhiWuYuanJianSheJiJieRuQingKuangBar' name: 'ZhiWuYuanJianSheJiJieRuQingKuangBar'
}); });
// ==================== ====================//
const modelStore = useModelStore();
// ==================== ====================
const select = ref({ const select = ref({
show: true, show: true,
value: undefined, value: undefined as string | undefined,
options: [], options: [] as Array<{ label: string; value: string }>,
picker: undefined, picker: undefined,
format: undefined format: undefined
}); });
// // ==================== ====================
const datetimePicker = ref({ const datetimePicker = ref({
show: true, show: true,
value: `2024`, value: '',
format: 'YYYY', format: 'YYYY',
picker: 'year' as const, picker: 'year' as const
options: [] // options: [] as string[]
}); });
// ==================== Tab ====================
const tabs = ref({ const tabs = ref({
show: true, show: true,
value: 'one', value: 'one'
}); });
const tabsa = ref('one');
// ==================== ====================
//
interface MediaItem { interface MediaItem {
type: 'image' | 'video'; type: 'image' | 'video';
url: string; url: string;
text: string; text: string;
stcd: string;
stnm: string;
fid?: string;
flpth?: string;
flnm?: string;
tm?: string;
} }
// 5 const originalMediaData = ref<MediaItem[]>([]);
const originalMediaData = ref<MediaItem[]>([
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '糯扎渡动物救护站 2025-04-11 15'
},
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '糯扎渡动物救护站 2025-04-11 15'
},
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '糯扎渡动物救护站 2025-04-11 15'
},
{
type: 'image',
url: 'https://211.99.26.225:12125/?20250709001636453909987812803850&view=jpg&token=bearer 0a2549fe-9bc6-4bef-b098-1344e569c395',
text: '糯扎渡动物救护站 2025-04-11 15'
},
]);
const tabsa = ref('one')
//
// [, ..., ]
const renderMediaData = ref<MediaItem[]>([]); const renderMediaData = ref<MediaItem[]>([]);
const currentIndex = ref(1);
// renderMediaData
const currentIndex = ref(1); // 1
//
let timer: any = null; let timer: any = null;
//
const isHovering = ref(false); const isHovering = ref(false);
// transition
const isTransitioning = ref(false); const isTransitioning = ref(false);
const loading = ref(false);
// // ==================== ====================
const initRenderData = () => { const initRenderData = () => {
const length = originalMediaData.value.length; const length = originalMediaData.value.length;
if (length === 0) return; if (length === 0) {
renderMediaData.value = [];
currentIndex.value = 0;
return;
}
// > 1
if (length === 1) {
renderMediaData.value = [...originalMediaData.value];
currentIndex.value = 0;
} else {
renderMediaData.value = [ renderMediaData.value = [
originalMediaData.value[length - 1], // originalMediaData.value[length - 1],
...originalMediaData.value, // ...originalMediaData.value,
originalMediaData.value[0] // originalMediaData.value[0]
]; ];
currentIndex.value = 1;
}
}; };
// // ==================== ====================
const startAutoPlay = () => { const startAutoPlay = () => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
// 1
if (originalMediaData.value.length <= 1) return;
timer = setInterval(() => { timer = setInterval(() => {
if (!isHovering.value && !isTransitioning.value) { if (
!isHovering.value &&
!isTransitioning.value &&
originalMediaData.value.length > 1
) {
nextSlide(); nextSlide();
} }
}, 2000); }, 3000);
};
const stopAutoPlay = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
}; };
//
const nextSlide = () => { const nextSlide = () => {
currentIndex.value++; currentIndex.value++;
//
setTimeout(() => { setTimeout(() => {
checkSeamlessJump(); checkSeamlessJump();
}, 500); // transition }, 500);
}; };
//
const checkSeamlessJump = () => { const checkSeamlessJump = () => {
const realLength = originalMediaData.value.length; const realLength = originalMediaData.value.length;
// = realLength + 1
if (currentIndex.value >= realLength + 1) { if (currentIndex.value >= realLength + 1) {
// 1.
isTransitioning.value = true; isTransitioning.value = true;
// 2. 1
currentIndex.value = 1; currentIndex.value = 1;
// 3. DOM
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
isTransitioning.value = false; isTransitioning.value = false;
@ -167,70 +187,270 @@ const checkSeamlessJump = () => {
} }
}; };
//
const handleMouseEnter = () => { const handleMouseEnter = () => {
isHovering.value = true; isHovering.value = true;
}; };
//
const handleMouseLeave = () => { const handleMouseLeave = () => {
isHovering.value = false; isHovering.value = false;
}; };
const handleTabChange = (data: { tabValue: string; tabLabel: string }) => { const handleTabChange = (data: { tabValue: string; tabLabel: string }) => {
console.log('当前选中标签:', data.tabValue);
console.log('标签名称:', data.tabLabel);
//
tabsa.value = data.tabValue; tabsa.value = data.tabValue;
// tab
getData();
}; };
// // ==================== ====================
onMounted(() => { const handlePanelChange = (data: any) => {
if (data.select !== undefined && data.select !== null) {
select.value.value = data.select;
}
if (data.datetime !== undefined && data.datetime !== null) {
datetimePicker.value.value = data.datetime;
}
if (data.tabs !== undefined && data.tabs !== null) {
tabsa.value = data.tabs;
}
//
getData();
};
// ==================== ====================
const handleItemClick = (item: MediaItem) => {
modelStore.modalVisible = true;
modelStore.params.sttp = 'WVA';
modelStore.params.stcd = item.stcd;
modelStore.title = `${item.stnm} 野生动物监测`;
modelStore.isBasicEdit = true;
};
// ==================== ====================
const getStationList = async () => {
try {
const res = await getVmsstbprptList({
filter: {
logic: 'and',
filters: [
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'WVA'
}
]
},
select: [
'id',
'stcd',
'rstcd',
'ennm',
'stnm',
'sttpCode',
'orderIndex',
'lgtd',
'lttd'
]
});
const list = res?.data?.data ?? [];
// debugger
if (list.length) {
select.value.options = list.map((item: any) => ({
label: item.stnm,
value: item.stcd
}));
select.value.value = list[0].stcd;
}
} catch (error) {
console.error('获取站点列表失败:', error);
}
};
// ==================== ====================
const getDefaultYear = async () => {
try {
let params = {
filter: {
logic: 'and',
filters: [
tabsa.value === 'one'
? {
field: 'fid',
operator: 'isnotnull',
dataType: 'string'
}
: { field: 'flpth', operator: 'isnotnull', dataType: 'string' }
]
}
};
const res = await getWvaDftTm(params);
const data = res?.data?.data ?? [];
if (data.length) {
const yrList = data
.map((el: any) => el.ym)
.sort((a: any, b: any) => b - a);
// datetimePicker.value.options = yrList;
datetimePicker.value.value = String(yrList[0]);
} else {
datetimePicker.value.value = String(new Date().getFullYear());
}
} catch (error) {
console.error('获取默认年份失败:', error);
datetimePicker.value.value = String(new Date().getFullYear());
}
};
// ==================== ====================
const getFileUrl = (path: string, view = 'jpg') => {
const fsAddress =import.meta.env.VITE_APP_ATTACHMENT_URL ||'';
return `${fsAddress}?${path}&view=${view}`;
};
const getData = async () => {
if (!datetimePicker.value.value) return;
loading.value = true;
try {
// datetimePicker.value.value YYYY-MM
const year = datetimePicker.value.value?.toString().slice(0, 4);
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: `${year}-01-01 00:00:00`
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: `${year}-12-31 23:59:59`
},
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: select.value.value
},
tabsa.value === 'one'
? {
field: 'fid',
operator: 'isnotnull',
dataType: 'string'
}
: { field: 'flpth', operator: 'isnotnull' }
]
}
};
const res = await getWvaData(params);
const datas = res?.data?.data ?? [];
if (tabsa.value === 'one') {
const pics = datas.reduce((result: MediaItem[], el: any) => {
const ids = el?.fid?.split(',') ?? [];
ids.forEach((id: string) => {
result.push({
type: 'image',
url: getFileUrl(id),
text: el?.flnm || '',
stcd: el?.stcd,
stnm: el?.stnm,
fid: id,
flnm: el?.flnm,
tm: el?.tm
});
});
return result;
}, []);
originalMediaData.value = pics;
} else {
const videos = datas.map((el: any) => ({
type: 'video' as const,
url: el.imgPath ? getFileUrl(el.imgPath) : '',
text: el?.flnm || '',
stcd: el?.stcd,
stnm: el?.stnm,
flpth: el?.flpth,
flnm: el?.flnm,
tm: el?.tm
}));
originalMediaData.value = videos;
}
initRenderData(); initRenderData();
startAutoPlay(); startAutoPlay();
} catch (error) {
console.error('获取数据失败:', error);
} finally {
loading.value = false;
}
};
// <= 1
watch(
() => originalMediaData.value.length,
(newLen) => {
if (newLen <= 1) {
stopAutoPlay();
}
}
);
// ==================== ====================
onMounted(async () => {
await getStationList();
await getDefaultYear();
//
getData();
}); });
//
onUnmounted(() => { onUnmounted(() => {
if (timer) clearInterval(timer); if (timer) clearInterval(timer);
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.empty-wrapper {
width: 100%;
height: 290px;
display: flex;
align-items: center;
justify-content: center;
}
.container { .container {
width: 100%; width: 100%;
height: 290px; height: 290px;
border: 1px solid #7fd6ff; border: 1px solid #7fd6ff;
border-radius: 5px; border-radius: 5px;
position: relative; position: relative;
overflow: hidden; // overflow: hidden;
//
.carousel-track { .carousel-track {
display: flex; // display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
transition: transform 0.5s ease-in-out; // 0.5 transition: transform 0.5s ease-in-out;
//
&.no-transition { &.no-transition {
transition: none; transition: none;
} }
//
.carousel-item { .carousel-item {
width: 100% !important; // width: 100% !important;
height: 100%; height: 100%;
position: relative; position: relative;
flex-shrink: 0; // flex-shrink: 0;
cursor: pointer;
img, img,
video { video {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; // object-fit: cover;
} }
// Ant Design Vue a-image
:deep(.ant-image) { :deep(.ant-image) {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -261,26 +481,25 @@ onUnmounted(() => {
.video-carousel-container { .video-carousel-container {
border: 0px solid #7fd6ff !important; border: 0px solid #7fd6ff !important;
border-radius: 0px !important; border-radius: 0px !important;
.carousel-item1 { .carousel-item1 {
position: relative; position: relative;
width: 40% !important; // width: 40% !important;
height: 100%; height: 100%;
position: relative; flex-shrink: 0;
flex-shrink: 0; //
// margin-right: 10px;
padding: 6px; padding: 6px;
border-top: #f0f0f0 solid 1px; border-top: #f0f0f0 solid 1px;
border-bottom: #f0f0f0 solid 1px; border-bottom: #f0f0f0 solid 1px;
border-right: #f0f0f0 solid 1px; border-right: #f0f0f0 solid 1px;
cursor: pointer;
img, img,
video { video {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; // object-fit: cover;
} }
// Ant Design Vue a-image
:deep(.ant-image) { :deep(.ant-image) {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -291,6 +510,7 @@ onUnmounted(() => {
object-fit: cover; object-fit: cover;
} }
} }
.biaozji { .biaozji {
position: absolute; position: absolute;
width: 40px; width: 40px;
@ -305,6 +525,7 @@ onUnmounted(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
span { span {
width: 16px; width: 16px;
height: 16px; height: 16px;

View File

@ -0,0 +1,615 @@
<!-- 沿程鱼类变化详情组件 -->
<template>
<div class="yancheng-yulei">
<!-- 搜索表单 -->
<a-form layout="inline" class="search-form">
<!-- 基地名称非沿程模式显示 -->
<a-form-item v-if="!props.ly" label="基地名称">
<a-select
v-model:value="formState.baseId"
placeholder="请选择"
style="width: 150px"
:options="baseOptions"
@change="handleBaseChange"
/>
</a-form-item>
<!-- 所在流域 -->
<a-form-item label="所在流域">
<a-select
v-model:value="formState.rvcd"
placeholder="请选择"
style="width: 150px"
:options="lyOptions"
@change="handleRvcdChange"
/>
</a-form-item>
<!-- 断面名称沿程模式显示 -->
<a-form-item v-if="props.ly" label="断面名称">
<a-select
v-model:value="formState.stcd"
placeholder="请选择"
style="width: 150px"
:options="dmOptions"
@change="handleStcdChange"
/>
</a-form-item>
<!-- 年份 -->
<a-form-item label="年份">
<a-date-picker
v-model:value="yearValue"
picker="year"
format="YYYY"
:allow-clear="false"
style="width: 120px"
@change="handleYearChange"
/>
</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>
<!-- Tab 切换 + 表格 -->
<a-tabs v-model:activeKey="tabIndex" class="ylycbh-tabs" @change="handleTabChange">
<a-tab-pane v-for="item in tabList" :key="item.key" :tab="item.labelWithTooltip">
<div class="table-wrapper">
<BasicTable
v-if="tabIndex === item.key"
:ref="el => setTableRef(el, item.key)"
:columns="tableColumns"
:scroll-y="500"
:scroll-x="800"
:list-url="listUrl"
:search-params="searchParams"
:transform-data="customTransform"
>
<!-- 沿程模式断面名称列带电站名称分组 -->
<template v-if="props.ly" #rsctdName="{ record }">
{{ record.rsctdName || '-' }}
</template>
<template v-if="props.ly" #stcdList="{ record }">
<div v-for="st in record.stcdList" :key="st.stcd">
<a @click="handleViewDetail(st.stcd, st.stnm)" class="text-link">
{{ st.stnm }}
</a>
</div>
</template>
<template v-if="props.ly" #fishList="{ record }">
<a-list size="small" :data-source="record.fishList">
<template #renderItem="{ item }">
<a-list-item>{{ item }}</a-list-item>
</template>
</a-list>
</template>
<!-- 基地模式调查断面列 -->
<template v-else #stnm="{ record }">
<a @click="handleViewDetail(record.stcd, record.stnm)" class="text-link">
{{ record.stnm }}
</a>
</template>
<template v-else #name="{ record }">
{{ record.name || '-' }}
</template>
</BasicTable>
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted, nextTick, h } from 'vue';
import { Tooltip } from 'ant-design-vue';
import BasicTable from '@/components/BasicTable/index.vue';
import {
wbsbGetKendoList,
getStcdList,
getTabNum,
fishDetail,
fishQgcDetail
} from '@/api/stdc';
import dayjs from 'dayjs';
import { useModelStore } from '@/store/modules/model';
const modelStore = useModelStore();
// ==================== Props ====================
const props = defineProps<{
tm: string; // "2024"
dataDimensionVal: string; // ID
hbrvcd: string; //
jdList: any[]; //
stateName: string; // ""
sectionName: string; //
stcd: string; //
ly: boolean; // 沿
}>();
// ==================== Tab ====================
const TAB_CONFIG = [
{ key: '1', label: '外来鱼类', prompt: '通过人为行为引入的本流域原生态没有的鱼类' },
{ key: '2', label: '本土受威胁鱼类', prompt: '流域原生态鱼类中处于近危或者濒危的鱼类' },
{ key: '3', label: '本土无危鱼类', prompt: '分类属于本土且保护类型属于无危的鱼类' }
];
// ==================== ====================
const formState = ref({
baseId: props.dataDimensionVal || 'all',
rvcd: props.hbrvcd || '',
stcd: props.stcd || '',
yr: props.tm || dayjs().format('YYYY')
});
const yearValue = ref(props.tm ? dayjs(props.tm) : dayjs());
// ==================== ====================
/** 基地下拉选项 */
const baseOptions = computed(() =>
props.jdList.map((item: any) => ({
label: item.wbsName,
value: item.wbsCode
}))
);
/** 流域下拉选项 */
const lyOptions = ref<Array<{ label: string; value: string }>>([]);
/** 断面下拉选项 */
const dmOptions = ref<Array<{ label: string; value: string }>>([
{ label: '全部', value: '' }
]);
// ==================== Tab ====================
/** 根据 props.stateName 确定初始 Tab */
const initTabIndex = (() => {
if (props.stateName === '外来鱼类') return '1';
if (props.stateName === '本土受威胁鱼类') return '2';
return '3';
})();
const tabIndex = ref(initTabIndex);
/** Tab 列表(带 Tooltip 渲染) */
const tabList = ref<any[]>(
TAB_CONFIG.map(item => ({
...item,
labelWithTooltip: renderTabLabel(item.label, item.prompt)
}))
);
/**
* 渲染 Tab 标签 Tooltip 提示
*/
function renderTabLabel(label: string, prompt: string) {
return () =>
h('div', { style: { display: 'flex', alignItems: 'center' } }, [
h('span', label),
h(
Tooltip,
{ placement: 'topLeft', title: prompt },
{
default: () =>
h('span', {
class: 'icon iconfont icon-iconQuestion',
style: { marginLeft: '4px', cursor: 'pointer' }
})
}
)
]);
}
// ==================== ====================
/** 沿程模式表格列 */
const lyColumns = [
{
key: 'rsctdName',
title: '电站名称',
dataIndex: 'rsctdName',
width: 200,
ellipsis: true,
slots: { customRender: 'rsctdName' }
},
{
key: 'stcdList',
title: '调查断面',
dataIndex: 'stcdList',
width: 200,
ellipsis: true,
slots: { customRender: 'stcdList' }
},
{
key: 'fishList',
title: '渔获物种类',
dataIndex: 'fishList',
width: 300,
ellipsis: true,
slots: { customRender: 'fishList' }
}
];
/** 基地模式表格列 */
const baseColumns = [
{
key: 'stnm',
title: '调查断面',
dataIndex: 'stnm',
fixed: 'left' as const,
width: 200,
ellipsis: true,
slots: { customRender: 'stnm' },
merge: true,
mergeDeps: ['stnm']
},
{
key: 'name',
title: '渔获物种类',
dataIndex: 'name',
width: 300,
ellipsis: true,
slots: { customRender: 'name' }
}
];
/** 根据 ly 模式切换表格列 */
const tableColumns = computed(() => (props.ly ? lyColumns : baseColumns));
/** 根据 ly 模式切换表格 API */
const listUrl = computed(() => (props.ly ? fishDetail : fishQgcDetail));
/** 表格搜索参数 */
const searchParams = computed(() => ({
group: [],
select: [],
groupResultFlat: true
}));
/** 表格 ref 映射(每个 Tab 一个 ref */
const tableRefs = ref<Record<string, any>>({});
function setTableRef(el: any, key: string) {
if (el) tableRefs.value[key] = el;
}
/** 数据转换 */
const customTransform = (res: any) => ({
records: res?.data?.data || [],
total: res?.data?.total || 0
});
// ==================== ====================
const fetchLyList = async () => {
try {
const params = {
filter: {
logic: 'and',
filters: [
{ field: 'wbsType', operator: 'eq', value: 'PSB_RVCD' },
{
field: 'fullPath',
operator: 'contains',
dataType: 'string',
value: formState.value.baseId + ','
}
]
},
group: [
{ dir: 'asc', field: 'objId' },
{ dir: 'asc', field: 'orderIndex' },
{ dir: 'des', field: 'wbsCode' },
{ dir: 'des', field: 'wbsName' }
],
groupResultFlat: true
};
const res = await wbsbGetKendoList(params);
const data = res?.data?.data || [];
if (data.length > 0) {
const options = data.map((item: any) => ({
label: item.wbsName,
value: item.wbsCode
}));
lyOptions.value = options;
// hbrvcd
const matched = options.find(o => o.value === props.hbrvcd);
formState.value.rvcd = matched?.value || options[0]?.value || '';
} else {
lyOptions.value = [];
formState.value.rvcd = '';
}
} catch (error) {
console.error('获取流域列表失败:', error);
lyOptions.value = [];
formState.value.rvcd = '';
}
};
// ==================== ====================
const fetchDmList = async () => {
try {
const params = {
logic: 'and',
filters: [
{ field: 'hbrvcd', operator: 'eq', value: formState.value.rvcd },
{ field: 'sttpCode', operator: 'eq', value: 'ENG' },
{
field: 'bldsttCcode',
operator: 'in',
value: ['1', '2']
}
]
};
const res = await getStcdList(params);
const data = res?.data?.data || [];
if (data.length > 0) {
const options = data.map((item: any) => ({
label: item.stnm,
value: item.stcd
}));
dmOptions.value = [{ label: '全部', value: '' }, ...options];
// sectionName
const matched = options.find(o => o.label === props.sectionName);
formState.value.stcd = matched?.value || options[0]?.value || '';
} else {
dmOptions.value = [{ label: '全部', value: '' }];
formState.value.stcd = '';
}
} catch (error) {
console.error('获取断面列表失败:', error);
dmOptions.value = [{ label: '全部', value: '' }];
formState.value.stcd = '';
}
};
// ==================== Tab ====================
const getTabNumData = async () => {
const params: any = {
baseId: formState.value.baseId,
rvcd: formState.value.rvcd,
yr: dayjs(yearValue.value).format('YYYY'),
ty: tabIndex.value
};
// 沿 stcd
if (props.ly) {
params.stcd = formState.value.stcd;
}
//
if (!params.stcd) delete params.stcd;
if (params.baseId === 'all') delete params.baseId;
try {
const res = await getTabNum(params);
// getTabNum res.data [{ty, num}, ...]
const data = res?.data || [];
const list = TAB_CONFIG.map(item => ({
...item,
labelWithTooltip: renderTabLabel(item.label, item.prompt)
}));
if (data.length > 0) {
data.forEach((el: any) => {
const found = list.find(item => item.key === el?.ty);
if (found) {
found.labelWithTooltip = renderTabLabel(
`${found.label}(${el?.num}种)`,
found.prompt
);
}
});
}
tabList.value = list;
} catch (error) {
console.error('获取 Tab 数量失败:', error);
tabList.value = TAB_CONFIG.map(item => ({
...item,
labelWithTooltip: renderTabLabel(item.label, item.prompt)
}));
}
};
// ==================== ====================
/** 构建表格过滤条件 */
const getFilter = () => {
const filters: any[] = [
{
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: formState.value.baseId
},
{
field: 'rvcd',
operator: 'eq',
dataType: 'string',
value: formState.value.rvcd
},
{
field: 'yr',
operator: 'eq',
dataType: 'string',
value: dayjs(yearValue.value).format('YYYY')
},
{
field: 'ty',
operator: 'eq',
dataType: 'string',
value: tabIndex.value
}
];
// 沿 stcd
if (props.ly && formState.value.stcd) {
filters.push({
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: formState.value.stcd
});
}
return {
logic: 'and',
filters: filters.filter(Boolean)
};
};
/** 查询 */
const handleSearch = () => {
const filter = getFilter();
const currentTable = tableRefs.value[tabIndex.value];
currentTable?.getList(filter);
getTabNumData();
};
/** 重置 */
const handleReset = () => {
formState.value = {
baseId: props.dataDimensionVal || 'all',
rvcd: props.hbrvcd || '',
stcd: props.stcd || '',
yr: props.tm || dayjs().format('YYYY')
};
yearValue.value = props.tm ? dayjs(props.tm) : dayjs();
handleSearch();
};
// ==================== ====================
/** 基地变化 */
const handleBaseChange = () => {
formState.value.rvcd = '';
formState.value.stcd = '';
fetchLyList();
};
/** 流域变化 */
const handleRvcdChange = () => {
formState.value.stcd = '';
if (props.ly) {
fetchDmList();
}
};
/** 断面变化 */
const handleStcdChange = () => {
//
};
/** 年份变化 */
const handleYearChange = () => {
//
};
/** Tab 切换 */
const handleTabChange = () => {
handleSearch();
};
// ==================== ====================
/**
* 点击断面链接弹出详情
* 参考 SsstdcgkTk.vue handleViewDetail
*/
const handleViewDetail = (stcd: string, stnm: string) => {
modelStore.modalVisible = true;
modelStore.params.sttp = 'WE';
modelStore.params.stcd = stcd;
modelStore.params.timeRange = [
dayjs(yearValue.value).startOf('year'),
dayjs(yearValue.value).endOf('year')
];
modelStore.params.ty1 = tabIndex.value;
modelStore.title = stnm;
};
// ==================== Watch ====================
/** 监听 props 变化,同步表单状态 */
watch(
() => [props.tm, props.hbrvcd, props.stcd, props.sectionName],
() => {
formState.value = {
baseId: props.dataDimensionVal || 'all',
rvcd: props.hbrvcd || '',
stcd: props.stcd || '',
yr: props.tm || dayjs().format('YYYY')
};
yearValue.value = props.tm ? dayjs(props.tm) : dayjs();
},
{ immediate: false }
);
// ==================== ====================
onMounted(async () => {
await nextTick();
//
await fetchLyList();
// 沿
if (props.ly) {
await fetchDmList();
}
// Tab
await getTabNumData();
//
handleSearch();
});
</script>
<style lang="scss" scoped>
.yancheng-yulei {
display: flex;
flex-direction: column;
height: 100%;
.search-form {
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 16px;
flex-shrink: 0;
}
.ylycbh-tabs {
flex: 1;
overflow: hidden;
:deep(.ant-tabs-content) {
height: 100%;
}
:deep(.ant-tabs-tabpane) {
height: 100%;
}
}
.table-wrapper {
width: 100%;
}
}
.text-link {
color: #1990ff;
cursor: pointer;
&:hover {
color: #40a9ff;
}
}
</style>

View File

@ -1,67 +1,171 @@
<!-- SidePanelItem.vue --> <!-- 沿程鱼类变化情况 -->
<template> <template>
<SidePanelItem title="水生生态调查" :select="select" :datetimePicker="datetimePicker"> <SidePanelItem
<div ref="chartRef" class="chart-container"></div> title="沿程鱼类变化情况"
:moreSelect="moreSelect"
:select="select"
@update-values="handlePanelChange"
>
<a-spin :spinning="dataLoading" tip="加载中...">
<div
v-show="chartData.length > 0 && select.options.length > 0"
ref="chartRef"
class="chart-container"
></div>
<a-empty
v-show="
!dataLoading &&
(chartData.length === 0 || select.options.length === 0)
"
:description="select.options.length === 0 ? '暂无数据' : '暂无数据'"
class="empty-placeholder"
/>
</a-spin>
</SidePanelItem> </SidePanelItem>
<!-- 沿程鱼类变化详情弹框 -->
<a-modal
v-model:open="modalVisible"
title="沿程鱼类变化"
width="1536px"
:footer="null"
@cancel="handleModalClose"
>
<Yanchengyulei
v-if="modalVisible"
:tm="modalYear"
:dataDimensionVal="baseid"
:hbrvcd="modalHbrvcd"
:jdList="JidiSelectEventStore.jidiData"
:stateName="modalStateName"
:sectionName="modalSectionName"
:stcd="modalStcd"
:ly="true"
/>
</a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'; import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import Yanchengyulei from './TwoLayers/Yanchengyulei.vue';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { wbsbGetKendoList, getDftYear, fishChanges2 } from '@/api/stdc';
import moment from 'moment';
//
defineOptions({ defineOptions({
name: 'shuiShengShengTaiDiaoCha' name: 'alongFishMod'
}); });
// ==================== ==================== // ==================== Store ====================
const chartRef = ref<HTMLDivElement>(); const JidiSelectEventStore = useJidiSelectEventStore();
let chartInstance: any = null;
// // ==================== ====================
/** 当前选中的基地ID来自 JidiSelectEventStore.selectedItem */
const baseid = ref('');
/** 图表数据加载状态 */
const dataLoading = ref(false);
/** 初始化标志位:初始化期间不响应控件变化触发数据请求 */
const isInitializing = ref(false);
// ==================== ====================
/** 流域树形选择器配置 */
const moreSelect = ref({
show: true,
value: undefined as string | undefined,
options: [] as any[]
});
/** 年份选择器配置 */
const select = ref({ const select = ref({
show: true, show: true,
value: undefined, value: undefined as string | undefined,
options: [], options: [] as any[],
picker: undefined, picker: undefined,
format: undefined format: undefined,
width: '120px'
}); });
// /**
const datetimePicker = ref({ * 按流域分组的年份数据
show: true, * 结构{ "wbsCode1": [{label:'2023', value:'2023'}], "wbsCode2": [...] }
value: '2020', * 用于流域切换时快速更新年份下拉选项
format: 'YYYY', */
picker: 'year' as const, const yearOptions = ref<Record<string, any[]>>({});
options: []
});
// // ==================== ====================
const chartRef = ref<HTMLDivElement>();
let chartInstance: any = null;
/** 图表原始数据,由 API 返回 */
const chartData = ref<any[]>([]);
// ==================== ====================
const modalVisible = ref(false);
const modalYear = ref('');
const modalHbrvcd = ref('');
const modalStateName = ref('');
const modalSectionName = ref('');
const modalStcd = ref('');
// ==================== ====================
/**
* 初始化 ECharts 实例
* onMounted 中调用绑定点击事件用于打开详情弹框
*/
const initChart = () => { const initChart = () => {
if (!chartRef.value) return; if (!chartRef.value) return;
//
if (chartInstance) { if (chartInstance) {
chartInstance.dispose(); chartInstance.dispose();
} }
//
chartInstance = echarts.init(chartRef.value); chartInstance = echarts.init(chartRef.value);
// //
chartInstance.on('click', (params: any) => {
if (params.componentType === 'series') {
openDetail(params);
}
});
updateChart();
};
/**
* 根据 chartData 更新图表配置
* API 返回的 detail 对象key: 1=外来鱼类, 2=本土受威胁, 3=本土无危
* 转换为 ECharts 堆叠柱状图数据
*/
const updateChart = () => {
if (!chartInstance) return;
// sort
const sortedData = [...chartData.value].sort(
(a, b) => (a.sort || 0) - (b.sort || 0)
);
const waimaiData: number[] = [];
const bentuThreatData: number[] = [];
const bentuSafeData: number[] = [];
const xAxisData: string[] = [];
sortedData.forEach((item: any) => {
xAxisData.push(item.stnm || '');
waimaiData.push(item.detail?.['1'] || 0);
bentuThreatData.push(item.detail?.['2'] || 0);
bentuSafeData.push(item.detail?.['3'] || 0);
});
const option: EChartsOption = { const option: EChartsOption = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: { type: 'shadow' },
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.5)', backgroundColor: 'rgba(0, 0, 0, 0.5)',
borderColor: 'transparent', borderColor: 'transparent',
textStyle: { textStyle: { color: '#ffffff' }
color: '#ffffff'
}
}, },
grid: { grid: {
top: 30, top: 30,
@ -79,105 +183,412 @@ const initChart = () => {
type: 'value', type: 'value',
minInterval: 1, minInterval: 1,
name: '种', name: '种',
axisLine: { axisLine: { lineStyle: { color: '#e0e0e0' } },
lineStyle: { axisLabel: { color: '#666' },
color: '#e0e0e0' splitLine: { lineStyle: { color: '#e0e0e0', type: 'solid' } }
}
},
axisLabel: {
color: '#666'
},
splitLine: {
lineStyle: {
color: '#e0e0e0',
type: 'solid'
}
}
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
axisTick: { axisTick: { show: false },
show: false
},
axisLabel: { axisLabel: {
interval: 0, interval: 0,
rotate: 0, rotate: 0,
color: '#666', color: '#666',
rich: { rich: {
a: { a: { height: 24, align: 'center' },
height: 24, b: { height: 30, align: 'center' }
align: 'center'
},
b: {
height: 30,
align: 'center'
}
} }
}, },
name: '数量(个)', name: '数量(个)',
data: [ data: xAxisData
'两河口',
'杨房沟',
'锦屏一级',
'锦屏二级',
'官地',
'二滩',
'桐子林'
]
}, },
series: [ series: [
{ {
name: '外来鱼类', name: '外来鱼类',
type: 'bar', type: 'bar',
stack: 'stack', stack: 'stack',
data: [0, 0, 0, 0, 0, 1, 2], data: waimaiData,
label: { label: { show: false }
show: true,
position: 'top',
color: '#FFF'
}
}, },
{ {
name: '本土受威胁鱼类', name: '本土受威胁鱼类',
type: 'bar', type: 'bar',
stack: 'stack', stack: 'stack',
data: [1, 10, 10, 5, 4, 6, 11], data: bentuThreatData,
label: { label: { show: false }
show: true,
position: 'top',
color: '#FFF'
}
}, },
{ {
name: '本土无危鱼类', name: '本土无危鱼类',
type: 'bar', type: 'bar',
stack: 'stack', stack: 'stack',
data: [1, 10, 6, 5, 2, 13, 23], data: bentuSafeData,
label: { label: { show: false }
show: true,
position: 'top',
color: '#FFF'
}
} }
], ],
barWidth: 15, barWidth: 15,
color: ['#4B79AB', '#78C300', '#9556A4'] color: ['#4B79AB', '#78C300', '#9556A4']
}; };
//
chartInstance.setOption(option); chartInstance.setOption(option);
nextTick(() => {
chartInstance.resize();
});
}; };
// /** 窗口大小变化时自适应 */
const handleResize = () => { const handleResize = () => {
if (chartInstance) { if (chartInstance) {
chartInstance.resize(); chartInstance.resize();
} }
}; };
// ==================== ====================
/**
* 获取年份选项数据
* 调用 getDftYear API按基地/流域分组返回可用年份
* 初始化时调用为每个流域缓存其对应的年份列表
*/
const getYearData = async () => {
try {
const filter: any = {};
if (baseid.value && baseid.value !== 'all') {
filter.baseId = baseid.value;
}
const params = {
filter,
group: [{ dir: 'des', field: 'yr' }]
};
const res = await getDftYear(params);
const data = res?.data?.data?.[0] || {};
if (Object.keys(data).length > 0) {
const objYear: Record<string, any[]> = {};
const allYears: any[] = [];
Object.keys(data).forEach(key => {
objYear[key] = data[key].map((item: any) => ({
label: String(item),
value: String(item)
}));
allYears.push(...data[key]);
});
yearOptions.value = objYear;
//
const uniqueYears = [...new Set(allYears)].sort(
(a: any, b: any) => Number(b) - Number(a)
);
const latestYear = uniqueYears[0];
// baseid all
const firstWbsCode = Object.keys(data)[0];
select.value.options = objYear[firstWbsCode] || [];
select.value.value = latestYear
? String(latestYear)
: moment().format('YYYY');
} else {
yearOptions.value = {};
select.value.options = [];
select.value.value = moment().format('YYYY');
}
} catch (error) {
console.error('获取年份数据失败:', error);
select.value.options = [];
select.value.value = moment().format('YYYY');
}
};
/**
* 根据选中的流域代码更新年份下拉选项
* yearOptions 缓存中查找对应流域的年份列表并更新 select.options
* @param wbsCode 流域代码
*/
const updateYearOptionsByBase = async (wbsCode: string) => {
const options = yearOptions.value[wbsCode];
if (options) {
select.value.options = options;
await nextTick();
//
select.value.value = options[0]?.value || '';
} else {
select.value.options = [];
await nextTick();
select.value.value = '';
}
};
/**
* 获取流域树形选择器数据 + 年份数据 + 图表数据
* 在基地变化时调用是整个模块数据加载的入口
*/
const getMoreSelectData = async () => {
if (!baseid.value) return;
isInitializing.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'wbsType',
operator: 'eq',
value: 'PSB_RVCD'
},
baseid.value !== 'all'
? {
field: 'objid',
operator: 'eq',
value: baseid.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);
const data = res?.data?.data || [];
if (data.length > 0) {
const treeData = buildTreeData(data);
const filteredData = filterSelectOptions(treeData);
//
moreSelect.value.options = filteredData;
moreSelect.value.value = treeData?.[0]?.children?.[0]?.value;
// yearOptions
await getYearData();
if (moreSelect.value.value) {
await updateYearOptionsByBase(moreSelect.value.value);
}
await getChartData();
} else {
moreSelect.value.options = [];
moreSelect.value.value = undefined;
await getYearData();
}
} catch (error) {
console.error('获取树形选择器数据失败:', error);
moreSelect.value.options = [];
moreSelect.value.value = undefined;
} finally {
isInitializing.value = false;
}
};
/**
* 获取图表数据
* 根据当前选中的流域(hbrvcd)年份(yr)基地(baseId)调用 fishChanges2 API
*/
const getChartData = async () => {
if (!select.value.value) return;
dataLoading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
moreSelect.value.value
? {
field: 'hbrvcd',
operator: 'eq',
dataType: 'string',
value: moreSelect.value.value
}
: null,
{
field: 'yr',
operator: 'eq',
dataType: 'string',
value: moment(select.value.value).format('YYYY')
},
baseid.value !== 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: baseid.value
}
: null
].filter(Boolean)
},
group: [
{ dir: 'des', field: 'stnm' },
{ dir: 'des', field: 'stcd' },
{ dir: 'des', field: 'ty' }
],
groupResultFlat: 'true'
};
const res = await fishChanges2(params);
chartData.value = res?.data?.data || [];
nextTick(() => {
updateChart();
});
} catch (error) {
console.error('获取图表数据失败:', error);
chartData.value = [];
nextTick(() => {
updateChart();
});
} finally {
dataLoading.value = false;
}
};
// ==================== ====================
/**
* API 返回的扁平数据转换为树形结构
* objid基地分组每个基地下包含其流域列表
* @param data 扁平的流域数据数组
*/
const buildTreeData = (data: any[]) => {
if (!data || data.length === 0) return [];
//
const jiDiListMap: Record<string, string> = {};
JidiSelectEventStore.jidiData.forEach((item: any) => {
jiDiListMap[item.wbsCode] = item.wbsName;
});
// objid
const dataMapNameMap: Record<string, any[]> = {};
data.forEach(item => {
const objId = item.objid || item.objId;
if (!dataMapNameMap[objId]) {
dataMapNameMap[objId] = [];
}
const exists = dataMapNameMap[objId].some(
existing => existing.wbsCode === item.wbsCode
);
if (!exists) {
dataMapNameMap[objId].push(item);
}
});
const treeData = Object.keys(dataMapNameMap).map(objId => ({
title: jiDiListMap[objId] || '---',
value: objId,
selectable: false,
children: dataMapNameMap[objId].map((item: any) => ({
title: item.wbsName,
value: item.wbsCode
}))
}));
//
treeData.sort((a, b) => {
const numA = Number(a.value);
const numB = Number(b.value);
const aIsNum = !isNaN(numA);
const bIsNum = !isNaN(numB);
if (aIsNum && bIsNum) return numA - numB;
if (aIsNum) return -1;
if (bIsNum) return 1;
return a.value.localeCompare(b.value);
});
return treeData;
};
/**
* 过滤树形选择器数据
* 当只有一个基地且只有一个流域时直接返回流域列表跳过基地层级
*/
const filterSelectOptions = (options: any[]) => {
if (options?.length === 1 && options?.[0]?.children?.length === 1) {
return options[0].children;
}
return options;
};
// ==================== ====================
/**
* 打开详情弹框
* 点击图表柱子时触发传递当前选中的年份流域站点名称等参数
*/
const openDetail = (params: any) => {
// chartData stcd
const found = chartData.value.find((el: any) => el.stnm === params.name);
modalYear.value = select.value.value;
modalHbrvcd.value = moreSelect.value.value || '';
modalStateName.value = params.name || '';
modalSectionName.value = params.name || '';
modalStcd.value = found?.stcd || '';
modalVisible.value = true;
};
/** 关闭详情弹框 */
const handleModalClose = () => {
modalVisible.value = false;
};
// ==================== ====================
/**
* 处理侧边面板控件变化流域或年份选择改变时触发
* SidePanelItem 内部通过 watch 监听用户操作后 emit 此事件
* @param data 包含 { select, moreSelect, ... } 的变更值
*/
const handlePanelChange = async (data: any) => {
//
if (isInitializing.value) return;
// +
if (
data.moreSelect !== undefined &&
data.moreSelect !== moreSelect.value.value
) {
moreSelect.value.value = data.moreSelect;
await updateYearOptionsByBase(data.moreSelect);
await getChartData();
return; //
}
//
if (data.select !== undefined && data.select !== select.value.value) {
select.value.value = data.select;
await getChartData();
}
};
// ==================== Watch ====================
/**
* 监听基地变化
* 当用户在顶部切换基地时重新加载流域列表年份和图表数据
*/
watch(
() => JidiSelectEventStore.selectedItem,
async newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
await getMoreSelectData();
}
},
{ deep: true, immediate: true }
);
// ==================== ==================== // ==================== ====================
//
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
setTimeout(() => { setTimeout(() => {
@ -187,12 +598,9 @@ onMounted(() => {
}); });
}); });
//
onUnmounted(() => { onUnmounted(() => {
//
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
//
if (chartInstance) { if (chartInstance) {
chartInstance.dispose(); chartInstance.dispose();
chartInstance = null; chartInstance = null;
@ -206,4 +614,18 @@ onUnmounted(() => {
height: 231px; height: 231px;
min-height: 231px; min-height: 231px;
} }
.empty-placeholder {
width: 100%;
min-height: 231px;
display: flex;
background: #fff;
flex-direction: column;
justify-content: center;
align-items: center;
}
:deep(.ant-empty-description) {
text-indent: 0em !important;
}
</style> </style>

View File

@ -14,7 +14,7 @@ onMounted(() => {});
<template> <template>
<div class="jidiSelectorMod"> <div class="jidiSelectorMod">
<a-spin :spinning="loading"> <a-spin :spinning="JidiSelectEventStore.loading">
<div <div
class="qgc-dropdown-select" class="qgc-dropdown-select"
@mouseenter="jidiDataNum = JidiSelectEventStore.jidiData.length" @mouseenter="jidiDataNum = JidiSelectEventStore.jidiData.length"

View File

@ -0,0 +1,234 @@
<template>
<div class="lsstjk-table">
<!-- 表格 -->
<div class="table-wrapper">
<BasicTable
ref="tableRef"
:columns="columns"
:scroll-y="500"
:scroll-x="800"
:list-url="listUrl"
:search-params="searchParams"
:transform-data="customTransform"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue';
import BasicTable from '@/components/BasicTable/index.vue';
import { getVmsstbprptList, getTetSpecList } from '@/api/stdc';
import dayjs from 'dayjs';
// ==================== Props ====================
const props = defineProps<{
selectDateTime?: any;
orderIndex?: string;
hbrvcd?: string;
}>();
// ==================== ====================
const tableRef = ref();
const yearValue = ref(
props.selectDateTime ? dayjs(props.selectDateTime) : dayjs()
);
// ==================== URL ====================
const listUrl = computed(() => {
return props.orderIndex === '20' ? getTetSpecList : getVmsstbprptList;
});
// ==================== ====================
const columns = computed(() => {
if (props.orderIndex === '20') {
return [
{
key: 'name',
title: '名称',
dataIndex: 'name',
fixed: 'left' as const,
width: 200,
ellipsis: true
},
{
key: 'nameEn',
title: '英文名称',
dataIndex: 'nameEn',
width: 200,
ellipsis: true
},
{
key: 'protectlvlName',
title: '保护级别',
dataIndex: 'protectlvlName',
width: 200,
ellipsis: true
}
];
}else{
// orderIndex === '16'
return [
{
key: 'box',
title: '监测区域',
dataIndex: 'box',
fixed: 'left' as const,
width: 200,
ellipsis: true
},
{
key: 'stnm',
title: '站点名称',
dataIndex: 'stnm',
width: 200,
ellipsis: true
},
{
key: 'baseName',
title: '所属基地',
dataIndex: 'baseName',
width: 200,
ellipsis: true
},
{
key: 'stlc',
title: '所在地址',
dataIndex: 'stlc',
width: 200,
ellipsis: true
}
];
}
});
// ==================== ====================
const searchParams = computed(() => {
const filters: any[] = [];
if (props.hbrvcd) {
filters.push({
field: 'hbrvcd',
operator: 'in',
dataType: 'string',
value: props.hbrvcd
});
}
if (props.orderIndex === '20') {
filters.push({
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'TE_ANIMALS'
});
}
const startTime = `${dayjs(yearValue.value).format('YYYY')}-01-01 00:00:00`;
const endTime = `${dayjs(yearValue.value).format('YYYY')}-12-31 23:59:59`;
filters.push({
field: 'startTime',
operator: 'gte',
dataType: 'date',
value: startTime
});
filters.push({
field: 'endTime',
operator: 'lte',
dataType: 'date',
value: endTime
});
return {
filter: {
logic: 'and',
filters
},
sort: [],
group: [],
select: [],
groupResultFlat: false
};
});
// ==================== ====================
const customTransform = (res: any) => {
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
};
};
// ==================== ====================
const handleSearch = () => {
const filter = getFilter();
tableRef.value?.getList(filter);
};
const getFilter = () => {
const filters: any[] = [
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'WVA'
}
];
if (props.hbrvcd) {
filters.push({
field: 'hbrvcd',
operator: 'in',
dataType: 'string',
value: props.hbrvcd
});
}
return {
logic: 'and',
filters
};
};
// ==================== selectDateTime ====================
watch(
() => props.selectDateTime,
newVal => {
if (newVal) {
yearValue.value = dayjs(newVal);
handleSearch();
}
},
{ immediate: false }
);
// ==================== orderIndex ====================
watch(
() => props.orderIndex,
() => {
handleSearch();
}
);
// ==================== ====================
onMounted(() => {
handleSearch();
});
</script>
<style lang="scss" scoped>
.lsstjk-table {
width: 100%;
.search-form {
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 16px;
}
.table-wrapper {
width: 100%;
}
}
</style>

View File

@ -1,42 +1,96 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="水生生态调查情况" :select="select" :datetimePicker="datetimePicker"> <SidePanelItem
title="陆生生态监测情况"
:select="select"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange"
>
<a-spin :spinning="dataLoading" tip="加载中...">
<div class="facility-grid"> <div class="facility-grid">
<div v-for="facility in facilities" :key="facility.name" class="facility-card"> <div
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;"> v-for="facility in facilities"
:key="facility.name"
class="facility-card"
@click="handleCardClick(facility)"
>
<div
style="
width: 60px;
height: 62px;
display: flex;
align-items: center;
justify-content: center;
"
>
<div class="facility-icon"> <div class="facility-icon">
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i> <i
style="color: #fff"
:class="facility.icon"
type="icon-shengtailiuliang2"
></i>
</div> </div>
</div> </div>
<div class="facility-info"> <div class="facility-info">
<div class="facility-name">{{ facility.name }}</div> <div class="facility-name">{{ facility.name }}</div>
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count <div style="font-size: 16px">
}}</span><span></span></div> <span class="facility-count">{{ facility.count }}</span>
<span>{{ facility.unit }}</span>
</div> </div>
</div> </div>
</div> </div>
</div>
</a-spin>
</SidePanelItem> </SidePanelItem>
<!-- 陆生生态调查概况弹框 -->
<a-modal
v-model:open="modalVisible"
:title="currentOrderIndex === '16' ? '红外相机信息' : '重点陆生动物'"
width="1536px"
:footer="null"
@cancel="handleModalClose"
>
<LsstjkTk
v-if="modalVisible"
:selectDateTime="datetimePicker.value"
:orderIndex="currentOrderIndex"
:hbrvcd="select.value"
/>
</a-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'; import { ref, watch } from 'vue';
import * as echarts from 'echarts'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import LsstjkTk from './LsstjkTk.vue';
import { wbsbGetKendoList, getEvnmAutoMonitor } from '@/api/stdc';
import moment from 'moment';
// //
defineOptions({ defineOptions({
name: 'shuishengshengtaijiance' name: 'lushengshengtaijiance'
}); });
// ==================== ====================// const JidiSelectEventStore = useJidiSelectEventStore();
const baseid = ref('');
const dataLoading = ref(false);
// ==================== ====================
const modalVisible = ref(false);
const currentOrderIndex = ref('16');
// ==================== ====================
//
const select = ref({ const select = ref({
show: true, show: true,
value: undefined, value: undefined,
options: [], options: [],
picker: undefined, picker: undefined,
format: undefined format: undefined,
width: '120px'
}); });
// //
@ -48,30 +102,202 @@ const datetimePicker = ref({
options: [] options: []
}); });
const facilities = ref([ const facilities:any = ref([
{ {
name: '红外相机数量', name: '野生动物监测',
count: 31, count: 0,
icon: 'icon iconfont icon-shipinjiankongshebei' unit: '个',
icon: 'icon iconfont icon-dongwujiuzhuzhan',
orderInx: '16'
}, },
{ {
name: '重点陆生动物', name: '重点陆生动物',
count: 24, count: 0,
icon: 'icon iconfont icon-dongwujiuzhuzhan' unit: '种',
}, icon: 'icon iconfont icon-shipinjiankongshebei',
orderInx: '20'
}
]); ]);
// ==================== ====================
// // ==================== ====================
onMounted(() => { const handlePanelChange = (data: any) => {
console.log('当前所有控件状态:', data);
if (data.select) {
select.value.value = data.select;
}
if (data.datetime) {
datetimePicker.value.value = data.datetime;
}
//
getCardData();
};
const handleCardClick = (facility: any) => {
console.log('卡片被点击,准备打开弹框');
// debugger
currentOrderIndex.value = facility.orderInx;
modalVisible.value = true;
};
const handleModalClose = () => {
modalVisible.value = false;
};
// ==================== ====================
const getCardData = async () => {
if (!datetimePicker.value.value) return;
dataLoading.value = true;
try {
const year = moment(datetimePicker.value.value).format('YYYY');
const startTime = `${year}-01-01 00:00:00`;
const endTime = `${year}-12-31 23:59:59`;
const filters: any[] = [
{
field: 'showIds',
operator: 'in',
value: ['16', '20']
},
{
field: 'startTime',
operator: 'gte',
dataType: 'date',
value: startTime
},
{
field: 'endTime',
operator: 'lte',
dataType: 'date',
value: endTime
}
];
if (baseid.value && baseid.value !== 'all') {
filters.unshift({
field: 'hbrvcd',
operator: 'in',
value: [baseid.value]
}); });
}
// const params = {
onUnmounted(() => { filter: {
logic: 'and',
filters
}
};
const res = await getEvnmAutoMonitor(params);
const data = res?.data?.data;
if (data && Array.isArray(data)) {
const wildAnimalItem = data.find((item: any) => item.orderInx === 16);
const keyAnimalItem = data.find((item: any) => item.orderInx === 20);
facilities.value = [
{
name: '野生动物监测',
count: wildAnimalItem?.cnt ?? 0,
unit: '个',
icon: 'icon iconfont icon-dongwujiuzhuzhan',
orderInx: '16'
},
{
name: '重点陆生动物',
count: keyAnimalItem?.cnt ?? 0,
unit: '种',
icon: 'icon iconfont icon-shipinjiankongshebei',
orderInx: '20'
}
];
}
} catch (error) {
console.error('获取统计数据失败:', error);
facilities.value = [
{
name: '野生动物监测',
count: 0,
unit: '个',
icon: 'icon iconfont icon-dongwujiuzhuzhan',
orderInx: '16'
},
{
name: '重点陆生动物',
count: 0,
unit: '种',
icon: 'icon iconfont icon-shipinjiankongshebei',
orderInx: '20'
}
];
} finally {
dataLoading.value = false;
}
};
// ==================== ====================
const getSelectData = async () => {
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'wbsType',
operator: 'eq',
value: 'PSB_RVCD'
},
baseid.value != 'all'
? {
field: 'objId',
operator: 'eq',
value: baseid.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
};
}); });
};
// ==================== ====================
watch(
() => JidiSelectEventStore.selectedItem,
async newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getSelectData();
getCardData();
}
},
{ deep: true, immediate: true }
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -80,7 +306,9 @@ onUnmounted(() => {
flex-flow: wrap; flex-flow: wrap;
display: flex; display: flex;
justify-content: space-between; 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; 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 { .facility-card {
@ -91,7 +319,7 @@ onUnmounted(() => {
justify-content: space-between; justify-content: space-between;
margin: 4px 0px; margin: 4px 0px;
background: #fff; background: #fff;
border: 1px solid #e8e8e8; border: 1px solid rgb(229, 236, 245);
border-radius: 2px; border-radius: 2px;
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
@ -104,7 +332,6 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
width: 40px; width: 40px;
height: 40px; height: 40px;
// margin-right: 8px;
background: #2f6b98; background: #2f6b98;
border-radius: 50%; border-radius: 50%;
@ -121,13 +348,14 @@ onUnmounted(() => {
.facility-name { .facility-name {
font-size: 16px; font-size: 16px;
color: #333; color: #333;
// margin-bottom: 4px;
// font-weight: 500;
} }
.facility-count { .facility-count {
font-size: 18px; font-size: 18px;
color: #2f6b98; color: #2f6b98;
// font-weight: 600; }
:deep(.ant-spin-nested-loading) {
min-height: 72px;
} }
</style> </style>

View File

@ -0,0 +1,362 @@
<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 });
if (res?.data?.data?.length) {
const options = res.data.data.map((item: any) => ({
label: item.dcpc,
value: item.dcpc
}));
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

@ -1,48 +1,101 @@
<!-- SidePanelItem.vue --> <!-- SidePanelItem.vue -->
<template> <template>
<SidePanelItem title="水生生态调查情况" :select="select" :datetimePicker="datetimePicker"> <SidePanelItem
title="水生生态调查情况"
:select="select"
:datetimePicker="datetimePicker"
@update-values="handlePanelChange"
>
<a-spin :spinning="dataLoading" tip="加载中...">
<div class="facility-grid"> <div class="facility-grid">
<div v-for="facility in facilities" :key="facility.name" class="facility-card"> <div
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;"> v-for="facility in facilities"
:key="facility.name"
class="facility-card"
@click="handleCardClick"
>
<div
style="
width: 60px;
height: 62px;
display: flex;
align-items: center;
justify-content: center;
"
>
<div class="facility-icon"> <div class="facility-icon">
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i> <i
style="color: #fff"
:class="facility.icon"
type="icon-shengtailiuliang2"
></i>
</div> </div>
</div> </div>
<div class="facility-info"> <div class="facility-info">
<div class="facility-name">{{ facility.name }}</div> <div class="facility-name">{{ facility.name }}</div>
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count <div style="font-size: 16px">
}}</span><span></span></div> <span class="facility-count">{{ facility.count }}</span>
<span>{{ facility.unit }}</span>
</div> </div>
</div> </div>
</div> </div>
</div>
</a-spin>
</SidePanelItem> </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> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'; import { ref, watch } from 'vue';
import * as echarts from 'echarts'; import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import type { EChartsOption } from 'echarts';
import SidePanelItem from '@/components/SidePanelItem/index.vue'; import SidePanelItem from '@/components/SidePanelItem/index.vue';
import SsstdcgkTk from './SsstdcgkTk.vue';
import { wbsbGetKendoList, getDftYear, getWeFisht } from '@/api/stdc';
import moment from 'moment';
import { Label } from 'cesium';
// //
defineOptions({ defineOptions({
name: 'shuishengshengtaijiance' name: 'shuishengshengtaijiance'
}); });
// ==================== ====================// const JidiSelectEventStore = useJidiSelectEventStore();
const baseid = ref('');
const dataLoading = ref(false);
// ==================== ====================
const modalVisible = ref(false);
// ==================== ====================
//
const select = ref({ const select = ref({
show: true, show: true,
value: undefined, value: undefined,
options: [], options: [],
picker: undefined, picker: undefined,
format: undefined format: undefined,
width: '120px'
}); });
// //
const datetimePicker = ref({ const datetimePicker = ref({
show: true, show: true,
value: '2023', value: '',
format: 'YYYY', format: 'YYYY',
picker: 'year' as const, picker: 'year' as const,
options: [] options: []
@ -51,27 +104,235 @@ const datetimePicker = ref({
const facilities = ref([ const facilities = ref([
{ {
name: '水生态调查断面', name: '水生态调查断面',
count: 145, count: 0,
unit: '个',
icon: 'icon iconfont icon-liuliangjiancezhan' icon: 'icon iconfont icon-liuliangjiancezhan'
}, },
{ {
name: '调查鱼类统计', name: '调查鱼类统计',
count: 24, count: 0,
unit: '种',
icon: 'icon iconfont icon-yuleizengzhizhan' icon: 'icon iconfont icon-yuleizengzhizhan'
}, }
]); ]);
// ==================== ====================
// // ==================== ====================
onMounted(() => { const handlePanelChange = (data: any) => {
console.log('当前所有控件状态:', data);
if (data.select) {
select.value.value = data.select;
}
if (data.datetime) {
datetimePicker.value.value = data.datetime;
}
//
getCardData();
};
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');
}
//
getCardData();
} catch (error) {
console.error('获取默认年份失败:', error);
datetimePicker.value.value = moment().format('YYYY');
getCardData();
}
};
// ==================== ====================
const getCardData = async () => {
if (!datetimePicker.value.value) return;
dataLoading.value = true;
try {
const filters: any[] = [
{
field: 'yr',
operator: 'eq',
dataType: 'string',
value: moment(datetimePicker.value.value).format('YYYY')
}
];
if (baseid.value && baseid.value !== 'all') {
filters.push({
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: baseid.value
}); });
}
// if (select.value.value) {
onUnmounted(() => { filters.push({
field: 'hbrvcd',
operator: 'eq',
dataType: 'string',
value: select.value.value
}); });
}
const params = {
filter: {
logic: 'and',
filters
},
group: [],
groupResultFlat: true
};
const res = await getWeFisht(params);
const data = res?.data?.data?.[0];
facilities.value = [
{
name: '水生态调查断面',
count: data?.stcdCount ?? 0,
unit: '个',
icon: 'icon iconfont icon-liuliangjiancezhan'
},
{
name: '调查鱼类统计',
count: data?.ftpCount ?? 0,
unit: '种',
icon: 'icon iconfont icon-yuleizengzhizhan'
}
];
} catch (error) {
console.error('获取统计数据失败:', error);
facilities.value = [
{
name: '水生态调查断面',
count: 0,
unit: '个',
icon: 'icon iconfont icon-liuliangjiancezhan'
},
{
name: '调查鱼类统计',
count: 0,
unit: '种',
icon: 'icon iconfont icon-yuleizengzhizhan'
}
];
} finally {
dataLoading.value = false;
}
};
// ==================== ====================
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
};
});
};
// ==================== ====================
watch(
() => JidiSelectEventStore.selectedItem,
async newVal => {
if (newVal && newVal.wbsCode) {
baseid.value = newVal.wbsCode;
getSelectData();
getDefaultYear();
}
},
{ deep: true, immediate: true }
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -80,7 +341,9 @@ onUnmounted(() => {
flex-flow: wrap; flex-flow: wrap;
display: flex; display: flex;
justify-content: space-between; 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; 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 { .facility-card {
@ -91,7 +354,7 @@ onUnmounted(() => {
justify-content: space-between; justify-content: space-between;
margin: 4px 0px; margin: 4px 0px;
background: #fff; background: #fff;
border: 1px solid #e8e8e8; border: 1px solid rgb(229, 236, 245);
border-radius: 2px; border-radius: 2px;
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
@ -130,4 +393,8 @@ onUnmounted(() => {
color: #2f6b98; color: #2f6b98;
// font-weight: 600; // font-weight: 600;
} }
:deep(.ant-spin-nested-loading) {
min-height: 72px;
}
</style> </style>

View File

@ -1,81 +1,15 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ref } from 'vue'; import { ref, onMounted } from 'vue';
import { wbsbGetKendoList } from '@/api/select';
export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => { export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
const jidiData: any = ref([ const jidiData: any = ref([
{ {
wbsCode: 'all', wbsCode: 'all',
wbsName: '当前全部', wbsName: '当前全部',
selected: true selected: true
},
{
wbsCode: '01',
wbsName: '金沙江干流',
selected: false
},
{
wbsCode: '02',
wbsName: '雅砻江干流',
selected: false
},
{
wbsCode: '03',
wbsName: '大渡河干流',
selected: false
},
{
wbsCode: '04',
wbsName: '乌江干流',
selected: false
},
{
wbsCode: '05',
wbsName: '长江上游干流',
selected: false
},
{
wbsCode: '10',
wbsName: '湘西',
selected: false
},
{
wbsCode: '08',
wbsName: '黄河上游干流',
selected: false
},
{
wbsCode: '09',
wbsName: '黄河中游干流',
selected: false
},
{
wbsCode: '06',
wbsName: '南盘江-红水河',
selected: false
},
{
wbsCode: '12',
wbsName: '东北',
selected: false
},
{
wbsCode: '07',
wbsName: '澜沧江干流',
selected: false
},
{
wbsCode: '13',
wbsName: '怒江干流'
},
{
wbsCode: '11',
wbsName: '闽浙赣'
},
{
wbsCode: 'other',
wbsName: '其他'
} }
]); ]);
const loading = ref(false);
const selectedItem = ref(jidiData.value[0]); const selectedItem = ref(jidiData.value[0]);
const updataJidiData = (index: any) => { const updataJidiData = (index: any) => {
console.log(index); console.log(index);
@ -85,10 +19,58 @@ export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
}); });
jidiData.value[index].selected = true; jidiData.value[index].selected = true;
}; };
const getJidiData = async () => {
loading.value = true;
let params = {
filter: {
logic: 'and',
filters: [
{
field: 'wbsType',
operator: 'eq',
dataType: 'string',
value: 'PSB'
},
{
field: 'treeLevel',
operator: 'eq',
dataType: 'string',
value: '1'
}
]
},
sort: [
{
field: 'orderIndex',
dir: 'asc'
}
],
select: ['wbsCode', 'wbsName']
};
const res = await wbsbGetKendoList(params);
let data = res?.data?.data || [
{
wbsCode: 'all',
wbsName: '当前全部',
selected: true
}
];
jidiData.value = data.map((item: any) => {
return {
...item,
selected: item.wbsCode == 'all' ? true : false
};
});
loading.value = false;
};
onMounted(() => {
getJidiData();
});
return { return {
jidiData, jidiData,
selectedItem, selectedItem,
updataJidiData updataJidiData,
loading
}; };
}); });

View File

@ -154,7 +154,7 @@ const showMapModal17 = () => {
<!-- <a-button @click="showMapModal7">打开动物救助站弹窗</a-button> --> <!-- <a-button @click="showMapModal7">打开动物救助站弹窗</a-button> -->
<!-- <a-button @click="showMapModal8">打开珍稀植物园弹窗</a-button> --> <!-- <a-button @click="showMapModal8">打开珍稀植物园弹窗</a-button> -->
<!-- <a-button @click="showMapModal9">打开水生生态调查断面弹窗</a-button> --> <!-- <a-button @click="showMapModal9">打开水生生态调查断面弹窗</a-button> -->
<!-- <a-button @click="showMapModal10">打开野生动物监测弹窗</a-button> --> <a-button @click="showMapModal10">打开野生动物监测弹窗</a-button>
<!-- <a-button @click="showMapModal11">打开AI视频监控站弹窗</a-button> --> <!-- <a-button @click="showMapModal11">打开AI视频监控站弹窗</a-button> -->
<!-- <a-button @click="showMapModal12">打开水电站告警情况弹窗</a-button> --> <!-- <a-button @click="showMapModal12">打开水电站告警情况弹窗</a-button> -->
<!-- <a-button @click="showMapModal13">打开鱼类调查装置弹窗</a-button> --> <!-- <a-button @click="showMapModal13">打开鱼类调查装置弹窗</a-button> -->