WholeProcessPlatform/frontend/src/modules/alongFishMod/TwoLayers/Yanchengyulei.vue

616 lines
16 KiB
Vue
Raw Normal View History

2026-06-26 17:52:57 +08:00
<!-- 沿程鱼类变化详情组件 -->
<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>