WholeProcessPlatform/frontend/src/modules/shengtaidabiaoMod/TwoLayer/ShengTaiLiuLiangDaBQKTwoLayer.vue

940 lines
22 KiB
Vue
Raw Normal View History

2026-06-12 16:59:35 +08:00
<template>
<div class="stlldbqk-container">
<!-- 搜索表单 -->
<a-form layout="inline" class="search-form">
<a-form-item label="基地选择">
<a-select
v-model:value="params.qid"
placeholder="请选择基地"
style="width: 200px"
show-search
:filter-option="filterOption"
:options="jidiOptions"
/>
</a-form-item>
<a-form-item label="规则类型">
<a-select
v-model:value="params.key"
placeholder="请选择规则类型"
style="width: 180px"
:options="ruleTypeOptions"
/>
</a-form-item>
<a-form-item label="时间维度">
<a-select
v-model:value="params.timeType"
placeholder="请选择时间维度"
style="width: 120px"
:options="timeTypeOptions"
/>
</a-form-item>
<a-form-item label="日期范围">
<a-range-picker
v-model:value="dateRange"
:allow-clear="false"
format="YYYY-MM-DD"
2026-06-16 17:39:51 +08:00
style="width: 260px"
2026-06-18 09:29:15 +08:00
:presets="DateSetting.RangeButton.days1"
2026-06-12 16:59:35 +08:00
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">查询</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- Tabs 标签页 -->
<a-tabs
v-if="tabList.length > 0"
v-model:activeKey="ecoFlowType"
class="tab-style"
@change="fetchTableData"
>
<a-tab-pane v-for="item in tabList" :key="item.key" :tab="item.label" />
</a-tabs>
<!-- 空状态 -->
<div v-if="!loading && tabList.length === 0" class="empty-state">
<a-empty description="暂无数据" />
</div>
2026-06-12 16:59:35 +08:00
<div v-show="tabList.length > 0" class="body_zhengti">
2026-06-12 16:59:35 +08:00
<div class="echarts">
<a-spin :spinning="dataLoading">
<div v-if="pieData.length > 0" ref="chartRef" class="pie-chart"></div>
<a-empty
v-if="!dataLoading && pieData.length === 0"
description="暂无数据"
/>
</a-spin>
</div>
2026-06-16 17:39:51 +08:00
<div class="table1">
2026-06-12 16:59:35 +08:00
<BasicTable
ref="tableRef"
:columns="columns"
2026-06-16 17:39:51 +08:00
:scrollY="400"
2026-06-12 16:59:35 +08:00
:list-url="
2026-06-15 14:10:19 +08:00
activeTimeType == 'day' ? dayGetKendoListCust : hourGetKendoListCust
2026-06-12 16:59:35 +08:00
"
:search-params="searchParams"
:transform-data="customTransform"
>
<template #ennm="{ column, record }">
<a-button type="link" size="small" @click="handleView(record)">{{
record.ennm
}}</a-button>
</template>
</BasicTable>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
import type { EChartsOption } from 'echarts';
import dayjs from 'dayjs';
import { useJidiSelectEventStore } from '@/store/modules/jidiSelectEvent';
import { dayGetKendoListCust, hourGetKendoListCust } from '@/api/stll';
import BasicTable from '@/components/BasicTable/index.vue';
import { useModelStore } from '@/store/modules/model';
2026-06-18 09:29:15 +08:00
import { DateSetting } from '@/utils/enumeration';
2026-06-12 16:59:35 +08:00
const modelStore = useModelStore();
const props = defineProps<{
options?: any[];
typeKey?: string;
time?: string;
basicId?: string;
dateArr?: any[];
2026-06-12 16:59:35 +08:00
}>();
const JidiSelectEventStore = useJidiSelectEventStore();
const columns = [
{
key: 'ennm',
title: '电站名称',
dataIndex: 'ennm',
slots: { customRender: 'action' }
},
{
key: 'baseName',
title: '所属基地',
dataIndex: 'baseName'
},
{
key: 'addvcdName',
title: '行政区',
dataIndex: 'addvcdName'
},
{
key: 'hbrvcdName',
title: '所在河段',
dataIndex: 'hbrvcdName',
width: '400px'
},
{
key: 'qecRate',
title: `生态流量达标率(%)`,
dataIndex: 'qecRate',
sort: true, // 启用排序
2026-06-12 16:59:35 +08:00
width: '160px',
customRender: ({ text }: any) => {
if (text === null || text === undefined || text === '' || text === '-')
return '-';
return Number(text).toFixed(2);
}
}
];
const tableRef = ref();
const sort1 = ref([
{
field: 'baseStepSort',
dir: 'asc'
},
{
field: 'rvcdStepSort',
dir: 'asc'
},
{
field: 'siteStepSort',
dir: 'asc'
},
{
field: 'avqRate',
dir: 'desc'
}
]);
const sort2 = ref([
{
field: 'baseStepSort',
dir: 'asc'
},
{
field: 'rvcdStepSort',
dir: 'asc'
},
{
field: 'siteStepSort',
dir: 'asc'
}
]);
// 动态计算搜索参数(包含排序)
const searchParams = computed(() => {
let baseSort;
baseSort = params.value.key ? sort1.value : sort2.value;
2026-06-12 16:59:35 +08:00
return {
sort: baseSort
};
});
// 初始化日期范围:优先使用父组件传入的 dateArr否则使用默认值上个月的本日到今天
2026-06-12 16:59:35 +08:00
const initDateRange = () => {
if (props.dateArr && props.dateArr.length === 2) {
return [dayjs(props.dateArr[0]), dayjs(props.dateArr[1])];
}
2026-06-12 16:59:35 +08:00
const today = dayjs();
const lastMonthSameDay = today.subtract(1, 'month');
return [lastMonthSameDay, today];
};
const dateRange = ref(initDateRange());
const params = ref({
qid: props.basicId || 'all',
key: props.typeKey || '',
timeType: props.time || 'day',
sTime: '',
eTime: ''
});
2026-06-15 14:10:19 +08:00
// 记录实际查询时使用的 timeType、key 和时间范围(用于 handleView 传递正确值)
const activeTimeType = ref(props.time || 'day');
const activeKey = ref(props.typeKey || '');
const activeSTime = ref('');
const activeETime = ref('');
2026-06-12 16:59:35 +08:00
// Tab 相关状态
const tabList = ref<any[]>([]);
const ecoFlowType = ref<string>('');
const loading = ref<boolean>(false);
const timeTypeOptions = [
{ label: '逐时', value: 'hour' },
{ label: '日', value: 'day' }
];
const jidiOptions = computed(() => {
return [
...JidiSelectEventStore.jidiData.map((item: any) => ({
value: item.wbsCode,
label: item.wbsName
}))
];
});
const ruleTypeOptions = computed(() => {
return (
props.options?.map((el: any) => ({
label: el.name,
value: el.key
})) || []
);
});
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
// ==================== 图表相关 ====================
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const pieData = ref<any[]>([]); // 饼图数据源
const dataLoading = ref<boolean>(false);
const pieCode = ref<string[]>([]); // 用于表格过滤的选中项
// 颜色配置
const themeecLists = [];
const jdColor = [
'#4b79ab',
'#78c300',
'#9556a4',
'#df91ab',
'#7399c6',
'#dbb629',
'#56c2e3',
'#f56a06',
'#cdba75',
'#76523b',
'#df75b6',
'#00a050',
'#f5a644',
'#4b79ab',
'#78c300',
'#9556a4',
'#df91ab',
'#7399c6',
'#dbb629',
'#56c2e3',
'#f56a06',
'#cdba75',
'#76523b',
'#df75b6',
'#00a050',
'#f5a644'
];
// 获取饼图数据
const fetchPieData = async () => {
if (!ecoFlowType.value || !params.value.sTime || !params.value.eTime) return;
dataLoading.value = true;
try {
const { sTime, eTime, qid, key, timeType } = params.value;
// 构建过滤条件
const filters = [
qid != 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: qid
}
: null,
{
field: 'dtin',
operator: 'eq',
dataType: 'string',
value: '1'
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: sTime
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: eTime
},
key
? {
field: key,
operator: 'eq',
dataType: 'string',
value: ecoFlowType.value
}
: null
].filter(Boolean);
// 当 key 为空时,添加额外的区间过滤条件
if (!key) {
filters.push(
{
field: 'qecInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'mwrInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'avqInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
}
);
}
let obj: any = {
filter: {
logic: 'and',
filters: filters
},
group: [
qid !== 'all' ? { dir: 'asc', field: 'rvcdStepSort' } : null,
qid === 'all' ? { dir: 'asc', field: 'baseStepSort' } : null,
{
dir: 'asc',
field: qid === 'all' ? 'baseId' : 'hbrvcd'
},
qid !== 'all'
? { dir: 'asc', field: 'hbrvcdName' }
: { dir: 'asc', field: 'baseName' }
].filter(Boolean),
groupResultFlat: true
};
// TODO: 调用实际 API
// const res = timeType == 'day'
// ? await shengTaiLiuLiang.STLLTabsDay(obj)
// : await shengTaiLiuLiang.STLLTabsHour(obj);
let res =
timeType == 'day'
? await dayGetKendoListCust(obj)
: await hourGetKendoListCust(obj);
if (res?.data?.data) {
const dataSource = res.data.data.map((el: any) => ({
id: qid === 'all' ? el.baseId : el.hbrvcd,
name: qid === 'all' ? el.baseName || el.key : el.hbrvcdName,
value: qid === 'all' ? el.count_baseId : el.count_hbrvcd,
key: qid === 'all' ? el.baseId : el.hbrvcd
}));
pieData.value = dataSource;
pieCode.value = []; // 重置选中状态
// 等待 DOM 更新后初始化或更新图表
await nextTick();
initOrUpdateChart();
} else {
pieData.value = [];
}
} catch (error) {
console.error('获取饼图数据失败:', error);
pieData.value = [];
} finally {
dataLoading.value = false;
}
};
// 初始化或更新图表
const initOrUpdateChart = () => {
if (!chartRef.value) return;
// 如果实例不存在,初始化
if (!chartInstance) {
chartInstance = echarts.init(chartRef.value);
// 绑定图例选择事件
chartInstance.on('legendselectchanged', handleLegendSelectChanged);
}
updateChart();
};
// 更新图表配置
const updateChart = () => {
if (!chartInstance) return;
const total =
pieData.value.reduce((pre: number, cur: any) => pre + cur.value, 0) || 0;
// 生成颜色数组
const colors = pieData.value.map((item: any, index: number) => {
const matched = themeecLists.find((i: any) => i.name === item.name);
return matched?.color || jdColor[index % jdColor.length];
});
const option: EChartsOption = {
color: colors,
tooltip: {
trigger: 'item',
formatter: '{b}: {c} (座) ({d}%)'
},
legend: {
orient: 'vertical',
right: 20,
top: 80,
type: 'scroll',
textStyle: {
width: 100, // 固定文本宽度
overflow: 'truncate', // 超出部分截断
ellipsis: '...' // 截断时显示的字符
}
},
title: [
{
text: total.toString(),
left: '34%',
top: '44%',
textAlign: 'center',
textStyle: {
fontSize: 24,
fontWeight: 'bold',
color: '#333'
}
},
{
text: '电站总数量',
left: '34%',
top: '51%',
textAlign: 'center',
textStyle: {
fontSize: 14,
color: '#666'
}
},
{
text: '(座)',
left: '34%',
top: '55%',
textAlign: 'center',
textStyle: {
fontSize: 12,
color: '#999'
}
}
],
series: [
{
name: '电站总数量',
type: 'pie',
radius: ['31%', '40%'],
center: ['35%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: function (params) {
const maxLen = 8; // 你希望显示的最大字符数
let name = params.name;
if (name.length > maxLen) {
return name.substring(0, maxLen) + '...';
}
return name;
}
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
}
},
data: pieData.value
}
]
};
chartInstance.setOption(option, true);
};
// 处理图例选择变化
const handleLegendSelectChanged = (params: any) => {
const selected = params.selected;
const trueRivers = Object.keys(selected).filter(key => selected[key]);
const allFalse = Object.values(selected).every(value => value === false);
const selectedKeys = pieData.value
.filter((item: any) => trueRivers.includes(item.name))
.map((item: any) => item.key);
pieCode.value = allFalse ? ['0'] : selectedKeys;
// 刷新表格
fetchTableData();
};
// 自定义数据转换
const customTransform = (res: any) => {
return {
records: res?.data?.data || [],
total: res?.data?.total || 0
};
};
// 获取 Tab 列表数据
const fetchTabList = async () => {
if (!params.value.sTime || !params.value.eTime) return;
loading.value = true;
try {
const { sTime, eTime, qid, key, timeType } = params.value;
// 构建过滤条件
const filters = [
qid != 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: qid
}
: null,
{
field: 'dtin',
operator: 'eq',
dataType: 'string',
value: '1'
},
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: sTime
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: eTime
}
].filter(Boolean);
// 当 key 为空时,添加额外的区间过滤条件
if (!key) {
filters.push(
{
field: 'qecInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'mwrInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'avqInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
}
);
}
let obj: any = {
filter: {
logic: 'and',
filters: filters
},
group: key
? [
{
dir: 'asc',
field: key
}
]
: [
{
dir: 'asc',
field: 'baseStepSort'
},
{
dir: 'asc',
field: 'baseId'
},
{
dir: 'asc',
field: 'baseName'
}
]
};
// 当 key 为空时,添加 groupResultFlat 属性
if (!key) {
obj.groupResultFlat = true;
}
// TODO: 根据实际 API 调用替换此处逻辑
let res =
timeType == 'day'
? await dayGetKendoListCust(obj)
: await hourGetKendoListCust(obj);
// const res = timeType == 'day'
// ? await shengTaiLiuLiang.STLLTabsDay(par, group) dayGetKendoListCust,hourGetKendoListCust
// : await shengTaiLiuLiang.STLLTabsHour(par, group);
// 模拟数据结构(待替换为真实 API
// const res = {
// data: [
// {
// type: 1,
// qecCnt: 14,
// qecTCnt: 50,
// code: 'QEC_STLL_1_DY95',
// desc: '环评要求_生态流量达标率所在统计区间:>=95%',
// typeName: '环评要求',
// key: 'QEC_STLL_1_DY95',
// keyName: '环评要求',
// count: 14
// },
// {
// type: 2,
// qecCnt: 13,
// qecTCnt: 71,
// code: 'QEC_STLL_2_DY95',
// desc: '水利部要求_生态流量达标率所在统计区间:>=95%',
// typeName: '水利部要求',
// key: 'QEC_STLL_2_DY95',
// keyName: '水利部要求',
// count: 13
// }
// ]
// };
if (res?.data?.data?.length > 0) {
const resData = res.data.data;
if (key) {
// 有规则类型时:过滤掉"无生态流量要求"的数据
tabList.value = resData
.map((el: any) => ({
...el,
value: el.key,
label: `${el.keyName || el.typeName}(${el.count || el.qecCnt}座)`
}))
.filter((el: any) => el.key !== 'QEC_STLL_6_NOEQMN');
// 查找当前选中的 ecoFlowType 是否在返回数据中
const found = resData.find(
(el: any) => el.items?.[0] === ecoFlowType.value
);
// 如果找到则保持,否则使用第一个
ecoFlowType.value = found ? ecoFlowType.value : resData[0]?.key;
} else {
// 没有规则类型时:只显示"无生态流量要求"
tabList.value = [
{
key: 'QEC_STLL_6_NOEQMN',
value: 'QEC_STLL_6_NOEQMN',
label: `无生态流量要求(${resData?.length}座)`
}
];
ecoFlowType.value = 'QEC_STLL_6_NOEQMN';
}
// 获取饼图数据
fetchPieData();
// 获取表格数据
fetchTableData();
} else {
tabList.value = [];
}
} catch (error) {
console.error('获取 Tab 列表失败:', error);
tabList.value = [];
} finally {
loading.value = false;
}
};
const handleView = (record: any) => {
modelStore.modalVisible = true;
modelStore.params.sttp = 'ENG';
modelStore.title = record.ennm;
modelStore.params.stcd = record.stcd;
modelStore.params.eqtp = record.eqtp;
2026-06-15 14:10:19 +08:00
modelStore.filter.stllgzlx = activeKey.value;
modelStore.filter.stllTmType = activeTimeType.value;
modelStore.filter.rangeTm = [activeSTime.value, activeETime.value];
2026-06-12 16:59:35 +08:00
};
//获取table数据
const fetchTableData = async () => {
// 刷新表格
const { sTime, eTime, qid, key, timeType } = params.value;
const filters: any[] = [
qid != 'all'
? {
field: 'baseId',
operator: 'eq',
dataType: 'string',
value: qid
}
: null,
{
field: 'tm',
operator: 'gte',
dataType: 'date',
value: sTime
},
{
field: 'tm',
operator: 'lte',
dataType: 'date',
value: eTime
},
{
field: 'dtin',
operator: 'eq',
dataType: 'string',
value: '1'
},
key
? {
field: key,
operator: 'eq',
dataType: 'string',
value: ecoFlowType.value
}
: null
].filter(Boolean);
if (!key) {
filters.push(
{
field: 'qecInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'mwrInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
},
{
field: 'avqInterval',
operator: 'eq',
dataType: 'string',
value: 'QEC_STLL_6_NOEQMN'
}
);
}
// 添加饼图选中项过滤
if (pieCode.value.length > 0) {
if (qid === 'all') {
filters.push({
field: 'baseId',
operator: 'in',
dataType: 'string',
value: pieCode.value
});
} else {
filters.push({
field: 'hbrvcd',
operator: 'in',
dataType: 'string',
value: pieCode.value
});
}
}
const filter = {
logic: 'and',
filters
};
tableRef.value?.getList(filter);
};
// 监听 ecoFlowType 变化,重新获取饼图数据
watch(ecoFlowType, () => {
fetchPieData();
});
const handleSearch = () => {
// 更新日期范围到 params
if (dateRange.value && dateRange.value.length === 2) {
params.value.sTime = dateRange.value[0].format('YYYY-MM-DD 00:00:00');
params.value.eTime = dateRange.value[1].format('YYYY-MM-DD 23:59:59');
}
2026-06-15 14:10:19 +08:00
// 记录当前查询使用的 timeType、key 和时间范围
activeTimeType.value = params.value.timeType;
activeKey.value = params.value.key;
activeSTime.value = params.value.sTime;
activeETime.value = params.value.eTime;
2026-06-12 16:59:35 +08:00
// 重新获取 Tab 列表
fetchTabList();
};
onMounted(() => {
// 初始化时设置默认日期并获取数据
if (dateRange.value && dateRange.value.length === 2) {
params.value.sTime = dateRange.value[0].format('YYYY-MM-DD 00:00:00');
params.value.eTime = dateRange.value[1].format('YYYY-MM-DD 23:59:59');
2026-06-15 14:10:19 +08:00
activeTimeType.value = params.value.timeType;
activeKey.value = params.value.key;
activeSTime.value = params.value.sTime;
activeETime.value = params.value.eTime;
2026-06-12 16:59:35 +08:00
fetchTabList();
}
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
// 清理图表实例
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener('resize', handleResize);
});
// 处理窗口大小变化
const handleResize = () => {
chartInstance?.resize();
};
</script>
<style scoped lang="scss">
.stlldbqk-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #ffffff;
box-sizing: border-box;
// padding: 16px;
}
.body_zhengti {
width: 100%;
display: flex;
justify-content: space-between;
flex: 1;
.echarts {
width: 35%;
height: 482px;
position: relative;
.pie-chart {
width: 100%;
height: 100%;
}
:deep(.ant-spin-nested-loading) {
width: 100%;
height: 100%;
}
:deep(.ant-spin-container) {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
2026-06-16 17:39:51 +08:00
.table1 {
2026-06-12 16:59:35 +08:00
width: 65%;
height: 482px;
}
}
.empty-state {
width: 100%;
height: 582px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
2026-06-12 16:59:35 +08:00
</style>