<template>
<div>
<div class="table-container">
<el-table :data="filteredTableData" :default-sort="{ prop: 'cityName', order: 'ascending' }"
@sort-change="handleSortChange" element-loading-text="数据加载中..." class="custom-header-table"
:row-class-name="rowClassName">
<el-table-column class="className" v-for="column in tableColumns" :key="column.prop || column.label"
:label="column.label" :fixed="column.fixed" :align="column.align" :sortable="column.sortable"
:sort-method="column.sortable ? createSorter(column.prop) : null">
<!-- 父列:只作为容器,不显示数据 -->
<template v-if="column.children && column.children.length > 0">
<el-table-column v-for="child in column.children" :key="child.prop" :prop="child.prop"
:label="child.label" :sortable="child.sortable" :align="child.align || 'right'"
class="className" :sort-method="child.sortable ? createSorter(child.prop) : null">
<template slot-scope="{ row }">
<span class="text-right cell-clickable"
@click.stop="handleCellClick(row, child, row[child.prop], child.type)">
{{ row[child.prop] + "%" }}
</span>
</template>
</el-table-column>
</template>
<!-- 普通列:显示数据 -->
<template slot-scope="{ row }" v-if="!column.children || column.children.length === 0">
<!-- 单元格点击事件 -->
<span class="cell-clickable"
@click.stop="handleCellClick(row, column, row[column.prop], column.type)">
{{
row && column.prop !== undefined
? (column.prop.endsWith('Rate') ? row[column.prop] + '%' : row[column.prop])
: '--'
}}
</span>
</template>
</el-table-column>
<el-table-column class="className" v-for="column in tableColumns" :key="column.prop || column.label"
:label="column.label" :fixed="column.fixed" :align="column.align" :sortable="column.sortable"
:sort-method="column.sortable ? createSorter(column.prop) : null">
<!-- 父列:只作为容器,不显示数据 -->
<template v-if="column.children && column.children.length > 0">
<el-table-column v-for="child in column.children" :key="child.prop" :prop="child.prop"
:label="child.label" :sortable="child.sortable" :align="child.align || 'right'"
class="className" :sort-method="child.sortable ? createSorter(child.prop) : null">
<template slot-scope="{ row }">
<span class="text-right cell-clickable"
@click.stop="handleCellClick(row, child, row[child.prop], child.type)">
{{ row[child.prop] + "%" }}
</span>
</template>
</el-table-column>
</template>
<!-- 普通列:显示数据 -->
<template slot-scope="{ row }" v-if="!column.children || column.children.length === 0">
<!-- 单元格点击事件 -->
<span class="cell-clickable"
@click.stop="handleCellClick(row, column, row[column.prop], column.type)">
{{
row && column.prop !== undefined
? (column.prop.endsWith('Rate') ? row[column.prop] + '%' : row[column.prop])
: '--'
}}
</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 单元格详情弹窗 -->
<el-dialog :title="dialogConfig.title" :visible.sync="showCellDialog" width="60%" :close-on-click-modal="false"
class="cell-detail-dialog">
<div class="cell-dialog-content">
<div class="info-section">
<div class="info-item">
<span class="info-label">数据来源:</span>
<span class="info-value">{{ dialogConfig.dataSources }}</span>
</div>
<div class="info-item">
<span class="info-label">统计口径:</span>
<span class="info-value">{{ dialogConfig.statisticalCaliber }}</span>
</div>
<div class="info-item">
<span class="info-label">指标值:</span>
<span class="info-value highlight">{{ selectedCell.cellValue }}</span>
</div>
</div>
<div class="table-section">
<div class="table-header">
<h3 class="table-title">失分项目明细</h3>
<el-button type="primary" @click="exportCellValue" size="small" icon="el-icon-download">
导出数据
</el-button>
</div>
<el-table :data="pagedDetialTable" stripe border v-loading="detailTableLoading"
element-loading-text="明细数据加载中..." style="width: 100%">
<el-table-column type="index" :index="indexMethod" label="序号" width="60" align="center">
</el-table-column>
<el-table-column prop="productCode" label="项目编号" width="140">
</el-table-column>
<el-table-column prop="productName" label="项目名称" min-width="200">
<template slot-scope="{ row }">
<el-tooltip effect="dark" :content="row.productName" placement="top">
<span class="product-name">{{ row.productName }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="area" label="区域" width="100" align="center">
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" align="center">
</el-table-column>
</el-table>
</div>
<!-- 分页控件 -->
<div class="pagination-container" v-if="detialTable.length > pageSize">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="currentPage" :page-sizes="[5, 10, 15, 20]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="detialTable.length">
</el-pagination>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="showCellDialog = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { detailConfig, getDataRevenueDetialInfo } from '../Mock/revenueEnvironment';
export default {
name: 'RevenueEnvironmentTable',
props: {
tableData: {
type: Array,
required: true,
default: () => []
},
tableColumns: {
type: Array,
required: true,
default: () => [
]
},
queryTime: {
required: true,
},
},
data() {
return {
searchQuery: '',
detailTableLoading: false,
// 新增:单元格弹窗相关状态
showCellDialog: false,
dialogConfig: detailConfig.dialogConfig,
selectedCell: {
row: {},
column: {},
cellValue: '',
cityName: '',
columnName: '',
columnProp: '',
},
detialTable: [],
currentPage: 1,
pageSize: 5,
dataSourceKey: '',
statisticalCaliberKey: '',
currentSortOrder: 'ascending' // 默认升序
};
},
computed: {
//数据的过滤 这里是作为后续的拓展
filteredTableData() {
// 分离数据:非平均行和平均行
const nonAverageData = this.tableData.filter(row => !row.isProvinceAverage);
// const nonAverageData = this.tableData;
// 对非平均行进行搜索过滤(后续扩展搜索功能)
let filteredNonAverage = nonAverageData;
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
filteredNonAverage = nonAverageData.filter(item =>
item.cityName.toLowerCase().includes(query)
);
}
// 返回结果:过滤后的非平均行 + 计算的平均行(如果存在)
const result = [...filteredNonAverage];
if (this.provinceAverage) {
result.push(this.provinceAverage);
}
return result;
},
/**
* 计算全省平均率
* @returns {Object|null} 包含全省平均数据的对象
*/
provinceAverage() {
if (!this.tableData || this.tableData.length === 0) {
return null;
}
// 从 tableColumns 中动态获取所有需要计算平均率的字段
const rateFields = this.getRateFields();
// console.log(rateFields);
const averageData = {
cityName: '全省平均',
isProvinceAverage: true // 标记为平均行,用于样式区分
};
// 计算每个百分比字段的平均值
rateFields.forEach(field => {
let sum = 0;
let count = 0;
this.tableData.forEach(item => {
const value = Number(item[field]);
if (!isNaN(value) && value >= 0) {
sum += value;
count++;
}
});
// 计算平均值,对avgDays字段特殊处理
if (field === 'avgDays') {
// 对天数字段进行四舍五入取整
averageData[field] = count > 0 ? Math.round(sum / count).toString() : '0';
} else {
// 其他字段保留2位小数
averageData[field] = count > 0 ? (sum / count).toFixed(2) : '0.00';
}
});
return averageData;
},
//弹框内容的分页
pagedDetialTable() {
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.detialTable.slice(start, end);
},
},
methods: {
//自定义排序 el排序失效
customSort(a, b, prop) {
// 如果其中一个是平均行,不参与排序比较
// 二选1
// if (a.isProvinceAverage || b.isProvinceAverage) {
// return 0;
// }
console.log("a", a);
console.log("b", b);
console.log("prop", prop);
// 处理平均行逻辑
if (a.isProvinceAverage && b.isProvinceAverage) {
console.log("排序结果: 两行都是平均行,相等");
return 0;
}
if (a.isProvinceAverage) {
console.log("排序结果: a 是平均行,排在 b 后面");
return 1;
}
if (b.isProvinceAverage) {
console.log("排序结果: b 是平均行,a 排在 b 前面");
return -1;
}
// 以下为非平均行的排序逻辑
const aVal = a[prop];
const bVal = b[prop];
// 处理空值
if (aVal === null || aVal === undefined || aVal === '') return 1;
if (bVal === null || bVal === undefined || bVal === '') return -1;
// 处理数字和百分比数据
let numA, numB;
if (typeof aVal === 'string' && aVal.includes('%')) {
// 百分比数据:提取数字部分
numA = parseFloat(aVal.replace('%', ''));
numB = parseFloat(bVal.replace('%', ''));
} else {
// 普通数字数据
numA = parseFloat(aVal);
numB = parseFloat(bVal);
}
// 处理转换失败的情况
if (isNaN(numA)) return 1;
if (isNaN(numB)) return -1;
const result = numA - numB;
console.log(`最终排序结果: ${a.cityName} vs ${b.cityName} = ${result}`);
return result;
},
handleSortChange({ prop, order }) {
this.currentSortProp = prop;
this.currentSortOrder = order || 'ascending';
},
/**
* 生成排序方法
*/
createSorter(prop) {
return (a, b) => {
// 获取当前排序方向
const sortOrder = this.currentSortOrder;
// 处理平均行逻辑
if (a.isProvinceAverage && b.isProvinceAverage) {
console.log("排序结果: 两行都是平均行,相等");
return 0;
}
if (a.isProvinceAverage) {
// 正序时平均行最大(返回1),反序时平均行最小(返回-1)
// 这样无论正序反序,平均行都在底部
console.log("排序结果: a 是平均行,排在 b 后面");
return sortOrder === 'ascending' ? 1 : -1;
}
if (b.isProvinceAverage) {
// 正序时平均行最大(返回-1),反序时平均行最小(返回1)
console.log("排序结果: b 是平均行,a 排在 b 前面");
return sortOrder === 'ascending' ? -1 : 1;
}
// 以下为非平均行的排序逻辑
const aVal = a[prop];
const bVal = b[prop];
// 处理空值
if (aVal === null || aVal === undefined || aVal === '') return 1;
if (bVal === null || bVal === undefined || bVal === '') return -1;
// 处理数字和百分比数据
let numA, numB;
if (typeof aVal === 'string' && aVal.includes('%')) {
numA = parseFloat(aVal.replace('%', ''));
numB = parseFloat(bVal.replace('%', ''));
} else {
numA = parseFloat(aVal);
numB = parseFloat(bVal);
}
// 处理转换失败的情况
if (isNaN(numA)) return 1;
if (isNaN(numB)) return -1;
const result = numA - numB;
// 根据排序方向调整结果
return sortOrder === 'descending' ? -result : result;
};
}, 代码这里有问题,当正序的时候全省确实是在下方,但反序的时候却在上方。
最新发布