WholeProcessPlatform/frontend/src/modules/shengtaidabiaoMod/TwoLayer/ShengTaiLiuLiangDaBQKTwoLayer.vue
2026-07-14 16:08:10 +08:00

940 lines
22 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>
<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"
style="width: 260px"
:presets="DateSetting.RangeButton.days1"
/>
</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>
<div v-show="tabList.length > 0" class="body_zhengti">
<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>
<div class="table1">
<BasicTable
ref="tableRef"
:columns="columns"
:scrollY="400"
:list-url="
activeTimeType == 'day' ? dayGetKendoListCust : hourGetKendoListCust
"
: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';
import { DateSetting } from '@/utils/enumeration';
const modelStore = useModelStore();
const props = defineProps<{
options?: any[];
typeKey?: string;
time?: string;
basicId?: string;
dateArr?: any[];
}>();
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, // 启用排序
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;
return {
sort: baseSort
};
});
// 初始化日期范围:优先使用父组件传入的 dateArr否则使用默认值上个月的本日到今天
const initDateRange = () => {
if (props.dateArr && props.dateArr.length === 2) {
return [dayjs(props.dateArr[0]), dayjs(props.dateArr[1])];
}
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: ''
});
// 记录实际查询时使用的 timeType、key 和时间范围(用于 handleView 传递正确值)
const activeTimeType = ref(props.time || 'day');
const activeKey = ref(props.typeKey || '');
const activeSTime = ref('');
const activeETime = ref('');
// 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: 5,
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;
modelStore.filter.stllgzlx = activeKey.value;
modelStore.filter.stllTmType = activeTimeType.value;
modelStore.filter.rangeTm = [activeSTime.value, activeETime.value];
};
//获取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');
}
// 记录当前查询使用的 timeType、key 和时间范围
activeTimeType.value = params.value.timeType;
activeKey.value = params.value.key;
activeSTime.value = params.value.sTime;
activeETime.value = params.value.eTime;
// 重新获取 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');
activeTimeType.value = params.value.timeType;
activeKey.value = params.value.key;
activeSTime.value = params.value.sTime;
activeETime.value = params.value.eTime;
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;
}
}
.table1 {
width: 65%;
height: 482px;
}
}
.empty-state {
width: 100%;
height: 582px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>