WholeProcessPlatform/frontend/src/views/dianZhanZhuanTi/components/ZXZWYYunXingShuJu.vue
2026-07-31 11:13:49 +08:00

465 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<SidePanelItem
title="珍稀植物园运行数据"
:moreSelect="moreSelect"
:select="select"
@update-values="handlePanelChange"
>
<!-- 加载状态 -->
<a-spin v-if="cardLoading" class="spin-center" />
<!-- 空数据 -->
<a-empty
v-else-if="!hasCardData"
description="暂无数据"
class="empty-center"
/>
<!-- 左右卡片 -->
<div v-else class="card-wrapper" @click="openModal">
<div class="card-item card-left">
<div class="card-icon">
<i class="icon iconfont icon-chengliu"></i>
</div>
<span class="card-name"
>{{ showData.botanyName || ''
}}{{ showData.unitName ? `(${showData.unitName})` : '' }}</span
>
<h5 class="card-count">{{ showData.tamarixChinensis ?? '-' }}</h5>
<img :src="arrowUpSvg" alt="" class="arrow-icon" />
<div class="card-rate">{{ showData.tamarixChinensisLive ?? '-' }}</div>
<p class="card-label">存活率</p>
</div>
<div class="card-item card-right">
<div class="card-icon"> <i class="icon iconfont icon-zhenxizhiwuyuan"></i></div>
<span class="card-name">其他</span>
<h5 class="card-count">{{ showData.other || 0 }}</h5>
<img :src="arrowUpSvg" alt="" class="arrow-icon" />
<div class="card-rate">{{ showData.otherLive ?? '-' }}</div>
<p class="card-label">存活率</p>
</div>
</div>
<!-- 详情弹窗 -->
<a-modal
v-model:open="modalVisible"
title="珍稀植物园运行数据列表"
:footer="null"
width="80%"
:destroyOnClose="true"
:centered="true"
class="detail-modal"
>
<div class="search-form">
<a-space size="middle">
<div>年份:</div>
<a-select
v-model:value="modalYear"
style="width: 100px"
:options="select.options"
@change="onModalSearch"
/>
<a-button type="primary" @click="onModalSearch">查询</a-button>
</a-space>
</div>
<BasicTable
ref="tableRef"
:list-url="getBotanicalGardenDetailData"
:columns="detailColumns"
:scrollY="400"
:search-params="modalSearchParams"
:transform-data="detailTransform"
/>
</a-modal>
</SidePanelItem>
</template>
<script lang="ts" setup>
import { ref, computed, watch, inject, nextTick } from 'vue';
import { message } from 'ant-design-vue';
import SidePanelItem from '@/components/SidePanelItem/index.vue';
import BasicTable from '@/components/BasicTable/index.vue';
import {
getBotanicalGardenList,
getBotanicalGardenStatistic,
getBotanicalGardenDetailData
} from '@/api/ZXZWYYunXingShuJu';
import arrowUpSvg from './arrowUpLg.svg';
defineOptions({ name: 'ZXZWYYunXingShuJu' });
// ==================== 注入电站上下文 ====================
const dianZhanStation = inject<any>('dianZhanStation', ref(null));
const apiId = computed(() => dianZhanStation.value?.stcd || '');
// ==================== SidePanelItem 选择器配置 ====================
const isInitializing = ref(false);
/** moreSelect: 植物园(标题栏树形选择器) */
const moreSelect = ref({
show: true,
value: undefined as string | undefined,
options: [] as any[]
});
/** select: 年份(标题栏普通选择器) */
const select = ref({
show: true,
value: undefined as string | undefined,
options: [] as any[],
width: '120px',
minWidth: '120px'
});
/** 各地植物园的年份列表:{ [stcd]: year[] } */
const yearMap = ref<Record<string, string[]>>({});
// ==================== 卡片数据 ====================
const cardLoading = ref(false);
const hasCardData = ref(false);
const showData = ref({
botanyName: '',
unitName: '',
tamarixChinensis: '-' as string | number,
tamarixChinensisLive: '-' as string | number,
other: 0 as string | number,
otherLive: '-' as string | number
});
// ==================== Modal ====================
const modalVisible = ref(false);
const modalYear = ref<string>();
const tableRef = ref<any>(null);
/** 当前选中的植物园 stcd从 moreSelect.options 中找) */
const currentGardenStcd = computed(() => {
const val = moreSelect.value.value;
const options = moreSelect.value.options || [];
const found = (options as any[]).find((o: any) => o.value === val);
return found?.stcd || '';
});
const modalSearchParams = computed(() => ({
group: [],
groupResultFlat: false
}));
// ==================== 详情表格列定义 ====================
const detailColumns = [
{
key: 'stnm',
title: '植物园名称',
dataIndex: 'stnm',
width: '150px',
ellipsis: true
},
{ key: 'tm', title: '年份', dataIndex: 'tm', width: '80px' },
{ key: 'tetp', title: '保护种类', dataIndex: 'tetp', width: '120px' },
{ key: 'unitName', title: '单位', dataIndex: 'unitName', width: '80px' },
{
key: 'tecntDisplay',
title: '实际种植/种植要求',
dataIndex: 'tecntDisplay',
width: '140px'
},
{
key: 'surnumDisplay',
title: '存活数量/移栽规模',
dataIndex: 'surnumDisplay',
width: '140px'
},
{ key: 'rate', title: '存活率(%)', dataIndex: 'rate', width: '100px' }
];
/** 预处理详情数据:拼接种植/存活字段 */
const detailTransform = (res: any) => {
const records = (res?.data?.data || []).map((item: any) => ({
...item,
tecntDisplay: `${item.tecnt ?? '-'}/${item.plantask ?? '-'}`,
surnumDisplay: `${item.surnum ?? '-'}/${item.transplant ?? '-'}`
}));
return { records, total: res?.data?.total || 0 };
};
// ==================== API 请求 ====================
/** 获取植物园下拉列表 */
const fetchGardenList = async () => {
if (!apiId.value) return;
isInitializing.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'rstcd',
operator: 'eq',
dataType: 'string',
value: apiId.value
}
]
},
group: [
{
dir: 'des',
field: 'year'
},
{
dir: 'des',
field: 'stcd'
},
{
dir: 'des',
field: 'name'
}
],
groupResultFlat: 'true'
};
const res = await getBotanicalGardenList(params);
const list = res?.data?.data?.[0]?.data || [];
if (list.length === 0) return;
const gardenMap = new Map<string, { stcd: string; stnm: string }>();
const yearMapTemp: Record<string, string[]> = {};
list.forEach((item: any) => {
if (!gardenMap.has(item.stcd)) {
gardenMap.set(item.stcd, { stcd: item.stcd, stnm: item.name });
}
if (!yearMapTemp[item.stcd]) {
yearMapTemp[item.stcd] = [];
}
yearMapTemp[item.stcd].push(item.year);
});
yearMap.value = yearMapTemp;
// moreSelect 使用树形选择器field-names 中 label 映射为 'title'
const gardens = Array.from(gardenMap.values());
moreSelect.value.options = gardens.map(g => ({
title: g.stnm,
value: g.stnm,
stcd: g.stcd
}));
// 设置默认值
const firstGarden = gardens[0];
moreSelect.value.value = firstGarden?.stnm;
updateYearOptions(firstGarden?.stcd);
} catch {
message.error('获取植物园列表失败');
} finally {
isInitializing.value = false;
}
};
/** 根据植物园 stcd 更新年份选项 */
const updateYearOptions = (stcd: string) => {
const years = yearMap.value[stcd] || [];
select.value.options = years.map(y => ({ label: y, value: y }));
select.value.value = years[0];
};
/** 获取卡片统计数据 */
const fetchStatistic = async () => {
const year = select.value.value;
const stcd = currentGardenStcd.value;
if (!year || !stcd) return;
cardLoading.value = true;
try {
const params = {
filter: {
logic: 'and',
filters: [
{
field: 'tm',
operator: 'eq',
dataType: 'string',
value: year
},
{
field: 'stcd',
operator: 'eq',
dataType: 'string',
value: stcd
}
]
}
};
const res = await getBotanicalGardenStatistic(params);
const list = res?.data?.data?.[0]?.data || [];
hasCardData.value = list.length > 0;
if (list.length > 0) {
const result = list.reduce((acc: any, cur: any) => {
if (cur.name === '其他') {
acc.other = cur.tecnt;
acc.otherLive = cur.rate;
} else {
acc.botanyName = cur.name;
acc.tamarixChinensis = cur.tecnt;
acc.tamarixChinensisLive = cur.rate;
acc.unitName = cur.unitName;
}
return acc;
}, {});
showData.value = {
botanyName: result.botanyName || '',
unitName: result.unitName || '',
tamarixChinensis: result.tamarixChinensis ?? '-',
tamarixChinensisLive: result.tamarixChinensisLive ?? '-',
other: result.other ?? 0,
otherLive: result.otherLive ?? '-'
};
}
} catch {
hasCardData.value = false;
} finally {
cardLoading.value = false;
}
};
// ==================== 事件处理 ====================
/** SidePanelItem 控件变化回调 */
const handlePanelChange = async (data: any) => {
if (isInitializing.value) return;
// 植物园变化 → 更新年份选项
if (
data.moreSelect !== undefined &&
data.moreSelect !== moreSelect.value.value
) {
moreSelect.value.value = data.moreSelect;
const stcd = (moreSelect.value.options as any[]).find(
(o: any) => o.value === data.moreSelect
)?.stcd;
updateYearOptions(stcd);
return; // select.value.value 被 updateYearOptions 修改后,会再次触发 handlePanelChange → 走下面的分支
}
// 年份变化 → 重新拉数据
if (data.select !== undefined && data.select !== select.value.value) {
select.value.value = data.select;
fetchStatistic();
}
};
/** 打开详情弹窗 */
const openModal = () => {
modalYear.value = select.value.value;
modalVisible.value = true;
nextTick(() => {
onModalSearch();
});
};
/** 弹窗内搜索 */
const onModalSearch = () => {
if (!modalYear.value || !currentGardenStcd.value) return;
tableRef.value?.getList({
endTime: [{ field: 'tm', operator: 'eq', value: modalYear.value }],
stcd: currentGardenStcd.value
});
};
// ==================== 监听 ====================
watch(apiId, newId => {
if (newId) {
fetchGardenList();
}
}, { immediate: true });
</script>
<style scoped>
.spin-center,
.empty-center {
min-height: 197px;
}
.card-wrapper {
display: flex;
gap: 8px;
cursor: pointer;
min-height: 197px;
padding: 0px 5px;
}
.card-icon{
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #2f6b98;
display: flex;
align-items: center;
justify-content: center;
}
.card-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8px;
border: 1px solid #e8e8e8;
border-radius: 4px;
transition: box-shadow 0.2s;
position: relative;
}
.card-item:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-left .icon {
font-size: 20px;
color: #fff;
}
.card-right .icon {
font-size: 20px;
color: #fff;
}
.card-name {
font-size: 14px;
color: #3E3E3E;
margin-top: 2px;
}
.card-count {
font-size: 24px;
line-height: 32px;
color: #2f6b98;
margin-bottom: 12px;
}
.arrow-icon {
width: 61px;
height: 64px;
margin-top: 2px;
}
.card-rate {
font-size: 14px;
color: #2f6b98;
position: absolute;
left: 50%;
top: 62%;
transform: translate(-50%, 0%);
}
.card-label {
font-size: 14px;
color: #3E3E3E;
text-indent:0 !important;
/* margin: 0; */
}
.detail-modal .search-form {
margin-bottom: 16px;
}
</style>