|
|
|
@ -5,9 +5,7 @@ |
|
|
|
|
<div class="tool"> |
|
|
|
|
<el-input style="width: 250px" placeholder="请输入文件名称" v-model="keyword" suffix-icon="el-icon-search" clearable size="small"></el-input> |
|
|
|
|
<div class="action"> |
|
|
|
|
<el-upload :headers="{token}" :action="api.fileUpload" name="file" :limit="1000" :show-file-list="false" :before-upload="beforeImport" :on-success="successImport"> |
|
|
|
|
<el-button class="cus-btn" type="primary" size="small">导入数据</el-button> |
|
|
|
|
</el-upload> |
|
|
|
|
<el-button class="cus-btn" type="primary" size="small" @click="sourceVisible = true">导入数据</el-button> |
|
|
|
|
<el-button class="cus-btn" style="margin-left: 10px;" type="primary" size="small" @click="delAll">批量删除</el-button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
@ -40,6 +38,55 @@ |
|
|
|
|
<div class="pagination"> |
|
|
|
|
<el-pagination background layout="total,prev, pager, next" :current-page="page" @current-change="handleCurrentChange" :total="total"></el-pagination> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<el-dialog title="请选择数据源" :visible.sync="sourceVisible" width="400px" :close-on-click-modal="false"> |
|
|
|
|
<ul class="source-list"> |
|
|
|
|
<li style="width: 48%" @click="showData"> |
|
|
|
|
<img src="@/assets/images/dataforward.png" alt=""> |
|
|
|
|
</li> |
|
|
|
|
<el-upload class="upload" :headers="{token}" :action="api.fileUpload" name="file" :limit="1000" :show-file-list="false" :before-upload="beforeImport" :on-success="successImport"> |
|
|
|
|
<li style="width: 100%;">本地上传</li> |
|
|
|
|
</el-upload> |
|
|
|
|
</ul> |
|
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
<!-- 导入数据 --> |
|
|
|
|
<el-dialog title="导入" :visible.sync="importVisible" width="80%" center @close="closeImport" class="dialog" :close-on-click-modal="false"> |
|
|
|
|
<el-container style="padding: 20px 0 20px 20px;background-color: #f0f0f0;"> |
|
|
|
|
<div style="overflow:auto;height: 558px;width:330px;padding:15px;background:#fff" ref="typeTreeWrap" @scroll="loadType"> |
|
|
|
|
<el-tree v-loading="importLoading" |
|
|
|
|
ref="typeTree" |
|
|
|
|
:data="importTypeList" |
|
|
|
|
node-key="id" |
|
|
|
|
accordion |
|
|
|
|
:default-expanded-keys="defaultTypeActive" |
|
|
|
|
:default-checked-keys="defaultTypeChecked" |
|
|
|
|
:current-node-key="curId" |
|
|
|
|
show-checkbox |
|
|
|
|
highlight-current |
|
|
|
|
@node-click="importTypeClick" |
|
|
|
|
@node-expand="importTypeExpand" |
|
|
|
|
> |
|
|
|
|
</el-tree> |
|
|
|
|
</div> |
|
|
|
|
<el-main style="padding-top: 0;padding-bottom: 0;"> |
|
|
|
|
<el-card shadow="hover"> |
|
|
|
|
<div class="flex-center mgb20"> |
|
|
|
|
<p class="hr_tag"></p> |
|
|
|
|
<span>预览</span> |
|
|
|
|
</div> |
|
|
|
|
<el-table :data="fieldData" stripe header-align="center" v-if="fieldHead.length"> |
|
|
|
|
<el-table-column type="index" width="100" label="序号" align="center"></el-table-column> |
|
|
|
|
<el-table-column v-for="(item,index) in fieldHead" :prop="item.field" :key="index" :label="item.comment" align="center"></el-table-column> |
|
|
|
|
</el-table> |
|
|
|
|
</el-card> |
|
|
|
|
</el-main> |
|
|
|
|
</el-container> |
|
|
|
|
<span slot="footer" class="dialog-footer"> |
|
|
|
|
<el-button @click="importVisible = false">取 消</el-button> |
|
|
|
|
<el-button type="primary" :loading="submited" @click="confirmImport">确 定</el-button> |
|
|
|
|
</span> |
|
|
|
|
</el-dialog> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
@ -58,7 +105,21 @@ export default { |
|
|
|
|
keyword: '', |
|
|
|
|
page: 1, |
|
|
|
|
pageSize: 10, |
|
|
|
|
total: 0 |
|
|
|
|
total: 0, |
|
|
|
|
sourceVisible: false, |
|
|
|
|
|
|
|
|
|
importVisible: false, |
|
|
|
|
defaultTypeActive: [], |
|
|
|
|
defaultTypeChecked: [], |
|
|
|
|
importLoading: false, |
|
|
|
|
tablePromises: [], |
|
|
|
|
importTypeList: [], |
|
|
|
|
fieldData: [], |
|
|
|
|
fieldHead: [], |
|
|
|
|
tableName: '', |
|
|
|
|
curId: '', |
|
|
|
|
curExpand: '', |
|
|
|
|
submited: false, |
|
|
|
|
}; |
|
|
|
|
}, |
|
|
|
|
components: { |
|
|
|
@ -119,6 +180,7 @@ export default { |
|
|
|
|
ossFileName: file.ossFileName |
|
|
|
|
}).then(res => { |
|
|
|
|
this.$message.success('导入成功') |
|
|
|
|
this.sourceVisible = false |
|
|
|
|
this.getData() |
|
|
|
|
}).catch(res => {}) |
|
|
|
|
}, |
|
|
|
@ -177,6 +239,287 @@ export default { |
|
|
|
|
}, |
|
|
|
|
handleSelectionChange(val) { |
|
|
|
|
this.multipleSelection = val |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 递归查询当前展开的分类下的子分类 |
|
|
|
|
getMoreTable(list,id){ |
|
|
|
|
list.map(n => { |
|
|
|
|
if(n.id == id){ |
|
|
|
|
this.getTable(n,1) |
|
|
|
|
}else if(n.children && n.children.length){ |
|
|
|
|
this.getMoreTable(n.children,id) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
// 懒加载后新加载出来的数据可能是之前就存在于这个分类下了的,因此是要自动勾选的,所以在这里去拿这个分类下已经存在了的表的id去做一个重新勾选 |
|
|
|
|
renderChecked(){ |
|
|
|
|
let result = [] |
|
|
|
|
let list = this.$refs.typeTree.getCheckedNodes() |
|
|
|
|
list.map((n,i) => { |
|
|
|
|
if(n.tableLen && n.tableLen > n.children.length){ |
|
|
|
|
result = [...result,...n.children.map(n => n.id)] |
|
|
|
|
}else{ |
|
|
|
|
result.push(n.id) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
this.$refs.typeTree.setCheckedKeys(result) |
|
|
|
|
}, |
|
|
|
|
// 因为数据会有上万条的情况,一次加载出来dom太多了,所以用懒加载去处理 |
|
|
|
|
loadType(e){ |
|
|
|
|
clearTimeout(this.typeTimer) |
|
|
|
|
let typeTree = this.$refs.typeTreeWrap |
|
|
|
|
// 防抖 |
|
|
|
|
this.typeTimer = setTimeout(() => { |
|
|
|
|
this.typeTreeScrollTop = typeTree.scrollTop - this.typeTreeScrollTop |
|
|
|
|
// 如果已经滚动到底部,则加载下面的数据 |
|
|
|
|
if(this.typeTreeScrollTop > 0 && typeTree.scrollHeight - (typeTree.clientHeight + parseInt(typeTree.scrollTop)) < 10){ |
|
|
|
|
this.getMoreTable(this.importTypeList,this.curExpand) |
|
|
|
|
Promise.all(this.tablePromises).then(_ => { |
|
|
|
|
this.renderChecked() |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
},50) |
|
|
|
|
}, |
|
|
|
|
// 查询分类下的表 |
|
|
|
|
getTable(n,isConcat){ |
|
|
|
|
this.tablePromises.push(new Promise((resolve,reject) => { |
|
|
|
|
this.$post(`${this.api.originalListById}?categoryId=${n.realId}&pageNum=${n.typeTreePage ? n.typeTreePage : 1}&pageSize=${this.typeTreeUnit}`).then(res => { |
|
|
|
|
let list = res.list.records |
|
|
|
|
list.map(n => { |
|
|
|
|
n.label = n.showName |
|
|
|
|
n.id = String(n.id) |
|
|
|
|
// 已经存在的表,禁止再次勾选 |
|
|
|
|
if(this.defaultTypeChecked.includes(n.id)){ |
|
|
|
|
n.disabled = true |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
// 如果是懒加载,则拼接 |
|
|
|
|
if(isConcat){ |
|
|
|
|
n.children = n.children.concat(list) |
|
|
|
|
}else{ |
|
|
|
|
n.children = list |
|
|
|
|
n.tableLen = res.list.total |
|
|
|
|
} |
|
|
|
|
// 每次加载完,分页码数自动+1 |
|
|
|
|
n.typeTreePage++ |
|
|
|
|
resolve() |
|
|
|
|
}).catch(res => { |
|
|
|
|
reject() |
|
|
|
|
}) |
|
|
|
|
})) |
|
|
|
|
}, |
|
|
|
|
// 导入数据 |
|
|
|
|
showData(){ |
|
|
|
|
this.importLoading = true |
|
|
|
|
this.importVisible = true |
|
|
|
|
// 这里查的是原始分类,跟其他任何地方查分类的接口都不一样,不要混淆,如果要改这个地方,要跟后端沟通 |
|
|
|
|
this.$post(this.api.originalList).then(res => { |
|
|
|
|
// 处理id和label。因为这个树形要同时展示分类和表,所以name的key统一用label(分类和表的name的key不一样) |
|
|
|
|
function handleId(data){ |
|
|
|
|
data.map(n => { |
|
|
|
|
n.realId = n.id |
|
|
|
|
// 分类id跟表id可能会冲突,所以加上uuid |
|
|
|
|
n.id = uuid.v4() |
|
|
|
|
n.label = n.categoryName |
|
|
|
|
if(n.children.length){ |
|
|
|
|
handleId(n.children) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
handleId(res) |
|
|
|
|
// 查询该分类下已勾选的表。pageSize传3000是防止查的表太少,无法选中分类,因为每个分类初始化的时候只查子下的20个表,后续可根据需求修改 |
|
|
|
|
this.$post(`${this.api.getIdQueryTable}?categoryId=${this.categoryId}&showName=${this.keyword}&pageNum=1&pageSize=3000&updateTime=${this.updateTime ? this.updateTime : ''}`).then(res1 => { |
|
|
|
|
let list = res1.pageList.records |
|
|
|
|
this.defaultTypeChecked = list.map(n => n.copyId) // copyId为原始表id,通过这个id才能选中表 |
|
|
|
|
// 递归处理分页 |
|
|
|
|
const handleId = data => { |
|
|
|
|
data.map(n => { |
|
|
|
|
if(n.children.length){ |
|
|
|
|
handleId(n.children) |
|
|
|
|
}else{ |
|
|
|
|
// 默认分页码数为1 |
|
|
|
|
n.typeTreePage = 1 |
|
|
|
|
this.getTable(n) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
handleId(res) |
|
|
|
|
// 如果是已经勾选了的表,则禁止勾选 |
|
|
|
|
function handleDisabled (list) { |
|
|
|
|
list.map(e => { |
|
|
|
|
if (e.children && e.children.length) { |
|
|
|
|
if (e.children.every(e => e.disabled)) e.disabled = true |
|
|
|
|
handleDisabled(e.children) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
Promise.all(this.tablePromises).then(_ => { |
|
|
|
|
handleDisabled(res) |
|
|
|
|
this.importTypeList = res |
|
|
|
|
this.importLoading = false |
|
|
|
|
}) |
|
|
|
|
}).catch(res => { |
|
|
|
|
this.importLoading = false |
|
|
|
|
}) |
|
|
|
|
}).catch(res => { |
|
|
|
|
this.importLoading = false |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
// 查询表的字段 |
|
|
|
|
getFields(){ |
|
|
|
|
this.$get(`${this.api.staticPreview}?tableName=${this.tableName}`).then(res => { |
|
|
|
|
let comment = res.comment |
|
|
|
|
let fieldHead = [] |
|
|
|
|
// id和操作时间这两个字段不用展示 |
|
|
|
|
comment.map(n => { |
|
|
|
|
n.field != 'id' && n.field != 'operation_time' && fieldHead.push(n) |
|
|
|
|
}) |
|
|
|
|
this.fieldHead = fieldHead |
|
|
|
|
|
|
|
|
|
let data = res.data |
|
|
|
|
data.map(n => { |
|
|
|
|
for(let i in n){ |
|
|
|
|
// 如果是以+0000结尾的,就表明这个是时间,则转化为正常的时间格式 |
|
|
|
|
if(typeof n[i] == 'string' && n[i].endsWith('+0000')) n[i] = this.formatDate('yyyy-MM-dd hh:mm:ss',new Date(n[i])) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
this.fieldData = data |
|
|
|
|
}).catch(res => {}) |
|
|
|
|
this.importVisible = true |
|
|
|
|
}, |
|
|
|
|
// 分类点击回调 |
|
|
|
|
importTypeClick(data){ |
|
|
|
|
// 如果点击的是表,则加载这个表下面的字段,分类则不用 |
|
|
|
|
if(data.name){ |
|
|
|
|
this.tableName = data.name |
|
|
|
|
this.curId = data.id |
|
|
|
|
this.getFields() |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
// 分类展开的回调 |
|
|
|
|
importTypeExpand(obj,node,com){ |
|
|
|
|
this.curExpand = obj.id |
|
|
|
|
this.renderChecked() |
|
|
|
|
}, |
|
|
|
|
// 关闭导入弹框回调 |
|
|
|
|
closeImport(){ |
|
|
|
|
this.$refs.typeTree.setCheckedKeys([]) |
|
|
|
|
this.categorys = [] |
|
|
|
|
}, |
|
|
|
|
// 查询分类下的表id |
|
|
|
|
getNames(list){ |
|
|
|
|
const categoryId = Number(this.categoryId) |
|
|
|
|
return new Promise((resolve,reject) => { |
|
|
|
|
this.$get(`${this.api.getAllTableInfoByCategoryId}?categoryId=${list.join()}`).then(res => { |
|
|
|
|
const list = res.tableInfo |
|
|
|
|
list.map(e => { |
|
|
|
|
delete e.id // id不用传给后端 |
|
|
|
|
e.categoryId = categoryId |
|
|
|
|
}) |
|
|
|
|
this.categorys = list |
|
|
|
|
resolve() |
|
|
|
|
}).catch(res => {}) |
|
|
|
|
}) |
|
|
|
|
}, |
|
|
|
|
// 保存成功后的操作 |
|
|
|
|
saveSuccess() { |
|
|
|
|
this.$message.success('导入成功') |
|
|
|
|
this.getData() |
|
|
|
|
this.importVisible = false |
|
|
|
|
setTimeout(() => { |
|
|
|
|
this.submited = false |
|
|
|
|
},1000) |
|
|
|
|
}, |
|
|
|
|
// 导入表 |
|
|
|
|
confirmImport(){ |
|
|
|
|
if(this.submited) return false // 导入防抖标识 |
|
|
|
|
const checked = this.$refs.typeTree.getCheckedNodes().filter(e => !e.disabled) // 勾选的数据 |
|
|
|
|
// 如果没有选择任何数据 |
|
|
|
|
if(!checked.length) return this.$message.warning('请选择数据') |
|
|
|
|
this.submited = true |
|
|
|
|
// 分类要调另一个接口去查询该分类下的表,所以,要先把分类id和表id分开,再一次性传分类id去查询表,再把查询出来的表跟另外勾选的表拼接,再保存。要做去重 |
|
|
|
|
const typeIds = checked.filter(e => !e.name).map(e => e.realId) // 先筛选出分类id(分类没有name,表才有name),再获取分类真正的id(id作了自增处理,获取的id不是真正的id,realId才是这个分类真正的id) |
|
|
|
|
const tableIds = checked.filter(e => e.name) // 筛选出表 |
|
|
|
|
const categoryId = Number(this.categoryId) // 外面勾选的分类,即要导入数据的分类 |
|
|
|
|
// 给表加categoryId |
|
|
|
|
tableIds.map(e => { |
|
|
|
|
delete e.id |
|
|
|
|
e.categoryId = categoryId |
|
|
|
|
}) |
|
|
|
|
/** |
|
|
|
|
* 根据某个值给数组对象去重 |
|
|
|
|
* @param arr 要处理的数组 |
|
|
|
|
* @param val 去重依据的值 |
|
|
|
|
*/ |
|
|
|
|
function uniq (arr, val) { |
|
|
|
|
const res = new Map() |
|
|
|
|
return arr.filter(e => !res.has(e[val]) && res.set(e[val], 1)) |
|
|
|
|
} |
|
|
|
|
// 分类id,如果有分类id,则调接口去查询分类下的表,再新增,否则,直接拿表id去新增 |
|
|
|
|
if (typeIds.length) { |
|
|
|
|
this.getNames(typeIds).then(() => { |
|
|
|
|
const allIds = [...this.categorys, ...tableIds] // 合并通过分类查询到的表id和另外勾选的表id |
|
|
|
|
const data = uniq(allIds, 'name') |
|
|
|
|
const dataList = [] |
|
|
|
|
// 每个接口只新增2000个表,一次新增太多无法请求成功,这里做个分割 |
|
|
|
|
for (let i = 0, len = data.length; i < len; i += 2000) { |
|
|
|
|
dataList.push(data.slice(i, i + 2000)) |
|
|
|
|
} |
|
|
|
|
const promiseList = [] |
|
|
|
|
// 如果切割成2000条一个item,超过6个item的话,就一次调太多次接口了,也有可能会请求失败,所以如果超过6个item,则再分两次去新增,后续如果数据又增加了,可以考虑做个二次分割,即先切割成2000个一个item,如果超过6个item,则再以6个item为单位切割。新增的时候就是每个接口新增2000个表,每次调6次新增,这6次调完后,再调另外的6次,以此循环 |
|
|
|
|
if (dataList.length > 6) { |
|
|
|
|
dataList.slice(0, 6).map(e => { |
|
|
|
|
promiseList.push(new Promise((resolve,reject) => { |
|
|
|
|
this.$post(this.api.saveTable, e).then(res => { |
|
|
|
|
resolve() |
|
|
|
|
}).catch(res => { |
|
|
|
|
reject() |
|
|
|
|
}) |
|
|
|
|
})) |
|
|
|
|
}) |
|
|
|
|
Promise.all(promiseList).then(res => { |
|
|
|
|
dataList.slice(6).map(e => { |
|
|
|
|
promiseList.push(new Promise((resolve,reject) => { |
|
|
|
|
this.$post(this.api.saveTable, e).then(res => { |
|
|
|
|
resolve() |
|
|
|
|
}).catch(res => { |
|
|
|
|
reject() |
|
|
|
|
}) |
|
|
|
|
})) |
|
|
|
|
}) |
|
|
|
|
Promise.all(promiseList).then(res => { |
|
|
|
|
this.saveSuccess() |
|
|
|
|
}).catch(err => { |
|
|
|
|
this.submited = false |
|
|
|
|
}) |
|
|
|
|
}).catch(err => { |
|
|
|
|
this.submited = false |
|
|
|
|
}) |
|
|
|
|
} else { |
|
|
|
|
dataList.map(e => { |
|
|
|
|
promiseList.push(new Promise((resolve,reject) => { |
|
|
|
|
this.$post(this.api.saveTable, e).then(res => { |
|
|
|
|
resolve() |
|
|
|
|
}).catch(res => { |
|
|
|
|
reject() |
|
|
|
|
}) |
|
|
|
|
})) |
|
|
|
|
}) |
|
|
|
|
Promise.all(promiseList).then(res => { |
|
|
|
|
this.saveSuccess() |
|
|
|
|
}).catch(err => { |
|
|
|
|
this.submited = false |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} else { // 保存表 |
|
|
|
|
this.$post(this.api.saveTable, tableIds).then(res => { |
|
|
|
|
this.saveSuccess() |
|
|
|
|
}).catch(res => { |
|
|
|
|
this.submited = false |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
@ -211,4 +554,32 @@ export default { |
|
|
|
|
cursor: pointer; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/deep/.source-list { |
|
|
|
|
display: flex; |
|
|
|
|
justify-content: space-between; |
|
|
|
|
li { |
|
|
|
|
display: inline-flex; |
|
|
|
|
justify-content: center; |
|
|
|
|
align-items: center; |
|
|
|
|
height: 100px; |
|
|
|
|
border: 1px solid #ccc; |
|
|
|
|
border-radius: 8px; |
|
|
|
|
cursor: pointer; |
|
|
|
|
img { |
|
|
|
|
width: 90%; |
|
|
|
|
} |
|
|
|
|
&:hover { |
|
|
|
|
border-color: #007eff; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.upload { |
|
|
|
|
width: 48%; |
|
|
|
|
.el-upload { |
|
|
|
|
width: 100%; |
|
|
|
|
&:focus { |
|
|
|
|
color: #007eff; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
</style> |