因公司项目需要,会用到大量表格作为数据展示,唯一不同的地方就是表格的展示,有的一级有的多级表头,重复写起来很麻烦,每生产一个表格,就得重复写一套或者 分页 查询 等套餐,或者对分页单独封装引用。还有需求上大同小异的变化,比如这个表格要输入框,那个表格要下拉框,索性干脆封装一下,自己用来方便 ,加班?加不了一点 !,封装完直接Copy,传入对应的接口等参数信息直接完事!支持的功能也都注释清楚了。
话不多说,直接上代码,当然自己写的可能不是很规范,比较随意,功能自己迭代中(有新需求就继续往上迭代优化),目前还存在一些问题和BUG,慢慢优化吧,也欢迎大佬指出问题。😂😂😂
2023/08/28 针对可编辑时渲染大量组件造成页面性能卡顿进行了优化,保持唯一性。
2023/11/17 新增拖拽等功能,代码优化,修复部分bug。
代码部分
主页面 index.vue
<template>
<div v-loading="Listloading">
<div
v-if="titleShow"
style="padding: 20px 0px;overflow: hidden;display: flex;align-items: center;justify-content: center;position: relative;"
>
<div style="position: absolute;left: 0;">
<slot name="ft"></slot>
</div>
<div style="font-size: 18px;font-weight: 900;position: absolute;">
{{ title }}
</div>
<div style="position: absolute;right: 0;">
<slot name="ht"></slot>
</div>
</div>
<div class="hrm-table">
<div
style="width: 100%;height: 100%;position: relative;"
v-loading="loading"
>
<el-table
stripe
border
ref="table"
:row-height="40"
:resizable="true"
:data="tableData"
:sum-text="sumText"
@row-click="rowClick"
highlight-current-row
@cell-click="cellClick"
@sort-change="sortChange"
:show-summary="showSummary"
:height="TabularData.height"
:summary-method="getSummaries"
:span-method="arraySpanMethod"
class="el-table-header--white"
:cell-class-name="cellClassName"
@header-dragend="handleHeaderDragend"
@selection-change="handleSelectionChange"
:header-cell-class-name="tableHeaderClassName"
>
<!-- 表头 -->
<template v-show="loading && columnData.length">
<!-- 勾选 -->
<el-table-column
type="selection"
width="55"
:selectable="selectEnable"
v-if="selection"
>
</el-table-column>
<Column
v-for="(item, index) in columnData"
:isEdit="isEdit"
:columnId="columnId"
:rowId="rowId"
:customHtml="customHtml"
:rowIdCode="rowIdCode"
:key="index"
:data="item"
:indexNum="index"
:fixedNum="fixedNumberColumns"
:inputType="item.inputType ? item.inputType : inputType"
:fieldName="fieldName"
:selectOption="selectOption"
@handleEdit="handleEdit"
@removeClass="removeClass"
>
<!-- 自定义HTML -->
<template
slot="customContent"
slot-scope="scope"
v-if="customHtml"
>
<slot name="Content" v-bind="scope"></slot>
</template>
</Column>
<!-- 操作列 -->
<el-table-column
v-if="isAction"
:fixed="actionData.fixed"
:label="actionData.title"
:width="actionData.width"
>
<!-- 操作列 -->
<template slot-scope="scope">
<slot name="ActionButton" v-bind="scope"></slot>
</template>
</el-table-column>
<!-- 额外自定义列 -->
<el-table-column
:resizable="false"
fixed="right"
width="40"
v-if="customColumnShow"
>
<!-- 列头 -->
<template
slot="header"
slot-scope="slot"
style="position: relative;"
>
<slot name="columnHeader" v-bind="slot"></slot>
</template>
<!-- 内容 -->
<template slot-scope="scope">
<slot name="columnBody" v-bind="scope"></slot>
</template>
</el-table-column>
</template>
</el-table>
<div
v-show="editLoading"
style="position: absolute;top: 50%; left: 50%;color: rgb(35, 98, 251);z-index: 999;"
>
加载中<i class="el-icon-loading"></i>
</div>
</div>
<div class="p-contianer" v-if="paginationShow">
<el-pagination
:current-page="currentPage"
:page-sizes="pageSizes"
:page-size.sync="pageSize"
:total="total"
class="p-bar"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script>
import Column from "./Column";
import { debounce } from "./debounce";
import { rowDrop } from "./sortable";
export default {
components: {
Column
},
provide() {
const { scopedSlots } = this;
return {
scopedSlots
};
},
props: {
TabularData: {
type: Object,
default: () => {
return {
SpanMethod: null, // 合并方法
customHtml: false, //自定义html
customColumnShow: false, //自定义列
isDrag: false, // 是否拖拽
height: 600, //高度
fieldName: false, //是否动态字段 fieldName + fieldExtend
isShowDynamicHeaders: true, //是否开启动态表头
FirstCall: true, // 是否首次调用
headerHata: [], //固定表头数据
HeaderDataApi: null, // 表头方法
HeaderQuery: {}, // 表头参数
DataApi: null, // 数据方法
DataQuery: {}, // 数据参数
EditApi: null, // 编辑方法
EditQuery: {}, // 编辑参数
rowIdCode: "id", // 数据唯一标识字段 默认取id
DeleteApi: null, // 删除方法
DeleteQuery: {}, // 删除参数
selection: false, // 是否开启勾选
titleShow: false, // 是否开启标题
title: "", //标题
updateNow: false, // 编辑时是否马上加载loading
showSummary: false, //是否开启合计
sumText: "合计", //合计文本
getSummaries: null, // 自定义合计方法
isEditFun: null, // 自定义编辑方法
isEdit: false, // 是否开启编辑
inputType: "input", // 可编辑组件类型input、textarea、select、number
selectOption: [], //组件类型为下拉框的时候需要传入数据
isAction: false, // 是否展示操作列
actionData: {
// 操作列配置
title: "操作",
width: 50,
fixed: "right"
},
fixedNumberColumns: 0, // 固定列数
frequencyTime: 1500, // 刷新频率
paginationShow: true // 是否展示分页
};
}
}
},
data() {
return {
SpanMethod: null, //合并方法
customHtml: false, //自定义html
customColumnShow: false, //自定义列
isDrag: false, // 列表是否可以拖动
loading: false,
editLoading: false,
Listloading: false,
columnId: "", // 列ID
rowId: "", //行ID
total: 0,
currentPage: 1,
pageSize: 15,
pageSizes: [15, 30, 60, 100],
columnData: [],
tableData: [],
FirstCall: true,
headFun: null,
hearQuery: {},
listFun: null,
listQuery: {},
EditFun: null,
EditQuery: {},
rowIdCode: "id", // 默认id唯一标识
DeleteFun: null,
DeleteQuery: {},
selection: false,
titleShow: false,
title: "",
updateNow: false, // 编辑时是否马上加载loading
multipleSelection: [],
showSummary: false, //是否开启合计
sumText: "合计", //合计文本
getSummaries: null, // 自定义合计方法
isShowDynamicHeaders: true,
headerHata: [],
isEdit: false, // 是否可以编辑
isEditFun: null, // 自定义编辑方法
inputType: "input", // 可编辑组件类型input、textarea、select、number
selectOption: [], //组件类型为下拉框的时候需要传入数据
isAction: false, // 是否展示操作列
actionData: {}, // 操作列配置
empty: [
{
columnName: "暂无数据",
fieldName: "NAN",
id: "1"
},
{
columnName: "暂无数据",
fieldName: "NAN",
id: "2"
},
{
columnName: "暂无数据",
fieldName: "NAN",
id: "3"
},
{
columnName: "暂无数据",
fieldName: "NAN",
id: "4"
},
{
columnName: "暂无数据",
fieldName: "NAN",
id: "5"
}
],
fixedNumberColumns: 0, //固定列数
frequencyTime: 900, // 刷新频率
fieldName: false, //是否动态字段 fieldName + fieldExtend
paginationShow: true, // 是否展示分页
// 传递
scopedSlots: {}
};
},
computed: {},
watch: {
TabularData: {
handler(val) {
this.init();
console.log("...更新数据");
},
immediate: true,
deep: true
}
},
updated() {}, // 生命周期 - 更新之后
created() {}, // 生命周期 - 创建完成
mounted() {
console.log(this.TabularData, "初始化====>");
this.getHead();
// 在挂载后给特定函数防抖
let than = this;
let time = than.frequencyTime;
this.updata = debounce(function() {
this.getList("NO");
}, time);
}, // 生命周期 - 挂载完成
beforeCreate() {}, // 生命周期 - 创建之前
beforeMount() {}, // 生命周期 - 挂载之前
beforeUpdate() {}, // 生命周期 - 更新之前
updated() {}, // 生命周期 - 更新之后
beforeDestroy() {}, // 生命周期 - 销毁之前
destroyed() {}, // 生命周期 - 销毁完成
activated() {}, // 如果页面有keep-alive缓存功能,这个函数会触发
methods: {
// 修改固定列高度 因为设置了滚动条高度 造成el-table表格错位
fixedHeight() {
const dom = this.$el.getElementsByClassName("el-table__fixed");
if (dom && dom.length > 0) {
for (let i = 0; i < dom.length; i++) {
let fix = dom[i];
let he = fix.style.height;
if (he) {
let px = he.replace("px", "");
fix.style.height = px - 2 + "px";
console.log(px, "px");
}
}
}
},
// 合并
arraySpanMethod({ row, column, rowIndex, columnIndex }) {
// this.$emit("arraySpanMethod", row, column, rowIndex, columnIndex);
// 判断是否存在自定义合并方法函数
if (typeof this.SpanMethod === "function" && this.SpanMethod != null) {
// 传递参数触发函数
return this.SpanMethod(row, column, rowIndex, columnIndex);
}
},
// 长度统计
computeColumnWidth(column) {
if (column.childes && column.childes.length > 0) {
return column.childes.reduce(
(prev, next) => prev + this.computeColumnWidth(next),
0
);
}
return column.realWidth || column.width || 150;
},
//重新渲染组件 解决fixed错位问题
doLayoutFun() {
this.$nextTick(() => {
if (this.$refs.table) {
this.$refs.table.doLayout();
}
});
},
// 合计方法
// getSummaries(param) {
// const { columns, data } = param;
// const sums = [];
// columns.forEach((column, index) => {
// if (index === 0) {
// sums[index] = "总价";
// return;
// }
// const values = data.map(item => Number(item[column.property]));
// if (!values.every(value => isNaN(value))) {
// sums[index] = values.reduce((prev, curr) => {
// const value = Number(curr);
// if (!isNaN(value)) {
// return prev + curr;
// } else {
// return prev;
// }
// }, 0);
// sums[index] += " 元";
// } else {
// sums[index] = "N/A";
// }
// });
// return sums;
// },
// 是否禁用
selectEnable(row) {
return true;
},
// 当选择项发生变化时会触发该事件
handleSelectionChange(selection) {
this.multipleSelection = selection;
},
// 获取列表数据
getList(NO) {
if (!this.listFun) return (this.Listloading = false);
// 如果不想加载lodong 传 NO
// this.loading = NO == undefined ? true : false;
// this.Listloading = NO == undefined ? true : false;
this.Listloading = true;
this.listFun({
...this.listQuery,
pageSize: this.pageSize,
pageNum: this.currentPage
}).then(res => {
if (res.code != 0) {
this.lodingFun(false);
this.tableData = [];
this.total = 0;
this.$message.error(res.msg);
return;
}
// 存在分页的情况数据路径在res.data.list中
if (res.data.list && Array.isArray(res.data.list)) {
this.tableData = res.data.list;
this.total = res.data.total;
this.doLayoutFun();
// 验证是否开启拖拽
if (this.isDrag) {
console.log("开启拖拽");
rowDrop(this);
}
}
this.lodingFun(false);
// 更新固定列高度
// this.fixedHeight();
});
},
// 全局loding管理
lodingFun(check) {
this.loading = check;
this.editLoading = check;
this.Listloading = check;
},
// 获取表头
async getHead(next) {
// next 是否在获取表头后,立马更新列表 true/false
// 判断是否开启动态表头
if (this.isShowDynamicHeaders) {
if (!this.headFun) return;
// this.loading = true;
this.Listloading = true;
this.headFun({
...this.hearQuery
}).then(res => {
// this.loading = false;
// 如果返回错误
if (res.code != 0) {
this.columnData = [];
this.columnData = this.empty;
this.lodingFun(false);
return this.$message.error(res.msg);
}
// 如果空数据
if (res.data.length == 0) {
this.columnData = [];
this.columnData = this.empty;
this.lodingFun(false);
} else {
this.columnData = [];
let arr = res.data;
// 解决element ui el-table 固定列在多级表头时宽度无法动态变更
arr.forEach(column => {
var fixedWidth = 0;
fixedWidth += this.computeColumnWidth(column);
column.width = fixedWidth;
});
// 表头赋值
this.columnData = arr;
//
this.$nextTick(() => {
// FirstCall 是否需要首次加载 更新完表头 更新数据
if (this.FirstCall || next) {
this.getList();
} else {
this.lodingFun(false);
}
});
}
});
} else {
// 固定表头
if (this.headerHata && this.headerHata.length > 0) {
this.columnData = this.headerHata;
// FirstCall 是否需要首次加载 更新完表头 更新数据
if (this.FirstCall || next) {
this.getList();
}
} else {
this.columnData = this.empty;
}
}
},
// 初始化
init() {
// 取值
const {
isShowDynamicHeaders,
headerHata,
FirstCall,
HeaderDataApi,
HeaderQuery,
DataApi,
DataQuery,
EditApi,
EditQuery,
DeleteApi,
DeleteQuery,
selection,
titleShow,
title,
updateNow,
showSummary,
sumText,
getSummaries,
isEditFun,
rowIdCode,
isEdit,
inputType,
selectOption,
isAction,
actionData,
fixedNumberColumns,
fieldName,
paginationShow,
frequencyTime,
isDrag,
customColumnShow,
customHtml,
SpanMethod
} = this.TabularData;
//加载
this.loading = true;
// 是否开启动态表头
this.isShowDynamicHeaders = isShowDynamicHeaders || false;
// 固定表头
this.headerHata = headerHata != undefined ? headerHata : [];
// 是否首次调用查询列表
this.FirstCall = FirstCall !== undefined ? FirstCall : true;
// 表头数据接口方法
this.headFun = HeaderDataApi || null;
// 表头参数
this.hearQuery = HeaderQuery || {};
// 列表数据接口方法
this.listFun = DataApi || null;
// 列表参数
this.listQuery = DataQuery || {};
// 编辑接口方法
this.EditFun = EditApi || null;
// 编辑参数
this.EditQuery = EditQuery || {};
// 删除方法
this.DeleteFun = DeleteApi || null;
// 删除参数
this.DeleteQuery = DeleteQuery || {};
// 是否开启选择
this.selection = selection || false;
// 是否开启标题
this.titleShow = titleShow || false;
// 标题
this.title = title || "";
// 是否编辑立马加载loading
this.updateNow = updateNow || false;
// 是否开启合计
this.showSummary = showSummary || false;
// 合计抬头
this.sumText = sumText || "合计";
// 自定义合计方法
this.getSummaries = getSummaries || null;
// 自定义是否可编辑判断 返回 true false
this.isEditFun = isEditFun || null;
// 数据唯一标识字段 用来可编辑时渲染组件 默认取id字段
this.rowIdCode = rowIdCode || "id";
// 是否可以编辑
this.isEdit = isEdit || false;
// 可编辑时 的组件类型 inputType input、textarea、select、number
this.inputType = inputType || "input";
// selectOption
this.selectOption = selectOption || [];
// 是否开启操作列
this.isAction = isAction || false;
// 操作项配置
this.actionData = actionData || {};
// 固定列数
this.fixedNumberColumns = fixedNumberColumns || 0;
// 是否动态字段
this.fieldName = fieldName || false;
// 是否分页
this.paginationShow = paginationShow != undefined ? paginationShow : true;
// 自定编辑后刷新频率
this.frequencyTime = frequencyTime || 1500;
// 列表是否可进行拖拽
this.isDrag = isDrag || false;
// 自定义列
this.customColumnShow = customColumnShow || false;
// 自定义html
this.customHtml = customHtml || false;
// 自定义合并
this.SpanMethod = SpanMethod || null;
console.log(this.TabularData, "=====初始化完成====");
// 结束加载
this.loading = false;
},
// 更改每页展示数量
handleSizeChange(val) {
this.pageSize = val;
this.getList();
},
// 更改当前页数
handleCurrentChange(val) {
this.currentPage = val;
this.getList();
},
// 表头单元格的 className 的回调方法,也可以使用字符串为所有表头单元格设置一个固定的 className。
tableHeaderClassName({ row, column, rowIndex, columnIndex }) {
// console.log(row, column, rowIndex, columnIndex,'======>tableHeaderClassName')
let titleName = "";
if (column.index == 1 && column.type == "default" && this.isEdit) {
titleName = "table-header-edited"; //edited
let dom = document.createElement("i");
dom.className = "el-icon-edit";
dom.title = "可编辑";
this.$nextTick(() => {
let col = document.getElementsByClassName("table-header-edited");
for (let index = 0; index < col.length; index++) {
let element = col[index];
if (element.children.length < 2) {
element.appendChild(dom);
}
}
});
}
return titleName;
},
// 行点击
rowClick(row, column, event) {},
// 单元格点击
cellClick(row, column, cell, event) {
this.rowId = row[this.rowIdCode];
this.columnId = column.id; // 避免大量渲染组件造成卡顿 只渲染局部
this.$emit("cellClick", row, column, cell, event);
// 是否开启编辑
if (!this.isEdit) return;
// 是否自定义编辑
setTimeout(() => {
if (typeof this.isEditFun === "function" && this.isEditFun != null) {
// 传递参数触发函数
let flx = this.isEditFun(row, column, cell, event);
// 有传自定义方法
if (!flx) return;
this.cellClickClassName(cell);
} else {
// 没有传方法 默认全部开启可编辑
this.cellClickClassName(cell);
}
});
},
// 点击时给列增加类名可编辑
cellClickClassName(cell) {
let than = this.$el;
let COLUMNDOM = than.getElementsByClassName("current-cell").length;
for (let i = 0; i < COLUMNDOM; i++) {
than
.getElementsByClassName("current-cell")
[i].classList.remove("current-cell");
}
cell.classList.add("current-cell");
},
// 通过回调控制class
cellClassName({ row, column, rowIndex, columnIndex }) {
// console.log(row, column, rowIndex, columnIndex, "=====cellClassName====");
if (this.detailAuth && column.property == "employeeName") {
return "can-visit--underline";
} else {
return "";
}
},
// 编辑
handleEdit(scope) {
this.getEdit(scope);
},
// 编辑数据
getEdit(scope) {
if (!this.EditFun) return;
// this.loading = true;
this.Listloading = this.updateNow;
this.EditFun({
...scope.row,
...this.TabularData.EditQuery
}).then(res => {
// this.loading = false;
if (res.code != 0) return this.$message.error(res.msg);
// this.$message.success("操作成功!");
// 编辑完数据 更新数据
let than = this;
// this.editLoading = true;
this.updata(than);
});
},
// 更新接口 防抖触发
updata: debounce(function() {
this.getList("NO");
}, 1500),
// 删除 类名
removeClass() {
let than = this.$el;
let COLUMNDOM = than.getElementsByClassName("current-cell").length;
for (let i = 0; i < COLUMNDOM; i++) {
than
.getElementsByClassName("current-cell")
[i].classList.remove("current-cell");
}
},
//字段排序
sortChange(column, prop, order) {},
// 列表点击
handleRowClick(row, column, event) {},
// 当拖动表头改变了列的宽度的时候会触发该事件
handleHeaderDragend(newWidth, oldWidth, column, event) {}
}
};
</script>
<style scoped lang="scss">
.hrm-table {
/deep/ .el-table__cell {
text-align: center;
}
// 输入框
/deep/ .el-input {
display: none;
}
/deep/ .current-cell .el-input {
display: block;
}
/deep/ .current-cell .el-input + span {
display: none;
}
// 多行文本
/deep/ .el-textarea {
display: none;
}
/deep/ .current-cell .el-textarea {
display: block;
}
/deep/ .current-cell .el-textarea + span {
display: none;
}
// 数字框
/deep/.el-input-number {
width: 100%;
}
/deep/ .el-input-number {
display: none;
}
/deep/ .current-cell .el-input-number {
display: block;
}
/deep/ .current-cell .el-input-number + span {
display: none;
}
// 下拉框
/deep/ .current-cell .el-select {
display: block;
}
/deep/ .current-cell .el-select + span {
display: none;
}
/deep/ .current-cell {
padding: 0px;
}
}
// 分页
.p-contianer {
/deep/ .el-input {
display: block !important;
}
/deep/ .el-pagination__jump {
display: inline-flex !important;
}
/deep/ .p-bar {
margin: 10px 10px 0 0 !important;
}
}
// /deep/ .el-table {
// overflow: visible !important;
// }
// /deep/ .el-table {
// display: flex;
// flex-direction: column;
// }
/* order默认值为0,只需将表格主体order设为1即可移到最后,合计就上移到最上方了 */
/deep/ .el-table__body-wrapper {
order: 0;
}
/deep/ .el-table__fixed-body-wrapper {
// top: 96px !important;
// 解决固定列后 ,表头和表体上下重叠 导致边框被遮挡
margin-top: 1px !important;
}
/deep/ .el-table__fixed-footer-wrapper {
z-index: 0;
}
/deep/ .el-table__body-wrapper {
z-index: 2;
border-right: 1px solid #ebeef5 !important;
}
/deep/ .el-table__fixed-right {
border-left: 1px solid #ebeef5 !important;
z-index: 2;
}
/deep/ .el-table__fixed {
z-index: 99;
}
// /deep/ .el-table__fixed-body-wrapper {
// .el-table__cell {
// border-top: 1px solid #ebeef5;
// }
// }
// 可编辑图标
/deep/ .table-header-edited {
position: relative;
}
/deep/ .table-header-edited > i {
position: absolute;
right: 4px;
top: 4px;
font-size: 14px;
}
// 滚动条
// /deep/.el-table__body-wrapper::-webkit-scrollbar {
// width: 10px;
// height: 10px;
// /deep/.el-scrollbar__wrap::-webkit-scrollbar {
// width: 10px;
// height: 10px;
// }
// }
</style>
动态表头 Column.vue
<template>
<!-- 勾选 -->
<!-- :sortable="data.sortable" -->
<el-table-column
v-if="data && Object.keys(data).length"
:prop="data.fieldName"
:label="data.columnName"
:min-width="data.width || 150"
show-overflow-tooltip
:index="+data.isEdit"
:fixed="fixedNum > indexNum"
:align="data.align || 'center'"
:formatter="fieldFormatter"
>
<template slot-scope="scope">
<!-- 输入框 -->
<el-input
v-if="
isEdit &&
scope.column['index'] == 1 &&
inputType == 'input' &&
columnId == scope.column.id &&
rowId == scope.row[rowIdCode]
"
ref="tableInput"
v-model="scope.row[data.fieldName]"
@blur="removeClass"
@change="$emit('handleEdit', scope)"
></el-input>
<!-- 多行文本 -->
<el-input
v-if="
isEdit &&
scope.column['index'] == 1 &&
inputType == 'textarea' &&
columnId == scope.column.id &&
rowId == scope.row[rowIdCode]
"
type="textarea"
:rows="1"
v-model="scope.row[data.fieldName]"
@blur="removeClass"
@change="$emit('handleEdit', scope)"
>
</el-input>
<!-- 下拉框 -->
<el-select
v-if="
isEdit &&
scope.column['index'] == 1 &&
inputType == 'select' &&
columnId == scope.column.id &&
rowId == scope.row[rowIdCode]
"
v-model="scope.row[data.fieldName]"
@blur="removeClass"
@change="$emit('handleEdit', scope)"
>
<el-option
v-for="item in selectOption"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
<!-- 数字 -->
<el-input-number
v-if="
isEdit &&
scope.column['index'] == 1 &&
inputType == 'number' &&
columnId == scope.column.id &&
rowId == scope.row[rowIdCode]
"
controls-position="right"
v-model="scope.row[data.fieldName]"
@blur="removeClass"
@change="$emit('handleEdit', scope)"
></el-input-number>
<!-- 是否自定义html -->
<template v-if="customHtml">
<span>
<slot name="customContent" v-bind="scope"> </slot>
</span>
</template>
<template v-else>
<!-- 动态字段 展示用-->
<span v-if="fieldName">{{
scope.row[`${data.fieldName}${data.fieldExtend}`]
}}</span>
<!-- 动态字段 展示用-->
<span v-else>{{ scope.row[data.fieldName] }}</span>
</template>
</template>
<Column
v-for="(item, index) in data.childes"
:key="index"
:data="item"
:columnId="columnId"
:rowId="rowId"
:isEdit="isEdit"
:customHtml="customHtml"
:rowIdCode="rowIdCode"
:inputType="
item.inputType && item.inputType != '' ? item.inputType : inputType
"
:fieldName="fieldName"
:selectOption="selectOption"
:indexNum="indexNum"
:fixedNum="fixedNum"
@removeClass="removeClass"
@handleEdit="handleEdit"
>
<!-- 是否自定义html -->
<template slot="customContent" slot-scope="scope" v-if="customHtml">
<span>
<slot name="customContent" v-bind="scope"> </slot>
</span>
</template>
</Column>
</el-table-column>
</template>
<script>
export default {
name: "Column",
components: {},
props: {
customHtml: {
type: Boolean,
default: () => {
return false;
}
},
rowIdCode: {
type: String,
default: () => {
return "";
}
},
columnId: {
type: String,
default: () => {
return "";
}
},
rowId: {
type: String,
default: () => {
return "";
}
},
isEdit: {
type: Boolean,
default: () => {
return false;
}
},
loading: {
type: Boolean,
default: () => {
return false;
}
},
fieldName: {
type: Boolean,
default: () => {
return false;
}
},
selectOption: {
type: Array,
default: () => {
return [];
}
},
inputType: {
type: String,
default: () => {
return "input";
}
},
indexNum: {
type: Number,
default: () => {
return 0;
}
},
fixedNum: {
type: Number,
default: () => {
return 0;
}
},
data: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {};
},
computed: {},
watch: {},
created() {}, // 生命周期 - 创建完成
mounted() {}, // 生命周期 - 挂载完成
beforeCreate() {}, // 生命周期 - 创建之前
beforeMount() {}, // 生命周期 - 挂载之前
beforeUpdate() {}, // 生命周期 - 更新之前
updated() {}, // 生命周期 - 更新之后
beforeDestroy() {}, // 生命周期 - 销毁之前
destroyed() {}, // 生命周期 - 销毁完成
activated() {}, // 如果页面有keep-alive缓存功能,这个函数会触发
methods: {
isShow(scope) {
return true;
},
fieldFormatter(row, column) {
return row[column.property] || "--";
},
removeClass() {
let than = document;
let COLUMNDOM = than.getElementsByClassName("current-cell").length;
for (let i = 0; i < COLUMNDOM; i++) {
than
.getElementsByClassName("current-cell")
[i].classList.remove("current-cell");
}
},
handleEdit(scope) {
this.$emit("handleEdit", scope);
}
}
};
</script>
<style scoped lang="scss"></style>
防抖JS页面 debounce.js
export function debounce(fn, delay) {
// 如果没传延迟时间,就默认1500毫秒
var delay = delay || 1500;
// 记录上一次触发的定时器
var timer = null;
return function() {
const th = this;
var args = arguments;
// 如果定时器还没结束,再次触发了这个函数
// 就清除这个定时器,重新计时
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function() {
timer = null;
// 定时器结束,正常执行函数
fn.apply(th, args);
}, delay);
};
}
补充样式
// 头部为白色 不加粗的表风格
.el-table-header--white {
th {
border-right-width: 0;
background-color: white !important;
.cell {
font-size: 13px;
font-weight: normal;
font-weight: bold;
}
&:hover {
border-right-width: 1px;
}
}
.el-table-column--selection .cell {
padding-right: 14px !important;
}
}
拖拽JS部分 sortable.js
import Sortable from "sortablejs";
export function rowDrop(than) {
// 存在固定和不固定的情况
const fixeDom =
than.$el.getElementsByClassName("el-table__fixed-body-wrapper")[0] || false; // 固定
const dom =
than.$el.getElementsByClassName("el-table__body-wrapper")[0] || false; // 未固定
// 存在固定行
if (than.fixedNumberColumns > 0) {
const el = fixeDom.querySelector("tbody");
if (el) {
Sortable.create(el, {
sort: true,
animation: 150,
ghostClass: "sortable-ghost",
// handle: ".table-row-drag",
// filter: ".cell", // 过滤器,不需要进行拖动的元素
// preventOnFilter: true, // 在触发过滤器`filter`的时候调用`event.preventDefault()`
onEnd: evt => {
// 下面将拖拽后的顺序进行修改
console.log(evt, "evt");
let index1 = evt.newIndex; // 新的下标
let index2 = evt.oldIndex; // 原来的下标
const obj = swapArrayObjects(than.tableData, index1, index2);
than.tableData = [];
than.$nextTick(() => {
than.tableData = obj.data;
});
than.$emit("sortableEnd", obj);
}
});
}
} else {
if (dom) {
const el = dom.querySelector("tbody");
if (el) {
Sortable.create(el, {
sort: true,
animation: 150,
ghostClass: "sortable-ghost",
// handle: ".table-row-drag",
// filter: ".cell", // 过滤器,不需要进行拖动的元素
// preventOnFilter: true, // 在触发过滤器`filter`的时候调用`event.preventDefault()`
onEnd: evt => {
// 下面将拖拽后的顺序进行修改
console.log(evt, "evt");
let index1 = evt.newIndex; // 新的下标
let index2 = evt.oldIndex; // 原来的下标
swapArrayObjects(than.tableData, index1, index2);
const obj = swapArrayObjects(than.tableData, index1, index2);
than.tableData = [];
than.$nextTick(() => {
than.tableData = obj.data;
});
than.$emit("sortableEnd", obj);
}
});
}
}
}
}
function swapArrayObjects(arr, index1, index2) {
// 创建一个新的数组对象,将原数组对象的元素添加到新数组中
let newArr = [...arr];
// 找到要交换的两个对象在原数组中的索引
let obj1 = newArr[index1]; // 新的下标
let obj2 = newArr[index2]; // 原来的下标
// 删除原来的位置的本体
newArr.splice(index2, 1);
// 在新下标插入
newArr.splice(index1, 0, obj2);
return {
row: obj2,
data: newArr
};
}
使用方法
HTML
<ElTable
:TabularData="TabularData"
@cellClick="cellClick"
@sortableEnd="sortableEnd"
ref="ElTable"
>
<!-- 表格头部左插槽 -->
<!-- <template slot="ft"></template> -->
<!-- 表格头部右插槽 -->
<!-- <template slot="ht"></template> -->
<!-- 操作列插槽 -->
<!-- <template slot="ActionButton" slot-scope="scope"></template> -->
<!-- 自定义追加列插槽 -->
<!-- 头部更多操作 -->
<!-- <template slot="columnHeader" slot-scope="scope"></template> -->
<!-- 内容 -->
<!-- <template slot="columnBody" slot-scope="scope"></template> -->
<!-- 自定义HTML -->
<!-- <template slot="Content" slot-scope="scope"></template> -->
</ElTable>
JS
// 开启拖拽需要安装
// npm install sortablejs --save
import ElTable from "@/components/ElTableColumn/index";
export default {
components: {
ElTable
},
data(){
return{
TabularData:{
customHtml: false, // 是否开启自定义html
customColumnShow:false,// 自定义追加列
isDrag:false,// 是否开启行拖拽
title: "", //标题
height:600,//高度
FirstCall: true, // 是否首次调用
// height: document.documentElement.clientHeight - 230, //高度
fieldName: false, //是否动态字段 fieldName + fieldExtend
isShowDynamicHeaders: false, //是否开启动态表头
headerHata: [], //固定表头数据
HeaderDataApi: null, // 表头方法
HeaderQuery: {}, // 表头参数
DataApi: null, // 数据方法
DataQuery: {}, // 数据参数
EditApi: null, // 编辑方法
EditQuery: {}, // 编辑参数
rowIdCode: "id", // 数据唯一标识字段 默认取id ,可编辑时需要配置对应字段
DeleteApi: null, // 删除方法
DeleteQuery: {}, // 删除参数
selection: false, // 是否开启勾选
titleShow: false, // 是否开启标题
updateNow: false, // 编辑时是否马上加载loading 需要提供前置编辑方法
showSummary: false, //是否开启合计
sumText: "合计", //合计文本
getSummaries: null, // 自定义合计方法
isEditFun: null, // 自定义编辑方法
isEdit: false, // 是否开启编辑
inputType: "input", // 可编辑组件类型input、textarea、select、number
selectOption: [], //组件类型为下拉框的时候需要传入数据
isAction: false, // 是否展示操作列
actionData: {
// 操作列配置
title: "操作",
width: 150,
fixed: "right"
},
fixedNumberColumns: 0, // 固定列数
frequencyTime: 500, // 刷新频率 防抖
paginationShow: true // 是否展示分页
};
}
},
methods:{
// 行点击
cellClick(row, column, cell, event) {
this.TabularData.isEditFun = this.isEditFun;
},
// 自定义是否可编辑 方法
isEditFun(row, column, cell, event) {
if (column.index && column.index == "1") return true;
return false;
},
// 行拖拽
sortableEnd(data){
console.log(data);
}
}
}