mirror of https://github.com/veops/cmdb.git
add batch module
This commit is contained in:
parent
13476128d5
commit
61f77cf311
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div id="title">
|
||||||
|
<ci-type-choice @getCiTypeAttr="showCiType">
|
||||||
|
</ci-type-choice>
|
||||||
|
</div>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="18">
|
||||||
|
<a-card style="height: 605px">
|
||||||
|
<a-button class="ant-btn-primary" style="margin-left: 10px;" :disabled="uploadFlag" id="upload-button" @click="uploadData">上传</a-button>
|
||||||
|
<upload-file-form v-if="displayUpload" ref="fileEditor"></upload-file-form>
|
||||||
|
<ci-table v-if="editorOnline" :ciTypeAttrs="ciTypeAttrs" ref="onlineEditor"></ci-table>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<div style="min-height: 604px; background: white">
|
||||||
|
<a-card title="上传结果">
|
||||||
|
<upload-result v-if="beginLoad" :upLoadData="needDataList" :ciType="ciType" :unique-field="uniqueField"></upload-result>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CiTypeChoice from './modules/CiTypeChoice'
|
||||||
|
import CiTable from './modules/CiTable'
|
||||||
|
import UploadFileForm from './modules/UploadFileForm'
|
||||||
|
import UploadResult from './modules/UploadResult'
|
||||||
|
import { filterNull } from '@/api/cmdb/batch'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Batch',
|
||||||
|
components: {
|
||||||
|
CiTypeChoice,
|
||||||
|
CiTable,
|
||||||
|
UploadFileForm,
|
||||||
|
UploadResult
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
editorOnline: false,
|
||||||
|
uploadFlag: true,
|
||||||
|
ciTypeAttrs: [],
|
||||||
|
needDataList: [],
|
||||||
|
ciType: -1,
|
||||||
|
uniqueField: '',
|
||||||
|
uniqueId: 0,
|
||||||
|
beginLoad: false,
|
||||||
|
displayUpload: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showCiType (message) {
|
||||||
|
this.ciTypeAttrs = message
|
||||||
|
this.ciType = message.type_id
|
||||||
|
this.uniqueField = message.unique
|
||||||
|
this.uniqueId = message.unique_id
|
||||||
|
this.editorOnline = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.editorOnline = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
uploadData () {
|
||||||
|
if (this.ciType < 0) {
|
||||||
|
alert('尚未选择模板类型!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.beginLoad = false
|
||||||
|
const fileData = this.$refs.fileEditor.dataList
|
||||||
|
if (fileData.length > 0) {
|
||||||
|
this.needDataList = filterNull(fileData)
|
||||||
|
} else {
|
||||||
|
this.needDataList = filterNull(this.$refs.onlineEditor.getDataList())
|
||||||
|
}
|
||||||
|
this.displayUpload = false
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.beginLoad = true
|
||||||
|
this.displayUpload = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div id="hotTable" class="hotTable" style="overflow: hidden; height:275px">
|
||||||
|
<HotTable :root="root" ref="HTable" :settings="hotSettings"></HotTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { HotTable } from '@handsontable-pro/vue'
|
||||||
|
export default {
|
||||||
|
name: 'Editor',
|
||||||
|
components: {
|
||||||
|
HotTable
|
||||||
|
},
|
||||||
|
props: { ciTypeAttrs: { type: Object, required: true } },
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
root: 'test-hot',
|
||||||
|
dataTitle: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hotSettings () {
|
||||||
|
const whiteColumn = []
|
||||||
|
const aliasList = []
|
||||||
|
const dataTitle = []
|
||||||
|
this.$props.ciTypeAttrs.attributes.forEach(item => {
|
||||||
|
dataTitle.push(item.name)
|
||||||
|
aliasList.push(item.alias)
|
||||||
|
whiteColumn.push('')
|
||||||
|
})
|
||||||
|
this.dataTitle = dataTitle
|
||||||
|
const dt = {
|
||||||
|
data: [whiteColumn],
|
||||||
|
startRows: 11,
|
||||||
|
startCols: 6,
|
||||||
|
minRows: 5,
|
||||||
|
minCols: 4,
|
||||||
|
maxRows: 90,
|
||||||
|
maxCols: 90,
|
||||||
|
rowHeaders: true,
|
||||||
|
// minSpareCols: 2, //列留白
|
||||||
|
colHeaders: aliasList,
|
||||||
|
minSpareRows: 2, // 行留白
|
||||||
|
// autoWrapRow: true, // 自动换行
|
||||||
|
// 自定义右键菜单,可汉化,默认布尔值
|
||||||
|
contextMenu: {
|
||||||
|
items: {
|
||||||
|
row_above: {
|
||||||
|
name: '上方插入一行'
|
||||||
|
},
|
||||||
|
row_below: {
|
||||||
|
name: '下方插入一行'
|
||||||
|
},
|
||||||
|
moverow: {
|
||||||
|
name: '删除行'
|
||||||
|
},
|
||||||
|
unfreeze_column: {
|
||||||
|
name: '取消列固定'
|
||||||
|
},
|
||||||
|
hsep1: '---------', // 提供分隔线
|
||||||
|
hsep2: '---------'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// width: '100%',
|
||||||
|
// fillHandle: true, // 选中拖拽复制 possible values: true, false, "horizontal", "vertical"
|
||||||
|
fixedColumnsLeft: 0, // 固定左边列数
|
||||||
|
fixedRowsTop: 0, // 固定上边列数
|
||||||
|
manualColumnFreeze: true, // 手动固定列
|
||||||
|
// manualColumnMove: true, // 手动移动列
|
||||||
|
// manualRowMove: true, // 手动移动行
|
||||||
|
// manualColumnResize: true, // 手工更改列距
|
||||||
|
// manualRowResize: true, // 手动更改行距
|
||||||
|
comments: true, // 添加注释
|
||||||
|
customBorders: [], // 添加边框
|
||||||
|
columnSorting: true, // 排序
|
||||||
|
stretchH: 'all', // 根据宽度横向扩展,last:只扩展最后一列,none:默认不扩展
|
||||||
|
afterChange: function (changes, source) {
|
||||||
|
if (changes !== null) {
|
||||||
|
document.getElementById('upload-button').disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDataList () {
|
||||||
|
const data = this.$refs.HTable.$data.hotInstance.getData()
|
||||||
|
data.unshift(this.dataTitle)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@import '~handsontable/dist/handsontable.full.css';
|
||||||
|
</style>
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-form :form="form" style="max-width: 500px; margin: 30px auto 0;">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="18">
|
||||||
|
<a-form-item label="模板类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||||
|
<a-select
|
||||||
|
placeholder="--请选择模板类型--"
|
||||||
|
v-decorator="['ciTypes', { rules: [{required: true, message: '模板类型必须选择'}] }]"
|
||||||
|
@change="selectCiType"
|
||||||
|
>
|
||||||
|
<a-select-option v-for="ciType in ciTypeList" :key="ciType.name" :value="ciType.id">{{ ciType.alias }}</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="6">
|
||||||
|
<a-form-item>
|
||||||
|
<a-button
|
||||||
|
style="margin-left: 20px"
|
||||||
|
:disabled="downLoadButtonDis"
|
||||||
|
@click="downLoadExcel"
|
||||||
|
>下载模板</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
<a-divider />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getCITypes } from '@/api/cmdb/CIType'
|
||||||
|
import { getCITypeAttributesById } from '@/api/cmdb/CITypeAttr'
|
||||||
|
import { writeExcel } from '@/api/cmdb/batch'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CiTypeChoice',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
labelCol: { lg: { span: 5 }, sm: { span: 5 } },
|
||||||
|
wrapperCol: { lg: { span: 19 }, sm: { span: 19 } },
|
||||||
|
form: this.$form.createForm(this),
|
||||||
|
ciTypeList: [],
|
||||||
|
ciTypeName: '',
|
||||||
|
downLoadButtonDis: true,
|
||||||
|
selectNum: 0,
|
||||||
|
selectCiTypeAttrList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
getCITypes().then(res => {
|
||||||
|
this.ciTypeList = res.ci_types
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectCiType (el) {
|
||||||
|
// 当选择好模板类型时的回调函数
|
||||||
|
this.downLoadButtonDis = false
|
||||||
|
this.selectNum = el
|
||||||
|
getCITypeAttributesById(el).then(res => {
|
||||||
|
this.$emit('getCiTypeAttr', res)
|
||||||
|
this.selectCiTypeAttrList = res
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ciTypeList.forEach(item => {
|
||||||
|
if (this.selectNum === item.id) {
|
||||||
|
this.ciTypeName = item.alias || item.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
downLoadExcel () {
|
||||||
|
const columns = []
|
||||||
|
this.selectCiTypeAttrList.attributes.forEach(item => {
|
||||||
|
columns.push(item.alias)
|
||||||
|
})
|
||||||
|
const excel = writeExcel(columns, this.ciTypeName)
|
||||||
|
const tempLink = document.createElement('a')
|
||||||
|
tempLink.download = this.ciTypeName + '.xls'
|
||||||
|
tempLink.style.display = 'none'
|
||||||
|
const blob = new Blob([excel])
|
||||||
|
tempLink.href = URL.createObjectURL(blob)
|
||||||
|
document.body.appendChild(tempLink)
|
||||||
|
tempLink.click()
|
||||||
|
document.body.removeChild(tempLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.step-form-style-desc {
|
||||||
|
padding: 0 56px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
color: rgba(0, 0, 0, 0.45);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-form :form="form" style="max-width: 500px; margin: 40px auto 0;">
|
||||||
|
<a-upload-dragger ref="upload" :multiple="true" :customRequest="customRequest" accept=".xls">
|
||||||
|
<p class="ant-upload-drag-icon">
|
||||||
|
<a-icon type="inbox" />
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-text">点击或拖拽文件至此上传!</p>
|
||||||
|
<p class="ant-upload-hint">支持文件类型:xls</p>
|
||||||
|
</a-upload-dragger>
|
||||||
|
</a-form>
|
||||||
|
<a-divider>or</a-divider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { processFile } from '@/api/cmdb/batch'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Step2',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
labelCol: { lg: { span: 5 }, sm: { span: 5 } },
|
||||||
|
wrapperCol: { lg: { span: 19 }, sm: { span: 19 } },
|
||||||
|
form: this.$form.createForm(this),
|
||||||
|
loading: false,
|
||||||
|
timer: 0,
|
||||||
|
ciItemNum: 0,
|
||||||
|
dataList: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
customRequest (data) {
|
||||||
|
processFile(data.file).then(res => {
|
||||||
|
this.ciItemNum = res.length - 1
|
||||||
|
document.getElementById('upload-button').disabled = false
|
||||||
|
this.dataList = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange (info) {
|
||||||
|
document.getElementById('load-button').disabled = false
|
||||||
|
console.log(info)
|
||||||
|
},
|
||||||
|
clear () {
|
||||||
|
console.log(this.$refs.upload.$children[0].onSuccess('', ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.stepFormText {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
.ant-form-item-label,
|
||||||
|
.ant-form-item-control {
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4>共 <span style="color: blue">{{ total }}</span> 条,已完成 <span style="color: lightgreen">{{ complete }}</span> 条
|
||||||
|
,失败 <span style="color: red">{{ errorNum }} </span>条</h4>
|
||||||
|
|
||||||
|
<a-progress :percent="mPercent"/>
|
||||||
|
<div class="my-box">
|
||||||
|
<span>错误信息:</span>
|
||||||
|
<ol>
|
||||||
|
<li :key="item" v-for="item in errorItems">{{ item }}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { uploadData } from '@/api/cmdb/batch'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Result',
|
||||||
|
props: {
|
||||||
|
upLoadData: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
ciType: {
|
||||||
|
required: true,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
uniqueField: {
|
||||||
|
required: true,
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
complete: 0,
|
||||||
|
errorNum: 0,
|
||||||
|
errorItems: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
document.getElementById('upload-button').disabled = true
|
||||||
|
this.upload2Server()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mpercent () {
|
||||||
|
return Math.round(this.complete / this.total * 10000) / 100
|
||||||
|
},
|
||||||
|
progressStatus () {
|
||||||
|
if (this.complete === this.total) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return 'active'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
upload2Server () {
|
||||||
|
this.total = this.$props.upLoadData.length - 1
|
||||||
|
for (let i = 0; i < this.total; i++) {
|
||||||
|
const item = {}
|
||||||
|
let itemUniqueName = 'unknown'
|
||||||
|
for (let j = 0; j < this.$props.upLoadData[0].length; j++) {
|
||||||
|
item[this.$props.upLoadData[0][j]] = this.$props.upLoadData[i + 1][j]
|
||||||
|
if (this.$props.upLoadData[0][j] === this.$props.uniqueField) {
|
||||||
|
itemUniqueName = this.$props.upLoadData[i + 1][j] || 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploadData(this.$props.ciType, item).then(res => {
|
||||||
|
console.log(res)
|
||||||
|
}).catch(err => {
|
||||||
|
this.errorNum += 1
|
||||||
|
console.log(err)
|
||||||
|
this.errorItems.push(itemUniqueName + ': ' + (((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'))
|
||||||
|
})
|
||||||
|
this.complete += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.my-box {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: red;
|
||||||
|
border: 1px red dashed;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius:5px;
|
||||||
|
height: 429px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue