动态文件夹+动态表格+表单(DynaActionForm)
使用技术 :Vue2 + element UI
01动态文件夹
过滤查询文件夹,动态添加文件夹,可修改文件名,添加子文件夹,删除文件夹
template
<el-input placeholder="输入关键字进行过滤" v-model="filterText">
</el-input>
<button class="icon-btn add-btn" @click="handlerAppend">
<div class="add-icon"></div>
<div class="btn-txt">添加 文件</div>
</button>
<el-tree class="filter-tree" :data="treeList" show-checkbox :props="defaultProps" default-expand-all
highlight-current :expand-on-click-node="showTree" @node-click="handleNodeClick" :filter-node-method="filterNode"
@check-change="handleCheckChange" ref="tree">
<template slot-scope="{ node, data }">
<div class="flex-row treeSortList">
<el-input v-model="data.label" placeholder="请输入文件名称" v-if="showEdit[data.id]" size="mini"></el-input>
<el-tooltip v-else class="item" effect="light" content="点击进行操作" placement="top">
<span> {{ node.label }} </span>
</el-tooltip>
<div class="doBtn" v-if="showEdit[data.id]">
<el-button type="text" size="mini" title="保存修改" @click.stop="() => save(node, data)">保存
</el-button>
<el-button type="text" size="mini" title="取消修改" @click.stop="() => cancel(node, data)">取消
</el-button>
</div>
<div class="doBtn" v-if="showBtn[data.id] && !showEdit[data.id]">
<el-tooltip class="item" effect="light" content="添加子项目" placement="top">
<el-button type="text" size="mini" title="添加" @click.stop="() => append(data)" icon="el-icon-plus">
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="light" content="编辑文件名" placement="top">
<el-button type="text" size="mini" title="编辑" @click.stop="() => edit(data)" icon="el-icon-edit-outline">
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="light" content="删除文件" placement="top">
<el-button type="text" size="mini" title="删除" @click.stop="() => remove(node, data)"
icon="el-icon-delete">
</el-button>
</el-tooltip>
</div>
</div>
</template>
</el-tree>
css
::v-deep .el-tree-node__content {
padding: 10px 0;
height: auto;
}
.treeSortList {
width: calc(100% - 30px);
display: flex;
align-items: center;
justify-content: space-between;
}
.treeSortList .doBtn {
padding-right: 10px;
}
.treeSortList .doBtn .el-button--text {
color: rgb(61, 61, 61);
padding: 5px;
}
.treeSortList .doBtn .el-button--text i {
color: rgb(80, 80, 80);
}
.treeSortList .doBtn .el-button--text i.el-icon-delete {
color: #f3a68e;
}
js
data(){
return{
filterText: "",
treeList: [],
defaultProps: {
children: "children",
label: "label",
},
showTree: false, //是否点击节点展开树,false 只能点前面三角图标展开
showBtn: [],
showEdit: [],
editData: [],
newAddData: false,
}
}
watch: {
filterText(val) {
this.$refs.tree.filter(val);
},
},
methods:{
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
handleCheckChange(data, checked, indeterminate) {
console.log(data, checked, indeterminate);
},
handlerAppend() {
let i = 0;
if (this.data) {
const newChild = {
id: id++,
label: "文件" + (Number(this.data.length) + 1),
children: [],
};
this.treeList.push(newChild);
} else {
const newChild = {
id: id++,
label: "文件" + (i + 1),
children: [],
};
this.treeList.push(newChild);
}
this.$message({
message: "添加成功,开始编写吧~",
type: "success",
});
},
//点击树节点
handleNodeClick(data) {
if (!this.ifEdit()) {
return;
}
this.showBtn = [];
this.$set(this.showBtn, data.id, true);
},
append(data) {
if (!this.ifEdit()) {
return;
}
const newChild = { id: id++, label: "", children: [] };
if (!data.children) {
this.$set(data, "children", []);
}
data.children.push(newChild);
this.newAddData = true;
this.$set(this.showEdit, newChild.id, true);
},
edit(data) {
var localEdit = localStorage.getItem("treeEdit");
var id = data.id;
var newlocalData = { [data.id]: data.label };
if (localEdit) {
var localData = JSON.parse(localEdit);
Object.assign(localData, newlocalData);
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData);
} else {
newlocalData = JSON.stringify(newlocalData);
localStorage.setItem("treeEdit", newlocalData);
}
// console.log(localStorage.getItem("treeEdit"));
this.showEdit = [];
this.$set(this.showEdit, data.id, true);
},
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
},
//保存
save(node, data) {
if (data.label == "") {
this.$message({
type: "error",
message: "请输入完整数据",
offset: 70,
});
return;
}
this.newAddData = "";
var localEdit = localStorage.getItem("treeEdit");
if (localEdit) {
var localData = JSON.parse(localEdit);
delete localData[data.id]; //删除已经取消项
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData); //重置缓存
}
this.$set(this.showEdit, data.id, false);
},
cancel(node, data) {
if (this.newAddData) {
this.remove(node, data);
}
var localEdit = localStorage.getItem("treeEdit");
if (localEdit) {
var localData = JSON.parse(localEdit);
data.label = localData[data.id];
delete localData[data.id]; //删除已经取消项
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData); //重置缓存
}
this.$set(this.showEdit, data.id, false);
},
//判断是否有编辑或增加的项
ifEdit() {
if (this.showEdit.indexOf(true) != -1) {
this.$message({
type: "error",
message: "先保存正在编辑的行",
offset: 70,
});
return false;
} else {
return true;
}
},
}
效果展示
02动态表格 - Table/DynamicTable.vue
参考网址:blog.csdn.net/coralime/ar…
02-1使用展示
- tableHeader 表头的数据
- tableData 表格的数据
- height 表格的高度
- isSelection 是否添加勾选
- isIndex 是否需要添加序号列
- loading 加载
<DynamicTable :tableHeader="tableHeader" :tableData="tableData" :isIndex="false" :isSelection="true"
@selection-change="handlerSelectionChange" :loading="loading" />
tableHeader 表头的数据
--普通模式
tableHeader: [
{
label: '姓名',
prop: 'name',
module: 'text',
show: true
}, {
label: '地址',
prop: 'address',
module: 'text',
show: true,
}
],
--多级表头
tableHeader: [
{
label: '姓名',
prop: 'name',
module: 'text',
show: true
}, {
label: '地址',
prop: 'address',
module: 'text',
show: true,
children: [
{
label: '地址1',
prop: 'address1',
module: 'text',
show: true,
children: [
{
label: '地址1',
prop: 'address1',
module: 'text',
show: true,
},
{
label: '地址2',
prop: 'address2',
module: 'text',
show: true,
}
]
},
{
label: '地址2',
prop: 'address2',
module: 'text',
show: true,
}
]
}
],
02-2动态表文件 - componebts/Table/DynamicTable.vue
<template>
<!-- 动态展示表格 -->
<el-table :key="tableRefresh" id="multipleTable" ref="multipleTable" v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)" :data="tableData" border stripe :height="height"
@row-click="handleRowClick" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" v-if="isSelection" align="center">
</el-table-column>
<el-table-column v-if="isIndex" type="index" width="100" label="序号" :index="hIndex" align="center" />
<!-- v-for 循环取表头数据 -->
<template v-for="(item, index) in bindTableColumns">
<table-column v-if="item.children && item.children.length" :key="index" :column-header="item" />
<el-table-column v-else :key="index" :label="item.label" :prop="item.prop" align="center" />
</template>
<!-- <el-table-column prop="onlineStatus" label="操作" width="140" align="center">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="info" size="small" icon="el-icon-document">详情</el-button>
</template>
</el-table-column> -->
</el-table>
</template>
<script>
import TableColumn from '@/components/Table/TableColumn'
export default {
name: 'DynamicTable',
components: {
TableColumn
},
props: {
// 表格的数据
tableData: {
type: Array,
required: true
},
// 多级表头的数据
tableHeader: {
type: Array,
required: true
},
// 表格的高度
height: {
type: String,
default: '300'
},
// 是否需要添加序号列
isIndex: {
type: Boolean
},
// 是否添加勾选
isSelection: {
type: Boolean
},
loading: {
type: Boolean,
default: false
}
},
data() {
return {
tableRefresh: 0
}
},
computed: {
bindTableColumns() {
this.tableRefresh++
return this.tableHeader.filter((column) => column.show);
},
},
methods: {
// 详情
handleClick(row) {
this.$emit('json-click', row)
//数据是string类型的需要用到JSON.parse(object)将string类型转换为JSON类型
//row.jsonData的jsonData是后台接口数据所提供的,this.jsonData是容器,用来实现数据绑定显示的:value="jsonData"
},
// 行点击事件
handleRowClick(row, column, event) {
// 通知调用父组件的row-click事件
// row作为参数传递过去
this.$emit('row-click', row)
},
handleSelectionChange(val) {
this.$emit('selection-change', val)
},
hIndex(index) {
// index索引从零开始,index +1即为当前数据序号
this.$options.parent.queryParams.pageNum <= 0
? (this.$options.parent.queryParams.pageNum = 1)
: this.$options.parent.queryParams.pageNum;
// 如果当前不是第一页数据
if (this.$options.parent.queryParams.pageNum != 1) {
// index + 1 + (( 当前页 - 1) * 每页展示条数)
// 比如是第二页数据 1 + ((2 - 1)*5) = 6,第二页数据也就是从序号6开始
return (
index + 1 + (this.$options.parent.queryParams.pageNum - 1) * this.$options.parent.queryParams.pageSize
);
}
// 否则直接返回索引+1作为序号
return index + 1;
},
}
}
</script>
复杂表头文件 - componebts/Table/TableColumn.vue
<template>
<el-table-column
:label="columnHeader.label"
align="center"
>
<!--columnHeader对应:column-header-->
<template v-for="(item,index) in columnHeader.children">
<tableColumn
v-if="item.children && item.children.length"
:key="index"
:column-header="item"
/>
<el-table-column
v-else
:key="index"
:label="item.label"
:prop="item.prop"
align="center"
/>
</template>
</el-table-column>
</template>
<script>
export default {
name: 'tableColumn',
props: {
columnHeader: {
type: Object,
required: true
}
},
}
</script>
<style scoped>
</style>
03动态表单
<template>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="25%">
<el-form :model="form" ref="ruleForm">
<el-form-item v-for="(item, index) in headerData" :key="index" :label="item.label">
<el-input v-if="item.module == 'text'" :placeholder="`请输入${item.label}`" v-model="form[item.prop]"
autocomplete="off"></el-input>
<el-input v-if="item.module == 'password'" :placeholder="`请输入${item.label}`" v-model="form[item.prop]"
show-password></el-input>
<el-input v-if="item.module == 'textarea'" :placeholder="`请输入${item.label}`" type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }" v-model="form[item.prop]">
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm('ruleForm')">取 消</el-button>
<el-button type="primary" @click="submitForm('ruleForm')">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
parentThat: {}
},
data() {
return {
// 表格添加
title: '',
dialogFormVisible: false,
form: {},
headerData: null,
editKeys: null,
}
},
mounted() {
},
methods: {
init(title, headerData, keys) {
this.dialogFormVisible = true;
this.title = title
this.headerData = headerData
this.editKeys = null
headerData.forEach(item => {
this.form = {
[item.prop]: ''
}
})
if (keys) {
this.editKeys = keys
this.form = JSON.parse(JSON.stringify(keys))
}
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.editKeys) {
this.$emit('handlerConfirm', this.form, this.editKeys)
} else {
this.$emit('handlerConfirm', this.form)
}
this.parentThat.$refs.TableList.$refs.multipleTable.clearSelection()
this.form = this.$options.data.call(this).form
this.dialogFormVisible = false;
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.form = this.$options.data.call(this).form
this.dialogFormVisible = false;
this.$refs[formName].resetFields();
},
}
}
</script>
<style></style>
导入+导出 -- 前端版
下载
npm install -S file-saver xlsx
npm install -D script-loader
npm install xlsx
main.js
import * as XLSX from 'xlsx'
Vue.use(XLSX)
上传获取数据参考网址:blog.csdn.net/weixin_4528…
导入导出参考网址:www.jianshu.com/p/02d615d60…
多级表头导出参考网址:blog.csdn.net/weixin_4268…
01导入
<el-button type="info" icon="el-icon-upload2" size="mini" @click="handlerLeadingIn" plain>导入</el-button>
// 导入表格
handlerLeadingIn() {
this.$refs.LeadingIn.init('导入', this.tableHeader);
},
//新建文件 - LeadingIn.vue
import LeadingIn from "./LeadingIn.vue"
<template>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="400px">
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :action="upload.url" :on-change="handleChange"
:auto-upload="false" drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
<div><el-checkbox v-model="upload.isUploading" />是否更新已经存在的数据</div>
<div>仅允许导入xls、xlsx格式文件。<a style="color:dodgerblue;" @click="importTemplate">下载模板</a> </div>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm('uploadRef')">确 定</el-button>
<el-button @click="resetForm('uploadRef')">取 消</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
parentThat: {},
},
data() {
return {
// 表格添加
title: '',
dialogFormVisible: false,
upload: {
// 是否更新已数据
isUploading: false,
// 上传的地址
url: process.env.VUE_APP_BASE_API +
"/efarmcloud-open-file/api/v1/files/upload"
},
XlsxData: [],
fileContent: null,
headerTable: [],
}
},
methods: {
init(title, headerTable) {
this.dialogFormVisible = true;
this.title = title
this.headerTable = headerTable
},
setDesc() {
// JS-获取到26个英文大写字母(A-Z)
const letterArr = []
Array(26).fill('').map((item, index) => {
letterArr.push(String.fromCharCode(index + 65))
})
return letterArr
},
/** 下载模板操作 多级表头和单个表头 表头测试只有2级一次类推 */
importTemplate() {
let tHeader = [], multiHeader = [], header1 = [], filterPop = [], merges = [], merges1 = [], merges3 = [], merges2 = [], headerRowLength = 1;
// this.handleExcel('user_')
// 有69个元素是空的,所以直接进行了截取
let flag = this.headerTable.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
if (flag) {
const randomAbc = this.setDesc()
this.headerTable.map((column, i) => {
if (column.children && column.children.length > 0) {
headerRowLength++
header1.push(column.label)
column.children.map((childColumn, j) => {
merges2.push(randomAbc[j] + headerRowLength)
tHeader.push(childColumn.label)
filterPop.push(childColumn.prop)
header1.push('')
})
} else {
merges1.push(randomAbc[i] + headerRowLength)
header1.push(column.label)
tHeader.push('')
filterPop.push(column.prop)
}
})
// 二维数组依次递增 表格头部依次递进 // tHeader表格头部最后一级 merges合并列或行编号自行调节
multiHeader.push(header1)
// 合并的行
merges1.forEach(i => {
merges2.forEach(j => {
if (i.substring(0, 1) == j.substring(0, 1)) {
merges.push(i + ':' + j)
}
})
})
// 合并的列
let len = merges1.length
tHeader.forEach((item, o) => {
merges3.push(randomAbc[o])
})
merges3.splice(0, len)
merges.push(merges3.slice(0, 1)[0] + (headerRowLength - 1) + ':' + merges3.slice(-1)[0] + (headerRowLength - 1))
this.getXlsxData_many(tHeader, multiHeader, filterPop, merges)
} else {
this.headerTable.map((column, i) => {
tHeader.push(column.label)
filterPop.push(column.prop)
})
this.getXlsxData(tHeader, filterPop)
}
},
// 多行表头下载
getXlsxData_many(tHeader, multiHeader, filterPop, merges, dataT = []) {
require.ensure([], () => {
const { export_json_to_excel_headerMany } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
const data = this.formatJson(filterPop, dataT);//格式化
// console.log(tHeader, multiHeader, filterPop, merges, dataT);
//这个得说明一下:网上得博客每个不一样,你那我的直接用也是没啥用得,你的理解这个合并是怎么写的:根据你的多级表头,如果没有合并得从上往下写,遇到开始合并单元格的,从左往右得单行写,从上到下,直到写完整
export_json_to_excel_headerMany({
multiHeader,
header: tHeader,
data,
filename: `user_${new Date().getTime()}`, merges,
autoWidth: true,
})
})
},
// 单行表头下载
getXlsxData(tHeader = [], filterVal = [], dataT = []) {
require.ensure([], () => {
const { export_json_to_excel } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
// const tHeader = []; //对应表格输出的title
// const filterVal = []; // 对应表格输出的数据
const data = this.formatJson(filterVal, dataT);
console.log(tHeader, data, filterVal, '下载');
export_json_to_excel(tHeader, data, `user_${new Date().getTime()}`); //对应下载文件的名字
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
// 核心部分代码(handleChange 和 importfile)
handleChange(file) {
this.fileContent = file.raw
const fileName = file.name
const fileType = fileName.substring(fileName.lastIndexOf('.') + 1)
if (this.fileContent) {
if (fileType === 'xlsx' || fileType === 'xls') {
// 判断是否有子项
let flag = this.headerTable.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
this.loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (flag) {
this.importFile(this.fileContent, flag)
this.loading.close();
} else {
this.importFile(this.fileContent, flag)
this.loading.close();
}
} else {
this.$message({
type: 'warning',
message: '附件格式错误,请重新上传!'
})
}
} else {
this.$message({
type: 'warning',
message: '请上传附件!'
})
}
},
importfile(obj) {
const reader = new FileReader()
const _this = this
reader.readAsArrayBuffer(obj)
reader.onload = function () {
const buffer = reader.result
const bytes = new Uint8Array(buffer)
const length = bytes.byteLength
let binary = ''
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i])
}
const XLSX = require('xlsx')
const wb = XLSX.read(binary, {
type: 'binary'
})
const outData = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]])
const arr = [...outData], newArr = []
arr.map((v) => {
//***** 重点重点数据 list ******
let list = _this.headerTable.map((s, i) => {
let key = Object.keys(v)[i]
if (key == s.label) {
return { id: Math.random(),[s.prop]: v[key] }
}
})
newArr.push(Object.assign(...list))
//表头数据多或数据不齐全请使用一对一获取 如:
// newArr.push({
// group: v.组别,
// farmer_name: v.农户姓名,
// ID_number: v.身份证号,
// age: v.年龄,
// phone: v.电话,
// remark: v.备注,
// public_security_system_list: v.公安系统名单中无此人原因,
// })
})
_this.XlsxData = newArr
}
},
submitForm(uploadRef) {
// console.log(this.XlsxData, this.parentThat._data.tableData, this.upload.isUploading);
if (this.upload.isUploading) {
this.parentThat._data.tableData = this.XlsxData
} else {
this.parentThat._data.tableData = this.parentThat._data.tableData.concat(this.XlsxData)
}
this.parentThat._data.total = this.parentThat._data.tableData.length //数量
this.dialogFormVisible = false;
this.$refs[uploadRef].clearFiles();
},
resetForm(uploadRef) {
this.dialogFormVisible = false;
this.$refs[uploadRef].clearFiles();
},
}
}
</script>
<style lang="scss" scoped>
.el-upload__tip {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
02导出
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" plain>导出</el-button>
setDesc() {
// JS-获取到26个英文大写字母(A-Z)
const letterArr = []
Array(26).fill('').map((item, index) => {
letterArr.push(String.fromCharCode(index + 65))
})
return letterArr
},
/** 导出按钮操作 */
handleExport() {
let tHeader = [], multiHeader = [], header1 = [], filterPop = [], merges = [], merges1 = [], merges3 = [], merges2 = [], headerRowLength = 1;
let flag = this.tableHeader.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
if (flag) {
const randomAbc = this.setDesc()
this.headerTable.map((column, i) => {
if (column.show) {
if (column.children && column.children.length > 0) {
headerRowLength++
header1.push(column.label)
column.children.map((childColumn, j) => {
merges2.push(randomAbc[j] + headerRowLength)
tHeader.push(childColumn.label)
filterPop.push(childColumn.prop)
header1.push('')
})
} else {
merges1.push(randomAbc[i] + headerRowLength)
header1.push(column.label)
tHeader.push('')
filterPop.push(column.prop)
}
}
})
// 二维数组依次递增 表格头部 // tHeader 表格头部最后一级
multiHeader.push(header1)
// 合并的行
merges1.forEach(i => {
merges2.forEach(j => {
if (i.substring(0, 1) == j.substring(0, 1)) {
merges.push(i + ':' + j)
}
})
})
// 合并的列
let len = merges1.length
tHeader.forEach((item, o) => {
merges3.push(randomAbc[o])
})
merges3.splice(0, len)
merges.push(merges3.slice(0, 1)[0] + (headerRowLength - 1) + ':' + merges3.slice(-1)[0] + (headerRowLength - 1))
this.$confirm('确定导出全部数据么?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 确定
this.getXlsxData_many(tHeader, multiHeader, filterPop, merges, this.tableData)
this.$message({
showClose: true,
message: '下载成功'
});
})
.catch(() => {
// 取消
this.$message({
showClose: true,
message: '取消下载'
});
})
} else {
this.tableHeader.map((column, i) => {
if (column.show) {
tHeader.push(column.label)
filterPop.push(column.prop)
}
})
this.$confirm('确定导出全部数据么?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 确定
this.getXlsxData(tHeader, filterPop, this.tableData)
this.$message({
showClose: true,
message: '下载成功'
});
})
.catch(() => {
// 取消
this.$message({
showClose: true,
message: '取消下载'
});
})
}
},
// 多行表头下载
getXlsxData_many(tHeader, multiHeader, filterPop, merges, dataT = []) {
require.ensure([], () => {
const { export_json_to_excel_headerMany } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
const data = this.formatJson(filterPop, dataT);//格式化
// console.log(tHeader, multiHeader, filterPop, merges, dataT);
//这个得说明一下:网上得博客每个不一样,你那我的直接用也是没啥用得,你的理解这个合并是怎么写的:根据你的多级表头,如果没有合并得从上往下写,遇到开始合并单元格的,从左往右得单行写,从上到下,直到写完整
export_json_to_excel_headerMany({
multiHeader,
header: tHeader,
data,
filename: `user_${new Date().getTime()}`, merges,
autoWidth: true,
})
})
},
// 单行表头下载
getXlsxData(tHeader = [], filterVal = [], dataT = []) {
require.ensure([], () => {
const { export_json_to_excel } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
// const tHeader = []; //对应表格输出的title
// const filterVal = []; // 对应表格输出的数据
const data = this.formatJson(filterVal, dataT);
export_json_to_excel(tHeader, data, `user_${new Date().getTime()}`); //对应下载文件的名字
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
execl/Export2Excel.js
/* eslint-disable */
require('script-loader!file-saver');
require('./Blob.js');
require('script-loader!xlsx/dist/xlsx.core.min');
function generateArray(table) {
var out = [];
var rows = table.querySelectorAll('tr');
var ranges = [];
for (var R = 0; R < rows.length; ++R) {
var outRow = [];
var row = rows[R];
var columns = row.querySelectorAll('td');
for (var C = 0; C < columns.length; ++C) {
var cell = columns[C];
var colspan = cell.getAttribute('colspan');
var rowspan = cell.getAttribute('rowspan');
var cellValue = cell.innerText;
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
//Skip ranges
ranges.forEach(function (range) {
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
}
});
//Handle Row Span
if (rowspan || colspan) {
rowspan = rowspan || 1;
colspan = colspan || 1;
ranges.push({
s: {
r: R,
c: outRow.length
},
e: {
r: R + rowspan - 1,
c: outRow.length + colspan - 1
}
});
};
//Handle Value
outRow.push(cellValue !== "" ? cellValue : null);
//Handle Colspan
if (colspan)
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
}
out.push(outRow);
}
return [out, ranges];
};
function datenum(v, date1904) {
if (date1904) v += 1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
};
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R;
if (range.s.c > C) range.s.c = C;
if (range.e.r < R) range.e.r = R;
if (range.e.c < C) range.e.c = C;
var cell = {
v: data[R][C]
};
if (cell.v == null) continue;
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
});
if (typeof cell.v === 'number') cell.t = 'n';
else if (typeof cell.v === 'boolean') cell.t = 'b';
else if (cell.v instanceof Date) {
cell.t = 'n';
cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
} else cell.t = 's';
ws[cell_ref] = cell;
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
export function export_table_to_excel(id) {
var theTable = document.getElementById(id);
var oo = generateArray(theTable);
var ranges = oo[1];
/* original data */
var data = oo[0];
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
/* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges;
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), "test.xlsx")
}
function formatJson(jsonData) {
console.log(jsonData)
}
export function export_json_to_excel(th, jsonData, defaultTitle) {
/* original data */
var data = jsonData;
data.unshift(th);
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
var title = defaultTitle || '列表'
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), title + ".xlsx")
}
export function export_json_to_excel_headerMany({
multiHeader = [], // 第二行表头
header,
data,
filename, //文件名
merges = [], // 合并
autoWidth = true,
bookType = 'xlsx'
} = {}) {
filename = filename || '列表';
data = [...data]
data.unshift(header);
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader [i])
}
console.log(data);
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = [];
merges.forEach(item => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
'wch': 10
};
}
/*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return {
'wch': val.toString().length * 2
};
} else {
return {
'wch': val.toString().length
};
}
}))
/*以第一行为初始值*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
}
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), `${filename}.${bookType}`);
}
execl/Blob.js
(function (view) {
"use strict";
view.URL = view.URL || view.webkitURL;
if (view.Blob && view.URL) {
try {
new Blob;
return;
} catch (e) {}
}
// Internally we use a BlobBuilder implementation to base Blob off of
// in order to support older browsers that only have BlobBuilder
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
var
get_class = function(object) {
return Object.prototype.toString.call(object).match(/^[object\s(.*)]$/)[1];
}
, FakeBlobBuilder = function BlobBuilder() {
this.data = [];
}
, FakeBlob = function Blob(data, type, encoding) {
this.data = data;
this.size = data.length;
this.type = type;
this.encoding = encoding;
}
, FBB_proto = FakeBlobBuilder.prototype
, FB_proto = FakeBlob.prototype
, FileReaderSync = view.FileReaderSync
, FileException = function(type) {
this.code = this[this.name = type];
}
, file_ex_codes = (
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
).split(" ")
, file_ex_code = file_ex_codes.length
, real_URL = view.URL || view.webkitURL || view
, real_create_object_URL = real_URL.createObjectURL
, real_revoke_object_URL = real_URL.revokeObjectURL
, URL = real_URL
, btoa = view.btoa
, atob = view.atob
, ArrayBuffer = view.ArrayBuffer
, Uint8Array = view.Uint8Array
;
FakeBlob.fake = FB_proto.fake = true;
while (file_ex_code--) {
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
}
if (!real_URL.createObjectURL) {
URL = view.URL = {};
}
URL.createObjectURL = function(blob) {
var
type = blob.type
, data_URI_header
;
if (type === null) {
type = "application/octet-stream";
}
if (blob instanceof FakeBlob) {
data_URI_header = "data:" + type;
if (blob.encoding === "base64") {
return data_URI_header + ";base64," + blob.data;
} else if (blob.encoding === "URI") {
return data_URI_header + "," + decodeURIComponent(blob.data);
} if (btoa) {
return data_URI_header + ";base64," + btoa(blob.data);
} else {
return data_URI_header + "," + encodeURIComponent(blob.data);
}
} else if (real_create_object_URL) {
return real_create_object_URL.call(real_URL, blob);
}
};
URL.revokeObjectURL = function(object_URL) {
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
real_revoke_object_URL.call(real_URL, object_URL);
}
};
FBB_proto.append = function(data/*, endings*/) {
var bb = this.data;
// decode data to a binary string
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
var
str = ""
, buf = new Uint8Array(data)
, i = 0
, buf_len = buf.length
;
for (; i < buf_len; i++) {
str += String.fromCharCode(buf[i]);
}
bb.push(str);
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
if (FileReaderSync) {
var fr = new FileReaderSync;
bb.push(fr.readAsBinaryString(data));
} else {
// async FileReader won't work as BlobBuilder is sync
throw new FileException("NOT_READABLE_ERR");
}
} else if (data instanceof FakeBlob) {
if (data.encoding === "base64" && atob) {
bb.push(atob(data.data));
} else if (data.encoding === "URI") {
bb.push(decodeURIComponent(data.data));
} else if (data.encoding === "raw") {
bb.push(data.data);
}
} else {
if (typeof data !== "string") {
data += ""; // convert unsupported types to strings
}
// decode UTF-16 to binary string
bb.push(unescape(encodeURIComponent(data)));
}
};
FBB_proto.getBlob = function(type) {
if (!arguments.length) {
type = null;
}
return new FakeBlob(this.data.join(""), type, "raw");
};
FBB_proto.toString = function() {
return "[object BlobBuilder]";
};
FB_proto.slice = function(start, end, type) {
var args = arguments.length;
if (args < 3) {
type = null;
}
return new FakeBlob(
this.data.slice(start, args > 1 ? end : this.data.length)
, type
, this.encoding
);
};
FB_proto.toString = function() {
return "[object Blob]";
};
FB_proto.close = function() {
this.size = this.data.length = 0;
};
return FakeBlobBuilder;
}(view));
view.Blob = function Blob(blobParts, options) {
var type = options ? (options.type || "") : "";
var builder = new BlobBuilder();
if (blobParts) {
for (var i = 0, len = blobParts.length; i < len; i++) {
builder.append(blobParts[i]);
}
}
return builder.getBlob(type);
};
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
最终效效果展示
普通表头
多级表头
终极代码
<template>
<div class="container">
<div class="left-con">
<el-input placeholder="输入关键字进行过滤" v-model="filterText">
</el-input>
<button class="icon-btn add-btn" @click="handlerAppend">
<div class="add-icon"></div>
<div class="btn-txt">添加 文件</div>
</button>
<el-tree class="filter-tree" :data="treeList" :props="defaultProps" default-expand-all highlight-current
:expand-on-click-node="showTree" @node-click="handleNodeClick" :filter-node-method="filterNode" ref="tree">
<template slot-scope="{ node, data }">
<div class="flex-row treeSortList">
<el-input v-model="data.label" placeholder="请输入文件名称" v-if="showEdit[data.id]" size="mini"></el-input>
<el-tooltip v-else class="item" effect="light" content="点击进行操作" placement="top">
<span> {{ node.label }} </span>
</el-tooltip>
<div class="doBtn" v-if="showEdit[data.id]">
<el-button type="text" size="mini" title="保存修改" @click.stop="() => save(node, data)">保存
</el-button>
<el-button type="text" size="mini" title="取消修改" @click.stop="() => cancel(node, data)">取消
</el-button>
</div>
<div class="doBtn" v-if="showBtn[data.id] && !showEdit[data.id]">
<el-tooltip class="item" effect="light" content="添加子项目" placement="top">
<el-button type="text" size="mini" title="添加" @click.stop="() => append(data)" icon="el-icon-plus">
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="light" content="编辑文件名" placement="top">
<el-button type="text" size="mini" title="编辑" @click.stop="() => edit(data)" icon="el-icon-edit-outline">
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="light" content="删除文件" placement="top">
<el-button type="text" size="mini" title="删除" @click.stop="() => remove(node, data)"
icon="el-icon-delete">
</el-button>
</el-tooltip>
</div>
</div>
</template>
</el-tree>
</div>
<div class="right-con">
<div class="header-con">
<el-popover placement="right" width="600" v-model="visible">
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="字段(label)" prop="label">
<el-input type="text" v-model="ruleForm.label" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="索引(prop)" prop="props">
<el-input type="text" v-model="ruleForm.props" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="类型" :required="true">
<el-select v-model="ruleForm.value" placeholder="请选择" style="width:100%" @change="changeModule">
<el-option v-for="item in modules" :key="item.value" :label="item.label" :value="item.value">
<span style="float: left">{{ item.label }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="是否有子项">
<el-radio-group v-model="ruleForm.radio" @input="inputChildren">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
<el-button style="margin-left:30px;" v-if="ruleForm.radio" @click="addDomain">新增子项</el-button>
</el-form-item>
<el-form-item label="子项" v-if="ruleForm.childrenArr.length != 0" :required="true">
<div style="display:flex;" v-for="(domain, index) in ruleForm.childrenArr" :key="domain.key">
<el-input placeholder="字段(label)" type="text" v-model="domain.label" autocomplete="off"></el-input>
<el-input placeholder="索引(prop)" type="text" v-model="domain.prop" autocomplete="off"></el-input>
<el-select style="width:100%;" v-model="domain.value" placeholder="请选择类型">
<el-option v-for="item in modules" :key="item.value" :label="item.label" :value="item.value">
<span style="float: left">{{ item.label }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.value }}</span>
</el-option>
</el-select>
<el-button @click.prevent="removeDomain(index)">删除</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<el-button slot="reference" icon="el-icon-plus">添加表头</el-button>
</el-popover>
<el-form v-if="showQueryParams" :model="queryParams" ref="ruleForm" class="fromCss" :inline="true">
<el-form-item v-for="(item, index) in tableHeader" :key="index" :label="item.label">
<el-input v-if="!item.children" :placeholder="`请输入${item.label}`" v-model="queryParams[item.prop]"
autocomplete="off" @change="changeQueryParams"></el-input>
<div v-if="item.children && item.children.length != 0" style="display: flex;
flex-wrap: wrap;">
<div style="width:20%;margin:5px" v-for="domain in item.children" :key="domain.key">
<el-input style="width:100%" v-if="domain.module == 'text'" :placeholder="`请输入${domain.label}`"
v-model="queryParams[domain.prop]" autocomplete="off"></el-input>
</div>
</div>
</el-form-item>
</el-form>
</div>
<div class="content-con" v-if="tableHeader.length != 0">
<div class="content-con-top">
<div class="content-con-l">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handlerAddTable" plain>新增</el-button>
<el-button type="success" icon="el-icon-edit" size="mini" :disabled="selectionKeys.length == 1 ? false : true"
@click="handlerEditTable" plain>修改</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" :disabled="selectionKeys.length > 0 ? false : true"
@click="handlerDelTable" plain>删除</el-button>
<el-button type="info" icon="el-icon-upload2" size="mini" @click="handlerLeadingIn" plain>导入</el-button>
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" plain>导出</el-button>
</div>
<div class="content-con-r">
<el-tooltip class="item" effect="dark" content="隐藏搜索" placement="top">
<el-button icon="el-icon-search" size="mini" @click="() => { this.showQueryParams = !this.showQueryParams }"
circle></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" size="mini" content="刷新" placement="top">
<el-button style="margin:0;" icon="el-icon-refresh" size="mini" @click="handlerUpdateTable"
circle></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top">
<el-popover placement="bottom" width="200" trigger="click">
<el-checkbox-group style="display: flex;flex-direction: column;" v-model="checkedTableColumns"
@change="handleCheckedCitiesChange">
<el-checkbox v-for="heads in tableHeader" :label="heads.prop" :key="heads.prop">{{ heads.label
}}</el-checkbox>
</el-checkbox-group>
<el-button slot="reference" icon="el-icon-menu" size="mini" circle></el-button>
</el-popover>
</el-tooltip>
</div>
</div>
<!-- .slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize) -->
<DynamicTable :tableHeader="tableHeader" :tableData="tableDataList" :isIndex="false" :isSelection="true"
@selection-change="handlerSelectionChange" @json-click="handlerJsonClick" :loading="loading" ref="TableList"
height="700" />
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:page-sizes="[20, 50, 300, 400]" :page-size="20" layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-empty v-else description="创建一个新表吧~"></el-empty>
</div>
<!-- 新增 -->
<TrendsFrom ref="TrendsFrom" :parentThat="this" @handlerConfirm="handlerConfirm" />
<!-- 导入 -->
<LeadingIn ref="LeadingIn" :parentThat="this" />
<el-dialog title="设备日志" :visible.sync="showJson" width="40%">
<json-viewer :value="jsonData" :expand-depth=5 copyable boxed sort></json-viewer>
</el-dialog>
</div>
</template>
<script>
import DynamicTable from "@/components/Table/DynamicTable.vue"
import TrendsFrom from "./TrendsFrom.vue"
import LeadingIn from "./LeadingIn.vue"
let id = 20;
export default {
components: { DynamicTable, TrendsFrom, LeadingIn },
data() {
return {
filterText: "",
treeList: [],
defaultProps: {
children: "children",
label: "label",
},
showTree: false, //是否点击节点展开树,false 只能点前面三角图标展开
showBtn: [],
showEdit: [],
editData: [],
newAddData: false,
// 表格数据
showQueryParams: true,
tableHeader: [],
loading: false,
tableData: [],
queryParams: {
pageNum: 1,
pageSize: 20
},
// 表头添加
ruleForm: {
label: '',
props: '',
value: '',
radio: false,
childrenArr: [],
},
selectShow: false,
modules: [{
value: 'text',
label: '文字'
}, {
value: 'textarea',
label: '文本域'
}, {
value: 'password',
label: '密码'
},
// {
// value:'select',
// label:'下拉框'
// }, {
// value: 'image',
// label: '图片'
// }
],
rules: {
label: [
{ required: true, message: '请填写表头字段', trigger: 'blur' }
],
props: [
{
required: true,
validator: (rule, value, callback) => {
var rul = /(?![A-Z]*$)||(?![a-z]*$)/
if (!rul.test(value)) {
callback(
new Error(
'索引必须是大写字母或小写字母以上类型组成!'
)
)
} else {
callback()
}
}, trigger: 'blur'
}
],
},
visible: false,
selectionKeys: [],
checkedTableColumns: [],
total: 0,
showJson: false,
jsonData: ''
};
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
},
},
computed: {
tableDataList() {
return this.tableData.slice((this.queryParams.pageNum - 1) * this.queryParams.pageSize, this.queryParams.pageNum * this.queryParams.pageSize)
}
},
mounted() {
this.treeList = [{
id: 1,
label: "文件1",
children: [
{
id: 11,
label: "文件1-01",
children: [],
},
{
id: 12,
label: "文件1-02",
children: [],
}
],
}, {
id: 2,
label: "文件2",
children: [],
}, {
id: 3,
label: "文件3",
children: [],
}]
},
methods: {
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
handlerAppend() {
let i = 0;
if (this.data) {
const newChild = {
id: id++,
label: "文件" + (Number(this.data.length) + 1),
children: [],
};
this.treeList.push(newChild);
} else {
const newChild = {
id: id++,
label: "文件" + (i + 1),
children: [],
};
this.treeList.push(newChild);
}
this.$message({
message: "添加成功,开始编写吧~",
type: "success",
});
},
//点击树节点
handleNodeClick(data) {
if (!this.ifEdit()) {
return;
}
this.showBtn = [];
// 数组:第一个参数是要修改的数组, 第二个值是修改的下标或字段,第三个是要修改成什么值
// 对象:第一个参数是要修改的对象, 第二个值是修改属性字段,第三个是要修改成什么值
this.$set(this.showBtn, data.id, true);
this.getTableData(data.id)
// console.log(data)
},
append(data) {
if (!this.ifEdit()) {
return;
}
const newChild = { id: id++, label: "", children: [] };
if (!data.children) {
this.$set(data, "children", []);
}
data.children.push(newChild);
this.newAddData = true;
this.$set(this.showEdit, newChild.id, true);
},
edit(data) {
var localEdit = localStorage.getItem("treeEdit");
var id = data.id;
var newlocalData = { [data.id]: data.label };
if (localEdit) {
var localData = JSON.parse(localEdit);
Object.assign(localData, newlocalData);
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData);
} else {
newlocalData = JSON.stringify(newlocalData);
localStorage.setItem("treeEdit", newlocalData);
}
// console.log(localStorage.getItem("treeEdit"));
this.showEdit = [];
this.$set(this.showEdit, data.id, true);
},
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
},
//保存
save(node, data) {
if (data.label == "") {
this.$message({
type: "error",
message: "请输入完整数据",
offset: 70,
});
return;
}
this.newAddData = "";
var localEdit = localStorage.getItem("treeEdit");
if (localEdit) {
var localData = JSON.parse(localEdit);
delete localData[data.id]; //删除已经取消项
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData); //重置缓存
}
this.$set(this.showEdit, data.id, false);
},
cancel(node, data) {
if (this.newAddData) {
this.remove(node, data);
}
var localEdit = localStorage.getItem("treeEdit");
if (localEdit) {
var localData = JSON.parse(localEdit);
data.label = localData[data.id];
delete localData[data.id]; //删除已经取消项
localData = JSON.stringify(localData);
localStorage.setItem("treeEdit", localData); //重置缓存
}
this.$set(this.showEdit, data.id, false);
},
//判断是否有编辑或增加的项
ifEdit() {
if (this.showEdit.indexOf(true) != -1) {
this.$message({
type: "error",
message: "先保存正在编辑的行",
offset: 70,
});
return false;
} else {
return true;
}
},
getTableData(id) {
if (id == 11) {
this.tableHeader = [
{
id: Math.random(),
label: '数量(家)',
prop: 'number',
module: 'text',
show: true
}, {
id: Math.random(),
label: '名称',
prop: 'name',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '详细地址',
prop: 'address',
module: 'textarea',
show: true,
}, {
id: Math.random(),
label: '负责人姓名、电话',
prop: 'name_phone',
module: 'text',
show: true,
}
]
this.checkedTableColumns = this.tableHeader.map(column => column.prop)
} else if (id == 12) {
this.tableHeader = [
{
id: Math.random(),
label: '姓名',
prop: 'name',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '账号',
prop: 'account',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '部门',
prop: 'department',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '职务',
prop: 'job',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '所属规则',
prop: 'Owning_rule',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '概括',
prop: 'generalize',
// module: 'text',
show: true,
children: [
{
id: Math.random(),
label: '应打卡天数(天)',
prop: 'day1',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '实际打卡天数(天)',
prop: 'day2',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '正常天数(天)',
prop: 'day3',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '异常天数(天)',
prop: 'day4',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '标准工作时长(小时)',
prop: 'day5',
module: 'text',
show: true,
}, {
id: Math.random(),
label: '实际工作时长(小时)',
prop: 'day6',
module: 'text',
show: true,
},
]
}
]
this.checkedTableColumns = this.tableHeader.map(column => column.prop)
this.tableData = [{
id: Math.random(),
name: '张三',
account: '23523',
department: '897',
job: 'nalvnl',
Owning_rule: '23',
generalize: '890',
day1: '1',
day2: '2',
day3: '3',
day4: '4',
day5: '5',
day6: '6',
}, {
id: Math.random(),
name: '李四',
account: '23523',
department: '897',
job: 'nalvnl',
Owning_rule: '23',
generalize: '890',
day1: '1',
day2: '2',
day3: '3',
day4: '4',
day5: '5',
day6: '6',
}]
} else {
this.tableHeader = []
this.tableData = []
this.checkedTableColumns = this.tableHeader.map(column => column.prop)
}
},
// 搜索
changeQueryParams(val) {
},
inputChildren(val) {
if (val) {
this.ruleForm.childrenArr.push({
key: Date.now(),
label: '',
prop: '',
value: '',
})
} else {
this.ruleForm.childrenArr = []
}
},
// 添加子项
removeDomain(index) {
// var index = this.ruleForm.childrenArr.indexOf(item => item.key == domain.key)
if (index !== 0) {
this.ruleForm.childrenArr.splice(index, 1)
}
},
addDomain() {
this.ruleForm.childrenArr.push({
key: Date.now(),
label: '',
prop: '',
value: '',
});
},
// 添加表头
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.tableHeader.length == 0) {
if (this.ruleForm.label && this.ruleForm.props && this.ruleForm.value) {
this.tableHeader.push({
label: this.ruleForm.label,
prop: this.ruleForm.props,
module: this.ruleForm.value,
show: true,
radio: this.ruleForm.radio,
children: this.ruleForm.childrenArr
})
this.ruleForm = this.$options.data.call(this).ruleForm
this.visible = false
}
} else {
this.tableHeader.forEach(item => {
if (item.label == this.ruleForm.label || item.prop == this.ruleForm.props) {
if (item.prop == this.ruleForm.props) {
this.$message.error('索引(prop)已存在,换一个索引(prop)填写!');
} else {
this.$message.error('字段(label)已存在,换一个字段(label)填写!');
}
} else {
if (this.ruleForm.label && this.ruleForm.props && this.ruleForm.value) {
this.tableHeader.push({
label: this.ruleForm.label,
prop: this.ruleForm.props,
module: this.ruleForm.value,
show: true,
radio: this.ruleForm.radio,
children: this.ruleForm.childrenArr
})
this.ruleForm = this.$options.data.call(this).ruleForm
this.visible = false
}
}
})
}
this.checkedTableColumns = this.tableHeader.map(column => column.prop)
} else {
console.log('error submit!!');
return false;
}
});
},
changeModule(val) {
console.log(val)
if (val == 'select') {
this.selectShow = true
} else {
this.selectShow = false
}
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
// 添加列表
handlerAddTable() {
this.$refs.TrendsFrom.init('添加', this.tableHeader);
},
// 修改列表
handlerEditTable() {
this.$refs.TrendsFrom.init('修改', this.tableHeader, this.selectionKeys[0]);
},
// 删除列表
handlerDelTable() {
this.$confirm('此操作将删除该条数据,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 确定
this.selectionKeys.forEach((sItem, j) => {
let key = Object.keys(sItem)[0]
let indexKey = this.tableData.findIndex((el) => el[key] == sItem[key]);//找到下标
this.tableData.splice(indexKey, this.selectionKeys.length)
})
this.$refs.TableList.$refs.multipleTable.clearSelection()
this.total = this.tableData.length
// this.queryParams.pageNum = 1
this.$message({
showClose: true,
message: '成功'
});
})
.catch(() => {
// 取消
this.$message({
showClose: true,
message: '取消'
});
})
},
// 导入表格
handlerLeadingIn() {
this.$refs.LeadingIn.init('导入', this.tableHeader, this.tableData);
},
setDesc() {
// JS-获取到26个英文大写字母(A-Z)
const letterArr = []
Array(26).fill('').map((item, index) => {
letterArr.push(String.fromCharCode(index + 65))
})
return letterArr
},
/** 导出按钮操作 */
handleExport() {
let tHeader = [], multiHeader = [], header1 = [], filterPop = [], merges = [], merges1 = [], merges3 = [], merges2 = [], headerRowLength = 1;
let flag = this.tableHeader.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
if (flag) {
const randomAbc = this.setDesc()
this.tableHeader.map((column, i) => {
if (column.show) {
if (column.children && column.children.length > 0) {
headerRowLength++
header1.push(column.label)
column.children.map((childColumn, j) => {
merges2.push(randomAbc[j] + headerRowLength)
tHeader.push(childColumn.label)
filterPop.push(childColumn.prop)
header1.push('')
})
} else {
merges1.push(randomAbc[i] + headerRowLength)
header1.push(column.label)
tHeader.push('')
filterPop.push(column.prop)
}
}
})
// 二维数组依次递增 表格头部 // tHeader 表格头部最后一级
multiHeader.push(header1)
// 合并的行
merges1.forEach(i => {
merges2.forEach(j => {
if (i.substring(0, 1) == j.substring(0, 1)) {
merges.push(i + ':' + j)
}
})
})
// 合并的列
let len = merges1.length
tHeader.forEach((item, o) => {
merges3.push(randomAbc[o])
})
merges3.splice(0, len)
merges.push(merges3.slice(0, 1)[0] + (headerRowLength - 1) + ':' + merges3.slice(-1)[0] + (headerRowLength - 1))
this.$confirm('确定导出全部数据么?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 确定
this.getXlsxData_many(tHeader, multiHeader, filterPop, merges, this.tableData)
this.$message({
showClose: true,
message: '下载成功'
});
})
.catch(() => {
// 取消
this.$message({
showClose: true,
message: '取消下载'
});
})
} else {
this.tableHeader.map((column, i) => {
if (column.show) {
tHeader.push(column.label)
filterPop.push(column.prop)
}
})
this.$confirm('确定导出全部数据么?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 确定
this.getXlsxData(tHeader, filterPop, this.tableData)
this.$message({
showClose: true,
message: '下载成功'
});
})
.catch(() => {
// 取消
this.$message({
showClose: true,
message: '取消下载'
});
})
}
},
// 多行表头下载
getXlsxData_many(tHeader, multiHeader, filterPop, merges, dataT = []) {
require.ensure([], () => {
const { export_json_to_excel_headerMany } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
const data = this.formatJson(filterPop, dataT);//格式化
// console.log(tHeader, multiHeader, filterPop, merges, dataT);
//这个得说明一下:网上得博客每个不一样,你那我的直接用也是没啥用得,你的理解这个合并是怎么写的:根据你的多级表头,如果没有合并得从上往下写,遇到开始合并单元格的,从左往右得单行写,从上到下,直到写完整
export_json_to_excel_headerMany({
multiHeader,
header: tHeader,
data,
filename: `user_${new Date().getTime()}`, merges,
autoWidth: true,
})
})
},
// 单行表头下载
getXlsxData(tHeader = [], filterVal = [], dataT = []) {
require.ensure([], () => {
const { export_json_to_excel } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
// const tHeader = []; //对应表格输出的title
// const filterVal = []; // 对应表格输出的数据
const data = this.formatJson(filterVal, dataT);
export_json_to_excel(tHeader, data, `user_${new Date().getTime()}`); //对应下载文件的名字
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
// 刷新列表
handlerUpdateTable() {
this.loading = true
setTimeout(() => {
this.tableData = this.$options.data.call(this).tableData
this.loading = false
}, 200);
},
// 显示隐藏列
handleCheckedCitiesChange(value) {
this.tableHeader.forEach(column => {
// 如果选中,则设置列显示
if (value.includes(column.prop)) {
column.show = true;
} else {
// 如果未选中,则设置列隐藏
column.show = false;
}
})
},
// 表格数据
handlerConfirm(data, keys) {
if (keys) {
let key = Object.keys(keys)[0]
let indexKey = this.tableData.findIndex((el) => el[key] == keys[key]);//找到下标
this.tableData[indexKey] = data //替换数据
this.$message({
message: '修改成功!',
type: 'success'
});
} else {
this.tableData.push(data)
this.$message({
message: '添加成功!',
type: 'success'
});
}
this.total = this.tableData.length
},
// 勾选
handlerSelectionChange(val) {
// console.log(val)
this.selectionKeys = val
},
// 分页
handleSizeChange(val) {
this.queryParams.pageNum = 1;
this.queryParams.pageSize = val;
},
handleCurrentChange(val) {
this.queryParams.pageNum = val;
},
// 日志
handlerJsonClick(row) {
this.showJson = true
this.jsonData = row
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .el-tree-node__content {
padding: 10px 0;
height: auto;
}
.treeSortList {
width: calc(100% - 30px);
display: flex;
align-items: center;
justify-content: space-between;
}
.treeSortList .doBtn {
padding-right: 10px;
}
.treeSortList .doBtn .el-button--text {
color: rgb(61, 61, 61);
padding: 5px;
}
.treeSortList .doBtn .el-button--text i {
color: rgb(80, 80, 80);
}
.treeSortList .doBtn .el-button--text i.el-icon-delete {
color: #f3a68e;
}
.container {
height: 100vh;
overflow: hidden;
display: flex;
box-sizing: border-box;
.left-con {
background-color: #d3dce6;
color: #333;
// text-align: center;
// line-height: 200px;
width: 320px;
box-sizing: border-box;
padding: 20px;
overflow-y: scroll;
}
.right-con {
flex: 1;
display: flex;
flex-direction: column;
box-sizing: border-box;
.header-con {
position: sticky;
top: 0;
margin-bottom: 10px;
padding: 10px;
}
.content-con {
flex: 1;
overflow-y: scroll;
padding: 10px;
.content-con-top {
display: flex;
margin: 10px 0;
justify-content: space-between;
.content-con-l {}
.content-con-r {
display: flex;
width: 10%;
justify-content: space-between;
}
}
}
}
}
.header-con,
.content-con {
background-color: #b3c0d1;
color: #333;
// text-align: center;
// line-height: 60px;
}
.fromCss {
.el-form-item {
margin-bottom: 0;
margin-top: 10px;
}
}
.icon-btn {
width: 50px;
height: 50px;
border: 1px solid #cdcdcd;
background: white;
border-radius: 25px;
overflow: hidden;
position: relative;
transition: width 0.2s ease-in-out;
font-weight: 500;
font-family: inherit;
margin: 10px auto;
}
.add-btn:hover {
width: 120px;
}
.add-btn::before,
.add-btn::after {
transition: width 0.2s ease-in-out, border-radius 0.2s ease-in-out;
content: "";
position: absolute;
height: 4px;
width: 10px;
top: calc(50% - 2px);
background: seagreen;
}
.add-btn::after {
right: 14px;
overflow: hidden;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.add-btn::before {
left: 14px;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.icon-btn:focus {
outline: none;
}
.btn-txt {
opacity: 0;
transition: opacity 0.2s;
}
.add-btn:hover::before,
.add-btn:hover::after {
width: 4px;
border-radius: 2px;
}
.add-btn:hover .btn-txt {
opacity: 1;
}
.add-icon::after,
.add-icon::before {
transition: all 0.2s ease-in-out;
content: "";
position: absolute;
height: 20px;
width: 2px;
top: calc(50% - 10px);
background: seagreen;
overflow: hidden;
}
.add-icon::before {
left: 22px;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
.add-icon::after {
right: 22px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.add-btn:hover .add-icon::before {
left: 15px;
height: 4px;
top: calc(50% - 2px);
}
.add-btn:hover .add-icon::after {
right: 15px;
height: 4px;
top: calc(50% - 2px);
}
</style>
TrendsFrom.vue 表单新增或修改
<template>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="45%">
<el-form :model="form" ref="ruleForm" class="fromCss">
<el-form-item v-for="(item, index) in headerData" :key="index" :label="item.label">
<el-input v-if="item.module == 'text'" :placeholder="`请输入${item.label}`" v-model="form[item.prop]"
autocomplete="off"></el-input>
<el-input v-if="item.module == 'password'" :placeholder="`请输入${item.label}`" v-model="form[item.prop]"
show-password></el-input>
<el-input v-if="item.module == 'textarea'" :placeholder="`请输入${item.label}`" type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }" v-model="form[item.prop]">
</el-input>
<div v-if="item.children && item.children.length != 0" style="display: flex;
flex-wrap: wrap;">
<div style="width:50%;margin:5px 0" v-for="domain in item.children" :key="domain.key">
<el-input style="width:100%" v-if="domain.module == 'text'" :placeholder="`请输入${domain.label}`"
v-model="form[domain.prop]" autocomplete="off"></el-input>
</div>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm('ruleForm')">取 消</el-button>
<el-button type="primary" @click="submitForm('ruleForm')">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
parentThat: {}
},
data() {
return {
// 表格添加
title: '',
dialogFormVisible: false,
form: {},
headerData: null,
editKeys: null,
}
},
mounted() {
},
methods: {
init(title, headerData, keys) {
this.dialogFormVisible = true;
this.title = title
this.headerData = headerData
this.editKeys = null
// console.log(headerData);
if (keys) {
this.editKeys = keys
this.form = JSON.parse(JSON.stringify(keys))
}
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.editKeys) {
this.$emit('handlerConfirm', this.form, this.editKeys)
} else {
this.$emit('handlerConfirm', this.form)
}
this.parentThat.$refs.TableList.$refs.multipleTable.clearSelection()
this.form = this.$options.data.call(this).form
this.dialogFormVisible = false;
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.form = this.$options.data.call(this).form
this.dialogFormVisible = false;
this.$refs[formName].resetFields();
},
}
}
</script>
<style lang="scss" scoped>
.fromCss {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
</style>
LeadingIn.vue 导入
<template>
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="400px">
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :action="upload.url" :on-change="handleChange"
:auto-upload="false" drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
<div><el-checkbox v-model="upload.isUploading" />是否更新已经存在的数据</div>
<div>仅允许导入xls、xlsx格式文件。<a style="color:dodgerblue;" @click="importTemplate">下载模板</a> </div>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm('uploadRef')">确 定</el-button>
<el-button @click="resetForm('uploadRef')">取 消</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
parentThat: {},
},
data() {
return {
// 表格添加
title: '',
dialogFormVisible: false,
upload: {
// 是否更新已数据
isUploading: false,
// 上传的地址
url: process.env.VUE_APP_BASE_API +
"/efarmcloud-open-file/api/v1/files/upload"
},
XlsxData: [],
fileContent: null,
headerTable: [],
tableData: [],
loading:null,
}
},
methods: {
init(title, headerTable, tableData) {
this.dialogFormVisible = true;
this.title = title
this.headerTable = headerTable
this.tableData = tableData
},
setDesc() {
// JS-获取到26个英文大写字母(A-Z)
const letterArr = []
Array(26).fill('').map((item, index) => {
letterArr.push(String.fromCharCode(index + 65))
})
return letterArr
},
/** 下载模板操作 */
importTemplate() {
let tHeader = [], multiHeader = [], header1 = [], filterPop = [], merges = [], merges1 = [], merges3 = [], merges2 = [], headerRowLength = 1;
// 有69个元素是空的,所以直接进行了截取
let flag = this.headerTable.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
if (flag) {
const randomAbc = this.setDesc()
this.headerTable.map((column, i) => {
if (column.show) {
if (column.children && column.children.length > 0) {
headerRowLength++
header1.push(column.label)
column.children.map((childColumn, j) => {
merges2.push(randomAbc[j] + headerRowLength)
tHeader.push(childColumn.label)
filterPop.push(childColumn.prop)
header1.push('')
})
} else {
merges1.push(randomAbc[i] + headerRowLength)
header1.push(column.label)
tHeader.push('')
filterPop.push(column.prop)
}
}
})
// 二维数组依次递增 表格头部 // tHeader 表格头部最后一级
multiHeader.push(header1)
// 合并的行
merges1.forEach(i => {
merges2.forEach(j => {
if (i.substring(0, 1) == j.substring(0, 1)) {
merges.push(i + ':' + j)
}
})
})
// 合并的列
let len = merges1.length
tHeader.forEach((item, o) => {
merges3.push(randomAbc[o])
})
merges3.splice(0, len)
merges.push(merges3.slice(0, 1)[0] + (headerRowLength - 1) + ':' + merges3.slice(-1)[0] + (headerRowLength - 1))
// console.log(filterPop, tHeader, multiHeader, headerRowLength, randomAbc, merges1, merges2, merges, merges3);
this.getXlsxData_many(tHeader, multiHeader, filterPop, merges)
} else {
this.tableHeader.map((column, i) => {
if (column.show) {
tHeader.push(column.label)
filterPop.push(column.prop)
}
})
this.getXlsxData(tHeader, filterPop)
}
},
// 多行表头下载
getXlsxData_many(tHeader, multiHeader, filterPop, merges, dataT = []) {
require.ensure([], () => {
const { export_json_to_excel_headerMany } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
const data = this.formatJson(filterPop, dataT);//格式化
// console.log(tHeader, multiHeader, filterPop, merges, dataT);
//这个得说明一下:网上得博客每个不一样,你那我的直接用也是没啥用得,你的理解这个合并是怎么写的:根据你的多级表头,如果没有合并得从上往下写,遇到开始合并单元格的,从左往右得单行写,从上到下,直到写完整
export_json_to_excel_headerMany({
multiHeader,
header: tHeader,
data,
filename: `user_${new Date().getTime()}`, merges,
autoWidth: true,
})
})
},
// 单行表头下载
getXlsxData(tHeader = [], filterVal = [], dataT = []) {
require.ensure([], () => {
const { export_json_to_excel } = require('@/excel/Export2Excel.js');// 这里 require 写你的Export2Excel.js的绝对地址
// const tHeader = []; //对应表格输出的title
// const filterVal = []; // 对应表格输出的数据
const data = this.formatJson(filterVal, dataT);
console.log(tHeader, data, filterVal, '下载');
export_json_to_excel(tHeader, data, `user_${new Date().getTime()}`); //对应下载文件的名字
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
// 核心部分代码(handleChange 和 importfile)
handleChange(file) {
this.fileContent = file.raw
const fileName = file.name
const fileType = fileName.substring(fileName.lastIndexOf('.') + 1)
if (this.fileContent) {
if (fileType === 'xlsx' || fileType === 'xls') {
// 判断是否有子项
let flag = this.headerTable.some(column => {
if (column.children && column.children.length > 0) {
return true
}
return false
})
this.loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (flag) {
this.importFile(this.fileContent, flag)
this.loading.close();
} else {
this.importFile(this.fileContent, flag)
this.loading.close();
}
} else {
this.$message({
type: 'warning',
message: '附件格式错误,请重新上传!'
})
}
} else {
this.$message({
type: 'warning',
message: '请上传附件!'
})
}
},
importFile(obj, bol) {
const reader = new FileReader()
const _this = this
reader.readAsArrayBuffer(obj)
reader.onload = function () {
const buffer = reader.result
const bytes = new Uint8Array(buffer)
const length = bytes.byteLength
let binary = ''
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i])
}
const XLSX = require('xlsx')
const wb = XLSX.read(binary, {
type: 'binary'
})
const outData = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]])
// console.log(outData);
const arr = [...outData], newArr = []
arr.map((v, j) => {
let list = _this.headerTable.map((s, i) => {
let key = Object.keys(v)[i]
if (key == s.label) {
return { id: Math.random(), [s.prop]: v[key] }
}
})
let newList = []
list.forEach(item => {
if (item) {
if (bol) {
// 手动填写子项的prop值与对应xlsx 对应的字段
newList.push(item, {
day1: v.概括,
day2: v.__EMPTY,
day3: v.__EMPTY_1,
day4: v.__EMPTY_2,
day5: v.__EMPTY_3,
day6: v.__EMPTY_4,
})
} else {
newList.push(item)
}
}
})
if (newList.length != 0) {
newArr.push(Object.assign(...newList))
} else {
if (j < 1 && !bol) {
_this.$notify.error({
title: '错误',
message: '上传的数据格式不匹配,请重新上传!'
});
_this.$refs.uploadRef.clearFiles();
}
}
})
_this.XlsxData = newArr
}
},
submitForm(uploadRef) {
// console.log(this.XlsxData, this.parentThat._data.tableData, this.upload.isUploading);
if (this.upload.isUploading) {
this.parentThat._data.tableData = this.XlsxData
} else {
this.parentThat._data.tableData = this.parentThat._data.tableData.concat(this.XlsxData)
}
this.parentThat._data.total = this.parentThat._data.tableData.length //数量
this.dialogFormVisible = false;
this.$refs[uploadRef].clearFiles();
},
resetForm(uploadRef) {
this.dialogFormVisible = false;
this.$refs[uploadRef].clearFiles();
},
}
}
</script>
<style lang="scss" scoped>
.el-upload__tip {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
技术难点:
-
在已有表头已有数据的情况下,再次添加表头修改数据回显慢,切换页面数据才会显示(不明原因)。
-
使用前端分页,会导致表格勾选错乱或不显示(解决方案使用computed)
<DynamicTable :tableHeader="tableHeader" :tableData="tableDataList" :isIndex="true" :isSelection="true" @selection-change="handlerSelectionChange" :loading="loading" ref="TableList" height="700" /> computed: { tableDataList() { return this.tableData.slice((this.queryParams.pageNum - 1) * this.queryParams.pageSize, this.queryParams.pageNum * this.queryParams.pageSize) } }, -
删除删掉的永远是首条(由于没有id值,拿到的永远是第一条)(已修改)
-
表头过多的情况下加载会很慢,导入字符串建议一对一填写,数据缺失或表头样式复杂数据不准确
-
动态表单或动态表格内用使用或展示根据使用情况进行增减
弹窗样式(居中显示不超过屏幕内含滚动条)
// el-dialog高度自适应,内容超出时中间出现滚动条
.common-dialog {
display: flex;
justify-content: center;
align-items: Center;
overflow: hidden;
.el-dialog:not(.is-fullscreen) {
margin-top: 0 !important;
}
.el-dialog {
margin: 0 auto !important;
position: relative;
.el-dialog__header {
position: absolute;
left: 0;
top: 0;
right: 0;
width: 100%;
height: 60px;
z-index: 1;
background-color: #fff;
}
.el-dialog__body {
width: 100%;
overflow: hidden;
overflow-y: auto;
max-height: 100vh; //最大高度为视口高度的90%
padding-top: 60px;
padding-bottom: 100px;
z-index: 1;
}
.el-dialog__footer {
position: absolute;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 80px;
z-index: 1;
background-color: #fff;
}
}
}