mirror of
https://github.com/veops/cmdb.git
synced 2025-09-17 01:56:53 +08:00
1117 lines
37 KiB
Python
1117 lines
37 KiB
Python
<template>
|
||
<div :style="{ marginBottom: '-24px' }">
|
||
<div v-if="!subscribeTreeViewCiTypesLoading && subscribeTreeViewCiTypes.length === 0">
|
||
<a-alert :message="$t('cmdb.tree.tips1')" banner></a-alert>
|
||
</div>
|
||
<div class="tree-views" v-else>
|
||
<SplitPane
|
||
:min="200"
|
||
:max="500"
|
||
:paneLengthPixel.sync="paneLengthPixel"
|
||
appName="cmdb-tree-views"
|
||
:triggerLength="18"
|
||
calcBasedParent
|
||
>
|
||
<template #one>
|
||
<div class="tree-views-left" :style="{ height: `${windowHeight - 64}px` }">
|
||
<draggable
|
||
v-model="subscribeTreeViewCiTypes"
|
||
:animation="300"
|
||
@change="
|
||
(e) => {
|
||
orderChange(e, subscribeTreeViewCiTypes)
|
||
}
|
||
"
|
||
>
|
||
<div v-for="ciType in subscribeTreeViewCiTypes" :key="ciType.type_id">
|
||
<div
|
||
@click="handleChangeCi(ciType.type_id)"
|
||
:class="{
|
||
'custom-header': true,
|
||
'custom-header-selected': Number(ciType.type_id) === Number(typeId) && !treeKeys.length,
|
||
}"
|
||
>
|
||
<OpsMoveIcon class="move-icon" />
|
||
<span class="tree-views-left-header-icon">
|
||
<template v-if="ciType.icon">
|
||
<img
|
||
v-if="ciType.icon.split('$$')[2]"
|
||
:src="`/api/common-setting/v1/file/${ciType.icon.split('$$')[3]}`"
|
||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||
/>
|
||
<ops-icon
|
||
v-else
|
||
:style="{
|
||
color: ciType.icon.split('$$')[1],
|
||
fontSize: '14px',
|
||
}"
|
||
:type="ciType.icon.split('$$')[0]"
|
||
/>
|
||
</template>
|
||
<span :style="{ color: '#2f54eb' }" v-else>{{ ciType.name[0].toUpperCase() }}</span>
|
||
</span>
|
||
<span class="tree-views-left-header-name">{{ ciType.alias || ciType.name }}</span>
|
||
<div class="actions">
|
||
<a-tooltip :title="$t('cmdb.preference.cancelSub')">
|
||
<div class="action" @click="(e) => cancelSubscribe(e, ciType)">
|
||
<a-icon type="star" />
|
||
</div>
|
||
</a-tooltip>
|
||
<a-tooltip :title="$t('cmdb.tree.subSettings')">
|
||
<div class="action" @click="(e) => subscribeSetting(e, ciType)">
|
||
<a-icon type="setting" />
|
||
</div>
|
||
</a-tooltip>
|
||
</div>
|
||
</div>
|
||
<a-tree
|
||
:selectedKeys="selectedKeys"
|
||
:tree-data="treeData"
|
||
:load-data="onLoadData"
|
||
:expandedKeys="expandedKeys"
|
||
v-if="Number(ciType.type_id) === Number(typeId)"
|
||
>
|
||
<template #title="{ key: treeKey, title, isLeaf, childLength}">
|
||
<TreeViewsNode
|
||
:title="title"
|
||
:treeKey="treeKey"
|
||
:levels="levels"
|
||
:childLength="childLength"
|
||
:isLeaf="isLeaf"
|
||
@onNodeClick="onNodeClick"
|
||
/>
|
||
</template>
|
||
</a-tree>
|
||
</div>
|
||
</draggable>
|
||
</div>
|
||
</template>
|
||
<template #two>
|
||
<div class="tree-views-right" id="tree-views-right" :style="{ height: `${windowHeight - 64}px` }">
|
||
<div class="cmdb-views-header">
|
||
<span>
|
||
<span class="cmdb-views-header-title">{{ currentCiTypeName }}</span>
|
||
<span
|
||
@click="
|
||
() => {
|
||
$refs.metadataDrawer.open(typeId)
|
||
}
|
||
"
|
||
class="cmdb-views-header-metadata"
|
||
><a-icon type="info-circle" />
|
||
{{ $t('cmdb.ci.attributeDesc') }}
|
||
</span>
|
||
</span>
|
||
<a-space>
|
||
<a-button
|
||
type="primary"
|
||
class="ops-button-ghost"
|
||
ghost
|
||
@click="$refs.create.handleOpen(true, 'create')"
|
||
><ops-icon type="veops-increase" />
|
||
{{ $t('create') }}
|
||
</a-button>
|
||
<EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs">
|
||
<a-button
|
||
type="primary"
|
||
ghost
|
||
class="ops-button-ghost"
|
||
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
|
||
>
|
||
</EditAttrsPopover>
|
||
</a-space>
|
||
</div>
|
||
<SearchForm
|
||
ref="search"
|
||
@refresh="reloadData"
|
||
:preferenceAttrList="currentAttrList"
|
||
:typeId="Number(typeId)"
|
||
@copyExpression="copyExpression"
|
||
>
|
||
<PreferenceSearch
|
||
v-show="!selectedRowKeys.length"
|
||
ref="preferenceSearch"
|
||
@getQAndSort="getQAndSort"
|
||
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
|
||
/>
|
||
<div class="ops-list-batch-action">
|
||
<template v-if="selectedRowKeys.length">
|
||
<span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span>
|
||
<a-divider type="vertical" />
|
||
<span @click="openBatchDownload">{{ $t('download') }}</span>
|
||
<a-divider type="vertical" />
|
||
<span @click="batchDelete">{{ $t('delete') }}</span>
|
||
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
|
||
</template>
|
||
</div>
|
||
</SearchForm>
|
||
|
||
<CITable
|
||
ref="xTable"
|
||
:id="`cmdb-tree-${typeId}`"
|
||
:loading="loading"
|
||
:attrList="currentAttrList"
|
||
:columns="columns"
|
||
:passwordValue="passwordValue"
|
||
:data="instanceList"
|
||
:height="`${windowHeight - 240}px`"
|
||
:loadingTip="loadTip"
|
||
@onSelectChange="onSelectChange"
|
||
@edit-closed="handleEditClose"
|
||
@edit-actived="handleEditActived"
|
||
@sort-change="handleSortCol"
|
||
@openDetail="openDetail"
|
||
@deleteCI="deleteCI"
|
||
/>
|
||
|
||
<div :style="{ textAlign: 'right', marginTop: '4px' }">
|
||
<a-pagination
|
||
:showSizeChanger="true"
|
||
:current="currentPage"
|
||
size="small"
|
||
:total="totalNumber"
|
||
show-quick-jumper
|
||
:page-size="pageSize"
|
||
:page-size-options="pageSizeOptions"
|
||
:show-total="
|
||
(total, range) =>
|
||
$t('pagination.total', {
|
||
range0: range[0],
|
||
range1: range[1],
|
||
total,
|
||
})
|
||
"
|
||
:style="{ alignSelf: 'flex-end' }"
|
||
@showSizeChange="onShowSizeChange"
|
||
@change="
|
||
(page) => {
|
||
currentPage = page
|
||
handleLoadInstance({ sortByTable })
|
||
}
|
||
"
|
||
>
|
||
<template slot="buildOptionText" slot-scope="props">
|
||
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
|
||
</template>
|
||
</a-pagination>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</SplitPane>
|
||
</div>
|
||
<SubscribeSetting
|
||
ref="subscribeSetting"
|
||
@reload="
|
||
() => {
|
||
reload()
|
||
}
|
||
"
|
||
/>
|
||
<CiDetailDrawer ref="detail" :typeId="Number(typeId)" :treeViewsLevels="treeViewsLevels" />
|
||
<create-instance-form
|
||
ref="create"
|
||
:typeIdFromRelation="Number(typeId)"
|
||
@reload="sumbitFromCreateInstance"
|
||
@submit="batchUpdateFromCreateInstance"
|
||
/>
|
||
<BatchDownload ref="batchDownload" @batchDownload="batchDownload" />
|
||
<MetadataDrawer ref="metadataDrawer" />
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
/* eslint-disable no-useless-escape */
|
||
import _ from 'lodash'
|
||
import Sortable from 'sortablejs'
|
||
import draggable from 'vuedraggable'
|
||
import {
|
||
getSubscribeTreeView,
|
||
getSubscribeAttributes,
|
||
subscribeTreeView,
|
||
preferenceCitypeOrder,
|
||
} from '@/modules/cmdb/api/preference'
|
||
import { searchCI, updateCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||
import { getCITableColumns } from '../../utils/helper'
|
||
import SearchForm from '../../components/searchForm/SearchForm.vue'
|
||
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
|
||
import SplitPane from '@/components/SplitPane'
|
||
import TreeViewsNode from './modules/treeViewsNode.vue'
|
||
import EditAttrsPopover from '../ci/modules/editAttrsPopover.vue'
|
||
import CiDetailDrawer from '../ci/modules/ciDetailDrawer.vue'
|
||
import CreateInstanceForm from '../ci/modules/CreateInstanceForm'
|
||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||
import BatchDownload from '../../components/batchDownload/batchDownload.vue'
|
||
import PreferenceSearch from '../../components/preferenceSearch/preferenceSearch.vue'
|
||
import MetadataDrawer from '../ci/modules/MetadataDrawer.vue'
|
||
import { intersection } from '@/utils/functions/set'
|
||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||
import { getAttrPassword } from '../../api/CITypeAttr'
|
||
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||
|
||
export default {
|
||
name: 'TreeViews',
|
||
components: {
|
||
SearchForm,
|
||
SubscribeSetting,
|
||
SplitPane,
|
||
TreeViewsNode,
|
||
EditAttrsPopover,
|
||
CiDetailDrawer,
|
||
CreateInstanceForm,
|
||
BatchDownload,
|
||
PreferenceSearch,
|
||
MetadataDrawer,
|
||
OpsMoveIcon,
|
||
draggable,
|
||
CITable
|
||
},
|
||
data() {
|
||
return {
|
||
keySplit: '---',
|
||
treeData: [],
|
||
treeNode: null,
|
||
treeKeys: [],
|
||
subscribeTreeViewCiTypes: [],
|
||
subscribeTreeViewCiTypesLoading: false,
|
||
levels: [],
|
||
typeId: null,
|
||
instanceList: [],
|
||
columns: [],
|
||
loading: false,
|
||
loadTip: '',
|
||
pageSizeOptions: ['50', '100', '200', '100000'],
|
||
pageSize: 50,
|
||
currentPage: 1,
|
||
totalNumber: 0,
|
||
currentAttrList: [],
|
||
trigger: false,
|
||
newLoad: true,
|
||
formatSearchFormData: '',
|
||
sortByTable: undefined,
|
||
paneLengthPixel: 205,
|
||
expandedKeys: [],
|
||
attrList: [],
|
||
attributes: {},
|
||
selectedRowKeys: [],
|
||
// 对照是否编辑
|
||
initialInstanceList: [],
|
||
citypes: [],
|
||
// 表格拖拽的参数
|
||
tableDragClassName: [],
|
||
// 已经设置过data的node
|
||
isSetDataNodes: [],
|
||
|
||
initialPasswordValue: {},
|
||
passwordValue: {},
|
||
lastEditCiId: null,
|
||
isContinueCloseEdit: true,
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
windowHeight() {
|
||
return this.$store.state.windowHeight
|
||
},
|
||
selectedKeys() {
|
||
if (this.treeKeys.length <= 1) {
|
||
return this.treeKeys.map((item) => `${this.keySplit}${item}`)
|
||
}
|
||
return [this.treeKeys.join(this.keySplit)]
|
||
},
|
||
treeViewsLevels() {
|
||
// 当前订阅的树型视图的字段
|
||
const _find = this.subscribeTreeViewCiTypes.find((item) => item.type_id === Number(this.typeId))
|
||
return _find?.levels || []
|
||
},
|
||
treeViewId() {
|
||
// 当前页面的id
|
||
const _find = this.subscribeTreeViewCiTypes.find((item) => item.type_id === Number(this.typeId))
|
||
return _find?.id
|
||
},
|
||
currentCiTypeName() {
|
||
const _find = this.citypes.find((item) => Number(item.id) === Number(this.typeId))
|
||
return _find?.alias || _find?.name || ''
|
||
},
|
||
},
|
||
watch: {
|
||
'$route.path': function(newPath, oldPath) {
|
||
this.newLoad = true
|
||
this.typeId = this.$route.params.typeId
|
||
this.initPage()
|
||
},
|
||
},
|
||
provide() {
|
||
return {
|
||
handleSearch: this.handleLoadInstance,
|
||
setPreferenceSearchCurrent: this.setPreferenceSearchCurrent,
|
||
attrList: () => {
|
||
return this.attrList
|
||
},
|
||
attributes: () => {
|
||
return this.attributes
|
||
},
|
||
filterCompPreferenceSearch: () => {
|
||
return { ptv_id: this.treeViewId }
|
||
},
|
||
}
|
||
},
|
||
inject: ['reload'],
|
||
async created() {
|
||
await this.getTreeViews()
|
||
},
|
||
mounted() {
|
||
setTimeout(() => {
|
||
this.columnDrop()
|
||
}, 1000)
|
||
getCITypes().then((res) => {
|
||
this.citypes = res.ci_types
|
||
})
|
||
},
|
||
beforeDestroy() {
|
||
if (this.sortable) {
|
||
this.sortable.destroy()
|
||
}
|
||
},
|
||
methods: {
|
||
async getAttributeList() {
|
||
const res = await getCITypeAttributesById(Number(this.typeId))
|
||
this.attrList = res.attributes
|
||
this.attributes = res
|
||
},
|
||
async getTreeViews() {
|
||
this.subscribeTreeViewCiTypesLoading = true
|
||
const res = await getSubscribeTreeView()
|
||
this.subscribeTreeViewCiTypesLoading = false
|
||
this.subscribeTreeViewCiTypes = res
|
||
if (this.subscribeTreeViewCiTypes.length) {
|
||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
this.levels = res.find((item) => item.type_id.toString() === this.typeId.toString()).levels
|
||
await this.initPage()
|
||
}
|
||
},
|
||
|
||
async initPage() {
|
||
this.treeNode = null
|
||
this.treeKeys = []
|
||
this.levels = []
|
||
this.currentPage = 1
|
||
this.totalNumber = 0
|
||
this.instanceList = []
|
||
this.selectedRowKeys = []
|
||
this.expandedKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
await this.loadCurrentView()
|
||
await this.getAttributeList()
|
||
await this.loadAttrList()
|
||
await this.handleLoadInstance()
|
||
},
|
||
|
||
async loadCurrentView() {
|
||
if (this.subscribeTreeViewCiTypes.length) {
|
||
this.typeId = this.$route.params.typeId || this.subscribeTreeViewCiTypes[0].type_id
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
this.levels = this.subscribeTreeViewCiTypes.find(
|
||
(item) => item.type_id.toString() === this.typeId.toString()
|
||
).levels
|
||
}
|
||
},
|
||
|
||
async loadAttrList() {
|
||
const res = await getSubscribeAttributes(this.typeId)
|
||
this.currentAttrList = res.attributes
|
||
},
|
||
|
||
async handleLoadInstance(params = {}) {
|
||
this.trigger = true
|
||
this.loading = true
|
||
let q = `_type:${this.typeId}`
|
||
|
||
if (this.treeKeys.length > 0) {
|
||
// 再增加垂直分类信息
|
||
this.treeKeys.forEach((item, idx) => {
|
||
q += `,${this.levels[idx].name}:${item}`
|
||
})
|
||
}
|
||
if (this.formatSearchFormData) {
|
||
q = `${q}${this.formatSearchFormData}`
|
||
}
|
||
|
||
const expression = this.$refs['search'] ? this.$refs['search'].expression || '' : ''
|
||
|
||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||
const regSort = /(?<=sort=).+/g
|
||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||
if (exp) {
|
||
q = `${q},${exp}`
|
||
}
|
||
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||
if (fuzzySearch) {
|
||
q = `${q},*${fuzzySearch}*`
|
||
}
|
||
const payload = { q }
|
||
|
||
// 如果是表格点击的排序 以表格为准
|
||
let sort
|
||
const { sortByTable } = params
|
||
if (sortByTable) {
|
||
sort = sortByTable
|
||
} else {
|
||
sort = expression.match(regSort) ? expression.match(regSort)[0] : undefined
|
||
}
|
||
payload.sort = sort
|
||
|
||
if (this.levels.length > this.treeKeys.length) {
|
||
// 增加切面信息
|
||
payload['facet'] = `${this.levels[this.treeKeys.length].name}`
|
||
}
|
||
payload['page'] = this.currentPage
|
||
payload['count'] = this.pageSize
|
||
|
||
try {
|
||
const res = await searchCI(payload)
|
||
this.totalNumber = res.numfound
|
||
|
||
if (Object.values(res.facet).length) {
|
||
this.wrapTreeData(res.facet)
|
||
}
|
||
|
||
const jsonAttrList = this.currentAttrList.filter((attr) => attr.value_type === '6')
|
||
this.instanceList = res['result'].map((item) => {
|
||
jsonAttrList.forEach(
|
||
(jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||
)
|
||
return { ..._.cloneDeep(item) }
|
||
})
|
||
this.initialInstanceList = _.cloneDeep(this.instanceList)
|
||
const treeViewsRight = document.getElementById('tree-views-right')
|
||
if (treeViewsRight) {
|
||
const width = treeViewsRight.clientWidth - 50
|
||
this.columns = getCITableColumns(res.result, this.currentAttrList, width)
|
||
this.columns.forEach((col) => {
|
||
if (col.is_password) {
|
||
this.initialPasswordValue[col.field] = ''
|
||
this.passwordValue[col.field] = ''
|
||
}
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.log(e)
|
||
this.$message.error(e)
|
||
} finally {
|
||
this.loading = false
|
||
this.$nextTick(() => {
|
||
this.trigger = false
|
||
if (this.$refs.xTable) {
|
||
this.$refs.xTable.getVxetableRef().refreshColumn()
|
||
}
|
||
})
|
||
}
|
||
this.newLoad = false
|
||
},
|
||
|
||
wrapTreeData(facet) {
|
||
// 切面
|
||
const _treeData = Object.values(facet)[0].map((item) => {
|
||
let title = item[0]
|
||
const attr = this.attrList.find((attr) => attr.name === item[2])
|
||
if (attr?.choice_value?.length) {
|
||
const choice = attr.choice_value.find((choice) => item[0] === choice?.[0])
|
||
if (choice?.[1]?.label) {
|
||
title = choice[1].label
|
||
}
|
||
}
|
||
|
||
return {
|
||
title: title,
|
||
childLength: item[1],
|
||
key: this.treeKeys.join(this.keySplit) + this.keySplit + item[0],
|
||
isLeaf: this.levels.length - 1 === this.treeKeys.length,
|
||
}
|
||
})
|
||
if (this.treeNode === null && this.newLoad) {
|
||
this.treeData = _treeData
|
||
this.treeNode = { dataRef: {} }
|
||
} else {
|
||
if (!this.isSetDataNodes.includes(this.treeNode.dataRef.key)) {
|
||
this.treeNode.dataRef.children = _treeData
|
||
this.treeData = [...this.treeData]
|
||
this.isSetDataNodes.push(this.treeNode.dataRef.key)
|
||
}
|
||
}
|
||
},
|
||
onLoadData(treeNode) {
|
||
this.triggerSelect = false
|
||
return new Promise((resolve) => {
|
||
if (treeNode.dataRef.children) {
|
||
resolve()
|
||
return
|
||
}
|
||
this.treeKeys = treeNode.eventKey.split(this.keySplit).filter((item) => item !== '')
|
||
this.treeNode = treeNode
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
resolve()
|
||
})
|
||
},
|
||
handleChangeCi(value) {
|
||
if (value && Number(this.typeId) !== Number(value)) {
|
||
this.treeData = []
|
||
this.$router.history.push({
|
||
name: 'cmdb_tree_views_item',
|
||
params: { typeId: Number(value) },
|
||
})
|
||
this.typeId = Number(value)
|
||
} else {
|
||
this.typeId = null
|
||
this.$nextTick(() => {
|
||
this.typeId = Number(value)
|
||
this.newLoad = true
|
||
this.initPage()
|
||
})
|
||
}
|
||
this.isSetDataNodes = []
|
||
},
|
||
async reloadData() {
|
||
const queryParams = this.$refs['search'].queryParam || {}
|
||
this.formatSearchFormData = this.mergeQ(queryParams)
|
||
this.currentPage = 1
|
||
this.sortByTable = undefined
|
||
const xTable = this.$refs.xTable.getVxetableRef()
|
||
xTable.clearSort().then(() => {
|
||
this.handleLoadInstance()
|
||
})
|
||
},
|
||
mergeQ(params) {
|
||
let q = ''
|
||
Object.keys(params).forEach((key) => {
|
||
if (!['pageNo', 'pageSize', 'sortField', 'sortOrder'].includes(key) && params[key] + '' !== '') {
|
||
if (typeof params[key] === 'object' && params[key] && params[key].length > 1) {
|
||
q += `,${key}:(${params[key].join(';')})`
|
||
} else if (params[key]) {
|
||
q += `,${key}:*${params[key]}*`
|
||
}
|
||
}
|
||
})
|
||
return q
|
||
},
|
||
cancelSubscribe(e, ciType) {
|
||
e.stopPropagation()
|
||
e.preventDefault()
|
||
const that = this
|
||
this.$confirm({
|
||
title: that.$t('warning'),
|
||
content: (h) => (
|
||
<div>{that.$t('cmdb.preference.confirmcancelSub2', { name: ciType.alias || ciType.name })}</div>
|
||
),
|
||
onOk() {
|
||
subscribeTreeView(ciType.type_id, []).then(() => {
|
||
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
|
||
if (Number(that.$route.params.typeId) === Number(ciType.type_id)) {
|
||
that.$router.history.push('/cmdb/treeviews')
|
||
that.reload()
|
||
} else {
|
||
that.reload()
|
||
}
|
||
})
|
||
},
|
||
})
|
||
},
|
||
subscribeSetting(e, ciType) {
|
||
e.stopPropagation()
|
||
e.preventDefault()
|
||
this.$refs.subscribeSetting.open(ciType)
|
||
},
|
||
columnDrop() {
|
||
this.$nextTick(() => {
|
||
const xTable = this.$refs.xTable.getVxetableRef()
|
||
this.sortable = Sortable.create(
|
||
xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'),
|
||
{
|
||
handle: '.vxe-handle',
|
||
onChoose: () => {
|
||
const header = xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row')
|
||
const classNameList = []
|
||
header.childNodes.forEach((item) => {
|
||
classNameList.push(item.classList[1])
|
||
})
|
||
this.tableDragClassName = classNameList
|
||
},
|
||
onEnd: (params) => {
|
||
// 由于开启了虚拟滚动,newIndex和oldIndex是虚拟的
|
||
const { newIndex, oldIndex } = params
|
||
// 从tableDragClassName拿到colid
|
||
const fromColid = this.tableDragClassName[oldIndex]
|
||
const toColid = this.tableDragClassName[newIndex]
|
||
const fromColumn = xTable.getColumnById(fromColid)
|
||
const toColumn = xTable.getColumnById(toColid)
|
||
const fromIndex = xTable.getColumnIndex(fromColumn)
|
||
const toIndex = xTable.getColumnIndex(toColumn)
|
||
const tableColumn = xTable.getColumns()
|
||
const currRow = tableColumn.splice(fromIndex, 1)[0]
|
||
tableColumn.splice(toIndex, 0, currRow)
|
||
xTable.loadColumn(tableColumn)
|
||
},
|
||
}
|
||
)
|
||
})
|
||
},
|
||
handleSortCol({ column, property, order, sortBy, sortList, $event }) {
|
||
let sortByTable
|
||
if (order === 'asc') {
|
||
sortByTable = property
|
||
} else if (order === 'desc') {
|
||
sortByTable = `-${property}`
|
||
}
|
||
this.sortByTable = sortByTable
|
||
this.currentPage = 1
|
||
this.handleLoadInstance({ sortByTable })
|
||
},
|
||
onNodeClick(keys, type) {
|
||
console.log(keys)
|
||
if (keys) {
|
||
const _tempKeys = keys.split(this.keySplit).filter((item) => item !== '')
|
||
if (_tempKeys.length === this.levels.length) {
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
this.selectedRowKeys = []
|
||
}
|
||
this.treeKeys = _tempKeys
|
||
}
|
||
const idx = this.expandedKeys.findIndex((item) => item === keys)
|
||
if (idx > -1) {
|
||
this.expandedKeys.splice(idx, 1)
|
||
} else {
|
||
this.expandedKeys.push(keys)
|
||
}
|
||
this.handleLoadInstance()
|
||
},
|
||
async refreshAfterEditAttrs() {
|
||
await this.loadAttrList()
|
||
await this.handleLoadInstance()
|
||
},
|
||
deleteCI(record) {
|
||
const that = this
|
||
this.$confirm({
|
||
title: that.$t('warning'),
|
||
content: that.$t('confirmDelete'),
|
||
onOk() {
|
||
deleteCI(record.ci_id || record._id).then((res) => {
|
||
that.$message.success(that.$t('deleteSuccess'))
|
||
that.reload()
|
||
})
|
||
},
|
||
})
|
||
},
|
||
onSelectChange(records) {
|
||
this.selectedRowKeys = records.map((i) => i.ci_id || i._id)
|
||
},
|
||
setSelectRows() {
|
||
const cached = new Set(this.selectedRowKeys)
|
||
const loaded = new Set(this.instanceList.map((i) => i.ci_id || i._id))
|
||
|
||
const inter = Array.from(intersection(cached, loaded))
|
||
|
||
if (inter.length === this.instanceList.length) {
|
||
this.$refs['xTable'].getVxetableRef().setAllCheckboxRow(true)
|
||
} else {
|
||
const rows = []
|
||
inter.forEach((rid) => {
|
||
rows.push(this.$refs['xTable'].getVxetableRef().getRowById(rid))
|
||
})
|
||
this.$refs['xTable'].getVxetableRef().setCheckboxRow(rows, true)
|
||
}
|
||
},
|
||
handleEditActived() {
|
||
const passwordCol = this.columns.filter((col) => col.is_password)
|
||
this.$nextTick(() => {
|
||
const editRecord = this.$refs.xTable.getVxetableRef().getEditRecord()
|
||
const { row, column } = editRecord
|
||
if (passwordCol.length && this.lastEditCiId !== row._id) {
|
||
this.$nextTick(async () => {
|
||
for (let i = 0; i < passwordCol.length; i++) {
|
||
await getAttrPassword(row._id, passwordCol[i].attr_id).then((res) => {
|
||
this.initialPasswordValue[passwordCol[i].field] = res.value
|
||
this.passwordValue[passwordCol[i].field] = res.value
|
||
})
|
||
}
|
||
this.isContinueCloseEdit = false
|
||
await this.$refs.xTable.getVxetableRef().clearEdit()
|
||
this.isContinueCloseEdit = true
|
||
this.$nextTick(() => {
|
||
this.$refs.xTable.getVxetableRef().setEditCell(row, column.field)
|
||
})
|
||
})
|
||
}
|
||
this.lastEditCiId = row._id
|
||
})
|
||
},
|
||
handleEditClose({ row, rowIndex, column }) {
|
||
if (!this.isContinueCloseEdit) {
|
||
return
|
||
}
|
||
const $table = this.$refs['xTable'].getVxetableRef()
|
||
const data = {}
|
||
this.columns.forEach((item) => {
|
||
if (
|
||
!(item.field in this.initialPasswordValue) &&
|
||
!_.isEqual(row[item.field], this.initialInstanceList[rowIndex][item.field])
|
||
) {
|
||
data[item.field] = row[item.field] ?? null
|
||
}
|
||
})
|
||
Object.keys(this.initialPasswordValue).forEach((key) => {
|
||
if (this.initialPasswordValue[key] !== this.passwordValue[key]) {
|
||
data[key] = this.passwordValue[key]
|
||
row[key] = this.passwordValue[key]
|
||
}
|
||
})
|
||
this.lastEditCiId = null
|
||
if (JSON.stringify(data) !== '{}') {
|
||
updateCI(row._id, data)
|
||
.then(() => {
|
||
this.$message.success(this.$t('saveSuccess'))
|
||
const arr1 = this.treeViewsLevels.map((item) => item.name)
|
||
const arr2 = Object.keys(data)
|
||
const arr3 = arr1.filter((item) => {
|
||
return arr2.includes(item)
|
||
})
|
||
if (arr3.length) {
|
||
this.reload()
|
||
return
|
||
}
|
||
$table.reloadRow(row, null)
|
||
const _initialInstanceList = _.cloneDeep(this.initialInstanceList)
|
||
_initialInstanceList[rowIndex] = {
|
||
..._initialInstanceList[rowIndex],
|
||
...data,
|
||
}
|
||
this.initialInstanceList = _initialInstanceList
|
||
})
|
||
.catch((err) => {
|
||
console.log(err)
|
||
$table.revertData(row)
|
||
})
|
||
}
|
||
this.columns.forEach((col) => {
|
||
if (col.is_password) {
|
||
this.initialPasswordValue[col.field] = ''
|
||
this.passwordValue[col.field] = ''
|
||
}
|
||
})
|
||
},
|
||
async openBatchDownload() {
|
||
this.$refs.batchDownload.open({ preferenceAttrList: this.currentAttrList.filter((attr) => !attr?.is_reference), ciTypeName: this.currentCiTypeName })
|
||
},
|
||
batchDownload({ filename, type, checkedKeys }) {
|
||
console.log(filename, type)
|
||
const jsonAttrList = []
|
||
checkedKeys.forEach((key) => {
|
||
const _find = this.currentAttrList.find((attr) => attr.name === key)
|
||
if (_find && _find.value_type === '6') jsonAttrList.push(key)
|
||
})
|
||
const data = _.cloneDeep([
|
||
...this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords(),
|
||
...this.$refs.xTable.getVxetableRef().getCheckboxRecords(true),
|
||
])
|
||
this.$refs.xTable.getVxetableRef().exportData({
|
||
filename,
|
||
type,
|
||
columnFilterMethod({ column }) {
|
||
return checkedKeys.includes(column.property)
|
||
},
|
||
data: [
|
||
...data.map((item) => {
|
||
jsonAttrList.forEach((jsonAttr) => (item[jsonAttr] = item[jsonAttr] ? JSON.stringify(item[jsonAttr]) : ''))
|
||
return { ...item }
|
||
}),
|
||
],
|
||
})
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
},
|
||
batchDelete() {
|
||
const that = this
|
||
this.$confirm({
|
||
title: that.$t('warning'),
|
||
content: that.$t('confirmDelete'),
|
||
onOk() {
|
||
that.batchDeleteAsync()
|
||
},
|
||
})
|
||
},
|
||
async batchDeleteAsync() {
|
||
let successNum = 0
|
||
let errorNum = 0
|
||
this.loading = true
|
||
this.loadTip = this.$t('cmdb.ci.batchDeleting')
|
||
const floor = Math.ceil(this.selectedRowKeys.length / 6)
|
||
for (let i = 0; i < floor; i++) {
|
||
const itemList = this.selectedRowKeys.slice(6 * i, 6 * i + 6)
|
||
const promises = itemList.map((x) => deleteCI(x, false))
|
||
await Promise.allSettled(promises)
|
||
.then((res) => {
|
||
res.forEach((r) => {
|
||
if (r.status === 'fulfilled') {
|
||
successNum += 1
|
||
} else {
|
||
errorNum += 1
|
||
}
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.loadTip = this.$t('cmdb.ci.batchDeleting2', {
|
||
total: this.selectedRowKeys.length,
|
||
successNum: successNum,
|
||
errorNum: errorNum,
|
||
})
|
||
})
|
||
}
|
||
this.loading = false
|
||
this.loadTip = ''
|
||
this.reload()
|
||
},
|
||
sumbitFromCreateInstance({ ci_id }) {
|
||
this.reload()
|
||
},
|
||
batchUpdateFromCreateInstance(values) {
|
||
const that = this
|
||
this.$confirm({
|
||
title: that.$t('warning'),
|
||
content: that.$t('cmdb.ci.batchUpdateConfirm'),
|
||
onOk() {
|
||
that.batchUpdateAsync(values)
|
||
},
|
||
})
|
||
},
|
||
async batchUpdateAsync(values) {
|
||
let successNum = 0
|
||
let errorNum = 0
|
||
this.loading = true
|
||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress')
|
||
const payload = {}
|
||
Object.keys(values).forEach((key) => {
|
||
if (values[key] || values[key] === 0) {
|
||
payload[key] = values[key]
|
||
}
|
||
// 字段值支持置空
|
||
// 目前存在字段值不支持置空,由后端返回
|
||
if (values[key] === undefined || values[key] === null) {
|
||
payload[key] = null
|
||
}
|
||
})
|
||
this.$refs.create.visible = false
|
||
const key = 'updatable'
|
||
let errorMsg = ''
|
||
for (let i = 0; i < this.selectedRowKeys.length; i++) {
|
||
await updateCI(this.selectedRowKeys[i], payload, false)
|
||
.then(() => {
|
||
successNum += 1
|
||
})
|
||
.catch((error) => {
|
||
errorMsg = errorMsg + '\n' + `${this.selectedRowKeys[i]}:${error.response?.data?.message ?? ''}`
|
||
this.$notification.warning({
|
||
key,
|
||
message: this.$t('warning'),
|
||
description: errorMsg,
|
||
duration: 0,
|
||
style: { whiteSpace: 'break-spaces' },
|
||
})
|
||
errorNum += 1
|
||
})
|
||
.finally(() => {
|
||
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress2', {
|
||
total: this.selectedRowKeys.length,
|
||
successNum: successNum,
|
||
errorNum: errorNum,
|
||
})
|
||
})
|
||
}
|
||
this.loading = false
|
||
this.loadTip = ''
|
||
const arr1 = this.treeViewsLevels.map((item) => item.name)
|
||
const arr2 = Object.keys(values)
|
||
const arr3 = arr1.filter((item) => {
|
||
return arr2.includes(item)
|
||
})
|
||
if (arr3.length) {
|
||
this.reload()
|
||
return
|
||
}
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
this.handleLoadInstance()
|
||
},
|
||
onShowSizeChange(current, pageSize) {
|
||
this.pageSize = pageSize
|
||
this.currentPage = 1
|
||
this.handleLoadInstance()
|
||
},
|
||
getQAndSort() {
|
||
const fuzzySearch = this.$refs['search'].fuzzySearch || ''
|
||
const expression = this.$refs['search'].expression || ''
|
||
this.$refs.preferenceSearch.savePreference({ fuzzySearch, expression })
|
||
},
|
||
setParamsFromPreferenceSearch(item) {
|
||
const { fuzzySearch, expression } = item.option
|
||
this.$refs.search.fuzzySearch = fuzzySearch
|
||
this.$refs.search.expression = expression
|
||
this.currentPage = 1
|
||
this.selectedRowKeys = []
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||
this.$nextTick(() => {
|
||
this.handleLoadInstance()
|
||
})
|
||
},
|
||
setPreferenceSearchCurrent(id = null) {
|
||
this.$refs.preferenceSearch.currentPreferenceSearch = id
|
||
},
|
||
copyExpression() {
|
||
const expression = this.$refs['search'].expression || ''
|
||
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||
|
||
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||
|
||
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||
const text = `q=_type:${this.typeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`
|
||
this.$copyText(text)
|
||
.then(() => {
|
||
this.$message.success(this.$t('copySuccess'))
|
||
})
|
||
.catch(() => {
|
||
this.$message.error(this.$t('cmdb.ci.copyFailed'))
|
||
})
|
||
},
|
||
orderChange(e, subscribeTreeViewCiTypes) {
|
||
preferenceCitypeOrder({ type_ids: subscribeTreeViewCiTypes.map((type) => type.type_id), is_tree: true }).catch(
|
||
() => {
|
||
this.getTreeViews()
|
||
}
|
||
)
|
||
},
|
||
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||
}
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="less">
|
||
@import '../index.less';
|
||
.tree-views {
|
||
width: 100%;
|
||
height: calc(100% - 32px);
|
||
.tree-views-left {
|
||
float: left;
|
||
position: relative;
|
||
overflow: hidden;
|
||
width: 100%;
|
||
&:hover {
|
||
overflow: auto;
|
||
}
|
||
.custom-header {
|
||
width: 100%;
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
justify-content: flex-start;
|
||
align-items: center;
|
||
padding: 8px 0 8px 12px;
|
||
cursor: move;
|
||
border-radius: 2px;
|
||
position: relative;
|
||
&:hover {
|
||
background-color: @primary-color_3;
|
||
> .actions,
|
||
> .move-icon {
|
||
display: inherit;
|
||
}
|
||
}
|
||
.move-icon {
|
||
width: 14px;
|
||
height: 20px;
|
||
cursor: move;
|
||
position: absolute;
|
||
display: none;
|
||
left: 0;
|
||
}
|
||
.tree-views-left-header-icon {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 2px;
|
||
margin-right: 6px;
|
||
}
|
||
.tree-views-left-header-name {
|
||
flex: 1;
|
||
font-weight: bold;
|
||
margin-left: 5px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: @text-color_1;
|
||
}
|
||
.actions {
|
||
display: none;
|
||
margin-left: auto;
|
||
cursor: pointer;
|
||
}
|
||
.action {
|
||
display: inline-block;
|
||
width: 22px;
|
||
text-align: center;
|
||
border-radius: 5px;
|
||
&:hover {
|
||
background-color: #cacaca;
|
||
}
|
||
}
|
||
}
|
||
.custom-header-selected {
|
||
background-color: @primary-color_3 !important;
|
||
}
|
||
.ant-tree li {
|
||
padding: 2px 0;
|
||
}
|
||
.ant-tree-switcher {
|
||
display: none;
|
||
}
|
||
.ant-tree-node-content-wrapper {
|
||
width: 100%;
|
||
padding: 4px 0;
|
||
display: inline-block;
|
||
height: 100%;
|
||
.ant-tree-title {
|
||
display: inline-block;
|
||
width: 100%;
|
||
padding: 0 6px;
|
||
}
|
||
}
|
||
.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||
background-color: @primary-color_3;
|
||
}
|
||
}
|
||
.tree-views-right {
|
||
background-color: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 20px;
|
||
overflow: auto;
|
||
width: 100%;
|
||
border-radius: @border-radius-box;
|
||
}
|
||
}
|
||
</style>
|