feat: UI更新 触发器 (#179)

* feat:新增api&适配

* feat:触发器

* add packages & 注释代码

* feat: webhook tips
This commit is contained in:
wang-liang0615 2023-09-26 18:25:04 +08:00 committed by GitHub
parent c2066b53f1
commit ab26033cea
23 changed files with 3895 additions and 2553 deletions

View File

@ -17,6 +17,8 @@
"@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-import-meta": "^7.10.4",
"@riophae/vue-treeselect": "^0.4.0", "@riophae/vue-treeselect": "^0.4.0",
"@vue/composition-api": "^1.7.1", "@vue/composition-api": "^1.7.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^1.0.0",
"ant-design-vue": "^1.6.5", "ant-design-vue": "^1.6.5",
"axios": "0.18.0", "axios": "0.18.0",
"babel-eslint": "^8.2.2", "babel-eslint": "^8.2.2",

View File

@ -117,3 +117,11 @@ export function getEmployeeListByFilter(data) {
data data
}) })
} }
export function getNoticeByEmployeeIds(data) {
return axios({
url: '/common-setting/v1/employee/get_notice_by_ids',
method: 'post',
data
})
}

View File

@ -1,207 +1,215 @@
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
/** /**
* 获取 所有的 ci_types * 获取 所有的 ci_types
* @param parameter * @param parameter
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCITypes(parameter) { export function getCITypes(parameter) {
return axios({ return axios({
url: '/v0.1/ci_types', url: '/v0.1/ci_types',
method: 'GET', method: 'GET',
params: parameter params: parameter
}) })
} }
/** /**
* 获取 某个 ci_types * 获取 某个 ci_types
* @param CITypeName * @param CITypeName
* @param parameter * @param parameter
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCIType(CITypeName, parameter) { export function getCIType(CITypeName, parameter) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeName}`, url: `/v0.1/ci_types/${CITypeName}`,
method: 'GET', method: 'GET',
params: parameter params: parameter
}) })
} }
/** /**
* 创建 ci_type * 创建 ci_type
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function createCIType(data) { export function createCIType(data) {
return axios({ return axios({
url: '/v0.1/ci_types', url: '/v0.1/ci_types',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
/** /**
* 更新 ci_type * 更新 ci_type
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function updateCIType(CITypeId, data) { export function updateCIType(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}`, url: `/v0.1/ci_types/${CITypeId}`,
method: 'PUT', method: 'PUT',
data: data data: data
}) })
} }
/** /**
* 删除 ci_type * 删除 ci_type
* @param CITypeId * @param CITypeId
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function deleteCIType(CITypeId) { export function deleteCIType(CITypeId) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}`, url: `/v0.1/ci_types/${CITypeId}`,
method: 'DELETE' method: 'DELETE'
}) })
} }
/** /**
* 获取 某个 ci_type 的分组 * 获取 某个 ci_type 的分组
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function getCITypeGroupById(CITypeId, data) { export function getCITypeGroupById(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`, url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'GET', method: 'GET',
params: data params: data
}) })
} }
/** /**
* 保存 某个 ci_type 的分组 * 保存 某个 ci_type 的分组
* @param CITypeId * @param CITypeId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function createCITypeGroupById(CITypeId, data) { export function createCITypeGroupById(CITypeId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${CITypeId}/attribute_groups`, url: `/v0.1/ci_types/${CITypeId}/attribute_groups`,
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
/** /**
* 修改 某个 ci_type 的分组 * 修改 某个 ci_type 的分组
* @param groupId * @param groupId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function updateCITypeGroupById(groupId, data) { export function updateCITypeGroupById(groupId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`, url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'PUT', method: 'PUT',
data: data data: data
}) })
} }
/** /**
* 删除 某个 ci_type 的分组 * 删除 某个 ci_type 的分组
* @param groupId * @param groupId
* @param data * @param data
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function deleteCITypeGroupById(groupId, data) { export function deleteCITypeGroupById(groupId, data) {
return axios({ return axios({
url: `/v0.1/ci_types/attribute_groups/${groupId}`, url: `/v0.1/ci_types/attribute_groups/${groupId}`,
method: 'delete', method: 'delete',
data: data data: data
}) })
} }
export function getUniqueConstraintList(type_id) { export function getUniqueConstraintList(type_id) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/unique_constraint`, url: `/v0.1/ci_types/${type_id}/unique_constraint`,
method: 'get', method: 'get',
}) })
} }
export function addUniqueConstraint(type_id, data) { export function addUniqueConstraint(type_id, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/unique_constraint`, url: `/v0.1/ci_types/${type_id}/unique_constraint`,
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function updateUniqueConstraint(type_id, id, data) { export function updateUniqueConstraint(type_id, id, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`, url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
method: 'put', method: 'put',
data: data data: data
}) })
} }
export function deleteUniqueConstraint(type_id, id) { export function deleteUniqueConstraint(type_id, id) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`, url: `/v0.1/ci_types/${type_id}/unique_constraint/${id}`,
method: 'delete', method: 'delete',
}) })
} }
export function getTriggerList(type_id) { export function getTriggerList(type_id) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/triggers`, url: `/v0.1/ci_types/${type_id}/triggers`,
method: 'get', method: 'get',
}) })
} }
export function addTrigger(type_id, data) { export function addTrigger(type_id, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/triggers`, url: `/v0.1/ci_types/${type_id}/triggers`,
method: 'post', method: 'post',
data: data data: data
}) })
} }
export function updateTrigger(type_id, id, data) { export function updateTrigger(type_id, id, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/triggers/${id}`, url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
method: 'put', method: 'put',
data: data data: data
}) })
} }
export function deleteTrigger(type_id, id) { export function deleteTrigger(type_id, id) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/triggers/${id}`, url: `/v0.1/ci_types/${type_id}/triggers/${id}`,
method: 'delete', method: 'delete',
}) })
} }
// CMDB的模型和实例的授权接口 // CMDB的模型和实例的授权接口
export function grantCiType(type_id, rid, data) { export function grantCiType(type_id, rid, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`, url: `/v0.1/ci_types/${type_id}/roles/${rid}/grant`,
method: 'post', method: 'post',
data data
}) })
} }
// CMDB的模型和实例的删除授权接口 // CMDB的模型和实例的删除授权接口
export function revokeCiType(type_id, rid, data) { export function revokeCiType(type_id, rid, data) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`, url: `/v0.1/ci_types/${type_id}/roles/${rid}/revoke`,
method: 'post', method: 'post',
data data
}) })
} }
// CMDB的模型和实例的过滤的权限 // CMDB的模型和实例的过滤的权限
export function ciTypeFilterPermissions(type_id) { export function ciTypeFilterPermissions(type_id) {
return axios({ return axios({
url: `/v0.1/ci_types/${type_id}/filters/permissions`, url: `/v0.1/ci_types/${type_id}/filters/permissions`,
method: 'get', method: 'get',
}) })
} }
export function getAllDagsName(params) {
return axios({
url: '/v1/dag/all_names',
method: 'GET',
params: params
})
}

View File

@ -1,40 +1,56 @@
import { axios } from '@/utils/request' import { axios } from '@/utils/request'
export function getCIHistory (ciId) { export function getCIHistory(ciId) {
return axios({ return axios({
url: `/v0.1/history/ci/${ciId}`, url: `/v0.1/history/ci/${ciId}`,
method: 'GET' method: 'GET'
}) })
} }
export function getCIHistoryTable (params) { export function getCIHistoryTable(params) {
return axios({ return axios({
url: `/v0.1/history/records/attribute`, url: `/v0.1/history/records/attribute`,
method: 'GET', method: 'GET',
params: params params: params
}) })
} }
export function getRelationTable (params) { export function getRelationTable(params) {
return axios({ return axios({
url: `/v0.1/history/records/relation`, url: `/v0.1/history/records/relation`,
method: 'GET', method: 'GET',
params: params params: params
}) })
} }
export function getCITypesTable (params) { export function getCITypesTable(params) {
return axios({ return axios({
url: `/v0.1/history/ci_types`, url: `/v0.1/history/ci_types`,
method: 'GET', method: 'GET',
params: params params: params
}) })
} }
export function getUsers (params) { export function getUsers(params) {
return axios({ return axios({
url: `/v1/acl/users/employee`, url: `/v1/acl/users/employee`,
method: 'GET', method: 'GET',
params: params 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
})
}

View File

@ -0,0 +1,2 @@
import NoticeContent from './index.vue'
export default NoticeContent

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,2 @@
import Webhook from './index.vue'
export default Webhook

View File

@ -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>

View File

@ -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>

View File

@ -1,319 +1,327 @@
<template> <template>
<CustomDrawer <CustomDrawer
width="80%" width="80%"
placement="left" placement="left"
@close=" @close="
() => { () => {
visible = false visible = false
} }
" "
:visible="visible" :visible="visible"
:hasTitle="false" :hasTitle="false"
:hasFooter="false" :hasFooter="false"
:bodyStyle="{ padding: 0, height: '100vh' }" :bodyStyle="{ padding: 0, height: '100vh' }"
wrapClassName="ci-detail" wrapClassName="ci-detail"
destroyOnClose destroyOnClose
> >
<a-tabs v-model="activeTabKey" @change="changeTab"> <a-tabs v-model="activeTabKey" @change="changeTab">
<a-tab-pane key="tab_1"> <a-tab-pane key="tab_1">
<span slot="tab"><a-icon type="book" />属性</span> <span slot="tab"><a-icon type="book" />属性</span>
<div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr"> <div :style="{ maxHeight: `${windowHeight - 44}px`, overflow: 'auto', padding: '24px' }" class="ci-detail-attr">
<el-descriptions <el-descriptions
:title="group.name || '其他'" :title="group.name || '其他'"
:key="group.name" :key="group.name"
v-for="group in attributeGroups" v-for="group in attributeGroups"
border border
:column="3" :column="3"
> >
<el-descriptions-item <el-descriptions-item
:label="`${attr.alias || attr.name}`" :label="`${attr.alias || attr.name}`"
:key="attr.name" :key="attr.name"
v-for="attr in group.attributes" v-for="attr in group.attributes"
> >
<CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" /> <CiDetailAttrContent :ci="ci" :attr="attr" @refresh="refresh" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_2"> <a-tab-pane key="tab_2">
<span slot="tab"><a-icon type="branches" />关系</span> <span slot="tab"><a-icon type="branches" />关系</span>
<div :style="{ padding: '24px' }"> <div :style="{ padding: '24px' }">
<CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" /> <CiDetailRelation ref="ciDetailRelation" :ciId="ciId" :typeId="typeId" :ci="ci" />
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="tab_3"> <a-tab-pane key="tab_3">
<span slot="tab"><a-icon type="clock-circle" />操作历史</span> <span slot="tab"><a-icon type="clock-circle" />操作历史</span>
<div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }"> <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:data="ciHistory" :data="ciHistory"
size="small" size="small"
:max-height="`${windowHeight - 94}px`" :max-height="`${windowHeight - 94}px`"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
border border
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-table-column sortable field="created_at" title="时间"></vxe-table-column> <vxe-table-column sortable field="created_at" title="时间"></vxe-table-column>
<vxe-table-column <vxe-table-column
field="username" field="username"
title="用户" title="用户"
:filters="[]" :filters="[]"
:filter-method="filterUsernameMethod" :filter-method="filterUsernameMethod"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column <vxe-table-column
field="operate_type" field="operate_type"
:filters="[ :filters="[
{ value: 0, label: '新增' }, { value: 0, label: '新增' },
{ value: 1, label: '删除' }, { value: 1, label: '删除' },
{ value: 3, label: '修改' }, { value: 3, label: '修改' },
]" ]"
:filter-method="filterOperateMethod" :filter-method="filterOperateMethod"
title="操作" title="操作"
> >
<template #default="{ row }"> <template #default="{ row }">
{{ operateTypeMap[row.operate_type] }} {{ operateTypeMap[row.operate_type] }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column <vxe-table-column
field="attr_alias" field="attr_alias"
title="属性" title="属性"
:filters="[]" :filters="[]"
:filter-method="filterAttrMethod" :filter-method="filterAttrMethod"
></vxe-table-column> ></vxe-table-column>
<vxe-table-column field="old" title=""></vxe-table-column> <vxe-table-column field="old" title=""></vxe-table-column>
<vxe-table-column field="new" title=""></vxe-table-column> <vxe-table-column field="new" title=""></vxe-table-column>
</vxe-table> </vxe-table>
</div> </div>
</a-tab-pane> </a-tab-pane>
</a-tabs> <a-tab-pane key="tab_4">
</CustomDrawer> <span slot="tab"><ops-icon type="itsm_auto_trigger" />触发历史</span>
</template> <div :style="{ padding: '24px', height: 'calc(100vh - 44px)' }">
<TriggerTable :ci_id="ci._id" />
<script> </div>
import { Descriptions, DescriptionsItem } from 'element-ui' </a-tab-pane>
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' </a-tabs>
import { getCIHistory } from '@/modules/cmdb/api/history' </CustomDrawer>
import { getCIById } from '@/modules/cmdb/api/ci' </template>
import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' <script>
import { Descriptions, DescriptionsItem } from 'element-ui'
export default { import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
components: { import { getCIHistory } from '@/modules/cmdb/api/history'
ElDescriptions: Descriptions, import { getCIById } from '@/modules/cmdb/api/ci'
ElDescriptionsItem: DescriptionsItem, import CiDetailAttrContent from './ciDetailAttrContent.vue'
CiDetailAttrContent, import CiDetailRelation from './ciDetailRelation.vue'
CiDetailRelation, import TriggerTable from '../../operation_history/modules/triggerTable.vue'
},
props: { export default {
typeId: { components: {
type: Number, ElDescriptions: Descriptions,
required: true, ElDescriptionsItem: DescriptionsItem,
}, CiDetailAttrContent,
treeViewsLevels: { CiDetailRelation,
type: Array, TriggerTable,
default: () => [], },
}, props: {
}, typeId: {
data() { type: Number,
const operateTypeMap = { required: true,
0: '新增', },
1: '删除', treeViewsLevels: {
2: '修改', type: Array,
} default: () => [],
return { },
operateTypeMap, },
visible: false, data() {
ci: {}, const operateTypeMap = {
attributeGroups: [], 0: '新增',
activeTabKey: 'tab_1', 1: '删除',
rowSpanMap: {}, 2: '修改',
ciHistory: [], }
ciId: null, return {
ci_types: [], operateTypeMap,
} visible: false,
}, ci: {},
computed: { attributeGroups: [],
windowHeight() { activeTabKey: 'tab_1',
return this.$store.state.windowHeight rowSpanMap: {},
}, ciHistory: [],
}, ciId: null,
provide() { ci_types: [],
return { }
ci_types: () => { },
return this.ci_types computed: {
}, windowHeight() {
} return this.$store.state.windowHeight
}, },
inject: ['reload', 'handleSearch', 'attrList'], },
methods: { provide() {
create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') { return {
this.visible = true ci_types: () => {
this.activeTabKey = activeTabKey return this.ci_types
if (activeTabKey === 'tab_2') { },
this.$nextTick(() => { }
this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey },
}) inject: ['reload', 'handleSearch', 'attrList'],
} methods: {
this.ciId = ciId create(ciId, activeTabKey = 'tab_1', ciDetailRelationKey = '1') {
this.getAttributes() this.visible = true
this.getCI() this.activeTabKey = activeTabKey
this.getCIHistory() if (activeTabKey === 'tab_2') {
getCITypes().then((res) => { this.$nextTick(() => {
this.ci_types = res.ci_types this.$refs.ciDetailRelation.activeKey = ciDetailRelationKey
}) })
}, }
getAttributes() { this.ciId = ciId
getCITypeGroupById(this.typeId, { need_other: 1 }) this.getAttributes()
.then((res) => { this.getCI()
this.attributeGroups = res this.getCIHistory()
}) getCITypes().then((res) => {
.catch((e) => {}) this.ci_types = res.ci_types
}, })
getCI() { },
getCIById(this.ciId) getAttributes() {
.then((res) => { getCITypeGroupById(this.typeId, { need_other: 1 })
// this.ci = res.ci .then((res) => {
this.ci = res.result[0] this.attributeGroups = res
}) })
.catch((e) => {}) .catch((e) => {})
}, },
getCI() {
getCIHistory() { getCIById(this.ciId)
getCIHistory(this.ciId) .then((res) => {
.then((res) => { // this.ci = res.ci
this.ciHistory = res this.ci = res.result[0]
})
const rowSpanMap = {} .catch((e) => {})
let startIndex = 0 },
let startCount = 1
res.forEach((item, index) => { getCIHistory() {
if (index === 0) { getCIHistory(this.ciId)
return .then((res) => {
} this.ciHistory = res
if (res[index].record_id === res[startIndex].record_id) {
startCount += 1 const rowSpanMap = {}
rowSpanMap[index] = 0 let startIndex = 0
if (index === res.length - 1) { let startCount = 1
rowSpanMap[startIndex] = startCount res.forEach((item, index) => {
} if (index === 0) {
} else { return
rowSpanMap[startIndex] = startCount }
startIndex = index if (res[index].record_id === res[startIndex].record_id) {
startCount = 1 startCount += 1
if (index === res.length - 1) { rowSpanMap[index] = 0
rowSpanMap[index] = 1 if (index === res.length - 1) {
} rowSpanMap[startIndex] = startCount
} }
}) } else {
this.rowSpanMap = rowSpanMap rowSpanMap[startIndex] = startCount
}) startIndex = index
.catch((e) => { startCount = 1
console.log(e) if (index === res.length - 1) {
}) rowSpanMap[index] = 1
}, }
changeTab(key) { }
this.activeTabKey = key })
if (key === 'tab_3') { this.rowSpanMap = rowSpanMap
this.$nextTick(() => { })
const $table = this.$refs.xTable .catch((e) => {
if ($table) { console.log(e)
const usernameColumn = $table.getColumnByField('username') })
const attrColumn = $table.getColumnByField('attr_alias') },
if (usernameColumn) { changeTab(key) {
const usernameList = [...new Set(this.ciHistory.map((item) => item.username))] this.activeTabKey = key
$table.setFilter( if (key === 'tab_3') {
usernameColumn, this.$nextTick(() => {
usernameList.map((item) => { const $table = this.$refs.xTable
return { if ($table) {
value: item, const usernameColumn = $table.getColumnByField('username')
label: item, const attrColumn = $table.getColumnByField('attr_alias')
} if (usernameColumn) {
}) const usernameList = [...new Set(this.ciHistory.map((item) => item.username))]
) $table.setFilter(
} usernameColumn,
if (attrColumn) { usernameList.map((item) => {
$table.setFilter( return {
attrColumn, value: item,
this.attrList().map((attr) => { label: item,
return { value: attr.alias || attr.name, label: attr.alias || attr.name } }
}) })
) )
} }
} if (attrColumn) {
}) $table.setFilter(
} attrColumn,
}, this.attrList().map((attr) => {
filterUsernameMethod({ value, row, column }) { return { value: attr.alias || attr.name, label: attr.alias || attr.name }
return row.username === value })
}, )
filterOperateMethod({ value, row, column }) { }
return Number(row.operate_type) === Number(value) }
}, })
filterAttrMethod({ value, row, column }) { }
return row.attr_alias === value },
}, filterUsernameMethod({ value, row, column }) {
refresh(editAttrName) { return row.username === value
this.getCI() },
const _find = this.treeViewsLevels.find((level) => level.name === editAttrName) filterOperateMethod({ value, row, column }) {
// 修改的字段为树形视图订阅的字段 则全部reload return Number(row.operate_type) === Number(value)
setTimeout(() => { },
if (_find) { filterAttrMethod({ value, row, column }) {
this.reload() return row.attr_alias === value
} else { },
this.handleSearch() refresh(editAttrName) {
} this.getCI()
}, 500) const _find = this.treeViewsLevels.find((level) => level.name === editAttrName)
}, // 修改的字段为树形视图订阅的字段 则全部reload
mergeRowMethod({ row, _rowIndex, column, visibleData }) { setTimeout(() => {
const fields = ['created_at', 'username'] if (_find) {
const cellValue = row[column.property] this.reload()
if (cellValue && fields.includes(column.property)) { } else {
const prevRow = visibleData[_rowIndex - 1] this.handleSearch()
let nextRow = visibleData[_rowIndex + 1] }
if (prevRow && prevRow[column.property] === cellValue) { }, 500)
return { rowspan: 0, colspan: 0 } },
} else { mergeRowMethod({ row, _rowIndex, column, visibleData }) {
let countRowspan = 1 const fields = ['created_at', 'username']
while (nextRow && nextRow[column.property] === cellValue) { const cellValue = row[column.property]
nextRow = visibleData[++countRowspan + _rowIndex] if (cellValue && fields.includes(column.property)) {
} const prevRow = visibleData[_rowIndex - 1]
if (countRowspan > 1) { let nextRow = visibleData[_rowIndex + 1]
return { rowspan: countRowspan, colspan: 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]
} }
</script> if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 }
<style lang="less" scoped></style> }
<style lang="less"> }
.ci-detail { }
.ant-tabs-bar { },
margin: 0; },
} }
.ci-detail-attr { </script>
.el-descriptions-item__content {
cursor: default; <style lang="less" scoped></style>
&:hover a { <style lang="less">
opacity: 1 !important; .ci-detail {
} .ant-tabs-bar {
} margin: 0;
.el-descriptions:first-child > .el-descriptions__header { }
margin-top: 0; .ci-detail-attr {
} .el-descriptions-item__content {
.el-descriptions__header { cursor: default;
margin-bottom: 5px; &:hover a {
margin-top: 20px; opacity: 1 !important;
} }
.ant-form-item { }
margin-bottom: 0; .el-descriptions:first-child > .el-descriptions__header {
} margin-top: 0;
.ant-form-item-control { }
line-height: 19px; .el-descriptions__header {
} margin-bottom: 5px;
} margin-top: 20px;
} }
</style> .ant-form-item {
margin-bottom: 0;
}
.ant-form-item-control {
line-height: 19px;
}
}
}
</style>

View File

@ -1,197 +1,577 @@
<template> <template>
<a-modal :title="title" :visible="visible" @cancel="handleCancel" @ok="handleOk"> <CustomDrawer
<a-space slot="footer"> wrapClassName="trigger-form"
<a-button type="primary" ghost @click="handleCancel">取消</a-button> :width="700"
<a-button v-if="triggerId" type="danger" @click="handleDetele">删除</a-button> :title="title"
<a-button @click="handleOk" type="primary">确定</a-button> :visible="visible"
</a-space> @close="handleCancel"
<a-form-model ref="triggerForm" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> @ok="handleOk"
<a-form-model-item label="属性" prop="attr_id" :hidden="!isCreateFromTriggerTable || triggerId"> >
<a-select v-model="form.attr_id"> <div class="custom-drawer-bottom-action">
<a-select-option v-for="attr in canAddTriggerAttr" :key="attr.id" :value="attr.id">{{ <a-button type="primary" ghost @click="handleCancel">取消</a-button>
attr.alias || attr.name <a-button v-if="triggerId" type="danger" @click="handleDetele">删除</a-button>
}}</a-select-option> <a-button @click="handleOk" type="primary">确定</a-button>
</a-select> </div>
</a-form-model-item> <a-form-model ref="triggerForm" :model="form" :rules="rules" :label-col="{ span: 3 }" :wrapper-col="{ span: 18 }">
<a-form-model-item label="主题" prop="subject"> <p><strong>基本信息</strong></p>
<a-input v-model="form.subject" /> <a-form-model-item label="名称" prop="name">
</a-form-model-item> <a-input v-model="form.name" placeholder="请输入名称" />
<a-form-model-item label="内容" prop="body"> </a-form-model-item>
<a-textarea v-model="form.body" :rows="3" /> <a-form-model-item label="类型">
</a-form-model-item> <a-radio-group v-model="category">
<a-form-model-item label="微信通知" prop="wx_to"> <a-radio-button :value="1">
<a-select 数据变更
mode="tags" </a-radio-button>
v-model="form.wx_to" <a-radio-button :value="2">
placeholder="选择微信通知人" 日期属性
showSearch </a-radio-button>
:filter-option="false" </a-radio-group>
@search="filterChange" </a-form-model-item>
> <a-form-model-item label="备注" prop="description">
<a-select-option v-for="item in filterWxUsers" :value="item['wx_id']" :key="item.id"> <a-input v-model="form.description" placeholder="请输入备注" />
<span>{{ item['nickname'] }}</span> </a-form-model-item>
<a-divider type="vertical" /> <a-form-model-item label="开启" prop="enable">
<span>{{ item['wx_id'].length > 12 ? item['wx_id'].slice(0, 10) + '...' : item['wx_id'] }}</span> <a-switch v-model="form.enable" />
</a-select-option> </a-form-model-item>
</a-select> <template v-if="category === 1">
</a-form-model-item> <p><strong>触发条件</strong></p>
<a-form-model-item label="邮箱通知" prop="mail_to"> <a-form-model-item label="事件" prop="action">
<a-textarea v-model="form.mail_to" :rows="3" placeholder="多个邮箱用逗号分隔" /> <a-radio-group v-model="form.action">
</a-form-model-item> <a-radio value="0">
<a-form-model-item label="提前" prop="before_days"> 新增实例
<a-input-number v-model="form.before_days" :min="0" /> </a-radio>
<a-radio value="1">
</a-form-model-item> 删除实例
<a-form-model-item label="发送时间" prop="notify_at"> </a-radio>
<a-time-picker v-model="form.notify_at" format="HH:mm" valueFormat="HH:mm" /> <a-radio value="2">
</a-form-model-item> 实例变更
</a-form-model> </a-radio>
</a-modal> </a-radio-group>
</template> </a-form-model-item>
<a-form-model-item v-if="form.action === '2'" label="属性" prop="attr_ids">
<script> <a-select v-model="form.attr_ids" show-search mode="multiple" placeholder="请选择属性(多选)">
import { getWX } from '../../api/perm' <a-select-option v-for="attr in attrList" :key="attr.id" :value="attr.id">{{
import { addTrigger, updateTrigger, deleteTrigger } from '../../api/CIType' attr.alias || attr.name
export default { }}</a-select-option>
name: 'TriggerForm', </a-select>
props: { </a-form-model-item>
CITypeId: { <a-form-model-item label="筛选" class="trigger-form-filter">
type: Number, <FilterComp
default: null, ref="filterComp"
}, :isDropdown="false"
}, :canSearchPreferenceAttrList="attrList"
data() { @setExpFromFilter="setExpFromFilter"
return { :expression="filterExp ? `q=${filterExp}` : ''"
visible: false, />
form: { attr_id: '', subject: '', body: '', wx_to: [], mail_to: '', before_days: 0, notify_at: '08:00' }, </a-form-model-item>
rules: { </template>
attr_id: [{ required: true, message: '请选择属性' }], </a-form-model>
subject: [{ required: true, message: '请填写主题' }], <template v-if="category === 2">
body: [{ required: true, message: '请填写内容' }], <p><strong>触发条件</strong></p>
}, <a-form-model
WxUsers: [], ref="dateForm"
filterValue: '', :model="dateForm"
triggerId: null, :rules="dateFormRules"
attr_id: null, :label-col="{ span: 3 }"
canAddTriggerAttr: [], :wrapper-col="{ span: 18 }"
isCreateFromTriggerTable: false, >
title: '新增触发器', <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">{{
computed: { attr.alias || attr.name
filterWxUsers() { }}</a-select-option>
if (!this.filterValue) { </a-select>
return this.WxUsers </a-form-model-item>
} <a-form-model-item label="筛选" class="trigger-form-filter">
return this.WxUsers.filter( <FilterComp
(user) => ref="filterComp"
user.nickname.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0 || :isDropdown="false"
user.username.toLowerCase().indexOf(this.filterValue.toLowerCase()) >= 0 :canSearchPreferenceAttrList="attrList"
) @setExpFromFilter="setExpFromFilter"
}, :expression="filterExp ? `q=${filterExp}` : ''"
}, />
inject: { </a-form-model-item>
refresh: { <a-form-model-item label="提前" prop="before_days">
from: 'refresh', <a-input-number v-model="dateForm.before_days" :min="0" />
default: null,
}, </a-form-model-item>
}, <a-form-model-item label="发送时间" prop="notify_at">
methods: { <a-time-picker v-model="dateForm.notify_at" format="HH:mm" valueFormat="HH:mm" />
createFromTriggerTable(canAddTriggerAttr) { </a-form-model-item>
this.visible = true </a-form-model>
this.getWxList() </template>
this.canAddTriggerAttr = canAddTriggerAttr <p><strong>触发动作</strong></p>
this.triggerId = null <a-radio-group
this.isCreateFromTriggerTable = true v-model="triggerAction"
this.title = '新增触发器' :style="{ width: '100%', display: 'flex', justifyContent: 'space-around', marginBottom: '10px' }"
this.form = { >
attr_id: '', <a-radio value="1">
subject: '', 通知
body: '', </a-radio>
wx_to: [], <a-radio value="2">
mail_to: '', Webhook
before_days: 0, </a-radio>
notify_at: '08:00', <!-- <a-radio value="3">
} DAG
}, </a-radio> -->
open(property) { </a-radio-group>
this.visible = true <a-form-model
this.getWxList() ref="notifiesForm"
if (property.has_trigger) { :model="notifies"
this.triggerId = property.trigger.id :rules="notifiesRules"
this.title = `编辑触发器 ${property.alias || property.name}` :label-col="{ span: 3 }"
this.form = { :wrapper-col="{ span: 18 }"
...property.trigger.notify, v-if="triggerAction === '1'"
attr_id: property.id, >
mail_to: property.trigger.notify.mail_to ? property.trigger.notify.mail_to.join(',') : '', <a-form-model-item label=" " :colon="false">
} <span class="trigger-tips">{{ tips }}</span>
} else { </a-form-model-item>
this.title = `新增触发器 ${property.alias || property.name}` <a-form-model-item label="收件人" prop="employee_ids" class="trigger-form-employee">
this.triggerId = null <EmployeeTreeSelect multiple v-model="notifies.employee_ids" />
this.form = { <div class="trigger-form-custom-email">
attr_id: property.id, <a-textarea
subject: '', v-if="showCustomEmail"
body: '', v-model="notifies.custom_email"
wx_to: [], placeholder="请输入邮箱,多个邮箱用;分隔"
mail_to: '', :rows="1"
before_days: 0, />
notify_at: '08:00', <a-button
} @click="
} () => {
}, showCustomEmail = !showCustomEmail
handleCancel() { }
this.$refs.triggerForm.clearValidate() "
this.$refs.triggerForm.resetFields() type="primary"
this.filterValue = '' size="small"
this.visible = false class="ops-button-primary"
}, >{{ `${showCustomEmail ? '删除' : '添加'}自定义收件人` }}</a-button
getWxList() { >
getWX().then((res) => { </div>
this.WxUsers = res.filter((item) => item.wx_id) </a-form-model-item>
}) <a-form-model-item label="通知标题" prop="subject">
}, <a-input v-model="notifies.subject" placeholder="请输入通知标题" />
filterChange(value) { </a-form-model-item>
this.filterValue = value <a-form-model-item label="内容" prop="body" :wrapper-col="{ span: 21 }">
}, <NoticeContent :needOld="category === 1 && form.action === '2'" :attrList="attrList" ref="noticeContent" />
handleOk() { </a-form-model-item>
this.$refs.triggerForm.validate(async (valid) => { <a-form-model-item label="通知方式" prop="method">
if (valid) { <a-checkbox-group v-model="notifies.method">
const { mail_to, attr_id } = this.form <a-checkbox value="wechatApp">
const params = { 微信
attr_id, </a-checkbox>
notify: { ...this.form, mail_to: mail_to ? mail_to.split(',') : undefined }, <a-checkbox value="email">
} 邮件
delete params.notify.attr_id </a-checkbox>
if (this.triggerId) { </a-checkbox-group>
await updateTrigger(this.CITypeId, this.triggerId, params) </a-form-model-item>
} else { </a-form-model>
await addTrigger(this.CITypeId, params) <div class="auto-complete-wrapper" v-if="triggerAction === '3'">
} <a-input
this.handleCancel() id="auto-complete-wrapper-input"
if (this.refresh) { ref="input"
this.refresh() v-model="searchValue"
} @focus="focusOnInput"
} @blur="handleBlurInput"
}) allowClear
}, >
handleDetele() { </a-input>
const that = this <div id="auto-complete-wrapper-popover" class="auto-complete-wrapper-popover" v-if="isShow">
this.$confirm({ <div
title: '警告', class="auto-complete-wrapper-popover-item"
content: '确认删除该触发器吗?', @click="handleClickSelect(item)"
onOk() { v-for="item in filterList"
deleteTrigger(that.CITypeId, that.triggerId).then(() => { :key="item.id"
that.$message.success('删除成功!') :title="item.label"
that.handleCancel() >
if (that.refresh) { {{ item.label }}
that.refresh() </div>
} </div>
}) </div>
}, <span v-if="triggerAction === '2'" class="trigger-tips">{{ webhookTips }}</span>
}) <Webhook ref="webhook" style="margin-top:10px" v-if="triggerAction === '2'" />
}, </CustomDrawer>
}, </template>
}
</script> <script>
import _ from 'lodash'
<style></style> 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 }}',
webhookTips: '请求参数可以引用该模型的属性值,引用方法为: {{ 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>

View File

@ -1,144 +1,172 @@
<template> <template>
<div class="ci-types-triggers"> <div class="ci-types-triggers">
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">
<a-button <a-button
type="primary" type="primary"
@click="handleAddTrigger" @click="handleAddTrigger"
size="small" size="small"
class="ops-button-primary" class="ops-button-primary"
icon="plus" icon="plus"
>新增触发器</a-button >新增触发器</a-button
> >
<span class="trigger-tips">{{ tips }}</span> </div>
</div> <vxe-table
<vxe-table stripe
stripe :data="tableData"
:data="tableData" size="small"
size="small" show-overflow
show-overflow highlight-hover-row
highlight-hover-row keep-source
keep-source :max-height="windowHeight - 180"
:max-height="windowHeight - 180" class="ops-stripe-table"
class="ops-stripe-table" >
> <vxe-column field="option.name" title="名称"></vxe-column>
<vxe-column field="attr_name" title="属性名"></vxe-column> <vxe-column field="option.description" title="备注"></vxe-column>
<vxe-column field="notify.subject" title="主题"></vxe-column> <vxe-column field="type" title="类型">
<vxe-column field="notify.body" title="内容"></vxe-column> <template #default="{ row }">
<vxe-column field="notify.wx_to" title="微信通知"> <span v-if="row.attr_id">日期属性</span>
<template #default="{ row }"> <span v-else>数据变更</span>
<span v-for="(person, index) in row.notify.wx_to" :key="person + index">[{{ person }}]</span> </template>
</template> </vxe-column>
</vxe-column> <vxe-column field="option.enable" title="开启">
<vxe-column field="notify.mail_to" title="邮件通知"> <template #default="{ row }">
<template #default="{ row }"> <a-switch :checked="row.option.enable" @click="changeEnable(row)"></a-switch>
<span v-for="(email, index) in row.notify.mail_to" :key="email + index">[{{ email }}]</span> </template>
</template> </vxe-column>
</vxe-column>
<vxe-column field="notify.before_days" title="提前"> <!-- <vxe-column field="attr_name" title="属性名"></vxe-column>
<template #default="{ row }"> <vxe-column field="option.subject" title="主题"></vxe-column>
<span v-if="row.notify.before_days">{{ row.notify.before_days }}</span> <vxe-column field="option.body" title="内容"></vxe-column>
</template> <vxe-column field="option.wx_to" title="微信通知">
</vxe-column> <template #default="{ row }">
<vxe-column field="notify.notify_at" title="发送时间"></vxe-column> <span v-for="(person, index) in row.option.wx_to" :key="person + index">[{{ person }}]</span>
<vxe-column field="operation" title="操作" width="200px" align="center"> </template>
<template #default="{ row }"> </vxe-column>
<a-space> <vxe-column field="option.mail_to" title="邮件通知">
<a @click="handleEdit(row)"><a-icon type="edit"/></a> <template #default="{ row }">
<a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a> <span v-for="(email, index) in row.option.mail_to" :key="email + index">[{{ email }}]</span>
</a-space> </template>
</template> </vxe-column>
</vxe-column> <vxe-column field="option.before_days" title="提前">
</vxe-table> <template #default="{ row }">
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> <span v-if="row.option.before_days">{{ row.option.before_days }}</span>
</div> </template>
</template> </vxe-column>
<vxe-column field="option.notify_at" title="发送时间"></vxe-column> -->
<script> <vxe-column field="operation" title="操作" width="80px" align="center">
import { getTriggerList, deleteTrigger } from '../../api/CIType' <template #default="{ row }">
import { getCITypeAttributesById } from '../../api/CITypeAttr' <a-space>
import TriggerForm from './triggerForm.vue' <a @click="handleEdit(row)"><a-icon type="edit"/></a>
export default { <a style="color:red;" @click="handleDetele(row.id)"><a-icon type="delete"/></a>
name: 'TriggerTable', </a-space>
components: { TriggerForm }, </template>
props: { </vxe-column>
CITypeId: { </vxe-table>
type: Number, <TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
default: null, </div>
}, </template>
},
data() { <script>
return { import _ from 'lodash'
tips: '主题、内容、微信通知和邮件通知都可以引用该模型的属性值,引用方法为: {{ attr_name }}', import { getTriggerList, deleteTrigger, updateTrigger } from '../../api/CIType'
tableData: [], import { getCITypeAttributesById } from '../../api/CITypeAttr'
attrList: [], import TriggerForm from './triggerForm.vue'
} import { getAllDepAndEmployee } from '@/api/company'
},
computed: { export default {
windowHeight() { name: 'TriggerTable',
return this.$store.state.windowHeight components: { TriggerForm },
}, props: {
canAddTriggerAttr() { CITypeId: {
return this.attrList.filter((attr) => attr.value_type === '3' || attr.value_type === '4') type: Number,
}, default: null,
}, },
provide() { },
return { refresh: this.getTableData } data() {
}, return {
mounted() {}, tableData: [],
methods: { attrList: [],
async getTableData() { allTreeDepAndEmp: [],
const [triggerList, attrList] = await Promise.all([ }
getTriggerList(this.CITypeId), },
getCITypeAttributesById(this.CITypeId), computed: {
]) windowHeight() {
triggerList.forEach((trigger) => { return this.$store.state.windowHeight
const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id) },
if (_find) { },
trigger.attr_name = _find.alias || _find.name provide() {
} return {
}) refresh: this.getTableData,
this.tableData = triggerList provide_allTreeDepAndEmp: () => {
this.attrList = attrList.attributes return this.allTreeDepAndEmp
}, },
handleAddTrigger() { }
this.$refs.triggerForm.createFromTriggerTable(this.canAddTriggerAttr) },
}, mounted() {
handleDetele(id) { this.getAllDepAndEmployee()
const that = this },
this.$confirm({ methods: {
title: '警告', getAllDepAndEmployee() {
content: '确认删除该触发器吗?', getAllDepAndEmployee({ block: 0 }).then((res) => {
onOk() { this.allTreeDepAndEmp = res
deleteTrigger(that.CITypeId, id).then(() => { })
that.$message.success('删除成功!') },
that.getTableData() async getTableData() {
}) const [triggerList, attrList] = await Promise.all([
}, getTriggerList(this.CITypeId),
}) getCITypeAttributesById(this.CITypeId),
}, ])
handleEdit(row) { triggerList.forEach((trigger) => {
const _find = this.attrList.find((attr) => attr.id === row.attr_id) const _find = attrList.attributes.find((attr) => attr.id === trigger.attr_id)
this.$refs.triggerForm.open({ if (_find) {
id: row.attr_id, trigger.attr_name = _find.alias || _find.name
alias: _find ? _find.alias || _find.name : '', }
trigger: { id: row.id, notify: row.notify }, })
has_trigger: true, this.tableData = triggerList
}) this.attrList = attrList.attributes
}, },
}, handleAddTrigger() {
} this.$refs.triggerForm.createFromTriggerTable(this.attrList)
</script> },
handleDetele(id) {
<style lang="less" scoped> const that = this
.ci-types-triggers { this.$confirm({
padding: 16px 24px 24px; title: '警告',
.trigger-tips { content: '确认删除该触发器吗?',
border: 1px solid #d4380d; onOk() {
background-color: #fff2e8; deleteTrigger(that.CITypeId, id).then(() => {
padding: 2px 10px; that.$message.success('删除成功!')
border-radius: 4px; that.getTableData()
color: #d4380d; })
float: right; },
} })
} },
</style> 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>

View File

@ -1,38 +1,43 @@
<template> <template>
<div> <div>
<a-card :bordered="false"> <a-card :bordered="false">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab="CI变更"> <a-tab-pane key="1" tab="CI变更">
<ci-table></ci-table> <ci-table></ci-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="关系变更"> <a-tab-pane key="2" tab="关系变更">
<relation-table></relation-table> <relation-table></relation-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab="模型变更"> <a-tab-pane key="3" tab="模型变更">
<type-table></type-table> <type-table></type-table>
</a-tab-pane> </a-tab-pane>
</a-tabs> <a-tab-pane key="4" tab="触发历史">
</a-card> <TriggerTable></TriggerTable>
</div> </a-tab-pane>
</template> </a-tabs>
</a-card>
<script> </div>
import CiTable from './modules/ciTable.vue' </template>
import RelationTable from './modules/relation.vue'
import TypeTable from './modules/typeTable.vue' <script>
export default { import CiTable from './modules/ciTable.vue'
name: 'Index', import RelationTable from './modules/relation.vue'
data() { import TypeTable from './modules/typeTable.vue'
return { import TriggerTable from './modules/triggerTable.vue'
userList: [] export default {
} name: 'OperationHistory',
}, components: {
components: { CiTable,
CiTable, RelationTable,
RelationTable, TypeTable,
TypeTable TriggerTable,
} },
} data() {
</script> return {
userList: [],
<style></style> }
},
}
</script>
<style></style>

View File

@ -1,420 +1,421 @@
<template> <template>
<div> <div>
<search-form <search-form
ref="child" ref="child"
:attrList="ciTableAttrList" :attrList="ciTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@searchFormChange="searchFormChange" @searchFormChange="searchFormChange"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
row-id="_XID" row-id="_XID"
:loading="loading" :loading="loading"
border border
size="small" size="small"
show-overflow="tooltip" show-overflow="tooltip"
show-header-overflow="tooltip" show-header-overflow="tooltip"
resizable resizable
:data="tableData" :data="tableData"
:max-height="`${windowHeight - windowHeightMinus}px`" :max-height="`${windowHeight - windowHeightMinus}px`"
:span-method="mergeRowMethod" :span-method="mergeRowMethod"
:scroll-y="{enabled: false}" :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="用户"> <vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
<template #header="{ column }"> <vxe-column field="user" width="100px" title="用户">
<span>{{ column.title }}</span> <template #header="{ column }">
<a-popover trigger="click" placement="bottom"> <span>{{ column.title }}</span>
<a-icon class="filter" type="filter" theme="filled"/> <a-popover trigger="click" placement="bottom">
<a slot="content"> <a-icon class="filter" type="filter" theme="filled"/>
<a-input placeholder="输入筛选用户名" size="small" v-model="queryParams.username" style="width: 200px" allowClear/> <a slot="content">
<a-button type="link" class="filterButton" @click="filterUser">筛选</a-button> <a-input placeholder="输入筛选用户名" size="small" v-model="queryParams.username" style="width: 200px" allowClear/>
<a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button> <a-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
</a> <a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
</a-popover> </a>
</template> </a-popover>
</vxe-column> </template>
<vxe-column field="type_id" width="100px" title="模型"></vxe-column> </vxe-column>
<vxe-column field="operate_type" width="89px" title="操作"> <vxe-column field="type_id" width="100px" title="模型"></vxe-column>
<template #header="{ column }"> <vxe-column field="operate_type" width="89px" title="操作">
<span>{{ column.title }}</span> <template #header="{ column }">
<a-popover trigger="click" placement="bottom"> <span>{{ column.title }}</span>
<a-icon class="filter" type="filter" theme="filled"/> <a-popover trigger="click" placement="bottom">
<a slot="content"> <a-icon class="filter" type="filter" theme="filled"/>
<a-select <a slot="content">
v-model="queryParams.operate_type" <a-select
placeholder="选择筛选操作" v-model="queryParams.operate_type"
show-search placeholder="选择筛选操作"
style="width: 200px" show-search
:filter-option="filterOption" style="width: 200px"
allowClear :filter-option="filterOption"
> allowClear
<a-select-option >
:value="Object.values(choice)[0]" <a-select-option
:key="index" :value="Object.values(choice)[0]"
v-for="(choice, index) in ciTableAttrList[4].choice_value" :key="index"
> v-for="(choice, index) in ciTableAttrList[4].choice_value"
{{ Object.keys(choice)[0] }} >
</a-select-option {{ Object.keys(choice)[0] }}
> </a-select-option
</a-select> >
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> </a-select>
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> <a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
</a> <a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
</a-popover> </a>
</template> </a-popover>
<template #default="{ row }"> </template>
<a-tag color="green" v-if="row.operate_type === '新增' "> <template #default="{ row }">
{{ row.operate_type }} <a-tag color="green" v-if="row.operate_type === '新增' ">
</a-tag> {{ row.operate_type }}
<a-tag color="orange" v-else-if="row.operate_type === '修改' "> </a-tag>
{{ row.operate_type }} <a-tag color="orange" v-else-if="row.operate_type === '修改' ">
</a-tag> {{ row.operate_type }}
<a-tag color="red" v-else> </a-tag>
{{ row.operate_type }} <a-tag color="red" v-else>
</a-tag> {{ row.operate_type }}
</template> </a-tag>
</vxe-column> </template>
<vxe-column field="attr_alias" title="属性"></vxe-column> </vxe-column>
<vxe-column field="old" title=""></vxe-column> <vxe-column field="attr_alias" title="属性"></vxe-column>
<vxe-column field="new" title=""></vxe-column> <vxe-column field="old" title=""></vxe-column>
</vxe-table> <vxe-column field="new" title=""></vxe-column>
<pager </vxe-table>
:current-page.sync="queryParams.page" <pager
:page-size.sync="queryParams.page_size" :current-page.sync="queryParams.page"
:page-sizes="[50,100,200]" :page-size.sync="queryParams.page_size"
:total="total" :page-sizes="[50,100,200]"
:isLoading="loading" :total="total"
@change="onChange" :isLoading="loading"
@showSizeChange="onShowSizeChange" @change="onChange"
></pager> @showSizeChange="onShowSizeChange"
</div> ></pager>
</template> </div>
<script> </template>
import Pager from './pager.vue' <script>
import SearchForm from './searchForm.vue' import Pager from './pager.vue'
import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history' import SearchForm from './searchForm.vue'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCIHistoryTable, getUsers } from '@/modules/cmdb/api/history'
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' import { getCITypes } from '@/modules/cmdb/api/CIType'
export default { import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
name: 'CiTable', export default {
components: { SearchForm, Pager }, name: 'CiTable',
data() { components: { SearchForm, Pager },
return { data() {
typeId: undefined, return {
operateTypeMap: new Map([ typeId: undefined,
['0', '新增'], operateTypeMap: new Map([
['1', '删除'], ['0', '新增'],
['2', '修改'], ['1', '删除'],
]), ['2', '修改'],
loading: true, ]),
typeList: null, loading: true,
userList: [], typeList: null,
tableData: [], userList: [],
total: 0, tableData: [],
isExpand: false, total: 0,
queryParams: { isExpand: false,
page: 1, queryParams: {
page_size: 50, page: 1,
}, page_size: 50,
ciTableAttrList: [ },
{ ciTableAttrList: [
alias: '日期', {
is_choice: false, alias: '日期',
name: 'datetime', is_choice: false,
value_type: '3' name: 'datetime',
}, value_type: '3'
{ },
alias: '用户', {
is_choice: true, alias: '用户',
name: 'username', is_choice: true,
value_type: '2', name: 'username',
choice_value: [] value_type: '2',
}, choice_value: []
{ },
alias: '模型', {
is_choice: true, alias: '模型',
name: 'type_id', is_choice: true,
value_type: '2', name: 'type_id',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: '属性', {
is_choice: true, alias: '属性',
name: 'attr_id', is_choice: true,
value_type: '2', name: 'attr_id',
choice_value: [] value_type: '2',
}, choice_value: []
{ },
alias: '操作', {
is_choice: true, alias: '操作',
name: 'operate_type', is_choice: true,
value_type: '2', name: 'operate_type',
choice_value: [ value_type: '2',
{ '新增': 0 }, choice_value: [
{ '删除': 1 }, { '新增': 0 },
{ '修改': 2 }, { '删除': 1 },
] { '修改': 2 },
}, ]
{ },
alias: 'CI_ID', {
is_choice: false, alias: 'CI_ID',
name: 'ci_id', is_choice: false,
value_type: '2' name: 'ci_id',
} value_type: '2'
], }
} ],
}, }
computed: { },
windowHeight() { computed: {
return this.$store.state.windowHeight windowHeight() {
}, return this.$store.state.windowHeight
windowHeightMinus() { },
return this.isExpand ? 396 : 331 windowHeightMinus() {
} return this.isExpand ? 396 : 331
}, }
async created() { },
this.$watch( async created() {
function () { this.$watch(
return this.ciTableAttrList[3].choice_value function () {
}, return this.ciTableAttrList[3].choice_value
function () { },
delete this.$refs.child.queryParams.attr_id function () {
} delete this.$refs.child.queryParams.attr_id
) }
await Promise.all([ )
this.getUserList(), await Promise.all([
this.getTypes() this.getUserList(),
]) this.getTypes()
await this.getTable(this.queryParams) ])
}, await this.getTable(this.queryParams)
updated() { },
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 updated() {
}, this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
methods: { },
// 获取表格数据 methods: {
async getTable(queryParams) { // 获取表格数据
try { async getTable(queryParams) {
this.loading = true try {
const res = await getCIHistoryTable(queryParams) this.loading = true
const tempArr = [] const res = await getCIHistoryTable(queryParams)
res.records.forEach(item => { const tempArr = []
item[0].type_id = this.handleTypeId(item[0].type_id) res.records.forEach(item => {
item[1].forEach((subItem) => { item[0].type_id = this.handleTypeId(item[0].type_id)
subItem.operate_type = this.handleOperateType(subItem.operate_type) item[1].forEach((subItem) => {
const tempObj = Object.assign(subItem, item[0]) subItem.operate_type = this.handleOperateType(subItem.operate_type)
tempArr.push(tempObj) const tempObj = Object.assign(subItem, item[0])
}) tempArr.push(tempObj)
}) })
this.tableData = tempArr })
this.total = res.total this.tableData = tempArr
} finally { this.total = res.total
this.loading = false } finally {
} this.loading = false
}, }
// 获取用户列表 },
async getUserList() { // 获取用户列表
const res = await getUsers() async getUserList() {
this.userList = res.map(x => { const res = await getUsers()
const username = x.nickname this.userList = res.map(x => {
const obj = { const username = x.nickname
[username]: username const obj = {
} [username]: username
return obj }
}) return obj
this.ciTableAttrList[1].choice_value = this.userList })
}, this.ciTableAttrList[1].choice_value = this.userList
// 获取模型 },
async getTypes() { // 获取模型
const res = await getCITypes() async getTypes() {
const typesArr = [] const res = await getCITypes()
const typesMap = new Map() const typesArr = []
res.ci_types.forEach(item => { const typesMap = new Map()
const tempObj = {} res.ci_types.forEach(item => {
tempObj[item.alias] = item.id const tempObj = {}
if (item.alias) { tempObj[item.alias] = item.id
typesArr.push(tempObj) if (item.alias) {
typesMap.set(item.id, item.alias) typesArr.push(tempObj)
} typesMap.set(item.id, item.alias)
}) }
this.typeList = typesMap })
this.ciTableAttrList[2].choice_value = typesArr this.typeList = typesMap
}, this.ciTableAttrList[2].choice_value = typesArr
// 获取模型对应属性 },
async getAttrs(type_id) { // 获取模型对应属性
if (!type_id) { async getAttrs(type_id) {
this.ciTableAttrList[3].choice_value = [] if (!type_id) {
return this.ciTableAttrList[3].choice_value = []
} return
const res = await getCITypeAttributesById(type_id) }
const attrsArr = [] const res = await getCITypeAttributesById(type_id)
res.attributes.forEach(item => { const attrsArr = []
const tempObj = {} res.attributes.forEach(item => {
tempObj[item.alias] = item.id const tempObj = {}
if (item.alias) { tempObj[item.alias] = item.id
attrsArr.push(tempObj) if (item.alias) {
} attrsArr.push(tempObj)
}) }
this.ciTableAttrList[3].choice_value = attrsArr })
}, this.ciTableAttrList[3].choice_value = attrsArr
onShowSizeChange(size) { },
this.queryParams.page_size = size onShowSizeChange(size) {
this.queryParams.page = 1 this.queryParams.page_size = size
this.getTable(this.queryParams) this.queryParams.page = 1
}, this.getTable(this.queryParams)
onChange(pageNum) { },
this.queryParams.page = pageNum onChange(pageNum) {
this.getTable(this.queryParams) this.queryParams.page = pageNum
}, this.getTable(this.queryParams)
handleExpandChange(expand) { },
this.isExpand = expand handleExpandChange(expand) {
}, this.isExpand = expand
// 处理查询 },
handleSearch(queryParams) { // 处理查询
this.queryParams = queryParams handleSearch(queryParams) {
this.getTable(this.queryParams) this.queryParams = queryParams
}, this.getTable(this.queryParams)
// 重置表单 },
searchFormReset() { // 重置表单
this.queryParams = { searchFormReset() {
page: 1, this.queryParams = {
page_size: 50, page: 1,
start: '', page_size: 50,
end: '', start: '',
username: '', end: '',
ci_id: undefined, username: '',
attr_id: undefined, ci_id: undefined,
operate_type: undefined attr_id: undefined,
} operate_type: undefined
// 将属性options重置 }
this.ciTableAttrList[3].choice_value = [] // 将属性options重置
this.getTable(this.queryParams) this.ciTableAttrList[3].choice_value = []
}, this.getTable(this.queryParams)
// 转换operate_type },
handleOperateType(operate_type) { // 转换operate_type
return this.operateTypeMap.get(operate_type) handleOperateType(operate_type) {
}, return this.operateTypeMap.get(operate_type)
// 转换type_id },
handleTypeId(type_id) { // 转换type_id
return this.typeList.get(type_id) ? this.typeList.get(type_id) : 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) { searchFormChange(queryParams) {
this.typeId = queryParams.type_id if (this.typeId !== queryParams.type_id) {
this.getAttrs(queryParams.type_id) this.typeId = queryParams.type_id
} this.getAttrs(queryParams.type_id)
if (queryParams.type_id === undefined) { }
this.typeId = undefined if (queryParams.type_id === undefined) {
this.$refs.child.queryParams.attr_id = undefined this.typeId = undefined
} this.$refs.child.queryParams.attr_id = undefined
}, }
// 合并表格 },
mergeRowMethod ({ row, _rowIndex, column, visibleData }) { // 合并表格
const fields = ['created_at', 'user', 'type_id'] mergeRowMethod ({ row, _rowIndex, column, visibleData }) {
// 单元格值 = [.属性] 确定一格 const fields = ['created_at', 'user', 'type_id']
const cellValue = row[column.property] // 单元格值 = [.属性] 确定一格
const created_at = row['created_at'] const cellValue = row[column.property]
// 如果单元格值不为空且作用域包含当前列 const created_at = row['created_at']
if (column.property === 'created_at') { // 如果单元格值不为空且作用域包含当前列
if (cellValue && fields.includes(column.property)) { if (column.property === 'created_at') {
// 前一行 if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] // 前一行
// 下一行 const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] // 下一行
// 如果前一行不为空且前一行单元格的值与cellValue相同 let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue) { // 如果前一行不为空且前一行单元格的值与cellValue相同
return { rowspan: 0, colspan: 0 } if (prevRow && prevRow[column.property] === cellValue) {
} else { return { rowspan: 0, colspan: 0 }
let countRowspan = 1 } else {
while (nextRow && nextRow[column.property] === cellValue) { let countRowspan = 1
nextRow = visibleData[++countRowspan + _rowIndex] while (nextRow && nextRow[column.property] === cellValue) {
} nextRow = visibleData[++countRowspan + _rowIndex]
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
} }
} else if (column.property === 'user') { }
if (cellValue && fields.includes(column.property)) { } else if (column.property === 'user') {
// 前一行 if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] // 前一行
// 下一行 const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] // 下一行
// 如果前一行不为空且前一行单元格的值与cellValue相同 let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { // 如果前一行不为空且前一行单元格的值与cellValue相同
return { rowspan: 0, colspan: 0 } if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
} else { return { rowspan: 0, colspan: 0 }
let countRowspan = 1 } else {
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { let countRowspan = 1
nextRow = visibleData[++countRowspan + _rowIndex] while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
} nextRow = visibleData[++countRowspan + _rowIndex]
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
} }
} else if (column.property === 'type_id') { }
if (cellValue && fields.includes(column.property)) { } else if (column.property === 'type_id') {
// 前一行 if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] // 前一行
// 下一行 const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] // 下一行
// 如果前一行不为空且前一行单元格的值与cellValue相同 let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { // 如果前一行不为空且前一行单元格的值与cellValue相同
return { rowspan: 0, colspan: 0 } if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
} else { return { rowspan: 0, colspan: 0 }
let countRowspan = 1 } else {
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { let countRowspan = 1
nextRow = visibleData[++countRowspan + _rowIndex] while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
} nextRow = visibleData[++countRowspan + _rowIndex]
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
} }
} }
}, }
filterUser() { },
this.queryParams.page = 1 filterUser() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.getTable(this.queryParams)
filterUserReset() { },
this.queryParams.page = 1 filterUserReset() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.username = '' this.queryParams.page_size = 50
this.getTable(this.queryParams) this.queryParams.username = ''
}, this.getTable(this.queryParams)
filterOperate() { },
this.queryParams.page = 1 filterOperate() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.getTable(this.queryParams)
filterOperateReset() { },
this.queryParams.page = 1 filterOperateReset() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.operate_type = undefined this.queryParams.page_size = 50
this.getTable(this.queryParams) this.queryParams.operate_type = undefined
}, this.getTable(this.queryParams)
filterOption(input, option) { },
return ( filterOption(input, option) {
option.componentOptions.children[0].text.indexOf(input) >= 0 return (
) option.componentOptions.children[0].text.indexOf(input) >= 0
} )
} }
} }
</script> }
</script>
<style lang="less" scoped>
.filter{ <style lang="less" scoped>
margin-left: 10px; .filter{
color: #c0c4cc; margin-left: 10px;
cursor: pointer; color: #c0c4cc;
&:hover{ cursor: pointer;
color: #606266; &:hover{
} color: #606266;
} }
</style> }
</style>

View File

@ -1,116 +1,116 @@
<template> <template>
<div> <div>
<a-row class="row" type="flex" justify="end"> <a-row class="row" type="flex" justify="end">
<a-col> <a-col>
<a-space align="end"> <a-space align="end">
<a-button class="left-button" size="small" :disabled="prevIsDisabled" @click="prevPage"><a-icon type="left" /></a-button> <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="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-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-dropdown class="dropdown" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)"> <a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ size }}/ {{ size }}/
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<a-button size="small"> {{ pageSize }}/ <a-icon type="down" /> </a-button> <a-button size="small"> {{ pageSize }}/ <a-icon type="down" /> </a-button>
</a-dropdown> </a-dropdown>
</a-space> </a-space>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
currentPage: { currentPage: {
type: Number, type: Number,
required: true required: true
}, },
pageSize: { pageSize: {
type: Number, type: Number,
required: true required: true
}, },
pageSizes: { pageSizes: {
type: Array, type: Array,
required: true required: true
}, },
total: { total: {
type: Number, type: Number,
required: true required: true
}, },
isLoading: { isLoading: {
type: Boolean, type: Boolean,
required: false required: false
} }
}, },
data() { data() {
return { return {
dropdownIsDisabled: false, dropdownIsDisabled: false,
prevIsDisabled: true, prevIsDisabled: true,
} }
}, },
computed: { computed: {
nextIsDisabled() { nextIsDisabled() {
return this.isLoading || this.total < this.pageSize return this.isLoading || this.total < this.pageSize
} }
}, },
watch: { watch: {
isLoading: { isLoading: {
immediate: true, immediate: true,
handler: function (val) { handler: function (val) {
if (val === true) { if (val === true) {
this.dropdownIsDisabled = true this.dropdownIsDisabled = true
this.prevIsDisabled = true this.prevIsDisabled = true
} else { } else {
this.dropdownIsDisabled = false this.dropdownIsDisabled = false
if (this.currentPage === 1) { if (this.currentPage === 1) {
this.prevIsDisabled = true this.prevIsDisabled = true
} else { } else {
this.prevIsDisabled = false this.prevIsDisabled = false
} }
} }
} }
}, },
currentPage: { currentPage: {
immediate: true, immediate: true,
handler: function (val) { handler: function (val) {
if (val === 1) { if (val === 1) {
this.prevIsDisabled = true this.prevIsDisabled = true
} }
} }
} }
}, },
methods: { methods: {
handleItemClick(size) { handleItemClick(size) {
this.$emit('showSizeChange', size) this.$emit('showSizeChange', size)
}, },
nextPage() { nextPage() {
const pageNum = this.currentPage + 1 const pageNum = this.currentPage + 1
this.$emit('change', pageNum) this.$emit('change', pageNum)
}, },
prevPage() { prevPage() {
const pageNum = this.currentPage - 1 const pageNum = this.currentPage - 1
this.$emit('change', pageNum) this.$emit('change', pageNum)
} }
} }
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.row{ .row{
margin-top: 5px; margin-top: 5px;
.left-button{ .left-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
.right-button{ .right-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
.page-button{ .page-button{
padding: 0; padding: 0;
width: 24px; width: 24px;
} }
} }
</style> </style>

View File

@ -1,403 +1,404 @@
<template> <template>
<div> <div>
<search-form <search-form
:attrList="relationTableAttrList" :attrList="relationTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:loading="loading" :loading="loading"
border size="small"
size="small" show-overflow="tooltip"
show-overflow="tooltip" show-header-overflow="tooltip"
show-header-overflow="tooltip" resizable
resizable :data="tableData"
:data="tableData" :max-height="`${windowHeight - windowHeightMinus}px`"
:max-height="`${windowHeight - windowHeightMinus}px`" row-id="_XID"
row-id="_XID" :scroll-y="{ enabled: false }"
:scroll-y="{ enabled: false }" :span-method="mergeRowMethod"
: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="用户"> <vxe-column field="created_at" width="159px" title="操作时间"></vxe-column>
<template #header="{ column }"> <vxe-column field="user" width="100px" title="用户">
<span>{{ column.title }}</span> <template #header="{ column }">
<a-popover trigger="click" placement="bottom"> <span>{{ column.title }}</span>
<a-icon class="filter" type="filter" theme="filled" /> <a-popover trigger="click" placement="bottom">
<a slot="content"> <a-icon class="filter" type="filter" theme="filled" />
<a-input <a slot="content">
placeholder="输入筛选用户名" <a-input
size="small" placeholder="输入筛选用户名"
v-model="queryParams.username" size="small"
style="width: 200px" v-model="queryParams.username"
allowClear 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-button type="link" class="filterButton" @click="filterUser">筛选</a-button>
</a> <a-button type="link" class="filterResetButton" @click="filterUserReset">重置</a-button>
</a-popover> </a>
</template> </a-popover>
</vxe-column> </template>
<vxe-column field="operate_type" width="89px" title="操作"> </vxe-column>
<template #header="{ column }"> <vxe-column field="operate_type" width="89px" title="操作">
<span>{{ column.title }}</span> <template #header="{ column }">
<a-popover trigger="click" placement="bottom"> <span>{{ column.title }}</span>
<a-icon class="filter" type="filter" theme="filled" /> <a-popover trigger="click" placement="bottom">
<a slot="content"> <a-icon class="filter" type="filter" theme="filled" />
<a-select <a slot="content">
v-model="queryParams.operate_type" <a-select
placeholder="选择筛选操作" v-model="queryParams.operate_type"
show-search placeholder="选择筛选操作"
style="width: 200px" show-search
:filter-option="filterOption" style="width: 200px"
allowClear :filter-option="filterOption"
> allowClear
<a-select-option >
:value="Object.values(choice)[0]" <a-select-option
:key="index" :value="Object.values(choice)[0]"
v-for="(choice, index) in relationTableAttrList[4].choice_value" :key="index"
> v-for="(choice, index) in relationTableAttrList[4].choice_value"
{{ Object.keys(choice)[0] }} >
</a-select-option> {{ Object.keys(choice)[0] }}
</a-select> </a-select-option>
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> </a-select>
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> <a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
</a> <a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
</a-popover> </a>
</template> </a-popover>
<template #default="{ row }"> </template>
<a-tag color="green" v-if="row.operate_type.includes('新增')"> <template #default="{ row }">
{{ row.operate_type }} <a-tag color="green" v-if="row.operate_type.includes('新增')">
</a-tag> {{ row.operate_type }}
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')"> </a-tag>
{{ row.operate_type }} <a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
</a-tag> {{ row.operate_type }}
<a-tag color="red" v-else> </a-tag>
{{ row.operate_type }} <a-tag color="red" v-else>
</a-tag> {{ row.operate_type }}
</template> </a-tag>
</vxe-column> </template>
<vxe-column field="changeDescription" title="描述"> </vxe-column>
<template #default="{ row }"> <vxe-column field="changeDescription" title="描述">
<a-tag v-if="row && row.first"> <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.ci_type_alias}${
? `${row.first.unique_alias}${row.first[row.first.unique]}` 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 === '没有修改'"> </a-tag>
{{ row.relation_type_id }} <a-tag v-if="row.changeDescription === '没有修改'">
</a-tag> {{ row.relation_type_id }}
<template v-else-if="row.operate_type.includes('修改')"> </a-tag>
<a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr"> <template v-else-if="row.operate_type.includes('修改')">
{{ tag }} <a-tag :key="index" color="orange" v-for="(tag, index) in row.changeArr">
</a-tag> {{ tag }}
</template> </a-tag>
<a-tag color="green" v-else-if="row.operate_type.includes('新增')" :style="{ fontWeight: 'bolder' }"> </template>
{{ row.relation_type_id }} <a-tag color="green" v-else-if="row.operate_type.includes('新增')" :style="{ fontWeight: 'bolder' }">
</a-tag> {{ row.relation_type_id }}
<a-tag color="red" v-else-if="row.operate_type.includes('删除')"> </a-tag>
{{ row.relation_type_id }} <a-tag color="red" v-else-if="row.operate_type.includes('删除')">
</a-tag> {{ row.relation_type_id }}
<a-tag v-if="row && row.second"> </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.ci_type_alias}${
? `${row.second.unique_alias}${row.second[row.second.unique]}` row.second.unique_alias && row.second[row.second.unique]
: '' ? `${row.second.unique_alias}${row.second[row.second.unique]}`
}` : ''
}} }`
</a-tag> }}
</template> </a-tag>
</vxe-column> </template>
</vxe-table> </vxe-column>
<pager </vxe-table>
:current-page.sync="queryParams.page" <pager
:page-size.sync="queryParams.page_size" :current-page.sync="queryParams.page"
:page-sizes="[50, 100, 200]" :page-size.sync="queryParams.page_size"
:total="total" :page-sizes="[50, 100, 200]"
:isLoading="loading" :total="total"
@change="onChange" :isLoading="loading"
@showSizeChange="onShowSizeChange" @change="onChange"
></pager> @showSizeChange="onShowSizeChange"
</div> ></pager>
</template> </div>
</template>
<script>
import SearchForm from './searchForm' <script>
import Pager from './pager.vue' import SearchForm from './searchForm'
import { getCITypes } from '@/modules/cmdb/api/CIType' import Pager from './pager.vue'
import { getRelationTable, getUsers } from '@/modules/cmdb/api/history' import { getCITypes } from '@/modules/cmdb/api/CIType'
import { getRelationTypes } from '@/modules/cmdb/api/relationType' import { getRelationTable, getUsers } from '@/modules/cmdb/api/history'
export default { import { getRelationTypes } from '@/modules/cmdb/api/relationType'
name: 'RelationTable', export default {
components: { SearchForm, Pager }, name: 'RelationTable',
data() { components: { SearchForm, Pager },
return { data() {
visible: false, return {
loading: true, visible: false,
isExpand: false, loading: true,
tableData: [], isExpand: false,
relationTypeList: null, tableData: [],
total: 0, relationTypeList: null,
userList: [], total: 0,
operateTypeMap: new Map([ userList: [],
['0', '新增'], operateTypeMap: new Map([
['1', '删除'], ['0', '新增'],
['2', '修改'], ['1', '删除'],
]), ['2', '修改'],
queryParams: { ]),
page: 1, queryParams: {
page_size: 50, page: 1,
start: '', page_size: 50,
end: '', start: '',
username: '', end: '',
first_ci_id: undefined, username: '',
second_ci_id: undefined, first_ci_id: undefined,
operate_type: undefined, second_ci_id: undefined,
}, operate_type: undefined,
relationTableAttrList: [ },
{ relationTableAttrList: [
alias: '日期', {
is_choice: false, alias: '日期',
name: 'datetime', is_choice: false,
value_type: '3', name: 'datetime',
}, value_type: '3',
{ },
alias: '用户', {
is_choice: true, alias: '用户',
name: 'username', is_choice: true,
value_type: '2', name: 'username',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: 'FirstCI_ID', {
is_choice: false, alias: 'FirstCI_ID',
name: 'first_ci_id', is_choice: false,
value_type: '2', name: 'first_ci_id',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: 'SecondCI_ID', {
is_choice: false, alias: 'SecondCI_ID',
name: 'second_ci_id', is_choice: false,
value_type: '2', name: 'second_ci_id',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: '操作', {
is_choice: true, alias: '操作',
name: 'operate_type', is_choice: true,
value_type: '2', name: 'operate_type',
choice_value: [{ 新增: 0 }, { 删除: 1 }, { 修改: 2 }], value_type: '2',
}, choice_value: [{ 新增: 0 }, { 删除: 1 }, { 修改: 2 }],
], },
} ],
}, }
async created() { },
await Promise.all([ async created() {
this.getRelationTypes(), await Promise.all([
this.getUserList(), this.getRelationTypes(),
this.getTypes(), this.getUserList(),
]) this.getTypes(),
await this.getTable(this.queryParams) ])
}, await this.getTable(this.queryParams)
updated() { },
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 updated() {
}, this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
computed: { },
windowHeight() { computed: {
return this.$store.state.windowHeight windowHeight() {
}, return this.$store.state.windowHeight
windowHeightMinus() { },
return this.isExpand ? 396 : 331 windowHeightMinus() {
}, return this.isExpand ? 396 : 331
}, },
methods: { },
// 获取表格数据 methods: {
async getTable(queryParams) { // 获取表格数据
try { async getTable(queryParams) {
this.loading = true try {
const res = await getRelationTable(queryParams) this.loading = true
const tempArr = [] const res = await getRelationTable(queryParams)
res.records.forEach((item) => { const tempArr = []
item[1].forEach((subItem) => { res.records.forEach((item) => {
subItem.operate_type = this.handleOperateType(subItem.operate_type) item[1].forEach((subItem) => {
subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id) subItem.operate_type = this.handleOperateType(subItem.operate_type)
subItem.first = res.cis[String(subItem.first_ci_id)] subItem.relation_type_id = this.handleRelationType(subItem.relation_type_id)
subItem.second = res.cis[String(subItem.second_ci_id)] subItem.first = res.cis[String(subItem.first_ci_id)]
const tempObj = Object.assign(subItem, item[0]) subItem.second = res.cis[String(subItem.second_ci_id)]
tempArr.push(tempObj) const tempObj = Object.assign(subItem, item[0])
}) tempArr.push(tempObj)
}) })
this.total = res.total })
this.tableData = tempArr this.total = res.total
} finally { this.tableData = tempArr
this.loading = false } finally {
} this.loading = false
}, }
// 获取用户列表 },
async getUserList() { // 获取用户列表
const res = await getUsers() async getUserList() {
this.userList = res.map((x) => { const res = await getUsers()
const username = x.nickname this.userList = res.map((x) => {
const obj = { const username = x.nickname
[username]: username, const obj = {
} [username]: username,
return obj }
}) return obj
this.relationTableAttrList[1].choice_value = this.userList })
}, this.relationTableAttrList[1].choice_value = this.userList
// 获取模型 },
async getTypes() { // 获取模型
const res = await getCITypes() async getTypes() {
const typesArr = [] const res = await getCITypes()
res.ci_types.forEach((item) => { const typesArr = []
const tempObj = {} res.ci_types.forEach((item) => {
tempObj[item.alias] = item.id const tempObj = {}
if (item.alias) { tempObj[item.alias] = item.id
typesArr.push(tempObj) if (item.alias) {
} typesArr.push(tempObj)
}) }
this.relationTableAttrList[2].choice_value = typesArr })
this.relationTableAttrList[3].choice_value = typesArr this.relationTableAttrList[2].choice_value = typesArr
}, this.relationTableAttrList[3].choice_value = typesArr
// 获取关系 },
async getRelationTypes() { // 获取关系
const res = await getRelationTypes() async getRelationTypes() {
const relationTypeMap = new Map() const res = await getRelationTypes()
res.forEach((item) => { const relationTypeMap = new Map()
relationTypeMap.set(item.id, item.name) res.forEach((item) => {
}) relationTypeMap.set(item.id, item.name)
this.relationTypeList = relationTypeMap })
}, this.relationTypeList = relationTypeMap
onShowSizeChange(size) { },
this.queryParams.page_size = size onShowSizeChange(size) {
this.queryParams.page = 1 this.queryParams.page_size = size
this.getTable(this.queryParams) this.queryParams.page = 1
}, this.getTable(this.queryParams)
onChange(pageNum) { },
this.queryParams.page = pageNum onChange(pageNum) {
this.getTable(this.queryParams) this.queryParams.page = pageNum
}, this.getTable(this.queryParams)
handleExpandChange(expand) { },
this.isExpand = expand handleExpandChange(expand) {
}, this.isExpand = expand
// 处理查询 },
handleSearch(queryParams) { // 处理查询
this.queryParams = queryParams handleSearch(queryParams) {
this.getTable(queryParams) this.queryParams = queryParams
}, this.getTable(queryParams)
// 重置表单 },
searchFormReset() { // 重置表单
this.queryParams = { searchFormReset() {
page: 1, this.queryParams = {
page_size: 50, page: 1,
start: '', page_size: 50,
end: '', start: '',
username: '', end: '',
first_ci_id: undefined, username: '',
second_ci_id: undefined, first_ci_id: undefined,
operate_type: undefined, second_ci_id: undefined,
} operate_type: undefined,
this.getTable(this.queryParams) }
}, this.getTable(this.queryParams)
// 转换operate_type },
handleOperateType(operate_type) { // 转换operate_type
return this.operateTypeMap.get(operate_type) handleOperateType(operate_type) {
}, return this.operateTypeMap.get(operate_type)
// 转换relation_type_id },
handleRelationType(relation_type_id) { // 转换relation_type_id
return this.relationTypeList.get(relation_type_id) handleRelationType(relation_type_id) {
}, return this.relationTypeList.get(relation_type_id)
// 合并表格 },
mergeRowMethod({ row, _rowIndex, column, visibleData }) { // 合并表格
const fields = ['created_at', 'user'] mergeRowMethod({ row, _rowIndex, column, visibleData }) {
// 单元格值 = [.属性] 确定一格 const fields = ['created_at', 'user']
const cellValue = row[column.property] // 单元格值 = [.属性] 确定一格
const created_at = row['created_at'] const cellValue = row[column.property]
// 如果单元格值不为空且作用域包含当前列 const created_at = row['created_at']
if (column.property === 'created_at') { // 如果单元格值不为空且作用域包含当前列
if (cellValue && fields.includes(column.property)) { if (column.property === 'created_at') {
// 前一行 if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] // 前一行
// 下一行 const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] // 下一行
// 如果前一行不为空且前一行单元格的值与cellValue相同 let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue) { // 如果前一行不为空且前一行单元格的值与cellValue相同
return { rowspan: 0, colspan: 0 } if (prevRow && prevRow[column.property] === cellValue) {
} else { return { rowspan: 0, colspan: 0 }
let countRowspan = 1 } else {
while (nextRow && nextRow[column.property] === cellValue) { let countRowspan = 1
nextRow = visibleData[++countRowspan + _rowIndex] while (nextRow && nextRow[column.property] === cellValue) {
} nextRow = visibleData[++countRowspan + _rowIndex]
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
} }
} else if (column.property === 'user') { }
if (cellValue && fields.includes(column.property)) { } else if (column.property === 'user') {
// 前一行 if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1] // 前一行
// 下一行 const prevRow = visibleData[_rowIndex - 1]
let nextRow = visibleData[_rowIndex + 1] // 下一行
// 如果前一行不为空且前一行单元格的值与cellValue相同 let nextRow = visibleData[_rowIndex + 1]
if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) { // 如果前一行不为空且前一行单元格的值与cellValue相同
return { rowspan: 0, colspan: 0 } if (prevRow && prevRow[column.property] === cellValue && prevRow['created_at'] === created_at) {
} else { return { rowspan: 0, colspan: 0 }
let countRowspan = 1 } else {
while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) { let countRowspan = 1
nextRow = visibleData[++countRowspan + _rowIndex] while (nextRow && nextRow[column.property] === cellValue && nextRow['created_at'] === created_at) {
} nextRow = visibleData[++countRowspan + _rowIndex]
if (countRowspan > 1) { }
return { rowspan: countRowspan, colspan: 1 } if (countRowspan > 1) {
} return { rowspan: countRowspan, colspan: 1 }
} }
} }
} }
}, }
filterUser() { },
this.queryParams.page = 1 filterUser() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.getTable(this.queryParams)
filterUserReset() { },
this.queryParams.page = 1 filterUserReset() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.username = '' this.queryParams.page_size = 50
this.getTable(this.queryParams) this.queryParams.username = ''
}, this.getTable(this.queryParams)
filterOperate() { },
this.queryParams.page = 1 filterOperate() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.getTable(this.queryParams)
filterOperateReset() { },
this.queryParams.page = 1 filterOperateReset() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.operate_type = undefined this.queryParams.page_size = 50
this.getTable(this.queryParams) this.queryParams.operate_type = undefined
}, this.getTable(this.queryParams)
filterOption(input, option) { },
return option.componentOptions.children[0].text.indexOf(input) >= 0 filterOption(input, option) {
}, return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
} },
</script> }
</script>
<style lang="less" scoped>
.filter { <style lang="less" scoped>
margin-left: 10px; .filter {
color: #c0c4cc; margin-left: 10px;
cursor: pointer; color: #c0c4cc;
&:hover { cursor: pointer;
color: #606266; &:hover {
} color: #606266;
} }
</style> }
</style>

View File

@ -1,191 +1,191 @@
<template> <template>
<div> <div>
<a-form :colon="false"> <a-form :colon="false">
<a-row :gutter="24"> <a-row :gutter="24">
<a-col <a-col
:sm="24" :sm="24"
:md="12" :md="12"
:lg="12" :lg="12"
:xl="8" :xl="8"
v-for="attr in attrList.slice(0,3)" v-for="attr in attrList.slice(0,3)"
:key="attr.name" :key="attr.name"
> >
<a-form-item <a-form-item
:label="attr.alias || attr.name " :label="attr.alias || attr.name "
:labelCol="{span:4}" :labelCol="{span:4}"
:wrapperCol="{span:20}" :wrapperCol="{span:20}"
labelAlign="right" labelAlign="right"
> >
<a-select <a-select
v-model="queryParams[attr.name]" v-model="queryParams[attr.name]"
placeholder="请选择" placeholder="请选择"
v-if="attr.is_choice" v-if="attr.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
v-for="(choice, index) in attr.choice_value" v-for="(choice, index) in attr.choice_value"
:key="'Search_' + attr.name + index" :key="'Search_' + attr.name + index"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-range-picker <a-range-picker
v-model="date" v-model="date"
@change="onChange" @change="onChange"
:style="{width:'100%'}" :style="{width:'100%'}"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']" :placeholder="['开始时间', '结束时间']"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
}" }"
v-else-if="valueTypeMap[attr.value_type] == 'date' || valueTypeMap[attr.value_type] == 'datetime'" 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-input v-model="queryParams[attr.name]" style="width: 100%" allowClear v-else />
</a-form-item> </a-form-item>
</a-col> </a-col>
<template v-if="expand && attrList.length >= 4"> <template v-if="expand && attrList.length >= 4">
<a-col <a-col
:sm="24" :sm="24"
:md="12" :md="12"
:lg="8" :lg="8"
:xl="8" :xl="8"
:key="'expand_' + item.name" :key="'expand_' + item.name"
v-for="item in attrList.slice(3)" v-for="item in attrList.slice(3)"
> >
<a-form-item <a-form-item
:label="item.alias || item.name" :label="item.alias || item.name"
:label-col="{ span: 4 }" :label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }" :wrapper-col="{ span: 20 }"
labelAlign="right" labelAlign="right"
> >
<a-select <a-select
v-model="queryParams[item.name]" v-model="queryParams[item.name]"
placeholder="请选择" placeholder="请选择"
v-if="item.is_choice" v-if="item.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
allowClear allowClear
> >
<a-select-option <a-select-option
:value="Object.values(choice)[0]" :value="Object.values(choice)[0]"
:key="'Search_' + item.name + index" :key="'Search_' + item.name + index"
v-for="(choice, index) in item.choice_value" v-for="(choice, index) in item.choice_value"
> >
{{ Object.keys(choice)[0] }} {{ Object.keys(choice)[0] }}
</a-select-option </a-select-option
> >
</a-select> </a-select>
<a-range-picker <a-range-picker
:style="{width:'100%'}" :style="{width:'100%'}"
@change="onChange" @change="onChange"
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
:placeholder="['开始时间', '结束时间']" :placeholder="['开始时间', '结束时间']"
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'" v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
}" }"
/> />
<a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/> <a-input v-model="queryParams[item.name]" style="width: 100%" allowClear v-else/>
</a-form-item> </a-form-item>
</a-col> </a-col>
</template> </template>
</a-row> </a-row>
<a-row> <a-row>
<a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }"> <a-col :span="24" :style="{ textAlign: 'right' , marginBottom: '10px' }">
<a-button type="primary" html-type="submit" @click="handleSearch"> <a-button type="primary" html-type="submit" @click="handleSearch">
查询 查询
</a-button> </a-button>
<a-button :style="{ marginLeft: '8px' }" @click="handleReset"> <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
重置 重置
</a-button> </a-button>
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4"> <a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 4">
{{ expand?'隐藏':'展开' }} <a-icon :type="expand ? 'up' : 'down'" /> {{ expand?'隐藏':'展开' }} <a-icon :type="expand ? 'up' : 'down'" />
</a> </a>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</div> </div>
</template> </template>
<script> <script>
import moment from 'moment' import moment from 'moment'
import { valueTypeMap } from '../../../utils/const' import { valueTypeMap } from '../../../utils/const'
export default { export default {
name: 'SearchForm', name: 'SearchForm',
props: { props: {
attrList: { attrList: {
type: Array, type: Array,
required: true required: true
} }
}, },
data() { data() {
return { return {
valueTypeMap, valueTypeMap,
expand: false, expand: false,
queryParams: { queryParams: {
page: 1, page: 1,
page_size: 50 page_size: 50
}, },
date: undefined date: undefined
} }
}, },
watch: { watch: {
queryParams: { queryParams: {
deep: true, deep: true,
handler: function (val) { handler: function (val) {
this.preProcessData() this.preProcessData()
this.$emit('searchFormChange', val) this.$emit('searchFormChange', val)
} }
}, },
}, },
methods: { methods: {
moment, moment,
handleSearch() { handleSearch() {
this.queryParams.page = 1 this.queryParams.page = 1
this.$emit('search', this.queryParams) this.$emit('search', this.queryParams)
}, },
handleReset() { handleReset() {
this.queryParams = { this.queryParams = {
page: 1, page: 1,
page_size: 50 page_size: 50
} }
this.date = undefined this.date = undefined
this.$emit('searchFormReset') this.$emit('searchFormReset')
}, },
toggle() { toggle() {
this.expand = !this.expand this.expand = !this.expand
this.$emit('expandChange', this.expand) this.$emit('expandChange', this.expand)
}, },
onChange(date, dateString) { onChange(date, dateString) {
this.queryParams.start = dateString[0] this.queryParams.start = dateString[0]
this.queryParams.end = dateString[1] this.queryParams.end = dateString[1]
}, },
filterOption(input, option) { filterOption(input, option) {
return ( return (
option.componentOptions.children[0].text.indexOf(input) >= 0 option.componentOptions.children[0].text.indexOf(input) >= 0
) )
}, },
preProcessData() { preProcessData() {
Object.keys(this.queryParams).forEach(item => { Object.keys(this.queryParams).forEach(item => {
if (this.queryParams[item] === '' || this.queryParams[item] === undefined) { if (this.queryParams[item] === '' || this.queryParams[item] === undefined) {
delete this.queryParams[item] delete this.queryParams[item]
} }
}) })
return this.queryParams return this.queryParams
}, },
}, },
} }
</script> </script>
<style> <style>
</style> </style>

View File

@ -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>

View File

@ -1,477 +1,478 @@
<template> <template>
<div> <div>
<search-form <search-form
:attrList="typeTableAttrList" :attrList="typeTableAttrList"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
></search-form> ></search-form>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
:loading="loading" :loading="loading"
border resizable
resizable :data="tableData"
:data="tableData" :max-height="`${windowHeight - windowHeightMinus}px`"
:max-height="`${windowHeight - windowHeightMinus}px`" row-id="_XID"
row-id="_XID" size="small"
size="small" :row-config="{isHover: true}"
: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="created_at" width="159px" title="操作时间"></vxe-column>
<vxe-column field="operate_type" width="135px" title="操作"> <vxe-column field="user" width="116px" title="用户"></vxe-column>
<template #header="{ column }"> <vxe-column field="operate_type" width="135px" title="操作">
<span>{{ column.title }}</span> <template #header="{ column }">
<a-popover trigger="click" placement="bottom"> <span>{{ column.title }}</span>
<a-icon class="filter" type="filter" theme="filled" /> <a-popover trigger="click" placement="bottom">
<a slot="content"> <a-icon class="filter" type="filter" theme="filled" />
<a-select <a slot="content">
v-model="queryParams.operate_type" <a-select
placeholder="选择筛选操作" v-model="queryParams.operate_type"
show-search placeholder="选择筛选操作"
style="width: 200px" show-search
:filter-option="filterOption" style="width: 200px"
allowClear :filter-option="filterOption"
> allowClear
<a-select-option >
:value="Object.values(choice)[0]" <a-select-option
:key="index" :value="Object.values(choice)[0]"
v-for="(choice, index) in typeTableAttrList[1].choice_value" :key="index"
> v-for="(choice, index) in typeTableAttrList[1].choice_value"
{{ Object.keys(choice)[0] }} >
</a-select-option> {{ Object.keys(choice)[0] }}
</a-select> </a-select-option>
<a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button> </a-select>
<a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button> <a-button type="link" class="filterButton" @click="filterOperate">筛选</a-button>
</a> <a-button type="link" class="filterResetButton" @click="filterOperateReset">重置</a-button>
</a-popover> </a>
</template> </a-popover>
<template #default="{ row }"> </template>
<a-tag color="green" v-if="row.operate_type.includes('新增')"> <template #default="{ row }">
{{ row.operate_type }} <a-tag color="green" v-if="row.operate_type.includes('新增')">
</a-tag> {{ row.operate_type }}
<a-tag color="orange" v-else-if="row.operate_type.includes('修改')"> </a-tag>
{{ row.operate_type }} <a-tag color="orange" v-else-if="row.operate_type.includes('修改')">
</a-tag> {{ row.operate_type }}
<a-tag color="red" v-else> </a-tag>
{{ row.operate_type }} <a-tag color="red" v-else>
</a-tag> {{ row.operate_type }}
</template> </a-tag>
</vxe-column> </template>
<vxe-column field="type_id" title="模型" width="150px"> </vxe-column>
<template #default="{ row }"> <vxe-column field="type_id" title="模型" width="150px">
{{ row.operate_type === '删除模型' ? row.change.alias : row.type_id }} <template #default="{ row }">
</template> {{ row.operate_type === '删除模型' ? row.change.alias : row.type_id}}
</vxe-column> </template>
<vxe-column field="changeDescription" title="描述"> </vxe-column>
<template #default="{ row }"> <vxe-column field="changeDescription" title="描述">
<p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === '没有修改'"> <template #default="{ row }">
{{ row.changeDescription }} <p style="color:rgba(0, 0, 0, 0.65);" v-if="row.changeDescription === '没有修改'">
</p> {{ row.changeDescription }}
<template v-else-if="row.operate_type.includes('修改')"> </p>
<p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr"> <template v-else-if="row.operate_type.includes('修改')">
{{ tag }} <p :key="index" style="color:#fa8c16;" v-for="(tag, index) in row.changeArr">
</p> {{ tag }}
</template> </p>
<p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes('新增')"> </template>
{{ row.changeDescription }} <p class="more-tag" style="color:#52c41a;" v-else-if="row.operate_type.includes('新增')">
</p> {{ row.changeDescription }}
<p style="color:#f5222d;" v-else-if="row.operate_type.includes('删除')"> </p>
{{ row.changeDescription }} <p style="color:#f5222d;" v-else-if="row.operate_type.includes('删除')">
</p> {{ row.changeDescription }}
</template> </p>
</vxe-column> </template>
</vxe-table> </vxe-column>
<a-row class="row" type="flex" justify="end"> </vxe-table>
<a-col> <a-row class="row" type="flex" justify="end">
<a-pagination <a-col>
size="small" <a-pagination
v-model="current" size="small"
:page-size-options="pageSizeOptions" v-model="current"
:total="numfound" :page-size-options="pageSizeOptions"
show-size-changer :total="numfound"
:page-size="pageSize" show-size-changer
@change="onChange" :page-size="pageSize"
@showSizeChange="onShowSizeChange" @change="onChange"
:show-total="(total) => `共 ${total} 条记录`" @showSizeChange="onShowSizeChange"
> :show-total="(total) => `共 ${total} 条记录`"
</a-pagination> >
</a-col> </a-pagination>
</a-row> </a-col>
</div> </a-row>
</template> </div>
</template>
<script>
import _ from 'lodash' <script>
import SearchForm from './searchForm' import _ from 'lodash'
import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history' import SearchForm from './searchForm'
import { getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypesTable, getUsers } from '@/modules/cmdb/api/history'
import { getRelationTypes } from '@/modules/cmdb/api/relationType' import { getCITypes } from '@/modules/cmdb/api/CIType'
export default { import { getRelationTypes } from '@/modules/cmdb/api/relationType'
name: 'TypeTable', export default {
components: { SearchForm }, name: 'TypeTable',
data() { components: { SearchForm },
return { data() {
loading: true, return {
relationTypeList: null, loading: true,
operateTypeMap: new Map([ relationTypeList: null,
['0', '新增模型'], operateTypeMap: new Map([
['1', '修改模型'], ['0', '新增模型'],
['2', '删除模型'], ['1', '修改模型'],
['3', '新增属性'], ['2', '删除模型'],
['4', '修改属性'], ['3', '新增属性'],
['5', '删除属性'], ['4', '修改属性'],
['6', '新增触发器'], ['5', '删除属性'],
['7', '修改触发器'], ['6', '新增触发器'],
['8', '删除触发器'], ['7', '修改触发器'],
['9', '新增联合唯一'], ['8', '删除触发器'],
['10', '修改联合唯一'], ['9', '新增联合唯一'],
['11', '删除联合唯一'], ['10', '修改联合唯一'],
['12', '新增关系'], ['11', '删除联合唯一'],
['13', '删除关系'], ['12', '新增关系'],
]), ['13', '删除关系'],
typeList: null, ]),
userList: [], typeList: null,
typeTableAttrList: [ userList: [],
{ typeTableAttrList: [
alias: '模型', {
is_choice: true, alias: '模型',
name: 'type_id', is_choice: true,
value_type: '2', name: 'type_id',
choice_value: [], value_type: '2',
}, choice_value: [],
{ },
alias: '操作', {
is_choice: true, alias: '操作',
name: 'operate_type', is_choice: true,
value_type: '2', name: 'operate_type',
choice_value: [ value_type: '2',
{ 新增模型: 0 }, choice_value: [
{ 修改模型: 1 }, { 新增模型: 0 },
{ 删除模型: 2 }, { 修改模型: 1 },
{ 新增属性: 3 }, { 删除模型: 2 },
{ 修改属性: 4 }, { 新增属性: 3 },
{ 删除属性: 5 }, { 修改属性: 4 },
{ 新增触发器: 6 }, { 删除属性: 5 },
{ 修改触发器: 7 }, { 新增触发器: 6 },
{ 删除触发器: 8 }, { 修改触发器: 7 },
{ 新增联合唯一: 9 }, { 删除触发器: 8 },
{ 修改联合唯一: 10 }, { 新增联合唯一: 9 },
{ 删除联合唯一: 11 }, { 修改联合唯一: 10 },
{ 新增关系: 12 }, { 删除联合唯一: 11 },
{ 删除关系: 13 }, { 新增关系: 12 },
], { 删除关系: 13 },
}, ],
], },
pageSizeOptions: ['50', '100', '200'], ],
isExpand: false, pageSizeOptions: ['50', '100', '200'],
current: 1, isExpand: false,
pageSize: 50, current: 1,
total: 0, pageSize: 50,
numfound: 0, total: 0,
tableData: [], numfound: 0,
queryParams: { tableData: [],
page: 1, queryParams: {
page_size: 50, page: 1,
type_id: undefined, page_size: 50,
operate_type: undefined, type_id: undefined,
}, operate_type: undefined,
} },
}, }
async created() { },
await Promise.all([ async created() {
this.getRelationTypes(), await Promise.all([
this.getTypes(), this.getRelationTypes(),
this.getUserList(), this.getTypes(),
]) this.getUserList(),
await this.getTable(this.queryParams) ])
}, await this.getTable(this.queryParams)
updated() { },
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 updated() {
}, this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
computed: { },
windowHeight() { computed: {
return this.$store.state.windowHeight windowHeight() {
}, return this.$store.state.windowHeight
windowHeightMinus() { },
return this.isExpand ? 396 : 331 windowHeightMinus() {
}, return this.isExpand ? 396 : 335
}, },
watch: { },
current(val) { watch: {
this.queryParams.page = val current(val) {
}, this.queryParams.page = val
pageSize(val) { },
this.queryParams.page_size = val pageSize(val) {
}, this.queryParams.page_size = val
}, },
methods: { },
// 获取表格数据 methods: {
async getTable(queryParams) { // 获取表格数据
try { async getTable(queryParams) {
this.loading = true try {
const res = await getCITypesTable(queryParams) this.loading = true
res.result.forEach((item) => { const res = await getCITypesTable(queryParams)
this.handleChangeDescription(item, item.operate_type) res.result.forEach((item) => {
item.operate_type = this.handleOperateType(item.operate_type) this.handleChangeDescription(item, item.operate_type)
item.type_id = this.handleTypeId(item.type_id) item.operate_type = this.handleOperateType(item.operate_type)
item.uid = this.handleUID(item.uid) item.type_id = this.handleTypeId(item.type_id)
}) item.uid = this.handleUID(item.uid)
this.tableData = res.result })
this.pageSize = res.page_size this.tableData = res.result
this.current = res.page this.pageSize = res.page_size
this.numfound = res.numfound this.current = res.page
this.total = res.total this.numfound = res.numfound
console.log(this.tableData) this.total = res.total
} finally { console.log(this.tableData)
this.loading = false } finally {
} this.loading = false
}, }
// 获取模型 },
async getTypes() { // 获取模型
const res = await getCITypes() async getTypes() {
const typesArr = [] const res = await getCITypes()
const typesMap = new Map() const typesArr = []
res.ci_types.forEach((item) => { const typesMap = new Map()
const tempObj = {} res.ci_types.forEach((item) => {
tempObj[item.alias] = item.id const tempObj = {}
if (item.alias) { tempObj[item.alias] = item.id
typesMap.set(item.id, item.alias) if (item.alias) {
typesArr.push(tempObj) typesMap.set(item.id, item.alias)
} typesArr.push(tempObj)
}) }
this.typeList = typesMap })
// 设置模型options选项 this.typeList = typesMap
this.typeTableAttrList[0].choice_value = typesArr // 设置模型options选项
}, this.typeTableAttrList[0].choice_value = typesArr
// 获取用户列表 },
async getUserList() { // 获取用户列表
const res = await getUsers() async getUserList() {
const userListMap = new Map() const res = await getUsers()
res.forEach((item) => { const userListMap = new Map()
userListMap.set(item.uid, item.nickname) res.forEach((item) => {
}) userListMap.set(item.uid, item.nickname)
this.userList = userListMap })
}, this.userList = userListMap
// 获取关系 },
async getRelationTypes() { // 获取关系
const res = await getRelationTypes() async getRelationTypes() {
const relationTypeMap = new Map() const res = await getRelationTypes()
res.forEach((item) => { const relationTypeMap = new Map()
relationTypeMap.set(item.id, item.name) res.forEach((item) => {
}) relationTypeMap.set(item.id, item.name)
this.relationTypeList = relationTypeMap })
}, this.relationTypeList = relationTypeMap
onChange(current) { },
this.current = current onChange(current) {
this.getTable(this.queryParams) this.current = current
}, this.getTable(this.queryParams)
onShowSizeChange(current, size) { },
this.current = 1 onShowSizeChange(current, size) {
this.pageSize = size this.current = 1
this.getTable(this.queryParams) this.pageSize = size
}, this.getTable(this.queryParams)
handleExpandChange(expand) { },
this.isExpand = expand handleExpandChange(expand) {
}, this.isExpand = expand
// 处理查询 },
handleSearch(queryParams) { // 处理查询
this.current = 1 handleSearch(queryParams) {
this.queryParams = queryParams this.current = 1
this.getTable(this.queryParams) this.queryParams = queryParams
}, this.getTable(this.queryParams)
// 重置表单 },
searchFormReset() { // 重置表单
this.queryParams = { searchFormReset() {
page: 1, this.queryParams = {
page_size: 50, page: 1,
type_id: undefined, page_size: 50,
operate_type: undefined, type_id: undefined,
} operate_type: undefined,
this.getTable(this.queryParams) }
}, this.getTable(this.queryParams)
// 转换operate_type },
handleOperateType(operate_type) { // 转换operate_type
return this.operateTypeMap.get(operate_type) handleOperateType(operate_type) {
}, return this.operateTypeMap.get(operate_type)
// 转换type_id },
handleTypeId(type_id) { // 转换type_id
return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id handleTypeId(type_id) {
}, return this.typeList.get(type_id) ? this.typeList.get(type_id) : type_id
// 转换uid },
handleUID(uid) { // 转换uid
return this.userList.get(uid) handleUID(uid) {
}, return this.userList.get(uid)
// 转换relation_type_id },
handleRelationType(relation_type_id) { // 转换relation_type_id
return this.relationTypeList.get(relation_type_id) handleRelationType(relation_type_id) {
}, return this.relationTypeList.get(relation_type_id)
// 处理改变描述 },
handleChangeDescription(item, operate_type) { // 处理改变描述
switch (operate_type) { handleChangeDescription(item, operate_type) {
// 新增模型 switch (operate_type) {
case '0': { // 新增模型
item.changeDescription = '新增模型:' + item.change.alias case '0': {
break item.changeDescription = '新增模型:' + item.change.alias
} break
// 修改模型 }
case '1': { // 修改模型
item.changeArr = [] case '1': {
for (const key in item.change.old) { item.changeArr = []
const newVal = item.change.new[key] for (const key in item.change.old) {
const oldVal = item.change.old[key] const newVal = item.change.new[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') { const oldVal = item.change.old[key]
if (oldVal === null) { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
const str = ` [ ${key} : 改为 ${newVal || '""'} ] ` if (oldVal === null) {
item.changeDescription += str const str = ` [ ${key} : 改为 ${newVal || '""'} ] `
item.changeArr.push(str) item.changeDescription += str
} else { item.changeArr.push(str)
const str = ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] ` } else {
item.changeDescription += ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] ` const str = ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] `
item.changeArr.push(str) item.changeDescription += ` [ ${key} : ${oldVal || '""'} 改为 ${newVal || '""'} ] `
} item.changeArr.push(str)
} }
} }
if (!item.changeDescription) item.changeDescription = '没有修改' }
break if (!item.changeDescription) item.changeDescription = '没有修改'
} break
// 删除模型 }
case '2': { // 删除模型
item.changeDescription = `删除模型${item.change.alias}` case '2': {
break item.changeDescription = `删除模型${item.change.alias}`
} break
// 新增属性 }
case '3': { // 新增属性
item.changeDescription = `属性名${item.change.alias}` case '3': {
break item.changeDescription = `属性名${item.change.alias}`
} break
// 修改属性 }
case '4': { // 修改属性
item.changeArr = [] case '4': {
for (const key in item.change.old) { item.changeArr = []
if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') { for (const key in item.change.old) {
let newStr = item.change.new[key] if (!_.isEqual(item.change.new[key], item.change.old[key]) && key !== 'updated_at') {
let oldStr = item.change.old[key] let newStr = item.change.new[key]
if (key === 'choice_value') { let oldStr = item.change.old[key]
newStr = newStr ? newStr.map((item) => item[0]).join(',') : '' if (key === 'choice_value') {
oldStr = oldStr ? oldStr.map((item) => item[0]).join(',') : '' 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(newStr) === '[object Object]') {
} newStr = JSON.stringify(newStr)
if (Object.prototype.toString.call(oldStr) === '[object Object]') { }
oldStr = JSON.stringify(oldStr) if (Object.prototype.toString.call(oldStr) === '[object Object]') {
} oldStr = JSON.stringify(oldStr)
const str = `${key} : ${oldStr ? ` ${oldStr || '""'} ` : ''} 改为 ${newStr || '""'}` }
item.changeDescription += ` [ ${str} ] ` const str = `${key} : ${oldStr ? ` ${oldStr || '""'} ` : ''} 改为 ${newStr || '""'}`
item.changeArr.push(str) item.changeDescription += ` [ ${str} ] `
} item.changeArr.push(str)
} }
if (!item.changeDescription) item.changeDescription = '没有修改' }
break if (!item.changeDescription) item.changeDescription = '没有修改'
} break
// 删除属性 }
case '5': { // 删除属性
item.changeDescription = `删除${item.change.alias}` case '5': {
break 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}` case '6': {
break 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 = [] case '7': {
for (const key in item.change.old.notify) { item.changeArr = []
const newVal = item.change.new.notify[key] for (const key in item.change.old.option) {
const oldVal = item.change.old.notify[key] const newVal = item.change.new.option[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') { const oldVal = item.change.old.option[key]
const str = ` [ ${key} : ${oldVal} 改为 ${newVal} ] ` if (!_.isEqual(newVal, oldVal) && key !== 'updated_at') {
item.changeDescription += str const str = ` [ ${key} : ${oldVal} 改为 ${newVal} ] `
item.changeArr.push(str) item.changeDescription += str
} item.changeArr.push(str)
} }
if (!item.changeDescription) item.changeDescription = '没有修改' }
break 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}` case '8': {
break 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}]` case '9': {
break item.changeDescription = `属性id[${item.change.attr_ids}]`
} break
// 修改联合唯一 }
case '10': { // 修改联合唯一
item.changeArr = [] case '10': {
const oldVal = item.change.old.attr_ids item.changeArr = []
const newVal = item.change.new.attr_ids const oldVal = item.change.old.attr_ids
const str = `属性id[${oldVal}] -> [${newVal}]` const newVal = item.change.new.attr_ids
item.changeDescription = str const str = `属性id[${oldVal}] -> [${newVal}]`
item.changeArr.push(str) item.changeDescription = str
break item.changeArr.push(str)
} break
// 删除联合唯一 }
case '11': { // 删除联合唯一
item.changeDescription = `属性id[${item.change.attr_ids}]` case '11': {
break item.changeDescription = `属性id[${item.change.attr_ids}]`
} break
// 新增关系 }
case '12': { // 新增关系
item.changeDescription = `新增${item.change.parent.alias} -> ${this.handleRelationType( case '12': {
item.change.relation_type_id item.changeDescription = `新增${item.change.parent.alias} -> ${this.handleRelationType(
)} -> ${item.change.child.alias}` item.change.relation_type_id
break )} -> ${item.change.child.alias}`
} break
// 删除关系 }
case '13': { // 删除关系
item.changeDescription = `删除${item.change.parent_id.alias} -> ${this.handleRelationType( case '13': {
item.change.relation_type_id item.changeDescription = `删除${item.change.parent_id.alias} -> ${this.handleRelationType(
)} -> ${item.change.child.alias}` item.change.relation_type_id
break )} -> ${item.change.child.alias}`
} break
} }
}, }
filterOperate() { },
this.queryParams.page = 1 filterOperate() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.getTable(this.queryParams) this.queryParams.page_size = 50
}, this.getTable(this.queryParams)
filterOperateReset() { },
this.queryParams.page = 1 filterOperateReset() {
this.queryParams.page_size = 50 this.queryParams.page = 1
this.queryParams.operate_type = undefined this.queryParams.page_size = 50
this.getTable(this.queryParams) this.queryParams.operate_type = undefined
}, this.getTable(this.queryParams)
filterOption(input, option) { },
return option.componentOptions.children[0].text.indexOf(input) >= 0 filterOption(input, option) {
}, return option.componentOptions.children[0].text.indexOf(input) >= 0
}, },
} },
</script> }
</script>
<style lang="less" scoped>
.row { <style lang="less" scoped>
margin-top: 5px; .row {
} margin-top: 5px;
.filter { }
margin-left: 10px; .filter {
color: #c0c4cc; margin-left: 10px;
cursor: pointer; color: #c0c4cc;
&:hover { cursor: pointer;
color: #606266; &:hover {
} color: #606266;
} }
.more-tag { }
max-width: 100%; .more-tag {
overflow: hidden; max-width: 100%;
text-overflow:ellipsis; overflow: hidden;
} text-overflow:ellipsis;
p { }
margin-bottom: 0; p {
} margin-bottom: 0;
</style> }
</style>

View File

@ -94,7 +94,7 @@
<ops-icon type="ops-setting-notice-wx" /> <ops-icon type="ops-setting-notice-wx" />
</div> </div>
<div @click="handleBindWx" class="setting-person-bind-button"> <div @click="handleBindWx" class="setting-person-bind-button">
{{ form.wx_id ? '重新绑定' : '绑定' }} {{ form.notice_info.wechatApp ? '重新绑定' : '绑定' }}
</div> </div>
</a-space> </a-space>
</a-form-model-item> </a-form-model-item>