element控件table的排序功能(sortable=“custom“远程排序即在后台排序)

本文详细介绍了如何在Vue.js的el-table组件中实现自定义排序功能。通过监听@sort-change事件,结合sortable参数,可以控制表格数据的升序或降序排列。同时,文章展示了相关JavaScript方法,包括SortChange方法用于处理排序箭头的点击事件,以及GetList方法用于根据排序条件获取分页数据。整个过程涉及接口调用和参数传递,确保了表格数据的动态更新和正确排序。

首先在el-table标签内添加监听事件 @sort-change="SortChange"

 

然后再要排序的列标签上添加参数 sortable="custom"

       

页面上只需要改动这两处

js方法

      这是排序箭头触发的事件(即table中添加的监听事件)

// table排序箭头变化时触发
	  SortChange(column, prop, order){
		  // 在方法触发时先获取并保存接口需要的字段,调用分页方法,分页方法中掉用了获取列表数据方法
	  		  let _this = this;
	  		  // console.log(column)
			  // column.order为空时仅刷新页面
	  		  if(column.order === null) {
	  		    this.descOrAce = "";
	  		    this.orderBy = "";
	  		    _this.handleCurrentChange(1);
	  		    return;
	  		  }
			  // 根据点击时打印出的colume.order判断是升序还是降序,并根据接口需要赋值0或1
	  		  this.descOrAce = column.order === "ascending" ? 1 : 0;//(descOrAce在全局data声明)
			  // 根据点击时打印出的colume.prop判断是哪一列
	  		  this.orderBy = column.prop;//(orderBy在全局data声明)
	  		  _this.handleCurrentChange(1);//(这里调用了分页的方法)
	  },

这是调用的分页方法

 这是请求页面数据的方法

GetList() {
        let _this = this;
		//这里声明的descOrAce和orderBy是为了在一面刚打开未点击排序按钮时加载默认列表(此时字段为空,请求路径中等同于没传)
		//当点击排序按钮后对descOrAce和orderBy进行赋值,根据值后台返回对应字段的正序或倒序的列表
		let descOrAce = "";
		let orderBy = "";
		if(this.descOrAce === 0 || this.descOrAce === 1) {
		    descOrAce = "&descOrAce="+_this.descOrAce;
		}
		if(this.orderBy) {
		orderBy = "&orderBy="+_this.orderBy;
		}
		// console.log(_this.orderBy)
        _this.$ajax(_this.$BaseUrl + "BigScreen/DuBanRecordList?rows=" + _this.Rows + "&page=" + 
		_this.Page + "&redOrYellow=" + _this.Type + "&poIntType=" + _this.PointType + "&duBanTime=" + 
		_this.MyDate + "&orgId=" + _this.StreetId + "&keyWord=" + _this.SearchText + descOrAce + orderBy,
		 "get", "").then(function (msg) {
          if (msg.data) {
            let data = msg.data.rows;
            _this.Records = msg.data.records;
            for (let i = 0; i < data.length; i++) {
              _this.TableData3.push(data[i]);
            }
          }
        }).catch((err) => {

        })
      },

 

 

<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; }; }, 代码这里有问题,当正序的时候全省确实是在下方,但反序的时候却在上方。
最新发布
10-23
<template> <eh-layout card full-content> <el-form :inline="true" :model="searchForm" label-width="80px" class="search-form" > <div class="search-container" style="display: flex; align-items: center; gap: 10px; margin-left: 10px;margin-bottom: 10px;"> <el-button class="custom-btn" @click="generateReport" :loading="loadingGenerate">核算</el-button> <el-select v-model="searchForm.type" placeholder="请选择查询类型" style="width: 140px;" clearable > <!-- 可扩展其他查询条件 --> <el-option label="加工单号" value="jgdh"></el-option> <el-option label="单据日期" value="documentDate"></el-option> <el-option label="产品名称" value="cpmc"></el-option> <el-option label="规格型号" value="ggxh"></el-option> <el-option label="高级查询" value="cpmcAndGgxh"></el-option> </el-select> <template v-if="searchForm.type === 'jgdh'"> <el-input v-model="searchForm.keyword" placeholder="请输入加工单号" style="width: 230px;" @keyup.enter.native="onSearch" /> </template> <template v-else-if="searchForm.type === 'documentDate'"> <el-date-picker v-model="searchForm.documentDate" type="date" placeholder="选择单据日期" value-format="yyyy-MM-dd" style="width: 230px;" @change="onSearch" /> </template> <template v-else-if="searchForm.type === 'cpmc'"> <el-input v-model="searchForm.cpmc" placeholder="请输入产品名称" style="width: 230px;" @keyup.enter.native="onSearch" clearable /> </template> <template v-else-if="searchForm.type === 'ggxh'"> <el-input v-model="searchForm.ggxh" placeholder="请输入规格型号" style="width: 230px;" @keyup.enter.native="onSearch" clearable /> </template> <template v-else-if="searchForm.type === 'cpmcAndGgxh'"> <el-input v-model="searchForm.keyword" placeholder="请输入加工单号" style="width: 230px;" @keyup.enter.native="onSearch" /> <el-input v-model="searchForm.cpmc" placeholder="请输入产品名称" style="width: 230px;" @keyup.enter.native="onSearch" clearable /> <el-input v-model="searchForm.ggxh" placeholder="请输入规格型号" style="width: 230px;" @keyup.enter.native="onSearch" clearable /> <el-date-picker v-model="searchForm.documentDate" type="date" placeholder="选择单据日期" value-format="yyyy-MM-dd" style="width: 230px;" @change="onSearch" /> </template> <el-button icon="el-icon-search" @click="onSearch" /> <el-button icon="el-icon-refresh-right" @click="resetSearch" /> </div> </el-form> <el-table v-loading="loading" :data="tableData" style="width: 100%" row-key="header.id" :expand-row-keys="expandedRowKeys" @expand-change="handleExpandChange" border stripe > <!-- 展开行显示明细数据 --> <el-table-column type="expand"> <template #default="props"> <el-table :data="props.row.mxList" border size="mini" style="width: 95%; margin: 0 auto" > <el-table-column prop="cbxm" label="成本项目" width="380" /> <el-table-column prop="zxwlmc" label="子项物料名称" width="389" /> <el-table-column prop="zxwlxhl" label="子项物料耗用量" width="400" align="right" /> <el-table-column prop="zxwlje" label="子项物料金额" width="400" align="right" /> </el-table> </template> </el-table-column> <!-- 主记录字段 --> <el-table-column prop="header.jgdh" label="加工单号" width="250" sortable /> <el-table-column prop="header.cpmc" label="产品名称" width="250" /> <el-table-column prop="header.ggxh" label="规格型号" width="250" /> <el-table-column prop="header.dw" label="主单位" width="250" /> <el-table-column prop="header.rksl" label="入库数量(主单位)" width="200" align="right" /> <el-table-column prop="header.fdw" label="辅单位" width="250" /> <el-table-column prop="header.rkslfdw" label="入库数量(辅单位)" width="200" align="right" /> <el-table-column prop="header.zcb" label="总成本" width="207" align="right" /> <el-table-column prop="header.dwcb" label="单位成本(主单位)" width="150" align="right" /> <el-table-column prop="header.dwcbfdw" label="单位成本(辅单位)" width="150" align="right" /> <el-table-column prop="header.documentDate" label="单据日期" width="150" align="right"> <template #default="scope"> {{ formatDate(scope.row.header.documentDate) }} </template> </el-table-column> </el-table> <!-- 分页组件 --> <div style="display: flex; justify-content: flex-end; margin-top: 20px;"> <el-pagination background layout="prev, pager, next, sizes, total" :total="totalRecords" :page-size="pageSize" :current-page="currentPage" @size-change="handleSizeChange" @current-change="handlePageChange" /> </div> </eh-layout> </template> <script> export default { name: 'WorkOrderPagination', data() { return { tableData: [], // 表格数据 totalRecords: 0, // 总记录数 currentPage: 1, // 当前页码 pageSize: 10, // 每页显示数量 expandedRowKeys: [], // 当前展开的行keys searchForm: { type: null, // 默认查询类型为空 keyword: '', // 查询关键词 documentDate: '', cpmc: '', ggxh: '' }, loading: false,//列表加载状态 loadingGenerate: false//核算按钮状态 }; }, mounted() { this.fetchData(); }, methods: { // 获取数据 async fetchData() { this.loading = true try { const params = { page: this.currentPage, pageSize: this.pageSize, cpmc: this.searchForm.cpmc?.trim() || '', // 始终传递,空则为空字符串 ggxh: this.searchForm.ggxh?.trim() || '' }; if (this.searchForm.jgdh) params.jgdh = this.searchForm.jgdh?.trim() || ''; if (this.searchForm.documentDate) params.documentDate = this.searchForm.documentDate; if (this.searchForm.cpmc) params.cpmc = this.searchForm.cpmc?.trim() || ''; if (this.searchForm.ggxh) params.ggxh = this.searchForm.ggxh?.trim() || ''; const response = await this.$http.post( 'uempCostRcbjsdController!findCostByCodeAndDateRange.m', params ); this.tableData = response.rows; this.totalRecords = parseInt(response.records); // 每次加载新数据时,清空展开的行 this.expandedRowKeys = []; } catch (error) { console.error('获取数据失败:', error); this.$message.error('数据加载失败'); } this.loading = false }, // 分页改变 handlePageChange(page) { this.currentPage = page; this.fetchData(); }, // 每页数量改变 handleSizeChange(size) { this.pageSize = size; this.currentPage = 1; this.fetchData(); }, // 格式化货币显示 formatCurrency(value) { if (!value) return '0.00'; const num = parseFloat(value); return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }, formatDate(timestamp) { if (!timestamp) return '--'; const date = new Date(timestamp); // 注意:JS 时间戳是毫秒,直接可用 const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始 const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }, // 处理展开行变化 handleExpandChange(row, expandedRows) { // 只保留当前展开的行 this.expandedRowKeys = expandedRows.length > 0 ? [row.header.id] : []; }, onSearch() { this.currentPage = 1 this.fetchData() }, // 重置方法 resetSearch() { this.searchForm = { type: null, // 重置为不选中任何选项 keyword: '', documentDate: '' }; this.currentPage = 1; this.fetchData(); // 重置后自动查询所有数据 }, // 核算 generateReport() { this.loadingGenerate = true this.$http .post('uempCostRcbjsdController!generateDailyCostReport.m') .then(response => { if (response.type === 'error') { this.$message.error(response.data || '操作失败'); } else if (response.type === 'warn' || response.type === 'warning') { this.$message.warning(response.data || '操作完成'); } else { this.$message.success(response.data || '核算成功,数据已刷新'); } }) .catch(error => { console.error('请求失败:', error); this.$message.error('网络错误,请检查连接或联系管理员'); }) .finally(() => { this.loadingGenerate = false; }); } } }; </script> <style scoped> /* 筛选*/ .search-form { display: flex; justify-content: flex-end; align-items: center; } .search-controls { display: flex; align-items: center; } .search-container { display: flex; align-items: center; gap: 10px; margin-left: 10px; } .custom-btn { background-color: #4068E0; border-color: #4068E0; color: #fff; border-radius: 18px; /* 圆角 */ } ::v-deep .el-table th, ::v-deep .el-table td { text-align: center !important; /* 水平居中 */ vertical-align: middle; /* 垂直居中(可选) */ height: 40px; padding: 6px 0; } /* 可选:让表头背景色更清晰 */ ::v-deep .el-table th.is-leaf, ::v-deep .el-table th > .cell { text-align: center !important; font-weight: 600; } </style> 能把这个组件改成使用 <eh-grid吗
09-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值