WholeProcessPlatform/frontend/src/views/dianZhanZhuanTi/components/ZXZWYYunXingShuJu.vue

465 lines
12 KiB
Vue
Raw Normal View History

2026-07-31 11:13:49 +08:00
<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>