Merge pull request #172 from veops/dev_ui

新建ci及批量导入时,新建关系
This commit is contained in:
pycook 2023-09-07 11:04:49 +08:00 committed by GitHub
commit 058585504f
10 changed files with 292 additions and 58 deletions

View File

@ -1,5 +1,7 @@
/* eslint-disable */ /* eslint-disable */
import _ from 'lodash' import _ from 'lodash'
import XLSX from 'xlsx'
import XLSXS from 'xlsx-js-style'
export function sum(arr) { export function sum(arr) {
if (!arr.length) { if (!arr.length) {
return 0 return 0
@ -150,3 +152,25 @@ export const toThousands = (num = 0) => {
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
}) })
} }
export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH:mm:ss')}.xls`) => {
// STEP 1: Create a new workbook
const wb = XLSXS.utils.book_new()
// STEP 2: Create data rows and styles
const rowArray = data
// STEP 3: Create worksheet with rows; Add worksheet to workbook
const ws = XLSXS.utils.aoa_to_sheet(rowArray)
XLSXS.utils.book_append_sheet(wb, ws, fileName)
let maxColumnNumber = 1 // 默认最大列数
rowArray.forEach(item => { if (item.length > maxColumnNumber) { maxColumnNumber = item.length } })
// 添加列宽
ws['!cols'] = (rowArray[0].map(item => {
return { width: 22 }
}))
// // 添加行高
// ws['!rows'] = [{ 'hpt': 80 }]
// STEP 4: Write Excel file to browser #导出
XLSXS.writeFile(wb, fileName + '.xlsx')
}

View File

@ -14,20 +14,83 @@
}}</a-select-option> }}</a-select-option>
</a-select> </a-select>
<a-button <a-button
@click="downLoadExcel" @click="openModal"
:disabled="!selectNum" :disabled="!selectNum"
type="primary" type="primary"
class="ops-button-primary" class="ops-button-primary"
icon="download" icon="download"
>下载模板</a-button >下载模板</a-button
> >
<a-modal
:bodyStyle="{ paddingTop: 0 }"
width="800px"
:title="`${ciTypeName}`"
:visible="visible"
@cancel="handleCancel"
@ok="handleOk"
wrapClassName="ci-type-choice-modal"
>
<a-divider orientation="left">模型属性</a-divider>
<a-checkbox
@change="changeCheckAll"
:style="{ marginBottom: '20px' }"
:indeterminate="indeterminate"
:checked="checkAll"
>
全选
</a-checkbox>
<br />
<a-checkbox-group v-model="checkedAttrs">
<a-row>
<a-col :span="6" v-for="item in selectCiTypeAttrList.attributes" :key="item.alias || item.name">
<a-checkbox :disabled="item.name === selectCiTypeAttrList.unique" :value="item.alias || item.name">
{{ item.alias || item.name }}
<span style="color: red" v-if="item.name === selectCiTypeAttrList.unique">*</span>
</a-checkbox>
</a-col>
</a-row>
</a-checkbox-group>
<template v-if="parentsType && parentsType.length">
<a-divider orientation="left">模型关联</a-divider>
<a-row :gutter="[24, 24]" align="top" type="flex">
<a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id">
<a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)">
</a-checkbox>
<span
:style="{
display: 'inline-block',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
width: '80px',
margin: '0 5px',
textAlign: 'right',
}"
:title="item.alias || item.name"
>{{ item.alias || item.name }}</span
>
<a-select :style="{ flex: 1 }" size="small" v-model="parentsForm[item.alias || item.name].attr">
<a-select-option
:title="attr.alias || attr.name"
v-for="attr in item.attributes"
:key="attr.alias || attr.name"
:value="attr.alias || attr.name"
>
{{ attr.alias || attr.name }}
</a-select-option>
</a-select>
</a-col>
</a-row>
</template>
</a-modal>
</a-space> </a-space>
</template> </template>
<script> <script>
import { downloadExcel } from '../../../utils/helper'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
import { writeExcel } from '@/modules/cmdb/api/batch' import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
export default { export default {
name: 'CiTypeChoice', name: 'CiTypeChoice',
@ -37,6 +100,13 @@ export default {
ciTypeName: '', ciTypeName: '',
selectNum: 0, selectNum: 0,
selectCiTypeAttrList: [], selectCiTypeAttrList: [],
visible: false,
checkedAttrs: [],
indeterminate: false,
checkAll: true,
parentsType: [],
parentsForm: {},
checkedParents: [],
} }
}, },
created: function() { created: function() {
@ -44,6 +114,18 @@ export default {
this.ciTypeList = res.ci_types this.ciTypeList = res.ci_types
}) })
}, },
watch: {
checkedAttrs() {
if (this.checkedAttrs.length < this.selectCiTypeAttrList.attributes.length) {
this.indeterminate = true
this.checkAll = false
}
if (this.checkedAttrs.length === this.selectCiTypeAttrList.attributes.length) {
this.indeterminate = false
this.checkAll = true
}
},
},
methods: { methods: {
selectCiType(el) { selectCiType(el) {
// 当选择好模板类型时的回调函数 // 当选择好模板类型时的回调函数
@ -60,24 +142,70 @@ export default {
}) })
}, },
downLoadExcel() { openModal() {
const columns = [] getCITypeParent(this.selectNum).then((res) => {
this.selectCiTypeAttrList.attributes.forEach((item) => { this.parentsType = res.parents
columns.push(item.alias) const _parentsForm = {}
res.parents.forEach((item) => {
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
_parentsForm[item.alias || item.name] = { attr: _find?.alias || _find?.name, value: '' }
})
this.parentsForm = _parentsForm
this.checkedParents = []
this.visible = true
this.checkedAttrs = this.selectCiTypeAttrList.attributes.map((item) => item.alias || item.name)
}) })
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)
}, },
filterOption(input, option) { filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}, },
handleCancel() {
this.visible = false
},
handleOk() {
const columns1 = this.checkedAttrs.map((item) => {
return {
v: item,
t: 's',
s: {
numFmt: 'string',
},
}
})
const columns2 = this.checkedParents.map((p) => {
return {
v: `$${p}.${this.parentsForm[p].attr}`,
t: 's',
s: {
font: {
color: {
rgb: 'FF0000',
},
},
},
}
})
downloadExcel([[...columns1, ...columns2]], this.ciTypeName)
this.handleCancel()
},
changeCheckAll(e) {
if (e.target.checked) {
this.checkedAttrs = this.selectCiTypeAttrList.attributes.map((item) => item.alias || item.name)
} else {
const _find = this.selectCiTypeAttrList.attributes.find(
(item) => item.name === this.selectCiTypeAttrList.unique
)
this.checkedAttrs = [_find?.alias || _find?.name]
}
},
clickParent(item) {
const _idx = this.checkedParents.findIndex((p) => p === (item.alias || item.name))
if (_idx > -1) {
this.checkedParents.splice(_idx, 1)
} else {
this.checkedParents.push(item.alias || item.name)
}
},
}, },
} }
</script> </script>
@ -105,3 +233,15 @@ export default {
} }
} }
</style> </style>
<style lang="less">
.ci-type-choice-modal {
.ant-checkbox-disabled .ant-checkbox-inner {
border-color: #2f54eb !important;
background-color: #2f54eb;
}
.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner::after {
border-color: #fff;
}
}
</style>

View File

@ -40,15 +40,25 @@ export default {
}, },
computed: { computed: {
columns() { columns() {
const _columns = []
if (this.ciTypeAttrs.attributes) { if (this.ciTypeAttrs.attributes) {
return this.ciTypeAttrs.attributes.map((item) => { _columns.push(
return { ...this.ciTypeAttrs.attributes.map((item) => {
title: item.alias || item.name, return {
field: item.alias || item.name, title: item.alias || item.name,
field: item.alias || item.name,
}
})
)
}
if (this.uploadData && this.uploadData.length) {
Object.keys(this.uploadData[0]).forEach((key) => {
if (key.startsWith('$')) {
_columns.push({ title: key, field: key })
} }
}) })
} }
return [] return _columns
}, },
dataSource() { dataSource() {
return _.cloneDeep(this.uploadData) return _.cloneDeep(this.uploadData)

View File

@ -4,13 +4,13 @@
ref="upload" ref="upload"
:multiple="false" :multiple="false"
:customRequest="customRequest" :customRequest="customRequest"
accept=".xls" accept=".xls,.xlsx"
:showUploadList="false" :showUploadList="false"
:fileList="fileList" :fileList="fileList"
> >
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> <img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
<p class="ant-upload-text">点击或拖拽文件至此上传</p> <p class="ant-upload-text">点击或拖拽文件至此上传</p>
<p class="ant-upload-hint">支持文件类型xls</p> <p class="ant-upload-hint">支持文件类型xlsxlsx</p>
</a-upload-dragger> </a-upload-dragger>
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file"> <div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span> <span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>

View File

@ -209,13 +209,19 @@
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" /> <EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" />
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a @click="$refs.detail.create(row.ci_id || row._id)"> <a-space>
<a-icon type="unordered-list" /> <a @click="$refs.detail.create(row.ci_id || row._id)">
</a> <a-icon type="unordered-list" />
<a-divider type="vertical" /> </a>
<a @click="deleteCI(row)" :style="{ color: 'red' }"> <a-tooltip title="添加关系">
<a-icon type="delete" /> <a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
</a> <a-icon type="retweet" />
</a>
</a-tooltip>
<a @click="deleteCI(row)" :style="{ color: 'red' }">
<a-icon type="delete" />
</a>
</a-space>
</template> </template>
</vxe-column> </vxe-column>
<template #empty> <template #empty>

View File

@ -38,7 +38,7 @@
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />关系</span> <span slot="tab"><a-icon type="branches" />关系</span>
<div :style="{ padding: '24px' }"> <div :style="{ padding: '24px' }">
<CiDetailRelation :ciId="ciId" :typeId="typeId" :ci="ci" /> <CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_3"> <a-tab-pane key="tab_3">
@ -147,8 +147,14 @@ export default {
}, },
inject: ['reload', 'handleSearch', 'attrList'], inject: ['reload', 'handleSearch', 'attrList'],
methods: { methods: {
create(ciId) { create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.visible = true this.visible = true
this.activeTabKey = activeTabKey
if (activeTabKey === 'tab_2') {
this.$nextTick(() => {
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
})
}
this.ciId = ciId this.ciId = ciId
this.getAttributes() this.getAttributes()
this.getCI() this.getCI()

View File

@ -23,6 +23,30 @@
:attributeList="attributeList" :attributeList="attributeList"
/> />
</template> </template>
<template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">模型关系</a-divider>
<a-form>
<a-row :gutter="24" align="top" type="flex">
<a-col :span="12" v-for="item in parentsType" :key="item.id">
<a-form-item :label="item.alias || item.name" :colon="false">
<a-input-group compact style="width: 100%">
<a-select v-model="parentsForm[item.name].attr">
<a-select-option
:title="attr.alias || attr.name"
v-for="attr in item.attributes"
:key="attr.name"
:value="attr.name"
>
{{ attr.alias || attr.name }}
</a-select-option>
</a-select>
<a-input placeholder="多个值使用,分割" v-model="parentsForm[item.name].value" style="width: 50%" />
</a-input-group>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
</template> </template>
<template v-if="action === 'update'"> <template v-if="action === 'update'">
<a-form :form="form"> <a-form :form="form">
@ -110,6 +134,7 @@ import { addCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import { valueTypeMap } from '../../../utils/const' import { valueTypeMap } from '../../../utils/const'
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue' import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
import { getCITypeParent } from '@/modules/cmdb/api/CITypeRelation'
export default { export default {
name: 'CreateInstanceForm', name: 'CreateInstanceForm',
@ -138,6 +163,8 @@ export default {
batchUpdateLists: [], batchUpdateLists: [],
editAttr: null, editAttr: null,
attributesByGroup: [], attributesByGroup: [],
parentsType: [],
parentsForm: {},
} }
}, },
computed: { computed: {
@ -231,6 +258,11 @@ export default {
} }
}) })
values.ci_type = _this.typeId values.ci_type = _this.typeId
Object.keys(this.parentsForm).forEach((type) => {
if (this.parentsForm[type].value) {
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
}
})
addCI(values).then((res) => { addCI(values).then((res) => {
_this.$message.success('新增成功!') _this.$message.success('新增成功!')
_this.visible = false _this.visible = false
@ -249,6 +281,17 @@ export default {
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => { Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
this.batchUpdateLists = [{ name: this.attributeList[0].name }] this.batchUpdateLists = [{ name: this.attributeList[0].name }]
}) })
if (action === 'create') {
getCITypeParent(this.typeId).then((res) => {
this.parentsType = res.parents
const _parentsForm = {}
res.parents.forEach((item) => {
const _find = item.attributes.find((attr) => attr.id === item.unique_id)
_parentsForm[item.name] = { attr: _find.name, value: '' }
})
this.parentsForm = _parentsForm
})
}
}) })
}, },
getFieldType(name) { getFieldType(name) {

View File

@ -244,14 +244,13 @@ export default {
}, },
}, },
mounted() { mounted() {
console.log(this.ci)
this.init(true) this.init(true)
}, },
methods: { methods: {
async init(isFirst) { async init(isFirst) {
await Promise.all([this.getParentCITypes(), this.getChildCITypes()]) await Promise.all([this.getParentCITypes(), this.getChildCITypes()])
Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => { Promise.all([this.getFirstCIs(), this.getSecondCIs()]).then(() => {
if (isFirst) { if (isFirst && this.$refs.ciDetailRelationTopo) {
this.$refs.ciDetailRelationTopo.setTopoData(this.topoData) this.$refs.ciDetailRelationTopo.setTopoData(this.topoData)
} }
}) })
@ -395,12 +394,6 @@ export default {
margin-top: 20px; margin-top: 20px;
margin-bottom: 5px; margin-bottom: 5px;
color: #303133; color: #303133;
> a {
display: none;
}
&:hover > a {
display: inline-block;
}
} }
} }
</style> </style>

View File

@ -243,17 +243,23 @@
/> />
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a @click="$refs.detail.create(row.ci_id || row._id)"> <a-space>
<a-icon type="unordered-list" /> <a @click="$refs.detail.create(row.ci_id || row._id)">
</a> <a-icon type="unordered-list" />
<template v-if="isLeaf"> </a>
<a-divider type="vertical" /> <a-tooltip title="添加关系">
<a-tooltip title="删除实例"> <a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
<a @click="deleteCI(row)" :style="{ color: 'red' }"> <a-icon type="retweet" />
<a-icon type="delete" />
</a> </a>
</a-tooltip> </a-tooltip>
</template> <template v-if="isLeaf">
<a-tooltip title="删除实例">
<a @click="deleteCI(row)" :style="{ color: 'red' }">
<a-icon type="delete" />
</a>
</a-tooltip>
</template>
</a-space>
</template> </template>
</vxe-column> </vxe-column>
<template #empty> <template #empty>

View File

@ -297,17 +297,23 @@
<EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs" /> <EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs" />
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a @click="$refs.detail.create(row.ci_id || row._id)"> <a-space>
<a-icon type="unordered-list" /> <a @click="$refs.detail.create(row.ci_id || row._id)">
</a> <a-icon type="unordered-list" />
<template> </a>
<a-divider type="vertical" /> <a-tooltip title="添加关系">
<a-tooltip title="删除实例"> <a @click="$refs.detail.create(row.ci_id || row._id, 'tab_2', '2')">
<a @click="deleteCI(row)" :style="{ color: 'red' }"> <a-icon type="retweet" />
<a-icon type="delete" />
</a> </a>
</a-tooltip> </a-tooltip>
</template> <template>
<a-tooltip title="删除实例">
<a @click="deleteCI(row)" :style="{ color: 'red' }">
<a-icon type="delete" />
</a>
</a-tooltip>
</template>
</a-space>
</template> </template>
</vxe-table-column> </vxe-table-column>
<template #empty> <template #empty>