mirror of https://github.com/veops/cmdb.git
feat(ui): update relative views menu display
This commit is contained in:
parent
b669775cd6
commit
bc3201656c
cmdb-ui
public/iconfont
src
components/Menu
modules/cmdb
components/cmdbGrant
lang
router
views/relation_views
|
@ -54,6 +54,54 @@
|
|||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-servicetree</div>
|
||||
<div class="code-name">&#xea0b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-switch (1)</div>
|
||||
<div class="code-name">&#xea0a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-label</div>
|
||||
<div class="code-name">&#xea09;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_acl</div>
|
||||
<div class="code-name">&#xea08;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_ticket</div>
|
||||
<div class="code-name">&#xea06;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">top_agent</div>
|
||||
<div class="code-name">&#xea07;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-table_download</div>
|
||||
<div class="code-name">&#xea05;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">itsm-image_download</div>
|
||||
<div class="code-name">&#xea04;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">veops-rear</div>
|
||||
|
@ -6162,9 +6210,9 @@
|
|||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
|
||||
url('iconfont.woff?t=1732673294759') format('woff'),
|
||||
url('iconfont.ttf?t=1732673294759') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
|
@ -6190,6 +6238,78 @@
|
|||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-servicetree"></span>
|
||||
<div class="name">
|
||||
veops-servicetree
|
||||
</div>
|
||||
<div class="code-name">.veops-servicetree
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-switch1"></span>
|
||||
<div class="name">
|
||||
veops-switch (1)
|
||||
</div>
|
||||
<div class="code-name">.veops-switch1
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-label"></span>
|
||||
<div class="name">
|
||||
veops-label
|
||||
</div>
|
||||
<div class="code-name">.veops-label
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_acl"></span>
|
||||
<div class="name">
|
||||
top_acl
|
||||
</div>
|
||||
<div class="code-name">.top_acl
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_ticket"></span>
|
||||
<div class="name">
|
||||
top_ticket
|
||||
</div>
|
||||
<div class="code-name">.top_ticket
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont top_agent"></span>
|
||||
<div class="name">
|
||||
top_agent
|
||||
</div>
|
||||
<div class="code-name">.top_agent
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-table_download"></span>
|
||||
<div class="name">
|
||||
itsm-table_download
|
||||
</div>
|
||||
<div class="code-name">.itsm-table_download
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont itsm-image_download"></span>
|
||||
<div class="name">
|
||||
itsm-image_download
|
||||
</div>
|
||||
<div class="code-name">.itsm-image_download
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont veops-rear"></span>
|
||||
<div class="name">
|
||||
|
@ -15352,6 +15472,70 @@
|
|||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-servicetree"></use>
|
||||
</svg>
|
||||
<div class="name">veops-servicetree</div>
|
||||
<div class="code-name">#veops-servicetree</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-switch1"></use>
|
||||
</svg>
|
||||
<div class="name">veops-switch (1)</div>
|
||||
<div class="code-name">#veops-switch1</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-label"></use>
|
||||
</svg>
|
||||
<div class="name">veops-label</div>
|
||||
<div class="code-name">#veops-label</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_acl"></use>
|
||||
</svg>
|
||||
<div class="name">top_acl</div>
|
||||
<div class="code-name">#top_acl</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_ticket"></use>
|
||||
</svg>
|
||||
<div class="name">top_ticket</div>
|
||||
<div class="code-name">#top_ticket</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#top_agent"></use>
|
||||
</svg>
|
||||
<div class="name">top_agent</div>
|
||||
<div class="code-name">#top_agent</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-table_download"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-table_download</div>
|
||||
<div class="code-name">#itsm-table_download</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#itsm-image_download"></use>
|
||||
</svg>
|
||||
<div class="name">itsm-image_download</div>
|
||||
<div class="code-name">#itsm-image_download</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#veops-rear"></use>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3857903 */
|
||||
src: url('iconfont.woff2?t=1732673294759') format('woff2'),
|
||||
url('iconfont.woff?t=1732673294759') format('woff'),
|
||||
url('iconfont.ttf?t=1732673294759') format('truetype');
|
||||
src: url('iconfont.woff2?t=1735191938771') format('woff2'),
|
||||
url('iconfont.woff?t=1735191938771') format('woff'),
|
||||
url('iconfont.ttf?t=1735191938771') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -13,6 +13,38 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.veops-servicetree:before {
|
||||
content: "\ea0b";
|
||||
}
|
||||
|
||||
.veops-switch1:before {
|
||||
content: "\ea0a";
|
||||
}
|
||||
|
||||
.veops-label:before {
|
||||
content: "\ea09";
|
||||
}
|
||||
|
||||
.top_acl:before {
|
||||
content: "\ea08";
|
||||
}
|
||||
|
||||
.top_ticket:before {
|
||||
content: "\ea06";
|
||||
}
|
||||
|
||||
.top_agent:before {
|
||||
content: "\ea07";
|
||||
}
|
||||
|
||||
.itsm-table_download:before {
|
||||
content: "\ea05";
|
||||
}
|
||||
|
||||
.itsm-image_download:before {
|
||||
content: "\ea04";
|
||||
}
|
||||
|
||||
.veops-rear:before {
|
||||
content: "\ea02";
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,62 @@
|
|||
"css_prefix_text": "",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "42930714",
|
||||
"name": "veops-servicetree",
|
||||
"font_class": "veops-servicetree",
|
||||
"unicode": "ea0b",
|
||||
"unicode_decimal": 59915
|
||||
},
|
||||
{
|
||||
"icon_id": "42921461",
|
||||
"name": "veops-switch (1)",
|
||||
"font_class": "veops-switch1",
|
||||
"unicode": "ea0a",
|
||||
"unicode_decimal": 59914
|
||||
},
|
||||
{
|
||||
"icon_id": "42857659",
|
||||
"name": "veops-label",
|
||||
"font_class": "veops-label",
|
||||
"unicode": "ea09",
|
||||
"unicode_decimal": 59913
|
||||
},
|
||||
{
|
||||
"icon_id": "42790685",
|
||||
"name": "top_acl",
|
||||
"font_class": "top_acl",
|
||||
"unicode": "ea08",
|
||||
"unicode_decimal": 59912
|
||||
},
|
||||
{
|
||||
"icon_id": "42790687",
|
||||
"name": "top_ticket",
|
||||
"font_class": "top_ticket",
|
||||
"unicode": "ea06",
|
||||
"unicode_decimal": 59910
|
||||
},
|
||||
{
|
||||
"icon_id": "42790686",
|
||||
"name": "top_agent",
|
||||
"font_class": "top_agent",
|
||||
"unicode": "ea07",
|
||||
"unicode_decimal": 59911
|
||||
},
|
||||
{
|
||||
"icon_id": "42732510",
|
||||
"name": "itsm-table_download",
|
||||
"font_class": "itsm-table_download",
|
||||
"unicode": "ea05",
|
||||
"unicode_decimal": 59909
|
||||
},
|
||||
{
|
||||
"icon_id": "42732515",
|
||||
"name": "itsm-image_download",
|
||||
"font_class": "itsm-image_download",
|
||||
"unicode": "ea04",
|
||||
"unicode_decimal": 59908
|
||||
},
|
||||
{
|
||||
"icon_id": "42510712",
|
||||
"name": "veops-rear",
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -171,7 +171,6 @@ export default {
|
|||
},
|
||||
renderMenuItem(menu) {
|
||||
const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/'
|
||||
const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/'
|
||||
const target = menu.meta.target || null
|
||||
const tag = target && 'a' || 'router-link'
|
||||
const props = { to: { name: menu.name } }
|
||||
|
@ -205,11 +204,9 @@ export default {
|
|||
<a-icon type="menu" ref="extraEllipsis" class="custom-menu-extra-ellipsis"></a-icon>
|
||||
</a-popover>
|
||||
}
|
||||
{isShowGrant && <a-icon class="custom-menu-extra-ellipsis" onClick={e => this.handlePerm(e, menu, 'RelationView')} type="user-add" />}
|
||||
</span>
|
||||
</tag>
|
||||
{isShowDot && <CMDBGrant ref="cmdbGrantCIType" resourceType="CIType" app_id="cmdb" />}
|
||||
{isShowGrant && <CMDBGrant ref="cmdbGrantRelationView" resourceType="RelationView" app_id="cmdb" />}
|
||||
</Item>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -88,6 +88,7 @@ import ReadGrantModal from './readGrantModal'
|
|||
import RelationViewGrant from './relationViewGrant.vue'
|
||||
import TopologyViewGrant from './topologyViewGrant.vue'
|
||||
import { getCITypeGroupById, ciTypeFilterPermissions } from '../../api/CIType'
|
||||
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||
|
||||
export default {
|
||||
name: 'GrantComp',
|
||||
|
@ -186,6 +187,13 @@ export default {
|
|||
},
|
||||
getFilterPermissions() {
|
||||
ciTypeFilterPermissions(this.CITypeId).then((res) => {
|
||||
Object.keys(res).forEach((key) => {
|
||||
const attr_filter = res?.[key]?.attr_filter
|
||||
if (attr_filter?.length) {
|
||||
res[key].attr_filter = attr_filter.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item))
|
||||
}
|
||||
})
|
||||
|
||||
this.filerPerimissions = res
|
||||
})
|
||||
},
|
||||
|
|
|
@ -26,7 +26,8 @@ const cmdb_en = {
|
|||
ad: 'AutoDiscovery',
|
||||
cidetail: 'CI Detail',
|
||||
scene: 'Scene',
|
||||
dcim: 'DCIM'
|
||||
dcim: 'DCIM',
|
||||
serviceTree: 'Service Tree'
|
||||
},
|
||||
ciType: {
|
||||
ciType: 'CIType',
|
||||
|
|
|
@ -26,7 +26,8 @@ const cmdb_zh = {
|
|||
ad: '自动发现',
|
||||
cidetail: 'CI 详情',
|
||||
scene: '场景',
|
||||
dcim: '数据中心'
|
||||
dcim: '数据中心',
|
||||
serviceTree: '服务树'
|
||||
},
|
||||
ciType: {
|
||||
ciType: '模型',
|
||||
|
|
|
@ -27,6 +27,17 @@ const genCmdbRoutes = async () => {
|
|||
name: 'cmdb_disabled1',
|
||||
meta: { title: 'cmdb.menu.resources', disabled: true },
|
||||
},
|
||||
{
|
||||
path: '/cmdb/relationviews/:viewId?',
|
||||
name: 'cmdb_relation_views',
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: {
|
||||
title: 'cmdb.menu.serviceTree',
|
||||
appName: 'cmdb',
|
||||
icon: 'veops-servicetree',
|
||||
keepAlive: false
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/cmdb/resourceviews',
|
||||
name: 'cmdb_resource_views',
|
||||
|
@ -194,15 +205,14 @@ const genCmdbRoutes = async () => {
|
|||
} else {
|
||||
routes.redirect = '/cmdb/dashboard'
|
||||
}
|
||||
const relationViews = relation.name2id.map(item => {
|
||||
return {
|
||||
path: `/cmdb/relationviews/${item[1]}`,
|
||||
name: `cmdb_relation_views_${item[1]}`,
|
||||
component: () => import('../views/relation_views/index'),
|
||||
meta: { title: item[0], icon: 'ops-cmdb-relation', selectedIcon: 'ops-cmdb-relation', keepAlive: false, name: item[0] },
|
||||
|
||||
if (relation?.name2id?.length === 0) {
|
||||
const relationViewRouteIndex = routes.children?.findIndex?.((route) => route.name === 'cmdb_relation_views')
|
||||
if (relationViewRouteIndex >= 0) {
|
||||
routes.children.splice(relationViewRouteIndex, 1)
|
||||
}
|
||||
})
|
||||
routes.children.splice(resourceViewsIndex, 0, ...relationViews)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,48 @@
|
|||
>
|
||||
<template #one>
|
||||
<div class="relation-views-left" :style="{ height: `${windowHeight - 64}px` }">
|
||||
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
|
||||
<div class="relation-views-left-header">
|
||||
<div class="relation-views-left-header-icon">
|
||||
<ops-icon type="ops-cmdb-relation" />
|
||||
</div>
|
||||
|
||||
<div class="relation-views-left-header-name relation-views-text-scroll">
|
||||
<span>
|
||||
{{ viewName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a-dropdown
|
||||
overlayClassName="relation-views-left-header-dropdown"
|
||||
>
|
||||
<div class="relation-views-left-header-down">
|
||||
<ops-icon type="veops-switch1" />
|
||||
</div>
|
||||
|
||||
<a-menu
|
||||
slot="overlay"
|
||||
:selectedKeys="[viewId]"
|
||||
class="relation-views-left-header-menu"
|
||||
>
|
||||
<a-menu-item
|
||||
v-for="(item) in relationViewMenu"
|
||||
:key="item.id"
|
||||
@click="clickRelationViewMenu(item.id)"
|
||||
>
|
||||
<a class="relation-views-left-header-menu-item">
|
||||
<div class="relation-views-left-header-menu-name relation-views-text-scroll">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
<a-icon
|
||||
class="relation-views-left-header-menu-grant"
|
||||
type="user-add"
|
||||
@click.stop="handlePerm(item.name)"
|
||||
/>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<a-input
|
||||
:placeholder="$t('cmdb.serviceTree.searchTips')"
|
||||
class="relation-views-left-input"
|
||||
|
@ -214,7 +255,7 @@
|
|||
</SplitPane>
|
||||
</div>
|
||||
<a-alert
|
||||
:message="$t('cmdb.serviceTreealert1')"
|
||||
:message="$t('noData')"
|
||||
banner
|
||||
v-else-if="relationViews.name2id && !relationViews.name2id.length"
|
||||
></a-alert>
|
||||
|
@ -271,6 +312,8 @@ import ReadPermissionsModal from './modules/ReadPermissionsModal.vue'
|
|||
import RevokeModal from '../../components/cmdbGrant/revokeModal.vue'
|
||||
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||
|
||||
const relationViewKeyStorage = 'cmdb_relation_view_menu_key'
|
||||
|
||||
export default {
|
||||
name: 'RelationViews',
|
||||
components: {
|
||||
|
@ -370,7 +413,7 @@ export default {
|
|||
return !!this.selectedRowKeys.length
|
||||
},
|
||||
topo_flatten() {
|
||||
return this.relationViews?.views[this.$route.meta.name]?.topo_flatten ?? []
|
||||
return this.relationViews?.views[this.viewName]?.topo_flatten ?? []
|
||||
},
|
||||
descendant_ids() {
|
||||
return this.topo_flatten.slice(this.treeKeys.length).join(',')
|
||||
|
@ -393,6 +436,15 @@ export default {
|
|||
leaf_tree_sort() {
|
||||
return this.viewOption?.sort ?? 1
|
||||
},
|
||||
relationViewMenu() {
|
||||
const name2id = this?.relationViews?.name2id || []
|
||||
return name2id.map((item) => {
|
||||
return {
|
||||
id: item?.[1] || -1,
|
||||
name: item?.[0] || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
|
@ -429,10 +481,6 @@ export default {
|
|||
},
|
||||
inject: ['reload'],
|
||||
watch: {
|
||||
'$route.path': function(newPath, oldPath) {
|
||||
this.viewId = this.$route.params.viewId
|
||||
this.reload()
|
||||
},
|
||||
pageNo: function(newPage, oldPage) {
|
||||
this.loadData({ parameter: { pageNo: newPage }, refreshType: undefined, sortByTable: this.sortByTable })
|
||||
},
|
||||
|
@ -869,36 +917,46 @@ export default {
|
|||
this.relationViews = res
|
||||
}
|
||||
if ((Object.keys(this.relationViews.views) || []).length) {
|
||||
this.viewId =
|
||||
parseInt(this.$route.path.split('/')[this.$route.path.split('/').length - 1]) ||
|
||||
this.relationViews.name2id[0][1]
|
||||
this.relationViews.name2id.forEach((item) => {
|
||||
if (item[1] === this.viewId) {
|
||||
this.viewName = item[0]
|
||||
}
|
||||
})
|
||||
this.levels = this.relationViews.views[this.viewName].topo
|
||||
this.origShowTypes = this.relationViews.views[this.viewName].show_types
|
||||
const showTypeIds = []
|
||||
this.origShowTypes.forEach((item) => {
|
||||
showTypeIds.push(item.id)
|
||||
})
|
||||
this.origShowTypeIds = showTypeIds
|
||||
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_types
|
||||
this.node2ShowTypes = this.relationViews.views[this.viewName].node2show_types
|
||||
this.level2constraint = this.relationViews.views[this.viewName].level2constraint
|
||||
this.leaf = this.relationViews.views[this.viewName].leaf
|
||||
this.currentView = `${this.viewId}`
|
||||
this.typeId = this.levels[0][0]
|
||||
this.viewOption = this.relationViews.views[this.viewName].option ?? {}
|
||||
let viewId = parseInt(localStorage.getItem(relationViewKeyStorage)) || parseInt(this.$route.params.viewId) || this.relationViews.name2id[0][1]
|
||||
let viewName = null
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.refreshTable()
|
||||
})
|
||||
const currentView = this.relationViews.name2id.find((item) => item?.[1] === viewId)
|
||||
if (currentView) {
|
||||
viewName = currentView[0]
|
||||
} else {
|
||||
viewId = this.relationViews.name2id[0][1]
|
||||
viewName = this.relationViews.name2id[0][0]
|
||||
}
|
||||
|
||||
localStorage.setItem(relationViewKeyStorage, viewId)
|
||||
this.viewId = viewId
|
||||
this.viewName = viewName
|
||||
this.refreshData()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
this.levels = this.relationViews.views[this.viewName].topo
|
||||
this.origShowTypes = this.relationViews.views[this.viewName].show_types
|
||||
const showTypeIds = []
|
||||
this.origShowTypes.forEach((item) => {
|
||||
showTypeIds.push(item.id)
|
||||
})
|
||||
this.origShowTypeIds = showTypeIds
|
||||
this.leaf2showTypes = this.relationViews.views[this.viewName].leaf2show_types
|
||||
this.node2ShowTypes = this.relationViews.views[this.viewName].node2show_types
|
||||
this.level2constraint = this.relationViews.views[this.viewName].level2constraint
|
||||
this.leaf = this.relationViews.views[this.viewName].leaf
|
||||
this.currentView = `${this.viewId}`
|
||||
this.typeId = this.levels[0][0]
|
||||
this.viewOption = this.relationViews.views[this.viewName].option ?? {}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.refreshTable()
|
||||
})
|
||||
},
|
||||
|
||||
async loadColumns() {
|
||||
if (this.currentTypeId[0]) {
|
||||
this.getAttributeList()
|
||||
|
@ -954,7 +1012,7 @@ export default {
|
|||
const that = this
|
||||
this.$confirm({
|
||||
title: that.$t('warning'),
|
||||
content: (h) => <div>{that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] })}</div>,
|
||||
content: that.$t('confirmDelete2', { name: Object.values(firstCIObj)[0] }),
|
||||
onOk() {
|
||||
deleteCIRelationView(_tempTreeParent[0], _tempTree[0], { ancestor_ids }).then((res) => {
|
||||
that.$message.success(that.$t('deleteSuccess'))
|
||||
|
@ -1063,18 +1121,20 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
handlePerm() {
|
||||
handlePerm(resourceName) {
|
||||
const _resource_name = resourceName ?? this.viewName
|
||||
|
||||
roleHasPermissionToGrant({
|
||||
app_id: 'cmdb',
|
||||
resource_type_name: 'RelationView',
|
||||
perm: 'grant',
|
||||
resource_name: this.$route.meta.title,
|
||||
resource_name: _resource_name,
|
||||
}).then((res) => {
|
||||
if (res.result) {
|
||||
searchResourceType({ page_size: 9999, app_id: 'cmdb' }).then((res) => {
|
||||
this.resource_type = { groups: res.groups, id2perms: res.id2perms }
|
||||
this.$nextTick(() => {
|
||||
this.$refs.cmdbGrant.open({ name: this.$route.meta.title, cmdbGrantType: 'relation_view' })
|
||||
this.$refs.cmdbGrant.open({ name: _resource_name, cmdbGrantType: 'relation_view' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
@ -1100,7 +1160,7 @@ export default {
|
|||
},
|
||||
columnDrop() {
|
||||
this.$nextTick(() => {
|
||||
const xTable = this.$refs.xTable.getVxetableRef()
|
||||
const xTable = this.$refs?.xTable?.getVxetableRef?.()
|
||||
this.sortable = Sortable.create(
|
||||
xTable.$el.querySelector('.body--wrapper>.vxe-table--header .vxe-header--row'),
|
||||
{
|
||||
|
@ -1282,7 +1342,7 @@ export default {
|
|||
async openBatchDownload() {
|
||||
this.$refs.batchDownload.open({
|
||||
preferenceAttrList: this.preferenceAttrList.filter((attr) => !attr?.is_reference),
|
||||
ciTypeName: this.$route.meta.name,
|
||||
ciTypeName: this.viewName,
|
||||
})
|
||||
},
|
||||
batchDownload({ filename, type, checkedKeys }) {
|
||||
|
@ -1629,6 +1689,13 @@ export default {
|
|||
|
||||
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||||
},
|
||||
|
||||
clickRelationViewMenu(id) {
|
||||
if (id) {
|
||||
localStorage.setItem(relationViewKeyStorage, id)
|
||||
this.reload()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1649,17 +1716,61 @@ export default {
|
|||
overflow: auto;
|
||||
}
|
||||
.relation-views-left-header {
|
||||
border-left: 4px solid @primary-color;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding-left: 12px;
|
||||
margin-bottom: 12px;
|
||||
color: @text-color_1;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: @border-color-base;
|
||||
margin-bottom: 14px;
|
||||
|
||||
&-icon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 22px;
|
||||
background-color: @primary-color;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-left: 9px;
|
||||
|
||||
span {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-down {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 1px;
|
||||
background-color: @primary-color_3;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @primary-color_4;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-tree li {
|
||||
padding: 2px 0;
|
||||
|
@ -1680,14 +1791,14 @@ export default {
|
|||
}
|
||||
.relation-views-left-input {
|
||||
margin-bottom: 12px;
|
||||
input {
|
||||
background-color: transparent;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}
|
||||
.ant-input:focus {
|
||||
box-shadow: none;
|
||||
|
||||
.ant-input {
|
||||
background-color: #FFFFFF;
|
||||
border: solid 1px transparent;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1703,4 +1814,75 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-left-header-dropdown {
|
||||
background-color: #FFFFFF;
|
||||
|
||||
.relation-views-left-header-menu {
|
||||
box-shadow: none;
|
||||
max-height: 400px;
|
||||
min-height: 150px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&-item {
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
.relation-views-left-header-menu-grant {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-grant {
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
color: @text-color_4;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-text-scroll {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
& > span {
|
||||
overflow: visible;
|
||||
animation: scroll-left 3s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scroll-left {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,367 +1,367 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'relation-views-node': true,
|
||||
'relation-views-node-checkbox': showCheckbox,
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span class="relation-views-node-switch">
|
||||
<a-icon v-if="!isLeaf" :type="switchIcon"></a-icon>
|
||||
</span>
|
||||
<span class="relation-views-node-content">
|
||||
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.includes('$$') && icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="icon.includes('$$') && icon.split('$$')[0]"
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||
</template>
|
||||
<span
|
||||
class="relation-views-node-title"
|
||||
v-if="!isEditNodeName"
|
||||
:title="title"
|
||||
v-highlight="{ value: fullSearchValue, class: 'relation-views-node-title-highlight' }"
|
||||
>{{ title }}
|
||||
</span>
|
||||
<a-input
|
||||
ref="input"
|
||||
@blur="changeNodeName"
|
||||
@pressEnter="
|
||||
() => {
|
||||
$refs.input.blur()
|
||||
}
|
||||
"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="editNodeName"
|
||||
:style="{ marginLeft: '5px' }"
|
||||
/>
|
||||
<span class="relation-views-node-number">{{ number }}</span>
|
||||
<a-dropdown overlayClassName="relation-views-node-dropdown" :overlayStyle="{ width: '200px' }">
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-divider orientation="left">{{ $t('cmdb.relation') }}</a-divider>
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('add') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{
|
||||
$t('cmdb.serviceTree.deleteNode', { name: title })
|
||||
}}</a-menu-item
|
||||
>
|
||||
<a-divider orientation="left">{{ $t('cmdb.components.perm') }}</a-divider>
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="editNodeName"
|
||||
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item key="batch"><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.remove') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateCI } from '../../../api/ci.js'
|
||||
import highlight from '@/directive/highlight'
|
||||
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
directives: {
|
||||
highlight,
|
||||
},
|
||||
props: {
|
||||
treeNodeData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
levels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentViews: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
id2type: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
ciTypeIcons: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showBatchLevel: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
batchTreeKey: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
fullSearchValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchIcon: 'caret-right',
|
||||
isEditNodeName: false,
|
||||
editNodeName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
childLength() {
|
||||
return this.number
|
||||
},
|
||||
splitTreeKey() {
|
||||
return this.treeKey.split('@^@')
|
||||
},
|
||||
_tempTree() {
|
||||
return this.splitTreeKey[this.splitTreeKey.length - 1].split('%')
|
||||
},
|
||||
_typeIdIdx() {
|
||||
return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index
|
||||
},
|
||||
showDelete() {
|
||||
if (this._typeIdIdx === 0) {
|
||||
// 如果是第一层节点,则不能删除
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
menuList() {
|
||||
let _menuList = []
|
||||
if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) {
|
||||
// 不是叶子节点
|
||||
const id = Number(this.levels[this._typeIdIdx + 1])
|
||||
_menuList = [
|
||||
{
|
||||
id,
|
||||
alias: this.id2type[id].alias || this.id2type[id].name,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// 叶子节点
|
||||
_menuList = this.currentViews.node2show_types[this._tempTree[1]].map((item) => {
|
||||
return { id: item.id, alias: item.alias || item.name }
|
||||
})
|
||||
}
|
||||
return _menuList
|
||||
},
|
||||
icon() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
|
||||
},
|
||||
showCheckbox() {
|
||||
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
|
||||
},
|
||||
title() {
|
||||
return this.treeNodeData.title
|
||||
},
|
||||
number() {
|
||||
return this.treeNodeData.number
|
||||
},
|
||||
treeKey() {
|
||||
return this.treeNodeData.key
|
||||
},
|
||||
isLeaf() {
|
||||
return this.treeNodeData.isLeaf
|
||||
},
|
||||
showName() {
|
||||
return this.treeNodeData.showName
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
if (menuKey === 'editNodeName') {
|
||||
this.isEditNodeName = true
|
||||
this.editNodeName = this.title
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$emit('onContextMenuClick', treeKey, menuKey)
|
||||
},
|
||||
clickNode() {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right'
|
||||
},
|
||||
clickCheckbox() {
|
||||
this.$emit('clickCheckbox', this.treeKey)
|
||||
},
|
||||
changeNodeName(e) {
|
||||
const value = e.target.value
|
||||
if (value !== this.title) {
|
||||
const ci = this.treeKey
|
||||
.split('@^@')
|
||||
.slice(-1)[0]
|
||||
.split('%')
|
||||
const unique = Object.keys(JSON.parse(ci[2]))[0]
|
||||
const ciId = Number(ci[0])
|
||||
let editAttrName = unique
|
||||
if (this.showName) {
|
||||
editAttrName = this.showName
|
||||
}
|
||||
updateCI(ciId, { [editAttrName]: value }).then((res) => {
|
||||
this.$message.success(this.$t('updateSuccess'))
|
||||
this.$emit('updateTreeData', ciId, value)
|
||||
})
|
||||
}
|
||||
this.isEditNodeName = false
|
||||
this.editNodeName = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.relation-views-node {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.relation-views-node-switch {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
color: @text-color_5;
|
||||
i {
|
||||
opacity: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
.relation-views-node-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #d3d3d3;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
padding-left: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
color: @text-color_1;
|
||||
}
|
||||
.relation-views-node-number {
|
||||
color: @text-color_4;
|
||||
font-size: 12px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.relation-views-node-operation {
|
||||
opacity: 0;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.relation-views-node-checkbox {
|
||||
> span {
|
||||
.relation-views-node-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
width: calc(100% - 42px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-left .ant-tree:hover {
|
||||
.relation-views-node .relation-views-node-switch i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.relation-views-node-title-highlight {
|
||||
color: @func-color_1;
|
||||
}
|
||||
.relation-views-left {
|
||||
ul:has(.relation-views-node-checkbox) > li > ul {
|
||||
margin-left: 26px;
|
||||
}
|
||||
ul:has(.relation-views-node-checkbox) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.ant-tree-node-content-wrapper:hover {
|
||||
.relation-views-node-operation {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected,
|
||||
.ant-tree li .ant-tree-node-content-wrapper:hover {
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-node-dropdown {
|
||||
.ant-divider {
|
||||
margin: 0;
|
||||
.ant-divider-inner-text {
|
||||
font-size: 12px;
|
||||
color: @text-color_3;
|
||||
}
|
||||
}
|
||||
.ant-dropdown-menu-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'relation-views-node': true,
|
||||
'relation-views-node-checkbox': showCheckbox,
|
||||
}"
|
||||
@click="clickNode"
|
||||
>
|
||||
<span class="relation-views-node-switch">
|
||||
<a-icon v-if="!isLeaf" :type="switchIcon"></a-icon>
|
||||
</span>
|
||||
<span class="relation-views-node-content">
|
||||
<a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
|
||||
<template v-if="icon">
|
||||
<img
|
||||
v-if="icon.includes('$$') && icon.split('$$')[2]"
|
||||
:src="`/api/common-setting/v1/file/${icon.split('$$')[3]}`"
|
||||
:style="{ maxHeight: '14px', maxWidth: '14px' }"
|
||||
/>
|
||||
<ops-icon
|
||||
v-else-if="icon.includes('$$') && icon.split('$$')[0]"
|
||||
:style="{
|
||||
color: icon.split('$$')[1],
|
||||
fontSize: '14px',
|
||||
}"
|
||||
:type="icon.split('$$')[0]"
|
||||
/>
|
||||
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
|
||||
</template>
|
||||
<span
|
||||
class="relation-views-node-title"
|
||||
v-if="!isEditNodeName"
|
||||
:title="title"
|
||||
v-highlight="{ value: fullSearchValue, class: 'relation-views-node-title-highlight' }"
|
||||
>{{ title }}
|
||||
</span>
|
||||
<a-input
|
||||
ref="input"
|
||||
@blur="changeNodeName"
|
||||
@pressEnter="
|
||||
() => {
|
||||
$refs.input.blur()
|
||||
}
|
||||
"
|
||||
size="small"
|
||||
v-else
|
||||
v-model="editNodeName"
|
||||
:style="{ marginLeft: '5px' }"
|
||||
/>
|
||||
<span class="relation-views-node-number">{{ number }}</span>
|
||||
<a-dropdown overlayClassName="relation-views-node-dropdown" :overlayStyle="{ width: '200px' }">
|
||||
<a-menu slot="overlay" @click="({ key: menuKey }) => onContextMenuClick(this.treeKey, menuKey)">
|
||||
<template v-if="showBatchLevel === null">
|
||||
<a-divider orientation="left">{{ $t('cmdb.relation') }}</a-divider>
|
||||
<a-menu-item
|
||||
v-for="item in menuList"
|
||||
:key="item.id"
|
||||
><a-icon type="plus-circle" />{{ $t('add') }} {{ item.alias }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
v-if="showDelete"
|
||||
key="delete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{
|
||||
$t('cmdb.serviceTree.deleteNode', { name: title })
|
||||
}}</a-menu-item
|
||||
>
|
||||
<a-divider orientation="left">{{ $t('cmdb.components.perm') }}</a-divider>
|
||||
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
|
||||
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
|
||||
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item
|
||||
key="editNodeName"
|
||||
><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item key="batch"><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchGrant"
|
||||
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item
|
||||
>
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchRevoke"
|
||||
><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
<template v-if="showBatchLevel > 0">
|
||||
<a-menu-item
|
||||
:disabled="!batchTreeKey || !batchTreeKey.length"
|
||||
key="batchDelete"
|
||||
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.remove') }}</a-menu-item
|
||||
>
|
||||
<a-menu-divider />
|
||||
</template>
|
||||
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
<a-icon class="relation-views-node-operation" type="ellipsis" />
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateCI } from '../../../api/ci.js'
|
||||
import highlight from '@/directive/highlight'
|
||||
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
directives: {
|
||||
highlight,
|
||||
},
|
||||
props: {
|
||||
treeNodeData: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
levels: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
currentViews: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
id2type: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
ciTypeIcons: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
showBatchLevel: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
batchTreeKey: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
fullSearchValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
switchIcon: 'caret-right',
|
||||
isEditNodeName: false,
|
||||
editNodeName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
childLength() {
|
||||
return this.number
|
||||
},
|
||||
splitTreeKey() {
|
||||
return this.treeKey.split('@^@')
|
||||
},
|
||||
_tempTree() {
|
||||
return this.splitTreeKey[this.splitTreeKey.length - 1].split('%')
|
||||
},
|
||||
_typeIdIdx() {
|
||||
return this.levels.findIndex((level) => level[0] === Number(this._tempTree[1])) // 当前节点在levels中的index
|
||||
},
|
||||
showDelete() {
|
||||
if (this._typeIdIdx === 0) {
|
||||
// 如果是第一层节点,则不能删除
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
menuList() {
|
||||
let _menuList = []
|
||||
if (this._typeIdIdx > -1 && this._typeIdIdx < this.levels.length - 1) {
|
||||
// 不是叶子节点
|
||||
const id = Number(this.levels[this._typeIdIdx + 1])
|
||||
_menuList = [
|
||||
{
|
||||
id,
|
||||
alias: this.id2type[id].alias || this.id2type[id].name,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// 叶子节点
|
||||
_menuList = this.currentViews?.node2show_types?.[this._tempTree?.[1]]?.map?.((item) => {
|
||||
return { id: item.id, alias: item.alias || item.name }
|
||||
}) || []
|
||||
}
|
||||
return _menuList
|
||||
},
|
||||
icon() {
|
||||
const _split = this.treeKey.split('@^@')
|
||||
const currentNodeTypeId = _split[_split.length - 1].split('%')[1]
|
||||
return this.ciTypeIcons[Number(currentNodeTypeId)] ?? null
|
||||
},
|
||||
showCheckbox() {
|
||||
return this.showBatchLevel === this.treeKey.split('@^@').filter((item) => !!item).length - 1
|
||||
},
|
||||
title() {
|
||||
return this.treeNodeData.title
|
||||
},
|
||||
number() {
|
||||
return this.treeNodeData.number
|
||||
},
|
||||
treeKey() {
|
||||
return this.treeNodeData.key
|
||||
},
|
||||
isLeaf() {
|
||||
return this.treeNodeData.isLeaf
|
||||
},
|
||||
showName() {
|
||||
return this.treeNodeData.showName
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onContextMenuClick(treeKey, menuKey) {
|
||||
if (menuKey === 'editNodeName') {
|
||||
this.isEditNodeName = true
|
||||
this.editNodeName = this.title
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$emit('onContextMenuClick', treeKey, menuKey)
|
||||
},
|
||||
clickNode() {
|
||||
this.$emit('onNodeClick', this.treeKey)
|
||||
this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right'
|
||||
},
|
||||
clickCheckbox() {
|
||||
this.$emit('clickCheckbox', this.treeKey)
|
||||
},
|
||||
changeNodeName(e) {
|
||||
const value = e.target.value
|
||||
if (value !== this.title) {
|
||||
const ci = this.treeKey
|
||||
.split('@^@')
|
||||
.slice(-1)[0]
|
||||
.split('%')
|
||||
const unique = Object.keys(JSON.parse(ci[2]))[0]
|
||||
const ciId = Number(ci[0])
|
||||
let editAttrName = unique
|
||||
if (this.showName) {
|
||||
editAttrName = this.showName
|
||||
}
|
||||
updateCI(ciId, { [editAttrName]: value }).then((res) => {
|
||||
this.$message.success(this.$t('updateSuccess'))
|
||||
this.$emit('updateTreeData', ciId, value)
|
||||
})
|
||||
}
|
||||
this.isEditNodeName = false
|
||||
this.editNodeName = ''
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.relation-views-node {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.relation-views-node-switch {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
color: @text-color_5;
|
||||
i {
|
||||
opacity: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
.relation-views-node-content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
.relation-views-node-icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #d3d3d3;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
padding-left: 5px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
color: @text-color_1;
|
||||
}
|
||||
.relation-views-node-number {
|
||||
color: @text-color_4;
|
||||
font-size: 12px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.relation-views-node-operation {
|
||||
opacity: 0;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.relation-views-node-checkbox {
|
||||
> span {
|
||||
.relation-views-node-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.relation-views-node-title {
|
||||
width: calc(100% - 42px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-left .ant-tree:hover {
|
||||
.relation-views-node .relation-views-node-switch i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.relation-views-node-title-highlight {
|
||||
color: @func-color_1;
|
||||
}
|
||||
.relation-views-left {
|
||||
ul:has(.relation-views-node-checkbox) > li > ul {
|
||||
margin-left: 26px;
|
||||
}
|
||||
ul:has(.relation-views-node-checkbox) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.ant-tree-node-content-wrapper:hover {
|
||||
.relation-views-node-operation {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected,
|
||||
.ant-tree li .ant-tree-node-content-wrapper:hover {
|
||||
background-color: @primary-color_3;
|
||||
}
|
||||
}
|
||||
|
||||
.relation-views-node-dropdown {
|
||||
.ant-divider {
|
||||
margin: 0;
|
||||
.ant-divider-inner-text {
|
||||
font-size: 12px;
|
||||
color: @text-color_3;
|
||||
}
|
||||
}
|
||||
.ant-dropdown-menu-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue