first commit
This commit is contained in:
@@ -0,0 +1,634 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
#app {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 64px);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
width: calc(100% - 20px);
|
||||
min-height: calc(100vh - 84px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.box-card .el-card__body {
|
||||
min-height: calc(100vh - 170px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.box-card-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 170px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-body-center {
|
||||
width: 60%;
|
||||
min-width: 800px;
|
||||
min-height: calc(100vh - 170px);
|
||||
position: relative;
|
||||
padding-bottom: 70px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card-body-center-bottom {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-body-center-bottom button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.card-body-center > .el-carousel {
|
||||
height: calc(100vh - 300px);
|
||||
min-height: 560px;
|
||||
position: relative;
|
||||
overflow-y: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.card-body-center > .el-carousel > .el-carousel__container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.carousel-table-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.carousel-table-item-center {
|
||||
height: 100%;
|
||||
width: 90%;
|
||||
min-width: 800px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-table-item-center .el-card__body {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.check-group {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
padding: 0 10px 5px 0;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.check-group .el-radio,
|
||||
.check-group .el-checkbox {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
margin: 0 !important;
|
||||
margin-top: 15px !important;
|
||||
}
|
||||
|
||||
.check-group .el-checkbox__label,
|
||||
.check-group .el-radio__label {
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
|
||||
.option-extra {
|
||||
float: right;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.carousel-table-item-relation .el-card__body,
|
||||
.carousel-table-item-form .el-card__body {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.carousel-table-item-form .el-card__body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.result-card .el-card__body {
|
||||
min-height: 170px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 12px;
|
||||
color: rgb(100, 100, 100);
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="box-card-title">DataKid</span>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="card-body-center">
|
||||
<el-steps :active="active" align-center>
|
||||
<el-step title="选择数据表" icon="el-icon-document-copy"></el-step>
|
||||
<el-step title="选择数据列" icon="el-icon-s-unfold"></el-step>
|
||||
<el-step title="数据列对应" icon="el-icon-s-operation"></el-step>
|
||||
<el-step title="提交数据" icon="el-icon-upload"></el-step>
|
||||
<el-step title="导入结果" icon="el-icon-bell"></el-step>
|
||||
</el-steps>
|
||||
|
||||
<el-carousel indicator-position="none" :autoplay="false" arrow="never" ref="carousel" direction="vertical">
|
||||
<el-carousel-item class="carousel-table-item">
|
||||
<el-card class="carousel-table-item-center">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model.trim="tableKeyword"
|
||||
placeholder="请输入关键字查询"
|
||||
clearable>
|
||||
</el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="filterTables"></el-button>
|
||||
<el-button v-if="formData.tableName" style="float: right; padding: 10px 0" type="text">
|
||||
当前选中:{{ formData.tableName }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-radio-group class="check-group" v-model="formData.tableName">
|
||||
<el-radio
|
||||
v-for="item in filteredTables"
|
||||
:key="item.table_name"
|
||||
:label="item.table_name"
|
||||
border>
|
||||
{{ item.table_name }}{{ item.table_comment ? " ( " + item.table_comment + " )" : "" }}
|
||||
<span class="option-extra">rows: {{ item.table_rows || 0 }}</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-card>
|
||||
</el-carousel-item>
|
||||
|
||||
<el-carousel-item class="carousel-table-item">
|
||||
<el-card class="carousel-table-item-center">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-input
|
||||
style="width: 200px"
|
||||
v-model.trim="columnKeyword"
|
||||
placeholder="请输入关键字查询"
|
||||
clearable>
|
||||
</el-input>
|
||||
<el-button type="primary" icon="el-icon-search" @click="filterColumns"></el-button>
|
||||
<el-button style="float: right; padding: 10px 0" type="text">
|
||||
{{ formData.tableName }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-checkbox-group class="check-group" v-model="formData.columns">
|
||||
<el-checkbox
|
||||
v-for="item in filteredColumns"
|
||||
:key="item.column_name"
|
||||
:label="item.column_name"
|
||||
border>
|
||||
{{ item.column_name }}{{ item.column_comment ? " ( " + item.column_comment + " )" : "" }}
|
||||
<span class="option-extra">{{ item.column_type }}</span>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-card>
|
||||
</el-carousel-item>
|
||||
|
||||
<el-carousel-item class="carousel-table-item">
|
||||
<el-card style="width: 100%" class="carousel-table-item-relation">
|
||||
<el-table :data="relation" height="100%" style="width: 100%" border size="medium">
|
||||
<el-table-column prop="column_name" label="列名"></el-table-column>
|
||||
<el-table-column prop="column_comment" label="描述" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="is_nullable" label="可为空"></el-table-column>
|
||||
<el-table-column prop="column_type" label="类型"></el-table-column>
|
||||
<el-table-column prop="column_key" label="key"></el-table-column>
|
||||
<el-table-column min-width="200px">
|
||||
<template slot="header">
|
||||
目标列
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
title="提示"
|
||||
trigger="hover"
|
||||
content="Excel 对应的表头名称,用于和表数据列对应">
|
||||
<i slot="reference" class="el-icon-question"></i>
|
||||
</el-popover>
|
||||
</template>
|
||||
<template slot-scope="{ row }">
|
||||
<el-input
|
||||
v-model.trim="row.relation"
|
||||
size="medium"
|
||||
placeholder="目标列">
|
||||
</el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-carousel-item>
|
||||
|
||||
<el-carousel-item class="carousel-table-item">
|
||||
<el-card style="width: 100%" class="carousel-table-item-form">
|
||||
<el-form label-width="120px" :model="formData" ref="form" :rules="formRules">
|
||||
<el-form-item label=" " label-width="135px" class="view-header"></el-form-item>
|
||||
|
||||
<el-form-item prop="method" label="导入方式">
|
||||
<el-radio-group v-model="formData.method">
|
||||
<el-radio-button :label="1">追加或更新</el-radio-button>
|
||||
<el-radio-button :label="2">仅追加</el-radio-button>
|
||||
<el-radio-button :label="3">仅更新</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="[1, 3].includes(formData.method)" prop="field" label="关键字段">
|
||||
<el-select v-model="formData.field" clearable filterable placeholder="请选择" style="width: 50%">
|
||||
<el-option
|
||||
v-for="item in relation"
|
||||
:key="item.column_name"
|
||||
:label="item.column_name"
|
||||
:value="item.column_name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="plugins" label="可选插件">
|
||||
<el-checkbox-group v-model="formData.plugins">
|
||||
<el-checkbox
|
||||
v-for="item in plugins"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
border>
|
||||
{{ item.description }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="文  件" class="is-required">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
drag
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleFileRemove">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<div slot="tip" class="el-upload__tip" style="color: #F56C6C">只能上传 xls/xlsx 文件</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-carousel-item>
|
||||
|
||||
<el-carousel-item style="padding: 10px">
|
||||
<el-card class="result-card">
|
||||
<el-progress type="circle" :percentage="resultPercentage" :status="resultProgressStatus"></el-progress>
|
||||
<div class="result-text">{{ resultSummary }}</div>
|
||||
<div style="margin-top: 10px" v-if="result.cacheKey">
|
||||
<el-link
|
||||
type="danger"
|
||||
icon="el-icon-download"
|
||||
style="font-size: 13px;"
|
||||
:href="loc() + '/exportErrors?cacheKey=' + result.cacheKey">
|
||||
下载错误记录
|
||||
</el-link>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
<div class="card-body-center-bottom">
|
||||
<el-button size="medium" @click="prev" :disabled="active === 0 || loading">上一步</el-button>
|
||||
<el-button type="primary" size="medium" :loading="loading" @click="active < 4 ? next() : carry()">
|
||||
{{ active < 4 ? primaryButtonText : "完成" }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
loading: false,
|
||||
tableKeyword: "",
|
||||
columnKeyword: "",
|
||||
tables: [],
|
||||
filteredTables: [],
|
||||
columns: [],
|
||||
filteredColumns: [],
|
||||
relation: [],
|
||||
plugins: [],
|
||||
fileList: [],
|
||||
cacheTableName: "",
|
||||
result: {
|
||||
total: 0,
|
||||
success: 0,
|
||||
cacheKey: ""
|
||||
},
|
||||
formRules: {
|
||||
method: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
field: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
|
||||
},
|
||||
formData: {
|
||||
tableName: "",
|
||||
columns: [],
|
||||
method: 1,
|
||||
field: "",
|
||||
plugins: []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
primaryButtonText() {
|
||||
if (this.active === 3 && this.formData.method === 3) {
|
||||
return "更新"
|
||||
}
|
||||
return "下一步"
|
||||
},
|
||||
resultSummary() {
|
||||
const total = this.result.total || 0
|
||||
const success = this.result.success || 0
|
||||
const fail = Math.max(total - success, 0)
|
||||
if (fail > 0) {
|
||||
return "总记录数 " + total + " 条,成功 " + success + " 条,失败 " + fail + " 条!"
|
||||
}
|
||||
return "总记录数 " + total + " 条,成功 " + success + " 条!"
|
||||
},
|
||||
resultProgressStatus() {
|
||||
const total = this.result.total || 0
|
||||
const success = this.result.success || 0
|
||||
if (total > 0 && success < total) {
|
||||
return "exception"
|
||||
}
|
||||
return "success"
|
||||
},
|
||||
resultPercentage() {
|
||||
if (!this.result.total) {
|
||||
return 0
|
||||
}
|
||||
return Number(((this.result.success / this.result.total) * 100).toFixed(2))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loc,
|
||||
normalizeRecord(item) {
|
||||
const normalized = {}
|
||||
Object.keys(item || {}).forEach(key => {
|
||||
normalized[(key || "").toLowerCase()] = item[key]
|
||||
})
|
||||
return normalized
|
||||
},
|
||||
async loadTables() {
|
||||
const resp = await this.$axios.post(loc() + "/tableNames")
|
||||
if (resp.code === 0) {
|
||||
this.tables = Array.isArray(resp.data)
|
||||
? resp.data.map(item => this.normalizeRecord(item))
|
||||
: []
|
||||
this.filteredTables = this.tables.slice()
|
||||
}
|
||||
},
|
||||
async loadPlugins() {
|
||||
this.plugins = await this.$businessTool.getEnumOptions("SysDataImportPlugin")
|
||||
},
|
||||
async loadColumns() {
|
||||
const resp = await this.$axios.post(loc() + "/columns", {
|
||||
tableName: this.formData.tableName
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.columns = Array.isArray(resp.data)
|
||||
? resp.data.map(item => this.normalizeRecord(item))
|
||||
: []
|
||||
this.filteredColumns = this.columns.slice()
|
||||
}
|
||||
},
|
||||
filterTables() {
|
||||
const keyword = (this.tableKeyword || "").toLowerCase()
|
||||
if (!keyword) {
|
||||
this.filteredTables = this.tables.slice()
|
||||
return
|
||||
}
|
||||
this.filteredTables = this.tables.filter(item => {
|
||||
const tableName = (item.table_name || "").toLowerCase()
|
||||
const tableComment = (item.table_comment || "").toLowerCase()
|
||||
return tableName.includes(keyword) || tableComment.includes(keyword)
|
||||
})
|
||||
},
|
||||
filterColumns() {
|
||||
const keyword = (this.columnKeyword || "").toLowerCase()
|
||||
if (!keyword) {
|
||||
this.filteredColumns = this.columns.slice()
|
||||
return
|
||||
}
|
||||
this.filteredColumns = this.columns.filter(item => {
|
||||
const columnName = (item.column_name || "").toLowerCase()
|
||||
const columnComment = (item.column_comment || "").toLowerCase()
|
||||
return columnName.includes(keyword) || columnComment.includes(keyword)
|
||||
})
|
||||
},
|
||||
buildRelation() {
|
||||
this.relation = this.columns
|
||||
.filter(item => this.formData.columns.includes(item.column_name))
|
||||
.map(item => Object.assign({}, item, { relation: "" }))
|
||||
},
|
||||
async next() {
|
||||
if (this.active === 0) {
|
||||
if (!this.formData.tableName) {
|
||||
this.$notify({
|
||||
title: "警告",
|
||||
message: "请选择数据表",
|
||||
type: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.cacheTableName !== this.formData.tableName) {
|
||||
await this.loadColumns()
|
||||
this.formData.columns = []
|
||||
this.formData.field = ""
|
||||
this.cacheTableName = this.formData.tableName
|
||||
}
|
||||
} else if (this.active === 1) {
|
||||
if (!this.formData.columns.length) {
|
||||
this.$notify({
|
||||
title: "警告",
|
||||
message: "请选择数据列",
|
||||
type: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.buildRelation()
|
||||
} else if (this.active === 2) {
|
||||
if (this.relation.find(item => !item.relation)) {
|
||||
this.$notify({
|
||||
title: "警告",
|
||||
message: "存在未填写的目标列",
|
||||
type: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if (this.active === 3) {
|
||||
const success = await this.doSubmit()
|
||||
if (!success) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.$refs.carousel.next()
|
||||
this.active++
|
||||
},
|
||||
prev() {
|
||||
if (this.active <= 0) {
|
||||
return
|
||||
}
|
||||
this.$refs.carousel.prev()
|
||||
this.active--
|
||||
},
|
||||
validateSubmitForm() {
|
||||
let validForm = true
|
||||
this.$refs.form.validate(valid => {
|
||||
validForm = valid
|
||||
})
|
||||
if (!validForm || !this.fileList.length) {
|
||||
this.$notify({
|
||||
title: "警告",
|
||||
message: "存在未填写的必填项",
|
||||
type: "warning"
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
async doSubmit() {
|
||||
if (!this.validateSubmitForm()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("tableName", this.formData.tableName)
|
||||
formData.append("method", this.formData.method)
|
||||
formData.append("field", this.formData.field || "")
|
||||
formData.append("relation", JSON.stringify(this.relation))
|
||||
formData.append("plugins", JSON.stringify(this.formData.plugins || []))
|
||||
formData.append("file", this.fileList[0].raw, this.fileList[0].name)
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post(loc() + "/importData", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
})
|
||||
|
||||
if (resp.code !== 0) {
|
||||
this.$notify.error({
|
||||
title: "错误",
|
||||
message: resp.msg
|
||||
})
|
||||
this.result = { total: 0, success: 0, cacheKey: "" }
|
||||
return false
|
||||
}
|
||||
|
||||
this.result = resp.data || { total: 0, success: 0, cacheKey: "" }
|
||||
return true
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleFileRemove(file, fileList) {
|
||||
this.fileList = fileList
|
||||
},
|
||||
handleFileChange(file, fileList) {
|
||||
const extension = ((file.name || "").split(".").pop() || "").toLowerCase()
|
||||
const remove = () => {
|
||||
const removeIndex = fileList.findIndex(item => item.uid === file.uid)
|
||||
if (removeIndex > -1) {
|
||||
fileList.splice(removeIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (file.size === 0) {
|
||||
this.$message.warning("您选择的是空文件")
|
||||
remove()
|
||||
} else if (!["xls", "xlsx"].includes(extension)) {
|
||||
this.$message.warning("上传文件只能是 xls/xlsx 格式")
|
||||
remove()
|
||||
}
|
||||
|
||||
this.fileList = fileList
|
||||
},
|
||||
carry() {
|
||||
this.active = 0
|
||||
this.tableKeyword = ""
|
||||
this.columnKeyword = ""
|
||||
this.columns = []
|
||||
this.filteredColumns = []
|
||||
this.relation = []
|
||||
this.fileList = []
|
||||
this.cacheTableName = ""
|
||||
this.result = {
|
||||
total: 0,
|
||||
success: 0,
|
||||
cacheKey: ""
|
||||
}
|
||||
this.formData = {
|
||||
tableName: "",
|
||||
columns: [],
|
||||
method: 1,
|
||||
field: "",
|
||||
plugins: []
|
||||
}
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
this.filterTables()
|
||||
this.$refs.carousel.setActiveItem(0)
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadTables()
|
||||
await this.loadPlugins()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Reference in New Issue
Block a user