WholeProcessPlatform/frontend/src/modules/shuidianhuangjingjieruMod/TwoLayer/ShuiDianKaiFQKTwoLayer.vue

1209 lines
36 KiB
Vue
Raw Normal View History

2026-05-15 08:51:17 +08:00
<script setup lang="ts">
// ==================== 导入依赖 ====================
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import type { EChartsOption } from 'echarts'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import { DownloadOutlined } from '@ant-design/icons-vue'
import BasicSearch from '@/components/BasicSearch/index.vue'
import BasicTable from '@/components/BasicTable/index.vue'
import { message } from 'ant-design-vue'
// TODO: 需要确认项目中是否有这些组件
// import { ModalTabs } from '@/components/MapModal/modalTabs'
// import { handleTabs, modalTabSetting } from '@/components/MapModal/setting.config'
// ==================== Props 定义 ====================
const props = defineProps<{
defaultJidiInfo?: Record<string, string | number | boolean> // 默认基地信息
seriesName?: string // 系列名称(如:"装机容量(万kW)" 或 "数量(座)"
stateName?: string // 状态名称
themeecList?: any[] // 主题颜色列表(用于饼图颜色自定义)
defaultTab?: string // 默认显示的Tab值'已建'/'在建'
}>()
// ==================== 类型定义 ====================
interface TabItem {
key: string
value: string
col: string
unit: string
count: number
}
interface PieDataItem {
key: string
name: string
value: number
unit: string
dataDimensionRecursive?: string
index?: number
}
interface TableFilterParams {
baseId?: string | Array<{ field: string; operator: string; dataType: string; value: string[] }>
hbrvcd?: string | Array<{ field: string; operator: string; dataType: string; value: string[] }>
bldsttCcode?: string
stnmContains?: string | null
prsc?: Array<{ field: string; operator: string; dataType: string; value: string[] }>
ttpwr?: Array<{ field: string; operator: string }>
sttpCode?: string
}
interface ModalData {
sttp: string
stcd: string
titleName: string
}
// ==================== 响应式状态 ====================
// 基地相关
const qid = ref<string>('')
const jiDiList = ref<any[]>([])
const defJD = ref<string>(props.defaultJidiInfo?.basename as string || '')
// Tab栏相关
const tabIndex = ref<string>('')
const tabKey = ref<string>('')
const tabCol = ref<string>('')
const tabList = ref<TabItem[]>([])
// 搜索相关
const stnm = ref<string | null>(null)
const formRef = ref<any>()
// 饼图相关
const pieData = ref<PieDataItem[]>([])
const pieCode = ref<string[]>([])
const pikData = ref<PieDataItem[]>([])
const chartRef = ref<HTMLElement | null>(null)
const chartInstance = ref<any>(null)
const dataLoading = ref<boolean>(false)
// 弹窗相关
const mapModal = ref<boolean>(false)
const modalData = ref<ModalData>({ sttp: '', stcd: '', titleName: '' })
// 单位配置
const unit = computed(() => getUnitConfigByCode('Other', 'ZJRL').unit)
// 颜色配置优先使用themeecList否则使用默认颜色
const jdColor = ['#5B8FF9', '#5AD8A6', '#F6BD16', '#7262FD', '#FF6B6B', '#9556a4', '#30b7b9']
// 表格引用(用于导出功能)
const tableRef = ref<any>(null)
// ==================== API 服务层(待完善) ====================
/**
* 获取Tab统计数据
*/
async function fetchTabData(params: any): Promise<TabItem[]> {
// TODO: 替换为实际API调用
// const res = await shuiDianKaifaZhuangKung.GetKendoList(params, sortConfig)
console.log('【模拟】获取Tab数据参数:', params)
return new Promise((resolve) => {
setTimeout(() => {
// 根据 seriesName 决定是否包含 ttpwr 过滤
const _name = `装机容量(${unit.value})`
const useTtpwrFilter = props.seriesName === _name
// 模拟返回数据结构与后端GetKendoList返回格式一致
const mockResponse = [
{
key: '2',
items: [{
SUM_TTPWR: 1069300000, // kW单位
COUNT_ENGID: 150
}]
},
{
key: '1',
items: [{
SUM_TTPWR: 331000000,
COUNT_ENGID: 80
}]
},
{
key: '0',
items: [{
SUM_TTPWR: 179000000,
COUNT_ENGID: 30
}]
}
]
// 模拟数据处理逻辑与React版本一致
let list: TabItem[] = []
const result = mockResponse.reduce((acc: any, cur: any) => {
if (cur.key === '0') {
list.push({
col: useTtpwrFilter ? 'ttpwr' : 'engTotal',
key: 'noBuild',
unit: useTtpwrFilter ? unit.value : '座',
value: '未建',
count: (cur?.items[0]?.SUM_TTPWR && transUnit(cur?.items[0]?.SUM_TTPWR, 'Other', 'ZJRL')) || cur.items[0].COUNT_ENGID
})
} else if (cur.key === '1') {
list.push({
col: useTtpwrFilter ? 'ttpwr' : 'engTotal',
key: 'building',
unit: useTtpwrFilter ? unit.value : '座',
value: '在建',
count: (cur?.items[0]?.SUM_TTPWR && transUnit(cur?.items[0]?.SUM_TTPWR, 'Other', 'ZJRL')) || cur.items[0].COUNT_ENGID
})
} else if (cur.key === '2') {
list.push({
col: useTtpwrFilter ? 'ttpwr' : 'engTotal',
key: 'build',
unit: useTtpwrFilter ? unit.value : '座',
value: '已建',
count: (cur?.items[0]?.SUM_TTPWR && transUnit(cur?.items[0]?.SUM_TTPWR, 'Other', 'ZJRL')) || cur.items[0].COUNT_ENGID
})
}
return list
}, [])
resolve(result)
}, 500)
})
}
/**
* 获取饼图分组数据完整实现React版本的复杂逻辑
*/
async function fetchPieData(params: any): Promise<PieDataItem[]> {
// TODO: 替换为实际API调用
console.log('【模拟】获取饼图数据,参数:', params)
return new Promise((resolve) => {
setTimeout(() => {
const _name = `装机容量(${unit.value})`
const useTtpwrFilter = props.seriesName === _name
// 模拟后端返回的嵌套数据结构与GetKendoList返回格式一致
let mockResponse: any[]
if (qid.value === 'all') {
// 全基地模式按baseId分组
mockResponse = [
{
key: 'cj',
items: [{
key: 'cj_base',
items: [{
BASENAME: '长江流域',
SUM_TTPWR: 456800000, // kW
COUNT_BASEID: 120
}]
}]
},
{
key: 'hh',
items: [{
key: 'hh_base',
items: [{
BASENAME: '黄河流域',
SUM_TTPWR: 234500000,
COUNT_BASEID: 85
}]
}]
},
{
key: 'zj',
items: [{
key: 'zj_base',
items: [{
BASENAME: '珠江流域',
SUM_TTPWR: 123000000,
COUNT_BASEID: 60
}]
}]
}
]
} else if (qid.value === 'other') {
// other模式特殊处理
mockResponse = [
{
key: 'river1',
items: [{
HBRVCDNAME: '长江干流',
SUM_TTPWR: 89000000,
COUNT_BLDSTTCCODE: 25
}]
},
{
key: 'river2',
items: [{
HBRVCDNAME: '黄河干流',
SUM_TTPWR: 56000000,
COUNT_BLDSTTCCODE: 18
}]
},
{
key: 'river3',
items: [{
HBRVCDNAME: '珠江干流',
SUM_TTPWR: 34000000,
COUNT_BLDSTTCCODE: 12
}]
}
]
} else {
// 单个基地模式按hbrvcd分组
mockResponse = [
{
items: [{
key: 'cjgl',
items: [{
HBRVCDNAME: '长江干流',
SUM_TTPWR: 456800000,
COUNT_BLDSTTCCODE: 50
}]
}]
},
{
items: [{
key: 'hhgl',
items: [{
HBRVCDNAME: '黄河干流',
SUM_TTPWR: 234500000,
COUNT_BLDSTTCCODE: 35
}]
}]
}
]
}
// 模拟数据处理逻辑完全复刻React版本的flatMap逻辑
let list = mockResponse.flatMap((item: any) => {
let arr: PieDataItem[] = []
// 关键:根据 qid 决定数据提取路径
const itmeData = (qid.value === 'all' || qid.value === 'other')
? item?.items
: item?.items[0]?.items
itmeData?.map((outItem: any) => {
const itemList = outItem?.items?.[0]?.items?.[0]
// 关键:根据 qid 决定 name 的来源
const name = qid.value === 'all'
? outItem?.items?.[0]?.items?.[0]?.items?.[0]?.BASENAME
: (outItem?.items?.[0]?.items?.[0]?.BASENAME || outItem?.items?.[0]?.items?.[0]?.HBRVCDNAME)
if (name) {
arr.push({
key: qid.value === 'all'
? item?.items[0].key
: (qid.value === 'other' ? item?.key : item?.items?.[0]?.key),
dataDimensionRecursive: 'true',
name: name,
unit: '',
value: qid.value === 'all' && useTtpwrFilter
? (itemList?.SUM_TTPWR && +transUnit(itemList?.SUM_TTPWR, 'Other', 'ZJRL'))
: (qid.value === 'all' && !useTtpwrFilter
? itemList?.COUNT_BASEID
: ((outItem?.items?.[0]?.items?.[0]?.SUM_TTPWR && +transUnit(outItem?.items?.[0]?.items?.[0]?.SUM_TTPWR, 'Other', 'ZJRL'))
|| outItem?.items?.[0]?.items?.[0]?.COUNT_BLDSTTCCODE))
})
}
})
return arr
})
// 特殊处理other 类型需要调用 getIntroduce 接口排序
if (qid.value === 'other' && list.length > 0) {
// 模拟 getIntroduce 接口返回
const introRes = [
{ wbsCode: 'river1', orderIndex: 1 },
{ wbsCode: 'river2', orderIndex: 2 },
{ wbsCode: 'river3', orderIndex: 3 }
]
if (introRes?.length && list?.length) {
list = list.map((el: any) => {
const found = introRes.find((item: any) => el?.key === item?.wbsCode)
return {
...el,
index: found?.orderIndex
}
}).sort((a: any, b: any) => a?.index - b?.index)
}
}
resolve(list)
}, 500)
})
}
/**
* 获取other类型的排序信息
*/
async function fetchIntroduce(params: any): Promise<any[]> {
// TODO: 替换为实际API调用
console.log('【模拟】获取排序信息,参数:', params)
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ wbsCode: 'river1', orderIndex: 1 },
{ wbsCode: 'river2', orderIndex: 2 },
{ wbsCode: 'river3', orderIndex: 3 }
])
}, 300)
})
}
/**
* 表格数据请求函数
*/
async function fetchTableData(params: any): Promise<{ records: any[], total: number }> {
// TODO: 替换为实际API调用
// const url = qid.value === 'all'
// ? '/dec-lygk-base-server/base/vmsstbprpt/GetKendoListCust'
// : '/dec-lygk-base-server/base/vmsstbprpt/GetKendoList'
console.log('【模拟】获取表格数据,参数:', params)
return new Promise((resolve) => {
setTimeout(() => {
// 根据建设状态和河段过滤生成对应的模拟数据
const buildStatus = params.bldsttCcode
const hbrvcdFilter = params.hbrvcd?.value || []
// 电站基础数据模板
const stationTemplates = [
{ name: '三峡', river: 'cjgl', baseName: '长江基地', province: '湖北省' },
{ name: '葛洲坝', river: 'cjgl', baseName: '长江基地', province: '湖北省' },
{ name: '向家坝', river: 'cjgl', baseName: '长江基地', province: '四川省' },
{ name: '溪洛渡', river: 'cjgl', baseName: '长江基地', province: '云南省' },
{ name: '白鹤滩', river: 'cjgl', baseName: '长江基地', province: '四川省' },
{ name: '乌东德', river: 'cjgl', baseName: '长江基地', province: '云南省' },
{ name: '小浪底', river: 'hhgl', baseName: '黄河基地', province: '河南省' },
{ name: '刘家峡', river: 'hhgl', baseName: '黄河基地', province: '甘肃省' },
{ name: '龙羊峡', river: 'hhgl', baseName: '黄河基地', province: '青海省' },
{ name: '拉西瓦', river: 'hhgl', baseName: '黄河基地', province: '青海省' },
{ name: '天生桥', river: 'zjgl', baseName: '珠江基地', province: '贵州省' },
{ name: '岩滩', river: 'zjgl', baseName: '珠江基地', province: '广西区' },
{ name: '大化', river: 'zjgl', baseName: '珠江基地', province: '广西区' },
{ name: '响洪甸', river: 'hhgl', baseName: '淮河基地', province: '安徽省' },
{ name: '梅山', river: 'hhgl', baseName: '淮河基地', province: '安徽省' },
{ name: '潘家口', river: 'hjgl', baseName: '海河基地', province: '河北省' },
{ name: '大黑汀', river: 'hjgl', baseName: '海河基地', province: '河北省' },
{ name: '丰满', river: 'sjgl', baseName: '松花江基地', province: '吉林省' },
{ name: '白山', river: 'sjgl', baseName: '松花江基地', province: '吉林省' },
{ name: '水丰', river: 'ljgl', baseName: '辽河基地', province: '辽宁省' }
]
// 根据建设状态筛选电站
let filteredStations = stationTemplates
// 如果有河段过滤,进一步筛选
if (hbrvcdFilter.length > 0) {
filteredStations = stationTemplates.filter(s => hbrvcdFilter.includes(s.river))
}
// 如果没有匹配的河段,返回空数组
if (filteredStations.length === 0) {
resolve({ records: [], total: 0 })
return
}
// 生成模拟记录
const mockRecords = filteredStations.map((station, index) => {
// 根据不同状态生成不同的装机容量范围
let ttpwrRange
if (buildStatus === '2') {
// 已建:较大容量
ttpwrRange = [5000, 30000]
} else if (buildStatus === '1') {
// 在建:中等容量
ttpwrRange = [2000, 15000]
} else {
// 未建:较小容量或规划中
ttpwrRange = [1000, 8000]
}
const ttpwr = Math.floor(Math.random() * (ttpwrRange[1] - ttpwrRange[0]) + ttpwrRange[0])
const yrge = (ttpwr * 0.04).toFixed(2) // 年发电量约为装机容量的4%
const ttcp = (Math.random() * 200 + 50).toFixed(2)
// 投产时间根据状态设置
let piodt, aiodt
if (buildStatus === '2') {
// 已建:过去的时间
const year = 2000 + Math.floor(Math.random() * 20)
piodt = `${year}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
aiodt = `${year + Math.floor(Math.random() * 5 + 1)}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
} else if (buildStatus === '1') {
// 在建未来1-3年
const year = 2026 + Math.floor(Math.random() * 3)
piodt = `${year}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
aiodt = `${year + Math.floor(Math.random() * 3 + 1)}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
} else {
// 未建:更远的未来
const year = 2028 + Math.floor(Math.random() * 7)
piodt = `${year}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
aiodt = `${year + Math.floor(Math.random() * 5 + 2)}-0${Math.floor(Math.random() * 9 + 1)}-${Math.floor(Math.random() * 28 + 1)}`
}
return {
id: `station_${buildStatus}_${index}`,
stcd: `ST${String(index + 1).padStart(4, '0')}`,
stnm: `${station.name}水电站`,
dvtp: ['堤坝式', '引水式', '混合式'][index % 3],
dvtpName: ['堤坝式', '引水式', '混合式'][index % 3],
ttpwr: ttpwr,
yrge: yrge,
ttcp: ttcp,
baseName: station.baseName,
addvcdName: station.province,
hbrvcdName: station.name === '三峡' ? '长江干流' :
station.name === '葛洲坝' ? '长江干流' :
station.name === '小浪底' ? '黄河干流' :
station.name === '刘家峡' ? '黄河干流' :
station.name === '天生桥' ? '珠江干流' :
station.name === '响洪甸' ? '淮河干流' :
station.name === '潘家口' ? '海河干流' :
station.name === '丰满' ? '松花江干流' :
station.name === '水丰' ? '辽河干流' : `${station.name.substring(0, 2)}河段`,
piodt: piodt,
aiodt: aiodt
}
})
resolve({
records: mockRecords,
total: mockRecords.length
})
}, 600)
})
}
// ==================== 工具函数 ====================
/**
* 单位转换函数待完善
*/
function transUnit(value: number, from: string, to: string): number {
// TODO: 根据实际业务规则实现
// 当前假设:接口返回的单位是 kW需要转换为万kW
if (from === 'Other' && to === 'ZJRL') {
return value / 10000 // kW → 万kW
}
return value
}
/**
* 获取单位配置
*/
function getUnitConfigByCode(code: string, type: string): { unit: string } {
// TODO: 从配置文件或API获取
// 根据项目实际情况,装机容量通常使用"万kW"作为单位
return { unit: '万kW' }
}
/**
* 处理导出功能
*/
function handleExport() {
if (tableRef.value && typeof tableRef.value.export === 'function') {
// 假设 BasicTable 暴露了 export 方法,请根据实际组件 API 调整
tableRef.value.export()
} else {
message.info('导出功能待实现:请确认 BasicTable 组件是否暴露了导出方法')
console.log('Table Ref:', tableRef.value)
}
}
/**
* 处理弹窗关闭
*/
function handleModalCancel() {
mapModal.value = false
modalData.value = { sttp: '', stcd: '', titleName: '' }
}
// ==================== ECharts 饼图配置 ====================
/**
* 计算饼图配置增强版支持themeecList自定义颜色
*/
const chartSetting = computed<EChartsOption>(() => {
const total = pieData.value.reduce((acc, item) => acc + item.value, 0).toFixed(2)
const flag = props.seriesName === '数量(座)'
const bbbb = flag ? '电站总数量' : '总装机容量'
const dddd = flag ? '电站数量' : '装机容量'
const cccc = flag ? ' (座)' : ` (${unit.value})`
// 关键:根据 themeecList 动态设置颜色与React版本一致
const colors = pieData.value.map((item: any, index: any) => {
if (props.themeecList && props.themeecList.length > 0) {
const themeColor = props.themeecList.find((i: any) => i.name === item.name)
return themeColor?.color || jdColor[index % jdColor.length]
}
return jdColor[index % jdColor.length]
})
return {
color: colors, // 添加颜色配置
tooltip: {
trigger: 'item',
formatter: (params: any) =>
`${dddd}<div>${params.marker}${params.name}: &nbsp;&nbsp;&nbsp;&nbsp;<span style="float:right">${params.value}${cccc}</span></div>`
},
title: [
{
text: String(total || 0), // 修复确保text为string类型
left: 'center',
top: '40%',
textStyle: { fontSize: 24, fontWeight: 'bold', color: '#333' }
},
{
text: bbbb,
left: 'center',
top: '50%',
textStyle: { fontSize: 14, color: '#666' }
},
{
text: cccc,
left: 'center',
top: '55%',
textStyle: { fontSize: 12, color: '#999' }
}
],
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 20,
bottom: 20,
textStyle: { fontSize: 12 }
},
series: [
{
name: dddd,
type: 'pie',
radius: ['40%', '70%'],
center: ['35%', '50%'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 4,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
position: 'outside',
formatter: '{b}\n{c}',
fontSize: 12
},
emphasis: {
scale: true,
scaleSize: 10
},
data: pieData.value.map((item, index) => ({
...item
// 颜色已在顶层color中统一设置
}))
}
]
}
})
/**
* 初始化图表
*/
function initChart() {
if (!chartRef.value) return
// 关键使用固定的图表尺寸541px × 412px
chartInstance.value = echarts.init(chartRef.value, null, {
width: 541,
height: 412
})
updateChart()
}
/**
* 更新图表
*/
function updateChart() {
if (!chartInstance.value) return
chartInstance.value.setOption(chartSetting.value, true)
}
/**
* 处理窗口大小变化
*/
function handleResize() {
chartInstance.value?.resize()
}
/**
* 饼图点击事件完全复刻React版本逻辑
*/
function handlePieClick(params: any) {
if (qid.value !== 'all') {
return // 仅在全基地模式下支持点击联动
}
// 关键根据点击的扇区名称查找对应的基地ID
const matchedOption = searchList.value[0]?.options?.find(
(value: any) => value.label === params.name
)
if (matchedOption) {
qid.value = matchedOption.qid || 'other'
// 关键:获取 dataDimensionRecursive 标志(用于控制表格刷新行为)
const matchedData = pieData.value.find((item: any) => item.name === params.name)
console.log('【饼图点击】切换到基地:', params.name, 'qid:', qid.value, 'dataDimensionRecursive:', matchedData?.dataDimensionRecursive)
}
}
/**
* 图例选择变化事件完全复刻React版本逻辑
*/
function handleLegendSelectChanged(e: any) {
const trueRivers = Object.keys(e.selected).filter(key => e.selected[key])
const allTrue = Object.values(e.selected).every(value => value === false)
const selectedKeys = pikData.value
.filter((item: any) => trueRivers.includes(item.name))
.map((item: any) => item.key)
// 关键与React版本保持一致全选时设置为 ["0"]
pieCode.value = allTrue ? ['0'] : selectedKeys
console.log('【图例联动】allTrue:', allTrue, 'selectedKeys:', selectedKeys, 'pieCode:', pieCode.value)
}
// ==================== 表格列配置 ====================
const columns: ColumnsType = [
{
title: '电站名称',
dataIndex: 'stnm',
key: 'stnm',
fixed: 'left',
ellipsis: true,
width: 150,
customRender: ({ record }: any) => ({
children: record.stnm,
attrs: {
onClick: () => handleRowClick(record)
},
style: {
color: '#1890ff',
cursor: 'pointer'
}
})
},
{
title: '开发方式',
dataIndex: 'dvtp',
key: 'dvtp',
ellipsis: true,
customRender: ({ record }: any) => record.dvtpName || '-'
},
{
title: `装机容量(${unit.value})`,
dataIndex: 'ttpwr',
key: 'ttpwr',
ellipsis: true,
customRender: ({ text }: any) => text ? transUnit(text, 'Other', 'ZJRL').toFixed(2) : '-'
},
{
title: '设计年发电量(亿kW·h)',
dataIndex: 'yrge',
key: 'yrge',
ellipsis: true,
},
{
title: '总库容(亿m³)',
dataIndex: 'ttcp',
key: 'ttcp',
ellipsis: true
},
{
title: '所属基地',
dataIndex: 'baseName',
key: 'baseName',
ellipsis: true
},
{
title: '所属行政区',
dataIndex: 'addvcdName',
key: 'addvcdName',
ellipsis: true
},
{
title: '所在河段',
dataIndex: 'hbrvcdName',
key: 'hbrvcdName',
ellipsis: true
},
{
title: '第一台机组投产时间',
dataIndex: 'piodt',
key: 'piodt',
ellipsis: true,
width: 170
},
{
title: '全部机组投产时间',
dataIndex: 'aiodt',
key: 'aiodt',
ellipsis: true
}
]
/**
* 处理表格行点击打开详情弹窗
*/
function handleRowClick(record: any) {
modalData.value = {
sttp: 'ENG',
stcd: record.stcd,
titleName: record.stnm
}
mapModal.value = true
}
// ==================== 搜索表单配置 ====================
const searchList = computed(() => [
{
type: 'Select',
name: 'Select',
label: '基地选择',
options: jiDiList.value?.map((item: any) => ({
qid: item.baseid,
value: item.basename,
label: item.basename
})) || []
},
{
type: 'Input',
name: 'stnm',
label: '',
fieldProps: {
allowClear: true,
placeholder: '请输入电站名称'
}
}
])
const initialValues = computed(() => ({
Select: defJD.value,
stnm: null
}))
/**
* 处理搜索提交完全复刻React版本逻辑
*/
function handleSearch(values: any) {
// 关键根据选中的基地名称查找对应的qid
const matchedOption = searchList.value[0]?.options?.find(
(value: any) => value.label === values.Select
)
qid.value = matchedOption?.qid || 'other'
stnm.value = values.stnm
console.log('【搜索】基地:', values.Select, 'qid:', qid.value, '电站名称:', stnm.value)
}
/**
* 处理重置
*/
function handleReset() {
qid.value = ''
stnm.value = null
defJD.value = props.defaultJidiInfo?.basename as string || ''
}
// ==================== 数据监听与副作用 ====================
/**
* 表格过滤条件计算完全复刻React版本逻辑
*/
const tableFilter = computed<TableFilterParams>(() => {
let res: TableFilterParams = {
// 关键:根据 pieCode 和 qid 动态决定 baseId 的格式
baseId: (pieCode.value?.length > 0 && qid.value === 'all')
? [{
field: 'baseId',
operator: 'in',
dataType: 'string',
value: pieCode.value
}]
: (qid.value || (props.defaultJidiInfo?.baseid as string)),
sttpCode: 'ENG',
// 关键:根据 pieCode 和 qid 动态决定 hbrvcd 的格式
hbrvcd: (pieCode.value?.length > 0 && qid.value !== 'all')
? [{
field: 'hbrvcd',
operator: 'in',
dataType: 'string',
value: pieCode.value
}]
: undefined,
bldsttCcode: tabKey.value === 'noBuild' ? '0' : tabKey.value === 'building' ? '1' : '2',
stnmContains: stnm.value,
prsc: [
{ field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }
]
}
// 根据 seriesName 决定是否添加 ttpwr 过滤与React版本保持一致但注释掉
// if (props.seriesName === `装机容量(${unit.value})`) {
// res = { ...res, ttpwr: [{ field: 'ttpwr', operator: 'isnotnull' }] }
// }
// 关键清理无效字段与React版本一致
if (res.baseId === 'all') {
delete res.baseId
}
if (!res.hbrvcd) {
delete res.hbrvcd
}
if (stnm.value === null) {
delete res.stnmContains
}
return res
})
/**
* 监听基地变化获取Tab数据
*/
watch(qid, async (newQid) => {
if (!newQid) return
dataLoading.value = true
try {
const params = {
baseId: newQid,
sttpCode: 'ENG',
prsc: [{ field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }]
}
const result = await fetchTabData(params)
tabList.value = result
if (result.length > 0) {
// 关键:根据 defaultTab 参数设置默认选中的 Tab
if (props.defaultTab) {
const matchedTab = result.find(item => item.value === props.defaultTab)
if (matchedTab) {
tabIndex.value = matchedTab.value
tabKey.value = matchedTab.key
tabCol.value = matchedTab.col
} else {
// 如果没匹配到,使用第一个 Tab
tabIndex.value = result[0].value
tabKey.value = result[0].key
tabCol.value = result[0].col
}
} else {
// 没有传入 defaultTab使用默认行为第一个 Tab
tabIndex.value = result[0].value
tabKey.value = result[0].key
tabCol.value = result[0].col
}
}
} catch (error) {
console.error('获取Tab数据失败:', error)
message.error('获取Tab数据失败')
} finally {
dataLoading.value = false
}
}, { immediate: true })
/**
* 监听 defaultTab 变化动态切换 Tab解决第二次进入页面时失效的问题
*/
watch(
() => props.defaultTab,
(newDefaultTab) => {
if (!newDefaultTab || tabList.value.length === 0) return
const matchedTab = tabList.value.find(item => item.value === newDefaultTab)
if (matchedTab) {
tabIndex.value = matchedTab.value
tabKey.value = matchedTab.key
tabCol.value = matchedTab.col
}
},
{ immediate: true }
)
/**
* 监听Tab和搜索条件变化获取饼图数据
*/
watch(
[tabKey, tabCol, stnm, qid],
async () => {
if (!tabKey.value || !tabCol.value) return
dataLoading.value = true
try {
const params: any = {
stnmContains: stnm.value,
baseId: qid.value,
sttpCode: 'ENG',
bldsttCcode: tabKey.value === 'noBuild' ? '0' : tabKey.value === 'building' ? '1' : '2',
prsc: [{ field: 'prsc', operator: 'in', dataType: 'string', value: ['1', '2', '3'] }]
}
if (props.seriesName === `装机容量(${unit.value})`) {
params.ttpwr = [{ field: 'ttpwr', operator: 'isnotnull' }]
}
if (stnm.value === null) {
delete params.stnmContains
}
const result = await fetchPieData(params)
// 特殊处理 other 类型
if (qid.value === 'other' && result.length > 0) {
const introRes = await fetchIntroduce({
logic: 'and',
filters: [
{ field: 'wbsType', operator: 'eq', value: 'PSB_RVCD' },
{ field: 'objId', operator: 'eq', value: 'other' },
{ field: 'orderIndex', operator: 'isnotnull' }
]
})
if (introRes?.length) {
result.forEach((item: any) => {
const found = introRes.find((i: any) => item.key === i.wbsCode)
if (found) {
item.index = found.orderIndex
}
})
result.sort((a: any, b: any) => (a.index || 0) - (b.index || 0))
}
}
pikData.value = result
pieData.value = result
pieCode.value = []
} catch (error) {
console.error('获取饼图数据失败:', error)
message.error('获取饼图数据失败')
} finally {
dataLoading.value = false
}
},
{ deep: true }
)
/**
* 生命周期钩子
*/
onMounted(() => {
// 模拟加载基地列表(更真实的基地数据)
jiDiList.value = [
{ baseid: 'all', basename: '全部基地' },
{ baseid: 'cj', basename: '长江流域' },
{ baseid: 'hh', basename: '黄河流域' },
{ baseid: 'zj', basename: '珠江流域' },
{ baseid: 'hh2', basename: '淮河流域' },
{ baseid: 'hj', basename: '海河流域' },
{ baseid: 'sj', basename: '松花江流域' },
{ baseid: 'lj', basename: '辽河流域' },
{ baseid: 'other', basename: '其他' }
]
// 设置默认选中"全部基地"
if (!qid.value) {
qid.value = 'all'
}
initChart()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
chartInstance.value?.dispose()
window.removeEventListener('resize', handleResize)
})
</script>
<template>
<div class="shuidian-kaifa-container">
<!-- 搜索表单 -->
<BasicSearch
ref="formRef"
:searchList="searchList"
:initialValues="initialValues"
@finish="handleSearch"
@reset="handleReset"
>
<template #actions>
<!-- TODO: 集成ExportBtnFileWithSelect组件 -->
<!-- <ExportBtnFileWithSelect
authCode="sdkfzk-export"
tableRef="tableRef"
exportUrl="/dec-lygk-base-server/base/msstbprpt/export"
:select="['stnm', 'baseName', 'addvcdName', 'rvcdName', 'reachcdName', 'dvtp', 'ttpwr', 'yrge', 'ttcp', 'piodt', 'aiodt']"
:useDefaultColumnSelect="false"
fileName="水电开发情况数据"
/> -->
<a-button @click="handleExport">
<template #icon><DownloadOutlined /></template>
导出
</a-button>
</template>
</BasicSearch>
<!-- Tab切换 -->
<a-tabs v-model:activeKey="tabIndex" class="dwsta-table-tu">
<a-tab-pane
v-for="item in tabList"
:key="item.value"
:tab="`${item.value}(${item.count}${item.unit})`"
>
<!-- 图表+表格布局 -->
<div class="chart-table-layout">
<!-- 左侧饼图 -->
<div class="chart-section">
<div
ref="chartRef"
class="pie-chart"
style="width: 541px; height: 412px;"
></div>
</div>
<!-- 右侧表格 -->
<div class="table-section">
<BasicTable
ref="tableRef"
:columns="columns"
:listUrl="fetchTableData"
:searchParams="tableFilter"
:defaultPageSize="20"
:scrollY="380"
/>
</div>
</div>
</a-tab-pane>
</a-tabs>
<!-- 详情弹窗 -->
<a-modal
v-if="mapModal && modalData.sttp"
:open="mapModal"
:title="modalData.titleName ? `${modalData.titleName} 详情信息` : '详情信息'"
:width="1200"
:footer="null"
:maskClosable="false"
class="tabsModal"
style="padding-bottom: 0"
@cancel="handleModalCancel"
>
<!-- TODO: 集成ModalTabs组件 -->
<!-- <ModalTabs v-if="modalData" :tabs="handleTabs(modalData)" :params="modalData" /> -->
<!-- 临时替代方案 -->
<div class="modal-content">
<a-descriptions bordered :column="2">
<a-descriptions-item label="电站编码">
{{ modalData.stcd }}
</a-descriptions-item>
<a-descriptions-item label="电站类型">
{{ modalData.sttp }}
</a-descriptions-item>
</a-descriptions>
<p style="margin-top: 16px; color: #999;">
TODO: 请确认 ModalTabs 组件路径后启用上方注释代码
</p>
</div>
</a-modal>
</div>
</template>
<style scoped lang="scss">
.shuidian-kaifa-container {
display: flex;
flex-direction: column;
height: 700px;
padding: 16px;
background: #fff;
border-radius: 4px;
}
.dwsta-table-tu {
flex: 1;
display: flex;
flex-direction: column;
:deep(.ant-tabs-content) {
height: 100%;
}
:deep(.ant-tabs-tabpane) {
height: 100%;
}
:deep(.ant-tabs-nav) {
margin-bottom: 16px;
}
}
.chart-table-layout {
display: flex;
gap: 16px;
height: calc(100% - 40px);
min-height: 450px;
.chart-section {
flex: 0 0 541px;
background: #fafafa;
border-radius: 4px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
justify-content: center;
.pie-chart {
width: 541px;
height: 412px;
}
}
.table-section {
flex: 1;
overflow: hidden;
background: #fff;
border-radius: 4px;
}
}
.modal-content {
padding: 16px;
:deep(.ant-descriptions) {
margin-bottom: 16px;
}
}
</style>