注:此功能改为独立模块直接拿代码就可以使用。
1、在 components 中创建 HeaderTable 文件夹,在创建 ColumnItem.vue 和 index.vue。
如下:
2、index.vue 代码内容,如下:
<template>
<div>
<div class="searchBar">
<el-form inline>
<el-form-item label="时间选择">
<el-date-picker
v-model="queryParams.dataTime"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择年份">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="resetSearchForm">重置</el-button>
</el-form-item>
<el-form-item>
<el-button @click="exportToExcel" type="warning">导出表格</el-button>
</el-form-item>
<el-form-item>
<el-button @click="handleEditBut(1)" type="primary" v-if="!showAddBut">编辑表头</el-button>
<el-button @click="handleEditBut('preserve')" type="primary" v-if="showAddBut">保存修改</el-button>
<el-button @click="handleEditBut('quit')" type="danger" v-if="showAddBut">退出修改</el-button>
</el-form-item>
</el-form>
</div>
<el-table
ref="table"
:data="dataTableData"
v-loading="vLoading"
style="width: 100%"
max-height="700"
:cell-style="{
'border-right': '1px solid #C4C6CB',
'border-bottom': '1px solid #C4C6CB',
}"
:header-cell-style="{
'background-color': '#f8f8f9',
'border-right': '1px solid #C4C6CB',
'border-bottom': '1px solid #C4C6CB',
}"
>
<el-table-column
prop="dataTime"
label="时间"
width="150"
align="center"
v-if="flag"
>
</el-table-column>
<!-- 递归组件 -->
<template v-if="flag">
<ColumnItem
v-for="(item,index) in dataHeaders"
:key="item.label"
:col="item"
:maxLength="maxLength"
:showAddBut="showAddBut"
>
</ColumnItem>
</template>
<el-table-column
prop=""
width="150"
align="center"
v-if="showAddBut && flag"
>
<template slot="header" slot-scope="scope">
<i class="el-icon-plus" @click="getAdd({})"/>
</template>
</el-table-column>
</el-table>
<el-drawer
:title="title"
:visible.sync="drawer"
:size="380"
direction="ltr"
:before-close="handleClose">
<el-form
:inline="true"
:model="column"
label-position="right"
ref="ruleFormRef"
:rules="rules"
class="demo-form-inline form-padding"
>
<el-form-item label="名称" prop="label">
<el-input v-model="column.label" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item label="宽度" prop="width">
<el-input
v-model="column.width"
type="number"
placeholder="请输入列宽度(不填为自适应)"
clearable
/>
</el-form-item>
<el-form-item label="对齐方式" prop="align">
<el-select
v-model="column.align"
placeholder="请选择对齐方式"
clearable
>
<el-option label="左对齐" value="left" />
<el-option label="居中对齐" value="center" />
<el-option label="右对齐" value="right" />
</el-select>
</el-form-item>
<el-form-item label="映射变量1" prop="prop">
<el-cascader
v-model="column.prop"
:options="options"
:props="optionProps"
@change="handleChange">
</el-cascader>
</el-form-item>
<el-form-item label="映射变量2" prop="propId" v-show="column.prop">
<el-select v-model="column.propId" placeholder="请选择">
<el-option
v-for="item in optionsId"
:key="item.id"
:label="item.equipName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div class="drawer-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="loading" @click="handleSubmit">
{{ submitText }}
</el-button>
</div>
</el-drawer>
</div>
</template>
<script>
import { EventBus } from "../../utils/event-bus.js";
import * as XLSX from "xlsx";
import ColumnItem from './ColumnItem.vue';
import { apiEditHeaderTable, apiGetList, apiGetTreeselect, listTableReport, listYearReport, listMonthReport, listDayReport } from "@/api/headerTable/index.js";
export default {
name: 'CustomElTable',
components: {
ColumnItem
},
props: {
// 区分哪个页面下的列表
tablekey: {
type: String,
required: true
},
// 限制新增最大列
maxLength: {
type: Number,
required: false
},
},
data() {
let today = new Date();
let todayString = `${today.getFullYear()}-${
today.getMonth() + 1
}-${today.getDate()}`;
return {
dataHeaders: [], // 表头数据
dataTableData: [], // 表格列表数据
// 查询参数
queryParams: {
dataTime: todayString,
ids: [],
},
vLoading: false,
showAddBut: false, // 控制按钮状态
flag: true, // 为了更新子组件的状态
drawer: false,
loading: false,
column: {
label: "",
width: "",
align: "center",
prop: "",
propId: "",
},
optionProps: {
expandTrigger: 'click',
value: "id",
label: "label",
children: "children",
},
copyCol: {},
rules: {
label: [{ required: true, message: "请输入名称", trigger: "blur" }],
align: [{ required: true, message: "请选择对齐方式", trigger: "blur" }],
prop: [{ required: true, message: "请选择映射变量1", trigger: ["blur","change"] }],
propId: [{ required: true, message: "请选择映射变量2", trigger: ["blur","change"] }],
},
title: '新增列',
submitText: '新增',
options: [],
optionsId: [],
}
},
methods: {
// 立即执行获取数据 搜索
getTableList() {
this.vLoading = true;
this.showTable = false;
listTableReport(this.tablekey).then(res => {
if(res.code == 200) {
let tableJson = res.data.tableJson ? JSON.parse(res.data.tableJson) : [];
console.log("tableJson",tableJson);
this.getForRecursion(tableJson);
this.dataHeaders = tableJson;
this.getList();
}
})
},
// 获取列表数据
async getList() {
console.log("this.queryParams",this.queryParams);
if(this.queryParams.ids) {
this.vLoading = true;
let data = {
dataTime: this.queryParams.dataTime,
ids: this.queryParams.ids.length == 0 ? [] : this.queryParams.ids
}
let res = null;
if(this.tablekey === 'year') {
res = await listYearReport(data);
}else if(this.tablekey === 'month') {
res = await listMonthReport(data);
}else {
res = await listDayReport(data);
}
this.dataTableData = res.data;
this.showTable = true;
this.vLoading = false;
}
},
// 递归遍历出 propId
getForRecursion(list) {
console.log("list",list);
if(list?.length > 0) {
list.map(item => {
if (item.children && item.children.length > 0) {
this.getForRecursion(item.children);
}else {
this.queryParams.ids.push(item.propId);
}
})
}else {
this.queryParams.ids = [];
}
},
// 请求接口,保存表头数据
handleSave() {
console.log("保存");
let data = {
tableKey: this.tablekey,
tableJson: JSON.stringify(this.dataHeaders)
}
apiEditHeaderTable(data).then(() => {
this.loading = false;
this.drawer = false;
this.$message({
message: '编辑成功',
type: 'success'
});
this.getTableList();
}).catch(() => {
this.loading = false;
this.drawer = false;
this.$message({
message: '编辑失败',
type: 'error'
});
})
},
// 重置
resetSearchForm() {
let today = new Date();
let todayString = `${today.getFullYear()}-${
today.getMonth() + 1
}-${today.getDate()}`;
this.queryParams.dataTime = todayString;
this.getList();
},
// 操作列表头
handleEditBut(val) {
this.showAddBut = !this.showAddBut;
if(val === 'preserve') {
this.handleSave();
}else if(val === 'quit') {
this.getTableList();
}
},
// 删除
getDelete(col) {
this.$confirm('是否删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(_ => {
let newHeaders = this.getRecursion(this.dataHeaders,col);
console.log("处理完数据的", newHeaders);
this.dataHeaders = newHeaders;
// 数据更新,更新子组件状态
this.flag = false;
this.$nextTick(() => {
this.flag = true;
})
// EventBus.$emit("getHeaders",this.headers)
}).catch(() => {});
},
// 递归删除
getRecursion(arr,col) {
arr.map((item,index) => {
if(item.label === col.label && item.deep === col.deep) {
arr.splice(index,1);
return;
}
if(item.children && item.children.length > 0) {
this.getRecursion(item.children,col)
}
})
return arr;
},
// 新增
getAdd(col) {
this.drawer = true;
this.title = '新增列';
this.submitText = '新增';
this.column.label = '新增列';
this.copyCol = col;
this.emptyColumn();
},
// 编辑
getEdit(col) {
console.log("col",col);
this.drawer = true;
this.title = '编辑列',
this.submitText = '保存',
this.column.label = col.label;
this.column.width = col.width;
this.column.align = col.align;
this.column.prop = col.prop.split(",").map(item => Number(item));
if(col?.prop) {
this.handleChange(this.column.prop);
}
console.log("this.column.prop",this.column.prop);
this.column.propId = col.propId;
this.copyCol = col;
},
// 编辑,新增里的取消
handleClose() {
this.$confirm('内容未保存,确认关闭?','提示',{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(( )=> {
this.emptyColumn();
this.drawer = false;
})
.catch(() => {});
},
// 编辑,新增里的保存
handleSubmit() {
this.$refs['ruleFormRef'].validate((valid) => {
if(!valid) {
return false;
}
console.log("this.column",this.column);
let col = this.copyCol
this.loading = true;
let item = {
label: this.column.label,
prop: this.column.prop.toString(),
propId: this.column.propId,
width: this.column.width,
align: this.column.align,
deep: 1,
selected: false,
children: []
}
if(this.title === '新增列') {
if(!Object.keys(col).length == 0){
item.deep = col.deep + 1;
if(!col.children) {
col.children = [];
}
col.children.push(item);
}else {
this.dataHeaders.push(item);
}
}else {
this.flag = false;
this.$nextTick(() => {
this.flag = true;
col.label = this.column.label;
col.width = this.column.width;
col.align = this.column.align;
col.prop = this.column.prop.toString();
col.propId = this.column.propId;
})
}
this.loading = false;
this.drawer = false;
})
},
// 清空 column 的数据
emptyColumn() {
this.column.width = "";
this.column.align = "center";
this.column.prop = "";
this.column.propId = "";
},
// 映射变量更改获取数据 ID
handleChange(val) {
let aa = val[val.length-1]
apiGetList({
deptId: aa
}).then(res => {
if(res.code == 200) {
this.optionsId = res.rows;
}
})
},
// 映射变量下拉数据
handleList() {
let data = {}
apiGetTreeselect(data).then((res) => {
this.options = res.data;
})
},
// 导出表格
exportToExcel() {
const table = this.$refs.table.$el;
const workbook = XLSX.utils.table_to_book(table, { raw: true });
const wbout = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
let tableName = '';
if(this.tablekey === 'year') {
tableName = '年报表';
}else if(this.tablekey === 'month') {
tableName = '月报表';
}else {
tableName = '日报表';
}
tableName ? tableName + ".xlsx" : "表格.xlsx";
this.downloadExcel(wbout, tableName);
},
downloadExcel(buffer, filename) {
const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
link.href = url;
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
},
},
mounted() {
console.log("this.tablekey",this.tablekey);
this.getTableList();
this.handleList();
// 接收子组件传递过来的事件
EventBus.$on("getDelete",(col) => {
this.getDelete(col) // 删除
});
EventBus.$on("getAdd",(col) => {
this.getAdd(col) // 新增
});
EventBus.$on("getEdit",(col) => {
this.getEdit(col) // 编辑
});
}
}
</script>
<style lang="scss" scoped>
.searchBar {
padding: 15px;
padding-bottom: 0;
}
.scope_p {
margin: 0;
}
.i_margin {
margin: 0 10px;
}
.content-main {
.table-title {
display: flex;
justify-content: space-around;
margin-bottom: 10px;
> p {
flex: 1;
font-weight: bold;
font-size: 18px;
}
}
.el-input {
width: 250px;
}
.el-select {
width: 280px;
}
}
.form-padding {
padding: 0 20px;
::v-deep .el-form-item__label {
width: 88px;
}
}
.drawer-footer {
display: flex;
align-items: center;
justify-content: center;
}
.header-btn {
display: flex;
align-items: center;
justify-content: center;
.el-icon-plus {
margin-left: 10px;
cursor: pointer;
}
.el-icon-edit {
margin-left: 10px;
cursor: pointer;
}
.el-icon-delete {
margin-left: 10px;
cursor: pointer;
}
}
.selected {
color: #409eff;
}
.header-scroll::v-deep {
.el-table__header-wrapper {
overflow-x: auto;
}
/* 修改滚动条样式 */
::-webkit-scrollbar {
height: 8px; /* 设置滚动条宽度 */
}
::-webkit-scrollbar-track {
background-color: #ffffff; /* 设置滚动条轨道背景色 */
}
::-webkit-scrollbar-thumb {
background-color: #ececec; /* 设置滚动条滑块颜色 */
border-radius: 4px; /* 设置滚动条滑块圆角 */
}
::-webkit-scrollbar-thumb:hover {
background-color: #e2e2e2; /* 设置滚动条滑块鼠标悬停时颜色 */
}
}
.error_label {
color: #f56c6c !important;
}
</style>
3、ColumnItem.vue 代码内容,如下:
<template>
<el-table-column
:prop="`column_${col.propId}`"
:width="col.width"
:min-width="col.width ? '' : '100'"
:align="col.align"
min-width="150"
>
<template slot="header" slot-scope="scope">
<p class="scope_p">{{col.label}}</p>
<div v-if="showAddBut">
<i class="el-icon-plus" v-if="col.deep < maxLength" @click="Add(col)"/>
<i class="el-icon-edit-outline i_margin" @click="Edit(col)"/>
<i class="el-icon-delete" @click="Delete(col)"/>
</div>
</template>
<!-- 遍历递归 -->
<template v-for="(item,index) in col.children">
<ColumnItem v-if="item.children" :col="item" :key="`${item.label}-${item.propId}`" :maxLength="maxLength" :showAddBut="showAddBut"/>
</template>
</el-table-column>
</template>
<script>
import { EventBus } from "../../utils/event-bus.js";
export default {
name: "ColumnItem",
props: {
col: {
type: Object,
required: true
},
maxLength: {
type: Number,
required: false
},
showAddBut: {
type: Boolean,
},
},
methods: {
Add(col) {
EventBus.$emit("getAdd",col)
},
Edit(col) {
EventBus.$emit("getEdit",col)
},
Delete(col) {
console.log(col);
EventBus.$emit("getDelete",col)
}
},
mounted() {
},
}
</script>
<style lang="scss" scoped>
.scope_p {
margin: 0;
}
i {
cursor: pointer;
}
.i_margin {
margin: 0 10px;
}
</style>
4、在 .vue 文件中使用和数据,如下:
<HeaderTable tablekey="year" :maxLength="4" />
5、效果图,如下:
6、引入事件总线 EventBus
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()