feat:ui 全面升级 (#444)

This commit is contained in:
dagongren 2024-03-28 18:38:15 +08:00 committed by GitHub
parent bf6331d215
commit 2224ebd533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 2012 additions and 1149 deletions

View File

@ -30,9 +30,9 @@ export function getAuthDataEnable() {
}) })
} }
export function testLDAP(test_type, data) { export function testLDAP(data) {
return axios({ return axios({
url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`, url: `/common-setting/v1/auth_config/LDAP/test`,
method: 'post', method: 'post',
data, data,
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -178,7 +178,7 @@
<div v-else :style="{ width: '175px' }"></div> <div v-else :style="{ width: '175px' }"></div>
<template v-if="!disabled"> <template v-if="!disabled">
<a-tooltip :title="$t('copy')"> <a-tooltip :title="$t('copy')">
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a> <a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
</a-tooltip> </a-tooltip>
<a-tooltip :title="$t('delete')"> <a-tooltip :title="$t('delete')">
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> <a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>

View File

@ -759,6 +759,52 @@ export const multicolorIconList = [
value: 'caise-redis', value: 'caise-redis',
label: 'redis' label: 'redis'
}] }]
}, {
value: 'cloud',
label: '云',
list: [{
value: 'AWS',
label: 'AWS'
}, {
value: 'Azure',
label: 'Azure'
}, {
value: 'Google_Cloud_Platform',
label: 'Google Cloud Platform'
}, {
value: 'Alibaba_Cloud',
label: '阿里云'
}, {
value: 'Huawei_Cloud',
label: '华为云'
}, {
value: 'Tencent_Cloud',
label: '腾讯云'
}, {
value: 'UCloud',
label: 'UCloud'
}, {
value: 'Ctyun',
label: '天翼云'
}, {
value: 'ECloud',
label: '移动云'
}, {
value: 'JDCloud',
label: '京东云'
}, {
value: 'Bytecloud',
label: '字节云'
}, {
value: 'OpenStack',
label: 'OpenStack'
}, {
value: 'ZStack',
label: 'ZStack'
}, {
value: 'Nutanix',
label: 'Nutanix'
}]
}, { }, {
value: 'system', value: 'system',
label: '操作系统', label: '操作系统',

View File

@ -1,7 +1,7 @@
<template> <template>
<a-layout-sider <a-layout-sider
:class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]" :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]"
width="200px" width="220px"
:collapsible="collapsible" :collapsible="collapsible"
v-model="collapsed" v-model="collapsed"
:trigger="null" :trigger="null"
@ -15,6 +15,7 @@
@select="onSelect" @select="onSelect"
style="padding: 16px 0px;" style="padding: 16px 0px;"
></s-menu> ></s-menu>
<!-- <OpsDocs :collapsed="collapsed" /> -->
</a-layout-sider> </a-layout-sider>
</template> </template>
@ -22,10 +23,13 @@
import Logo from '@/components/tools/Logo' import Logo from '@/components/tools/Logo'
import SMenu from './index' import SMenu from './index'
import { mixin, mixinDevice } from '@/utils/mixin' import { mixin, mixinDevice } from '@/utils/mixin'
// import OpsDocs from '@/modules/docs/index.vue'
export default { export default {
name: 'SideMenu', name: 'SideMenu',
components: { Logo, SMenu }, components: { Logo, SMenu,
// OpsDocs
},
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
props: { props: {
mode: { mode: {

View File

@ -1,121 +1,49 @@
<template> <template>
<vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable"> <span>
<slot></slot> <ops-icon :type="getPropertyIcon(attr)" />
<template #empty> </span>
<slot name="empty">
<div :style="{ paddingTop: '10px' }">
<img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</slot>
</template>
<template #loading>
<slot name="loading"></slot>
</template>
</vxe-table>
</template> </template>
<script> <script>
import _ from 'lodash'
// 该组件使用方法与vxe-table一致但调用它的方法时需先调用getVxetableRef()获取到vxe-table实体
export default { export default {
name: 'OpsTable', name: 'ValueTypeIcon',
data() { props: {
return { attr: {
// isShifting: false, type: Object,
// lastIndex: -1, default: () => {},
lastSelected: [],
currentSelected: [],
}
},
computed: {
new$listeners() {
if (!Object.keys(this.$listeners).length) {
return this.$listeners
}
return Object.assign(this.$listeners, {
// 在这里覆盖原有的change事件
// 'checkbox-change': this.selectChangeEvent,
'checkbox-range-change': this.checkboxRangeChange,
'checkbox-range-start': this.checkboxRangeStart,
'checkbox-range-end': this.checkboxRangeEnd,
})
}, },
}, },
mounted() {
// window.onkeydown = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = true
// }
// }
// window.onkeyup = (e) => {
// if (e.key === 'Shift') {
// this.isShifting = false
// this.lastIndex = -1
// }
// }
},
beforeDestroy() {
// window.onkeydown = ''
// window.onkeyup = ''
},
methods: { methods: {
getVxetableRef() { getPropertyIcon(attr) {
return this.$refs.xTable switch (attr.value_type) {
}, case '0':
// selectChangeEvent(e) { return 'duose-shishu'
// const xTable = this.$refs.xTable case '1':
// const { lastIndex } = this return 'duose-fudianshu'
// const currentIndex = e.rowIndex case '2':
// const { tableData } = xTable.getTableData() if (attr.is_password) {
// if (lastIndex > -1 && this.isShifting) { return 'duose-password'
// let start = lastIndex }
// let end = currentIndex if (attr.is_link) {
// if (lastIndex > currentIndex) { return 'duose-link'
// start = currentIndex }
// end = lastIndex return 'duose-wenben'
// } case '3':
// const rangeData = tableData.slice(start, end + 1) return 'duose-datetime'
// xTable.setCheckboxRow(rangeData, true) case '4':
// } return 'duose-date'
// this.lastIndex = currentIndex case '5':
// this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() }) return 'duose-time'
// }, case '6':
checkboxRangeStart(e) { return 'duose-json'
const xTable = this.$refs.xTable case '7':
const lastSelected = xTable.getCheckboxRecords() return 'duose-password'
const selectedReserve = xTable.getCheckboxReserveRecords() case '8':
this.lastSelected = [...lastSelected, ...selectedReserve] return 'duose-link'
this.$emit('checkbox-range-start', e)
},
checkboxRangeChange(e) {
const xTable = this.$refs.xTable
xTable.setCheckboxRow(this.lastSelected, true)
this.currentSelected = e.records
// this.lastSelected = [...new Set([...this.lastSelected, ...e.records])]
this.$emit('checkbox-range-change', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
},
checkboxRangeEnd(e) {
const xTable = this.$refs.xTable
const isAllSelected = this.currentSelected.every((item) => {
const _idx = this.lastSelected.findIndex((ele) => _.isEqual(ele, item))
return _idx > -1
})
if (isAllSelected) {
xTable.setCheckboxRow(this.currentSelected, false)
} }
this.currentSelected = []
this.lastSelected = []
this.$emit('checkbox-range-end', {
...e,
records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()],
})
}, },
}, },
} }
</script> </script>
<style lang="less"></style> <style></style>

View File

@ -64,7 +64,7 @@ export default {
}, },
triggerColor: { triggerColor: {
type: String, type: String,
default: '#f0f2f5', default: '#f7f8fa',
}, },
}, },
data() { data() {

View File

@ -35,7 +35,7 @@ export default {
}, },
triggerColor: { triggerColor: {
type: String, type: String,
default: '#F0F5FF', default: '#f7f8fa',
}, },
}, },
data() { data() {
@ -52,22 +52,22 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.two-column-layout { .two-column-layout {
margin-bottom: -24px; margin-bottom: -24px;
width: 100%; width: 100%;
.two-column-layout-sidebar { .two-column-layout-sidebar {
height: 100%; height: 100%;
padding: 15px 7px;
border-radius: 15px; border-radius: 15px;
overflow-y: auto; overflow-y: auto;
background-color: #fff;
} }
.two-column-layout-main { .two-column-layout-main {
height: 100%; height: 100%;
padding: 12px; padding: 12px;
background-color: #fff; background-color: #fff;
overflow-y: auto; overflow-y: auto;
border-radius: 15px; border-radius: @border-radius-box;
} }
} }
</style> </style>

View File

@ -11,9 +11,9 @@
:key="route.name" :key="route.name"
@click="() => handleClick(route)" @click="() => handleClick(route)"
> >
{{ route.meta.title }} {{ $t(route.meta.title) }}
</span> </span>
<!-- <a-popover v-model="visible" placement="bottom" trigger="click" overlayClassName="top-menu-dropdown"> <a-popover v-model="visible" placement="bottom" trigger="click" overlayClassName="top-menu-dropdown">
<template slot="content"> <template slot="content">
<div class="title"> <div class="title">
更多应用 更多应用
@ -36,20 +36,31 @@
</div> </div>
</template> </template>
<span class="top-menu-icon"><gridSvg /></span> <span class="top-menu-icon"><gridSvg /></span>
</a-popover> --> </a-popover>
</div> </div>
</template> </template>
<script> <script>
import store from '@/store' import store from '@/store'
import { gridSvg, top_agent, top_acl } from '@/core/icons' import { gridSvg, top_agent, top_acl } from '@/core/icons'
import { getPreference } from '@/modules/cmdb/api/preference'
export default { export default {
name: 'TopMenu', name: 'TopMenu',
components: { gridSvg, top_agent, top_acl }, components: { gridSvg, top_agent, top_acl },
data() { data() {
return { return {
defaultShowRouteName: ['cmdb', 'acl'], defaultShowRouteName: [
defaultUnShowRouteName: [], 'dag',
'cmdb',
'itsm',
'ticket',
'monitor',
'calendar',
'datainsight',
'fullscreen',
'oneterm',
],
defaultUnShowRouteName: ['acl', 'agent'],
routes: store.getters.appRoutes.filter((i) => !(i.meta || {}).hiddenInTopMenu), routes: store.getters.appRoutes.filter((i) => !(i.meta || {}).hiddenInTopMenu),
current: store.getters.appRoutes[0].name, current: store.getters.appRoutes[0].name,
visible: false, visible: false,
@ -78,10 +89,20 @@ export default {
this.current = this.$route.matched[0].name this.current = this.$route.matched[0].name
}, },
methods: { methods: {
handleClick(route) { async handleClick(route) {
this.visible = false this.visible = false
if (route.name !== this.current) { if (route.name !== this.current) {
this.$router.push(route.redirect) if (route.name === 'cmdb') {
const preference = await getPreference()
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.some((item) => item.id === Number(lastTypeId))) {
this.$router.push(`/cmdb/instances/types/${lastTypeId}`)
} else {
this.$router.push('/cmdb/dashboard')
}
} else {
this.$router.push(route.redirect)
}
// this.current = route.name // this.current = route.name
} }
}, },
@ -110,33 +131,21 @@ export default {
line-height: @layout-header-icon-height; line-height: @layout-header-icon-height;
border-radius: 4px !important; border-radius: 4px !important;
display: inline-flex; display: inline-flex;
align-items: flex-end; align-items: center;
} }
> span { > span {
cursor: pointer; cursor: pointer;
padding: 4px 10px; padding: 4px 10px;
margin: 0 5px; margin: 0 5px;
border-radius: 4px;
color: @layout-header-font-color; color: @layout-header-font-color;
height: @layout-header-height; height: @layout-header-height;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
&:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color;
border-radius: 3px 3px 0px 0px;
}
} }
> span:hover,
.top-menu-selected { .top-menu-selected {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%); font-weight: bold;
color: @layout-header-font-selected-color; color: @layout-header-font-selected-color;
border-radius: 3px 3px 0px 0px;
border-bottom: 3px solid @layout-header-font-selected-color;
&:hover {
background: linear-gradient(0deg, rgba(0, 80, 201, 0.2) 0%, rgba(174, 207, 255, 0.06) 86.76%);
color: @layout-header-font-selected-color;
border-radius: 3px 3px 0px 0px;
}
} }
} }

View File

@ -87,21 +87,21 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
// 动态主路由 // 动态主路由
mainMenu: state => state.routes.appRoutes, mainMenu: (state) => state.routes.appRoutes,
}), }),
contentPaddingLeft() { contentPaddingLeft() {
if (!this.fixSidebar || this.isMobile()) { if (!this.fixSidebar || this.isMobile()) {
return '0' return '0'
} }
if (this.sidebarOpened) { if (this.sidebarOpened) {
return '200px' return '220px'
} }
return '80px' return '80px'
}, },
sideBarMenu() { sideBarMenu() {
const sideMenus = this.mainMenu.filter(item => this.$route.path.startsWith(item.path)) const sideMenus = this.mainMenu.filter((item) => this.$route.path.startsWith(item.path))
if (sideMenus.length > 1) { if (sideMenus.length > 1) {
return sideMenus.find(item => item.path !== '/').children return sideMenus.find((item) => item.path !== '/').children
} else { } else {
return sideMenus[0].children return sideMenus[0].children
} }
@ -110,6 +110,9 @@ export default {
provide() { provide() {
return { return {
reloadBoard: this.reload, reloadBoard: this.reload,
collapsed: () => {
return this.collapsed
},
} }
}, },
watch: { watch: {
@ -146,15 +149,6 @@ export default {
this.alive = true this.alive = true
}) })
}, },
paddingCalc() {
let left = ''
if (this.sidebarOpened) {
left = this.isDesktop() ? '200px' : '80px'
} else {
left = (this.isMobile() && '0') || (this.fixSidebar && '80px') || '0'
}
return left
},
menuSelect() { menuSelect() {
if (!this.isDesktop()) { if (!this.isDesktop()) {
this.collapsed = false this.collapsed = false

View File

@ -233,8 +233,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-history { .acl-history {
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;
padding: 24px; padding: 24px;

View File

@ -290,7 +290,6 @@ export default {
const str = ` ${key} : -> ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.description += str item.description += str
} else { } else {
const str = ` ${key} : ${oldVal} -> ${newVal} `
item.description += ` ${key} : ${oldVal} -> ${newVal} ` item.description += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }

View File

@ -241,7 +241,6 @@ export default {
const str = ` ${key} : -> ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} -> ${newVal} `
item.changeDescription += ` ${key} : ${oldVal} -> ${newVal} ` item.changeDescription += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }

View File

@ -32,8 +32,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-operation-history { .acl-operation-history {
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;
padding: 24px; padding: 24px;

View File

@ -189,8 +189,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-resource-types { .acl-resource-types {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -352,8 +352,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-resources { .acl-resources {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -285,8 +285,10 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.acl-roles { .acl-roles {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -88,10 +88,12 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.acl-secret-key { .acl-secret-key {
background-color: #fff; background-color: #fff;
padding: 24px; padding: 24px;
border-radius: 15px; border-radius: @border-radius-box;
height: calc(100% + 24px); height: calc(100% + 24px);
.ant-input[disabled] { .ant-input[disabled] {
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);

View File

@ -320,8 +320,10 @@ export default {
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-trigger { .acl-trigger {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -188,8 +188,10 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.acl-users { .acl-users {
border-radius: 15px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -71,6 +71,14 @@ export function subscribeRelationView(payload) {
}) })
} }
export function putRelationView(id, data) {
return axios({
url: `/v0.1/preference/relation/view/${id}`,
method: 'put',
data
})
}
// 用户保存条件过滤选项 // 用户保存条件过滤选项
export function getPreferenceSearch(payload) { export function getPreferenceSearch(payload) {
// 参数有prv_id: 关系视图的id ptv_id: 层级视图的id, type_id: 模型id // 参数有prv_id: 关系视图的id ptv_id: 层级视图的id, type_id: 模型id

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,5 +1,11 @@
<template> <template>
<a-modal width="800px" :visible="visible" @ok="handleOk" @cancel="handleCancel" :bodyStyle="{ padding: 0 }"> <a-modal
width="800px"
:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
:bodyStyle="{ padding: 0, paddingTop: '20px' }"
>
<GrantComp <GrantComp
:resourceType="resourceType" :resourceType="resourceType"
:app_id="app_id" :app_id="app_id"

View File

@ -11,7 +11,7 @@
@blur="handleInputConfirm" @blur="handleInputConfirm"
@keyup.enter="handleInputConfirm" @keyup.enter="handleInputConfirm"
/> />
<a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button> <a v-else @click="showInput"> {{ $t('cmdb.components.saveQuery') }}</a>
</span> </span>
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)"> <template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
<span <span
@ -178,10 +178,10 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.preference-search-tag { .preference-search-tag {
cursor: pointer; cursor: pointer;
border-radius: 5px; border-radius: 2px;
border: none; border: 1px solid #d9d9d9;
display: inline-block; display: inline-block;
padding: 0 7px; padding: 2px 7px;
margin-right: 8px; margin-right: 8px;
> span { > span {
margin-right: 4px; margin-right: 4px;

View File

@ -5,8 +5,15 @@
<a-space> <a-space>
<treeselect <treeselect
v-if="type === 'resourceSearch'" v-if="type === 'resourceSearch'"
class="custom-treeselect" class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }" :style="{
width: '200px',
marginRight: '10px',
'--custom-height': '32px',
'--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9',
'--custom-multiple-lineHeight': '16px',
}"
v-model="currenCiType" v-model="currenCiType"
:multiple="true" :multiple="true"
:clearable="true" :clearable="true"
@ -41,15 +48,14 @@
</treeselect> </treeselect>
<a-input <a-input
v-model="fuzzySearch" v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '200px' }"
:placeholder="$t('cmdb.components.pleaseSearch')" :placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh" @pressEnter="emitRefresh"
class="ops-input ops-input-radius"
> >
<a-icon <a-icon
type="search" type="search"
slot="suffix" slot="suffix"
:style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }" :style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }"
@click="emitRefresh" @click="emitRefresh"
/> />
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }"> <a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }">
@ -59,6 +65,9 @@
<a><a-icon type="question-circle"/></a> <a><a-icon type="question-circle"/></a>
</a-tooltip> </a-tooltip>
</a-input> </a-input>
<a-tooltip :title="$t('reset')">
<a-button @click="reset">重置</a-button>
</a-tooltip>
<FilterComp <FilterComp
ref="filterComp" ref="filterComp"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList" :canSearchPreferenceAttrList="canSearchPreferenceAttrList"
@ -69,7 +78,7 @@
<div slot="popover_item" class="search-form-bar-filter"> <div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" /> <a-icon class="search-form-bar-filter-icon" type="filter" />
{{ $t('cmdb.components.conditionFilter') }} {{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" /> <a-icon class="search-form-bar-filter-icon" type="down" :style="{ color: '#d9d9d9' }" />
</div> </div>
</FilterComp> </FilterComp>
<a-input <a-input
@ -91,14 +100,13 @@
:placeholder="placeholder" :placeholder="placeholder"
@keyup.enter="emitRefresh" @keyup.enter="emitRefresh"
> >
<a-icon slot="suffix" type="copy" @click="handleCopyExpression" /> <ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" />
</a-input> </a-input>
<slot></slot> <slot></slot>
</a-space> </a-space>
</div> </div>
<a-space> <a-space>
<slot name="extraContent"></slot> <slot name="extraContent"></slot>
<a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
<a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'"> <a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a <a
@click=" @click="
@ -237,7 +245,6 @@ export default {
} }
}, },
inputCiTypeGroup(value) { inputCiTypeGroup(value) {
console.log(value)
if (!value || !value.length) { if (!value || !value.length) {
this.$emit('updateAllAttributesList', value) this.$emit('updateAllAttributesList', value)
} }
@ -257,6 +264,7 @@ export default {
} }
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
@import '../../views/index.less'; @import '../../views/index.less';
.ci-searchform-expression { .ci-searchform-expression {
> input { > input {
@ -266,14 +274,14 @@ export default {
border-right: none; border-right: none;
&:hover, &:hover,
&:focus { &:focus {
border-bottom: 2px solid #2f54eb; border-bottom: 2px solid @primary-color;
} }
&:focus { &:focus {
box-shadow: 0 2px 2px -2px #1f78d133; box-shadow: 0 2px 2px -2px #1f78d133;
} }
} }
.ant-input-suffix { .ant-input-suffix {
color: #2f54eb; color: #d9d9d9;
cursor: pointer; cursor: pointer;
} }
} }
@ -290,14 +298,15 @@ export default {
@import '~@/style/static.less'; @import '~@/style/static.less';
.search-form-bar { .search-form-bar {
margin-bottom: 10px; margin-bottom: 20px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 32px;
.search-form-bar-filter { .search-form-bar-filter {
.ops_display_wrapper(); .ops_display_wrapper(transparent);
.search-form-bar-filter-icon { .search-form-bar-filter-icon {
color: #custom_colors[color_1]; color: @primary-color;
font-size: 12px; font-size: 12px;
} }
} }

View File

@ -1,6 +1,7 @@
const cmdb_en = { const cmdb_en = {
relation: 'Relation', relation: 'Relation',
attribute: 'Attributes', attribute: 'Attributes',
configTable: 'Config Table',
menu: { menu: {
views: 'Views', views: 'Views',
config: 'Configuration', config: 'Configuration',
@ -182,7 +183,11 @@ const cmdb_en = {
inheritType: 'Inherit Type', inheritType: 'Inherit Type',
inheritTypePlaceholder: 'Please select inherit types', inheritTypePlaceholder: 'Please select inherit types',
inheritFrom: 'inherit from {name}', inheritFrom: 'inherit from {name}',
groupInheritFrom: 'Please go to the {name} for modification' groupInheritFrom: 'Please go to the {name} for modification',
downloadType: 'Download CIType',
deleteCIType: 'Delete CIType',
otherGroupTips: 'Non sortable within the other group',
filterTips: 'click to show {name}'
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -245,8 +250,10 @@ const cmdb_en = {
unselectCIType: 'No CIType selected yet', unselectCIType: 'No CIType selected yet',
pleaseUploadFile: 'Please upload files', pleaseUploadFile: 'Please upload files',
batchUploadCanceled: 'Batch upload canceled', batchUploadCanceled: 'Batch upload canceled',
selectCITypeTips: 'Please select CIType', selectCIType: 'Select CIType',
selectCITypeTips: 'Please select a CIType and then download',
downloadTemplate: 'Download Template', downloadTemplate: 'Download Template',
clickDownload: 'Click to Download',
drawTips: 'Click or drag files here to upload!', drawTips: 'Click or drag files here to upload!',
supportFileTypes: 'Supported file types: xls, xlsx', supportFileTypes: 'Supported file types: xls, xlsx',
uploadResult: 'Upload results', uploadResult: 'Upload results',
@ -257,6 +264,16 @@ const cmdb_en = {
errorTips: 'Error message', errorTips: 'Error message',
requestFailedTips: 'An error occurred with the request, please try again later', requestFailedTips: 'An error occurred with the request, please try again later',
requestSuccessTips: 'Upload completed', requestSuccessTips: 'Upload completed',
uploadFile: 'Upload File',
drawTips1: 'Please <span class="cmdb-batch-upload-tips">select a CIType</span>, and then <span class="cmdb-batch-upload-tips">download</span> ,',
drawTips2: '<span class="cmdb-batch-upload-tips">click or drag file</span> to upload',
dataPreview: 'Preview data and upload',
tips1: 'Kind Reminder :',
tips2: '1. Click to download the template, and users can customize the header of the template file, including model properties and model associations',
// eslint-disable-next-line no-template-curly-in-string
tips3: '2. The red color in the template file represents the model relationship, such as the $Product. Product Name (${Model Name}. {Attribute Name}) column, which establishes the relationship between the physical machine and the product.',
tips4: '3. In the download template Excel file, the predefined values of attributes will be set as dropdown options. Please note that due to the limitations of Excel itself, a single dropdown box is limited to a maximum of 255 characters. If it exceeds 255 characters, we will not set the dropdown options for this attribute',
tips5: '4. When using Excel templates, please ensure that a single file does not exceed 5000 lines.',
}, },
preference: { preference: {
mySub: 'My Subscription', mySub: 'My Subscription',
@ -274,6 +291,7 @@ const cmdb_en = {
monthsAgo: 'month ago', monthsAgo: 'month ago',
yearsAgo: 'years ago', yearsAgo: 'years ago',
just: 'just now', just: 'just now',
searchPlaceholder: 'Please search CIType',
}, },
custom_dashboard: { custom_dashboard: {
charts: 'Chart', charts: 'Chart',
@ -313,14 +331,23 @@ const cmdb_en = {
noCustomDashboard: 'The administrator has not customized the dashboard yet', noCustomDashboard: 'The administrator has not customized the dashboard yet',
}, },
preference_relation: { preference_relation: {
newServiceTree: 'Add ServiceTree', newServiceTree: 'Add Service Tree',
editServiceTree: 'Edit Service Tree',
serviceTreeName: 'Name', serviceTreeName: 'Name',
serviceTreeNamePlaceholder: 'Please enter the service tree name',
public: 'Public', public: 'Public',
saveLayout: 'Save Layout', saveLayout: 'Save Layout',
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!', childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
tips1: 'Cannot form a view with the currently selected node, please select again!', tips1: 'Cannot form a view with the currently selected node, please select again!',
tips2: 'Please enter the new serviceTree name!', tips2: 'Please enter the new serviceTree name!',
tips3: 'Please select at least two nodes!', tips3: 'Please select at least two nodes!',
tips4: 'Select at least one in leaf node or tree node',
tips5: 'Select the tree directory node and display the service tree sub nodes as a Table',
showLeafNode: 'Show Leaf Node',
showTreeNode: 'Show Tree Node',
sort: 'Sort',
sort1: 'Leaf node information comes first',
sort2: 'Tree node information comes first'
}, },
history: { history: {
ciChange: 'CI', ciChange: 'CI',
@ -478,13 +505,15 @@ const cmdb_en = {
noPermission: 'No Permission' noPermission: 'No Permission'
}, },
serviceTree: { serviceTree: {
deleteNode: 'Delete Node', deleteNode: 'Delete {name}',
tips1: 'For example: q=os_version:centos&sort=os_version', tips1: 'For example: q=os_version:centos&sort=os_version',
tips2: 'Expression search', tips2: 'Expression search',
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!', alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
copyFailed: 'Copy failed', copyFailed: 'Copy failed',
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?', deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
batch: 'Batch', batch: 'Batch',
editNode: 'Edit Node',
editNodeName: 'Edit Node Name',
grantTitle: 'Grant(read)', grantTitle: 'Grant(read)',
userPlaceholder: 'Please select users', userPlaceholder: 'Please select users',
rolePlaceholder: 'Please select roles', rolePlaceholder: 'Please select roles',

View File

@ -1,6 +1,7 @@
const cmdb_zh = { const cmdb_zh = {
relation: '关系', relation: '关系',
attribute: '属性', attribute: '属性',
configTable: '配置表格',
menu: { menu: {
views: '视图', views: '视图',
config: '配置', config: '配置',
@ -35,7 +36,7 @@ const cmdb_zh = {
attributeLibray: '属性库', attributeLibray: '属性库',
addCITypeInGroup: '在该组中新增CI模型', addCITypeInGroup: '在该组中新增CI模型',
addCIType: '新增CI模型', addCIType: '新增CI模型',
editGroupName: '编辑组名称', editGroupName: '重命名分组',
deleteGroup: '删除该组', deleteGroup: '删除该组',
CITypeName: '模型名(英文)', CITypeName: '模型名(英文)',
English: '英文', English: '英文',
@ -182,7 +183,11 @@ const cmdb_zh = {
inheritType: '继承模型', inheritType: '继承模型',
inheritTypePlaceholder: '请选择继承模型(多选)', inheritTypePlaceholder: '请选择继承模型(多选)',
inheritFrom: '属性继承自{name}', inheritFrom: '属性继承自{name}',
groupInheritFrom: '请至{name}进行修改' groupInheritFrom: '请至{name}进行修改',
downloadType: '下载模型',
deleteCIType: '删除模型',
otherGroupTips: '其他分组属性不可排序',
filterTips: '点击可仅查看{name}属性'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -220,7 +225,7 @@ const cmdb_zh = {
beforeChange: '变更前', beforeChange: '变更前',
afterChange: '变更后', afterChange: '变更后',
noticeContentTips: '请输入通知内容', noticeContentTips: '请输入通知内容',
saveQuery: '保存筛选条件', saveQuery: '保存条件',
pleaseSearch: '请查找', pleaseSearch: '请查找',
conditionFilter: '条件过滤', conditionFilter: '条件过滤',
attributeDesc: '属性说明', attributeDesc: '属性说明',
@ -245,9 +250,10 @@ const cmdb_zh = {
unselectCIType: '尚未选择模板类型', unselectCIType: '尚未选择模板类型',
pleaseUploadFile: '请上传文件', pleaseUploadFile: '请上传文件',
batchUploadCanceled: '批量上传已取消', batchUploadCanceled: '批量上传已取消',
selectCITypeTips: '请选择模板类型', selectCIType: '选择模型',
selectCITypeTips: '请选择模型后下载模板',
downloadTemplate: '下载模板', downloadTemplate: '下载模板',
drawTips: '点击或拖拽文件至此上传!', clickDownload: '点击下载',
supportFileTypes: '支持文件类型xlsxlsx', supportFileTypes: '支持文件类型xlsxlsx',
uploadResult: '上传结果', uploadResult: '上传结果',
total: '共', total: '共',
@ -257,6 +263,16 @@ const cmdb_zh = {
errorTips: '错误信息', errorTips: '错误信息',
requestFailedTips: '请求出现错误,请稍后再试', requestFailedTips: '请求出现错误,请稍后再试',
requestSuccessTips: '批量上传已完成', requestSuccessTips: '批量上传已完成',
uploadFile: '文件上传',
drawTips1: '请先<span class="cmdb-batch-upload-tips">选择模型</span><span class="cmdb-batch-upload-tips">下载模板</span>后',
drawTips2: '<span class="cmdb-batch-upload-tips">点击或拖拽文件</span>至此上传',
dataPreview: '数据预览并导入',
tips1: '温馨提示:',
tips2: '1. 点击下载模板,用户可以自定义模板文件的表头,包括模型属性、模型关联',
// eslint-disable-next-line no-template-curly-in-string
tips3: '2. 模板文件中红色为模型关系,如$产品.产品名(${模型名}.{属性名})这一列就可建立物理机和产品之间的关系',
tips4: '3. 下载模板excel文件中会将属性的预定义值置为下拉选项请注意受excel本身的限制单个下拉框限制了最多255个字符如果超过255个字符我们不会设置该属性的下拉选项',
tips5: '4. 在使用excel模板时请确保单个文件不超过5000行',
}, },
preference: { preference: {
mySub: '我的订阅', mySub: '我的订阅',
@ -274,6 +290,7 @@ const cmdb_zh = {
monthsAgo: '月前', monthsAgo: '月前',
yearsAgo: '年前', yearsAgo: '年前',
just: '刚刚', just: '刚刚',
searchPlaceholder: '请搜索模型',
}, },
custom_dashboard: { custom_dashboard: {
charts: '图表', charts: '图表',
@ -314,13 +331,23 @@ const cmdb_zh = {
}, },
preference_relation: { preference_relation: {
newServiceTree: '新增服务树', newServiceTree: '新增服务树',
editServiceTree: '编辑服务树',
serviceTreeName: '服务树名', serviceTreeName: '服务树名',
serviceTreeNamePlaceholder: '请输入服务树名',
public: '公开', public: '公开',
saveLayout: '保存布局', saveLayout: '保存布局',
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!', childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
tips1: '不能与当前选中节点形成视图,请重新选择!', tips1: '不能与当前选中节点形成视图,请重新选择!',
tips2: '请输入新增服务树名!', tips2: '请输入新增服务树名!',
tips3: '请选择至少两个节点!', tips3: '请选择至少两个节点!',
tips4: '叶子节点/树节点信息至少展示一个',
tips5: '选中树目录节点服务树子节点展示成Table',
showLeafNode: '树的子节点展示成Table',
showTreeNode: '展示树节点信息',
sort: '顺序',
sort1: '树子节点信息在前',
sort2: '树节点信息在前'
}, },
history: { history: {
ciChange: 'CI变更', ciChange: 'CI变更',
@ -443,7 +470,7 @@ const cmdb_zh = {
disk: '硬盘', disk: '硬盘',
}, },
ci: { ci: {
attributeDesc: '属性说明', attributeDesc: '查看属性配置',
selectRows: '选取:{rows} 项', selectRows: '选取:{rows} 项',
addRelation: '添加关系', addRelation: '添加关系',
all: '全部', all: '全部',
@ -477,13 +504,15 @@ const cmdb_zh = {
noPermission: '暂无权限' noPermission: '暂无权限'
}, },
serviceTree: { serviceTree: {
deleteNode: '删除节点', deleteNode: '移除 {name}',
tips1: '例q=os_version:centos&sort=os_version', tips1: '例q=os_version:centos&sort=os_version',
tips2: '表达式搜索', tips2: '表达式搜索',
alert1: '管理员 还未配置业务关系, 或者你无权限访问!', alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
copyFailed: '复制失败', copyFailed: '复制失败',
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?', deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
batch: '批量操作', batch: '批量操作',
editNode: '编辑节点',
editNodeName: '修改节点名',
grantTitle: '授权(查看权限)', grantTitle: '授权(查看权限)',
userPlaceholder: '请选择用户', userPlaceholder: '请选择用户',
rolePlaceholder: '请选择角色', rolePlaceholder: '请选择角色',

View File

@ -156,8 +156,8 @@ const genCmdbRoutes = async () => {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) { if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) {
routes.redirect = `/cmdb/instances/types/${lastTypeId}` routes.redirect = `/cmdb/instances/types/${lastTypeId}`
} else if (routes.children[2].children.length > 0) { } else if (routes.children[3]?.children?.length > 0) {
routes.redirect = routes.children[2].children.find(item => !item.hidden).path routes.redirect = routes.children[3]?.children.find(item => !item.hidden)?.path
} else { } else {
routes.redirect = '/cmdb/dashboard' routes.redirect = '/cmdb/dashboard'
} }

View File

@ -49,7 +49,6 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc']) const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
const columns = [] const columns = []
for (let attr of _attrList) { for (let attr of _attrList) {
const editRender = { name: 'input' } const editRender = { name: 'input' }
switch (attr.value_type) { switch (attr.value_type) {
case '0': case '0':
@ -85,7 +84,7 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
} }
columns.push({ columns.push({
attr_id:attr.id, attr_id: attr.id,
editRender, editRender,
title: attr.alias || attr.name, title: attr.alias || attr.name,
field: attr.name, field: attr.name,
@ -135,6 +134,35 @@ export const getPropertyStyle = (attr) => {
} }
} }
export const getPropertyIcon = (attr) => {
switch (attr.value_type) {
case '0':
return 'duose-shishu'
case '1':
return 'duose-fudianshu'
case '2':
if (attr.is_password) {
return 'duose-password'
}
if (attr.is_link) {
return 'duose-link'
}
return 'duose-wenben'
case '3':
return 'duose-datetime'
case '4':
return 'duose-date'
case '5':
return 'duose-time'
case '6':
return 'duose-json'
case '7':
return 'duose-password'
case '8':
return 'duose-link'
}
}
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => { export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc']) const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
if (!_tempData.length) { if (!_tempData.length) {
@ -185,7 +213,7 @@ export const getAllParentNodesLabel = (node, label) => {
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`) return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
} }
return label return label
} }
export const getTreeSelectLabel = (node) => { export const getTreeSelectLabel = (node) => {
return `${getAllParentNodesLabel(node, node.label)}` return `${getAllParentNodesLabel(node, node.label)}`
} }

View File

@ -1,39 +1,41 @@
<template> <template>
<div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }"> <div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }">
<div id="title"> <div class="cmdb-views-header">
<ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" /> <span>
<span class="cmdb-views-header-title">{{ $t('cmdb.menu.batchUpload') }}</span>
</span>
</div> </div>
<a-row> <CiTypeChoice ref="ciTypeChoice" @getCiTypeAttr="showCiType" />
<a-col :span="12"> <p class="cmdb-batch-upload-label"><span>*</span>3. {{ $t('cmdb.batch.uploadFile') }}</p>
<upload-file-form <UploadFileForm
:isUploading="isUploading" :isUploading="isUploading"
:ciType="ciType" :ciType="ciType"
ref="uploadFileForm" ref="uploadFileForm"
@uploadDone="uploadDone" @uploadDone="uploadDone"
></upload-file-form> ></UploadFileForm>
</a-col> <p class="cmdb-batch-upload-label">4. {{ $t('cmdb.batch.dataPreview') }}</p>
<a-col :span="24" v-if="ciType && uploadData.length"> <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> <div class="cmdb-batch-upload-action">
<div class="cmdb-batch-upload-action"> <a-space size="large">
<a-space size="large"> <a-button :disabled="!(ciType && uploadData.length)" @click="handleUpload" type="primary">{{
<a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button> $t('upload')
<a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button> }}</a-button>
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button> <a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
</a-space> <a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{
</div> $t('cmdb.batch.downloadFailed')
</a-col> }}</a-button>
<a-col :span="24" v-if="ciType"> </a-space>
<upload-result </div>
ref="uploadResult" <UploadResult
:upLoadData="uploadData" v-if="ciType"
:ciType="ciType" ref="uploadResult"
:unique-field="uniqueField" :upLoadData="uploadData"
:isUploading="isUploading" :ciType="ciType"
@uploadResultDone="uploadResultDone" :unique-field="uniqueField"
@uploadResultError="uploadResultError" :isUploading="isUploading"
></upload-result> @uploadResultDone="uploadResultDone"
</a-col> @uploadResultError="uploadResultError"
</a-row> ></UploadResult>
</div> </div>
</template> </template>
@ -124,7 +126,7 @@ export default {
handleCancel() { handleCancel() {
if (!this.isUploading) { if (!this.isUploading) {
this.showCiType(null) this.showCiType(null)
this.$refs.ciTypeChoice.selectNum = null this.$refs.ciTypeChoice.selectNum = undefined
this.hasError = false this.hasError = false
} else { } else {
this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled')) this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
@ -144,16 +146,29 @@ export default {
}, },
} }
</script> </script>
<style lang="less">
@import '~@/style/static.less';
@import '../index.less';
.cmdb-batch-upload-label {
color: @text-color_1;
font-weight: bold;
white-space: pre;
> span {
color: red;
}
}
</style>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload { .cmdb-batch-upload {
margin-bottom: -24px; margin-bottom: -24px;
padding: 24px; padding: 20px;
background-color: #fff; background-color: #fff;
border-radius: 20px; border-radius: @border-radius-box;
overflow: auto; overflow: auto;
.cmdb-batch-upload-action { .cmdb-batch-upload-action {
width: 50%; width: 50%;
text-align: center;
margin: 12px 0; margin: 12px 0;
} }
} }

View File

@ -1,11 +1,11 @@
<template> <template>
<a-space> <div>
<span>{{ $t('cmdb.ciType.ciType') }}: </span> <p class="cmdb-batch-upload-label"><span>*</span>1. {{ $t('cmdb.batch.selectCIType') }}</p>
<a-select <a-select
showSearch showSearch
:placeholder="$t('cmdb.batch.selectCITypeTips')" :placeholder="$t('cmdb.batch.selectCITypeTips')"
@change="selectCiType" @change="selectCiType"
:style="{ width: '300px' }" :style="{ width: '50%', marginBottom: '1em' }"
class="ops-select" class="ops-select"
:filter-option="filterOption" :filter-option="filterOption"
v-model="selectNum" v-model="selectNum"
@ -14,13 +14,16 @@
ciType.alias ciType.alias
}}</a-select-option> }}</a-select-option>
</a-select> </a-select>
<p class="cmdb-batch-upload-label">&nbsp;&nbsp;2. {{ $t('cmdb.batch.downloadTemplate') }}</p>
<a-button <a-button
:style="{ marginBottom: '1em' }"
@click="openModal" @click="openModal"
:disabled="!selectNum" :disabled="!selectNum"
type="primary" type="primary"
class="ops-button-primary" ghost
class="ops-button-ghost"
icon="download" icon="download"
>{{ $t('cmdb.batch.downloadTemplate') }}</a-button >{{ $t('cmdb.batch.clickDownload') }}</a-button
> >
<a-modal <a-modal
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
@ -88,7 +91,7 @@
</a-row> </a-row>
</template> </template>
</a-modal> </a-modal>
</a-space> </div>
</template> </template>
<script> <script>
@ -107,7 +110,7 @@ export default {
return { return {
ciTypeList: [], ciTypeList: [],
ciTypeName: '', ciTypeName: '',
selectNum: null, selectNum: undefined,
selectCiTypeAttrList: [], selectCiTypeAttrList: [],
visible: false, visible: false,
checkedAttrs: [], checkedAttrs: [],
@ -238,6 +241,7 @@ export default {
for (let row = 2; row < 5000; row++) { for (let row = 2; row < 5000; row++) {
Object.keys(choice_value_obj).forEach((key) => { Object.keys(choice_value_obj).forEach((key) => {
const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"` const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"`
console.log(formulae)
if (formulae.length <= 255) { if (formulae.length <= 255) {
ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = { ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = {
type: 'list', type: 'list',

View File

@ -1,13 +1,14 @@
<template> <template>
<div class="cmdb-batch-upload-table"> <div class="cmdb-batch-upload-table">
<vxe-table <vxe-table
v-if="uploadData && uploadData.length"
ref="xTable" ref="xTable"
stripe stripe
show-header-overflow show-header-overflow
show-overflow="" show-overflow=""
size="small" size="small"
class="ops-stripe-table" class="ops-stripe-table"
:max-height="200" height="auto"
:data="dataSource" :data="dataSource"
resizable resizable
:row-style="rowStyle" :row-style="rowStyle"
@ -21,6 +22,19 @@
:min-width="100" :min-width="100"
></vxe-column> ></vxe-column>
</vxe-table> </vxe-table>
<a-empty
v-else
:image-style="{
height: '80px',
marginTop: '10px',
}"
>
<img slot="image" :src="require('@/assets/data_empty.png')" />
<template slot="description">
<p>{{ $t('noData') }}</p>
<p>{{ $t('cmdb.batch.pleaseUploadFile') }}</p>
</template>
</a-empty>
</div> </div>
</template> </template>
@ -99,7 +113,21 @@ export default {
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload-table { .cmdb-batch-upload-table {
overflow: auto; height: 200px;
padding: 20px;
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
.ant-empty-description {
p:last-child {
color: @primary-color;
}
}
} }
</style> </style>

View File

@ -9,13 +9,21 @@
:fileList="fileList" :fileList="fileList"
:disabled="!ciType || isUploading" :disabled="!ciType || isUploading"
> >
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> <ops-icon type="itsm-folder" />
<p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p>
<p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p> <p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
<p v-html="$t('cmdb.batch.drawTips1')"></p>
<p v-html="$t('cmdb.batch.drawTips2')"></p>
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>
<a-progress :status="progressStatus" :percent="percent" />
</div>
</a-upload-dragger> </a-upload-dragger>
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file"> <div class="cmdb-batch-upload-tips">
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span> <p>{{ $t('cmdb.batch.tips1') }}</p>
<a-progress :status="progressStatus" :percent="percent" /> <div>{{ $t('cmdb.batch.tips2') }}</div>
<div>{{ $t('cmdb.batch.tips3') }}</div>
<div>{{ $t('cmdb.batch.tips4') }}</div>
<div>{{ $t('cmdb.batch.tips5') }}</div>
</div> </div>
</div> </div>
</template> </template>
@ -46,15 +54,13 @@ export default {
}, },
watch: { watch: {
ciType: { ciType: {
handler(newValue) { handler() {
if (!newValue) { this.ciItemNum = 0
this.ciItemNum = 0 this.fileList = []
this.fileList = [] this.dataList = []
this.dataList = [] this.progressStatus = 'active'
this.progressStatus = 'active' this.percent = 0
this.percent = 0 this.$emit('uploadDone', this.dataList)
this.$emit('uploadDone', this.dataList)
}
}, },
}, },
}, },
@ -77,12 +83,28 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less';
.cmdb-batch-upload-dragger { .cmdb-batch-upload-dragger {
height: 220px; height: auto;
margin: 16px 0; margin: 16px 0;
.ant-upload p {
margin-bottom: 5px;
}
.ant-upload.ant-upload-drag { .ant-upload.ant-upload-drag {
background: rgba(240, 245, 255, 0.35);
border: none; border: none;
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
.ant-upload-drag-container > i {
font-size: 60px;
}
.cmdb-batch-upload-tips {
color: @primary-color;
}
} }
.ant-upload.ant-upload-drag .ant-upload-drag-container { .ant-upload.ant-upload-drag .ant-upload-drag-container {
vertical-align: baseline; vertical-align: baseline;
@ -90,23 +112,37 @@ export default {
} }
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.cmdb-batch-upload-dragger { .cmdb-batch-upload-dragger {
position: relative; position: relative;
display: flex;
> span {
display: inline-block;
width: 50%;
}
.cmdb-batch-upload-dragger-file { .cmdb-batch-upload-dragger-file {
background-color: #fff; background-color: @primary-color_7;
box-shadow: 0px 2px 5px rgba(78, 94, 160, 0.2); border-radius: 2px;
border-radius: 4px;
position: absolute;
width: 80%; width: 80%;
left: 50%;
bottom: 24px;
padding: 2px 8px; padding: 2px 8px;
transform: translate(-50%);
display: inline-flex; display: inline-flex;
> span { > span {
white-space: nowrap; white-space: nowrap;
margin-right: 10px; margin-right: 10px;
} }
} }
.cmdb-batch-upload-tips {
width: 50%;
padding-left: 20px;
color: @text-color_3;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
p:first-child {
color: @text-color_1;
}
}
} }
</style> </style>

View File

@ -1,10 +1,11 @@
<template> <template>
<div class="cmdb-batch-upload-result" v-if="visible"> <div class="cmdb-batch-upload-result" v-if="visible">
<h3 class="cmdb-batch-upload-result-title">{{ $t('cmdb.batch.uploadResult') }}</h3> <p class="cmdb-batch-upload-label">5. {{ $t('cmdb.batch.uploadResult') }}</p>
<div class="cmdb-batch-upload-result-content"> <div class="cmdb-batch-upload-result-content">
<h4> <h4>
{{ $t('cmdb.batch.total') }}&nbsp;<span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }} {{ $t('cmdb.batch.total') }}&nbsp;<span style="color: blue">{{ total }}</span>
<span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }} {{ $t('cmdb.batch.successItems') }} <span style="color: lightgreen">{{ success }}</span>
{{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
</h4> </h4>
<div> <div>
<span>{{ $t('cmdb.batch.errorTips') }}: </span> <span>{{ $t('cmdb.batch.errorTips') }}: </span>
@ -39,7 +40,7 @@ export default {
default: false, default: false,
}, },
}, },
data: function() { data() {
return { return {
visible: false, visible: false,
complete: 0, complete: 0,
@ -54,6 +55,13 @@ export default {
return this.upLoadData.length || 0 return this.upLoadData.length || 0
}, },
}, },
watch: {
ciType: {
handler() {
this.visible = false
},
},
},
methods: { methods: {
async upload2Server() { async upload2Server() {
this.visible = true this.visible = true
@ -96,10 +104,6 @@ export default {
@import '~@/style/static.less'; @import '~@/style/static.less';
.cmdb-batch-upload-result { .cmdb-batch-upload-result {
.cmdb-batch-upload-result-title {
border-left: 4px solid #custom_colors[color_1];
padding-left: 10px;
}
.cmdb-batch-upload-result-content { .cmdb-batch-upload-result-content {
background-color: rgba(240, 245, 255, 0.35); background-color: rgba(240, 245, 255, 0.35);
border-radius: 5px; border-radius: 5px;

View File

@ -15,21 +15,35 @@
</span> </span>
</span> </span>
<a-space> <a-space>
<a-button size="small" icon="plus" type="primary" @click="$refs.create.handleOpen(true, 'create')"> <a-button
type="primary"
class="ops-button-ghost"
ghost
@click="$refs.create.handleOpen(true, 'create')"
><ops-icon type="veops-increase" />
{{ $t('create') }} {{ $t('create') }}
</a-button> </a-button>
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button> <EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs">
<a-popconfirm <a-button
:title=" type="primary"
$t('cmdb.preference.confirmcancelSub2', { name: `${this.$route.meta.title || this.$route.meta.name}` }) ghost
" class="ops-button-ghost"
:ok-text="$t('confirm')" ><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
:cancel-text="$t('cancel')" >
@confirm="unsubscribe" </EditAttrsPopover>
placement="bottomRight" <a-dropdown v-model="visible">
> <a-button type="primary" ghost class="ops-button-ghost">···</a-button>
<a-button size="small" icon="star" type="primary" ghost>{{ $t('cmdb.preference.cancelSub') }}</a-button> <a-menu slot="overlay" @click="handleMenuClick">
</a-popconfirm> <a-menu-item @click="handlePerm" key="grant">
<a-icon type="user-add" />
{{ $t('grant') }}
</a-menu-item>
<a-menu-item key="cancelSub" @click="unsubscribe">
<a-icon type="star" />
{{ $t('cmdb.preference.cancelSub') }}
</a-menu-item>
</a-menu>
</a-dropdown>
</a-space> </a-space>
</div> </div>
<div class="cmdb-ci-main"> <div class="cmdb-ci-main">
@ -86,7 +100,6 @@
:scroll-y="{ enabled: true, gt: 20 }" :scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
class="ops-unstripe-table" class="ops-unstripe-table"
:style="{ margin: '0 -12px' }"
:custom-config="{ storage: true }" :custom-config="{ storage: true }"
> >
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column> <vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
@ -219,11 +232,9 @@
</template> </template>
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-column align="left" field="operate" fixed="right" width="120"> <vxe-column align="left" field="operate" fixed="right" width="80">
<template #header> <template #header>
<span>{{ $t('operation') }}</span> <span>{{ $t('operation') }}</span>
<EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" />
<!-- <a-icon class="operation-icon" type="control" /> -->
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a-space> <a-space>
@ -342,7 +353,7 @@ export default {
// if (this.selectedRowKeys && this.selectedRowKeys.length) { // if (this.selectedRowKeys && this.selectedRowKeys.length) {
// return this.windowHeight - 246 // return this.windowHeight - 246
// } // }
return this.windowHeight - 210 return this.windowHeight - 240
}, },
}, },
data() { data() {
@ -377,6 +388,7 @@ export default {
passwordValue: {}, passwordValue: {},
lastEditCiId: null, lastEditCiId: null,
isContinueCloseEdit: true, isContinueCloseEdit: true,
visible: false,
} }
}, },
watch: { watch: {
@ -916,15 +928,24 @@ export default {
}) })
}, },
unsubscribe(ciType, type = 'all') { unsubscribe(ciType, type = 'all') {
const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')] const that = this
Promise.all(promises).then(() => { this.$confirm({
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined title: that.$t('warning'),
if (Number(ciType) === Number(lastTypeId)) { content: that.$t('cmdb.preference.confirmcancelSub2', {
localStorage.setItem('ops_ci_typeid', '') name: `${that.$route.meta.title || that.$route.meta.name}`,
} }),
this.$message.success(this.$t('cmdb.preference.cancelSubSuccess')) onOk() {
this.resetRoute() const promises = [subscribeCIType(that.typeId, ''), subscribeTreeView(that.typeId, '')]
this.$router.push('/cmdb/preference') Promise.all(promises).then(() => {
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined
if (Number(ciType) === Number(lastTypeId)) {
localStorage.setItem('ops_ci_typeid', '')
}
that.$message.success(that.$t('cmdb.preference.cancelSubSuccess'))
that.resetRoute()
that.$router.push('/cmdb/preference')
})
},
}) })
}, },
resetRoute() { resetRoute() {
@ -957,6 +978,11 @@ export default {
} }
}) })
}, },
handleMenuClick(e) {
if (e.key === 'grant') {
this.visible = false
}
},
}, },
} }
</script> </script>
@ -968,11 +994,11 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.cmdb-ci { .cmdb-ci {
background-color: #fff;
padding: 20px;
border-radius: @border-radius-box;
height: calc(100vh - 64px);
overflow: auto;
margin-bottom: -24px; margin-bottom: -24px;
.cmdb-ci-main {
background-color: #fff;
border-radius: 15px;
padding: 12px;
}
} }
</style> </style>

View File

@ -106,9 +106,9 @@
<a-date-picker <a-date-picker
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
style="width: 100%" style="width: 100%"
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'" v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }" :showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
/> />
<a-input <a-input
v-if="getFieldType(list.name) === 'input'" v-if="getFieldType(list.name) === 'input'"
@ -373,7 +373,7 @@ export default {
} else if (_find.value_type === '0' || _find.value_type === '1') { } else if (_find.value_type === '0' || _find.value_type === '1') {
return 'input_number' return 'input_number'
} else if (_find.value_type === '4' || _find.value_type === '3') { } else if (_find.value_type === '4' || _find.value_type === '3') {
return this.valueTypeMap[_find.value_type] return _find.value_type
} else { } else {
return 'input' return 'input'
} }

View File

@ -11,9 +11,11 @@
@setFixedList="setFixedList" @setFixedList="setFixedList"
/> />
</template> </template>
<div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }"> <slot>
<a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" /> <div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }">
</div> <a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" />
</div>
</slot>
</a-popover> </a-popover>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="attr-ad" :style="{ height: `${windowHeight - 104}px` }"> <div class="attr-ad" :style="{ height: `${windowHeight - 130}px` }">
<div v-if="adCITypeList && adCITypeList.length"> <div v-if="adCITypeList && adCITypeList.length">
<a-tabs size="small" v-model="currentTab"> <a-tabs size="small" v-model="currentTab">
<a-tab-pane v-for="item in adCITypeList" :key="item.id"> <a-tab-pane v-for="item in adCITypeList" :key="item.id">
@ -207,7 +207,7 @@ export default {
@import '~@/style/static.less'; @import '~@/style/static.less';
.attr-ad { .attr-ad {
position: relative; position: relative;
padding: 0 12px; padding: 0 20px;
.attr-ad-header { .attr-ad-header {
width: 100%; width: 100%;
display: inline-flex; display: inline-flex;

View File

@ -1,5 +1,5 @@
<template> <template>
<div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }"> <div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }">
<a <a
v-if="!adrIsInner" v-if="!adrIsInner"
:style="{ position: 'absolute', right: 0, top: 0 }" :style="{ position: 'absolute', right: 0, top: 0 }"

View File

@ -1,71 +1,83 @@
<template> <template>
<div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }"> <div
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''"> @click="
<div class="attribute-card-content"> () => {
<div if (isAdd) {
:class="{ 'attribute-card-value-type-icon': true, handle: !inherited }" $emit('add')
:style="{ ...getPropertyStyle(property) }" }
> }
<ValueTypeIcon :attr="property" /> "
</div> :class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }"
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }"> >
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }"> <div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div>
{{ property.alias || property.name }} <template v-if="!isAdd">
<a-tooltip :title="inherited ? $t('cmdb.ciType.inheritFrom', { name: property.inherited_from }) : ''">
<div class="attribute-card-content">
<div :class="{ 'attribute-card-value-type-icon': true, handle: !inherited }">
<ValueTypeIcon :attr="property" />
</div>
<div :class="{ 'attribute-card-content-inner': true, 'attribute-card-name-required': property.is_required }">
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
{{ property.alias || property.name }}
</div>
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
</div>
<div
class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore"
>
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a>
</div> </div>
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div>
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
</div> </div>
<div </a-tooltip>
class="attribute-card-trigger"
v-if="(property.value_type === '3' || property.value_type === '4') && !isStore" <div class="attribute-card-footer">
<a-popover
trigger="click"
:arrowPointAtCenter="true"
placement="bottom"
overlayClassName="attribute-card-footer-popover"
> >
<a @click="openTrigger"><ops-icon type="ops-trigger"/></a> <div slot="content">
</div> <h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }">
</div> <span>{{ property.alias }}({{ property.name }})</span>
</a-tooltip> </h3>
<a-descriptions layout="horizontal" bordered size="small" :column="2">
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
<ops-icon
:style="{ color: property[item.property] ? '#7f97fa' : '', fontSize: '10px' }"
:type="`ops-${item.property}-disabled`"
/>
</a-descriptions-item>
<a-descriptions-item label></a-descriptions-item>
</a-descriptions>
</div>
<a-space :style="{ cursor: 'pointer' }">
<ops-icon
v-for="item in propertyList.filter((p) => property[p.property])"
:key="item.property"
:style="{ color: '#7f97fa', fontSize: '10px' }"
:type="`ops-${item.property}-disabled`"
/>
</a-space>
</a-popover>
<div class="attribute-card-footer"> <a-space class="attribute-card-operation" v-if="!inherited">
<a-popover <a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a>
trigger="click" <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')">
:arrowPointAtCenter="true" <a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a>
placement="bottom" </a-tooltip>
overlayClassName="attribute-card-footer-popover" <a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
>
<div slot="content">
<h3 :style="{ textAlign: 'center', paddingTop: '0.5em' }">
<span>{{ property.alias }}({{ property.name }})</span>
</h3>
<a-descriptions layout="horizontal" bordered size="small" :column="2">
<a-descriptions-item v-for="item in propertyList" :key="item.property" :label="item.label">
<components
:is="`ops_${item.property}`"
v-if="property[item.property]"
:style="{ width: '1em', height: '1em' }"
/>
<ops-icon v-else :type="`ops-${item.property}-disabled`" />
</a-descriptions-item>
<a-descriptions-item label></a-descriptions-item>
</a-descriptions>
</div>
<a-space :style="{ cursor: 'pointer' }">
<components
v-for="item in propertyList.filter((p) => property[p.property])"
:key="item.property"
:is="`ops_${item.property}`"
/>
</a-space> </a-space>
</a-popover> </div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
<a-space class="attribute-card-operation" v-if="!inherited"> </template>
<a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> <template v-else>
<a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')"> <a><a-icon type="plus"/></a>
<a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> <div>{{ $t('cmdb.ciType.addAttribute') }}</div>
</a-tooltip> </template>
<a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a>
</a-space>
</div>
<TriggerForm ref="triggerForm" :CITypeId="CITypeId" />
</div> </div>
</template> </template>
@ -82,10 +94,15 @@ import {
ops_is_unique, ops_is_unique,
} from '@/core/icons' } from '@/core/icons'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import { getPropertyStyle } from '../../utils/helper'
import TriggerForm from './triggerForm.vue' import TriggerForm from './triggerForm.vue'
export default { export default {
name: 'AttributeCard', name: 'AttributeCard',
inject: {
unique: {
from: 'unique',
default: () => undefined,
},
},
components: { components: {
ValueTypeIcon, ValueTypeIcon,
TriggerForm, TriggerForm,
@ -114,11 +131,21 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
isAdd: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return {} return {}
}, },
computed: { computed: {
isUnique() {
if (this.unique) {
return this.property?.name === this.unique()
}
return false
},
valueTypeMap() { valueTypeMap() {
return valueTypeMap() return valueTypeMap()
}, },
@ -147,11 +174,10 @@ export default {
] ]
}, },
inherited() { inherited() {
return this.property.inherited || false return this.property?.inherited || false
}, },
}, },
methods: { methods: {
getPropertyStyle,
handleEdit() { handleEdit() {
this.$emit('edit') this.$emit('edit')
}, },
@ -196,11 +222,12 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.attribute-card { .attribute-card {
width: 182px; width: 182px;
height: 80px; height: 80px;
background: #f8faff; background: @primary-color_6;
border-radius: 5px; border-radius: 2px;
position: relative; position: relative;
margin-bottom: 16px; margin-bottom: 16px;
transition: all 0.3s; transition: all 0.3s;
@ -219,7 +246,7 @@ export default {
.attribute-card-value-type-icon { .attribute-card-value-type-icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
font-size: 12px; font-size: 16px;
background: #ffffff !important; background: #ffffff !important;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
border-radius: 2px; border-radius: 2px;
@ -243,7 +270,7 @@ export default {
white-space: nowrap; white-space: nowrap;
} }
.attribute-card-name-default-show { .attribute-card-name-default-show {
color: #2f54eb; color: @primary-color;
} }
.attribute-card_value-type { .attribute-card_value-type {
font-size: 10px; font-size: 10px;
@ -270,20 +297,67 @@ export default {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%); background: @primary-color_5;
border-radius: 0px 0px 5px 5px; border-radius: 0px 0px 2px 2px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
border-top: 1px solid @primary-color_3;
.attribute-card-operation { .attribute-card-operation {
visibility: hidden; visibility: hidden;
} }
} }
.attribute-card-uniqueKey {
position: absolute;
right: -12px;
top: 0;
color: @func-color_2;
background: url('../../assets/unique_card.png');
font-size: 10px;
z-index: 1;
background-size: 100% 100%;
background-repeat: no-repeat;
min-width: 55px;
padding: 2px 0 2px 5px;
}
} }
.attribute-card-inherited { .attribute-card-inherited {
background: #f3f4f7; background: @primary-color_7;
.attribute-card-footer { .attribute-card-footer {
background: #eaedf3; background: @text-color_7;
}
}
.attribute-card-add {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
background-color: inherit;
&:hover {
box-shadow: none;
background-color: @primary-color_6;
}
&:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1;
background: linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(90deg, @text-color_5 50%, transparent 0) repeat-x,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y,
linear-gradient(0deg, @text-color_5 50%, transparent 0) repeat-y;
background-size: 15px 1px, 15px 1px, 1px 15px, 1px 15px;
background-position: 0 0, 0 100%, 0 0, 100% 0;
}
div {
color: @text-color_4;
font-size: 12px;
} }
} }
</style> </style>

View File

@ -172,8 +172,6 @@ export default {
margin-right: 60px; margin-right: 60px;
.ant-input-group.ant-input-group-compact > *:first-child, .ant-input-group.ant-input-group-compact > *:first-child,
.ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection { .ant-input-group.ant-input-group-compact > .ant-select:first-child > .ant-select-selection {
border-top-left-radius: 20px !important;
border-bottom-left-radius: 20px !important;
background-color: #custom_colors[color_1]; background-color: #custom_colors[color_1];
color: #fff; color: #fff;
border: none; border: none;
@ -202,7 +200,6 @@ export default {
.ant-input { .ant-input {
background-color: #f0f5ff; background-color: #f0f5ff;
border: none; border: none;
border-radius: 20px;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }

View File

@ -12,14 +12,28 @@
</a-form-item> </a-form-item>
</span> </span>
</a-modal> </a-modal>
<div class="ci-types-attributes" :style="{ maxHeight: `${windowHeight - 104}px` }"> <div class="ci-types-attributes" :style="{ height: `${windowHeight - 130}px` }">
<a-space style="margin-bottom: 10px"> <a-space style="margin-bottom: 10px">
<a-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{ <a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</a-button>
$t('cmdb.ciType.group') <a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
}}</a-button> <div>
<a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{ <a-tooltip
$t('cmdb.ciType.uniqueConstraint') v-for="type in Object.keys(valueTypeMap)"
}}</a-button> :key="type"
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[type] })"
>
<span
@click="handleFilterType(type)"
:class="{
'ci-types-attributes-filter': true,
'ci-types-attributes-filter-selected': attrTypeFilter.includes(type),
}"
>
<ops-icon :type="getPropertyIcon({ value_type: type })" />
{{ valueTypeMap[type] }}
</span>
</a-tooltip>
</div>
</a-space> </a-space>
<div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups"> <div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups">
<div> <div>
@ -29,13 +43,6 @@
> >
<span style="font-weight:700">{{ CITypeGroup.name }}</span> <span style="font-weight:700">{{ CITypeGroup.name }}</span>
<span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span> <span style="color: #c3cdd7;margin:0 5px;">({{ CITypeGroup.attributes.length }})</span>
<a v-if="!CITypeGroup.inherited" @click="handleEditGroupName(index, CITypeGroup)">
<a-icon type="edit" />
</a>
<a v-else :style="{ cursor: 'not-allowed', color: 'gray' }">
<a-icon type="edit" />
</a>
</div> </div>
<template v-else> <template v-else>
<span> <span>
@ -64,21 +71,24 @@
@click="handleMoveGroup(index, index + 1)" @click="handleMoveGroup(index, index + 1)"
/></a> /></a>
</a-tooltip> </a-tooltip>
<a-dropdown>
<a-tooltip> <a><ops-icon type="veops-more"/></a>
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template> <a-menu slot="overlay">
<a><a-icon type="plus" @click="handleAddGroupAttr(index)"/></a> <a-menu-item @click="handleAddGroupAttr(index)">
</a-tooltip> <template slot="title"></template>
<a-tooltip> <a-icon type="plus" />
<template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template> {{ $t('cmdb.ciType.addAttribute') }}
<a </a-menu-item>
:style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }" <a-menu-item @click="handleEditGroupName(index, CITypeGroup)" :disabled="CITypeGroup.inherited">
:disabled="CITypeGroup.inherited" <a-icon type="edit" />
><a-icon {{ $t('cmdb.ciType.editGroupName') }}
type="delete" </a-menu-item>
@click="handleDeleteGroup(CITypeGroup)" <a-menu-item @click="handleDeleteGroup(CITypeGroup)" :disabled="CITypeGroup.inherited">
/></a> <a-icon type="delete" />
</a-tooltip> {{ $t('cmdb.ciType.deleteGroup') }}
</a-menu-item>
</a-menu>
</a-dropdown>
</a-space> </a-space>
</div> </div>
<div class="ci-types-attributes-wrapper"> <div class="ci-types-attributes-wrapper">
@ -98,7 +108,9 @@
handle=".handle" handle=".handle"
> >
<AttributeCard <AttributeCard
v-for="item in CITypeGroup.attributes" v-for="item in CITypeGroup.attributes.filter(
(attr) => !attrTypeFilter.length || (attrTypeFilter.length && attrTypeFilter.includes(attr.value_type))
)"
:key="item.id" :key="item.id"
@edit="handleEditProperty(item)" @edit="handleEditProperty(item)"
:property="item" :property="item"
@ -106,6 +118,7 @@
:CITypeId="CITypeId" :CITypeId="CITypeId"
:attributes="attributes" :attributes="attributes"
/> />
<AttributeCard isAdd @add="handleAddGroupAttr(index)" />
<i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i>
</draggable> </draggable>
</div> </div>
@ -114,10 +127,11 @@
<div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }"> <div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }">
<span style="font-weight:700">{{ $t('other') }}</span> <span style="font-weight:700">{{ $t('other') }}</span>
<span style="color: #c3cdd7;margin-left:5px;">({{ otherGroupAttributes.length }})</span> <span style="color: #c3cdd7;margin-left:5px;">({{ otherGroupAttributes.length }})</span>
<span style="color: #c3cdd7;margin-left:5px;font-size:10px;">{{ $t('cmdb.ciType.otherGroupTips') }}</span>
</div> </div>
<div style="float: right"> <div style="float: right">
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template> <template slot="title">{{ $t('cmdb.ciType.addAttribute') }}</template>
<a @click="handleAddGroupAttr(undefined)"><a-icon type="plus"/></a> <a @click="handleAddGroupAttr(undefined)"><a-icon type="plus"/></a>
</a-tooltip> </a-tooltip>
</div> </div>
@ -138,7 +152,9 @@
handle=".handle" handle=".handle"
> >
<AttributeCard <AttributeCard
v-for="item in otherGroupAttributes" v-for="item in otherGroupAttributes.filter(
(attr) => !attrTypeFilter.length || (attrTypeFilter.length && attrTypeFilter.includes(attr.value_type))
)"
:key="item.id" :key="item.id"
@edit="handleEditProperty(item)" @edit="handleEditProperty(item)"
:property="item" :property="item"
@ -146,6 +162,7 @@
:CITypeId="CITypeId" :CITypeId="CITypeId"
:attributes="attributes" :attributes="attributes"
/> />
<AttributeCard isAdd @add="handleAddGroupAttr(undefined)" />
<i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i>
</draggable> </draggable>
</div> </div>
@ -185,6 +202,8 @@ import AttributeCard from './attributeCard.vue'
import AttributeEditForm from './attributeEditForm.vue' import AttributeEditForm from './attributeEditForm.vue'
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue' import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
import UniqueConstraint from './uniqueConstraint.vue' import UniqueConstraint from './uniqueConstraint.vue'
import { valueTypeMap } from '../../utils/const'
import { getPropertyIcon } from '../../utils/helper'
export default { export default {
name: 'AttributesTable', name: 'AttributesTable',
@ -214,6 +233,8 @@ export default {
otherGroupAttributes: [], otherGroupAttributes: [],
addGroupModal: false, addGroupModal: false,
newGroupName: '', newGroupName: '',
attrTypeFilter: [],
unique: '',
} }
}, },
computed: { computed: {
@ -223,9 +244,17 @@ export default {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
valueTypeMap() {
return valueTypeMap()
},
}, },
provide() { provide() {
return { refresh: this.getCITypeGroupData } return {
refresh: this.getCITypeGroupData,
unique: () => {
return this.unique
},
}
}, },
beforeCreate() {}, beforeCreate() {},
created() {}, created() {},
@ -233,6 +262,7 @@ export default {
this.getCITypeGroupData() this.getCITypeGroupData()
}, },
methods: { methods: {
getPropertyIcon,
handleEditProperty(property) { handleEditProperty(property) {
this.$refs.attributeEditForm.handleEdit(property, this.attributes) this.$refs.attributeEditForm.handleEdit(property, this.attributes)
}, },
@ -260,6 +290,7 @@ export default {
group.editable = false group.editable = false
group.originOrder = group.order group.originOrder = group.order
group.originName = group.name group.originName = group.name
// group.attributes = group.attributes.sort((a, b) => a.order - b.order)
}) })
this.otherGroupAttributes = this.attributes this.otherGroupAttributes = this.attributes
@ -282,6 +313,7 @@ export default {
Promise.all(promises).then((values) => { Promise.all(promises).then((values) => {
console.log(values) console.log(values)
this.attributes = values[0].attributes this.attributes = values[0].attributes
this.unique = values[0].unique
const temp = {} const temp = {}
this.attributes.forEach((attr) => { this.attributes.forEach((attr) => {
temp[attr.id] = attr temp[attr.id] = attr
@ -387,6 +419,7 @@ export default {
group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id)) group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id))
} }
}) })
// this.CITypeGroups = this.CITypeGroups
this.otherGroupAttributes.forEach((attributes) => { this.otherGroupAttributes.forEach((attributes) => {
if (values.groupId === null) { if (values.groupId === null) {
@ -523,20 +556,40 @@ export default {
handleOpenUniqueConstraint() { handleOpenUniqueConstraint() {
this.$refs.uniqueConstraint.open(this.attributes) this.$refs.uniqueConstraint.open(this.attributes)
}, },
handleFilterType(type) {
const _idx = this.attrTypeFilter.findIndex((item) => item === type)
if (_idx > -1) {
this.attrTypeFilter.splice(_idx, 1)
} else {
this.attrTypeFilter.push(type)
}
},
}, },
watch: {},
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.fold { .fold {
width: calc(100% - 216px); width: calc(100% - 216px);
display: inline-block; display: inline-block;
} }
.ci-types-attributes { .ci-types-attributes {
padding: 16px 24px 24px; padding: 0 20px;
overflow-y: auto; overflow-y: auto;
.ci-types-attributes-filter {
color: @text-color_4;
cursor: pointer;
padding: 3px 8px;
white-space: nowrap;
margin-right: 5px;
}
.ci-types-attributes-filter:hover,
.ci-types-attributes-filter-selected {
background-color: @primary-color_5;
}
.property-item-empty { .property-item-empty {
color: #40a9ff; color: #40a9ff;
width: calc(100% - 20px); width: calc(100% - 20px);

View File

@ -1,6 +1,6 @@
<template> <template>
<a-card :bordered="false" :bodyStyle="{ padding: '0' }"> <a-card :bordered="false" :bodyStyle="{ padding: '0' }">
<a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab" type="card"> <a-tabs :activeKey="activeKey" @change="changeTab" class="ops-tab">
<a-tab-pane key="1" :tab="$t('cmdb.ciType.attributes')"> <a-tab-pane key="1" :tab="$t('cmdb.ciType.attributes')">
<AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable> <AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable>
</a-tab-pane> </a-tab-pane>
@ -16,8 +16,10 @@
<a-tab-pane key="5" :tab="$t('cmdb.ciType.relationAD')"> <a-tab-pane key="5" :tab="$t('cmdb.ciType.relationAD')">
<RelationAD :CITypeId="CITypeId"></RelationAD> <RelationAD :CITypeId="CITypeId"></RelationAD>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="6" :tab="$t('cmdb.ciType.grant')"> <a-tab-pane key="6" :tab="$t('cmdb.components.relationGrant')">
<GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp> <GrantComp :CITypeId="CITypeId" resourceType="CIType" :resourceTypeName="CITypeName"></GrantComp>
<div class="citype-detail-title">{{ $t('cmdb.ciType.relation') }}</div>
<RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-card> </a-card>
@ -74,4 +76,13 @@ export default {
} }
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped>
@import '~@/style/static.less';
.citype-detail-title {
border-left: 4px solid @primary-color;
padding-left: 10px;
margin-left: 20px;
margin-bottom: 10px;
}
</style>

View File

@ -8,11 +8,10 @@
</div> </div>
<SplitPane <SplitPane
v-else v-else
:min="280" :min="220"
:max="500" :max="500"
:paneLengthPixel.sync="paneLengthPixel" :paneLengthPixel.sync="paneLengthPixel"
appName="cmdb-ci-types" appName="cmdb-ci-types"
triggerColor="#F0F5FF"
:triggerLength="18" :triggerLength="18"
> >
<template #one> <template #one>
@ -22,22 +21,23 @@
:disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')" :disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')"
type="primary" type="primary"
size="small" size="small"
icon="plus" ghost
@click="handleClickAddGroup" @click="handleClickAddGroup"
class="ops-button-primary" class="ops-button-ghost"
>{{ $t('cmdb.ciType.group') }}</a-button ><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button
> >
<a-space> <a-space>
<a <span
:style="{ cursor: 'pointer' }"
@click=" @click="
() => { () => {
$refs.attributeStore.open() $refs.attributeStore.open()
} }
" "
>{{ $t('cmdb.ciType.attributeLibray') }}</a >{{ $t('cmdb.ciType.attributeLibray') }}
> </span>
<a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"> <a-dropdown v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')">
<a><ops-icon type="ops-menu"/></a> <ops-icon type="ops-menu" :style="{ cursor: 'pointer' }" />
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item key="0"> <a-menu-item key="0">
<a-upload <a-upload
@ -83,7 +83,7 @@
<a-space> <a-space>
<a-tooltip> <a-tooltip>
<template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template> <template slot="title">{{ $t('cmdb.ciType.addCITypeInGroup') }}</template>
<a><a-icon type="plus" @click="handleCreate(g)"/></a> <a><ops-icon type="veops-increase" @click="handleCreate(g)"/></a>
</a-tooltip> </a-tooltip>
<template v-if="g.id !== -1"> <template v-if="g.id !== -1">
<a-tooltip> <a-tooltip>
@ -113,7 +113,7 @@
> >
<div> <div>
<OpsMoveIcon <OpsMoveIcon
style="width: 17px; height: 17px; display: none; position: absolute; left: 15px; top: 5px" style="width: 17px; height: 17px; display: none; position: absolute; left: -1px; top: 8px"
/> />
<span class="ci-types-left-detail-icon"> <span class="ci-types-left-detail-icon">
<template v-if="ci.icon"> <template v-if="ci.icon">
@ -134,18 +134,33 @@
</span> </span>
</div> </div>
<span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span> <span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span>
<a-space class="ci-types-left-detail-action"> <a-dropdown :getPopupContainer="(trigger) => trigger">
<a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a> <a class="ci-types-left-detail-action">
<a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a> <ops-icon type="veops-more" />
<a
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
:disabled="ci.inherited"
@click="(e) => handleDownloadCiType(e, ci)"
>
<a-icon type="download" />
</a> </a>
<a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a> <a-menu slot="overlay">
</a-space> <a-menu-item @click="(e) => handlePerm(e, ci)">
<a-icon type="user-add" />
{{ $t('grant') }}
</a-menu-item>
<a-menu-item @click="(e) => handleEdit(e, ci)">
<a-icon type="edit" />
{{ $t('cmdb.ciType.editCIType') }}
</a-menu-item>
<a-menu-item
v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')"
:disabled="ci.inherited"
@click="(e) => handleDownloadCiType(e, ci)"
>
<a-icon type="download" />
{{ $t('cmdb.ciType.downloadType') }}
</a-menu-item>
<a-menu-item @click="(e) => handleDelete(e, ci)">
<a-icon type="delete" />
{{ $t('cmdb.ciType.deleteCIType') }}
</a-menu-item>
</a-menu>
</a-dropdown>
</div> </div>
</draggable> </draggable>
</div> </div>
@ -270,17 +285,7 @@
</div> </div>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item :help="$t('cmdb.ciType.uniqueKeyTips')" :label="$t('cmdb.ciType.uniqueKey')">
<template slot="label">
<a-tooltip :title="$t('cmdb.ciType.uniqueKeyTips')">
<a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;"
type="question-circle"
theme="filled"
/>
</a-tooltip>
<span>{{ $t('cmdb.ciType.uniqueKey') }}</span>
</template>
<el-select <el-select
size="small" size="small"
filterable filterable
@ -456,7 +461,6 @@ export default {
}, },
currentCName() { currentCName() {
if (this.currentId) { if (this.currentId) {
console.log(this.currentId)
if (this.currentId.split('%')[2] !== 'null') { if (this.currentId.split('%')[2] !== 'null') {
return this.currentId.split('%')[2] return this.currentId.split('%')[2]
} }
@ -817,8 +821,8 @@ export default {
}) })
}, },
handleDelete(e, record) { handleDelete(e, record) {
e.preventDefault() e.domEvent.preventDefault()
e.stopPropagation() e.domEvent.stopPropagation()
const that = this const that = this
this.$confirm({ this.$confirm({
title: that.$t('warning'), title: that.$t('warning'),
@ -833,8 +837,8 @@ export default {
}) })
}, },
handleDownloadCiType(e, ci) { handleDownloadCiType(e, ci) {
e.preventDefault() e.domEvent.preventDefault()
e.stopPropagation() e.domEvent.stopPropagation()
const x = new XMLHttpRequest() const x = new XMLHttpRequest()
x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true) x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true)
x.responseType = 'blob' x.responseType = 'blob'
@ -855,8 +859,8 @@ export default {
}) })
}, },
async handleEdit(e, record) { async handleEdit(e, record) {
e.preventDefault() e.domEvent.preventDefault()
e.stopPropagation() e.domEvent.stopPropagation()
this.drawerTitle = this.$t('cmdb.ciType.editCIType') this.drawerTitle = this.$t('cmdb.ciType.editCIType')
this.drawerVisible = true this.drawerVisible = true
await getCITypeAttributesById(record.id).then((res) => { await getCITypeAttributesById(record.id).then((res) => {
@ -909,8 +913,8 @@ export default {
} }
}, },
handlePerm(e, ci) { handlePerm(e, ci) {
e.preventDefault() e.domEvent.preventDefault()
e.stopPropagation() e.domEvent.stopPropagation()
roleHasPermissionToGrant({ roleHasPermissionToGrant({
app_id: 'cmdb', app_id: 'cmdb',
resource_type_name: 'CIType', resource_type_name: 'CIType',
@ -942,6 +946,8 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.ci-types-wrap { .ci-types-wrap {
margin: 0 0 -24px 0; margin: 0 0 -24px 0;
.ci-types-empty { .ci-types-empty {
@ -955,21 +961,24 @@ export default {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
float: left; float: left;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
.ci-types-left-content { .ci-types-left-content {
max-height: calc(100% - 45px); max-height: calc(100% - 45px);
overflow: auto; overflow: hidden;
&:hover {
overflow: auto;
}
} }
.ci-types-left-title { .ci-types-left-title {
padding: 10px 15px; padding: 10px 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
color: @text-color_3;
} }
.ci-types-left-group { .ci-types-left-group {
position: relative; position: relative;
padding: 8px 15px; padding: 8px 0 8px 14px;
color: rgb(99, 99, 99); color: rgb(99, 99, 99);
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
@ -982,7 +991,7 @@ export default {
display: none; display: none;
} }
&:hover { &:hover {
background-color: #e1efff; background-color: @primary-color_3;
> div:nth-child(2) { > div:nth-child(2) {
display: inline-flex; display: inline-flex;
} }
@ -992,17 +1001,25 @@ export default {
} }
} }
.ci-types-left-detail { .ci-types-left-detail {
padding: 3px 14px 3px 36px; padding: 3px 14px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: center;
margin-bottom: 4px; margin-bottom: 4px;
height: 32px;
line-height: 32px;
.ci-types-left-detail-action { .ci-types-left-detail-action {
display: none; display: none;
margin-left: auto; margin-left: auto;
} }
.ci-types-left-detail-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ci-types-left-detail-icon { .ci-types-left-detail-icon {
display: flex; display: flex;
align-items: center; align-items: center;
@ -1019,7 +1036,7 @@ export default {
} }
} }
&:hover { &:hover {
background-color: #e1efff; background-color: @primary-color_3;
svg { svg {
display: inline !important; display: inline !important;
} }
@ -1029,7 +1046,7 @@ export default {
} }
} }
.selected { .selected {
background-color: #e1efff; background-color: @primary-color_3;
.ci-types-left-detail-title { .ci-types-left-detail-title {
font-weight: 700; font-weight: 700;
} }
@ -1038,6 +1055,7 @@ export default {
.ci-types-right { .ci-types-right {
width: 100%; width: 100%;
position: relative; position: relative;
background-color: #fff;
.ci-types-right-empty { .ci-types-right-empty {
position: absolute; position: absolute;
text-align: center; text-align: center;
@ -1049,7 +1067,6 @@ export default {
.ci-types-left, .ci-types-left,
.ci-types-right { .ci-types-right {
height: 100%; height: 100%;
background-color: #fff;
} }
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="relation-ad" :style="{ height: `${windowHeight - 104}px` }"> <div class="relation-ad" :style="{ height: `${windowHeight - 130}px` }">
<div class="relation-ad-item" v-for="item in relationList" :key="item.id"> <div class="relation-ad-item" v-for="item in relationList" :key="item.id">
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
@ -253,7 +253,7 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.relation-ad { .relation-ad {
overflow: auto; overflow: auto;
padding: 24px; padding: 0 20px;
.relation-ad-item { .relation-ad-item {
display: inline-flex; display: inline-flex;
justify-content: flex-start; justify-content: flex-start;

View File

@ -1,6 +1,7 @@
<template> <template>
<div :style="{ padding: '16px 24px 24px' }"> <div :style="{ padding: '0 20px 20px' }">
<a-button <a-button
v-if="!isInGrantComp"
style="margin-bottom: 10px" style="margin-bottom: 10px"
@click="handleCreate" @click="handleCreate"
type="primary" type="primary"
@ -43,7 +44,7 @@
<template #default="{row}"> <template #default="{row}">
<a-space v-if="!row.isParent && row.source_ci_type_id"> <a-space v-if="!row.isParent && row.source_ci_type_id">
<a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a> <a @click="handleOpenGrant(row)"><a-icon type="user-add"/></a>
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)"> <a-popconfirm v-if="!isInGrantComp" :title="$t('cmdb.ciType.confirmDelete2')" @confirm="handleDelete(row)">
<a style="color: red;"><a-icon type="delete"/></a> <a style="color: red;"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
@ -148,6 +149,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
isInGrantComp: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -178,7 +183,9 @@ export default {
async mounted() { async mounted() {
this.getCITypes() this.getCITypes()
this.getRelationTypes() this.getRelationTypes()
await this.getCITypeParent() if (!this.isInGrantComp) {
await this.getCITypeParent()
}
this.getData() this.getData()
}, },
methods: { methods: {

View File

@ -134,6 +134,6 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.ci-types-triggers { .ci-types-triggers {
padding: 16px 24px 24px; padding: 0 20px 20px;
} }
</style> </style>

View File

@ -165,7 +165,7 @@
: '#fafafa', : '#fafafa',
}" }"
> >
<div :style="{ color: fontColor }">{{ form.name }}</div> <div v-if="chartType === 'count'" :style="{ color: fontColor }">{{ form.name }}</div>
<Chart <Chart
:ref="`chart_${item.id}`" :ref="`chart_${item.id}`"
:chartId="item.id" :chartId="item.id"
@ -613,6 +613,7 @@ export default {
} else { } else {
this.form.category = 1 this.form.category = 1
} }
console.log(this.chartType)
}, },
showPreview() { showPreview() {
this.$refs.chartForm.validate(async (valid) => { this.$refs.chartForm.validate(async (valid) => {

View File

@ -15,7 +15,8 @@
@click="openChartForm('add', { options: { w: 3 } })" @click="openChartForm('add', { options: { w: 3 } })"
type="primary" type="primary"
icon="plus-circle" icon="plus-circle"
class="ops-button-primary" ghost
class="ops-button-ghost"
>{{ $t('cmdb.custom_dashboard.newChart') }}</a-button >{{ $t('cmdb.custom_dashboard.newChart') }}</a-button
> >
</div> </div>
@ -44,7 +45,7 @@
? Array.isArray(item.options.bgColor) ? Array.isArray(item.options.bgColor)
? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)` ? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)`
: item.options.bgColor : item.options.bgColor
: '#fafafa', : '#fff',
}" }"
> >
<div class="cmdb-dashboard-grid-item-title"> <div class="cmdb-dashboard-grid-item-title">
@ -78,7 +79,10 @@
></a> ></a>
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item> <a-menu-item>
<a @click="() => openChartForm('edit', item)"><a-icon style="margin-right:5px" type="edit" />{{ $t('edit') }}</a> <a
@click="() => openChartForm('edit', item)"
><a-icon style="margin-right:5px" type="edit" />{{ $t('edit') }}</a
>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<a @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />{{ $t('delete') }}</a> <a @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />{{ $t('delete') }}</a>
@ -261,7 +265,7 @@ export default {
text-align: center; text-align: center;
} }
.cmdb-dashboard-grid-item { .cmdb-dashboard-grid-item {
border-radius: 8px; border-radius: 2px;
padding: 6px 12px; padding: 6px 12px;
.cmdb-dashboard-grid-item-title { .cmdb-dashboard-grid-item-title {
overflow: hidden; overflow: hidden;
@ -299,6 +303,7 @@ export default {
display: inline-block; display: inline-block;
width: 16px; width: 16px;
height: 16px; height: 16px;
line-height: 16px;
font-size: 16px; font-size: 16px;
text-align: center; text-align: center;
margin-right: 5px; margin-right: 5px;

View File

@ -144,6 +144,9 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.setting-discovery { .setting-discovery {
background-color: #fff;
padding: 20px;
border-radius: @border-radius-box;
.type-header { .type-header {
width: 100%; width: 100%;
display: inline-flex; display: inline-flex;

View File

@ -1,11 +1,11 @@
<template> <template>
<TwoColumnLayout appName="cmdb-adc"> <TwoColumnLayout appName="cmdb-adc">
<template #one> <template #one>
<div v-for="group in ci_types_list" :key="group.id"> <div class="cmdb-adc-group" v-for="group in ci_types_list" :key="group.id">
<div> <p>
<strong>{{ group.name || $t('other') }}</strong <strong>{{ group.name || $t('other') }}</strong
><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span> ><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span>
</div> </p>
<div <div
:class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === type.id }" :class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === type.id }"
v-for="type in group.ci_types" v-for="type in group.ci_types"
@ -34,7 +34,6 @@
<div id="discovery-ci"> <div id="discovery-ci">
<a-input-search <a-input-search
:placeholder="$t('cmdb.components.pleaseSearch')" :placeholder="$t('cmdb.components.pleaseSearch')"
class="ops-input ops-input-radius"
:style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }" :style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }"
@search="handleSearch" @search="handleSearch"
allowClear allowClear
@ -102,7 +101,11 @@
sortable sortable
v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }" v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }"
></vxe-column> ></vxe-column>
<vxe-column :title="$t('operation')" v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }" align="center"> <vxe-column
:title="$t('operation')"
v-bind="columns.length ? { width: '60px' } : { minWidth: '60px' }"
align="center"
>
<template #default="{row}"> <template #default="{row}">
<a-space> <a-space>
<a-tooltip :title="$t('cmdb.ad.accept')"> <a-tooltip :title="$t('cmdb.ad.accept')">
@ -312,6 +315,9 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.cmdb-adc { .cmdb-adc {
.cmdb-adc-group {
margin-bottom: 20px;
}
.cmdb-adc-side-item { .cmdb-adc-side-item {
.ops_popover_item(); .ops_popover_item();
height: 32px; height: 32px;
@ -343,7 +349,7 @@ export default {
} }
.cmdb-adc-side-item-selected { .cmdb-adc-side-item-selected {
.ops_popover_item_selected(); .ops_popover_item_selected();
background-color: #custom_colors[color_2]; background-color: @primary-color_3;
} }
} }
</style> </style>

View File

@ -77,7 +77,7 @@
} }
.cmdb-views-header { .cmdb-views-header {
border-left: 4px solid #custom_colors[color_1]; border-left: 4px solid @primary-color;
height: 32px; height: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -85,17 +85,17 @@
margin-bottom: 18px; margin-bottom: 18px;
.cmdb-views-header-title { .cmdb-views-header-title {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: bold;
color: rgba(0, 0, 0, 0.75); color: @text-color_1;
margin-left: 10px; margin-left: 10px;
} }
.cmdb-views-header-metadata { .cmdb-views-header-metadata {
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.55); color: @text-color_3;
margin-left: 20px; margin-left: 20px;
&:hover { &:hover {
color: #custom_colors[color_1]; color: @primary-color;
} }
} }
} }

View File

@ -242,9 +242,11 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.model-relation { .model-relation {
background-color: #fff; background-color: #fff;
border-radius: 15px; border-radius: @border-radius-box;
padding: 24px; padding: 24px;
height: calc(100vh - 64px); height: calc(100vh - 64px);
margin-bottom: -24px; margin-bottom: -24px;

View File

@ -51,7 +51,6 @@
:class="{ :class="{
'cmdb-preference-avatar': true, 'cmdb-preference-avatar': true,
'cmdb-preference-avatar-noicon': !ciType.icon, 'cmdb-preference-avatar-noicon': !ciType.icon,
'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed,
}" }"
:style="{ width: '30px', height: '30px', marginRight: '10px' }" :style="{ width: '30px', height: '30px', marginRight: '10px' }"
> >
@ -94,7 +93,12 @@
</div> </div>
</div> </div>
<div class="cmdb-preference-right"> <div class="cmdb-preference-right">
<div v-for="group in citypeData" :key="group.id"> <a-input-search
v-model="searchValue"
:style="{ width: '300px', marginBottom: '20px' }"
:placeholder="$t('cmdb.preference.searchPlaceholder')"
/>
<div v-for="group in filterCiTypeData" :key="group.id">
<p @click="changeGroupExpand(group)" :style="{ display: 'inline-block', cursor: 'pointer' }"> <p @click="changeGroupExpand(group)" :style="{ display: 'inline-block', cursor: 'pointer' }">
<a-icon :type="expandKeys.includes(group.id) ? 'caret-down' : 'caret-right'" />{{ group.name }}({{ <a-icon :type="expandKeys.includes(group.id) ? 'caret-down' : 'caret-right'" />{{ group.name }}({{
group.ci_types ? group.ci_types.length : 0 group.ci_types ? group.ci_types.length : 0
@ -108,7 +112,6 @@
:class="{ :class="{
'cmdb-preference-avatar': true, 'cmdb-preference-avatar': true,
'cmdb-preference-avatar-noicon': !item.icon, 'cmdb-preference-avatar-noicon': !item.icon,
'cmdb-preference-avatar-noicon-is_subscribed': !item.icon && item.is_subscribed,
}" }"
> >
<template v-if="item.icon"> <template v-if="item.icon">
@ -188,6 +191,7 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import router, { resetRouter } from '@/router' import router, { resetRouter } from '@/router'
import store from '@/store' import store from '@/store'
import { mapState } from 'vuex' import { mapState } from 'vuex'
@ -219,12 +223,29 @@ export default {
}, },
type_id2users: {}, type_id2users: {},
myPreferences: [], myPreferences: [],
searchValue: '',
} }
}, },
computed: { computed: {
...mapState({ ...mapState({
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
}), }),
filterCiTypeData() {
if (this.searchValue) {
const _citypeData = _.cloneDeep(this.citypeData)
_citypeData.forEach((group) => {
if (group.ci_types) {
group.ci_types = group.ci_types.filter(
(item) =>
item.name.toLowerCase().includes(this.searchValue.toLowerCase()) ||
item.alias.toLowerCase().includes(this.searchValue.toLowerCase())
)
}
})
return _citypeData
}
return this.citypeData
},
}, },
mounted() { mounted() {
this.getCITypes(true) this.getCITypes(true)
@ -376,7 +397,6 @@ export default {
.cmdb-preference { .cmdb-preference {
margin: -24px; margin: -24px;
overflow: auto; overflow: auto;
background: url('../../assets/preference_background.png');
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -392,7 +412,7 @@ export default {
.cmdb-preference-left { .cmdb-preference-left {
width: 300px; width: 300px;
height: 100%; height: 100%;
padding: 12px 18px; padding: 24px 18px;
.cmdb-preference-left-card { .cmdb-preference-left-card {
background: url('../../assets/preference_card.png'); background: url('../../assets/preference_card.png');
background-repeat: no-repeat; background-repeat: no-repeat;
@ -426,17 +446,19 @@ export default {
.cmdb-preference-group-title { .cmdb-preference-group-title {
text-align: center; text-align: center;
margin-bottom: 5px; margin-bottom: 5px;
i {
color: @primary-color;
}
> span { > span {
display: inline-block; display: inline-block;
color: #fff; color: @text-color_2;
background: linear-gradient(90deg, #305bec, #78cfff);
border-radius: 16px; border-radius: 16px;
font-weight: 600; font-weight: 600;
padding: 6px 12px; padding: 6px 12px;
} }
} }
.cmdb-preference-group-content { .cmdb-preference-group-content {
color: rgba(0, 0, 0, 0.75); color: @text-color_1;
font-weight: 400; font-weight: 400;
display: flex; display: flex;
align-items: center; align-items: center;
@ -447,7 +469,11 @@ export default {
&:hover { &:hover {
background: #ffffff; background: #ffffff;
box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25); box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25);
border-radius: 8px; border-radius: @border-radius-box;
.cmdb-preference-avatar {
box-shadow: none;
background-color: @primary-color_5;
}
.cmdb-preference-group-content-action { .cmdb-preference-group-content-action {
display: inline; display: inline;
white-space: nowrap; white-space: nowrap;
@ -479,7 +505,7 @@ export default {
.cmdb-preference-right { .cmdb-preference-right {
flex: 1; flex: 1;
height: 100%; height: 100%;
padding-top: 18px; padding-top: 24px;
.cmdb-preference-content { .cmdb-preference-content {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -494,11 +520,11 @@ export default {
display: inline-block; display: inline-block;
width: 195px; width: 195px;
height: 155px; height: 155px;
border-radius: 8px; border-radius: @border-radius-box;
background-color: #fff; background-color: #fff;
box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25); box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25);
margin: 0 20px 20px 0; margin: 0 20px 20px 0;
padding: 8px; padding: 12px;
&:hover { &:hover {
box-shadow: 4px 25px 30px rgba(50, 89, 134, 0.25); box-shadow: 4px 25px 30px rgba(50, 89, 134, 0.25);
transform: scale(1.1); transform: scale(1.1);
@ -550,7 +576,7 @@ export default {
.cmdb-preference-progress-gray { .cmdb-preference-progress-gray {
height: 5px; height: 5px;
border-radius: 5px; border-radius: 5px;
background-color: #d9d9d9; background-color: @text-color_6;
margin-top: 5px; margin-top: 5px;
width: 100%; width: 100%;
position: relative; position: relative;
@ -560,7 +586,7 @@ export default {
top: 0; top: 0;
left: 0; left: 0;
border-radius: 5px; border-radius: 5px;
background: linear-gradient(90deg, #305bec, #78cfff); background: @primary-color_8;
} }
} }
} }
@ -592,20 +618,17 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 40px; width: 34px;
height: 40px; height: 34px;
box-shadow: 0px 4px 4px rgba(129, 140, 186, 0.25); box-shadow: 0px 4px 4px rgba(129, 140, 186, 0.25);
border-radius: 5px; border-radius: 1px;
background-color: #fff;
} }
.cmdb-preference-avatar-noicon { .cmdb-preference-avatar-noicon {
background-color: #7f97fa;
> span { > span {
font-size: 24px; font-size: 18px;
color: #fff; color: @text-color_4;
} }
} }
.cmdb-preference-avatar-noicon-is_subscribed {
background-color: #47a964;
}
} }
</style> </style>

View File

@ -15,9 +15,7 @@
>{{ $t('cmdb.preference_relation.newServiceTree') }}</a-button >{{ $t('cmdb.preference_relation.newServiceTree') }}</a-button
> >
<template v-else> <template v-else>
<a-input v-model="newRelationViewName" :placeholder="$t('cmdb.preference_relation.serviceTreeName')"></a-input> <a-button type="primary" size="small" @click="openServiceTreeModal({}, 'add')">{{ $t('save') }}</a-button>
<a-checkbox v-model="is_public">{{ $t('cmdb.preference_relation.public') }}</a-checkbox>
<a-button type="primary" size="small" @click="handleSaveRelationViews">{{ $t('save') }}</a-button>
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
@ -26,13 +24,13 @@
() => { () => {
isEdit = false isEdit = false
checkedNodes = [] checkedNodes = []
newRelationViewName = ''
} }
" "
>{{ $t('cancel') }}</a-button >{{ $t('cancel') }}</a-button
> >
</template> </template>
<a-button type="primary" size="small" @click="handleSave">{{ $t('cmdb.preference_relation.saveLayout') }}</a-button> <a-button size="small" @click="handleSave">{{ $t('cmdb.preference_relation.saveLayout') }}</a-button>
<span>{{ $t('cmdb.preference_relation.tips5') }}</span>
</a-space> </a-space>
</div> </div>
<SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions"> <SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions">
@ -46,7 +44,7 @@
</div> </div>
</SeeksRelationGraph> </SeeksRelationGraph>
</div> </div>
<template v-if="relationViews.views"> <template v-if="relationViews.views && !loading">
<a-row :gutter="4"> <a-row :gutter="4">
<a-col <a-col
:xl="12" :xl="12"
@ -54,11 +52,17 @@
:md="12" :md="12"
:sm="24" :sm="24"
:xs="24" :xs="24"
:key="`${view}${idx}`" :key="`${view}`"
v-for="(view, idx) in Object.keys(relationViews.views)" v-for="view in Object.keys(relationViews.views)"
> >
<div class="relation-views"> <div class="relation-views">
<h3 :style="{ padding: '10px 0 0 20px' }">{{ view }}</h3> <h3 :style="{ padding: '10px 0 0 20px' }">{{ view }}</h3>
<a
class="relation-views-edit"
@click="openServiceTreeModal({ name: view }, 'edit')"
><ops-icon
type="icon-xianxing-edit"
/></a>
<a-popconfirm :title="$t('cmdb.ciType.confirmDelete', { name: `${view}` })" @confirm="confirmDelete(view)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete', { name: `${view}` })" @confirm="confirmDelete(view)">
<a class="relation-views-close"><a-icon type="close"/></a> <a class="relation-views-close"><a-icon type="close"/></a>
</a-popconfirm> </a-popconfirm>
@ -69,6 +73,7 @@
</a-col> </a-col>
</a-row> </a-row>
</template> </template>
<ServiceTreeModal ref="serviceTreeModal" @submitServiceTree="submitServiceTree" />
</div> </div>
</template> </template>
@ -78,11 +83,17 @@ import router, { resetRouter } from '@/router'
import store from '@/store' import store from '@/store'
import SeeksRelationGraph from '@/modules/cmdb/3rd/relation-graph' import SeeksRelationGraph from '@/modules/cmdb/3rd/relation-graph'
import { getCITypeRelations } from '@/modules/cmdb/api/CITypeRelation' import { getCITypeRelations } from '@/modules/cmdb/api/CITypeRelation'
import { getRelationView, deleteRelationView, subscribeRelationView } from '@/modules/cmdb/api/preference' import {
getRelationView,
deleteRelationView,
subscribeRelationView,
putRelationView,
} from '@/modules/cmdb/api/preference'
import { getSystemConfig, saveSystemConfig } from '../../api/system_config' import { getSystemConfig, saveSystemConfig } from '../../api/system_config'
import ServiceTreeModal from './serviceTreeModal.vue'
export default { export default {
name: 'PreferenceRelation', name: 'PreferenceRelation',
components: { SeeksRelationGraph }, components: { SeeksRelationGraph, ServiceTreeModal },
data() { data() {
const defaultOptions = { const defaultOptions = {
allowShowMiniToolBar: false, allowShowMiniToolBar: false,
@ -103,6 +114,7 @@ export default {
} }
const relationViewOptions = { const relationViewOptions = {
...defaultOptions, ...defaultOptions,
disableZoom: true,
layouts: [ layouts: [
{ {
layoutName: 'tree', layoutName: 'tree',
@ -116,11 +128,10 @@ export default {
relationViewOptions, relationViewOptions,
isEdit: false, isEdit: false,
relationViews: {}, relationViews: {},
newRelationViewName: '',
graphJsonData: {}, graphJsonData: {},
checkedNodes: [], checkedNodes: [],
is_public: true,
isPullConfig: false, isPullConfig: false,
loading: false,
} }
}, },
async created() { async created() {
@ -210,6 +221,7 @@ export default {
return maxEle return maxEle
}, },
async getViewsData() { async getViewsData() {
this.loading = true
const data = await getRelationView() const data = await getRelationView()
this.relationViews = data this.relationViews = data
const { views } = data const { views } = data
@ -240,6 +252,7 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.relationViewsGraph[index].setJsonData(_graphJsonData) this.$refs.relationViewsGraph[index].setJsonData(_graphJsonData)
}) })
this.loading = false
}) })
}, },
checked(e, node) { checked(e, node) {
@ -288,32 +301,59 @@ export default {
this.checkedNodes.push(node.id) this.checkedNodes.push(node.id)
} }
}, },
async handleSaveRelationViews() { openServiceTreeModal(treeData, type) {
if (!this.newRelationViewName) { if (type === 'add' && this.checkedNodes.length < 2) {
this.$message.warning(this.$t('cmdb.preference_relation.tips2'))
return
}
if (this.checkedNodes.length < 2) {
this.$message.warning(this.$t('cmdb.preference_relation.tips3')) this.$message.warning(this.$t('cmdb.preference_relation.tips3'))
return return
} }
// eslint-disable-next-line camelcase let _treeData = { ...treeData }
const cr_ids = [] if (type === 'edit') {
this.checkedNodes.forEach((item, idx) => { const { name } = _treeData
if (idx !== this.checkedNodes.length - 1) { _treeData = {
cr_ids.push({ parent_id: Number(item), child_id: Number(this.checkedNodes[idx + 1]) }) ...treeData,
...(this.relationViews?.views[name]?.option ?? {}),
is_public: this.relationViews?.views[name]?.is_public ?? true,
} }
}) }
await subscribeRelationView({ this.$refs.serviceTreeModal.open(_treeData, type)
cr_ids, },
name: this.newRelationViewName, async submitServiceTree(treeData, type, originName) {
is_public: this.is_public, const { name, is_public, is_show_leaf_node, is_show_tree_node, sort } = treeData
}) if (type === 'add') {
const cr_ids = []
this.checkedNodes.forEach((item, idx) => {
if (idx !== this.checkedNodes.length - 1) {
cr_ids.push({ parent_id: Number(item), child_id: Number(this.checkedNodes[idx + 1]) })
}
})
await subscribeRelationView({
cr_ids,
name,
is_public,
option: { is_show_leaf_node, is_show_tree_node, sort, is_public },
})
} else {
const _name = name === originName ? name : originName
const topo_flatten = this.relationViews?.views[_name]?.topo_flatten ?? []
const name2id = this.relationViews?.name2id.find((item) => item[0] === _name)
const cr_ids = []
topo_flatten.forEach((item, idx) => {
if (idx !== topo_flatten.length - 1) {
cr_ids.push({ parent_id: Number(item), child_id: Number(topo_flatten[idx + 1]) })
}
})
console.log(originName, name, cr_ids, name2id)
await putRelationView(name2id[1], {
cr_ids,
name,
is_public,
option: { is_show_leaf_node, is_show_tree_node, sort, is_public },
})
}
this.resetRoute() this.resetRoute()
this.getViewsData() this.getViewsData()
this.isEdit = false this.isEdit = false
this.checkedNodes = [] this.checkedNodes = []
this.newRelationViewName = ''
}, },
async confirmDelete(viewName) { async confirmDelete(viewName) {
await deleteRelationView(viewName) await deleteRelationView(viewName)
@ -359,7 +399,7 @@ export default {
width: 100%; width: 100%;
.ci-type-relation-header { .ci-type-relation-header {
position: absolute; position: absolute;
top: 10px; top: 20px;
left: 20px; left: 20px;
z-index: 10; z-index: 10;
} }
@ -368,12 +408,19 @@ export default {
background-color: #fff; background-color: #fff;
margin-top: 5px; margin-top: 5px;
position: relative; position: relative;
.relation-views-edit,
.relation-views-close { .relation-views-close {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
right: 20px; right: 60px;
top: 10px; top: 10px;
} }
.relation-views-edit {
right: 46px;
}
.relation-views-close {
right: 20px;
}
} }
} }
</style> </style>

View File

@ -0,0 +1,110 @@
<template>
<a-modal width="700px" :title="title" :visible="visible" @cancel="handleCancel" @ok="handleOK">
<a-form-model ref="form" :model="form" :rules="rules" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
<a-form-model-item :label="$t('cmdb.preference_relation.serviceTreeName')" prop="name">
<a-input v-model="form.name" :placeholder="$t('cmdb.preference_relation.serviceTreeNamePlaceholder')" />
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.preference_relation.public')" prop="is_public">
<a-checkbox v-model="form.is_public"> </a-checkbox>
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.preference_relation.showLeafNode')" prop="is_show_leaf_node">
<a-checkbox :checked="form.is_show_leaf_node" @change="changeLeaf"> </a-checkbox>
</a-form-model-item>
<a-form-model-item :label="$t('cmdb.preference_relation.showTreeNode')" prop="is_show_tree_node">
<a-checkbox :checked="form.is_show_tree_node" @change="changeNode"> </a-checkbox>
</a-form-model-item>
<a-form-model-item
v-if="form.is_show_leaf_node && form.is_show_tree_node"
:label="$t('cmdb.preference_relation.sort')"
prop="sort"
>
<a-radio-group v-model="form.sort">
<a-radio :value="1">
{{ $t('cmdb.preference_relation.sort1') }}
</a-radio>
<a-radio :value="2">
{{ $t('cmdb.preference_relation.sort2') }}
</a-radio>
</a-radio-group>
</a-form-model-item>
</a-form-model>
</a-modal>
</template>
<script>
export default {
name: 'ServiceTreeModal',
data() {
return {
visible: false,
type: 'add',
originTreeData: {},
form: {
name: '',
is_public: true,
is_show_leaf_node: true,
is_show_tree_node: false,
sort: 1,
},
rules: {
name: [{ required: true, message: this.$t('cmdb.preference_relation.serviceTreeNamePlaceholder') }],
is_public: [{ required: false }],
is_show_leaf_node: [{ required: false }],
is_show_tree_node: [{ required: false }],
},
}
},
computed: {
title() {
if (this.type === 'edit') {
return this.$t('cmdb.preference_relation.editServiceTree')
}
return this.$t('cmdb.preference_relation.newServiceTree')
},
},
methods: {
open(treeData = {}, type) {
this.visible = true
this.type = type
this.originTreeData = { ...treeData }
this.form = { name: '', is_public: true, is_show_leaf_node: true, is_show_tree_node: false, sort: 1, ...treeData }
},
handleCancel() {
this.$refs.form.resetFields()
this.visible = false
},
handleOK() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$emit('submitServiceTree', this.form, this.type, this.originTreeData?.name ?? undefined)
this.handleCancel()
}
})
},
changeLeaf(e) {
const checked = e.target.checked
if (!checked && !this.form.is_show_tree_node) {
this.$message.warning(this.$t('cmdb.preference_relation.tips4'))
return
}
this.form = {
...this.form,
is_show_leaf_node: checked,
}
},
changeNode(e) {
const checked = e.target.checked
if (!checked && !this.form.is_show_leaf_node) {
this.$message.warning(this.$t('cmdb.preference_relation.tips4'))
return
}
this.form = {
...this.form,
is_show_tree_node: checked,
}
},
},
}
</script>
<style></style>

View File

@ -1,61 +1,51 @@
<template> <template>
<div :style="{ marginBottom: '-24px', overflow: 'hidden' }"> <div :style="{ marginBottom: '-24px', overflow: 'hidden' }">
<div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper"> <div v-if="relationViews.name2id && relationViews.name2id.length" class="relation-views-wrapper">
<div class="cmdb-views-header">
<span
class="cmdb-views-header-title"
>{{ $route.meta.name }}
<div
class="ops-list-batch-action"
:style="{ backgroundColor: '#c0ceeb' }"
v-if="showBatchLevel !== null && batchTreeKey && batchTreeKey.length"
>
<span
@click="
() => {
$refs.grantModal.open('depart')
}
"
>{{ $t('grant') }}</span
>
<a-divider type="vertical" />
<span
@click="
() => {
$refs.revokeModal.open()
}
"
>{{ $t('revoke') }}</span
>
<template v-if="showBatchLevel > 0">
<a-divider type="vertical" />
<span @click="batchDeleteCIRelationFromTree">{{ $t('delete') }}</span>
</template>
<a-divider type="vertical" />
<span
@click="
() => {
showBatchLevel = null
batchTreeKey = []
}
"
>{{ $t('cancel') }}</span
>
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
</div>
</span>
<a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button>
</div>
<SplitPane <SplitPane
:min="200" :min="200"
:max="500" :max="500"
:paneLengthPixel.sync="paneLengthPixel" :paneLengthPixel.sync="paneLengthPixel"
:appName="`cmdb-relation-views-${viewId}`" :appName="`cmdb-relation-views-${viewId}`"
triggerColor="#F0F5FF"
:triggerLength="18" :triggerLength="18"
> >
<template #one> <template #one>
<div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }"> <div class="relation-views-left" :style="{ height: `${windowHeight - 115}px` }">
<div class="relation-views-left-header" :title="$route.meta.name">{{ $route.meta.name }}</div>
<div
class="ops-list-batch-action"
:style="{ marginBottom: '10px' }"
v-if="showBatchLevel !== null && batchTreeKey && batchTreeKey.length"
>
<span
@click="
() => {
$refs.grantModal.open('depart')
}
"
>{{ $t('grant') }}</span
>
<span
@click="
() => {
$refs.revokeModal.open()
}
"
>{{ $t('revoke') }}</span
>
<template v-if="showBatchLevel > 0">
<span @click="batchDeleteCIRelationFromTree">{{ $t('delete') }}</span>
</template>
<span
@click="
() => {
showBatchLevel = null
batchTreeKey = []
}
"
>{{ $t('cancel') }}</span
>
<span>{{ $t('selectRows', { rows: batchTreeKey.length }) }}</span>
</div>
<a-tree <a-tree
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
:loadData="onLoadData" :loadData="onLoadData"
@ -65,9 +55,10 @@
@drop="onDrop" @drop="onDrop"
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
> >
<template #title="{ key: treeKey, title, isLeaf }"> <template #title="{ key: treeKey, title,number, isLeaf }">
<ContextMenu <ContextMenu
:title="title" :title="title"
:number="number"
:treeKey="treeKey" :treeKey="treeKey"
:levels="levels" :levels="levels"
:isLeaf="isLeaf" :isLeaf="isLeaf"
@ -79,51 +70,69 @@
:showBatchLevel="showBatchLevel" :showBatchLevel="showBatchLevel"
:batchTreeKey="batchTreeKey" :batchTreeKey="batchTreeKey"
@clickCheckbox="clickCheckbox" @clickCheckbox="clickCheckbox"
@updateTreeData="updateTreeData"
/> />
</template> </template>
</a-tree> </a-tree>
</div> </div>
</template> </template>
<template #two> <template #two>
<div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 115}px` }"> <div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 64}px` }">
<a-tabs :activeKey="String(currentTypeId[0])" type="card" @change="changeCIType" class="ops-tab"> <a-tabs :activeKey="currentTypeId[0]" class="ops-tab" @change="changeCIType" size="small">
<a-tab-pane v-for="item in showTypes" :key="`${item.id}`" :tab="item.alias || item.name"> </a-tab-pane> <a-tab-pane v-for="item in showTypes" :key="item.id" :tab="item.alias || item.name"> </a-tab-pane>
<a-space slot="tabBarExtraContent">
<a-button
v-if="isLeaf"
type="primary"
class="ops-button-ghost"
ghost
@click="$refs.create.handleOpen(true, 'create')"
><ops-icon type="veops-increase" />{{ $t('create') }}</a-button
>
<a-button icon="user-add" type="primary" ghost @click="handlePerm" class="ops-button-ghost">{{
$t('grant')
}}</a-button>
<EditAttrsPopover
:typeId="Number(currentTypeId[0])"
class="operation-icon"
@refresh="refreshAfterEditAttrs"
>
<a-button
type="primary"
ghost
class="ops-button-ghost"
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
>
</EditAttrsPopover>
</a-space>
</a-tabs> </a-tabs>
<SearchForm <SearchForm
ref="search" ref="search"
@refresh="refreshTable" @refresh="refreshTable"
:preferenceAttrList="preferenceAttrList" :preferenceAttrList="preferenceAttrList"
:isShowExpression="true" :isShowExpression="!(isLeaf && isShowBatchIcon)"
:typeId="Number(currentTypeId[0])" :typeId="Number(currentTypeId[0])"
@copyExpression="copyExpression" @copyExpression="copyExpression"
type="relationView" type="relationView"
:style="{ padding: '0 12px', marginTop: '16px' }" >
/> <PreferenceSearch
<div class="relation-views-right-bar"> v-if="!(isLeaf && isShowBatchIcon)"
<a-space> ref="preferenceSearch"
<a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{ @getQAndSort="getQAndSort"
$t('create') @setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
}}</a-button> />
<a-space slot="extraContent">
<div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon"> <div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon">
<template v-if="selectedRowKeys.length"> <template v-if="selectedRowKeys.length">
<span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span> <span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span>
<a-divider type="vertical" />
<span @click="openBatchDownload">{{ $t('download') }}</span> <span @click="openBatchDownload">{{ $t('download') }}</span>
<a-divider type="vertical" />
<span @click="batchDelete">{{ $t('cmdb.ciType.deleteInstance') }}</span> <span @click="batchDelete">{{ $t('cmdb.ciType.deleteInstance') }}</span>
<a-divider type="vertical" />
<span @click="batchDeleteCIRelation">{{ $t('cmdb.history.deleteRelation') }}</span> <span @click="batchDeleteCIRelation">{{ $t('cmdb.history.deleteRelation') }}</span>
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</template> </template>
</div> </div>
<PreferenceSearch
ref="preferenceSearch"
@getQAndSort="getQAndSort"
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
/>
</a-space> </a-space>
</div> </SearchForm>
<vxe-table <vxe-table
:id="`cmdb-relation-${viewId}-${currentTypeId}`" :id="`cmdb-relation-${viewId}-${currentTypeId}`"
border border
@ -283,14 +292,9 @@
</template> </template>
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-column align="left" field="operate" fixed="right" width="120"> <vxe-column align="left" field="operate" fixed="right" width="80">
<template #header> <template #header>
<span>{{ $t('operation') }}</span> <span>{{ $t('operation') }}</span>
<EditAttrsPopover
:typeId="Number(currentTypeId[0])"
class="operation-icon"
@refresh="refreshAfterEditAttrs"
/>
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a-space> <a-space>
@ -482,6 +486,9 @@ export default {
contextMenuKey: null, contextMenuKey: null,
showBatchLevel: null, showBatchLevel: null,
batchTreeKey: [], batchTreeKey: [],
statisticsObj: {},
viewOption: {},
} }
}, },
@ -490,7 +497,7 @@ export default {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableHeight() { tableHeight() {
return this.windowHeight - 295 return this.windowHeight - 244
}, },
selectedKeys() { selectedKeys() {
if (this.treeKeys.length <= 1) { if (this.treeKeys.length <= 1) {
@ -519,6 +526,15 @@ export default {
.map((item) => item.split('%')[0]) .map((item) => item.split('%')[0])
.join(',') .join(',')
}, },
is_show_leaf_node() {
return this.viewOption?.is_show_leaf_node ?? true
},
is_show_tree_node() {
return this.viewOption?.is_show_tree_node ?? false
},
leaf_tree_sort() {
return this.viewOption?.sort ?? 1
},
}, },
provide() { provide() {
return { return {
@ -638,6 +654,7 @@ export default {
if (q && q[0] === ',') { if (q && q[0] === ',') {
q = q.slice(1) q = q.slice(1)
} }
if (this.treeKeys.length === 0) { if (this.treeKeys.length === 0) {
// await this.judgeCITypes(q) // await this.judgeCITypes(q)
if (!refreshType) { if (!refreshType) {
@ -684,8 +701,10 @@ export default {
.map((item) => item.split('%')[0]) .map((item) => item.split('%')[0])
.join(',')}` .join(',')}`
} }
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
await this.judgeCITypes()
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
let level = [] let level = []
if (!this.leaf.includes(typeId)) { if (!this.leaf.includes(typeId)) {
let startIdx = 0 let startIdx = 0
@ -705,8 +724,8 @@ export default {
} else { } else {
level = [1] level = [1]
} }
q += `&level=${level.join(',')}`
await this.judgeCITypes(q) q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}`
if (!refreshType) { if (!refreshType) {
this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level) this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level)
} }
@ -793,69 +812,106 @@ export default {
this.selectedRowKeys = [] this.selectedRowKeys = []
this.currentTypeId = [typeId] this.currentTypeId = [typeId]
this.loadColumns() this.loadColumns()
this.$nextTick(() => { // this.$nextTick(() => {
this.refreshTable() // this.refreshTable()
}) // })
}, },
async judgeCITypes(q) { async judgeCITypes() {
const showTypeIds = []
let _showTypes = []
let _showTypeIds = [] let _showTypeIds = []
let _showTypes = []
if (this.treeKeys.length) { if (this.treeKeys.length) {
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1]) if (this.is_show_leaf_node) {
const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
_showTypes = this.node2ShowTypes[typeId + ''] _showTypeIds = _.cloneDeep(this.origShowTypeIds)
_showTypes.forEach((item) => { _showTypes = _.cloneDeep(this.node2ShowTypes[typeId])
_showTypeIds.push(item.id)
})
} else {
_showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
_showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
}
const promises = _showTypeIds.map((typeId) => {
let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
if (Object.values(this.level2constraint).includes('2')) {
_q = _q + `&has_m2m=1`
} }
if (this.root_parent_path) { if (this.is_show_tree_node) {
_q = _q + `&root_parent_path=${this.root_parent_path}` const treeKeyTypeId = Number(this.treeKeys.slice(-1)[0].split('%')[1])
} const _idx = this.topo_flatten.findIndex((item) => item === treeKeyTypeId)
// if (this.treeKeys.length === 0) { if (_idx > -1 && _idx < this.topo_flatten.length - 1) {
// return searchCI2(_q).then((res) => { const _showTreeTypeId = this.topo_flatten[_idx + 1]
// if (res.numfound !== 0) { const _showTreeTypes = this.relationViews.id2type[_showTreeTypeId]
// showTypeIds.push(typeId) if (this.leaf_tree_sort === 1) {
// } _showTypeIds.push(_showTreeTypeId)
// }) _showTypes.push(_showTreeTypes)
// } else { } else {
_q = _q + `&descendant_ids=${this.descendant_ids}` _showTypeIds.unshift(_showTreeTypeId)
return searchCIRelation(_q).then((res) => { _showTypes.unshift(_showTreeTypes)
if (res.numfound !== 0) {
showTypeIds.push(typeId)
}
})
// }
})
await Promise.all(promises).then(async () => {
if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
const showTypes = []
_showTypes.forEach((item) => {
if (showTypeIds.includes(item.id)) {
showTypes.push(item)
} }
})
this.showTypes = showTypes
this.showTypeIds = showTypeIds
if (
!this.currentTypeId.length ||
(this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
) {
this.currentTypeId = [this.showTypeIds[0]]
await this.loadColumns()
} }
} }
}) this.showTypeIds = _showTypeIds
this.showTypes = _showTypes
} else {
this.showTypeIds = _.cloneDeep(this.origShowTypeIds)
this.showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
}
if (
!this.currentTypeId.length ||
(this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
) {
this.currentTypeId = [this.showTypeIds[0]]
await this.loadColumns()
}
// const showTypeIds = []
// let _showTypes = []
// let _showTypeIds = []
// if (this.treeKeys.length) {
// const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1])
// _showTypes = this.node2ShowTypes[typeId + '']
// _showTypes.forEach((item) => {
// _showTypeIds.push(item.id)
// })
// } else {
// _showTypeIds = JSON.parse(JSON.stringify(this.origShowTypeIds))
// _showTypes = JSON.parse(JSON.stringify(this.origShowTypes))
// }
// const promises = _showTypeIds.map((typeId) => {
// let _q = (`q=_type:${typeId},` + q).replace(/count=\d*/, 'count=1')
// if (Object.values(this.level2constraint).includes('2')) {
// _q = _q + `&has_m2m=1`
// }
// if (this.root_parent_path) {
// _q = _q + `&root_parent_path=${this.root_parent_path}`
// }
// // if (this.treeKeys.length === 0) {
// // return searchCI2(_q).then((res) => {
// // if (res.numfound !== 0) {
// // showTypeIds.push(typeId)
// // }
// // })
// // } else {
// _q = _q + `&descendant_ids=${this.descendant_ids}`
// return searchCIRelation(_q).then((res) => {
// if (res.numfound !== 0) {
// showTypeIds.push(typeId)
// }
// })
// // }
// })
// await Promise.all(promises).then(async () => {
// if (showTypeIds.length && showTypeIds.sort().join(',') !== this.showTypeIds.sort().join(',')) {
// const showTypes = []
// _showTypes.forEach((item) => {
// if (showTypeIds.includes(item.id)) {
// showTypes.push(item)
// }
// })
// console.log(showTypes)
// this.showTypes = showTypes
// this.showTypeIds = showTypeIds
// if (
// !this.currentTypeId.length ||
// (this.currentTypeId.length && !this.showTypeIds.includes(this.currentTypeId[0]))
// ) {
// this.currentTypeId = [this.showTypeIds[0]]
// await this.loadColumns()
// }
// }
// })
}, },
async loadRoot() { async loadRoot() {
@ -980,7 +1036,8 @@ export default {
const treeData = [] const treeData = []
facet.forEach((item) => { facet.forEach((item) => {
treeData.push({ treeData.push({
title: `${item[0]} (${item[1]})`, title: item[0],
number: item[1],
key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`, key: this.treeKeys.join('@^@') + '@^@' + item[2] + '%' + item[3] + '%' + `{"${item[4]}":"${item[0]}"}`,
isLeaf: this.leaf.includes(item[3]), isLeaf: this.leaf.includes(item[3]),
id: item[2], id: item[2],
@ -1041,16 +1098,19 @@ export default {
this.leaf = this.relationViews.views[this.viewName].leaf this.leaf = this.relationViews.views[this.viewName].leaf
this.currentView = `${this.viewId}` this.currentView = `${this.viewId}`
this.typeId = this.levels[0][0] this.typeId = this.levels[0][0]
this.viewOption = this.relationViews.views[this.viewName].option ?? {}
this.refreshTable() this.refreshTable()
} }
}) })
}, },
async loadColumns() { async loadColumns() {
this.getAttributeList() if (this.currentTypeId[0]) {
const res = await getSubscribeAttributes(this.currentTypeId[0]) this.getAttributeList()
this.preferenceAttrList = res.attributes const res = await getSubscribeAttributes(this.currentTypeId[0])
this.calcColumns() this.preferenceAttrList = res.attributes
this.calcColumns()
}
}, },
calcColumns() { calcColumns() {
@ -1183,7 +1243,6 @@ export default {
const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0] const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0]
// const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2]) // const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2])
const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0] const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0]
console.log(_splitDragKey)
// TODO 拖拽这里不造咋弄 等等再说吧 // TODO 拖拽这里不造咋弄 等等再说吧
batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => { batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => {
this.reload() this.reload()
@ -1354,6 +1413,9 @@ export default {
...data, ...data,
} }
this.initialInstanceList = _initialInstanceList this.initialInstanceList = _initialInstanceList
this.$nextTick(() => {
this.refreshTable()
})
}) })
.catch((err) => { .catch((err) => {
console.log(err) console.log(err)
@ -1579,7 +1641,6 @@ export default {
.split('@^@') .split('@^@')
.filter((item) => !!item) .filter((item) => !!item)
.reverse() .reverse()
console.log(needGrantNodes)
const needGrantRids = [...department, ...user] const needGrantRids = [...department, ...user]
const floor = Math.ceil(needGrantRids.length / 6) const floor = Math.ceil(needGrantRids.length / 6)
@ -1687,6 +1748,29 @@ export default {
this.showBatchLevel = null this.showBatchLevel = null
this.batchTreeKey = [] this.batchTreeKey = []
}, },
findNode(node, target) {
for (let i = 0; i < node.length; i++) {
if (node[i].id === target) {
return node[i]
}
if (node[i].children && node[i].children.length) {
for (let i = 0; i < node[i].children.length; i++) {
const found = this.findNode(node[i].children, target)
if (found) {
return found
}
}
}
}
return null
},
updateTreeData(ciId, value) {
const _find = this.findNode(this.treeData, ciId)
if (_find) {
this.$set(_find, 'title', value)
}
this.refreshTable()
},
}, },
} }
</script> </script>
@ -1701,15 +1785,24 @@ export default {
width: 100%; width: 100%;
float: left; float: left;
position: relative; position: relative;
// transition: all 0.3s;
background-color: #fff;
overflow: hidden; overflow: hidden;
padding: 12px; padding: 0;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
&:hover { &:hover {
overflow: auto; 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;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: default;
}
.ant-tree li { .ant-tree li {
padding: 2px 0; padding: 2px 0;
} }
@ -1732,15 +1825,8 @@ export default {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
background-color: #fff; background-color: #fff;
.relation-views-right-bar { padding: 20px;
display: flex; border-radius: @border-radius-box;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-bottom: 5px;
height: 32px;
padding: 0 12px;
}
} }
} }
</style> </style>

View File

@ -6,7 +6,10 @@
}" }"
@click="clickNode" @click="clickNode"
> >
<span> <span class="relation-views-node-switch">
<a-icon v-if="childLength && !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" /> <a-checkbox @click.stop="clickCheckbox" class="relation-views-node-checkbox" v-if="showCheckbox" />
<template v-if="icon"> <template v-if="icon">
<img <img
@ -24,61 +27,82 @@
/> />
<span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span> <span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span>
</template> </template>
<span class="relation-views-node-title">{{ this.title }}</span> <span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span>
</span> <a-input
<a-dropdown> ref="input"
<a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)"> @blur="changeNodeName"
<template v-if="showBatchLevel === null"> @pressEnter="
<a-menu-item () => {
v-for="item in menuList" $refs.input.blur()
:key="item.id" }
><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item "
> size="small"
<a-menu-item v-else
v-if="showDelete" v-model="editNodeName"
key="delete" :style="{ marginLeft: '5px' }"
><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item />
> <span class="relation-views-node-number">{{ number }}</span>
<a-menu-divider /> <a-dropdown overlayClassName="relation-views-node-dropdown" :overlayStyle="{ width: '200px' }">
<a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item> <a-menu slot="overlay" @click="({ key: menuKey }) => onContextMenuClick(this.treeKey, menuKey)">
<a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item> <template v-if="showBatchLevel === null">
<a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item> <a-divider orientation="left">{{ $t('cmdb.relation') }}</a-divider>
<a-menu-divider /> <a-menu-item
<a-menu-item v-for="item in menuList"
key="batch" :key="item.id"
><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item ><a-icon type="plus-circle" />{{ $t('add') }} {{ item.alias }}</a-menu-item
> >
</template> <a-menu-item
<template v-else> v-if="showDelete"
<a-menu-item key="delete"
:disabled="!batchTreeKey || !batchTreeKey.length" ><ops-icon type="icon-xianxing-delete" />{{
key="batchGrant" $t('cmdb.serviceTree.deleteNode', { name: title })
><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item }}</a-menu-item
> >
<a-menu-item <a-divider orientation="left">{{ $t('cmdb.components.perm') }}</a-divider>
:disabled="!batchTreeKey || !batchTreeKey.length" <a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item>
key="batchRevoke" <a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item>
><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-divider /> <a-menu-item
<template v-if="showBatchLevel > 0"> 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 <a-menu-item
:disabled="!batchTreeKey || !batchTreeKey.length" :disabled="!batchTreeKey || !batchTreeKey.length"
key="batchDelete" key="batchGrant"
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item ><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 /> <a-menu-divider />
<template v-if="showBatchLevel > 0">
<a-menu-item
:disabled="!batchTreeKey || !batchTreeKey.length"
key="batchDelete"
><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item
>
<a-menu-divider />
</template>
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item>
</template> </template>
<a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item> </a-menu>
</template> <a-icon class="relation-views-node-operation" type="ellipsis" />
</a-menu> </a-dropdown>
<a-icon class="relation-views-node-operation" type="ellipsis" /> </span>
</a-dropdown>
<a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
</div> </div>
</template> </template>
<script> <script>
import { updateCI } from '../../../api/ci.js'
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
props: { props: {
@ -86,6 +110,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
number: {
type: Number,
default: 0,
},
treeKey: { treeKey: {
type: String, type: String,
default: '', default: '',
@ -121,13 +149,14 @@ export default {
}, },
data() { data() {
return { return {
switchIcon: 'down', switchIcon: 'caret-right',
isEditNodeName: false,
editNodeName: '',
} }
}, },
computed: { computed: {
childLength() { childLength() {
const reg = /(?<=\()\S+(?=\))/g return this.number
return Number(this.title.match(reg)[0])
}, },
splitTreeKey() { splitTreeKey() {
return this.treeKey.split('@^@') return this.treeKey.split('@^@')
@ -175,26 +204,63 @@ export default {
}, },
methods: { methods: {
onContextMenuClick(treeKey, menuKey) { 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) this.$emit('onContextMenuClick', treeKey, menuKey)
}, },
clickNode() { clickNode() {
this.$emit('onNodeClick', this.treeKey) this.$emit('onNodeClick', this.treeKey)
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down' this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right'
}, },
clickCheckbox() { clickCheckbox() {
this.$emit('clickCheckbox', this.treeKey) 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])
updateCI(ciId, { [unique]: value }).then((res) => {
this.$message.success(this.$t('updateSuccess'))
this.$emit('updateTreeData', ciId, value)
})
}
this.isEditNodeName = false
this.editNodeName = ''
},
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less';
.relation-views-node { .relation-views-node {
width: 100%; width: 100%;
display: inline-flex; display: inline-flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
> span { .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; display: flex;
overflow: hidden; overflow: hidden;
align-items: center; align-items: center;
@ -215,16 +281,21 @@ export default {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
width: calc(100% - 16px); 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-operation {
display: none;
margin-right: 5px;
}
} }
.relation-views-node-checkbox, .relation-views-node-checkbox {
.relation-views-node-moveright {
> span { > span {
.relation-views-node-checkbox { .relation-views-node-checkbox {
margin-right: 10px; margin-right: 10px;
@ -234,14 +305,16 @@ export default {
} }
} }
} }
.relation-views-left .ant-tree:hover {
.relation-views-node .relation-views-node-switch i {
opacity: 1;
}
}
</style> </style>
<style lang="less"> <style lang="less">
.relation-views-left .ant-tree-node-content-wrapper:hover { @import '~@/style/static.less';
.relation-views-node-operation {
display: inline-block;
}
}
.relation-views-left { .relation-views-left {
ul:has(.relation-views-node-checkbox) > li > ul { ul:has(.relation-views-node-checkbox) > li > ul {
margin-left: 26px; margin-left: 26px;
@ -249,5 +322,28 @@ export default {
ul:has(.relation-views-node-checkbox) { ul:has(.relation-views-node-checkbox) {
margin-left: 0 !important; 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> </style>

View File

@ -1,196 +1,194 @@
<template> <template>
<div id="resource_search" :style="{ height: fromCronJob ? `${windowHeight - 48}px` : `${windowHeight - 90}px` }"> <div
class="resource-search"
id="resource_search"
:style="{ height: fromCronJob ? `${windowHeight - 48}px` : `${windowHeight - 64}px` }"
>
<div class="cmdb-views-header"> <div class="cmdb-views-header">
<span> <span>
<span class="cmdb-views-header-title">{{ $t('cmdb.menu.ciSearch') }}</span> <span class="cmdb-views-header-title">{{ $t('cmdb.menu.ciSearch') }}</span>
</span> </span>
</div> <a-button
<div :style="{ backgroundColor: '#fff', padding: '12px', borderRadius: '15px' }">
<SearchForm
ref="search"
type="resourceSearch"
@refresh="handleSearch"
:preferenceAttrList="allAttributesList"
@updateAllAttributesList="updateAllAttributesList"
@copyExpression="copyExpression"
/>
<div
v-if="!fromCronJob" v-if="!fromCronJob"
:style="{ icon="download"
display: 'flex', type="primary"
justifyContent: 'space-between', class="ops-button-ghost"
height: '32px', ghost
marginBottom: '5px', @click="handleExport"
alignItems: 'center', >{{ $t('download') }}</a-button
}"
>
<a-button icon="download" type="primary" ghost size="small" @click="handleExport">{{
$t('download')
}}</a-button>
<PreferenceSearch
ref="preferenceSearch"
@getQAndSort="getQAndSort"
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
/>
</div>
<vxe-table
:id="`cmdb-resource`"
border
keep-source
show-overflow
resizable
ref="xTable"
size="small"
row-id="_id"
:loading="loading"
:height="fromCronJob ? windowHeight - 180 : windowHeight - 250"
show-header-overflow
highlight-hover-row
:data="instanceList"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="handleSortCol"
:row-key="true"
:column-key="true"
:cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }"
:export-config="{
isColgroup: true,
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
mode: 'current',
modes: ['current'],
isFooter: false,
isHeader: true,
isColgroup: true,
}"
class="ops-unstripe-table"
:style="{ margin: '0 -12px' }"
:custom-config="{ storage: true }"
> >
</div>
<SearchForm
ref="search"
type="resourceSearch"
@refresh="handleSearch"
:preferenceAttrList="allAttributesList"
@updateAllAttributesList="updateAllAttributesList"
@copyExpression="copyExpression"
>
<PreferenceSearch
v-if="!fromCronJob"
ref="preferenceSearch"
@getQAndSort="getQAndSort"
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
/>
</SearchForm>
<vxe-table
:id="`cmdb-resource`"
border
keep-source
show-overflow
resizable
ref="xTable"
size="small"
row-id="_id"
:loading="loading"
:height="fromCronJob ? windowHeight - 180 : windowHeight - 240"
show-header-overflow
highlight-hover-row
:data="instanceList"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="handleSortCol"
:row-key="true"
:column-key="true"
:cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }"
:export-config="{
isColgroup: true,
type: 'xlsx',
types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
mode: 'current',
modes: ['current'],
isFooter: false,
isHeader: true,
isColgroup: true,
}"
class="ops-unstripe-table"
:custom-config="{ storage: true }"
>
<vxe-column
v-if="instanceList.length"
:title="$t('cmdb.ciType.ciType')"
field="ci_type_alias"
:width="100"
fixed="left"
></vxe-column>
<vxe-colgroup v-for="colGroup in columnsGroup" :key="colGroup.value" :title="colGroup.label">
<template #header>
<span :style="{ display: 'inline-flex', alignItems: 'center' }">
{{ colGroup.label }}
<EditAttrsPopover
:style="{ borderLeft: 'none', width: '30px', height: '38px', cursor: 'pointer' }"
v-if="colGroup.isCiType"
:typeId="Number(colGroup.id.split('-')[1])"
@refresh="loadInstance"
/>
</span>
</template>
<vxe-column <vxe-column
v-if="instanceList.length" v-for="(col, index) in colGroup.children"
:title="$t('cmdb.ciType.ciType')" :key="`${col.field}_${index}`"
field="ci_type_alias" :title="col.title"
:width="100" :field="col.field"
fixed="left" :width="col.width"
></vxe-column> :minWidth="100"
<vxe-colgroup v-for="colGroup in columnsGroup" :key="colGroup.value" :title="colGroup.label"> :cell-type="col.value_type === '2' ? 'string' : 'auto'"
<template #header> >
<span :style="{ display: 'inline-flex', alignItems: 'center' }"> <template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
{{ colGroup.label }} <span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
<EditAttrsPopover <a
:style="{ borderLeft: 'none', width: '30px', height: '38px', cursor: 'pointer' }" v-else-if="col.is_link && row[col.field]"
v-if="colGroup.isCiType" :href="
:typeId="Number(colGroup.id.split('-')[1])" row[col.field].startsWith('http') || row[col.field].startsWith('https')
@refresh="loadInstance" ? `${row[col.field]}`
/> : `http://${row[col.field]}`
</span> "
</template> target="_blank"
<vxe-column >{{ row[col.field] }}</a
v-for="(col, index) in colGroup.children" >
:key="`${col.field}_${index}`" <PasswordField
:title="col.title" v-else-if="col.is_password && row[col.field]"
:field="col.field" :ci_id="row._id"
:width="col.width" :attr_id="col.attr_id"
:minWidth="100" ></PasswordField>
:cell-type="col.value_type === '2' ? 'string' : 'auto'" <template v-else-if="col.is_choice">
> <template v-if="col.is_list">
<template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" #default="{row}">
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
<a
v-else-if="col.is_link && row[col.field]"
:href="
row[col.field].startsWith('http') || row[col.field].startsWith('https')
? `${row[col.field]}`
: `http://${row[col.field]}`
"
target="_blank"
>{{ row[col.field] }}</a
>
<PasswordField
v-else-if="col.is_password && row[col.field]"
:ci_id="row._id"
:attr_id="col.attr_id"
></PasswordField>
<template v-else-if="col.is_choice">
<template v-if="col.is_list">
<span
v-for="value in row[col.field]"
:key="value"
:style="{
borderRadius: '4px',
padding: '1px 5px',
margin: '2px',
...getChoiceValueStyle(col, value),
}"
><ops-icon
:style="{ color: getChoiceValueIcon(col, value).color }"
:type="getChoiceValueIcon(col, value).name"
/>{{ value }}</span
>
</template>
<span <span
v-else v-for="value in row[col.field]"
:key="value"
:style="{ :style="{
borderRadius: '4px', borderRadius: '4px',
padding: '1px 5px', padding: '1px 5px',
margin: '2px 0', margin: '2px',
...getChoiceValueStyle(col, row[col.field]), ...getChoiceValueStyle(col, value),
}" }"
> ><ops-icon
<ops-icon :style="{ color: getChoiceValueIcon(col, value).color }"
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }" :type="getChoiceValueIcon(col, value).name"
:type="getChoiceValueIcon(col, row[col.field]).name" />{{ value }}</span
/>
{{ row[col.field] }}</span
> >
</template> </template>
<span
v-else
:style="{
borderRadius: '4px',
padding: '1px 5px',
margin: '2px 0',
...getChoiceValueStyle(col, row[col.field]),
}"
>
<ops-icon
:style="{ color: getChoiceValueIcon(col, row[col.field]).color }"
:type="getChoiceValueIcon(col, row[col.field]).name"
/>
{{ row[col.field] }}</span
>
</template> </template>
</vxe-column>
</vxe-colgroup>
<template #empty>
<div>
<img :style="{ width: '200px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</template>
<template #loading>
<div style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
</template>
</vxe-table>
<div :style="{ textAlign: 'right', marginTop: '4px' }">
<a-pagination
:showSizeChanger="true"
:current="currentPage"
size="small"
:total="totalNumber"
show-quick-jumper
:page-size="pageSize"
:page-size-options="pageSizeOptions"
@showSizeChange="onShowSizeChange"
:show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
@change="
(page) => {
currentPage = page
loadInstance(sortByTable)
}
"
>
<template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
</template> </template>
</a-pagination> </vxe-column>
</div> </vxe-colgroup>
<template #empty>
<div>
<img :style="{ width: '140px' }" :src="require('@/assets/data_empty.png')" />
<div>{{ $t('noData') }}</div>
</div>
</template>
<template #loading>
<div style="height: 200px; line-height: 200px">{{ $t('loading') }}</div>
</template>
</vxe-table>
<div :style="{ textAlign: 'right', marginTop: '4px' }">
<a-pagination
:showSizeChanger="true"
:current="currentPage"
size="small"
:total="totalNumber"
show-quick-jumper
:page-size="pageSize"
:page-size-options="pageSizeOptions"
@showSizeChange="onShowSizeChange"
:show-total="
(total, range) =>
$t('pagination.total', {
range0: range[0],
range1: range[1],
total,
})
"
@change="
(page) => {
currentPage = page
loadInstance(sortByTable)
}
"
>
<template slot="buildOptionText" slot-scope="props">
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
</template>
</a-pagination>
</div> </div>
<BatchDownload <BatchDownload
@ -227,6 +225,7 @@ export default {
data() { data() {
return { return {
ciTypes: [], ciTypes: [],
originAllAttributesList: [],
allAttributesList: [], // 当前选择的模型的全部attributes 默认全部 allAttributesList: [], // 当前选择的模型的全部attributes 默认全部
currentPage: 1, currentPage: 1,
pageSizeOptions: ['50', '100', '200', '100000'], pageSizeOptions: ['50', '100', '200', '100000'],
@ -260,18 +259,19 @@ export default {
this.ciTypes = res.ci_types this.ciTypes = res.ci_types
}) })
}, },
getAllAttr() { async getAllAttr() {
searchAttributes({ page_size: 9999 }).then((res) => { await searchAttributes({ page_size: 9999 }).then((res) => {
this.allAttributesList = res.attributes this.allAttributesList = res.attributes
this.originAllAttributesList = res.attributes
}) })
}, },
updateAllAttributesList(value) { async updateAllAttributesList(value) {
if (value && value.length) { if (value && value.length) {
getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => { await getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => {
this.allAttributesList = res.attributes this.allAttributesList = res.attributes
}) })
} else { } else {
this.getAllAttr() this.allAttributesList = this.originAllAttributesList
} }
}, },
async loadInstance(sortByTable = undefined) { async loadInstance(sortByTable = undefined) {
@ -373,7 +373,6 @@ export default {
} }
} }
}) })
const _commonColumnsGroup = Object.keys(commonObject).map((key) => { const _commonColumnsGroup = Object.keys(commonObject).map((key) => {
return { return {
id: `parent-${key}`, id: `parent-${key}`,
@ -411,7 +410,7 @@ export default {
return { ...item, id: item.field, label: item.title } return { ...item, id: item.field, label: item.title }
}) })
}, },
handleSearch() { async handleSearch() {
this.currentPage = 1 this.currentPage = 1
this.loadInstance() this.loadInstance()
}, },
@ -534,3 +533,14 @@ export default {
}, },
} }
</script> </script>
<style lang="less" scoped>
@import '~@/style/static.less';
.resource-search {
margin-bottom: -24px;
background-color: #fff;
padding: 20px;
border-radius: @border-radius-box;
}
</style>

View File

@ -4,34 +4,15 @@
<a-alert :message="$t('cmdb.tree.tips1')" banner></a-alert> <a-alert :message="$t('cmdb.tree.tips1')" banner></a-alert>
</div> </div>
<div class="tree-views" v-else> <div class="tree-views" v-else>
<div class="cmdb-views-header">
<span>
<span class="cmdb-views-header-title">{{ currentCiTypeName }}</span>
<span
@click="
() => {
$refs.metadataDrawer.open(typeId)
}
"
class="cmdb-views-header-metadata"
><a-icon type="info-circle" />
{{ $t('cmdb.components.attributeDesc') }}
</span>
</span>
<a-button size="small" icon="plus" type="primary" @click="$refs.create.handleOpen(true, 'create')">{{
$t('create')
}}</a-button>
</div>
<SplitPane <SplitPane
:min="200" :min="200"
:max="500" :max="500"
:paneLengthPixel.sync="paneLengthPixel" :paneLengthPixel.sync="paneLengthPixel"
appName="cmdb-tree-views" appName="cmdb-tree-views"
triggerColor="#F0F5FF"
:triggerLength="18" :triggerLength="18"
> >
<template #one> <template #one>
<div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }"> <div class="tree-views-left" :style="{ height: `${windowHeight - 64}px` }">
<draggable <draggable
v-model="subscribeTreeViewCiTypes" v-model="subscribeTreeViewCiTypes"
:animation="300" :animation="300"
@ -89,12 +70,12 @@
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
v-if="Number(ciType.type_id) === Number(typeId)" v-if="Number(ciType.type_id) === Number(typeId)"
> >
<a-icon slot="switcherIcon" type="down" /> <template #title="{ key: treeKey, title, isLeaf, childLength}">
<template #title="{ key: treeKey, title, isLeaf }">
<TreeViewsNode <TreeViewsNode
:title="title" :title="title"
:treeKey="treeKey" :treeKey="treeKey"
:levels="levels" :levels="levels"
:childLength="childLength"
:isLeaf="isLeaf" :isLeaf="isLeaf"
@onNodeClick="onNodeClick" @onNodeClick="onNodeClick"
/> />
@ -105,16 +86,49 @@
</div> </div>
</template> </template>
<template #two> <template #two>
<div class="tree-views-right" id="tree-views-right" :style="{ height: `${windowHeight - 115}px` }"> <div class="tree-views-right" id="tree-views-right" :style="{ height: `${windowHeight - 64}px` }">
<div class="cmdb-views-header">
<span>
<span class="cmdb-views-header-title">{{ currentCiTypeName }}</span>
<span
@click="
() => {
$refs.metadataDrawer.open(typeId)
}
"
class="cmdb-views-header-metadata"
><a-icon type="info-circle" />
{{ $t('cmdb.ci.attributeDesc') }}
</span>
</span>
<a-space>
<a-button
type="primary"
class="ops-button-ghost"
ghost
@click="$refs.create.handleOpen(true, 'create')"
><ops-icon type="veops-increase" />
{{ $t('create') }}
</a-button>
<EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs">
<a-button
type="primary"
ghost
class="ops-button-ghost"
><ops-icon type="veops-configuration_table" />{{ $t('cmdb.configTable') }}</a-button
>
</EditAttrsPopover>
</a-space>
</div>
<SearchForm <SearchForm
ref="search" ref="search"
@refresh="reloadData" @refresh="reloadData"
:preferenceAttrList="currentAttrList" :preferenceAttrList="currentAttrList"
:typeId="Number(typeId)" :typeId="Number(typeId)"
@copyExpression="copyExpression" @copyExpression="copyExpression"
/> >
<div class="tree-views-right-bar">
<PreferenceSearch <PreferenceSearch
v-show="!selectedRowKeys.length"
ref="preferenceSearch" ref="preferenceSearch"
@getQAndSort="getQAndSort" @getQAndSort="getQAndSort"
@setParamsFromPreferenceSearch="setParamsFromPreferenceSearch" @setParamsFromPreferenceSearch="setParamsFromPreferenceSearch"
@ -129,7 +143,7 @@
<span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span>
</template> </template>
</div> </div>
</div> </SearchForm>
<ops-table <ops-table
:id="`cmdb-tree-${typeId}`" :id="`cmdb-tree-${typeId}`"
border border
@ -150,7 +164,7 @@
:cell-style="getCellStyle" :cell-style="getCellStyle"
:scroll-y="{ enabled: true, gt: 20 }" :scroll-y="{ enabled: true, gt: 20 }"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
:height="`${windowHeight - 252}px`" :height="`${windowHeight - 240}px`"
@checkbox-change="onSelectChange" @checkbox-change="onSelectChange"
@checkbox-all="onSelectChange" @checkbox-all="onSelectChange"
@checkbox-range-end="onSelectRangeEnd" @checkbox-range-end="onSelectRangeEnd"
@ -159,7 +173,6 @@
@edit-actived="handleEditActived" @edit-actived="handleEditActived"
:edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }" :edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }"
class="ops-unstripe-table" class="ops-unstripe-table"
:style="{ margin: '0 -12px' }"
:custom-config="{ storage: true }" :custom-config="{ storage: true }"
> >
<vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column> <vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column>
@ -294,7 +307,6 @@
<vxe-table-column align="left" field="operate" fixed="right" width="120"> <vxe-table-column align="left" field="operate" fixed="right" width="120">
<template #header> <template #header>
<span>{{ $t('operation') }}</span> <span>{{ $t('operation') }}</span>
<EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs" />
</template> </template>
<template #default="{ row }"> <template #default="{ row }">
<a-space> <a-space>
@ -693,7 +705,8 @@ export default {
console.log('facet', facet) console.log('facet', facet)
const _treeData = Object.values(facet)[0].map((item) => { const _treeData = Object.values(facet)[0].map((item) => {
return { return {
title: `${item[0]} (${item[1]})`, title: item[0],
childLength: item[1],
key: this.treeKeys.join(this.keySplit) + this.keySplit + item[0], key: this.treeKeys.join(this.keySplit) + this.keySplit + item[0],
isLeaf: this.levels.length - 1 === this.treeKeys.length, isLeaf: this.levels.length - 1 === this.treeKeys.length,
} }
@ -1228,12 +1241,8 @@ export default {
.tree-views-left { .tree-views-left {
float: left; float: left;
position: relative; position: relative;
background-color: #fff;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
padding: 12px;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
&:hover { &:hover {
overflow: auto; overflow: auto;
} }
@ -1249,7 +1258,7 @@ export default {
border-radius: 2px; border-radius: 2px;
position: relative; position: relative;
&:hover { &:hover {
background-color: #f0f5ff; background-color: @primary-color_3;
> .actions, > .actions,
> .move-icon { > .move-icon {
display: inherit; display: inherit;
@ -1270,9 +1279,7 @@ export default {
width: 20px; width: 20px;
height: 20px; height: 20px;
border-radius: 2px; border-radius: 2px;
box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2);
margin-right: 6px; margin-right: 6px;
background-color: #fff;
} }
.tree-views-left-header-name { .tree-views-left-header-name {
flex: 1; flex: 1;
@ -1281,6 +1288,7 @@ export default {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
color: @text-color_1;
} }
.actions { .actions {
display: none; display: none;
@ -1298,7 +1306,7 @@ export default {
} }
} }
.custom-header-selected { .custom-header-selected {
background-color: #d3e3fd !important; background-color: @primary-color_3 !important;
} }
.ant-tree li { .ant-tree li {
padding: 2px 0; padding: 2px 0;
@ -1317,23 +1325,18 @@ export default {
padding: 0 6px; padding: 0 6px;
} }
} }
.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected {
background-color: @primary-color_3;
}
} }
.tree-views-right { .tree-views-right {
background-color: #fff; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 12px; padding: 20px;
overflow: auto; overflow: auto;
width: 100%; width: 100%;
border-radius: 15px; border-radius: @border-radius-box;
.tree-views-right-bar {
display: inline-flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-bottom: 10px;
height: 36px;
}
} }
} }
</style> </style>

View File

@ -1,15 +1,11 @@
<template> <template>
<div <div @click="clickNode" class="tree-views-node">
:style="{ <a-icon v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
width: '100%', <div v-else></div>
display: 'inline-flex', <div class="tree-views-node-content">
justifyContent: 'space-between', <span>{{ this.title }}</span>
alignItems: 'center', <span>{{ childLength }}</span>
}" </div>
@click="clickNode"
>
<span :style="{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }">{{ this.title }}</span>
<a-icon :style="{ fontSize: '10px', color: '#0C3CFF' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon>
</div> </div>
</template> </template>
@ -18,7 +14,7 @@ export default {
name: 'TreeViewsNode', name: 'TreeViewsNode',
props: { props: {
title: { title: {
type: String, type: [String, Number],
default: '', default: '',
}, },
treeKey: { treeKey: {
@ -33,25 +29,57 @@ export default {
type: Boolean, type: Boolean,
default: () => false, default: () => false,
}, },
childLength: {
type: Number,
default: 0,
},
}, },
data() { data() {
return { return {
switchIcon: 'down', switchIcon: 'caret-right',
} }
}, },
computed: { computed: {},
childLength() {
const reg = /(?<=\()\S+(?=\))/g
return Number(this.title.match(reg)[0])
},
},
methods: { methods: {
clickNode() { clickNode() {
this.$emit('onNodeClick', this.treeKey) this.$emit('onNodeClick', this.treeKey)
this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down' this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right'
}, },
}, },
} }
</script> </script>
<style></style> <style lang="less" scoped>
@import '~@/style/static.less';
.tree-views-node {
width: 100%;
display: inline-flex;
justify-content: space-between;
align-items: center;
> div:first-child {
width: 10px;
}
i {
font-size: 10px;
color: @text-color_5;
}
.tree-views-node-content {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: space-between;
margin-left: 5px;
width: calc(100% - 10px);
> span:first-child {
width: calc(100% - 30px);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: @text-color_1;
}
> span:last-child {
color: @text-color_4;
}
}
}
</style>

View File

@ -50,7 +50,7 @@ body {
} }
.ant-layout { .ant-layout {
background-color: #f7f8fa; background-color: @layout-content-background;
} }
.layout.ant-layout { .layout.ant-layout {
@ -161,7 +161,7 @@ body {
transition: width 0.2s; transition: width 0.2s;
&.ant-header-side-opened { &.ant-header-side-opened {
width: calc(100% - 200px); width: calc(100% - 220px);
} }
&.ant-header-side-closed { &.ant-header-side-closed {
@ -411,7 +411,6 @@ body {
// 菜单样式 // 菜单样式
.sider { .sider {
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
position: relative; position: relative;
z-index: 10; z-index: 10;
min-height: 100vh; min-height: 100vh;
@ -431,19 +430,9 @@ body {
&.ant-fixed-sidemenu { &.ant-fixed-sidemenu {
position: fixed; position: fixed;
height: 100%; height: 100%;
background: @layout-background-color;
z-index: 99; z-index: 99;
} }
.ant-menu-dark {
background: @layout-background-color;
.ant-menu-inline.ant-menu-sub {
background: @layout-background-color-light;
box-shadow: none;
}
}
.logo { .logo {
position: relative; position: relative;
height: @layout-header-height; height: @layout-header-height;
@ -478,21 +467,11 @@ body {
&.light { &.light {
background-color: #fff; background-color: #fff;
box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05);
.logo { .logo {
background: @layout-header-background; background: @layout-header-background;
box-shadow: 1px 1px 0px 0px #e8e8e8; box-shadow: 0 1px 3px 0px #9eabbe25;
} }
// .ant-menu-light {
// .ant-menu-item {
// margin: 0;
// }
// .ant-menu-item-selected {
// background: #0000000a;
// }
// }
} }
} }
@ -506,6 +485,9 @@ body {
// 从此处开始 // 从此处开始
.ops-side-bar.ant-menu { .ops-side-bar.ant-menu {
transition: none; transition: none;
.ant-menu-item {
text-overflow: initial !important;
}
} }
.ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { .ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background: @layout-sidebar-selected-color; background: @layout-sidebar-selected-color;
@ -525,7 +507,7 @@ body {
} }
.ops-side-bar.ant-menu-light { .ops-side-bar.ant-menu-light {
border-right-color: transparent; border-right-color: #e8e8e8;
background: @layout-sidebar-color; background: @layout-sidebar-color;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-size: cover; background-size: cover;
@ -538,6 +520,9 @@ body {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: @layout-sidebar-font-color; color: @layout-sidebar-font-color;
i {
color: @text-color_5;
}
} }
&:hover { &:hover {
.scroll { .scroll {
@ -590,8 +575,11 @@ body {
.ant-menu-item-selected { .ant-menu-item-selected {
a, a,
a:hover { a:hover {
color: @layout-sidebar-font-color; color: @layout-sidebar-selected-font-color;
font-weight: 600; font-weight: 600;
i {
color: @layout-sidebar-selected-font-color;
}
} }
} }
.ant-menu-item::after, .ant-menu-item::after,
@ -601,6 +589,9 @@ body {
.ant-menu-submenu { .ant-menu-submenu {
color: @layout-sidebar-font-color; color: @layout-sidebar-font-color;
i {
color: @text-color_5;
}
} }
.ant-menu-submenu-title:hover { .ant-menu-submenu-title:hover {
color: @layout-sidebar-font-color; color: @layout-sidebar-font-color;
@ -613,8 +604,11 @@ body {
} }
.ant-menu-submenu-selected { .ant-menu-submenu-selected {
> .ant-menu-submenu-title { > .ant-menu-submenu-title {
color: @layout-sidebar-font-color; color: @layout-sidebar-selected-font-color;
font-weight: 800; font-weight: 800;
i {
color: @layout-sidebar-selected-font-color;
}
} }
} }
.ant-menu-submenu-content .ant-menu-submenu-active .ant-menu-submenu-title { .ant-menu-submenu-content .ant-menu-submenu-active .ant-menu-submenu-title {
@ -852,7 +846,7 @@ body {
.ant-layout-sider { .ant-layout-sider {
box-shadow: none; box-shadow: none;
.ant-layout-sider-children { .ant-layout-sider-children {
background: @primary-color_5; background: @primary-color_7;
.ant-menu { .ant-menu {
display: none; display: none;
} }
@ -883,7 +877,7 @@ body {
} }
} }
.custom-vue-treeselect__control(@bgColor:@primary-color_5,@border:none) { .custom-vue-treeselect__control(@bgColor:@primary-color_7,@border:none) {
background-color: @bgColor; background-color: @bgColor;
border: @border; border: @border;
} }
@ -956,13 +950,13 @@ body {
//非斑马纹 //非斑马纹
.ops-unstripe-table { .ops-unstripe-table {
.vxe-table--border-line { .vxe-table--border-line {
border: none !important; // border: none !important;
} }
.vxe-table--header-wrapper { .vxe-table--header-wrapper {
background-color: @primary-color_5 !important; background-color: @primary-color_6 !important;
} }
.vxe-header--row .vxe-header--column:hover { .vxe-header--row .vxe-header--column:hover {
background: #2f54eb1f !important; background: @primary-color_3;
} }
} }
.ops-unstripe-table.vxe-table--render-default.border--full { .ops-unstripe-table.vxe-table--render-default.border--full {
@ -983,7 +977,7 @@ body {
border: none !important; border: none !important;
} }
.vxe-table--header-wrapper { .vxe-table--header-wrapper {
background-color: @primary-color_5 !important; background-color: @primary-color_6 !important;
} }
// .vxe-table--header-wrapper.body--wrapper { // .vxe-table--header-wrapper.body--wrapper {
// border-radius: 8px !important; // border-radius: 8px !important;
@ -1010,19 +1004,19 @@ body {
} }
} }
.vxe-header--row .vxe-header--column:hover { .vxe-header--row .vxe-header--column:hover {
background: #2f54eb1f !important; background: @primary-color_3;
} }
} }
.ops-input { .ops-input {
.ant-input, .ant-input,
.ant-time-picker-input { .ant-time-picker-input {
background-color: @primary-color_5; background-color: @primary-color_7;
border: none; border: none;
} }
} }
.ops-input.ant-input { .ops-input.ant-input {
background-color: @primary-color_5; background-color: @primary-color_7;
border: none; border: none;
} }
.ops-input.ant-input[disabled] { .ops-input.ant-input[disabled] {
@ -1030,14 +1024,6 @@ body {
color: #333; color: #333;
cursor: default; cursor: default;
} }
.ops-input-radius {
.ant-input {
border-radius: 20px;
}
}
.ops-input-radius.ant-input {
border-radius: 20px;
}
// vxe-table checkbox 选中 highlight // vxe-table checkbox 选中 highlight
.vxe-table--render-default .vxe-body--row.row--checked, .vxe-table--render-default .vxe-body--row.row--checked,
@ -1110,12 +1096,12 @@ body {
//批量操作 //批量操作
.ops-list-batch-action { .ops-list-batch-action {
display: inline-block; display: inline-block;
background-color: @primary-color_5; background-color: @primary-color_6;
font-size: 12px; font-size: 12px;
color: rgba(0, 0, 0, 0.55); color: @text-color_3;
> span { > span {
display: inline-block; display: inline-block;
padding: 4px 8px; padding: 7px 8px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: @primary-color; color: @primary-color;
@ -1127,7 +1113,7 @@ body {
} }
} }
//tab // card tab
.ops-tab.ant-tabs.ant-tabs-card { .ops-tab.ant-tabs.ant-tabs-card {
.ant-tabs-card-bar { .ant-tabs-card-bar {
margin: 0; margin: 0;
@ -1147,15 +1133,53 @@ body {
} }
} }
//button // line tab
.ops-tab.ant-tabs {
.ant-tabs-bar {
margin-bottom: 20px;
border-bottom: none;
.ant-tabs-nav.ant-tabs-nav-animated,
.ant-tabs-nav.ant-tabs-nav-animated > div:first-child {
border-bottom: 1px solid @border-color-base;
}
.ant-tabs-tab {
color: @text-color_4;
}
.ant-tabs-tab-active {
color: @primary-color;
}
}
}
// button
.ops-button-primary { .ops-button-primary {
background-color: @primary-color_4; background-color: @primary-color_4;
border-color: @primary-color_4; border-color: @primary-color_4;
color: @primary-color; color: @primary-color;
box-shadow: none; box-shadow: none;
} }
// button
.ops-button-ghost.ant-btn-background-ghost.ant-btn-primary {
background-color: @primary-color_5!important;
border-color: @primary-color_8;
}
.ops-button-ghost.ant-btn-background-ghost.ant-btn-primary[disabled] {
border-color: #d9d9d9;
background-color: @primary-color_7!important;
}
//select // button
.ant-btn.ant-btn-icon-only {
&:hover > i {
color: @primary-color;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
> i {
color: #d9d9d9;
}
}
// select
.ops-select { .ops-select {
&.white { &.white {
.ant-select-selection { .ant-select-selection {
@ -1173,10 +1197,6 @@ body {
} }
} }
} }
.ant-select-selection {
background-color: @primary-color_5;
border-color: @primary-color_5;
}
} }
//dropdown //dropdown
@ -1190,9 +1210,7 @@ body {
//modal //modal
.ant-modal-content { .ant-modal-content {
border-radius: 15px;
.ant-modal-header { .ant-modal-header {
border-radius: 15px;
border-bottom: none; border-bottom: none;
.ant-modal-title { .ant-modal-title {
padding-left: 10px; padding-left: 10px;
@ -1328,12 +1346,8 @@ body {
border-bottom-color: @primary-color; border-bottom-color: @primary-color;
} }
} }
// .ant-menu.ant-menu-light {
// &.ops-menu { .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected,
// background-color: white; .ant-tree li .ant-tree-node-content-wrapper:hover {
// background: none; background-color: @primary-color_3;
// .ant-menu-submenu { }
// color: rgba(0, 0, 0, 0.65);
// }
// }
// }

View File

@ -1,41 +1,48 @@
@border-radius-base: 2px; // 组件/浮层圆角 @border-radius-base: 2px; // 组件/浮层圆角
@border-radius-box: 4px; // big box radius @border-radius-box: 4px; // big box radius
@primary-color: #2f54eb; // 全局主色 六大品牌色 // primary color
@primary-color: #2f54eb;
@primary-color_2: #7f97fa; @primary-color_2: #7f97fa;
@primary-color_3: #d3e3fd; @primary-color_3: #ebeff8;
@primary-color_4: #e1efff; @primary-color_4: #e1efff;
@primary-color_5: #f0f5ff; @primary-color_5: #f0f5ff;
@primary-color_6: #f9fbff; @primary-color_6: #f9fbff;
@primary-color_7: #f7f8fa;
@primary-color_8: #b1c9ff;
@text-color_1: #1d2119; // Neutral color
@text-color_1: #1d2129;
@text-color_2: #4e5969; @text-color_2: #4e5969;
@text-color_3: #86909c; @text-color_3: #86909c;
@text-color_4: #a5a9bc; @text-color_4: #a5a9bc;
@text-color_5: #cacdd9;
@text-color_6: #e4e7ed;
@text-color_7: #f0f1f5;
@border-color: #e4e7ed; @func-color_1: #fd4c6a;
@func-color_2: #ff7d00;
@func-color_3: #00b42a;
@func-color_4: #fad337;
@border-color-base: @text-color_6; // 边框色
@scrollbar-color: rgba(47, 122, 235, 0.2); @scrollbar-color: rgba(47, 122, 235, 0.2);
@layout-content-background: @primary-color_7;
@layout-header-background: #fff; @layout-header-background: #fff;
@layout-header-height: 40px; @layout-header-height: 40px;
@layout-header-icon-height: 34px; @layout-header-icon-height: 34px;
@layout-header-font-color: #020000; @layout-header-font-color: #020000;
@layout-header-font-selected-color: #2857bc; @layout-header-font-selected-color: @primary-color;
//dark @layout-sidebar-color: #ffffff; //bg
@layout-background-color: #182132; @layout-sidebar-sub-color: @primary-color_7; //bg
@layout-background-color-light: #262f40; @layout-sidebar-selected-color: @primary-color_5; //selected bg
//light @layout-sidebar-arrow-color: @text-color_4;
@layout-background-light-color: #fafafa; @layout-sidebar-font-color: @text-color_2;
@layout-background-light-color-light: #f0f0f0; @layout-sidebar-selected-font-color: @primary-color;
@layout-sidebar-disabled-font-color: @text-color_4;
@layout-sidebar-color: #1d264c; //bg
@layout-sidebar-sub-color: #000c37; //bg
@layout-sidebar-selected-color: #3e4a71; //selected bg
@layout-sidebar-arrow-color: rgba(255, 255, 255, 0.6);
@layout-sidebar-font-color: #fff;
@layout-sidebar-disabled-font-color: rgba(255, 255, 255, 0.6);
#custom_colors() { #custom_colors() {
color_1: #2f54eb; //primary color color_1: #2f54eb; //primary color
@ -47,7 +54,7 @@
cursor: pointer; cursor: pointer;
padding: 5px 8px; padding: 5px 8px;
background-color: @backgroundColor; background-color: @backgroundColor;
border-radius: 5px; border-radius: @border-radius-box;
height: 30px; height: 30px;
color: rgba(0, 0, 0, 0.6); color: rgba(0, 0, 0, 0.6);
white-space: nowrap; white-space: nowrap;
@ -57,10 +64,10 @@
cursor: pointer; cursor: pointer;
padding: 5px 10px; padding: 5px 10px;
&:hover { &:hover {
background-color: @primary-color_5; background-color: @primary-color_3;
} }
} }
.ops_popover_item_selected() { .ops_popover_item_selected() {
background-color: @primary-color_5; background-color: @primary-color_3;
color: @primary-color; color: @primary-color;
} }

View File

@ -17,8 +17,12 @@
<a-space> <a-space>
<a-button :loading="loading" type="primary" @click="handleSave">{{ $t('save') }}</a-button> <a-button :loading="loading" type="primary" @click="handleSave">{{ $t('save') }}</a-button>
<template v-if="item.value === 'LDAP'"> <template v-if="item.value === 'LDAP'">
<a-button :loading="loading" ghost type="primary" @click="handleTest('connect')">{{ $t('cs.auth.testConnect') }}</a-button> <a-button :loading="loading" ghost type="primary" @click="handleTest('connect')">{{
<a-button :loading="loading" ghost type="primary" @click="handleTest('login')">{{ $t('cs.auth.testLogin') }}</a-button> $t('cs.auth.testConnect')
}}</a-button>
<a-button :loading="loading" ghost type="primary" @click="handleTest('login')">{{
$t('cs.auth.testLogin')
}}</a-button>
</template> </template>
<a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button> <a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button>
</a-space> </a-space>
@ -56,28 +60,28 @@ export default {
computed: { computed: {
authList() { authList() {
return [ return [
{ {
value: 'LDAP', value: 'LDAP',
label: 'LDAP', label: 'LDAP',
}, },
{ {
value: 'CAS', value: 'CAS',
label: 'CAS', label: 'CAS',
}, },
{ {
value: 'OAUTH2', value: 'OAUTH2',
label: 'OAUTH2', label: 'OAUTH2',
}, },
{ {
value: 'OIDC', value: 'OIDC',
label: 'OIDC', label: 'OIDC',
}, },
{ {
value: 'AuthCommonConfig', value: 'AuthCommonConfig',
label: this.$t('cs.auth.common'), label: this.$t('cs.auth.common'),
}, },
] ]
} },
}, },
methods: { methods: {
getAuthDataEnable() { getAuthDataEnable() {
@ -129,7 +133,7 @@ export default {
...values, ...values,
} }
} }
testLDAP(type, { data: _data }) testLDAP({ data: _data, test_type: type })
.then((res) => { .then((res) => {
this.$message.success(this.$t('cs.auth.testSuccess')) this.$message.success(this.$t('cs.auth.testSuccess'))
}) })

View File

@ -1,10 +1,12 @@
<template> <template>
<div class="ops-login"> <div class="ops-login">
<div class="ops-login-left"> <div class="ops-login-left">
<span>维易科技<br />让运维更简单</span> <span>OneOps统一运维平台</span>
<img src="../../assets/login_img.png" />
</div> </div>
<div class="ops-login-right"> <div class="ops-login-right">
<img src="../../assets/logo_VECMDB.png" /> <img src="../../assets/OneOps.png" />
<div class="ops-login-right-welcome"><span>欢迎登录</span></div>
<a-form <a-form
id="formLogin" id="formLogin"
ref="formLogin" ref="formLogin"
@ -43,7 +45,7 @@
<a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox> <a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox>
</a-form-item> </a-form-item>
<a-form-item style="margin-top: 24px"> <a-form-item style="margin-top:24px">
<a-button <a-button
size="large" size="large"
type="primary" type="primary"
@ -53,13 +55,18 @@
:disabled="state.loginBtn" :disabled="state.loginBtn"
>登录</a-button >登录</a-button
> >
<a-checkbox
v-if="enable_list && enable_list.length === 1 && enable_list[0].auth_type === 'LDAP'"
v-model="auth_with_ldap"
>LDAP</a-checkbox
>
</a-form-item> </a-form-item>
</a-form> </a-form>
<template v-if="_enable_list && _enable_list.length >= 1"> <template v-if="_enable_list && _enable_list.length >= 1">
<a-divider style="font-size:14px">其他登录方式</a-divider> <a-divider style="font-size:14px">其他登录方式</a-divider>
<div style="text-align:center"> <div style="text-align:center">
<span v-for="(item, index) in _enable_list" :key="item.auth_type"> <span v-for="(item, index) in _enable_list" :key="item.auth_type">
<ops-icon :type="item.auth_type"/> <ops-icon :type="item.auth_type" />
<a @click="otherLogin(item.auth_type)">{{ item.auth_type }}</a> <a @click="otherLogin(item.auth_type)">{{ item.auth_type }}</a>
<a-divider v-if="index < _enable_list.length - 1" type="vertical" /> <a-divider v-if="index < _enable_list.length - 1" type="vertical" />
</span> </span>
@ -93,6 +100,7 @@ export default {
loginType: 0, loginType: 0,
smsSendBtn: false, smsSendBtn: false,
}, },
auth_with_ldap: false,
} }
}, },
computed: { computed: {
@ -104,6 +112,18 @@ export default {
return this.enable_list.filter((en) => en.auth_type !== 'LDAP') return this.enable_list.filter((en) => en.auth_type !== 'LDAP')
}, },
}, },
watch: {
enable_list: {
immediate: true,
handler(newVal) {
if (newVal && newVal.length === 1 && newVal[0].auth_type === 'LDAP') {
this.auth_with_ldap = true
} else {
this.auth_with_ldap = false
}
},
},
},
created() {}, created() {},
async mounted() { async mounted() {
await this.GetAuthDataEnable() await this.GetAuthDataEnable()
@ -124,10 +144,12 @@ export default {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault() e.preventDefault()
const { const {
enable_list,
form: { validateFields }, form: { validateFields },
state, state,
customActiveKey, customActiveKey,
Login, Login,
auth_with_ldap,
} = this } = this
state.loginBtn = true state.loginBtn = true
@ -136,11 +158,15 @@ export default {
validateFields(validateFieldsKey, { force: true }, (err, values) => { validateFields(validateFieldsKey, { force: true }, (err, values) => {
if (!err) { if (!err) {
console.log('login form', values)
const loginParams = { ...values } const loginParams = { ...values }
delete loginParams.username delete loginParams.username
loginParams.username = values.username loginParams.username = values.username
loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password loginParams.password = appConfig.useEncryption ? md5(values.password) : values.password
loginParams.auth_with_ldap =
enable_list && enable_list.length === 1 && enable_list[0].auth_type === 'LDAP'
? Number(auth_with_ldap)
: undefined
localStorage.setItem('ops_auth_type', '') localStorage.setItem('ops_auth_type', '')
Login({ userInfo: loginParams }) Login({ userInfo: loginParams })
.then((res) => this.loginSuccess(res)) .then((res) => this.loginSuccess(res))
@ -184,6 +210,13 @@ export default {
background: url('../../assets/login_bg.png') no-repeat; background: url('../../assets/login_bg.png') no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
> img {
width: 80%;
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
}
> span { > span {
color: white; color: white;
position: absolute; position: absolute;
@ -191,7 +224,6 @@ export default {
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
font-size: 1.75vw; font-size: 1.75vw;
text-align: center;
} }
} }
.ops-login-right { .ops-login-right {
@ -202,6 +234,14 @@ export default {
width: 70%; width: 70%;
margin-left: 15%; margin-left: 15%;
} }
.ops-login-right-welcome {
text-align: center;
color: rgba(29, 57, 196, 1);
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-size: 1.25vw;
}
.login-button { .login-button {
width: 100%; width: 100%;
} }