feat: add history export

This commit is contained in:
songlh 2024-07-16 13:45:31 +08:00
parent 579339d13c
commit 835df1bdeb
6 changed files with 689 additions and 546 deletions

View File

@ -1,89 +1,92 @@
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
export function getCIHistory(ciId) { export function getCIHistory(ciId) {
return axios({ return axios({
url: `/v0.1/history/ci/${ciId}`, url: `/v0.1/history/ci/${ciId}`,
method: 'GET' method: 'GET'
}) })
} }
export function getCIHistoryTable(params) { export function getCIHistoryTable(params) {
return axios({ return axios({
url: `/v0.1/history/records/attribute`, url: `/v0.1/history/records/attribute`,
method: 'GET', method: 'GET',
params: params params: params,
}) timeout: 30 * 1000
} })
}
export function getRelationTable(params) {
return axios({ export function getRelationTable(params) {
url: `/v0.1/history/records/relation`, return axios({
method: 'GET', url: `/v0.1/history/records/relation`,
params: params method: 'GET',
}) params: params,
} timeout: 30 * 1000
})
export function getCITypesTable(params) { }
return axios({
url: `/v0.1/history/ci_types`, export function getCITypesTable(params) {
method: 'GET', return axios({
params: params url: `/v0.1/history/ci_types`,
}) method: 'GET',
} params: params,
timeout: 30 * 1000
export function getUsers(params) { })
return axios({ }
url: `/v1/acl/users/employee`,
method: 'GET', export function getUsers(params) {
params: params return axios({
}) url: `/v1/acl/users/employee`,
} method: 'GET',
params: params
export function getCiTriggers(params) { })
return axios({ }
url: `/v0.1/history/ci_triggers`,
method: 'GET', export function getCiTriggers(params) {
params: params return axios({
}) url: `/v0.1/history/ci_triggers`,
} method: 'GET',
params: params
export function getCiTriggersByCiId(ci_id, params) { })
return axios({ }
url: `/v0.1/history/ci_triggers/${ci_id}`,
method: 'GET', export function getCiTriggersByCiId(ci_id, params) {
params return axios({
}) url: `/v0.1/history/ci_triggers/${ci_id}`,
} method: 'GET',
params
export function getCiRelatedTickets(params) { })
return axios({ }
url: `/itsm/v1/process_ticket/get_tickets_by`,
method: 'POST', export function getCiRelatedTickets(params) {
data: params, return axios({
isShowMessage: false url: `/itsm/v1/process_ticket/get_tickets_by`,
}) method: 'POST',
} data: params,
isShowMessage: false
export function judgeItsmInstalled() { })
return axios({ }
url: `/itsm/v1/process_ticket/itsm_existed`,
method: 'GET', export function judgeItsmInstalled() {
isShowMessage: false return axios({
}) url: `/itsm/v1/process_ticket/itsm_existed`,
} method: 'GET',
isShowMessage: false
export function getCIsBaseline(params) { })
return axios({ }
url: `/v0.1/ci/baseline`,
method: 'GET', export function getCIsBaseline(params) {
params return axios({
}) url: `/v0.1/ci/baseline`,
} method: 'GET',
params
export function CIBaselineRollback(ciId, params) { })
return axios({ }
url: `/v0.1/ci/${ciId}/baseline/rollback`,
method: 'POST', export function CIBaselineRollback(ciId, params) {
data: params return axios({
}) url: `/v0.1/ci/${ciId}/baseline/rollback`,
} method: 'POST',
data: params
})
}

View File

@ -1,449 +1,463 @@
<template> <template>
<div :style="{ height: '100%' }"> <div :style="{ height: '100%' }">
<a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab"> <a-tabs v-if="hasPermission" class="ci-detail-tab" v-model="activeTabKey" @change="changeTab">
<a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }"> <a @click="shareCi" slot="tabBarExtraContent" :style="{ marginRight: '24px' }">
<a-icon type="share-alt" /> <a-icon type="share-alt" />
{{ $t('cmdb.ci.share') }} {{ $t('cmdb.ci.share') }}
</a> </a>
<a-tab-pane key="tab_1"> <a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span> <span slot="tab"><a-icon type="book" />{{ $t('cmdb.attribute') }}</span>
<div class="ci-detail-attr"> <div class="ci-detail-attr">
<el-descriptions <el-descriptions
:title="group.name || $t('other')" :title="group.name || $t('other')"
:key="group.name" :key="group.name"
v-for="group in attributeGroups" v-for="group in attributeGroups"
border border
:column="3" :column="3"
> >
<el-descriptions-item <el-descriptions-item
:label="`${attr.alias || attr.name}`" :label="`${attr.alias || attr.name}`"
:key="attr.name" :key="attr.name"
v-for="attr in group.attributes" v-for="attr in group.attributes"
> >
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span> <span slot="tab"><a-icon type="branches" />{{ $t('cmdb.relation') }}</span>
<div :style="{ height: '100%', padding: '24px', overflow: 'auto' }"> <div :style="{ height: '100%', padding: '24px', overflow: 'auto' }">
<ci-detail-relation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> <ci-detail-relation 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">
<span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span> <span slot="tab"><a-icon type="clock-circle" />{{ $t('cmdb.ci.history') }}</span>
<div :style="{ padding: '24px', height: '100%' }"> <div :style="{ padding: '24px', height: '100%' }">
<a-space :style="{ 'margin-bottom': '10px', display: 'flex' }"> <a-space :style="{ 'margin-bottom': '10px', display: 'flex' }">
<a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()"> <a-button type="primary" class="ops-button-ghost" ghost @click="handleRollbackCI()">
<ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }} <ops-icon type="shishizhuangtai" />{{ $t('cmdb.ci.rollback') }}
</a-button> </a-button>
</a-space> <a-button type="primary" class="ops-button-ghost" ghost @click="handleExport">
<ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" /> <ops-icon type="veops-export" />{{ $t('export') }}
<vxe-table </a-button>
ref="xTable" </a-space>
show-overflow <ci-rollback-form ref="ciRollbackForm" :ciIds="[ciId]" @getCIHistory="getCIHistory" />
show-header-overflow <vxe-table
:data="ciHistory" ref="xTable"
size="small" show-overflow
:height="tableHeight" show-header-overflow
highlight-hover-row :data="ciHistory"
:span-method="mergeRowMethod" size="small"
:scroll-y="{ enabled: false, gt: 20 }" :height="tableHeight"
:scroll-x="{ enabled: false, gt: 0 }" highlight-hover-row
border :span-method="mergeRowMethod"
resizable :scroll-y="{ enabled: false, gt: 20 }"
class="ops-unstripe-table" :scroll-x="{ enabled: false, gt: 0 }"
> border
<template #empty> resizable
<a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }"> class="ops-unstripe-table"
<img slot="image" :src="require('@/assets/data_empty.png')" /> >
<span slot="description"> {{ $t('noData') }} </span> <template #empty>
</a-empty> <a-empty :image-style="{ height: '100px' }" :style="{ paddingTop: '10%' }">
</template> <img slot="image" :src="require('@/assets/data_empty.png')" />
<vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column> <span slot="description"> {{ $t('noData') }} </span>
<vxe-table-column </a-empty>
field="username" </template>
:title="$t('user')" <vxe-table-column sortable field="created_at" :title="$t('created_at')"></vxe-table-column>
:filters="[]" <vxe-table-column
:filter-method="filterUsernameMethod" field="username"
></vxe-table-column> :title="$t('user')"
<vxe-table-column :filters="[]"
field="operate_type" :filter-method="filterUsernameMethod"
:filters="[ ></vxe-table-column>
{ value: 0, label: $t('new') }, <vxe-table-column
{ value: 1, label: $t('delete') }, field="operate_type"
{ value: 2, label: $t('update') }, :filters="[
]" { value: 0, label: $t('new') },
:filter-method="filterOperateMethod" { value: 1, label: $t('delete') },
:title="$t('operation')" { value: 2, label: $t('update') },
> ]"
<template #default="{ row }"> :filter-method="filterOperateMethod"
{{ operateTypeMap[row.operate_type] }} :title="$t('operation')"
</template> >
</vxe-table-column> <template #default="{ row }">
<vxe-table-column {{ operateTypeMap[row.operate_type] }}
field="attr_alias" </template>
:title="$t('cmdb.attribute')" </vxe-table-column>
:filters="[]" <vxe-table-column
:filter-method="filterAttrMethod" field="attr_alias"
></vxe-table-column> :title="$t('cmdb.attribute')"
<vxe-table-column field="old" :title="$t('cmdb.history.old')"> :filters="[]"
<template #default="{ row }"> :filter-method="filterAttrMethod"
<span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span> ></vxe-table-column>
<span v-else>{{ row.old }}</span> <vxe-table-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')">
</template> <template #default="{ row }">
</vxe-table-column> <span v-if="row.value_type === '6'">{{ JSON.parse(row.old) }}</span>
<vxe-table-column field="new" :title="$t('cmdb.history.new')"> <span v-else>{{ row.old }}</span>
<template #default="{ row }"> </template>
<span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span> </vxe-table-column>
<span v-else>{{ row.new }}</span> <vxe-table-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')">
</template> <template #default="{ row }">
</vxe-table-column> <span v-if="row.value_type === '6'">{{ JSON.parse(row.new) }}</span>
</vxe-table> <span v-else>{{ row.new }}</span>
</div> </template>
</a-tab-pane> </vxe-table-column>
<a-tab-pane key="tab_4"> </vxe-table>
<span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span> </div>
<div :style="{ padding: '24px', height: '100%' }"> </a-tab-pane>
<TriggerTable :ci_id="ci._id" /> <a-tab-pane key="tab_4">
</div> <span slot="tab"><ops-icon type="itsm_auto_trigger" />{{ $t('cmdb.history.triggerHistory') }}</span>
</a-tab-pane> <div :style="{ padding: '24px', height: '100%' }">
<a-tab-pane key="tab_5"> <TriggerTable :ci_id="ci._id" />
<span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span> </div>
<div :style="{ padding: '24px', height: '100%' }"> </a-tab-pane>
<RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" /> <a-tab-pane key="tab_5">
</div> <span slot="tab"><ops-icon type="itsm-association" />{{ $t('cmdb.ci.relITSM') }}</span>
</a-tab-pane> <div :style="{ padding: '24px', height: '100%' }">
</a-tabs> <RelatedItsmTable ref="relatedITSMTable" :ci_id="ci._id" :ciHistory="ciHistory" :itsmInstalled="itsmInstalled" :attrList="attrList" />
<a-empty </div>
v-else </a-tab-pane>
:image-style="{ </a-tabs>
height: '100px', <a-empty
}" v-else
:style="{ paddingTop: '20%' }" :image-style="{
> height: '100px',
<img slot="image" :src="require('@/assets/data_empty.png')" /> }"
<span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span> :style="{ paddingTop: '20%' }"
</a-empty> >
</div> <img slot="image" :src="require('@/assets/data_empty.png')" />
</template> <span slot="description"> {{ $t('cmdb.ci.noPermission') }} </span>
</a-empty>
<script> </div>
import _ from 'lodash' </template>
import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' <script>
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history' import _ from 'lodash'
import { getCIById } from '@/modules/cmdb/api/ci' import { Descriptions, DescriptionsItem } from 'element-ui'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import CiDetailRelation from './ciDetailRelation.vue' import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import { getCIById } from '@/modules/cmdb/api/ci'
import RelatedItsmTable from './ciDetailRelatedItsmTable.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiRollbackForm from './ciRollbackForm.vue' import CiDetailRelation from './ciDetailRelation.vue'
export default { import TriggerTable from '../../operation_history/modules/triggerTable.vue'
name: 'CiDetailTab', import RelatedItsmTable from './ciDetailRelatedItsmTable.vue'
components: { import CiRollbackForm from './ciRollbackForm.vue'
ElDescriptions: Descriptions, export default {
ElDescriptionsItem: DescriptionsItem, name: 'CiDetailTab',
CiDetailAttrContent, components: {
CiDetailRelation, ElDescriptions: Descriptions,
TriggerTable, ElDescriptionsItem: DescriptionsItem,
RelatedItsmTable, CiDetailAttrContent,
CiRollbackForm, CiDetailRelation,
}, TriggerTable,
props: { RelatedItsmTable,
typeId: { CiRollbackForm,
type: Number, },
required: true, props: {
}, typeId: {
treeViewsLevels: { type: Number,
type: Array, required: true,
default: () => [], },
}, treeViewsLevels: {
attributeHistoryTableHeight: { type: Array,
type: Number, default: () => [],
default: null },
} attributeHistoryTableHeight: {
}, type: Number,
data() { default: null
return { }
ci: {}, },
item: [], data() {
attributeGroups: [], return {
activeTabKey: 'tab_1', ci: {},
rowSpanMap: {}, item: [],
ciHistory: [], attributeGroups: [],
ciId: null, activeTabKey: 'tab_1',
ci_types: [], rowSpanMap: {},
hasPermission: true, ciHistory: [],
itsmInstalled: true, ciId: null,
tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120), ci_types: [],
} hasPermission: true,
}, itsmInstalled: true,
computed: { tableHeight: this.attributeHistoryTableHeight || (this.$store.state.windowHeight - 120),
windowHeight() { }
return this.$store.state.windowHeight },
}, computed: {
operateTypeMap() { windowHeight() {
return { return this.$store.state.windowHeight
0: this.$t('new'), },
1: this.$t('delete'), operateTypeMap() {
2: this.$t('update'), return {
} 0: this.$t('new'),
}, 1: this.$t('delete'),
}, 2: this.$t('update'),
provide() { }
return { },
ci_types: () => { },
return this.ci_types provide() {
}, return {
} ci_types: () => {
}, return this.ci_types
inject: { },
reload: { }
from: 'reload', },
default: null, inject: {
}, reload: {
handleSearch: { from: 'reload',
from: 'handleSearch', default: null,
default: null, },
}, handleSearch: {
attrList: { from: 'handleSearch',
from: 'attrList', default: null,
default: () => [], },
}, attrList: {
}, from: 'attrList',
methods: { default: () => [],
async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { },
this.activeTabKey = activeTabKey },
if (activeTabKey === 'tab_2') { methods: {
this.$nextTick(() => { async create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey this.activeTabKey = activeTabKey
}) if (activeTabKey === 'tab_2') {
} this.$nextTick(() => {
this.ciId = ciId this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
await this.getCI() })
await this.judgeItsmInstalled() }
if (this.hasPermission) { this.ciId = ciId
this.getAttributes() await this.getCI()
this.getCIHistory() await this.judgeItsmInstalled()
getCITypes().then((res) => { if (this.hasPermission) {
this.ci_types = res.ci_types this.getAttributes()
}) this.getCIHistory()
} getCITypes().then((res) => {
}, this.ci_types = res.ci_types
getAttributes() { })
getCITypeGroupById(this.typeId, { need_other: 1 }) }
.then((res) => { },
this.attributeGroups = res getAttributes() {
}) getCITypeGroupById(this.typeId, { need_other: 1 })
.catch((e) => {}) .then((res) => {
}, this.attributeGroups = res
async getCI() { })
await getCIById(this.ciId) .catch((e) => {})
.then((res) => { },
if (res.result.length) { async getCI() {
this.ci = res.result[0] await getCIById(this.ciId)
} else { .then((res) => {
this.hasPermission = false if (res.result.length) {
} this.ci = res.result[0]
}) } else {
.catch((e) => { this.hasPermission = false
if (e.response.status === 404) { }
this.itsmInstalled = false })
} .catch((e) => {
}) if (e.response.status === 404) {
}, this.itsmInstalled = false
async judgeItsmInstalled() { }
await judgeItsmInstalled().catch((e) => { })
this.itsmInstalled = false },
}) async judgeItsmInstalled() {
}, await judgeItsmInstalled().catch((e) => {
this.itsmInstalled = false
getCIHistory() { })
getCIHistory(this.ciId) },
.then((res) => {
this.ciHistory = res getCIHistory() {
getCIHistory(this.ciId)
const rowSpanMap = {} .then((res) => {
let startIndex = 0 this.ciHistory = res
let startCount = 1
res.forEach((item, index) => { const rowSpanMap = {}
if (index === 0) { let startIndex = 0
return let startCount = 1
} res.forEach((item, index) => {
if (res[index].record_id === res[startIndex].record_id) { if (index === 0) {
startCount += 1 return
rowSpanMap[index] = 0 }
if (index === res.length - 1) { if (res[index].record_id === res[startIndex].record_id) {
rowSpanMap[startIndex] = startCount startCount += 1
} rowSpanMap[index] = 0
} else { if (index === res.length - 1) {
rowSpanMap[startIndex] = startCount rowSpanMap[startIndex] = startCount
startIndex = index }
startCount = 1 } else {
if (index === res.length - 1) { rowSpanMap[startIndex] = startCount
rowSpanMap[index] = 1 startIndex = index
} startCount = 1
} if (index === res.length - 1) {
}) rowSpanMap[index] = 1
this.rowSpanMap = rowSpanMap }
}) }
.catch((e) => { })
console.log(e) this.rowSpanMap = rowSpanMap
}) })
}, .catch((e) => {
changeTab(key) { console.log(e)
this.activeTabKey = key })
if (key === 'tab_3') { },
this.$nextTick(() => { changeTab(key) {
const $table = this.$refs.xTable this.activeTabKey = key
if ($table) { if (key === 'tab_3') {
const usernameColumn = $table.getColumnByField('username') this.$nextTick(() => {
const attrColumn = $table.getColumnByField('attr_alias') const $table = this.$refs.xTable
if (usernameColumn) { if ($table) {
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] const usernameColumn = $table.getColumnByField('username')
$table.setFilter( const attrColumn = $table.getColumnByField('attr_alias')
usernameColumn, if (usernameColumn) {
usernameList.map((item) => { const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
return { $table.setFilter(
value: item, usernameColumn,
label: item, usernameList.map((item) => {
} return {
}) value: item,
) label: item,
} }
if (attrColumn) { })
$table.setFilter( )
attrColumn, }
this.attrList().map((attr) => { if (attrColumn) {
return { value: attr.alias || attr.name, label: attr.alias || attr.name } $table.setFilter(
}) attrColumn,
) this.attrList().map((attr) => {
} return { value: attr.alias || attr.name, label: attr.alias || attr.name }
} })
}) )
} }
}, }
filterUsernameMethod({ value, row, column }) { })
return row.username === value }
}, },
filterOperateMethod({ value, row, column }) { filterUsernameMethod({ value, row, column }) {
return Number(row.operate_type) === Number(value) return row.username === value
}, },
filterAttrMethod({ value, row, column }) { filterOperateMethod({ value, row, column }) {
return row.attr_alias === value return Number(row.operate_type) === Number(value)
}, },
refresh(editAttrName) { filterAttrMethod({ value, row, column }) {
this.getCI() return row.attr_alias === value
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) },
// 修改的字段为树形视图订阅的字段 则全部reload refresh(editAttrName) {
setTimeout(() => { this.getCI()
if (_find) { const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
if (this.reload) { // 修改的字段为树形视图订阅的字段 则全部reload
this.reload() setTimeout(() => {
} if (_find) {
} else { if (this.reload) {
if (this.handleSearch) { this.reload()
this.handleSearch() }
} } else {
} if (this.handleSearch) {
}, 500) this.handleSearch()
}, }
mergeRowMethod({ row, _rowIndex, column, visibleData }) { }
const fields = ['created_at', 'username'] }, 500)
const cellValue1 = row['created_at'] },
const cellValue2 = row['username'] mergeRowMethod({ row, _rowIndex, column, visibleData }) {
if (cellValue1 && cellValue2 && fields.includes(column.property)) { const fields = ['created_at', 'username']
const prevRow = visibleData[_rowIndex - 1] const cellValue1 = row['created_at']
let nextRow = visibleData[_rowIndex + 1] const cellValue2 = row['username']
if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) { if (cellValue1 && cellValue2 && fields.includes(column.property)) {
return { rowspan: 0, colspan: 0 } const prevRow = visibleData[_rowIndex - 1]
} else { let nextRow = visibleData[_rowIndex + 1]
let countRowspan = 1 if (prevRow && prevRow['created_at'] === cellValue1 && prevRow['username'] === cellValue2) {
while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) { return { rowspan: 0, colspan: 0 }
nextRow = visibleData[++countRowspan + _rowIndex] } else {
} let countRowspan = 1
if (countRowspan > 1) { while (nextRow && nextRow['created_at'] === cellValue1 && nextRow['username'] === cellValue2) {
return { rowspan: countRowspan, colspan: 1 } nextRow = visibleData[++countRowspan + _rowIndex]
} }
} if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
}, }
updateCIByself(params, editAttrName) { }
const _ci = { ..._.cloneDeep(this.ci), ...params } }
this.ci = _ci },
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) updateCIByself(params, editAttrName) {
// 修改的字段为树形视图订阅的字段 则全部reload const _ci = { ..._.cloneDeep(this.ci), ...params }
setTimeout(() => { this.ci = _ci
if (_find) { const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
if (this.reload) { // 修改的字段为树形视图订阅的字段 则全部reload
this.reload() setTimeout(() => {
} if (_find) {
} else { if (this.reload) {
if (this.handleSearch) { this.reload()
this.handleSearch() }
} } else {
} if (this.handleSearch) {
}, 500) this.handleSearch()
}, }
shareCi() { }
const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}` }, 500)
this.$copyText(text) },
.then(() => { shareCi() {
this.$message.success(this.$t('copySuccess')) const text = `${document.location.host}/cmdb/cidetail/${this.typeId}/${this.ciId}`
}) this.$copyText(text)
.catch(() => { .then(() => {
this.$message.error(this.$t('cmdb.ci.copyFailed')) this.$message.success(this.$t('copySuccess'))
}) })
}, .catch(() => {
handleRollbackCI() { this.$message.error(this.$t('cmdb.ci.copyFailed'))
this.$nextTick(() => { })
this.$refs.ciRollbackForm.onOpen() },
}) handleRollbackCI() {
}, this.$nextTick(() => {
}, this.$refs.ciRollbackForm.onOpen()
} })
</script> },
async handleExport() {
<style lang="less"> this.$refs.xTable.exportData({
.ci-detail-tab { filename: this.$t('cmdb.ci.history'),
height: 100%; sheetName: 'Sheet1',
.ant-tabs-content { type: 'xlsx',
height: calc(100% - 45px); types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
.ant-tabs-tabpane { data: this.ciHistory,
height: 100%; isMerge: true,
} isColgroup: true,
} })
.ant-tabs-bar { }
margin: 0; },
} }
.ant-tabs-extra-content { </script>
line-height: 44px;
} <style lang="less">
.ci-detail-attr { .ci-detail-tab {
height: 100%; height: 100%;
overflow: auto; .ant-tabs-content {
padding: 24px; height: calc(100% - 45px);
.el-descriptions-item__content { .ant-tabs-tabpane {
cursor: default; height: 100%;
&:hover a { }
opacity: 1 !important; }
} .ant-tabs-bar {
} margin: 0;
.el-descriptions:first-child > .el-descriptions__header { }
margin-top: 0; .ant-tabs-extra-content {
} line-height: 44px;
.el-descriptions__header { }
margin-bottom: 5px; .ci-detail-attr {
margin-top: 20px; height: 100%;
} overflow: auto;
.ant-form-item { padding: 24px;
margin-bottom: 0; .el-descriptions-item__content {
} cursor: default;
.ant-form-item-control { &:hover a {
line-height: 19px; opacity: 1 !important;
} }
} }
} .el-descriptions:first-child > .el-descriptions__header {
</style> margin-top: 0;
}
.el-descriptions__header {
margin-bottom: 5px;
margin-top: 20px;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control {
line-height: 19px;
}
}
}
</style>

View File

@ -7,6 +7,7 @@
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@searchFormChange="searchFormChange" @searchFormChange="searchFormChange"
@export="handleExport"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
@ -86,13 +87,13 @@
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="attr_alias" :title="$t('cmdb.history.attribute')"></vxe-column> <vxe-column field="attr_alias" :title="$t('cmdb.history.attribute')"></vxe-column>
<vxe-column field="old" :title="$t('cmdb.history.old')"></vxe-column> <vxe-column :cell-type="'string'" field="old" :title="$t('cmdb.history.old')"></vxe-column>
<vxe-column field="new" :title="$t('cmdb.history.new')"></vxe-column> <vxe-column :cell-type="'string'" field="new" :title="$t('cmdb.history.new')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
:page-size.sync="queryParams.page_size" :page-size.sync="queryParams.page_size"
:page-sizes="[50, 100, 200]" :page-sizes="[50, 100, 200, 500]"
:total="total" :total="total"
:isLoading="loading" :isLoading="loading"
@change="onChange" @change="onChange"
@ -391,6 +392,37 @@ export default {
filterOption(input, option) { filterOption(input, option) {
return option.componentOptions.children[0].text.indexOf(input) >= 0 return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
async handleExport(params) {
const hide = this.$message.loading(this.$t('loading'), 0)
const res = await getCIHistoryTable({
...params,
page: this.queryParams.page,
page_size: this.queryParams.page_size,
})
hide()
const data = []
res.records.forEach((item) => {
item[0].type_id = this.handleTypeId(item[0].type_id)
item[1].forEach((subItem) => {
subItem.operate_type = this.handleOperateType(subItem.operate_type)
subItem.new = subItem.new || ''
subItem.old = subItem.old || ''
const tempObj = Object.assign(subItem, item[0])
data.push(tempObj)
})
})
this.$refs.xTable.exportData({
filename: this.$t('cmdb.history.ciChange'),
sheetName: 'Sheet1',
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
isMerge: true,
isColgroup: true,
data,
})
}
}, },
} }
</script> </script>

View File

@ -5,6 +5,7 @@
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@export="handleExport"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
@ -122,7 +123,7 @@
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
:page-size.sync="queryParams.page_size" :page-size.sync="queryParams.page_size"
:page-sizes="[50, 100, 200]" :page-sizes="[50, 100, 200, 500]"
:total="total" :total="total"
:isLoading="loading" :isLoading="loading"
@change="onChange" @change="onChange"
@ -379,6 +380,58 @@ export default {
filterOption(input, option) { filterOption(input, option) {
return option.componentOptions.children[0].text.indexOf(input) >= 0 return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
async handleExport(params) {
const hide = this.$message.loading(this.$t('loading'), 0)
const res = await getRelationTable({
...params,
page: this.queryParams.page,
page_size: this.queryParams.page_size,
})
hide()
const data = []
res.records.forEach((item) => {
item[1].forEach((subItem) => {
subItem.operate_type = this.handleOperateType(subItem.operate_type)
subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id)
subItem.first = res.cis[String(subItem.first_ci_id)]
subItem.second = res.cis[String(subItem.second_ci_id)]
const tempObj = Object.assign(subItem, item[0])
tempObj.changeDescription = this.getExportChangeDescription(tempObj)
data.push(tempObj)
})
})
this.$refs.xTable.exportData({
filename: this.$t('cmdb.history.relationChange'),
sheetName: 'Sheet1',
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
isMerge: true,
isColgroup: true,
data,
})
},
getExportChangeDescription(item) {
const first = item.first ? `${item.first.ci_type_alias}${item.first.unique_alias && item.first[item.first.unique] ? `${item.first.unique_alias}${item.first[item.first.unique]}` : ''}` : ''
const second = item.second ? `${item.second.ci_type_alias}${item.second.unique_alias && item.second[item.second.unique] ? `${item.second.unique_alias}${item.second[item.second.unique]}` : ''}` : ''
let center = ''
if (item.changeDescription === this.$t('cmdb.history.noUpdate')) {
center = item.relation_type_id
} else if (item.operate_type.includes(this.$t('update'))) {
center = item.changeArr.join(';')
} else if (item.operate_type.includes(this.$t('new'))) {
center = item.relation_type_id
} else if (item.operate_type.includes(this.$t('delete'))) {
center = item.relation_type_id
}
return `${first || ''} => ${center || ''} => ${second || ''}`
}
}, },
} }
</script> </script>

View File

@ -27,7 +27,7 @@
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
v-for="(choice, index) in attr.choice_value" v-for="(choice, index) in attr.choice_value"
:key="'Search_' + attr.name + index" :key="'Search_' + attr.name + Object.values(choice)[0] + index"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option> </a-select-option>
@ -42,7 +42,7 @@
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
}" }"
v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'" v-else-if="attr.value_type === '3'"
/> />
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else /> <a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
</a-form-item> </a-form-item>
@ -85,7 +85,7 @@
@change="onChange" @change="onChange"
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
:placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]" :placeholder="[$t('cmdb.history.startTime'), $t('cmdb.history.endTime')]"
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'" v-else-if="attr.value_type === '3'"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
@ -101,6 +101,9 @@
<a-button type="primary" html-type="submit" @click="handleSearch"> <a-button type="primary" html-type="submit" @click="handleSearch">
{{ $t('query') }} {{ $t('query') }}
</a-button> </a-button>
<a-button :style="{ marginLeft: '8px' }" @click="handleExport">
{{ $t('export') }}
</a-button>
<a-button :style="{ marginLeft: '8px' }" @click="handleReset"> <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
{{ $t('reset') }} {{ $t('reset') }}
</a-button> </a-button>
@ -155,6 +158,13 @@ export default {
this.$emit('search', this.queryParams) this.$emit('search', this.queryParams)
}, },
handleExport() {
const queryParams = {
...this.queryParams
}
this.$emit('export', queryParams)
},
handleReset() { handleReset() {
this.queryParams = { this.queryParams = {
page: 1, page: 1,

View File

@ -5,6 +5,7 @@
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@export="handleExport"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
@ -121,7 +122,7 @@ export default {
relationTypeList: null, relationTypeList: null,
typeList: null, typeList: null,
userList: [], userList: [],
pageSizeOptions: ['50', '100', '200'], pageSizeOptions: ['50', '100', '200', '500'],
isExpand: false, isExpand: false,
current: 1, current: 1,
pageSize: 50, pageSize: 50,
@ -508,6 +509,36 @@ export default {
filterOption(input, option) { filterOption(input, option) {
return option.componentOptions.children[0].text.indexOf(input) >= 0 return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
async handleExport(params) {
const hide = this.$message.loading(this.$t('loading'), 0)
const res = await getCITypesTable({
...params,
page: this.queryParams.page,
page_size: this.queryParams.page_size,
})
hide()
res.result.forEach((item) => {
this.handleChangeDescription(item, item.operate_type)
item.operate_type = this.handleOperateType(item.operate_type)
item.type_id = this.handleTypeId(item.type_id)
item.uid = this.handleUID(item.uid)
if (item.operate_type.includes(this.$t('update'))) {
item.changeDescription = item.changeArr.join(';')
}
})
this.$refs.xTable.exportData({
filename: this.$t('cmdb.history.ciTypeChange'),
sheetName: 'Sheet1',
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
isMerge: true,
isColgroup: true,
data: res.result,
})
},
}, },
} }
</script> </script>