mirror of https://github.com/veops/cmdb.git
feat:触发器
This commit is contained in:
parent
ff1626ff07
commit
7181f2879a
|
@ -1,207 +1,215 @@
|
|||
import { axios } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取 所有的 ci_types
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypes(parameter) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 ci_types
|
||||
* @param CITypeName
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCIType(CITypeName, parameter) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeName}`,
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ci_type
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCIType(data) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 ci_type
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateCIType(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ci_type
|
||||
* @param CITypeId
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCIType(CITypeId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 ci_type 的分组
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypeGroupById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||
method: 'GET',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 某个 ci_type 的分组
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCITypeGroupById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 某个 ci_type 的分组
|
||||
* @param groupId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateCITypeGroupById(groupId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 某个 ci_type 的分组
|
||||
* @param groupId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCITypeGroupById(groupId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function getUniqueConstraintList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addUniqueConstraint(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUniqueConstraint(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUniqueConstraint(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTriggerList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addTrigger(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTrigger(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrigger(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// CMDB的模型和实例的授权接口
|
||||
export function grantCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的删除授权接口
|
||||
export function revokeCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的过滤的权限
|
||||
export function ciTypeFilterPermissions(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/filters/permissions`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取 所有的 ci_types
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypes(parameter) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 ci_types
|
||||
* @param CITypeName
|
||||
* @param parameter
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCIType(CITypeName, parameter) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeName}`,
|
||||
method: 'GET',
|
||||
params: parameter
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ci_type
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCIType(data) {
|
||||
return axios({
|
||||
url: '/v0.1/ci_types',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 ci_type
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateCIType(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ci_type
|
||||
* @param CITypeId
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCIType(CITypeId) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 某个 ci_type 的分组
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getCITypeGroupById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||
method: 'GET',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 某个 ci_type 的分组
|
||||
* @param CITypeId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function createCITypeGroupById(CITypeId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 某个 ci_type 的分组
|
||||
* @param groupId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function updateCITypeGroupById(groupId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||
method: 'PUT',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 某个 ci_type 的分组
|
||||
* @param groupId
|
||||
* @param data
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function deleteCITypeGroupById(groupId, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/attribute_groups/${groupId}`,
|
||||
method: 'delete',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function getUniqueConstraintList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addUniqueConstraint(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateUniqueConstraint(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUniqueConstraint(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
export function getTriggerList(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function addTrigger(type_id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers`,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTrigger(type_id, id, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrigger(type_id, id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
|
||||
method: 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
// CMDB的模型和实例的授权接口
|
||||
export function grantCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的删除授权接口
|
||||
export function revokeCiType(type_id, rid, data) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// CMDB的模型和实例的过滤的权限
|
||||
export function ciTypeFilterPermissions(type_id) {
|
||||
return axios({
|
||||
url: `/v0.1/ci_types/${type_id}/filters/permissions`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function getAllDagsName(params) {
|
||||
return axios({
|
||||
url: '/v1/dag/all_names',
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,40 +1,56 @@
|
|||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getCIHistory (ciId) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci/${ciId}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIHistoryTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/attribute`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/relation`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypesTable (params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci_types`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getUsers (params) {
|
||||
return axios({
|
||||
url: `/v1/acl/users/employee`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
import { axios } from '@/utils/request'
|
||||
|
||||
export function getCIHistory(ciId) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci/${ciId}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCIHistoryTable(params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/attribute`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getRelationTable(params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/records/relation`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCITypesTable(params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci_types`,
|
||||
method: 'GET',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getUsers(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',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
export function getCiTriggersByCiId(ci_id, params) {
|
||||
return axios({
|
||||
url: `/v0.1/history/ci_triggers/${ci_id}`,
|
||||
method: 'GET',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import NoticeContent from './index.vue'
|
||||
export default NoticeContent
|
|
@ -0,0 +1,199 @@
|
|||
<template>
|
||||
<div class="notice-content">
|
||||
<div class="notice-content-main">
|
||||
<Toolbar
|
||||
:editor="editor"
|
||||
:defaultConfig="{
|
||||
excludeKeys: [
|
||||
'emotion',
|
||||
'group-image',
|
||||
'group-video',
|
||||
'insertTable',
|
||||
'codeBlock',
|
||||
'blockquote',
|
||||
'fullScreen',
|
||||
],
|
||||
}"
|
||||
mode="default"
|
||||
/>
|
||||
<Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" />
|
||||
<div class="notice-content-sidebar">
|
||||
<template v-if="needOld">
|
||||
<div class="notice-content-sidebar-divider">变更前</div>
|
||||
<div
|
||||
@dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)"
|
||||
class="notice-content-sidebar-item"
|
||||
v-for="attr in attrList"
|
||||
:key="`old_${attr.id}`"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</div>
|
||||
<div class="notice-content-sidebar-divider">变更后</div>
|
||||
</template>
|
||||
<div
|
||||
@dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)"
|
||||
class="notice-content-sidebar-item"
|
||||
v-for="attr in attrList"
|
||||
:key="attr.id"
|
||||
:title="attr.alias || attr.name"
|
||||
>
|
||||
{{ attr.alias || attr.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import '@wangeditor/editor/dist/css/style.css'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
export default {
|
||||
name: 'NoticeContent',
|
||||
components: { Editor, Toolbar },
|
||||
props: {
|
||||
attrList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
needOld: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
editorConfig: { placeholder: '请输入通知内容', readOnly: this.readOnly },
|
||||
content: '',
|
||||
defaultParams: [],
|
||||
value2LabelMap: {},
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
const editor = this.editor
|
||||
if (editor == null) return
|
||||
editor.destroy() // 组件销毁时,及时销毁编辑器
|
||||
},
|
||||
methods: {
|
||||
onCreated(editor) {
|
||||
this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
|
||||
},
|
||||
getContent() {
|
||||
const html = _.cloneDeep(this.editor.getHtml())
|
||||
const _html = html.replace(
|
||||
/<span data-w-e-type="attachment" data-w-e-is-void data-w-e-is-inline.*?<\/span>/gm,
|
||||
(value) => {
|
||||
const _match = value.match(/(?<=data-attachmentValue=").*?(?=")/)
|
||||
return `{{${_match}}}`
|
||||
}
|
||||
)
|
||||
return { body_html: html, body: _html }
|
||||
},
|
||||
setContent(html) {
|
||||
this.editor.setHtml(html)
|
||||
},
|
||||
dblclickSidebar(value, label) {
|
||||
if (!this.readOnly) {
|
||||
this.editor.restoreSelection()
|
||||
|
||||
const node = {
|
||||
type: 'attachment',
|
||||
attachmentValue: value,
|
||||
attachmentLabel: `${label}`,
|
||||
children: [{ text: '' }],
|
||||
}
|
||||
this.editor.insertNode(node)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
.notice-content {
|
||||
width: 100%;
|
||||
& &-main {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
.notice-content-editor {
|
||||
height: 300px;
|
||||
width: 75%;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-top: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notice-content-sidebar {
|
||||
width: 25%;
|
||||
position: absolute;
|
||||
height: 300px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
overflow: auto;
|
||||
.notice-content-sidebar-divider {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #afafaf;
|
||||
background-color: #fff;
|
||||
line-height: 20px;
|
||||
padding-left: 12px;
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-top: 1px solid #d1d1d1;
|
||||
top: 50%;
|
||||
transition: translateY(-50%);
|
||||
}
|
||||
&::before {
|
||||
left: 3px;
|
||||
width: 5px;
|
||||
}
|
||||
&::after {
|
||||
right: 3px;
|
||||
width: 78px;
|
||||
}
|
||||
}
|
||||
.notice-content-sidebar-item:first-child {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.notice-content-sidebar-item {
|
||||
line-height: 1.5;
|
||||
padding: 4px 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: #custom_colors[color_2];
|
||||
color: #custom_colors[color_1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.notice-content {
|
||||
.w-e-bar {
|
||||
background-color: #custom_colors[color_2];
|
||||
}
|
||||
.w-e-text-placeholder {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<div class="authorization-wrapper">
|
||||
<div class="authorization-header">
|
||||
<a-space>
|
||||
<span>Authorization Type</span>
|
||||
<a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true">
|
||||
<a-select-option value="none">
|
||||
None
|
||||
</a-select-option>
|
||||
<a-select-option value="BasicAuth">
|
||||
Basic Auth
|
||||
</a-select-option>
|
||||
<a-select-option value="Bearer">
|
||||
Bearer
|
||||
</a-select-option>
|
||||
<a-select-option value="APIKey">
|
||||
APIKey
|
||||
</a-select-option>
|
||||
<a-select-option value="OAuth2.0">
|
||||
OAuth2.0
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</div>
|
||||
<div style="margin-top:10px">
|
||||
<table v-if="authorizationType === 'BasicAuth'">
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="BasicAuth.username" placeholder="用户名" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="BasicAuth.password" placeholder="密码" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table v-else-if="authorizationType === 'Bearer'">
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table v-else-if="authorizationType === 'APIKey'">
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table v-else-if="authorizationType === 'OAuth2.0'">
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a-input
|
||||
class="authorization-input"
|
||||
v-model="OAuth2.authorization_base_url"
|
||||
placeholder="authorization_base_url"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '60px',
|
||||
}"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> 暂无请求认证 </span>
|
||||
</a-empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Authorization',
|
||||
data() {
|
||||
return {
|
||||
authorizationType: 'none',
|
||||
BasicAuth: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
Bearer: {
|
||||
token: '',
|
||||
},
|
||||
APIKey: {
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
OAuth2: {
|
||||
client_id: '',
|
||||
client_secret: '',
|
||||
authorization_base_url: '',
|
||||
token_url: '',
|
||||
redirect_url: '',
|
||||
scope: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.authorization-wrapper {
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.authorization-input {
|
||||
border: none;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div class="body-wrapper">
|
||||
<div class="body-header">
|
||||
<!-- <a-space>
|
||||
<span>Content Type</span>
|
||||
<a-select size="small" v-model="contentType" style="width: 200px" :showSearch="true">
|
||||
<a-select-option value="none">
|
||||
None
|
||||
</a-select-option>
|
||||
<a-select-opt-group v-for="item in segmentedContentTypes" :key="item.title" :label="item.title">
|
||||
<a-select-option v-for="ele in item.contentTypes" :key="ele" :value="ele">
|
||||
{{ ele }}
|
||||
</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</a-select>
|
||||
</a-space> -->
|
||||
</div>
|
||||
<div style="margin-top:10px">
|
||||
<vue-json-editor v-model="jsonData" :showBtns="false" :mode="'text'" />
|
||||
<!-- <a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '60px',
|
||||
}"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> 暂无请求体 </span>
|
||||
</a-empty> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vueJsonEditor from 'vue-json-editor'
|
||||
|
||||
export default {
|
||||
name: 'Body',
|
||||
components: { vueJsonEditor },
|
||||
data() {
|
||||
const segmentedContentTypes = [
|
||||
{
|
||||
title: 'text',
|
||||
contentTypes: [
|
||||
'application/json',
|
||||
'application/ld+json',
|
||||
'application/hal+json',
|
||||
'application/vnd.api+json',
|
||||
'application/xml',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'structured',
|
||||
contentTypes: ['application/x-www-form-urlencoded', 'multipart/form-data'],
|
||||
},
|
||||
{
|
||||
title: 'others',
|
||||
contentTypes: ['text/html', 'text/plain'],
|
||||
},
|
||||
]
|
||||
return {
|
||||
segmentedContentTypes,
|
||||
// contentType: 'none',
|
||||
jsonData: {},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less">
|
||||
.body-wrapper {
|
||||
div.jsoneditor-menu {
|
||||
display: none;
|
||||
}
|
||||
div.jsoneditor {
|
||||
border-color: #f3f4f6;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="headers-header">
|
||||
<span>请求参数</span>
|
||||
<a-space>
|
||||
<a-tooltip title="清空">
|
||||
<ops-icon
|
||||
type="icon-xianxing-delete"
|
||||
@click="
|
||||
() => {
|
||||
headers = [
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
]
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="新增">
|
||||
<a-icon type="plus" @click="add" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="headers-box">
|
||||
<table>
|
||||
<tr v-for="(item, index) in headers" :key="item.id">
|
||||
<td><a-input class="headers-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td>
|
||||
<td><a-input class="headers-input" v-model="item.value" :placeholder="`值${index + 1}`" /></td>
|
||||
<td>
|
||||
<a style="color:red">
|
||||
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
uuidv4,
|
||||
add() {
|
||||
this.headers.push({
|
||||
id: uuidv4(),
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
},
|
||||
deleteParam(index) {
|
||||
this.headers.splice(index, 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.headers-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.headers-box {
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.headers-input {
|
||||
border: none;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,2 @@
|
|||
import Webhook from './index.vue'
|
||||
export default Webhook
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<treeselect
|
||||
:disable-branch-nodes="true"
|
||||
class="custom-treeselect custom-treeselect-bgcAndBorder"
|
||||
:style="{
|
||||
'--custom-height': '30px',
|
||||
lineHeight: '30px',
|
||||
'--custom-bg-color': '#fff',
|
||||
'--custom-border': '1px solid #d9d9d9',
|
||||
display: 'inline-block',
|
||||
width: '100px',
|
||||
}"
|
||||
v-model="method"
|
||||
:multiple="false"
|
||||
:clearable="false"
|
||||
searchable
|
||||
:options="methodList"
|
||||
value-consists-of="LEAF_PRIORITY"
|
||||
placeholder="请选择方式"
|
||||
>
|
||||
</treeselect>
|
||||
<a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" />
|
||||
</a-input-group>
|
||||
<a-tabs>
|
||||
<a-tab-pane key="Parameters" tab="Parameters">
|
||||
<Parameters ref="Parameters" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="Body" tab="Body" force-render>
|
||||
<Body ref="Body" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="Headers" tab="Headers" force-render>
|
||||
<Header ref="Header" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="Authorization" tab="Authorization" force-render>
|
||||
<Authorization ref="Authorization" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import Parameters from './paramaters.vue'
|
||||
import Body from './body.vue'
|
||||
import Header from './header.vue'
|
||||
import Authorization from './authorization.vue'
|
||||
export default {
|
||||
name: 'Webhook',
|
||||
components: { Parameters, Body, Header, Authorization },
|
||||
data() {
|
||||
const methodList = [
|
||||
{
|
||||
id: 'GET',
|
||||
label: 'GET',
|
||||
},
|
||||
{
|
||||
id: 'POST',
|
||||
label: 'POST',
|
||||
},
|
||||
{
|
||||
id: 'PUT',
|
||||
label: 'PUT',
|
||||
},
|
||||
{
|
||||
id: 'DELETE',
|
||||
label: 'DELETE',
|
||||
},
|
||||
]
|
||||
return {
|
||||
methodList,
|
||||
method: 'GET',
|
||||
url: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getParams() {
|
||||
const parameters = {}
|
||||
this.$refs.Parameters.parameters.forEach((item) => {
|
||||
parameters[item.key] = item.value
|
||||
})
|
||||
const body = this.$refs.Body.jsonData
|
||||
const headers = {}
|
||||
this.$refs.Header.headers.forEach((item) => {
|
||||
headers[item.key] = item.value
|
||||
})
|
||||
let authorization = {}
|
||||
const type = this.$refs.Authorization.authorizationType
|
||||
if (type !== 'none') {
|
||||
if (type === 'OAuth2.0') {
|
||||
authorization = { ...this.$refs.Authorization['OAuth2'], type }
|
||||
} else {
|
||||
authorization = { ...this.$refs.Authorization[type], type }
|
||||
}
|
||||
}
|
||||
const { method, url } = this
|
||||
return { method, url, parameters, body, headers, authorization }
|
||||
},
|
||||
setParams(params) {
|
||||
console.log(2222, params)
|
||||
const { method, url, parameters, body, headers, authorization = {} } = params ?? {}
|
||||
this.method = method
|
||||
this.url = url
|
||||
this.$refs.Parameters.parameters =
|
||||
Object.keys(parameters).map((key) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
key: key,
|
||||
value: parameters[key],
|
||||
}
|
||||
}) || []
|
||||
this.$refs.Body.jsonData = body
|
||||
this.$refs.Header.headers =
|
||||
Object.keys(headers).map((key) => {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
key: key,
|
||||
value: headers[key],
|
||||
}
|
||||
}) || []
|
||||
const { type = 'none' } = authorization
|
||||
console.log(type)
|
||||
this.$refs.Authorization.authorizationType = type
|
||||
if (type !== 'none') {
|
||||
const _authorization = _.cloneDeep(authorization)
|
||||
delete _authorization.type
|
||||
if (type === 'OAuth2.0') {
|
||||
this.$refs.Authorization.OAuth2 = _authorization
|
||||
} else {
|
||||
this.$refs.Authorization[type] = _authorization
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="parameters-header">
|
||||
<span>请求参数</span>
|
||||
<a-space>
|
||||
<a-tooltip title="清空">
|
||||
<ops-icon
|
||||
type="icon-xianxing-delete"
|
||||
@click="
|
||||
() => {
|
||||
parameters = []
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="新增">
|
||||
<a-icon type="plus" @click="add" />
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="parameters-box" v-if="parameters && parameters.length">
|
||||
<table>
|
||||
<tr v-for="(item, index) in parameters" :key="item.id">
|
||||
<td><a-input class="parameters-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td>
|
||||
<td><a-input class="parameters-input" v-model="item.value" :placeholder="`值${index + 1}`" /></td>
|
||||
<td>
|
||||
<a style="color:red">
|
||||
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
:image-style="{
|
||||
height: '60px',
|
||||
}"
|
||||
>
|
||||
<img slot="image" :src="require('@/assets/data_empty.png')" />
|
||||
<span slot="description"> 暂无请求参数 </span>
|
||||
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
|
||||
添加
|
||||
</a-button>
|
||||
</a-empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export default {
|
||||
name: 'Parameters',
|
||||
data() {
|
||||
return {
|
||||
parameters: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.parameters.push({
|
||||
id: uuidv4(),
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
},
|
||||
deleteParam(index) {
|
||||
this.parameters.splice(index, 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.parameters-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.parameters-box {
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
.parameters-input {
|
||||
border: none;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,319 +1,327 @@
|
|||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
||||
wrapClassName="ci-detail"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-tabs v-model="activeTabKey" @change="changeTab">
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />属性</span>
|
||||
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || '其他'"
|
||||
:key="group.name"
|
||||
v-for="group in attributeGroups"
|
||||
border
|
||||
:column="3"
|
||||
>
|
||||
<el-descriptions-item
|
||||
:label="`${attr.alias || attr.name}`"
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />关系</span>
|
||||
<div :style="{ padding: '24px' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />操作历史</span>
|
||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
:max-height="`${windowHeight - 94}px`"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-table-column sortable field="created_at" title="时间"></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
title="用户"
|
||||
:filters="[]"
|
||||
:filter-method="filterUsernameMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
:filters="[
|
||||
{ value: 0, label: '新增' },
|
||||
{ value: 1, label: '删除' },
|
||||
{ value: 3, label: '修改' },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
title="操作"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
title="属性"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column field="old" title="旧"></vxe-table-column>
|
||||
<vxe-table-column field="new" title="新"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
treeViewsLevels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const operateTypeMap = {
|
||||
0: '新增',
|
||||
1: '删除',
|
||||
2: '修改',
|
||||
}
|
||||
return {
|
||||
operateTypeMap,
|
||||
visible: false,
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
rowSpanMap: {},
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
ci_types: () => {
|
||||
return this.ci_types
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: ['reload', 'handleSearch', 'attrList'],
|
||||
methods: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.visible = true
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
this.getAttributes()
|
||||
this.getCI()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = res
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
getCI() {
|
||||
getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
// this.ci = res.ci
|
||||
this.ci = res.result[0]
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
getCIHistory() {
|
||||
getCIHistory(this.ciId)
|
||||
.then((res) => {
|
||||
this.ciHistory = res
|
||||
|
||||
const rowSpanMap = {}
|
||||
let startIndex = 0
|
||||
let startCount = 1
|
||||
res.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
return
|
||||
}
|
||||
if (res[index].record_id === res[startIndex].record_id) {
|
||||
startCount += 1
|
||||
rowSpanMap[index] = 0
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
}
|
||||
} else {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
startIndex = index
|
||||
startCount = 1
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[index] = 1
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rowSpanMap = rowSpanMap
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
},
|
||||
changeTab(key) {
|
||||
this.activeTabKey = key
|
||||
if (key === 'tab_3') {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const usernameColumn = $table.getColumnByField('username')
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (usernameColumn) {
|
||||
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
|
||||
$table.setFilter(
|
||||
usernameColumn,
|
||||
usernameList.map((item) => {
|
||||
return {
|
||||
value: item,
|
||||
label: item,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (attrColumn) {
|
||||
$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 }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
refresh(editAttrName) {
|
||||
this.getCI()
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
this.reload()
|
||||
} else {
|
||||
this.handleSearch()
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'username']
|
||||
const cellValue = row[column.property]
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less">
|
||||
.ci-detail {
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
.el-descriptions:first-child > .el-descriptions__header {
|
||||
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>
|
||||
<template>
|
||||
<CustomDrawer
|
||||
width="80%"
|
||||
placement="left"
|
||||
@close="
|
||||
() => {
|
||||
visible = false
|
||||
}
|
||||
"
|
||||
:visible="visible"
|
||||
:hasTitle="false"
|
||||
:hasFooter="false"
|
||||
:bodyStyle="{ padding: 0, height: '100vh' }"
|
||||
wrapClassName="ci-detail"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-tabs v-model="activeTabKey" @change="changeTab">
|
||||
<a-tab-pane key="tab_1">
|
||||
<span slot="tab"><a-icon type="book" />属性</span>
|
||||
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
|
||||
<el-descriptions
|
||||
:title="group.name || '其他'"
|
||||
:key="group.name"
|
||||
v-for="group in attributeGroups"
|
||||
border
|
||||
:column="3"
|
||||
>
|
||||
<el-descriptions-item
|
||||
:label="`${attr.alias || attr.name}`"
|
||||
:key="attr.name"
|
||||
v-for="attr in group.attributes"
|
||||
>
|
||||
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" />
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_2">
|
||||
<span slot="tab"><a-icon type="branches" />关系</span>
|
||||
<div :style="{ padding: '24px' }">
|
||||
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_3">
|
||||
<span slot="tab"><a-icon type="clock-circle" />操作历史</span>
|
||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:data="ciHistory"
|
||||
size="small"
|
||||
:max-height="`${windowHeight - 94}px`"
|
||||
:span-method="mergeRowMethod"
|
||||
border
|
||||
:scroll-y="{ enabled: false }"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-table-column sortable field="created_at" title="时间"></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="username"
|
||||
title="用户"
|
||||
:filters="[]"
|
||||
:filter-method="filterUsernameMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="operate_type"
|
||||
:filters="[
|
||||
{ value: 0, label: '新增' },
|
||||
{ value: 1, label: '删除' },
|
||||
{ value: 3, label: '修改' },
|
||||
]"
|
||||
:filter-method="filterOperateMethod"
|
||||
title="操作"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ operateTypeMap[row.operate_type] }}
|
||||
</template>
|
||||
</vxe-table-column>
|
||||
<vxe-table-column
|
||||
field="attr_alias"
|
||||
title="属性"
|
||||
:filters="[]"
|
||||
:filter-method="filterAttrMethod"
|
||||
></vxe-table-column>
|
||||
<vxe-table-column field="old" title="旧"></vxe-table-column>
|
||||
<vxe-table-column field="new" title="新"></vxe-table-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="tab_4">
|
||||
<span slot="tab"><ops-icon type="itsm_auto_trigger" />触发历史</span>
|
||||
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
|
||||
<TriggerTable :ci_id="ci._id" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Descriptions, DescriptionsItem } from 'element-ui'
|
||||
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCIHistory } from '@/modules/cmdb/api/history'
|
||||
import { getCIById } from '@/modules/cmdb/api/ci'
|
||||
import CiDetailAttrContent from './ciDetailAttrContent.vue'
|
||||
import CiDetailRelation from './ciDetailRelation.vue'
|
||||
import TriggerTable from '../../operation_history/modules/triggerTable.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ElDescriptions: Descriptions,
|
||||
ElDescriptionsItem: DescriptionsItem,
|
||||
CiDetailAttrContent,
|
||||
CiDetailRelation,
|
||||
TriggerTable,
|
||||
},
|
||||
props: {
|
||||
typeId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
treeViewsLevels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const operateTypeMap = {
|
||||
0: '新增',
|
||||
1: '删除',
|
||||
2: '修改',
|
||||
}
|
||||
return {
|
||||
operateTypeMap,
|
||||
visible: false,
|
||||
ci: {},
|
||||
attributeGroups: [],
|
||||
activeTabKey: 'tab_1',
|
||||
rowSpanMap: {},
|
||||
ciHistory: [],
|
||||
ciId: null,
|
||||
ci_types: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
ci_types: () => {
|
||||
return this.ci_types
|
||||
},
|
||||
}
|
||||
},
|
||||
inject: ['reload', 'handleSearch', 'attrList'],
|
||||
methods: {
|
||||
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
|
||||
this.visible = true
|
||||
this.activeTabKey = activeTabKey
|
||||
if (activeTabKey === 'tab_2') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
|
||||
})
|
||||
}
|
||||
this.ciId = ciId
|
||||
this.getAttributes()
|
||||
this.getCI()
|
||||
this.getCIHistory()
|
||||
getCITypes().then((res) => {
|
||||
this.ci_types = res.ci_types
|
||||
})
|
||||
},
|
||||
getAttributes() {
|
||||
getCITypeGroupById(this.typeId, { need_other: 1 })
|
||||
.then((res) => {
|
||||
this.attributeGroups = res
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
getCI() {
|
||||
getCIById(this.ciId)
|
||||
.then((res) => {
|
||||
// this.ci = res.ci
|
||||
this.ci = res.result[0]
|
||||
})
|
||||
.catch((e) => {})
|
||||
},
|
||||
|
||||
getCIHistory() {
|
||||
getCIHistory(this.ciId)
|
||||
.then((res) => {
|
||||
this.ciHistory = res
|
||||
|
||||
const rowSpanMap = {}
|
||||
let startIndex = 0
|
||||
let startCount = 1
|
||||
res.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
return
|
||||
}
|
||||
if (res[index].record_id === res[startIndex].record_id) {
|
||||
startCount += 1
|
||||
rowSpanMap[index] = 0
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
}
|
||||
} else {
|
||||
rowSpanMap[startIndex] = startCount
|
||||
startIndex = index
|
||||
startCount = 1
|
||||
if (index === res.length - 1) {
|
||||
rowSpanMap[index] = 1
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rowSpanMap = rowSpanMap
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
})
|
||||
},
|
||||
changeTab(key) {
|
||||
this.activeTabKey = key
|
||||
if (key === 'tab_3') {
|
||||
this.$nextTick(() => {
|
||||
const $table = this.$refs.xTable
|
||||
if ($table) {
|
||||
const usernameColumn = $table.getColumnByField('username')
|
||||
const attrColumn = $table.getColumnByField('attr_alias')
|
||||
if (usernameColumn) {
|
||||
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
|
||||
$table.setFilter(
|
||||
usernameColumn,
|
||||
usernameList.map((item) => {
|
||||
return {
|
||||
value: item,
|
||||
label: item,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
if (attrColumn) {
|
||||
$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 }) {
|
||||
return Number(row.operate_type) === Number(value)
|
||||
},
|
||||
filterAttrMethod({ value, row, column }) {
|
||||
return row.attr_alias === value
|
||||
},
|
||||
refresh(editAttrName) {
|
||||
this.getCI()
|
||||
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
|
||||
// 修改的字段为树形视图订阅的字段 则全部reload
|
||||
setTimeout(() => {
|
||||
if (_find) {
|
||||
this.reload()
|
||||
} else {
|
||||
this.handleSearch()
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'username']
|
||||
const cellValue = row[column.property]
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less">
|
||||
.ci-detail {
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
.ci-detail-attr {
|
||||
.el-descriptions-item__content {
|
||||
cursor: default;
|
||||
&:hover a {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
.el-descriptions:first-child > .el-descriptions__header {
|
||||
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>
|
||||
|
|
|
@ -1,197 +1,575 @@
|
|||
<template>
|
||||
<a-modal :title="title" :visible="visible" @cancel="handleCancel" @ok="handleOk">
|
||||
<a-space slot="footer">
|
||||
<a-button type="primary" ghost @click="handleCancel">取消</a-button>
|
||||
<a-button v-if="triggerId" type="danger" @click="handleDetele">删除</a-button>
|
||||
<a-button @click="handleOk" type="primary">确定</a-button>
|
||||
</a-space>
|
||||
<a-form-model ref="triggerForm" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<a-form-model-item label="属性" prop="attr_id" :hidden="!isCreateFromTriggerTable || triggerId">
|
||||
<a-select v-model="form.attr_id">
|
||||
<a-select-option v-for="attr in canAddTriggerAttr" :key="attr.id" :value="attr.id">{{
|
||||
attr.alias || attr.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="主题" prop="subject">
|
||||
<a-input v-model="form.subject" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="内容" prop="body">
|
||||
<a-textarea v-model="form.body" :rows="3" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="微信通知" prop="wx_to">
|
||||
<a-select
|
||||
mode="tags"
|
||||
v-model="form.wx_to"
|
||||
placeholder="选择微信通知人"
|
||||
showSearch
|
||||
:filter-option="false"
|
||||
@search="filterChange"
|
||||
>
|
||||
<a-select-option v-for="item in filterWxUsers" :value="item['wx_id']" :key="item.id">
|
||||
<span>{{ item['nickname'] }}</span>
|
||||
<a-divider type="vertical" />
|
||||
<span>{{ item['wx_id'].length > 12 ? item['wx_id'].slice(0, 10) + '...' : item['wx_id'] }}</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="邮箱通知" prop="mail_to">
|
||||
<a-textarea v-model="form.mail_to" :rows="3" placeholder="多个邮箱用逗号分隔" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="提前" prop="before_days">
|
||||
<a-input-number v-model="form.before_days" :min="0" />
|
||||
天
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="发送时间" prop="notify_at">
|
||||
<a-time-picker v-model="form.notify_at" format="HH:mm" valueFormat="HH:mm" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getWX } from '../../api/perm'
|
||||
import { addTrigger, updateTrigger, deleteTrigger } from '../../api/CIType'
|
||||
export default {
|
||||
name: 'TriggerForm',
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
form: { attr_id: '', subject: '', body: '', wx_to: [], mail_to: '', before_days: 0, notify_at: '08:00' },
|
||||
rules: {
|
||||
attr_id: [{ required: true, message: '请选择属性' }],
|
||||
subject: [{ required: true, message: '请填写主题' }],
|
||||
body: [{ required: true, message: '请填写内容' }],
|
||||
},
|
||||
WxUsers: [],
|
||||
filterValue: '',
|
||||
triggerId: null,
|
||||
attr_id: null,
|
||||
canAddTriggerAttr: [],
|
||||
isCreateFromTriggerTable: false,
|
||||
title: '新增触发器',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterWxUsers() {
|
||||
if (!this.filterValue) {
|
||||
return this.WxUsers
|
||||
}
|
||||
return this.WxUsers.filter(
|
||||
(user) =>
|
||||
user.nickname.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0 ||
|
||||
user.username.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
)
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
refresh: {
|
||||
from: 'refresh',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
createFromTriggerTable(canAddTriggerAttr) {
|
||||
this.visible = true
|
||||
this.getWxList()
|
||||
this.canAddTriggerAttr = canAddTriggerAttr
|
||||
this.triggerId = null
|
||||
this.isCreateFromTriggerTable = true
|
||||
this.title = '新增触发器'
|
||||
this.form = {
|
||||
attr_id: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
wx_to: [],
|
||||
mail_to: '',
|
||||
before_days: 0,
|
||||
notify_at: '08:00',
|
||||
}
|
||||
},
|
||||
open(property) {
|
||||
this.visible = true
|
||||
this.getWxList()
|
||||
if (property.has_trigger) {
|
||||
this.triggerId = property.trigger.id
|
||||
this.title = `编辑触发器 ${property.alias || property.name}`
|
||||
this.form = {
|
||||
...property.trigger.notify,
|
||||
attr_id: property.id,
|
||||
mail_to: property.trigger.notify.mail_to ? property.trigger.notify.mail_to.join(',') : '',
|
||||
}
|
||||
} else {
|
||||
this.title = `新增触发器 ${property.alias || property.name}`
|
||||
this.triggerId = null
|
||||
this.form = {
|
||||
attr_id: property.id,
|
||||
subject: '',
|
||||
body: '',
|
||||
wx_to: [],
|
||||
mail_to: '',
|
||||
before_days: 0,
|
||||
notify_at: '08:00',
|
||||
}
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.$refs.triggerForm.clearValidate()
|
||||
this.$refs.triggerForm.resetFields()
|
||||
this.filterValue = ''
|
||||
this.visible = false
|
||||
},
|
||||
getWxList() {
|
||||
getWX().then((res) => {
|
||||
this.WxUsers = res.filter((item) => item.wx_id)
|
||||
})
|
||||
},
|
||||
filterChange(value) {
|
||||
this.filterValue = value
|
||||
},
|
||||
handleOk() {
|
||||
this.$refs.triggerForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const { mail_to, attr_id } = this.form
|
||||
const params = {
|
||||
attr_id,
|
||||
notify: { ...this.form, mail_to: mail_to ? mail_to.split(',') : undefined },
|
||||
}
|
||||
delete params.notify.attr_id
|
||||
if (this.triggerId) {
|
||||
await updateTrigger(this.CITypeId, this.triggerId, params)
|
||||
} else {
|
||||
await addTrigger(this.CITypeId, params)
|
||||
}
|
||||
this.handleCancel()
|
||||
if (this.refresh) {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDetele() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(that.CITypeId, that.triggerId).then(() => {
|
||||
that.$message.success('删除成功!')
|
||||
that.handleCancel()
|
||||
if (that.refresh) {
|
||||
that.refresh()
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<CustomDrawer
|
||||
wrapClassName="trigger-form"
|
||||
:width="700"
|
||||
:title="title"
|
||||
:visible="visible"
|
||||
@close="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<div class="custom-drawer-bottom-action">
|
||||
<a-button type="primary" ghost @click="handleCancel">取消</a-button>
|
||||
<a-button v-if="triggerId" type="danger" @click="handleDetele">删除</a-button>
|
||||
<a-button @click="handleOk" type="primary">确定</a-button>
|
||||
</div>
|
||||
<a-form-model ref="triggerForm" :model="form" :rules="rules" :label-col="{ span: 3 }" :wrapper-col="{ span: 18 }">
|
||||
<p><strong>基本信息</strong></p>
|
||||
<a-form-model-item label="名称" prop="name">
|
||||
<a-input v-model="form.name" placeholder="请输入名称" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="类型">
|
||||
<a-radio-group v-model="category">
|
||||
<a-radio-button :value="1">
|
||||
数据变更
|
||||
</a-radio-button>
|
||||
<a-radio-button :value="2">
|
||||
日期属性
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="备注" prop="description">
|
||||
<a-input v-model="form.description" placeholder="请输入备注" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="开启" prop="enable">
|
||||
<a-switch v-model="form.enable" />
|
||||
</a-form-model-item>
|
||||
<template v-if="category === 1">
|
||||
<p><strong>触发条件</strong></p>
|
||||
<a-form-model-item label="事件" prop="action">
|
||||
<a-radio-group v-model="form.action">
|
||||
<a-radio value="0">
|
||||
新增实例
|
||||
</a-radio>
|
||||
<a-radio value="1">
|
||||
删除实例
|
||||
</a-radio>
|
||||
<a-radio value="2">
|
||||
实例变更
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.action === '2'" label="属性" prop="attr_ids">
|
||||
<a-select v-model="form.attr_ids" show-search mode="multiple" placeholder="请选择属性(多选)">
|
||||
<a-select-option v-for="attr in attrList" :key="attr.id" :value="attr.id">{{
|
||||
attr.alias || attr.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="筛选" class="trigger-form-filter">
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="attrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="filterExp ? `q=${filterExp}` : ''"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</template>
|
||||
</a-form-model>
|
||||
<template v-if="category === 2">
|
||||
<p><strong>触发条件</strong></p>
|
||||
<a-form-model
|
||||
ref="dateForm"
|
||||
:model="dateForm"
|
||||
:rules="dateFormRules"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-model-item label="属性" prop="attr_id">
|
||||
<a-select v-model="dateForm.attr_id" placeholder="请选择属性(单选)">
|
||||
<a-select-option v-for="attr in canAddTriggerAttr" :key="attr.id" :value="attr.id">{{
|
||||
attr.alias || attr.name
|
||||
}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="筛选" class="trigger-form-filter">
|
||||
<FilterComp
|
||||
ref="filterComp"
|
||||
:isDropdown="false"
|
||||
:canSearchPreferenceAttrList="attrList"
|
||||
@setExpFromFilter="setExpFromFilter"
|
||||
:expression="filterExp ? `q=${filterExp}` : ''"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="提前" prop="before_days">
|
||||
<a-input-number v-model="dateForm.before_days" :min="0" />
|
||||
天
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="发送时间" prop="notify_at">
|
||||
<a-time-picker v-model="dateForm.notify_at" format="HH:mm" valueFormat="HH:mm" />
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
<p><strong>触发动作</strong></p>
|
||||
<a-radio-group
|
||||
v-model="triggerAction"
|
||||
:style="{ width: '100%', display: 'flex', justifyContent: 'space-around', marginBottom: '10px' }"
|
||||
>
|
||||
<a-radio value="1">
|
||||
通知
|
||||
</a-radio>
|
||||
<a-radio value="2">
|
||||
Webhook
|
||||
</a-radio>
|
||||
<!-- <a-radio value="3">
|
||||
DAG
|
||||
</a-radio> -->
|
||||
</a-radio-group>
|
||||
<a-form-model
|
||||
ref="notifiesForm"
|
||||
:model="notifies"
|
||||
:rules="notifiesRules"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
v-if="triggerAction === '1'"
|
||||
>
|
||||
<a-form-model-item label=" " :colon="false">
|
||||
<span class="trigger-tips">{{ tips }}</span>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="收件人" prop="employee_ids" class="trigger-form-employee">
|
||||
<EmployeeTreeSelect multiple v-model="notifies.employee_ids" />
|
||||
<div class="trigger-form-custom-email">
|
||||
<a-textarea
|
||||
v-if="showCustomEmail"
|
||||
v-model="notifies.custom_email"
|
||||
placeholder="请输入邮箱,多个邮箱用;分隔"
|
||||
:rows="1"
|
||||
/>
|
||||
<a-button
|
||||
@click="
|
||||
() => {
|
||||
showCustomEmail = !showCustomEmail
|
||||
}
|
||||
"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
>{{ `${showCustomEmail ? '删除' : '添加'}自定义收件人` }}</a-button
|
||||
>
|
||||
</div>
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="通知标题" prop="subject">
|
||||
<a-input v-model="notifies.subject" placeholder="请输入通知标题" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="内容" prop="body" :wrapper-col="{ span: 21 }">
|
||||
<NoticeContent :needOld="category === 1 && form.action === '2'" :attrList="attrList" ref="noticeContent" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="通知方式" prop="method">
|
||||
<a-checkbox-group v-model="notifies.method">
|
||||
<a-checkbox value="wechatApp">
|
||||
微信
|
||||
</a-checkbox>
|
||||
<a-checkbox value="email">
|
||||
邮件
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
<div class="auto-complete-wrapper" v-if="triggerAction === '3'">
|
||||
<a-input
|
||||
id="auto-complete-wrapper-input"
|
||||
ref="input"
|
||||
v-model="searchValue"
|
||||
@focus="focusOnInput"
|
||||
@blur="handleBlurInput"
|
||||
allowClear
|
||||
>
|
||||
</a-input>
|
||||
<div id="auto-complete-wrapper-popover" class="auto-complete-wrapper-popover" v-if="isShow">
|
||||
<div
|
||||
class="auto-complete-wrapper-popover-item"
|
||||
@click="handleClickSelect(item)"
|
||||
v-for="item in filterList"
|
||||
:key="item.id"
|
||||
:title="item.label"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Webhook ref="webhook" style="margin-top:10px" v-if="triggerAction === '2'" />
|
||||
</CustomDrawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getWX } from '../../api/perm'
|
||||
import { addTrigger, updateTrigger, deleteTrigger, getAllDagsName } from '../../api/CIType'
|
||||
import FilterComp from '@/components/CMDBFilterComp'
|
||||
import EmployeeTreeSelect from '@/views/setting/components/employeeTreeSelect.vue'
|
||||
import Webhook from '../../components/webhook'
|
||||
import NoticeContent from '../../components/noticeContent'
|
||||
import { getNoticeByEmployeeIds } from '@/api/employee'
|
||||
|
||||
export default {
|
||||
name: 'TriggerForm',
|
||||
components: { FilterComp, Webhook, EmployeeTreeSelect, NoticeContent },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const defaultForm = {
|
||||
name: '',
|
||||
description: '',
|
||||
enable: true,
|
||||
action: '0',
|
||||
attr_ids: [],
|
||||
}
|
||||
const defaultDateForm = {
|
||||
attr_id: undefined,
|
||||
before_days: 0,
|
||||
notify_at: '08:00',
|
||||
}
|
||||
const defaultNotify = {
|
||||
employee_ids: undefined,
|
||||
custom_email: '',
|
||||
subject: '',
|
||||
body: '',
|
||||
method: ['wechatApp'],
|
||||
}
|
||||
return {
|
||||
defaultForm,
|
||||
defaultDateForm,
|
||||
defaultNotify,
|
||||
tips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}',
|
||||
visible: false,
|
||||
category: 1,
|
||||
form: _.cloneDeep(defaultForm),
|
||||
rules: {
|
||||
name: [{ required: true, message: '请填写名称' }],
|
||||
},
|
||||
dateForm: _.cloneDeep(defaultDateForm),
|
||||
dateFormRules: {
|
||||
attr_id: [{ required: true, message: '请选择属性' }],
|
||||
},
|
||||
notifies: _.cloneDeep(defaultNotify),
|
||||
notifiesRules: {},
|
||||
WxUsers: [],
|
||||
filterValue: '',
|
||||
triggerId: null,
|
||||
title: '新增触发器',
|
||||
attrList: [],
|
||||
filterExp: '',
|
||||
triggerAction: '1',
|
||||
searchValue: '',
|
||||
dags: [],
|
||||
isShow: false,
|
||||
dag_id: null,
|
||||
showCustomEmail: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterWxUsers() {
|
||||
if (!this.filterValue) {
|
||||
return this.WxUsers
|
||||
}
|
||||
return this.WxUsers.filter(
|
||||
(user) =>
|
||||
user.nickname.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0 ||
|
||||
user.username.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0
|
||||
)
|
||||
},
|
||||
canAddTriggerAttr() {
|
||||
return this.attrList.filter((attr) => attr.value_type === '3' || attr.value_type === '4')
|
||||
},
|
||||
filterList() {
|
||||
if (this.searchValue) {
|
||||
return this.dags.filter((item) => item.label.toLowerCase().includes(this.searchValue.toLowerCase()))
|
||||
}
|
||||
return this.dags
|
||||
},
|
||||
},
|
||||
inject: {
|
||||
refresh: {
|
||||
from: 'refresh',
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
async getDags() {
|
||||
await getAllDagsName().then((res) => {
|
||||
this.dags = res.map((dag) => ({ id: dag[1], label: dag[0] }))
|
||||
})
|
||||
},
|
||||
createFromTriggerTable(attrList) {
|
||||
this.visible = true
|
||||
this.getWxList()
|
||||
this.getDags()
|
||||
this.attrList = attrList
|
||||
this.triggerId = null
|
||||
this.title = '新增触发器'
|
||||
this.form = _.cloneDeep(this.defaultForm)
|
||||
this.dateForm = _.cloneDeep(this.defaultDateForm)
|
||||
this.notifies = _.cloneDeep(this.defaultNotify)
|
||||
this.category = 1
|
||||
this.triggerAction = '1'
|
||||
this.filterExp = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true, false)
|
||||
setTimeout(() => {
|
||||
this.$refs.noticeContent.setContent('')
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
async open(property, attrList) {
|
||||
this.visible = true
|
||||
this.getWxList()
|
||||
await this.getDags()
|
||||
this.attrList = attrList
|
||||
if (property.has_trigger) {
|
||||
this.triggerId = property.trigger.id
|
||||
this.title = `编辑触发器 ${property.alias || property.name}`
|
||||
const { name, description, enable, action = '0', attr_ids, filter = '' } = property?.trigger?.option ?? {}
|
||||
this.filterExp = filter
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterComp.visibleChange(true, false)
|
||||
})
|
||||
this.form = { name, description, enable, action, attr_ids }
|
||||
const { attr_id } = property?.trigger ?? {}
|
||||
if (attr_id) {
|
||||
this.category = 2
|
||||
const { before_days, notify_at } = property?.trigger?.option?.notifies ?? {}
|
||||
this.dateForm = {
|
||||
attr_id,
|
||||
before_days,
|
||||
notify_at,
|
||||
}
|
||||
} else {
|
||||
this.category = 1
|
||||
}
|
||||
const { notifies = undefined, webhooks = undefined, dag_id = undefined } = property?.trigger?.option ?? {}
|
||||
if (webhooks) {
|
||||
this.triggerAction = '2'
|
||||
this.$nextTick(() => {
|
||||
this.$refs.webhook.setParams(webhooks)
|
||||
})
|
||||
} else if (dag_id) {
|
||||
this.triggerAction = '3'
|
||||
this.dag_id = dag_id
|
||||
const _find = this.dags.find((item) => item.id === dag_id)
|
||||
this.searchValue = _find?.label
|
||||
} else if (notifies) {
|
||||
this.triggerAction = '1'
|
||||
const { tos = [], subject = '', body_html = '', method = ['wechatApp'] } =
|
||||
property?.trigger?.option?.notifies ?? {}
|
||||
const employee_ids = property?.trigger?.option?.employee_ids ?? undefined
|
||||
const custom_email =
|
||||
tos
|
||||
.filter((t) => !t.employee_id)
|
||||
.map((t) => t.email)
|
||||
.join(';') ?? ''
|
||||
|
||||
if (custom_email) {
|
||||
this.showCustomEmail = true
|
||||
}
|
||||
if (body_html) {
|
||||
setTimeout(() => {
|
||||
this.$refs.noticeContent.setContent(body_html)
|
||||
}, 100)
|
||||
}
|
||||
this.notifies = { employee_ids, custom_email, subject, method }
|
||||
}
|
||||
} else {
|
||||
this.title = `新增触发器 ${property.alias || property.name}`
|
||||
this.triggerId = null
|
||||
this.form = _.cloneDeep(this.defaultForm)
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.$refs.triggerForm.clearValidate()
|
||||
this.$refs.triggerForm.resetFields()
|
||||
this.filterValue = ''
|
||||
this.form = _.cloneDeep(this.defaultForm)
|
||||
this.dateForm = _.cloneDeep(this.defaultDateForm)
|
||||
this.notifies = _.cloneDeep(this.defaultNotify)
|
||||
this.category = 1
|
||||
this.triggerAction = '1'
|
||||
this.filterExp = ''
|
||||
this.visible = false
|
||||
},
|
||||
getWxList() {
|
||||
getWX().then((res) => {
|
||||
this.WxUsers = res.filter((item) => item.wx_id)
|
||||
})
|
||||
},
|
||||
filterChange(value) {
|
||||
this.filterValue = value
|
||||
},
|
||||
handleOk() {
|
||||
this.$refs.triggerForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.$refs.filterComp.handleSubmit()
|
||||
const { name, description, enable, action, attr_ids } = this.form
|
||||
const params = {
|
||||
attr_id: '',
|
||||
option: {
|
||||
filter: this.filterExp,
|
||||
name,
|
||||
description,
|
||||
enable,
|
||||
},
|
||||
}
|
||||
switch (this.triggerAction) {
|
||||
case '1':
|
||||
const { employee_ids, custom_email, subject, method } = this.notifies
|
||||
const { body, body_html } = this.$refs.noticeContent.getContent()
|
||||
let tos = []
|
||||
if (employee_ids && employee_ids.length) {
|
||||
await getNoticeByEmployeeIds({ employee_ids: employee_ids.map((item) => item.split('-')[1]) }).then(
|
||||
(res) => {
|
||||
tos = tos.concat(res)
|
||||
}
|
||||
)
|
||||
params.option.employee_ids = employee_ids
|
||||
}
|
||||
if (this.showCustomEmail) {
|
||||
custom_email.split(';').forEach((email) => {
|
||||
tos.push({ email })
|
||||
})
|
||||
}
|
||||
if (this.category === 2) {
|
||||
const { before_days, notify_at } = this.dateForm
|
||||
params.option.notifies = { tos, subject, body, body_html, method, before_days, notify_at }
|
||||
} else {
|
||||
params.option.notifies = { tos, subject, body, body_html, method }
|
||||
}
|
||||
break
|
||||
case '2':
|
||||
const webhooks = this.$refs.webhook.getParams()
|
||||
params.option.webhooks = webhooks
|
||||
break
|
||||
case '3':
|
||||
params.option.dag_id = this.dag_id
|
||||
break
|
||||
}
|
||||
if (this.category === 1) {
|
||||
params.option.action = action
|
||||
if (action === '2') {
|
||||
params.option.attr_ids = attr_ids
|
||||
}
|
||||
}
|
||||
if (this.category === 2) {
|
||||
this.$refs.dateForm.validate((valid) => {
|
||||
if (valid) {
|
||||
const { attr_id, before_days, notify_at } = this.dateForm
|
||||
params.attr_id = attr_id
|
||||
params.option.notifies = { ..._.cloneDeep(params.option.notifies), before_days, notify_at }
|
||||
} else {
|
||||
throw Error()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.triggerId) {
|
||||
await updateTrigger(this.CITypeId, this.triggerId, params)
|
||||
} else {
|
||||
await addTrigger(this.CITypeId, params)
|
||||
}
|
||||
this.handleCancel()
|
||||
if (this.refresh) {
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDetele() {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(that.CITypeId, that.triggerId).then(() => {
|
||||
that.$message.success('删除成功!')
|
||||
that.handleCancel()
|
||||
if (that.refresh) {
|
||||
that.refresh()
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
setExpFromFilter(filterExp) {
|
||||
if (filterExp) {
|
||||
this.filterExp = `${filterExp}`
|
||||
} else {
|
||||
this.filterExp = ''
|
||||
}
|
||||
},
|
||||
handleBlurInput() {
|
||||
setTimeout(() => {
|
||||
this.isShow = false
|
||||
}, 100)
|
||||
},
|
||||
focusOnInput() {
|
||||
this.isShow = true
|
||||
},
|
||||
handleClickSelect(item) {
|
||||
this.searchValue = item.label
|
||||
this.dag_id = item.id
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.trigger-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.trigger-form-employee,
|
||||
.trigger-form-filter {
|
||||
.ant-form-item-control {
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.trigger-form-filter {
|
||||
.table-filter-add {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~@/style/static.less';
|
||||
|
||||
.auto-complete-wrapper {
|
||||
position: relative;
|
||||
margin-left: 25px;
|
||||
width: 250px;
|
||||
margin-top: 20px;
|
||||
.auto-complete-wrapper-popover {
|
||||
position: fixed;
|
||||
width: 250px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px #00000026;
|
||||
.auto-complete-wrapper-popover-item {
|
||||
.ops_popover_item();
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trigger-form-custom-email {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.trigger-tips {
|
||||
border: 1px solid #d4380d;
|
||||
background-color: #fff2e8;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
color: #d4380d;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,144 +1,172 @@
|
|||
<template>
|
||||
<div class="ci-types-triggers">
|
||||
<div style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleAddTrigger"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>新增触发器</a-button
|
||||
>
|
||||
<span class="trigger-tips">{{ tips }}</span>
|
||||
</div>
|
||||
<vxe-table
|
||||
stripe
|
||||
:data="tableData"
|
||||
size="small"
|
||||
show-overflow
|
||||
highlight-hover-row
|
||||
keep-source
|
||||
:max-height="windowHeight - 180"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column field="attr_name" title="属性名"></vxe-column>
|
||||
<vxe-column field="notify.subject" title="主题"></vxe-column>
|
||||
<vxe-column field="notify.body" title="内容"></vxe-column>
|
||||
<vxe-column field="notify.wx_to" title="微信通知">
|
||||
<template #default="{ row }">
|
||||
<span v-for="(person, index) in row.notify.wx_to" :key="person + index">[{{ person }}]</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="notify.mail_to" title="邮件通知">
|
||||
<template #default="{ row }">
|
||||
<span v-for="(email, index) in row.notify.mail_to" :key="email + index">[{{ email }}]</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="notify.before_days" title="提前">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.notify.before_days">{{ row.notify.before_days }}天</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="notify.notify_at" title="发送时间"></vxe-column>
|
||||
<vxe-column field="operation" title="操作" width="200px" align="center">
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTriggerList, deleteTrigger } from '../../api/CIType'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import TriggerForm from './triggerForm.vue'
|
||||
export default {
|
||||
name: 'TriggerTable',
|
||||
components: { TriggerForm },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tips: '主题、内容、微信通知和邮件通知都可以引用该模型的属性值,引用方法为: {{ attr_name }}',
|
||||
tableData: [],
|
||||
attrList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
canAddTriggerAttr() {
|
||||
return this.attrList.filter((attr) => attr.value_type === '3' || attr.value_type === '4')
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return { refresh: this.getTableData }
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
async getTableData() {
|
||||
const [triggerList, attrList] = await Promise.all([
|
||||
getTriggerList(this.CITypeId),
|
||||
getCITypeAttributesById(this.CITypeId),
|
||||
])
|
||||
triggerList.forEach((trigger) => {
|
||||
const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id)
|
||||
if (_find) {
|
||||
trigger.attr_name = _find.alias || _find.name
|
||||
}
|
||||
})
|
||||
this.tableData = triggerList
|
||||
this.attrList = attrList.attributes
|
||||
},
|
||||
handleAddTrigger() {
|
||||
this.$refs.triggerForm.createFromTriggerTable(this.canAddTriggerAttr)
|
||||
},
|
||||
handleDetele(id) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(that.CITypeId, id).then(() => {
|
||||
that.$message.success('删除成功!')
|
||||
that.getTableData()
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEdit(row) {
|
||||
const _find = this.attrList.find((attr) => attr.id === row.attr_id)
|
||||
this.$refs.triggerForm.open({
|
||||
id: row.attr_id,
|
||||
alias: _find ? _find.alias || _find.name : '',
|
||||
trigger: { id: row.id, notify: row.notify },
|
||||
has_trigger: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-types-triggers {
|
||||
padding: 16px 24px 24px;
|
||||
.trigger-tips {
|
||||
border: 1px solid #d4380d;
|
||||
background-color: #fff2e8;
|
||||
padding: 2px 10px;
|
||||
border-radius: 4px;
|
||||
color: #d4380d;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="ci-types-triggers">
|
||||
<div style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleAddTrigger"
|
||||
size="small"
|
||||
class="ops-button-primary"
|
||||
icon="plus"
|
||||
>新增触发器</a-button
|
||||
>
|
||||
</div>
|
||||
<vxe-table
|
||||
stripe
|
||||
:data="tableData"
|
||||
size="small"
|
||||
show-overflow
|
||||
highlight-hover-row
|
||||
keep-source
|
||||
:max-height="windowHeight - 180"
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column field="option.name" title="名称"></vxe-column>
|
||||
<vxe-column field="option.description" title="备注"></vxe-column>
|
||||
<vxe-column field="type" title="类型">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.attr_id">日期属性</span>
|
||||
<span v-else>数据变更</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="option.enable" title="开启">
|
||||
<template #default="{ row }">
|
||||
<a-switch :checked="row.option.enable" @click="changeEnable(row)"></a-switch>
|
||||
</template>
|
||||
</vxe-column>
|
||||
|
||||
<!-- <vxe-column field="attr_name" title="属性名"></vxe-column>
|
||||
<vxe-column field="option.subject" title="主题"></vxe-column>
|
||||
<vxe-column field="option.body" title="内容"></vxe-column>
|
||||
<vxe-column field="option.wx_to" title="微信通知">
|
||||
<template #default="{ row }">
|
||||
<span v-for="(person, index) in row.option.wx_to" :key="person + index">[{{ person }}]</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="option.mail_to" title="邮件通知">
|
||||
<template #default="{ row }">
|
||||
<span v-for="(email, index) in row.option.mail_to" :key="email + index">[{{ email }}]</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="option.before_days" title="提前">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.option.before_days">{{ row.option.before_days }}天</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="option.notify_at" title="发送时间"></vxe-column> -->
|
||||
<vxe-column field="operation" title="操作" width="80px" align="center">
|
||||
<template #default="{ row }">
|
||||
<a-space>
|
||||
<a @click="handleEdit(row)"><a-icon type="edit"/></a>
|
||||
<a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType'
|
||||
import { getCITypeAttributesById } from '../../api/CITypeAttr'
|
||||
import TriggerForm from './triggerForm.vue'
|
||||
import { getAllDepAndEmployee } from '@/api/company'
|
||||
|
||||
export default {
|
||||
name: 'TriggerTable',
|
||||
components: { TriggerForm },
|
||||
props: {
|
||||
CITypeId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
attrList: [],
|
||||
allTreeDepAndEmp: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
refresh: this.getTableData,
|
||||
provide_allTreeDepAndEmp: () => {
|
||||
return this.allTreeDepAndEmp
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getAllDepAndEmployee()
|
||||
},
|
||||
methods: {
|
||||
getAllDepAndEmployee() {
|
||||
getAllDepAndEmployee({ block: 0 }).then((res) => {
|
||||
this.allTreeDepAndEmp = res
|
||||
})
|
||||
},
|
||||
async getTableData() {
|
||||
const [triggerList, attrList] = await Promise.all([
|
||||
getTriggerList(this.CITypeId),
|
||||
getCITypeAttributesById(this.CITypeId),
|
||||
])
|
||||
triggerList.forEach((trigger) => {
|
||||
const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id)
|
||||
if (_find) {
|
||||
trigger.attr_name = _find.alias || _find.name
|
||||
}
|
||||
})
|
||||
this.tableData = triggerList
|
||||
this.attrList = attrList.attributes
|
||||
},
|
||||
handleAddTrigger() {
|
||||
this.$refs.triggerForm.createFromTriggerTable(this.attrList)
|
||||
},
|
||||
handleDetele(id) {
|
||||
const that = this
|
||||
this.$confirm({
|
||||
title: '警告',
|
||||
content: '确认删除该触发器吗?',
|
||||
onOk() {
|
||||
deleteTrigger(that.CITypeId, id).then(() => {
|
||||
that.$message.success('删除成功!')
|
||||
that.getTableData()
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.$refs.triggerForm.open(
|
||||
{
|
||||
id: row.attr_id,
|
||||
alias: row?.option?.name ?? '',
|
||||
trigger: { id: row.id, attr_id: row.attr_id, option: row.option },
|
||||
has_trigger: true,
|
||||
},
|
||||
this.attrList
|
||||
)
|
||||
},
|
||||
changeEnable(row) {
|
||||
const _row = _.cloneDeep(row)
|
||||
delete _row.id
|
||||
const enable = row?.option?.enable ?? true
|
||||
_row.option.enable = !enable
|
||||
updateTrigger(this.CITypeId, row.id, _row).then(() => {
|
||||
this.getTableData()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ci-types-triggers {
|
||||
padding: 16px 24px 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,38 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-card :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="CI变更">
|
||||
<ci-table></ci-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="关系变更">
|
||||
<relation-table></relation-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="模型变更">
|
||||
<type-table></type-table>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CiTable from './modules/ciTable.vue'
|
||||
import RelationTable from './modules/relation.vue'
|
||||
import TypeTable from './modules/typeTable.vue'
|
||||
export default {
|
||||
name: 'Index',
|
||||
data() {
|
||||
return {
|
||||
userList: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CiTable,
|
||||
RelationTable,
|
||||
TypeTable
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<template>
|
||||
<div>
|
||||
<a-card :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="CI变更">
|
||||
<ci-table></ci-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="关系变更">
|
||||
<relation-table></relation-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="模型变更">
|
||||
<type-table></type-table>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab="触发历史">
|
||||
<TriggerTable></TriggerTable>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CiTable from './modules/ciTable.vue'
|
||||
import RelationTable from './modules/relation.vue'
|
||||
import TypeTable from './modules/typeTable.vue'
|
||||
import TriggerTable from './modules/triggerTable.vue'
|
||||
export default {
|
||||
name: 'OperationHistory',
|
||||
components: {
|
||||
CiTable,
|
||||
RelationTable,
|
||||
TypeTable,
|
||||
TriggerTable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userList: [],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -1,420 +1,421 @@
|
|||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="ciTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_XID"
|
||||
:loading="loading"
|
||||
border
|
||||
size="small"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:span-method="mergeRowMethod"
|
||||
:scroll-y="{enabled: false}"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="100px" title="用户">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled"/>
|
||||
<a slot="content">
|
||||
<a-input placeholder="输入筛选用户名" size="small" v-model="queryParams.username" style="width: 200px" allowClear/>
|
||||
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="type_id" width="100px" title="模型"></vxe-column>
|
||||
<vxe-column field="operate_type" width="89px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled"/>
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in ciTableAttrList[4].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type === '新增' ">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type === '修改' ">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="attr_alias" title="属性"></vxe-column>
|
||||
<vxe-column field="old" title="旧"></vxe-column>
|
||||
<vxe-column field="new" title="新"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="total"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
export default {
|
||||
name: 'CiTable',
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
typeId: undefined,
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增'],
|
||||
['1', '删除'],
|
||||
['2', '修改'],
|
||||
]),
|
||||
loading: true,
|
||||
typeList: null,
|
||||
userList: [],
|
||||
tableData: [],
|
||||
total: 0,
|
||||
isExpand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
},
|
||||
ciTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'username',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '模型',
|
||||
is_choice: true,
|
||||
name: 'type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '属性',
|
||||
is_choice: true,
|
||||
name: 'attr_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新增': 0 },
|
||||
{ '删除': 1 },
|
||||
{ '修改': 2 },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: 'CI_ID',
|
||||
is_choice: false,
|
||||
name: 'ci_id',
|
||||
value_type: '2'
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.ciTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.attr_id
|
||||
}
|
||||
)
|
||||
await Promise.all([
|
||||
this.getUserList(),
|
||||
this.getTypes()
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getCIHistoryTable(queryParams)
|
||||
const tempArr = []
|
||||
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)
|
||||
const tempObj = Object.assign(subItem, item[0])
|
||||
tempArr.push(tempObj)
|
||||
})
|
||||
})
|
||||
this.tableData = tempArr
|
||||
this.total = res.total
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
this.userList = res.map(x => {
|
||||
const username = x.nickname
|
||||
const obj = {
|
||||
[username]: username
|
||||
}
|
||||
return obj
|
||||
})
|
||||
this.ciTableAttrList[1].choice_value = this.userList
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
const typesMap = new Map()
|
||||
res.ci_types.forEach(item => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesArr.push(tempObj)
|
||||
typesMap.set(item.id, item.alias)
|
||||
}
|
||||
})
|
||||
this.typeList = typesMap
|
||||
this.ciTableAttrList[2].choice_value = typesArr
|
||||
},
|
||||
// 获取模型对应属性
|
||||
async getAttrs(type_id) {
|
||||
if (!type_id) {
|
||||
this.ciTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const res = await getCITypeAttributesById(type_id)
|
||||
const attrsArr = []
|
||||
res.attributes.forEach(item => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
attrsArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.ciTableAttrList[3].choice_value = attrsArr
|
||||
},
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
ci_id: undefined,
|
||||
attr_id: undefined,
|
||||
operate_type: undefined
|
||||
}
|
||||
// 将属性options重置
|
||||
this.ciTableAttrList[3].choice_value = []
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换type_id
|
||||
handleTypeId(type_id) {
|
||||
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
|
||||
},
|
||||
// 表单改变,重新获取属性列表
|
||||
searchFormChange(queryParams) {
|
||||
if (this.typeId !== queryParams.type_id) {
|
||||
this.typeId = queryParams.type_id
|
||||
this.getAttrs(queryParams.type_id)
|
||||
}
|
||||
if (queryParams.type_id === undefined) {
|
||||
this.typeId = undefined
|
||||
this.$refs.child.queryParams.attr_id = undefined
|
||||
}
|
||||
},
|
||||
// 合并表格
|
||||
mergeRowMethod ({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'user', 'type_id']
|
||||
// 单元格值 = 行[列.属性] 确定一格
|
||||
const cellValue = row[column.property]
|
||||
const created_at = row['created_at']
|
||||
// 如果单元格值不为空且作用域包含当前列
|
||||
if (column.property === 'created_at') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'user') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'type_id') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filterUser() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterUserReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.username = ''
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return (
|
||||
option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter{
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
ref="child"
|
||||
:attrList="ciTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
@searchFormChange="searchFormChange"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
row-id="_XID"
|
||||
:loading="loading"
|
||||
border
|
||||
size="small"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
:span-method="mergeRowMethod"
|
||||
:scroll-y="{enabled: false}"
|
||||
class="ops-unstripe-table"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="100px" title="用户">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled"/>
|
||||
<a slot="content">
|
||||
<a-input placeholder="输入筛选用户名" size="small" v-model="queryParams.username" style="width: 200px" allowClear/>
|
||||
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="type_id" width="100px" title="模型"></vxe-column>
|
||||
<vxe-column field="operate_type" width="89px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled"/>
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in ciTableAttrList[4].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type === '新增' ">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type === '修改' ">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="attr_alias" title="属性"></vxe-column>
|
||||
<vxe-column field="old" title="旧"></vxe-column>
|
||||
<vxe-column field="new" title="新"></vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50,100,200]"
|
||||
:total="total"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Pager from './pager.vue'
|
||||
import SearchForm from './searchForm.vue'
|
||||
import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||
export default {
|
||||
name: 'CiTable',
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
typeId: undefined,
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增'],
|
||||
['1', '删除'],
|
||||
['2', '修改'],
|
||||
]),
|
||||
loading: true,
|
||||
typeList: null,
|
||||
userList: [],
|
||||
tableData: [],
|
||||
total: 0,
|
||||
isExpand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
},
|
||||
ciTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3'
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'username',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '模型',
|
||||
is_choice: true,
|
||||
name: 'type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '属性',
|
||||
is_choice: true,
|
||||
name: 'attr_id',
|
||||
value_type: '2',
|
||||
choice_value: []
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ '新增': 0 },
|
||||
{ '删除': 1 },
|
||||
{ '修改': 2 },
|
||||
]
|
||||
},
|
||||
{
|
||||
alias: 'CI_ID',
|
||||
is_choice: false,
|
||||
name: 'ci_id',
|
||||
value_type: '2'
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$watch(
|
||||
function () {
|
||||
return this.ciTableAttrList[3].choice_value
|
||||
},
|
||||
function () {
|
||||
delete this.$refs.child.queryParams.attr_id
|
||||
}
|
||||
)
|
||||
await Promise.all([
|
||||
this.getUserList(),
|
||||
this.getTypes()
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getCIHistoryTable(queryParams)
|
||||
const tempArr = []
|
||||
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)
|
||||
const tempObj = Object.assign(subItem, item[0])
|
||||
tempArr.push(tempObj)
|
||||
})
|
||||
})
|
||||
this.tableData = tempArr
|
||||
this.total = res.total
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
this.userList = res.map(x => {
|
||||
const username = x.nickname
|
||||
const obj = {
|
||||
[username]: username
|
||||
}
|
||||
return obj
|
||||
})
|
||||
this.ciTableAttrList[1].choice_value = this.userList
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
const typesMap = new Map()
|
||||
res.ci_types.forEach(item => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesArr.push(tempObj)
|
||||
typesMap.set(item.id, item.alias)
|
||||
}
|
||||
})
|
||||
this.typeList = typesMap
|
||||
this.ciTableAttrList[2].choice_value = typesArr
|
||||
},
|
||||
// 获取模型对应属性
|
||||
async getAttrs(type_id) {
|
||||
if (!type_id) {
|
||||
this.ciTableAttrList[3].choice_value = []
|
||||
return
|
||||
}
|
||||
const res = await getCITypeAttributesById(type_id)
|
||||
const attrsArr = []
|
||||
res.attributes.forEach(item => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
attrsArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.ciTableAttrList[3].choice_value = attrsArr
|
||||
},
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
ci_id: undefined,
|
||||
attr_id: undefined,
|
||||
operate_type: undefined
|
||||
}
|
||||
// 将属性options重置
|
||||
this.ciTableAttrList[3].choice_value = []
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换type_id
|
||||
handleTypeId(type_id) {
|
||||
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
|
||||
},
|
||||
// 表单改变,重新获取属性列表
|
||||
searchFormChange(queryParams) {
|
||||
if (this.typeId !== queryParams.type_id) {
|
||||
this.typeId = queryParams.type_id
|
||||
this.getAttrs(queryParams.type_id)
|
||||
}
|
||||
if (queryParams.type_id === undefined) {
|
||||
this.typeId = undefined
|
||||
this.$refs.child.queryParams.attr_id = undefined
|
||||
}
|
||||
},
|
||||
// 合并表格
|
||||
mergeRowMethod ({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'user', 'type_id']
|
||||
// 单元格值 = 行[列.属性] 确定一格
|
||||
const cellValue = row[column.property]
|
||||
const created_at = row['created_at']
|
||||
// 如果单元格值不为空且作用域包含当前列
|
||||
if (column.property === 'created_at') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'user') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'type_id') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filterUser() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterUserReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.username = ''
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return (
|
||||
option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter{
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,116 +1,116 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
|
||||
<a-button class="page-button" size="small" >{{ currentPage }}</a-button>
|
||||
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
|
||||
<a-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ size }}条/页
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small"> {{ pageSize }}条/页 <a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === true) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row{
|
||||
margin-top: 5px;
|
||||
.left-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-space align="end">
|
||||
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button>
|
||||
<a-button class="page-button" size="small" >{{ currentPage }}</a-button>
|
||||
<a-button class="right-button" size="small" :disabled="nextIsDisabled" @click="nextPage"><a-icon type="right" /></a-button>
|
||||
<a-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
|
||||
{{ size }}条/页
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-button size="small"> {{ pageSize }}条/页 <a-icon type="down" /> </a-button>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dropdownIsDisabled: false,
|
||||
prevIsDisabled: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nextIsDisabled() {
|
||||
return this.isLoading || this.total < this.pageSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isLoading: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === true) {
|
||||
this.dropdownIsDisabled = true
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.dropdownIsDisabled = false
|
||||
if (this.currentPage === 1) {
|
||||
this.prevIsDisabled = true
|
||||
} else {
|
||||
this.prevIsDisabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
currentPage: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (val === 1) {
|
||||
this.prevIsDisabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(size) {
|
||||
this.$emit('showSizeChange', size)
|
||||
},
|
||||
nextPage() {
|
||||
const pageNum = this.currentPage + 1
|
||||
this.$emit('change', pageNum)
|
||||
},
|
||||
prevPage() {
|
||||
const pageNum = this.currentPage - 1
|
||||
this.$emit('change', pageNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row{
|
||||
margin-top: 5px;
|
||||
.left-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.right-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
.page-button{
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,403 +1,404 @@
|
|||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
:attrList="relationTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
border
|
||||
size="small"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
row-id="_XID"
|
||||
:scroll-y="{ enabled: false }"
|
||||
:span-method="mergeRowMethod"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="100px" title="用户">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-input
|
||||
placeholder="输入筛选用户名"
|
||||
size="small"
|
||||
v-model="queryParams.username"
|
||||
style="width: 200px"
|
||||
allowClear
|
||||
/>
|
||||
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_type" width="89px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in relationTableAttrList[4].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type.includes('新增')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="changeDescription" title="描述">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-if="row && row.first">
|
||||
{{
|
||||
`${row.first.ci_type_alias}${
|
||||
row.first.unique_alias && row.first[row.first.unique]
|
||||
? `(${row.first.unique_alias}:${row.first[row.first.unique]})`
|
||||
: ''
|
||||
}`
|
||||
}}
|
||||
</a-tag>
|
||||
<a-tag v-if="row.changeDescription === '没有修改'">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<template v-else-if="row.operate_type.includes('修改')">
|
||||
<a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr">
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-tag color="green" v-else-if="row.operate_type.includes('新增')" :style="{ fontWeight: 'bolder' }">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else-if="row.operate_type.includes('删除')">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<a-tag v-if="row && row.second">
|
||||
{{
|
||||
`${row.second.ci_type_alias}${
|
||||
row.second.unique_alias && row.second[row.second.unique]
|
||||
? `(${row.second.unique_alias}:${row.second[row.second.unique]})`
|
||||
: ''
|
||||
}`
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="total"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SearchForm from './searchForm'
|
||||
import Pager from './pager.vue'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getRelationTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
export default {
|
||||
name: 'RelationTable',
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
loading: true,
|
||||
isExpand: false,
|
||||
tableData: [],
|
||||
relationTypeList: null,
|
||||
total: 0,
|
||||
userList: [],
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增'],
|
||||
['1', '删除'],
|
||||
['2', '修改'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
first_ci_id: undefined,
|
||||
second_ci_id: undefined,
|
||||
operate_type: undefined,
|
||||
},
|
||||
relationTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'username',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: 'FirstCI_ID',
|
||||
is_choice: false,
|
||||
name: 'first_ci_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: 'SecondCI_ID',
|
||||
is_choice: false,
|
||||
name: 'second_ci_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新增: 0 }, { 删除: 1 }, { 修改: 2 }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([
|
||||
this.getRelationTypes(),
|
||||
this.getUserList(),
|
||||
this.getTypes(),
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getRelationTable(queryParams)
|
||||
const tempArr = []
|
||||
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])
|
||||
tempArr.push(tempObj)
|
||||
})
|
||||
})
|
||||
this.total = res.total
|
||||
this.tableData = tempArr
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
this.userList = res.map((x) => {
|
||||
const username = x.nickname
|
||||
const obj = {
|
||||
[username]: username,
|
||||
}
|
||||
return obj
|
||||
})
|
||||
this.relationTableAttrList[1].choice_value = this.userList
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
res.ci_types.forEach((item) => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.relationTableAttrList[2].choice_value = typesArr
|
||||
this.relationTableAttrList[3].choice_value = typesArr
|
||||
},
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
const relationTypeMap = new Map()
|
||||
res.forEach((item) => {
|
||||
relationTypeMap.set(item.id, item.name)
|
||||
})
|
||||
this.relationTypeList = relationTypeMap
|
||||
},
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
first_ci_id: undefined,
|
||||
second_ci_id: undefined,
|
||||
operate_type: undefined,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换relation_type_id
|
||||
handleRelationType(relation_type_id) {
|
||||
return this.relationTypeList.get(relation_type_id)
|
||||
},
|
||||
// 合并表格
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'user']
|
||||
// 单元格值 = 行[列.属性] 确定一格
|
||||
const cellValue = row[column.property]
|
||||
const created_at = row['created_at']
|
||||
// 如果单元格值不为空且作用域包含当前列
|
||||
if (column.property === 'created_at') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'user') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filterUser() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterUserReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.username = ''
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter {
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
:attrList="relationTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
show-overflow="tooltip"
|
||||
show-header-overflow="tooltip"
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
row-id="_XID"
|
||||
:scroll-y="{ enabled: false }"
|
||||
:span-method="mergeRowMethod"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="100px" title="用户">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-input
|
||||
placeholder="输入筛选用户名"
|
||||
size="small"
|
||||
v-model="queryParams.username"
|
||||
style="width: 200px"
|
||||
allowClear
|
||||
/>
|
||||
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operate_type" width="89px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in relationTableAttrList[4].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type.includes('新增')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="changeDescription" title="描述">
|
||||
<template #default="{ row }">
|
||||
<a-tag v-if="row && row.first">
|
||||
{{
|
||||
`${row.first.ci_type_alias}${
|
||||
row.first.unique_alias && row.first[row.first.unique]
|
||||
? `(${row.first.unique_alias}:${row.first[row.first.unique]})`
|
||||
: ''
|
||||
}`
|
||||
}}
|
||||
</a-tag>
|
||||
<a-tag v-if="row.changeDescription === '没有修改'">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<template v-else-if="row.operate_type.includes('修改')">
|
||||
<a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr">
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-tag color="green" v-else-if="row.operate_type.includes('新增')" :style="{ fontWeight: 'bolder' }">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else-if="row.operate_type.includes('删除')">
|
||||
{{ row.relation_type_id }}
|
||||
</a-tag>
|
||||
<a-tag v-if="row && row.second">
|
||||
{{
|
||||
`${row.second.ci_type_alias}${
|
||||
row.second.unique_alias && row.second[row.second.unique]
|
||||
? `(${row.second.unique_alias}:${row.second[row.second.unique]})`
|
||||
: ''
|
||||
}`
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<pager
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-size.sync="queryParams.page_size"
|
||||
:page-sizes="[50, 100, 200]"
|
||||
:total="total"
|
||||
:isLoading="loading"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
></pager>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SearchForm from './searchForm'
|
||||
import Pager from './pager.vue'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getRelationTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
export default {
|
||||
name: 'RelationTable',
|
||||
components: { SearchForm, Pager },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
loading: true,
|
||||
isExpand: false,
|
||||
tableData: [],
|
||||
relationTypeList: null,
|
||||
total: 0,
|
||||
userList: [],
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增'],
|
||||
['1', '删除'],
|
||||
['2', '修改'],
|
||||
]),
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
first_ci_id: undefined,
|
||||
second_ci_id: undefined,
|
||||
operate_type: undefined,
|
||||
},
|
||||
relationTableAttrList: [
|
||||
{
|
||||
alias: '日期',
|
||||
is_choice: false,
|
||||
name: 'datetime',
|
||||
value_type: '3',
|
||||
},
|
||||
{
|
||||
alias: '用户',
|
||||
is_choice: true,
|
||||
name: 'username',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: 'FirstCI_ID',
|
||||
is_choice: false,
|
||||
name: 'first_ci_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: 'SecondCI_ID',
|
||||
is_choice: false,
|
||||
name: 'second_ci_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [{ 新增: 0 }, { 删除: 1 }, { 修改: 2 }],
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([
|
||||
this.getRelationTypes(),
|
||||
this.getUserList(),
|
||||
this.getTypes(),
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getRelationTable(queryParams)
|
||||
const tempArr = []
|
||||
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])
|
||||
tempArr.push(tempObj)
|
||||
})
|
||||
})
|
||||
this.total = res.total
|
||||
this.tableData = tempArr
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
this.userList = res.map((x) => {
|
||||
const username = x.nickname
|
||||
const obj = {
|
||||
[username]: username,
|
||||
}
|
||||
return obj
|
||||
})
|
||||
this.relationTableAttrList[1].choice_value = this.userList
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
res.ci_types.forEach((item) => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.relationTableAttrList[2].choice_value = typesArr
|
||||
this.relationTableAttrList[3].choice_value = typesArr
|
||||
},
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
const relationTypeMap = new Map()
|
||||
res.forEach((item) => {
|
||||
relationTypeMap.set(item.id, item.name)
|
||||
})
|
||||
this.relationTypeList = relationTypeMap
|
||||
},
|
||||
onShowSizeChange(size) {
|
||||
this.queryParams.page_size = size
|
||||
this.queryParams.page = 1
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onChange(pageNum) {
|
||||
this.queryParams.page = pageNum
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.queryParams = queryParams
|
||||
this.getTable(queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
start: '',
|
||||
end: '',
|
||||
username: '',
|
||||
first_ci_id: undefined,
|
||||
second_ci_id: undefined,
|
||||
operate_type: undefined,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换relation_type_id
|
||||
handleRelationType(relation_type_id) {
|
||||
return this.relationTypeList.get(relation_type_id)
|
||||
},
|
||||
// 合并表格
|
||||
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
|
||||
const fields = ['created_at', 'user']
|
||||
// 单元格值 = 行[列.属性] 确定一格
|
||||
const cellValue = row[column.property]
|
||||
const created_at = row['created_at']
|
||||
// 如果单元格值不为空且作用域包含当前列
|
||||
if (column.property === 'created_at') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (column.property === 'user') {
|
||||
if (cellValue && fields.includes(column.property)) {
|
||||
// 前一行
|
||||
const prevRow = visibleData[_rowIndex - 1]
|
||||
// 下一行
|
||||
let nextRow = visibleData[_rowIndex + 1]
|
||||
// 如果前一行不为空且前一行单元格的值与cellValue相同
|
||||
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
} else {
|
||||
let countRowspan = 1
|
||||
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
|
||||
nextRow = visibleData[++countRowspan + _rowIndex]
|
||||
}
|
||||
if (countRowspan > 1) {
|
||||
return { rowspan: countRowspan, colspan: 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filterUser() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterUserReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.username = ''
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter {
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,191 +1,191 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-form :colon="false">
|
||||
<a-row :gutter="24">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="8"
|
||||
v-for="attr in attrList.slice(0,3)"
|
||||
:key="attr.name"
|
||||
>
|
||||
<a-form-item
|
||||
:label="attr.alias || attr.name "
|
||||
:labelCol="{span:4}"
|
||||
:wrapperCol="{span:20}"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
v-model="queryParams[attr.name]"
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
v-for="(choice, index) in attr.choice_value"
|
||||
:key="'Search_' + attr.name + index"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model="date"
|
||||
@change="onChange"
|
||||
:style="{width:'100%'}"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
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'"
|
||||
/>
|
||||
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<template v-if="expand && attrList.length >= 4">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="8"
|
||||
:key="'expand_' + item.name"
|
||||
v-for="item in attrList.slice(3)"
|
||||
>
|
||||
<a-form-item
|
||||
:label="item.alias || item.name"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
v-model="queryParams[item.name]"
|
||||
placeholder="请选择"
|
||||
v-if="item.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="'Search_' + item.name + index"
|
||||
v-for="(choice, index) in item.choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
:style="{width:'100%'}"
|
||||
@change="onChange"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
/>
|
||||
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }">
|
||||
<a-button type="primary" html-type="submit" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4">
|
||||
{{ expand?'隐藏':'展开' }} <a-icon :type="expand ? 'up' : 'down'" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
props: {
|
||||
attrList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueTypeMap,
|
||||
expand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
},
|
||||
date: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler: function (val) {
|
||||
this.preProcessData()
|
||||
this.$emit('searchFormChange', val)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleSearch() {
|
||||
this.queryParams.page = 1
|
||||
this.$emit('search', this.queryParams)
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
}
|
||||
this.date = undefined
|
||||
this.$emit('searchFormReset')
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.expand = !this.expand
|
||||
this.$emit('expandChange', this.expand)
|
||||
},
|
||||
|
||||
onChange(date, dateString) {
|
||||
this.queryParams.start = dateString[0]
|
||||
this.queryParams.end = dateString[1]
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return (
|
||||
option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
)
|
||||
},
|
||||
preProcessData() {
|
||||
Object.keys(this.queryParams).forEach(item => {
|
||||
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
|
||||
delete this.queryParams[item]
|
||||
}
|
||||
})
|
||||
return this.queryParams
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<a-form :colon="false">
|
||||
<a-row :gutter="24">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="8"
|
||||
v-for="attr in attrList.slice(0,3)"
|
||||
:key="attr.name"
|
||||
>
|
||||
<a-form-item
|
||||
:label="attr.alias || attr.name "
|
||||
:labelCol="{span:4}"
|
||||
:wrapperCol="{span:20}"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
v-model="queryParams[attr.name]"
|
||||
placeholder="请选择"
|
||||
v-if="attr.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
v-for="(choice, index) in attr.choice_value"
|
||||
:key="'Search_' + attr.name + index"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model="date"
|
||||
@change="onChange"
|
||||
:style="{width:'100%'}"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
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'"
|
||||
/>
|
||||
<a-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<template v-if="expand && attrList.length >= 4">
|
||||
<a-col
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="8"
|
||||
:key="'expand_' + item.name"
|
||||
v-for="item in attrList.slice(3)"
|
||||
>
|
||||
<a-form-item
|
||||
:label="item.alias || item.name"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
labelAlign="right"
|
||||
>
|
||||
<a-select
|
||||
v-model="queryParams[item.name]"
|
||||
placeholder="请选择"
|
||||
v-if="item.is_choice"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="'Search_' + item.name + index"
|
||||
v-for="(choice, index) in item.choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
:style="{width:'100%'}"
|
||||
@change="onChange"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
|
||||
:show-time="{
|
||||
hideDisabledOptions: true,
|
||||
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
|
||||
}"
|
||||
/>
|
||||
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }">
|
||||
<a-button type="primary" html-type="submit" @click="handleSearch">
|
||||
查询
|
||||
</a-button>
|
||||
<a-button :style="{ marginLeft: '8px' }" @click="handleReset">
|
||||
重置
|
||||
</a-button>
|
||||
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4">
|
||||
{{ expand?'隐藏':'展开' }} <a-icon :type="expand ? 'up' : 'down'" />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { valueTypeMap } from '../../../utils/const'
|
||||
export default {
|
||||
name: 'SearchForm',
|
||||
props: {
|
||||
attrList: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueTypeMap,
|
||||
expand: false,
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
},
|
||||
date: undefined
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler: function (val) {
|
||||
this.preProcessData()
|
||||
this.$emit('searchFormChange', val)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleSearch() {
|
||||
this.queryParams.page = 1
|
||||
this.$emit('search', this.queryParams)
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50
|
||||
}
|
||||
this.date = undefined
|
||||
this.$emit('searchFormReset')
|
||||
},
|
||||
|
||||
toggle() {
|
||||
this.expand = !this.expand
|
||||
this.$emit('expandChange', this.expand)
|
||||
},
|
||||
|
||||
onChange(date, dateString) {
|
||||
this.queryParams.start = dateString[0]
|
||||
this.queryParams.end = dateString[1]
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return (
|
||||
option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
)
|
||||
},
|
||||
preProcessData() {
|
||||
Object.keys(this.queryParams).forEach(item => {
|
||||
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
|
||||
delete this.queryParams[item]
|
||||
}
|
||||
})
|
||||
return this.queryParams
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<div>
|
||||
<vxe-table
|
||||
show-overflow
|
||||
show-header-overflow
|
||||
stripe
|
||||
size="small"
|
||||
class="ops-stripe-table"
|
||||
:data="tableData"
|
||||
v-bind="ci_id ? { maxHeight: `${windowHeight - 94}px` } : { height: `${windowHeight - 225}px` }"
|
||||
>
|
||||
<vxe-column field="trigger_name" title="触发器名称"> </vxe-column>
|
||||
<vxe-column field="type" title="类型">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.trigger && row.trigger.attr_id">日期属性</span>
|
||||
<span v-else-if="row.trigger && !row.trigger.attr_id">数据变更</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="事件">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.operate_type === '0'">新增实例</span>
|
||||
<span v-else-if="row.operate_type === '1'">删除实例</span>
|
||||
<span v-else-if="row.operate_type === '2'">实例变更</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="动作">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.webhook">Webhook</span>
|
||||
<span v-else-if="row.notify">通知</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="触发时间">
|
||||
<template #default="{row}">
|
||||
{{ row.updated_at || row.created_at }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div :style="{ textAlign: 'right' }" v-if="!ci_id">
|
||||
<a-pagination
|
||||
size="small"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
:page-size-options="pageSizeOptions"
|
||||
:current="tablePage.currentPage"
|
||||
:total="tablePage.totalResult"
|
||||
:show-total="(total, range) => `共 ${total} 条记录`"
|
||||
:page-size="tablePage.pageSize"
|
||||
:default-current="1"
|
||||
@change="pageOrSizeChange"
|
||||
@showSizeChange="pageOrSizeChange"
|
||||
>
|
||||
</a-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCiTriggers, getCiTriggersByCiId } from '../../../api/history'
|
||||
export default {
|
||||
name: 'TriggerTable',
|
||||
props: {
|
||||
ci_id: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tablePage: {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
totalResult: 0,
|
||||
},
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateTableData()
|
||||
},
|
||||
methods: {
|
||||
updateTableData(currentPage = 1, pageSize = this.tablePage.pageSize) {
|
||||
const params = { page: currentPage, page_size: pageSize }
|
||||
if (this.ci_id) {
|
||||
getCiTriggersByCiId(this.ci_id, params).then((res) => {
|
||||
this.tableData = res.items.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
trigger: res.id2trigger[item.trigger_id],
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
getCiTriggers(params).then((res) => {
|
||||
this.tableData = res?.result || []
|
||||
this.tablePage = {
|
||||
...this.tablePage,
|
||||
currentPage: res.page,
|
||||
pageSize: res.page_size,
|
||||
totalResult: res.numfound,
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
pageOrSizeChange(currentPage, pageSize) {
|
||||
this.updateTableData(currentPage, pageSize)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -1,477 +1,478 @@
|
|||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
:attrList="typeTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
border
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
row-id="_XID"
|
||||
size="small"
|
||||
:row-config="{isHover: true}"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="116px" title="用户"></vxe-column>
|
||||
<vxe-column field="operate_type" width="135px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in typeTableAttrList[1].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type.includes('新增')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="type_id" title="模型" width="150px">
|
||||
<template #default="{ row }">
|
||||
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="changeDescription" title="描述">
|
||||
<template #default="{ row }">
|
||||
<p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === '没有修改'">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
<template v-else-if="row.operate_type.includes('修改')">
|
||||
<p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr">
|
||||
{{ tag }}
|
||||
</p>
|
||||
</template>
|
||||
<p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes('新增')">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
<p style="color:#f5222d;" v-else-if="row.operate_type.includes('删除')">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-pagination
|
||||
size="small"
|
||||
v-model="current"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:total="numfound"
|
||||
show-size-changer
|
||||
:page-size="pageSize"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
:show-total="(total) => `共 ${total} 条记录`"
|
||||
>
|
||||
</a-pagination>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import SearchForm from './searchForm'
|
||||
import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
export default {
|
||||
name: 'TypeTable',
|
||||
components: { SearchForm },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
relationTypeList: null,
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增模型'],
|
||||
['1', '修改模型'],
|
||||
['2', '删除模型'],
|
||||
['3', '新增属性'],
|
||||
['4', '修改属性'],
|
||||
['5', '删除属性'],
|
||||
['6', '新增触发器'],
|
||||
['7', '修改触发器'],
|
||||
['8', '删除触发器'],
|
||||
['9', '新增联合唯一'],
|
||||
['10', '修改联合唯一'],
|
||||
['11', '删除联合唯一'],
|
||||
['12', '新增关系'],
|
||||
['13', '删除关系'],
|
||||
]),
|
||||
typeList: null,
|
||||
userList: [],
|
||||
typeTableAttrList: [
|
||||
{
|
||||
alias: '模型',
|
||||
is_choice: true,
|
||||
name: 'type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ 新增模型: 0 },
|
||||
{ 修改模型: 1 },
|
||||
{ 删除模型: 2 },
|
||||
{ 新增属性: 3 },
|
||||
{ 修改属性: 4 },
|
||||
{ 删除属性: 5 },
|
||||
{ 新增触发器: 6 },
|
||||
{ 修改触发器: 7 },
|
||||
{ 删除触发器: 8 },
|
||||
{ 新增联合唯一: 9 },
|
||||
{ 修改联合唯一: 10 },
|
||||
{ 删除联合唯一: 11 },
|
||||
{ 新增关系: 12 },
|
||||
{ 删除关系: 13 },
|
||||
],
|
||||
},
|
||||
],
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
isExpand: false,
|
||||
current: 1,
|
||||
pageSize: 50,
|
||||
total: 0,
|
||||
numfound: 0,
|
||||
tableData: [],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
type_id: undefined,
|
||||
operate_type: undefined,
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([
|
||||
this.getRelationTypes(),
|
||||
this.getTypes(),
|
||||
this.getUserList(),
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 331
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
current(val) {
|
||||
this.queryParams.page = val
|
||||
},
|
||||
pageSize(val) {
|
||||
this.queryParams.page_size = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getCITypesTable(queryParams)
|
||||
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)
|
||||
})
|
||||
this.tableData = res.result
|
||||
this.pageSize = res.page_size
|
||||
this.current = res.page
|
||||
this.numfound = res.numfound
|
||||
this.total = res.total
|
||||
console.log(this.tableData)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
const typesMap = new Map()
|
||||
res.ci_types.forEach((item) => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesMap.set(item.id, item.alias)
|
||||
typesArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.typeList = typesMap
|
||||
// 设置模型options选项
|
||||
this.typeTableAttrList[0].choice_value = typesArr
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
const userListMap = new Map()
|
||||
res.forEach((item) => {
|
||||
userListMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.userList = userListMap
|
||||
},
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
const relationTypeMap = new Map()
|
||||
res.forEach((item) => {
|
||||
relationTypeMap.set(item.id, item.name)
|
||||
})
|
||||
this.relationTypeList = relationTypeMap
|
||||
},
|
||||
onChange(current) {
|
||||
this.current = current
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onShowSizeChange(current, size) {
|
||||
this.current = 1
|
||||
this.pageSize = size
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.current = 1
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
type_id: undefined,
|
||||
operate_type: undefined,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换type_id
|
||||
handleTypeId(type_id) {
|
||||
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
|
||||
},
|
||||
// 转换uid
|
||||
handleUID(uid) {
|
||||
return this.userList.get(uid)
|
||||
},
|
||||
// 转换relation_type_id
|
||||
handleRelationType(relation_type_id) {
|
||||
return this.relationTypeList.get(relation_type_id)
|
||||
},
|
||||
// 处理改变描述
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// 新增模型
|
||||
case '0': {
|
||||
item.changeDescription = '新增模型:' + item.change.alias
|
||||
break
|
||||
}
|
||||
// 修改模型
|
||||
case '1': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
const newVal = item.change.new[key]
|
||||
const oldVal = item.change.old[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` [ ${key} : 改为 ${newVal || '""'} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
} else {
|
||||
const str = ` [ ${key} : 由 ${oldVal || '""'} 改为 ${newVal || '""'} ] `
|
||||
item.changeDescription += ` [ ${key} : 由 ${oldVal || '""'} 改为 ${newVal || '""'} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除模型
|
||||
case '2': {
|
||||
item.changeDescription = `删除模型:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 新增属性
|
||||
case '3': {
|
||||
item.changeDescription = `属性名:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 修改属性
|
||||
case '4': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') {
|
||||
let newStr = item.change.new[key]
|
||||
let oldStr = item.change.old[key]
|
||||
if (key === 'choice_value') {
|
||||
newStr = newStr ? newStr.map((item) => item[0]).join(',') : ''
|
||||
oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : ''
|
||||
}
|
||||
if (Object.prototype.toString.call(newStr) === '[object Object]') {
|
||||
newStr = JSON.stringify(newStr)
|
||||
}
|
||||
if (Object.prototype.toString.call(oldStr) === '[object Object]') {
|
||||
oldStr = JSON.stringify(oldStr)
|
||||
}
|
||||
const str = `${key} : ${oldStr ? `由 ${oldStr || '""'} ` : ''} 改为 ${newStr || '""'}`
|
||||
item.changeDescription += ` [ ${str} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除属性
|
||||
case '5': {
|
||||
item.changeDescription = `删除:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 新增触发器
|
||||
case '6': {
|
||||
item.changeDescription = `属性ID:${item.change.attr_id},提前:${item.change.notify.before_days}天,主题:${item.change.notify.subject}\n内容:${item.change.notify.body}\n通知时间:${item.change.notify.notify_at}`
|
||||
break
|
||||
}
|
||||
// 修改触发器
|
||||
case '7': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old.notify) {
|
||||
const newVal = item.change.new.notify[key]
|
||||
const oldVal = item.change.old.notify[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
const str = ` [ ${key} : 由 ${oldVal} 改为 ${newVal} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除触发器
|
||||
case '8': {
|
||||
item.changeDescription = `属性ID:${item.change.attr_id},提前:${item.change.notify.before_days}天,主题:${item.change.notify.subject}\n内容:${item.change.notify.body}\n通知时间:${item.change.notify.notify_at}`
|
||||
break
|
||||
}
|
||||
// 新增联合唯一
|
||||
case '9': {
|
||||
item.changeDescription = `属性id:[${item.change.attr_ids}]`
|
||||
break
|
||||
}
|
||||
// 修改联合唯一
|
||||
case '10': {
|
||||
item.changeArr = []
|
||||
const oldVal = item.change.old.attr_ids
|
||||
const newVal = item.change.new.attr_ids
|
||||
const str = `属性id:[${oldVal}] -> [${newVal}]`
|
||||
item.changeDescription = str
|
||||
item.changeArr.push(str)
|
||||
break
|
||||
}
|
||||
// 删除联合唯一
|
||||
case '11': {
|
||||
item.changeDescription = `属性id:[${item.change.attr_ids}]`
|
||||
break
|
||||
}
|
||||
// 新增关系
|
||||
case '12': {
|
||||
item.changeDescription = `新增:${item.change.parent.alias} -> ${this.handleRelationType(
|
||||
item.change.relation_type_id
|
||||
)} -> ${item.change.child.alias}`
|
||||
break
|
||||
}
|
||||
// 删除关系
|
||||
case '13': {
|
||||
item.changeDescription = `删除:${item.change.parent_id.alias} -> ${this.handleRelationType(
|
||||
item.change.relation_type_id
|
||||
)} -> ${item.change.child.alias}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.filter {
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
.more-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<search-form
|
||||
:attrList="typeTableAttrList"
|
||||
@expandChange="handleExpandChange"
|
||||
@search="handleSearch"
|
||||
@searchFormReset="searchFormReset"
|
||||
></search-form>
|
||||
<vxe-table
|
||||
ref="xTable"
|
||||
:loading="loading"
|
||||
resizable
|
||||
:data="tableData"
|
||||
:max-height="`${windowHeight - windowHeightMinus}px`"
|
||||
row-id="_XID"
|
||||
size="small"
|
||||
:row-config="{isHover: true}"
|
||||
stripe
|
||||
class="ops-stripe-table"
|
||||
>
|
||||
<vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
|
||||
<vxe-column field="user" width="116px" title="用户"></vxe-column>
|
||||
<vxe-column field="operate_type" width="135px" title="操作">
|
||||
<template #header="{ column }">
|
||||
<span>{{ column.title }}</span>
|
||||
<a-popover trigger="click" placement="bottom">
|
||||
<a-icon class="filter" type="filter" theme="filled" />
|
||||
<a slot="content">
|
||||
<a-select
|
||||
v-model="queryParams.operate_type"
|
||||
placeholder="选择筛选操作"
|
||||
show-search
|
||||
style="width: 200px"
|
||||
:filter-option="filterOption"
|
||||
allowClear
|
||||
>
|
||||
<a-select-option
|
||||
:value="Object.values(choice)[0]"
|
||||
:key="index"
|
||||
v-for="(choice, index) in typeTableAttrList[1].choice_value"
|
||||
>
|
||||
{{ Object.keys(choice)[0] }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
|
||||
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
|
||||
</a>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<a-tag color="green" v-if="row.operate_type.includes('新增')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
<a-tag color="red" v-else>
|
||||
{{ row.operate_type }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="type_id" title="模型" width="150px">
|
||||
<template #default="{ row }">
|
||||
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id}}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="changeDescription" title="描述">
|
||||
<template #default="{ row }">
|
||||
<p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === '没有修改'">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
<template v-else-if="row.operate_type.includes('修改')">
|
||||
<p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr">
|
||||
{{ tag }}
|
||||
</p>
|
||||
</template>
|
||||
<p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes('新增')">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
<p style="color:#f5222d;" v-else-if="row.operate_type.includes('删除')">
|
||||
{{ row.changeDescription }}
|
||||
</p>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<a-row class="row" type="flex" justify="end">
|
||||
<a-col>
|
||||
<a-pagination
|
||||
size="small"
|
||||
v-model="current"
|
||||
:page-size-options="pageSizeOptions"
|
||||
:total="numfound"
|
||||
show-size-changer
|
||||
:page-size="pageSize"
|
||||
@change="onChange"
|
||||
@showSizeChange="onShowSizeChange"
|
||||
:show-total="(total) => `共 ${total} 条记录`"
|
||||
>
|
||||
</a-pagination>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import SearchForm from './searchForm'
|
||||
import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history'
|
||||
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||
import { getRelationTypes } from '@/modules/cmdb/api/relationType'
|
||||
export default {
|
||||
name: 'TypeTable',
|
||||
components: { SearchForm },
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
relationTypeList: null,
|
||||
operateTypeMap: new Map([
|
||||
['0', '新增模型'],
|
||||
['1', '修改模型'],
|
||||
['2', '删除模型'],
|
||||
['3', '新增属性'],
|
||||
['4', '修改属性'],
|
||||
['5', '删除属性'],
|
||||
['6', '新增触发器'],
|
||||
['7', '修改触发器'],
|
||||
['8', '删除触发器'],
|
||||
['9', '新增联合唯一'],
|
||||
['10', '修改联合唯一'],
|
||||
['11', '删除联合唯一'],
|
||||
['12', '新增关系'],
|
||||
['13', '删除关系'],
|
||||
]),
|
||||
typeList: null,
|
||||
userList: [],
|
||||
typeTableAttrList: [
|
||||
{
|
||||
alias: '模型',
|
||||
is_choice: true,
|
||||
name: 'type_id',
|
||||
value_type: '2',
|
||||
choice_value: [],
|
||||
},
|
||||
{
|
||||
alias: '操作',
|
||||
is_choice: true,
|
||||
name: 'operate_type',
|
||||
value_type: '2',
|
||||
choice_value: [
|
||||
{ 新增模型: 0 },
|
||||
{ 修改模型: 1 },
|
||||
{ 删除模型: 2 },
|
||||
{ 新增属性: 3 },
|
||||
{ 修改属性: 4 },
|
||||
{ 删除属性: 5 },
|
||||
{ 新增触发器: 6 },
|
||||
{ 修改触发器: 7 },
|
||||
{ 删除触发器: 8 },
|
||||
{ 新增联合唯一: 9 },
|
||||
{ 修改联合唯一: 10 },
|
||||
{ 删除联合唯一: 11 },
|
||||
{ 新增关系: 12 },
|
||||
{ 删除关系: 13 },
|
||||
],
|
||||
},
|
||||
],
|
||||
pageSizeOptions: ['50', '100', '200'],
|
||||
isExpand: false,
|
||||
current: 1,
|
||||
pageSize: 50,
|
||||
total: 0,
|
||||
numfound: 0,
|
||||
tableData: [],
|
||||
queryParams: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
type_id: undefined,
|
||||
operate_type: undefined,
|
||||
},
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await Promise.all([
|
||||
this.getRelationTypes(),
|
||||
this.getTypes(),
|
||||
this.getUserList(),
|
||||
])
|
||||
await this.getTable(this.queryParams)
|
||||
},
|
||||
updated() {
|
||||
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
|
||||
},
|
||||
computed: {
|
||||
windowHeight() {
|
||||
return this.$store.state.windowHeight
|
||||
},
|
||||
windowHeightMinus() {
|
||||
return this.isExpand ? 396 : 335
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
current(val) {
|
||||
this.queryParams.page = val
|
||||
},
|
||||
pageSize(val) {
|
||||
this.queryParams.page_size = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取表格数据
|
||||
async getTable(queryParams) {
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getCITypesTable(queryParams)
|
||||
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)
|
||||
})
|
||||
this.tableData = res.result
|
||||
this.pageSize = res.page_size
|
||||
this.current = res.page
|
||||
this.numfound = res.numfound
|
||||
this.total = res.total
|
||||
console.log(this.tableData)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
// 获取模型
|
||||
async getTypes() {
|
||||
const res = await getCITypes()
|
||||
const typesArr = []
|
||||
const typesMap = new Map()
|
||||
res.ci_types.forEach((item) => {
|
||||
const tempObj = {}
|
||||
tempObj[item.alias] = item.id
|
||||
if (item.alias) {
|
||||
typesMap.set(item.id, item.alias)
|
||||
typesArr.push(tempObj)
|
||||
}
|
||||
})
|
||||
this.typeList = typesMap
|
||||
// 设置模型options选项
|
||||
this.typeTableAttrList[0].choice_value = typesArr
|
||||
},
|
||||
// 获取用户列表
|
||||
async getUserList() {
|
||||
const res = await getUsers()
|
||||
const userListMap = new Map()
|
||||
res.forEach((item) => {
|
||||
userListMap.set(item.uid, item.nickname)
|
||||
})
|
||||
this.userList = userListMap
|
||||
},
|
||||
// 获取关系
|
||||
async getRelationTypes() {
|
||||
const res = await getRelationTypes()
|
||||
const relationTypeMap = new Map()
|
||||
res.forEach((item) => {
|
||||
relationTypeMap.set(item.id, item.name)
|
||||
})
|
||||
this.relationTypeList = relationTypeMap
|
||||
},
|
||||
onChange(current) {
|
||||
this.current = current
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
onShowSizeChange(current, size) {
|
||||
this.current = 1
|
||||
this.pageSize = size
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
handleExpandChange(expand) {
|
||||
this.isExpand = expand
|
||||
},
|
||||
// 处理查询
|
||||
handleSearch(queryParams) {
|
||||
this.current = 1
|
||||
this.queryParams = queryParams
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 重置表单
|
||||
searchFormReset() {
|
||||
this.queryParams = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
type_id: undefined,
|
||||
operate_type: undefined,
|
||||
}
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
// 转换operate_type
|
||||
handleOperateType(operate_type) {
|
||||
return this.operateTypeMap.get(operate_type)
|
||||
},
|
||||
// 转换type_id
|
||||
handleTypeId(type_id) {
|
||||
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
|
||||
},
|
||||
// 转换uid
|
||||
handleUID(uid) {
|
||||
return this.userList.get(uid)
|
||||
},
|
||||
// 转换relation_type_id
|
||||
handleRelationType(relation_type_id) {
|
||||
return this.relationTypeList.get(relation_type_id)
|
||||
},
|
||||
// 处理改变描述
|
||||
handleChangeDescription(item, operate_type) {
|
||||
switch (operate_type) {
|
||||
// 新增模型
|
||||
case '0': {
|
||||
item.changeDescription = '新增模型:' + item.change.alias
|
||||
break
|
||||
}
|
||||
// 修改模型
|
||||
case '1': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
const newVal = item.change.new[key]
|
||||
const oldVal = item.change.old[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
if (oldVal === null) {
|
||||
const str = ` [ ${key} : 改为 ${newVal || '""'} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
} else {
|
||||
const str = ` [ ${key} : 由 ${oldVal || '""'} 改为 ${newVal || '""'} ] `
|
||||
item.changeDescription += ` [ ${key} : 由 ${oldVal || '""'} 改为 ${newVal || '""'} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除模型
|
||||
case '2': {
|
||||
item.changeDescription = `删除模型:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 新增属性
|
||||
case '3': {
|
||||
item.changeDescription = `属性名:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 修改属性
|
||||
case '4': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old) {
|
||||
if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') {
|
||||
let newStr = item.change.new[key]
|
||||
let oldStr = item.change.old[key]
|
||||
if (key === 'choice_value') {
|
||||
newStr = newStr ? newStr.map((item) => item[0]).join(',') : ''
|
||||
oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : ''
|
||||
}
|
||||
if (Object.prototype.toString.call(newStr) === '[object Object]') {
|
||||
newStr = JSON.stringify(newStr)
|
||||
}
|
||||
if (Object.prototype.toString.call(oldStr) === '[object Object]') {
|
||||
oldStr = JSON.stringify(oldStr)
|
||||
}
|
||||
const str = `${key} : ${oldStr ? `由 ${oldStr || '""'} ` : ''} 改为 ${newStr || '""'}`
|
||||
item.changeDescription += ` [ ${str} ] `
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除属性
|
||||
case '5': {
|
||||
item.changeDescription = `删除:${item.change.alias}`
|
||||
break
|
||||
}
|
||||
// 新增触发器
|
||||
case '6': {
|
||||
item.changeDescription = `属性ID:${item.change.attr_id},提前:${item.change.option.before_days}天,主题:${item.change.option.subject}\n内容:${item.change.option.body}\n通知时间:${item.change.option.notify_at}`
|
||||
break
|
||||
}
|
||||
// 修改触发器
|
||||
case '7': {
|
||||
item.changeArr = []
|
||||
for (const key in item.change.old.option) {
|
||||
const newVal = item.change.new.option[key]
|
||||
const oldVal = item.change.old.option[key]
|
||||
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
|
||||
const str = ` [ ${key} : 由 ${oldVal} 改为 ${newVal} ] `
|
||||
item.changeDescription += str
|
||||
item.changeArr.push(str)
|
||||
}
|
||||
}
|
||||
if (!item.changeDescription) item.changeDescription = '没有修改'
|
||||
break
|
||||
}
|
||||
// 删除触发器
|
||||
case '8': {
|
||||
item.changeDescription = `属性ID:${item.change.attr_id},提前:${item.change.option.before_days}天,主题:${item.change.option.subject}\n内容:${item.change.option.body}\n通知时间:${item.change.option.notify_at}`
|
||||
break
|
||||
}
|
||||
// 新增联合唯一
|
||||
case '9': {
|
||||
item.changeDescription = `属性id:[${item.change.attr_ids}]`
|
||||
break
|
||||
}
|
||||
// 修改联合唯一
|
||||
case '10': {
|
||||
item.changeArr = []
|
||||
const oldVal = item.change.old.attr_ids
|
||||
const newVal = item.change.new.attr_ids
|
||||
const str = `属性id:[${oldVal}] -> [${newVal}]`
|
||||
item.changeDescription = str
|
||||
item.changeArr.push(str)
|
||||
break
|
||||
}
|
||||
// 删除联合唯一
|
||||
case '11': {
|
||||
item.changeDescription = `属性id:[${item.change.attr_ids}]`
|
||||
break
|
||||
}
|
||||
// 新增关系
|
||||
case '12': {
|
||||
item.changeDescription = `新增:${item.change.parent.alias} -> ${this.handleRelationType(
|
||||
item.change.relation_type_id
|
||||
)} -> ${item.change.child.alias}`
|
||||
break
|
||||
}
|
||||
// 删除关系
|
||||
case '13': {
|
||||
item.changeDescription = `删除:${item.change.parent_id.alias} -> ${this.handleRelationType(
|
||||
item.change.relation_type_id
|
||||
)} -> ${item.change.child.alias}`
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
filterOperate() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOperateReset() {
|
||||
this.queryParams.page = 1
|
||||
this.queryParams.page_size = 50
|
||||
this.queryParams.operate_type = undefined
|
||||
this.getTable(this.queryParams)
|
||||
},
|
||||
filterOption(input, option) {
|
||||
return option.componentOptions.children[0].text.indexOf(input) >= 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.row {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.filter {
|
||||
margin-left: 10px;
|
||||
color: #c0c4cc;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
.more-tag {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue