WholeProcessPlatform/frontend/src/modules/shengTaiLiuLiangXieFangSheShiMod/xieFangFangShi/TwoLayer/STLLXFFS.vue
2026-07-07 13:50:21 +08:00

864 lines
19 KiB
Vue

<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; margin-right: 10px"
show-search
:filter-option="filterOption"
:options="jidiOptions"
@change="handleJidiChange"
/>
<a-select
v-model:value="params.rstcd"
placeholder="请选择电站"
style="width: 200px"
show-search
:filter-option="filterOption"
:options="rstcdOptions"
allowClear
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch">查询</a-button>
</a-space>
</a-form-item>
</a-form>
<a-spin :spinning="bodyLoading">
<template v-if="tabList.length > 0">
<!-- Tabs 标签页 -->
<a-tabs
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 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="table_1">
<BasicTable
ref="tableRef"
:columns="columns"
:scrollY="380"
:list-url="vmsstbprptGetKendoList"
:search-params="searchParams"
:transform-data="customTransform"
>
<template #stnm="{ record }">
<a-button
type="link"
size="small"
@click="handleView(record, 'EQ')"
>{{ record.stnm }}</a-button
>
</template>
<template #ennm="{ record }">
<a-button
type="link"
size="small"
@click="handleView(record, 'ENG')"
>{{ record.ennm }}</a-button
>
</template>
</BasicTable>
</div>
</div>
</template>
<a-empty
v-else-if="!bodyLoading"
description="暂无数据"
/>
</a-spin>
</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,
vmsstbprptGetKendoList,
msstbprptGetKendoList
} from '@/api/stll';
import BasicTable from '@/components/BasicTable/index.vue';
import { useModelStore } from '@/store/modules/model';
import { NullableDateType } from 'ant-design-vue/es/vc-picker/interface';
const modelStore = useModelStore();
const props = defineProps<{
basicId?: string;
sttpCodekey: string;
}>();
const JidiSelectEventStore = useJidiSelectEventStore();
// ==================== 列配置 ====================
// 三套列配置,根据 Tab 类型动态切换
const columnsConfig = {
EQ_6: [
{
key: 'stnm',
title: '设施名称',
dataIndex: 'stnm',
slots: { customRender: 'stnm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'ennm',
title: '所属电站',
dataIndex: 'ennm',
slots: { customRender: 'ennm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'ttpwr',
title: '装机容量',
dataIndex: 'ttpwr',
width: '160px',
sort: true
},
{
key: 'stjzysq',
title: '生态机组额定流量(m³/s)',
dataIndex: 'stjzysq',
width: '200px',
sort: true
}
],
EQ_5: [
{
key: 'stnm',
title: '设施名称',
dataIndex: 'stnm',
slots: { customRender: 'stnm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'ennm',
title: '所属电站',
dataIndex: 'ennm',
slots: { customRender: 'ennm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'jhfdgk',
title: '基荷发电工况',
dataIndex: 'jhfdgk',
width: '100px',
sort: true
},
{
key: 'fdll',
title: '发电量(MKW)',
dataIndex: 'fdll',
width: '120px',
sort: true
},
{
key: 'blprdName',
title: '所属建设阶段',
dataIndex: 'blprdName',
width: '160px',
sort: true
}
],
default: [
{
key: 'stnm',
title: '设施名称',
dataIndex: 'stnm',
slots: { customRender: 'stnm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'ennm',
title: '所属电站',
dataIndex: 'ennm',
slots: { customRender: 'ennm' },
fixed: 'left',
width: '120px',
sort: true
},
{
key: 'ddzgq',
title: '死水位时保证流量(m³/s)',
dataIndex: 'ddzgq',
width: '190px',
sort: true
},
{
key: 'flkq',
title: '最大泄放流量(m³/s)',
dataIndex: 'flkq',
sort: true,
width: '160px'
},
{
key: 'jskgc',
title: '进水口底版高程(m)',
dataIndex: 'jskgc',
sort: true,
width: '160px'
},
{
key: 'outflrelev',
title: '出水口底版高程(m)',
dataIndex: 'outflrelev',
sort: true,
width: '160px'
},
{
key: 'flksz',
title: '孔口尺寸(m)',
dataIndex: 'flksz',
sort: true,
width: '120px'
},
{
key: 'frntwlltyp',
title: '结构形式',
dataIndex: 'frntwlltyp',
sort: true,
width: '160px'
},
{
key: 'blprdName',
title: '所属建设阶段',
dataIndex: 'blprdName',
sort: true,
width: '160px'
}
]
};
// 动态列(根据 Tab 类型切换)
const columns = computed(() => {
const type = ecoFlowType.value;
if (type === 'EQ_6') return columnsConfig.EQ_6;
if (type === 'EQ_5') return columnsConfig.EQ_5;
return columnsConfig.default;
});
const tableRef = ref();
const sort2 = ref([
{
field: 'baseStepSort',
dir: 'asc'
},
{
field: 'rvcdStepSort',
dir: 'asc'
},
{
field: 'rstcdStepSort',
dir: 'asc'
},
{
field: 'siteStepSort',
dir: 'asc'
}
]);
// 动态计算搜索参数(包含排序)
const searchParams = computed(() => {
let baseSort;
// 使用默认排序
baseSort = sort2.value;
return {
sort: baseSort,
group: [],
select: [],
groupResultFlat: false
};
});
const params = ref({
qid: props.basicId || 'all',
rstcd: ''
});
// 记录实际查询时使用的时间范围(用于 handleView 传递正确值)
const activeSTime = ref('');
const activeETime = ref('');
// Tab 相关状态
const tabList = ref<any[]>([]);
const ecoFlowType = ref<string>(props.sttpCodekey || '');
const loading = ref<boolean>(false);
const bodyLoading = ref<boolean>(false);
const jidiOptions = computed(() => {
return [
...JidiSelectEventStore.jidiData.map((item: any) => ({
value: item.wbsCode,
label: item.wbsName
}))
];
});
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 () => {
dataLoading.value = true;
try {
const { qid, rstcd } = params.value;
let obj = {
filter: {
logic: 'and',
filters: [
qid != 'all'
? {
field: 'baseId',
operator: 'contains',
dataType: 'string',
value: qid
}
: null,
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: ecoFlowType.value
},
{
field: 'baseName',
operator: 'isnotnull',
dataType: 'string'
},
rstcd
? {
field: 'rstcd',
operator: 'eq',
dataType: 'string',
value: rstcd
}
: null
].filter(Boolean)
},
group: [
{
dir: 'asc',
field: 'baseStepSort'
},
{
dir: 'asc',
field: 'baseId'
},
{
dir: 'desc',
field: 'baseName'
},
{
dir: 'desc',
field: 'sttpCode'
}
],
groupResultFlat: 'true'
};
let res = await vmsstbprptGetKendoList(obj);
if (res?.data?.data) {
const dataSource = res.data.data.map((el: any) => ({
id: el.baseId,
name: el.baseName,
value: el.count_baseId,
key: el.baseId
}));
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 () => {
loading.value = true;
bodyLoading.value = true;
try {
const { qid, rstcd } = params.value;
let obj = {
filter: {
logic: 'and',
filters: [
qid != 'all'
? {
field: 'baseId',
operator: 'contains',
dataType: 'string',
value: qid
}
: null,
{
field: 'sttp',
operator: 'contains',
dataType: 'string',
value: 'EQ_'
},
{
field: 'baseName',
operator: 'isnotnull',
dataType: 'string'
},
rstcd
? {
field: 'rstcd',
operator: 'eq',
dataType: 'string',
value: rstcd
}
: null
].filter(Boolean)
},
group: [
{
dir: 'desc',
field: 'sttpCode'
}
]
};
let res = await msstbprptGetKendoList(obj);
if (res?.data?.data?.length > 0) {
const resData = res.data.data;
tabList.value = resData.map(item => ({
key: item.key,
value: item.key,
label: `${item.keyName}(${item.count}个)`
}));
// 只显示"无生态流量要求"
// ecoFlowType.value = tabList.value[0].key;
// 等待 DOM 渲染完成(图表和表格组件需要挂载后才能操作)
await nextTick();
// 获取饼图数据
fetchPieData();
// 获取表格数据
fetchTableData();
} else {
tabList.value = [];
}
} catch (error) {
console.error('获取 Tab 列表失败:', error);
tabList.value = [];
} finally {
loading.value = false;
bodyLoading.value = false;
}
};
const handleView = (record: any, type: 'ENG' | 'EQ') => {
modelStore.modalVisible = true;
modelStore.params.sttp = type == 'EQ' ? record.sttpCode : 'ENG';
modelStore.title = type === 'ENG' ? record.ennm : record.stnm;
modelStore.params.stcd = type === 'ENG' ? record.rstcd : record.stcd;
modelStore.params.eqtp = record.eqtp;
modelStore.filter.rangeTm = [activeSTime.value, activeETime.value];
};
//获取table数据
const fetchTableData = async () => {
// 刷新表格
const { qid, rstcd } = params.value;
const filters: any[] = [
{
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: ecoFlowType.value
},
{
field: 'baseName',
operator: 'isnotnull',
dataType: 'string'
},
qid != 'all'
? {
field: 'baseId',
operator: 'contains',
dataType: 'string',
value: qid
}
: null,
rstcd
? {
field: 'rstcd',
operator: 'eq',
dataType: 'string',
value: rstcd
}
: null
].filter(Boolean);
// 添加饼图选中项过滤
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();
});
// 监听 tabList 变化,当变为空时清理图表实例
watch(
() => tabList.value.length,
(newLen) => {
if (newLen === 0 && chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
}
);
const handleSearch = () => {
// 重新获取 Tab 列表
fetchTabList();
};
//获取电站列表
let rstcdOptions: any = ref([]);
const rstcdOption = async () => {
let obj = {
filter: {
logic: 'and',
filters: [
params.value.qid != 'all'
? {
field: 'sttpCode',
operator: 'eq',
dataType: 'string',
value: 'ENG'
}
: null
].filter(Boolean)
},
sort: [
{
field: 'ttpwr',
dir: 'desc'
}
],
select: ['stcd', 'stnm']
};
let res = await vmsstbprptGetKendoList(obj);
rstcdOptions.value = res.data.data.map((item: any) => {
return {
value: item.stcd,
label: item.stnm
};
});
};
const handleJidiChange = (value: string) => {
rstcdOption();
};
onMounted(() => {
fetchTabList();
rstcdOption();
// 监听窗口大小变化
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;
}
}
.table_1 {
width: 65%;
height: 482px;
// overflow: auto;
// max-width: 500px;
}
}
</style>