diff --git a/cmdb-ui/src/api/auth.js b/cmdb-ui/src/api/auth.js index 4ac0a24..31aab7e 100644 --- a/cmdb-ui/src/api/auth.js +++ b/cmdb-ui/src/api/auth.js @@ -30,9 +30,9 @@ export function getAuthDataEnable() { }) } -export function testLDAP(test_type, data) { +export function testLDAP(data) { return axios({ - url: `/common-setting/v1/auth_config/LDAP/test?test_type=${test_type}`, + url: `/common-setting/v1/auth_config/LDAP/test`, method: 'post', data, }) diff --git a/cmdb-ui/src/assets/data_empty.png b/cmdb-ui/src/assets/data_empty.png index a6529f8..562d85b 100644 Binary files a/cmdb-ui/src/assets/data_empty.png and b/cmdb-ui/src/assets/data_empty.png differ diff --git a/cmdb-ui/src/components/CMDBFilterComp/expression.vue b/cmdb-ui/src/components/CMDBFilterComp/expression.vue index 2b0fbef..5b43f41 100644 --- a/cmdb-ui/src/components/CMDBFilterComp/expression.vue +++ b/cmdb-ui/src/components/CMDBFilterComp/expression.vue @@ -178,7 +178,7 @@ <div v-else :style="{ width: '175px' }"></div> <template v-if="!disabled"> <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 :title="$t('delete')"> <a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> diff --git a/cmdb-ui/src/components/CustomIconSelect/constants.js b/cmdb-ui/src/components/CustomIconSelect/constants.js index cb49c52..9af14b9 100644 --- a/cmdb-ui/src/components/CustomIconSelect/constants.js +++ b/cmdb-ui/src/components/CustomIconSelect/constants.js @@ -759,6 +759,52 @@ export const multicolorIconList = [ value: 'caise-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', label: '操作系统', diff --git a/cmdb-ui/src/components/Menu/SideMenu.vue b/cmdb-ui/src/components/Menu/SideMenu.vue index c05ef73..032bdf2 100644 --- a/cmdb-ui/src/components/Menu/SideMenu.vue +++ b/cmdb-ui/src/components/Menu/SideMenu.vue @@ -1,7 +1,7 @@ <template> <a-layout-sider :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null]" - width="200px" + width="220px" :collapsible="collapsible" v-model="collapsed" :trigger="null" @@ -15,6 +15,7 @@ @select="onSelect" style="padding: 16px 0px;" ></s-menu> + <!-- <OpsDocs :collapsed="collapsed" /> --> </a-layout-sider> </template> @@ -22,10 +23,13 @@ import Logo from '@/components/tools/Logo' import SMenu from './index' import { mixin, mixinDevice } from '@/utils/mixin' +// import OpsDocs from '@/modules/docs/index.vue' export default { name: 'SideMenu', - components: { Logo, SMenu }, + components: { Logo, SMenu, + // OpsDocs + }, mixins: [mixin, mixinDevice], props: { mode: { diff --git a/cmdb-ui/src/components/OpsTable/index.vue b/cmdb-ui/src/components/OpsTable/index.vue index ee359c3..11fa56e 100644 --- a/cmdb-ui/src/components/OpsTable/index.vue +++ b/cmdb-ui/src/components/OpsTable/index.vue @@ -1,121 +1,49 @@ <template> - <vxe-table v-bind="$attrs" v-on="new$listeners" ref="xTable"> - <slot></slot> - <template #empty> - <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> + <span> + <ops-icon :type="getPropertyIcon(attr)" /> + </span> </template> <script> -import _ from 'lodash' -// 该组件使用方法与vxe-table一致,但调用它的方法时,需先调用getVxetableRef()获取到vxe-table实体 export default { - name: 'OpsTable', - data() { - return { - // isShifting: false, - // lastIndex: -1, - 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, - }) + name: 'ValueTypeIcon', + props: { + attr: { + type: Object, + default: () => {}, }, }, - 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: { - getVxetableRef() { - return this.$refs.xTable - }, - // selectChangeEvent(e) { - // const xTable = this.$refs.xTable - // const { lastIndex } = this - // const currentIndex = e.rowIndex - // const { tableData } = xTable.getTableData() - // if (lastIndex > -1 && this.isShifting) { - // let start = lastIndex - // let end = currentIndex - // if (lastIndex > currentIndex) { - // start = currentIndex - // end = lastIndex - // } - // const rangeData = tableData.slice(start, end + 1) - // xTable.setCheckboxRow(rangeData, true) - // } - // this.lastIndex = currentIndex - // this.$emit('checkbox-change', { ...e, records: xTable.getCheckboxRecords() }) - // }, - checkboxRangeStart(e) { - const xTable = this.$refs.xTable - const lastSelected = xTable.getCheckboxRecords() - const selectedReserve = xTable.getCheckboxReserveRecords() - this.lastSelected = [...lastSelected, ...selectedReserve] - 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) + 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' } - this.currentSelected = [] - this.lastSelected = [] - this.$emit('checkbox-range-end', { - ...e, - records: [...xTable.getCheckboxRecords(), ...xTable.getCheckboxReserveRecords()], - }) }, }, } </script> -<style lang="less"></style> +<style></style> diff --git a/cmdb-ui/src/components/SplitPane/SplitPane.vue b/cmdb-ui/src/components/SplitPane/SplitPane.vue index 8a7a21d..62a9a4e 100644 --- a/cmdb-ui/src/components/SplitPane/SplitPane.vue +++ b/cmdb-ui/src/components/SplitPane/SplitPane.vue @@ -64,7 +64,7 @@ export default { }, triggerColor: { type: String, - default: '#f0f2f5', + default: '#f7f8fa', }, }, data() { diff --git a/cmdb-ui/src/components/TwoColumnLayout/TwoColumnLayout.vue b/cmdb-ui/src/components/TwoColumnLayout/TwoColumnLayout.vue index 351e83e..8dd4f9b 100644 --- a/cmdb-ui/src/components/TwoColumnLayout/TwoColumnLayout.vue +++ b/cmdb-ui/src/components/TwoColumnLayout/TwoColumnLayout.vue @@ -35,7 +35,7 @@ export default { }, triggerColor: { type: String, - default: '#F0F5FF', + default: '#f7f8fa', }, }, data() { @@ -52,22 +52,22 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .two-column-layout { margin-bottom: -24px; width: 100%; .two-column-layout-sidebar { height: 100%; - padding: 15px 7px; border-radius: 15px; overflow-y: auto; - background-color: #fff; } .two-column-layout-main { height: 100%; padding: 12px; background-color: #fff; overflow-y: auto; - border-radius: 15px; + border-radius: @border-radius-box; } } </style> diff --git a/cmdb-ui/src/components/tools/TopMenu.vue b/cmdb-ui/src/components/tools/TopMenu.vue index 52a66be..a84f580 100644 --- a/cmdb-ui/src/components/tools/TopMenu.vue +++ b/cmdb-ui/src/components/tools/TopMenu.vue @@ -11,9 +11,9 @@ :key="route.name" @click="() => handleClick(route)" > - {{ route.meta.title }} + {{ $t(route.meta.title) }} </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"> <div class="title"> 更多应用 @@ -36,20 +36,31 @@ </div> </template> <span class="top-menu-icon"><gridSvg /></span> - </a-popover> --> + </a-popover> </div> </template> <script> import store from '@/store' import { gridSvg, top_agent, top_acl } from '@/core/icons' +import { getPreference } from '@/modules/cmdb/api/preference' export default { name: 'TopMenu', components: { gridSvg, top_agent, top_acl }, data() { return { - defaultShowRouteName: ['cmdb', 'acl'], - defaultUnShowRouteName: [], + defaultShowRouteName: [ + 'dag', + 'cmdb', + 'itsm', + 'ticket', + 'monitor', + 'calendar', + 'datainsight', + 'fullscreen', + 'oneterm', + ], + defaultUnShowRouteName: ['acl', 'agent'], routes: store.getters.appRoutes.filter((i) => !(i.meta || {}).hiddenInTopMenu), current: store.getters.appRoutes[0].name, visible: false, @@ -78,10 +89,20 @@ export default { this.current = this.$route.matched[0].name }, methods: { - handleClick(route) { + async handleClick(route) { this.visible = false 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 } }, @@ -110,33 +131,21 @@ export default { line-height: @layout-header-icon-height; border-radius: 4px !important; display: inline-flex; - align-items: flex-end; + align-items: center; } > span { cursor: pointer; padding: 4px 10px; margin: 0 5px; - border-radius: 4px; color: @layout-header-font-color; height: @layout-header-height; display: inline-flex; 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 { - 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; - 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; - } } } diff --git a/cmdb-ui/src/layouts/BasicLayout.vue b/cmdb-ui/src/layouts/BasicLayout.vue index 53fd34c..42b005e 100644 --- a/cmdb-ui/src/layouts/BasicLayout.vue +++ b/cmdb-ui/src/layouts/BasicLayout.vue @@ -87,21 +87,21 @@ export default { computed: { ...mapState({ // 动态主路由 - mainMenu: state => state.routes.appRoutes, + mainMenu: (state) => state.routes.appRoutes, }), contentPaddingLeft() { if (!this.fixSidebar || this.isMobile()) { return '0' } if (this.sidebarOpened) { - return '200px' + return '220px' } return '80px' }, 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) { - return sideMenus.find(item => item.path !== '/').children + return sideMenus.find((item) => item.path !== '/').children } else { return sideMenus[0].children } @@ -110,6 +110,9 @@ export default { provide() { return { reloadBoard: this.reload, + collapsed: () => { + return this.collapsed + }, } }, watch: { @@ -146,15 +149,6 @@ export default { 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() { if (!this.isDesktop()) { this.collapsed = false diff --git a/cmdb-ui/src/modules/acl/views/history.vue b/cmdb-ui/src/modules/acl/views/history.vue index be77b2e..386e50a 100644 --- a/cmdb-ui/src/modules/acl/views/history.vue +++ b/cmdb-ui/src/modules/acl/views/history.vue @@ -233,8 +233,10 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-history { - border-radius: 15px; + border-radius: @border-radius-box; height: calc(100vh - 64px); margin-bottom: -24px; padding: 24px; diff --git a/cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue b/cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue index 8e7e10b..5d03ce0 100644 --- a/cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue +++ b/cmdb-ui/src/modules/acl/views/module/roleHistoryTable.vue @@ -290,7 +290,6 @@ export default { const str = ` 【 ${key} : -> ${newVal} 】 ` item.description += str } else { - const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 ` item.description += ` 【 ${key} : ${oldVal} -> ${newVal} 】 ` } } diff --git a/cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue b/cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue index 53a9fc6..644f61f 100644 --- a/cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue +++ b/cmdb-ui/src/modules/acl/views/module/triggerHistoryTable.vue @@ -241,7 +241,6 @@ export default { const str = ` 【 ${key} : -> ${newVal} 】 ` item.changeDescription += str } else { - const str = ` 【 ${key} : ${oldVal} -> ${newVal} 】 ` item.changeDescription += ` 【 ${key} : ${oldVal} -> ${newVal} 】 ` } } diff --git a/cmdb-ui/src/modules/acl/views/operation_history/index.vue b/cmdb-ui/src/modules/acl/views/operation_history/index.vue index 342a14c..4b6b4f2 100644 --- a/cmdb-ui/src/modules/acl/views/operation_history/index.vue +++ b/cmdb-ui/src/modules/acl/views/operation_history/index.vue @@ -32,8 +32,10 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-operation-history { - border-radius: 15px; + border-radius: @border-radius-box; height: calc(100vh - 64px); margin-bottom: -24px; padding: 24px; diff --git a/cmdb-ui/src/modules/acl/views/resource_types.vue b/cmdb-ui/src/modules/acl/views/resource_types.vue index 15f0321..2fda663 100644 --- a/cmdb-ui/src/modules/acl/views/resource_types.vue +++ b/cmdb-ui/src/modules/acl/views/resource_types.vue @@ -189,8 +189,10 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-resource-types { - border-radius: 15px; + border-radius: @border-radius-box; background-color: #fff; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/acl/views/resources.vue b/cmdb-ui/src/modules/acl/views/resources.vue index 8ecd4fa..bf1a090 100644 --- a/cmdb-ui/src/modules/acl/views/resources.vue +++ b/cmdb-ui/src/modules/acl/views/resources.vue @@ -352,8 +352,10 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-resources { - border-radius: 15px; + border-radius: @border-radius-box; background-color: #fff; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/acl/views/roles.vue b/cmdb-ui/src/modules/acl/views/roles.vue index abf18f7..11ed56e 100644 --- a/cmdb-ui/src/modules/acl/views/roles.vue +++ b/cmdb-ui/src/modules/acl/views/roles.vue @@ -285,8 +285,10 @@ export default { </script> <style lang="less"> +@import '~@/style/static.less'; + .acl-roles { - border-radius: 15px; + border-radius: @border-radius-box; background-color: #fff; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/acl/views/secretKey.vue b/cmdb-ui/src/modules/acl/views/secretKey.vue index 548ead3..3d6b622 100644 --- a/cmdb-ui/src/modules/acl/views/secretKey.vue +++ b/cmdb-ui/src/modules/acl/views/secretKey.vue @@ -88,10 +88,12 @@ export default { </script> <style lang="less"> +@import '~@/style/static.less'; + .acl-secret-key { background-color: #fff; padding: 24px; - border-radius: 15px; + border-radius: @border-radius-box; height: calc(100% + 24px); .ant-input[disabled] { color: rgba(0, 0, 0, 0.5); diff --git a/cmdb-ui/src/modules/acl/views/trigger.vue b/cmdb-ui/src/modules/acl/views/trigger.vue index 8724ff3..af5b698 100644 --- a/cmdb-ui/src/modules/acl/views/trigger.vue +++ b/cmdb-ui/src/modules/acl/views/trigger.vue @@ -320,8 +320,10 @@ export default { } </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-trigger { - border-radius: 15px; + border-radius: @border-radius-box; background-color: #fff; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/acl/views/users.vue b/cmdb-ui/src/modules/acl/views/users.vue index 9e688af..f70baa3 100644 --- a/cmdb-ui/src/modules/acl/views/users.vue +++ b/cmdb-ui/src/modules/acl/views/users.vue @@ -188,8 +188,10 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .acl-users { - border-radius: 15px; + border-radius: @border-radius-box; background-color: #fff; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/cmdb/api/preference.js b/cmdb-ui/src/modules/cmdb/api/preference.js index 4dcfc2b..10c7dd6 100644 --- a/cmdb-ui/src/modules/cmdb/api/preference.js +++ b/cmdb-ui/src/modules/cmdb/api/preference.js @@ -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) { // 参数有prv_id: 关系视图的id, ptv_id: 层级视图的id, type_id: 模型id diff --git a/cmdb-ui/src/modules/cmdb/assets/unique_card.png b/cmdb-ui/src/modules/cmdb/assets/unique_card.png new file mode 100644 index 0000000..124fba2 Binary files /dev/null and b/cmdb-ui/src/modules/cmdb/assets/unique_card.png differ diff --git a/cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue b/cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue index 848ebae..124fba2 100644 Binary files a/cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue and b/cmdb-ui/src/modules/cmdb/components/cmdbGrant/grantComp.vue differ diff --git a/cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue b/cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue index c279817..6f4c10c 100644 --- a/cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue +++ b/cmdb-ui/src/modules/cmdb/components/cmdbGrant/index.vue @@ -1,5 +1,11 @@ <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 :resourceType="resourceType" :app_id="app_id" diff --git a/cmdb-ui/src/modules/cmdb/components/preferenceSearch/preferenceSearch.vue b/cmdb-ui/src/modules/cmdb/components/preferenceSearch/preferenceSearch.vue index b1f439e..205fd99 100644 --- a/cmdb-ui/src/modules/cmdb/components/preferenceSearch/preferenceSearch.vue +++ b/cmdb-ui/src/modules/cmdb/components/preferenceSearch/preferenceSearch.vue @@ -11,7 +11,7 @@ @blur="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> <template v-for="(item, index) in preferenceSearchList.slice(0, 3)"> <span @@ -178,10 +178,10 @@ export default { <style lang="less" scoped> .preference-search-tag { cursor: pointer; - border-radius: 5px; - border: none; + border-radius: 2px; + border: 1px solid #d9d9d9; display: inline-block; - padding: 0 7px; + padding: 2px 7px; margin-right: 8px; > span { margin-right: 4px; diff --git a/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue b/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue index 9394345..c0545b9 100644 --- a/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue +++ b/cmdb-ui/src/modules/cmdb/components/searchForm/SearchForm.vue @@ -5,8 +5,15 @@ <a-space> <treeselect v-if="type === 'resourceSearch'" - class="custom-treeselect" - :style="{ width: '250px', marginRight: '10px', '--custom-height': '32px' }" + class="custom-treeselect custom-treeselect-bgcAndBorder" + :style="{ + width: '200px', + marginRight: '10px', + '--custom-height': '32px', + '--custom-bg-color': '#fff', + '--custom-border': '1px solid #d9d9d9', + '--custom-multiple-lineHeight': '16px', + }" v-model="currenCiType" :multiple="true" :clearable="true" @@ -41,15 +48,14 @@ </treeselect> <a-input v-model="fuzzySearch" - :style="{ display: 'inline-block', width: '244px' }" + :style="{ display: 'inline-block', width: '200px' }" :placeholder="$t('cmdb.components.pleaseSearch')" @pressEnter="emitRefresh" - class="ops-input ops-input-radius" > <a-icon type="search" slot="suffix" - :style="{ color: fuzzySearch ? '#2f54eb' : '', cursor: 'pointer' }" + :style="{ color: fuzzySearch ? '#2f54eb' : '#d9d9d9', cursor: 'pointer' }" @click="emitRefresh" /> <a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px', whiteSpace: 'pre-line' }"> @@ -59,6 +65,9 @@ <a><a-icon type="question-circle"/></a> </a-tooltip> </a-input> + <a-tooltip :title="$t('reset')"> + <a-button @click="reset">重置</a-button> + </a-tooltip> <FilterComp ref="filterComp" :canSearchPreferenceAttrList="canSearchPreferenceAttrList" @@ -69,7 +78,7 @@ <div slot="popover_item" class="search-form-bar-filter"> <a-icon class="search-form-bar-filter-icon" type="filter" /> {{ $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> </FilterComp> <a-input @@ -91,14 +100,13 @@ :placeholder="placeholder" @keyup.enter="emitRefresh" > - <a-icon slot="suffix" type="copy" @click="handleCopyExpression" /> + <ops-icon slot="suffix" type="veops-copy" @click="handleCopyExpression" /> </a-input> <slot></slot> </a-space> </div> <a-space> <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 @click=" @@ -237,7 +245,6 @@ export default { } }, inputCiTypeGroup(value) { - console.log(value) if (!value || !value.length) { this.$emit('updateAllAttributesList', value) } @@ -257,6 +264,7 @@ export default { } </script> <style lang="less"> +@import '~@/style/static.less'; @import '../../views/index.less'; .ci-searchform-expression { > input { @@ -266,14 +274,14 @@ export default { border-right: none; &:hover, &:focus { - border-bottom: 2px solid #2f54eb; + border-bottom: 2px solid @primary-color; } &:focus { box-shadow: 0 2px 2px -2px #1f78d133; } } .ant-input-suffix { - color: #2f54eb; + color: #d9d9d9; cursor: pointer; } } @@ -290,14 +298,15 @@ export default { @import '~@/style/static.less'; .search-form-bar { - margin-bottom: 10px; + margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; + height: 32px; .search-form-bar-filter { - .ops_display_wrapper(); + .ops_display_wrapper(transparent); .search-form-bar-filter-icon { - color: #custom_colors[color_1]; + color: @primary-color; font-size: 12px; } } diff --git a/cmdb-ui/src/modules/cmdb/lang/en.js b/cmdb-ui/src/modules/cmdb/lang/en.js index 820ef4a..d7616a4 100644 --- a/cmdb-ui/src/modules/cmdb/lang/en.js +++ b/cmdb-ui/src/modules/cmdb/lang/en.js @@ -1,6 +1,7 @@ const cmdb_en = { relation: 'Relation', attribute: 'Attributes', + configTable: 'Config Table', menu: { views: 'Views', config: 'Configuration', @@ -182,7 +183,11 @@ const cmdb_en = { inheritType: 'Inherit Type', inheritTypePlaceholder: 'Please select inherit types', 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: { unselectAttributes: 'Unselected', @@ -245,8 +250,10 @@ const cmdb_en = { unselectCIType: 'No CIType selected yet', pleaseUploadFile: 'Please upload files', batchUploadCanceled: 'Batch upload canceled', - selectCITypeTips: 'Please select CIType', + selectCIType: 'Select CIType', + selectCITypeTips: 'Please select a CIType and then download', downloadTemplate: 'Download Template', + clickDownload: 'Click to Download', drawTips: 'Click or drag files here to upload!', supportFileTypes: 'Supported file types: xls, xlsx', uploadResult: 'Upload results', @@ -257,6 +264,16 @@ const cmdb_en = { errorTips: 'Error message', requestFailedTips: 'An error occurred with the request, please try again later', 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: { mySub: 'My Subscription', @@ -274,6 +291,7 @@ const cmdb_en = { monthsAgo: 'month ago', yearsAgo: 'years ago', just: 'just now', + searchPlaceholder: 'Please search CIType', }, custom_dashboard: { charts: 'Chart', @@ -313,14 +331,23 @@ const cmdb_en = { noCustomDashboard: 'The administrator has not customized the dashboard yet', }, preference_relation: { - newServiceTree: 'Add ServiceTree', + newServiceTree: 'Add Service Tree', + editServiceTree: 'Edit Service Tree', serviceTreeName: 'Name', + serviceTreeNamePlaceholder: 'Please enter the service tree name', public: 'Public', saveLayout: 'Save Layout', 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!', tips2: 'Please enter the new serviceTree name!', 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: { ciChange: 'CI', @@ -478,13 +505,15 @@ const cmdb_en = { noPermission: 'No Permission' }, serviceTree: { - deleteNode: 'Delete Node', + deleteNode: 'Delete {name}', tips1: 'For example: q=os_version:centos&sort=os_version', tips2: 'Expression search', alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!', copyFailed: 'Copy failed', deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?', batch: 'Batch', + editNode: 'Edit Node', + editNodeName: 'Edit Node Name', grantTitle: 'Grant(read)', userPlaceholder: 'Please select users', rolePlaceholder: 'Please select roles', diff --git a/cmdb-ui/src/modules/cmdb/lang/zh.js b/cmdb-ui/src/modules/cmdb/lang/zh.js index e278f58..b336e4d 100644 --- a/cmdb-ui/src/modules/cmdb/lang/zh.js +++ b/cmdb-ui/src/modules/cmdb/lang/zh.js @@ -1,6 +1,7 @@ const cmdb_zh = { relation: '关系', attribute: '属性', + configTable: '配置表格', menu: { views: '视图', config: '配置', @@ -35,7 +36,7 @@ const cmdb_zh = { attributeLibray: '属性库', addCITypeInGroup: '在该组中新增CI模型', addCIType: '新增CI模型', - editGroupName: '编辑组名称', + editGroupName: '重命名分组', deleteGroup: '删除该组', CITypeName: '模型名(英文)', English: '英文', @@ -182,7 +183,11 @@ const cmdb_zh = { inheritType: '继承模型', inheritTypePlaceholder: '请选择继承模型(多选)', inheritFrom: '属性继承自{name}', - groupInheritFrom: '请至{name}进行修改' + groupInheritFrom: '请至{name}进行修改', + downloadType: '下载模型', + deleteCIType: '删除模型', + otherGroupTips: '其他分组属性不可排序', + filterTips: '点击可仅查看{name}属性' }, components: { unselectAttributes: '未选属性', @@ -220,7 +225,7 @@ const cmdb_zh = { beforeChange: '变更前', afterChange: '变更后', noticeContentTips: '请输入通知内容', - saveQuery: '保存筛选条件', + saveQuery: '保存条件', pleaseSearch: '请查找', conditionFilter: '条件过滤', attributeDesc: '属性说明', @@ -245,9 +250,10 @@ const cmdb_zh = { unselectCIType: '尚未选择模板类型', pleaseUploadFile: '请上传文件', batchUploadCanceled: '批量上传已取消', - selectCITypeTips: '请选择模板类型', + selectCIType: '选择模型', + selectCITypeTips: '请选择模型后下载模板', downloadTemplate: '下载模板', - drawTips: '点击或拖拽文件至此上传!', + clickDownload: '点击下载', supportFileTypes: '支持文件类型:xls,xlsx', uploadResult: '上传结果', total: '共', @@ -257,6 +263,16 @@ const cmdb_zh = { errorTips: '错误信息', requestFailedTips: '请求出现错误,请稍后再试', 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: { mySub: '我的订阅', @@ -274,6 +290,7 @@ const cmdb_zh = { monthsAgo: '月前', yearsAgo: '年前', just: '刚刚', + searchPlaceholder: '请搜索模型', }, custom_dashboard: { charts: '图表', @@ -314,13 +331,23 @@ const cmdb_zh = { }, preference_relation: { newServiceTree: '新增服务树', + editServiceTree: '编辑服务树', serviceTreeName: '服务树名', + serviceTreeNamePlaceholder: '请输入服务树名', public: '公开', saveLayout: '保存布局', childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!', tips1: '不能与当前选中节点形成视图,请重新选择!', tips2: '请输入新增服务树名!', tips3: '请选择至少两个节点!', + tips4: '叶子节点/树节点信息至少展示一个', + tips5: '选中树目录节点,服务树子节点展示成Table', + showLeafNode: '树的子节点展示成Table', + showTreeNode: '展示树节点信息', + sort: '顺序', + sort1: '树子节点信息在前', + sort2: '树节点信息在前' + }, history: { ciChange: 'CI变更', @@ -443,7 +470,7 @@ const cmdb_zh = { disk: '硬盘', }, ci: { - attributeDesc: '属性说明', + attributeDesc: '查看属性配置', selectRows: '选取:{rows} 项', addRelation: '添加关系', all: '全部', @@ -477,13 +504,15 @@ const cmdb_zh = { noPermission: '暂无权限' }, serviceTree: { - deleteNode: '删除节点', + deleteNode: '移除 {name}', tips1: '例:q=os_version:centos&sort=os_version', tips2: '表达式搜索', alert1: '管理员 还未配置业务关系, 或者你无权限访问!', copyFailed: '复制失败', deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?', batch: '批量操作', + editNode: '编辑节点', + editNodeName: '修改节点名', grantTitle: '授权(查看权限)', userPlaceholder: '请选择用户', rolePlaceholder: '请选择角色', diff --git a/cmdb-ui/src/modules/cmdb/router/index.js b/cmdb-ui/src/modules/cmdb/router/index.js index 54663a9..da8909e 100644 --- a/cmdb-ui/src/modules/cmdb/router/index.js +++ b/cmdb-ui/src/modules/cmdb/router/index.js @@ -156,8 +156,8 @@ const genCmdbRoutes = async () => { const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined if (lastTypeId && preference.some(item => item.id === Number(lastTypeId))) { routes.redirect = `/cmdb/instances/types/${lastTypeId}` - } else if (routes.children[2].children.length > 0) { - routes.redirect = routes.children[2].children.find(item => !item.hidden).path + } else if (routes.children[3]?.children?.length > 0) { + routes.redirect = routes.children[3]?.children.find(item => !item.hidden)?.path } else { routes.redirect = '/cmdb/dashboard' } diff --git a/cmdb-ui/src/modules/cmdb/utils/helper.js b/cmdb-ui/src/modules/cmdb/utils/helper.js index f81fa2b..f4edca6 100644 --- a/cmdb-ui/src/modules/cmdb/utils/helper.js +++ b/cmdb-ui/src/modules/cmdb/utils/helper.js @@ -49,7 +49,6 @@ export function getCITableColumns(data, attrList, width = 1600, height) { const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc']) const columns = [] for (let attr of _attrList) { - const editRender = { name: 'input' } switch (attr.value_type) { case '0': @@ -85,7 +84,7 @@ export function getCITableColumns(data, attrList, width = 1600, height) { } columns.push({ - attr_id:attr.id, + attr_id: attr.id, editRender, title: attr.alias || 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) => { const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc']) if (!_tempData.length) { @@ -185,7 +213,7 @@ export const getAllParentNodesLabel = (node, label) => { return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`) } return label - } - export const getTreeSelectLabel = (node) => { +} +export const getTreeSelectLabel = (node) => { return `${getAllParentNodesLabel(node, node.label)}` - } \ No newline at end of file +} \ No newline at end of file diff --git a/cmdb-ui/src/modules/cmdb/views/batch/index.vue b/cmdb-ui/src/modules/cmdb/views/batch/index.vue index 17eb136..131a8b5 100644 --- a/cmdb-ui/src/modules/cmdb/views/batch/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/batch/index.vue @@ -1,39 +1,41 @@ <template> <div class="cmdb-batch-upload" :style="{ height: `${windowHeight - 64}px` }"> - <div id="title"> - <ci-type-choice ref="ciTypeChoice" @getCiTypeAttr="showCiType" /> + <div class="cmdb-views-header"> + <span> + <span class="cmdb-views-header-title">{{ $t('cmdb.menu.batchUpload') }}</span> + </span> </div> - <a-row> - <a-col :span="12"> - <upload-file-form - :isUploading="isUploading" - :ciType="ciType" - ref="uploadFileForm" - @uploadDone="uploadDone" - ></upload-file-form> - </a-col> - <a-col :span="24" v-if="ciType && uploadData.length"> - <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> - <div class="cmdb-batch-upload-action"> - <a-space size="large"> - <a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button> - <a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button> - <a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button> - </a-space> - </div> - </a-col> - <a-col :span="24" v-if="ciType"> - <upload-result - ref="uploadResult" - :upLoadData="uploadData" - :ciType="ciType" - :unique-field="uniqueField" - :isUploading="isUploading" - @uploadResultDone="uploadResultDone" - @uploadResultError="uploadResultError" - ></upload-result> - </a-col> - </a-row> + <CiTypeChoice ref="ciTypeChoice" @getCiTypeAttr="showCiType" /> + <p class="cmdb-batch-upload-label"><span>*</span>3. {{ $t('cmdb.batch.uploadFile') }}</p> + <UploadFileForm + :isUploading="isUploading" + :ciType="ciType" + ref="uploadFileForm" + @uploadDone="uploadDone" + ></UploadFileForm> + <p class="cmdb-batch-upload-label">4. {{ $t('cmdb.batch.dataPreview') }}</p> + <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> + <div class="cmdb-batch-upload-action"> + <a-space size="large"> + <a-button :disabled="!(ciType && uploadData.length)" @click="handleUpload" type="primary">{{ + $t('upload') + }}</a-button> + <a-button @click="handleCancel">{{ $t('cancel') }}</a-button> + <a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ + $t('cmdb.batch.downloadFailed') + }}</a-button> + </a-space> + </div> + <UploadResult + v-if="ciType" + ref="uploadResult" + :upLoadData="uploadData" + :ciType="ciType" + :unique-field="uniqueField" + :isUploading="isUploading" + @uploadResultDone="uploadResultDone" + @uploadResultError="uploadResultError" + ></UploadResult> </div> </template> @@ -124,7 +126,7 @@ export default { handleCancel() { if (!this.isUploading) { this.showCiType(null) - this.$refs.ciTypeChoice.selectNum = null + this.$refs.ciTypeChoice.selectNum = undefined this.hasError = false } else { this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled')) @@ -144,16 +146,29 @@ export default { }, } </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> +@import '~@/style/static.less'; + .cmdb-batch-upload { margin-bottom: -24px; - padding: 24px; + padding: 20px; background-color: #fff; - border-radius: 20px; + border-radius: @border-radius-box; overflow: auto; .cmdb-batch-upload-action { width: 50%; - text-align: center; margin: 12px 0; } } diff --git a/cmdb-ui/src/modules/cmdb/views/batch/modules/CiTypeChoice.vue b/cmdb-ui/src/modules/cmdb/views/batch/modules/CiTypeChoice.vue index 59c837c..0636534 100644 --- a/cmdb-ui/src/modules/cmdb/views/batch/modules/CiTypeChoice.vue +++ b/cmdb-ui/src/modules/cmdb/views/batch/modules/CiTypeChoice.vue @@ -1,11 +1,11 @@ <template> - <a-space> - <span>{{ $t('cmdb.ciType.ciType') }}: </span> + <div> + <p class="cmdb-batch-upload-label"><span>*</span>1. {{ $t('cmdb.batch.selectCIType') }}</p> <a-select showSearch :placeholder="$t('cmdb.batch.selectCITypeTips')" @change="selectCiType" - :style="{ width: '300px' }" + :style="{ width: '50%', marginBottom: '1em' }" class="ops-select" :filter-option="filterOption" v-model="selectNum" @@ -14,13 +14,16 @@ ciType.alias }}</a-select-option> </a-select> + <p class="cmdb-batch-upload-label"> 2. {{ $t('cmdb.batch.downloadTemplate') }}</p> <a-button + :style="{ marginBottom: '1em' }" @click="openModal" :disabled="!selectNum" type="primary" - class="ops-button-primary" + ghost + class="ops-button-ghost" icon="download" - >{{ $t('cmdb.batch.downloadTemplate') }}</a-button + >{{ $t('cmdb.batch.clickDownload') }}</a-button > <a-modal :bodyStyle="{ paddingTop: 0 }" @@ -88,7 +91,7 @@ </a-row> </template> </a-modal> - </a-space> + </div> </template> <script> @@ -107,7 +110,7 @@ export default { return { ciTypeList: [], ciTypeName: '', - selectNum: null, + selectNum: undefined, selectCiTypeAttrList: [], visible: false, checkedAttrs: [], @@ -238,6 +241,7 @@ export default { for (let row = 2; row < 5000; row++) { Object.keys(choice_value_obj).forEach((key) => { const formulae = `"${choice_value_obj[key].choice_value.map((value) => value[0]).join(',')}"` + console.log(formulae) if (formulae.length <= 255) { ws.getCell(row, choice_value_obj[key].columnIdx).dataValidation = { type: 'list', diff --git a/cmdb-ui/src/modules/cmdb/views/batch/modules/CiUploadTable.vue b/cmdb-ui/src/modules/cmdb/views/batch/modules/CiUploadTable.vue index ec97359..a3670a3 100644 --- a/cmdb-ui/src/modules/cmdb/views/batch/modules/CiUploadTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/batch/modules/CiUploadTable.vue @@ -1,13 +1,14 @@ <template> <div class="cmdb-batch-upload-table"> <vxe-table + v-if="uploadData && uploadData.length" ref="xTable" stripe show-header-overflow show-overflow="" size="small" class="ops-stripe-table" - :max-height="200" + height="auto" :data="dataSource" resizable :row-style="rowStyle" @@ -21,6 +22,19 @@ :min-width="100" ></vxe-column> </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> </template> @@ -99,7 +113,21 @@ export default { } </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .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> diff --git a/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadFileForm.vue b/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadFileForm.vue index d18a048..600b52f 100644 --- a/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadFileForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadFileForm.vue @@ -9,13 +9,21 @@ :fileList="fileList" :disabled="!ciType || isUploading" > - <img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> - <p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p> + <ops-icon type="itsm-folder" /> <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> - <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 class="cmdb-batch-upload-tips"> + <p>{{ $t('cmdb.batch.tips1') }}</p> + <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> </template> @@ -46,15 +54,13 @@ export default { }, watch: { ciType: { - handler(newValue) { - if (!newValue) { - this.ciItemNum = 0 - this.fileList = [] - this.dataList = [] - this.progressStatus = 'active' - this.percent = 0 - this.$emit('uploadDone', this.dataList) - } + handler() { + this.ciItemNum = 0 + this.fileList = [] + this.dataList = [] + this.progressStatus = 'active' + this.percent = 0 + this.$emit('uploadDone', this.dataList) }, }, }, @@ -77,12 +83,28 @@ export default { </script> <style lang="less"> +@import '~@/style/static.less'; + .cmdb-batch-upload-dragger { - height: 220px; + height: auto; margin: 16px 0; + .ant-upload p { + margin-bottom: 5px; + } .ant-upload.ant-upload-drag { - background: rgba(240, 245, 255, 0.35); 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 { vertical-align: baseline; @@ -90,23 +112,37 @@ export default { } </style> <style lang="less" scoped> +@import '~@/style/static.less'; + .cmdb-batch-upload-dragger { position: relative; + display: flex; + > span { + display: inline-block; + width: 50%; + } .cmdb-batch-upload-dragger-file { - background-color: #fff; - box-shadow: 0px 2px 5px rgba(78, 94, 160, 0.2); - border-radius: 4px; - position: absolute; + background-color: @primary-color_7; + border-radius: 2px; width: 80%; - left: 50%; - bottom: 24px; padding: 2px 8px; - transform: translate(-50%); display: inline-flex; > span { white-space: nowrap; 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> diff --git a/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadResult.vue b/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadResult.vue index 1ccab2c..f6028a2 100644 --- a/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadResult.vue +++ b/cmdb-ui/src/modules/cmdb/views/batch/modules/UploadResult.vue @@ -1,10 +1,11 @@ <template> <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"> <h4> - {{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }} - <span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }} + {{ $t('cmdb.batch.total') }} <span style="color: blue">{{ total }}</span> + {{ $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> <div> <span>{{ $t('cmdb.batch.errorTips') }}: </span> @@ -39,7 +40,7 @@ export default { default: false, }, }, - data: function() { + data() { return { visible: false, complete: 0, @@ -54,6 +55,13 @@ export default { return this.upLoadData.length || 0 }, }, + watch: { + ciType: { + handler() { + this.visible = false + }, + }, + }, methods: { async upload2Server() { this.visible = true @@ -96,10 +104,6 @@ export default { @import '~@/style/static.less'; .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 { background-color: rgba(240, 245, 255, 0.35); border-radius: 5px; diff --git a/cmdb-ui/src/modules/cmdb/views/ci/index.vue b/cmdb-ui/src/modules/cmdb/views/ci/index.vue index cab9c07..cd0d519 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/index.vue @@ -15,21 +15,35 @@ </span> </span> <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') }} </a-button> - <a-button size="small" icon="user-add" type="primary" ghost @click="handlePerm">{{ $t('grant') }}</a-button> - <a-popconfirm - :title=" - $t('cmdb.preference.confirmcancelSub2', { name: `${this.$route.meta.title || this.$route.meta.name}` }) - " - :ok-text="$t('confirm')" - :cancel-text="$t('cancel')" - @confirm="unsubscribe" - placement="bottomRight" - > - <a-button size="small" icon="star" type="primary" ghost>{{ $t('cmdb.preference.cancelSub') }}</a-button> - </a-popconfirm> + <EditAttrsPopover :typeId="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-dropdown v-model="visible"> + <a-button type="primary" ghost class="ops-button-ghost">···</a-button> + <a-menu slot="overlay" @click="handleMenuClick"> + <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> </div> <div class="cmdb-ci-main"> @@ -86,7 +100,6 @@ :scroll-y="{ enabled: true, gt: 20 }" :scroll-x="{ enabled: true, gt: 0 }" class="ops-unstripe-table" - :style="{ margin: '0 -12px' }" :custom-config="{ storage: true }" > <vxe-column align="center" type="checkbox" width="60" :fixed="isCheckboxFixed ? 'left' : ''"></vxe-column> @@ -219,11 +232,9 @@ </template> </template> </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> <span>{{ $t('operation') }}</span> - <EditAttrsPopover :typeId="typeId" class="operation-icon" @refresh="refreshAfterEditAttrs" /> - <!-- <a-icon class="operation-icon" type="control" /> --> </template> <template #default="{ row }"> <a-space> @@ -342,7 +353,7 @@ export default { // if (this.selectedRowKeys && this.selectedRowKeys.length) { // return this.windowHeight - 246 // } - return this.windowHeight - 210 + return this.windowHeight - 240 }, }, data() { @@ -377,6 +388,7 @@ export default { passwordValue: {}, lastEditCiId: null, isContinueCloseEdit: true, + visible: false, } }, watch: { @@ -916,15 +928,24 @@ export default { }) }, unsubscribe(ciType, type = 'all') { - const promises = [subscribeCIType(this.typeId, ''), subscribeTreeView(this.typeId, '')] - Promise.all(promises).then(() => { - const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined - if (Number(ciType) === Number(lastTypeId)) { - localStorage.setItem('ops_ci_typeid', '') - } - this.$message.success(this.$t('cmdb.preference.cancelSubSuccess')) - this.resetRoute() - this.$router.push('/cmdb/preference') + const that = this + this.$confirm({ + title: that.$t('warning'), + content: that.$t('cmdb.preference.confirmcancelSub2', { + name: `${that.$route.meta.title || that.$route.meta.name}`, + }), + onOk() { + const promises = [subscribeCIType(that.typeId, ''), subscribeTreeView(that.typeId, '')] + 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() { @@ -957,6 +978,11 @@ export default { } }) }, + handleMenuClick(e) { + if (e.key === 'grant') { + this.visible = false + } + }, }, } </script> @@ -968,11 +994,11 @@ export default { <style lang="less" scoped> @import '~@/style/static.less'; .cmdb-ci { + background-color: #fff; + padding: 20px; + border-radius: @border-radius-box; + height: calc(100vh - 64px); + overflow: auto; margin-bottom: -24px; - .cmdb-ci-main { - background-color: #fff; - border-radius: 15px; - padding: 12px; - } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/CreateInstanceForm.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/CreateInstanceForm.vue index 53f24e8..1bbbc87 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/CreateInstanceForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/CreateInstanceForm.vue @@ -106,9 +106,9 @@ <a-date-picker v-decorator="[list.name, { rules: [{ required: false }] }]" style="width: 100%" - :format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" - v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'" - :showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }" + :format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" + v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'" + :showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }" /> <a-input v-if="getFieldType(list.name) === 'input'" @@ -373,7 +373,7 @@ export default { } else if (_find.value_type === '0' || _find.value_type === '1') { return 'input_number' } else if (_find.value_type === '4' || _find.value_type === '3') { - return this.valueTypeMap[_find.value_type] + return _find.value_type } else { return 'input' } diff --git a/cmdb-ui/src/modules/cmdb/views/ci/modules/editAttrsPopover.vue b/cmdb-ui/src/modules/cmdb/views/ci/modules/editAttrsPopover.vue index 4766add..9255db8 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci/modules/editAttrsPopover.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci/modules/editAttrsPopover.vue @@ -11,9 +11,11 @@ @setFixedList="setFixedList" /> </template> - <div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }"> - <a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" /> - </div> + <slot> + <div :style="{ height: '100%', width: '30px', float: 'right', borderLeft: '1px solid #e8eaec' }"> + <a-icon :style="{ margin: '13px 0 0 10px ' }" type="control" /> + </div> + </slot> </a-popover> </template> diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue index ea79501..03a6563 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attrAD.vue @@ -1,5 +1,5 @@ <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"> <a-tabs size="small" v-model="currentTab"> <a-tab-pane v-for="item in adCITypeList" :key="item.id"> @@ -207,7 +207,7 @@ export default { @import '~@/style/static.less'; .attr-ad { position: relative; - padding: 0 12px; + padding: 0 20px; .attr-ad-header { width: 100%; display: inline-flex; diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue index 3fa87c6..30bf11a 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attrADTabpane.vue @@ -1,5 +1,5 @@ <template> - <div :style="{ height: `${windowHeight - 156}px`, overflow: 'auto', position: 'relative' }"> + <div :style="{ height: `${windowHeight - 187}px`, overflow: 'auto', position: 'relative' }"> <a v-if="!adrIsInner" :style="{ position: 'absolute', right: 0, top: 0 }" diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue index 8a390be..10f1d35 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeCard.vue @@ -1,71 +1,83 @@ <template> - <div :class="{ 'attribute-card': true, 'attribute-card-inherited': inherited }"> - <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 }" - :style="{ ...getPropertyStyle(property) }" - > - <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 + @click=" + () => { + if (isAdd) { + $emit('add') + } + } + " + :class="{ 'attribute-card': true, 'attribute-card-add': isAdd, 'attribute-card-inherited': inherited }" + > + <div class="attribute-card-uniqueKey" v-if="isUnique">{{ $t('cmdb.ciType.uniqueKey') }}</div> + <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 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-tooltip> + + <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> - </div> - </a-tooltip> + <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"> + <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-popover - trigger="click" - :arrowPointAtCenter="true" - placement="bottom" - overlayClassName="attribute-card-footer-popover" - > - <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 class="attribute-card-operation" v-if="!inherited"> + <a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> + <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')"> + <a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> + </a-tooltip> + <a v-if="!isUnique" style="color:red;"><a-icon type="delete" @click="handleDelete"/></a> </a-space> - </a-popover> - - <a-space class="attribute-card-operation" v-if="!inherited"> - <a v-if="!isStore"><a-icon type="edit" @click="handleEdit"/></a> - <a-tooltip :title="$t('cmdb.ciType.computeForAllCITips')"> - <a v-if="!isStore && property.is_computed"><a-icon type="redo" @click="handleCalcComputed"/></a> - </a-tooltip> - <a style="color:red;"><a-icon type="delete" @click="handleDelete"/></a> - </a-space> - </div> - <TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> + </div> + <TriggerForm ref="triggerForm" :CITypeId="CITypeId" /> + </template> + <template v-else> + <a><a-icon type="plus"/></a> + <div>{{ $t('cmdb.ciType.addAttribute') }}</div> + </template> </div> </template> @@ -82,10 +94,15 @@ import { ops_is_unique, } from '@/core/icons' import { valueTypeMap } from '../../utils/const' -import { getPropertyStyle } from '../../utils/helper' import TriggerForm from './triggerForm.vue' export default { name: 'AttributeCard', + inject: { + unique: { + from: 'unique', + default: () => undefined, + }, + }, components: { ValueTypeIcon, TriggerForm, @@ -114,11 +131,21 @@ export default { type: Array, default: () => [], }, + isAdd: { + type: Boolean, + default: false, + }, }, data() { return {} }, computed: { + isUnique() { + if (this.unique) { + return this.property?.name === this.unique() + } + return false + }, valueTypeMap() { return valueTypeMap() }, @@ -147,11 +174,10 @@ export default { ] }, inherited() { - return this.property.inherited || false + return this.property?.inherited || false }, }, methods: { - getPropertyStyle, handleEdit() { this.$emit('edit') }, @@ -196,11 +222,12 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; .attribute-card { width: 182px; height: 80px; - background: #f8faff; - border-radius: 5px; + background: @primary-color_6; + border-radius: 2px; position: relative; margin-bottom: 16px; transition: all 0.3s; @@ -219,7 +246,7 @@ export default { .attribute-card-value-type-icon { width: 32px; height: 32px; - font-size: 12px; + font-size: 16px; background: #ffffff !important; box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); border-radius: 2px; @@ -243,7 +270,7 @@ export default { white-space: nowrap; } .attribute-card-name-default-show { - color: #2f54eb; + color: @primary-color; } .attribute-card_value-type { font-size: 10px; @@ -270,20 +297,67 @@ export default { position: absolute; bottom: 0; left: 0; - background: linear-gradient(180deg, #96abd6 0%, #ecf2ff 0.01%, #ffffff 143.33%); - border-radius: 0px 0px 5px 5px; + background: @primary-color_5; + border-radius: 0px 0px 2px 2px; display: inline-flex; align-items: center; justify-content: space-between; + border-top: 1px solid @primary-color_3; .attribute-card-operation { 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 { - background: #f3f4f7; + background: @primary-color_7; .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> diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeStore.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeStore.vue index f8b2876..4b19024 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributeStore.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributeStore.vue @@ -172,8 +172,6 @@ export default { margin-right: 60px; .ant-input-group.ant-input-group-compact > *:first-child, .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]; color: #fff; border: none; @@ -202,7 +200,6 @@ export default { .ant-input { background-color: #f0f5ff; border: none; - border-radius: 20px; &:focus { box-shadow: none; } diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/attributesTable.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/attributesTable.vue index 4c55133..9a1a749 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/attributesTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/attributesTable.vue @@ -12,14 +12,28 @@ </a-form-item> </span> </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-button type="primary" @click="handleAddGroup" size="small" class="ops-button-primary" icon="plus">{{ - $t('cmdb.ciType.group') - }}</a-button> - <a-button type="primary" @click="handleOpenUniqueConstraint" size="small" class="ops-button-primary">{{ - $t('cmdb.ciType.uniqueConstraint') - }}</a-button> + <a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</a-button> + <a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button> + <div> + <a-tooltip + v-for="type in Object.keys(valueTypeMap)" + :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> <div :key="CITypeGroup.id" v-for="(CITypeGroup, index) in CITypeGroups"> <div> @@ -29,13 +43,6 @@ > <span style="font-weight:700">{{ CITypeGroup.name }}</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> <template v-else> <span> @@ -64,21 +71,24 @@ @click="handleMoveGroup(index, index + 1)" /></a> </a-tooltip> - - <a-tooltip> - <template slot="title">{{ $t('cmdb.ciType.selectAttribute') }}</template> - <a><a-icon type="plus" @click="handleAddGroupAttr(index)"/></a> - </a-tooltip> - <a-tooltip> - <template slot="title">{{ $t('cmdb.ciType.deleteGroup') }}</template> - <a - :style="{ color: CITypeGroup.inherited ? 'gray' : 'red' }" - :disabled="CITypeGroup.inherited" - ><a-icon - type="delete" - @click="handleDeleteGroup(CITypeGroup)" - /></a> - </a-tooltip> + <a-dropdown> + <a><ops-icon type="veops-more"/></a> + <a-menu slot="overlay"> + <a-menu-item @click="handleAddGroupAttr(index)"> + <template slot="title"></template> + <a-icon type="plus" /> + {{ $t('cmdb.ciType.addAttribute') }} + </a-menu-item> + <a-menu-item @click="handleEditGroupName(index, CITypeGroup)" :disabled="CITypeGroup.inherited"> + <a-icon type="edit" /> + {{ $t('cmdb.ciType.editGroupName') }} + </a-menu-item> + <a-menu-item @click="handleDeleteGroup(CITypeGroup)" :disabled="CITypeGroup.inherited"> + <a-icon type="delete" /> + {{ $t('cmdb.ciType.deleteGroup') }} + </a-menu-item> + </a-menu> + </a-dropdown> </a-space> </div> <div class="ci-types-attributes-wrapper"> @@ -98,7 +108,9 @@ handle=".handle" > <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" @edit="handleEditProperty(item)" :property="item" @@ -106,6 +118,7 @@ :CITypeId="CITypeId" :attributes="attributes" /> + <AttributeCard isAdd @add="handleAddGroupAttr(index)" /> <i></i> <i></i> <i></i> <i></i> <i></i> </draggable> </div> @@ -114,10 +127,11 @@ <div :style="{ height: '32px', lineHeight: '32px', display: 'inline-block', fontSize: '14px' }"> <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;font-size:10px;">{{ $t('cmdb.ciType.otherGroupTips') }}</span> </div> <div style="float: right"> <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-tooltip> </div> @@ -138,7 +152,9 @@ handle=".handle" > <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" @edit="handleEditProperty(item)" :property="item" @@ -146,6 +162,7 @@ :CITypeId="CITypeId" :attributes="attributes" /> + <AttributeCard isAdd @add="handleAddGroupAttr(undefined)" /> <i></i> <i></i> <i></i> <i></i> <i></i> </draggable> </div> @@ -185,6 +202,8 @@ import AttributeCard from './attributeCard.vue' import AttributeEditForm from './attributeEditForm.vue' import NewCiTypeAttrModal from './newCiTypeAttrModal.vue' import UniqueConstraint from './uniqueConstraint.vue' +import { valueTypeMap } from '../../utils/const' +import { getPropertyIcon } from '../../utils/helper' export default { name: 'AttributesTable', @@ -214,6 +233,8 @@ export default { otherGroupAttributes: [], addGroupModal: false, newGroupName: '', + attrTypeFilter: [], + unique: '', } }, computed: { @@ -223,9 +244,17 @@ export default { windowHeight() { return this.$store.state.windowHeight }, + valueTypeMap() { + return valueTypeMap() + }, }, provide() { - return { refresh: this.getCITypeGroupData } + return { + refresh: this.getCITypeGroupData, + unique: () => { + return this.unique + }, + } }, beforeCreate() {}, created() {}, @@ -233,6 +262,7 @@ export default { this.getCITypeGroupData() }, methods: { + getPropertyIcon, handleEditProperty(property) { this.$refs.attributeEditForm.handleEdit(property, this.attributes) }, @@ -260,6 +290,7 @@ export default { group.editable = false group.originOrder = group.order group.originName = group.name + // group.attributes = group.attributes.sort((a, b) => a.order - b.order) }) this.otherGroupAttributes = this.attributes @@ -282,6 +313,7 @@ export default { Promise.all(promises).then((values) => { console.log(values) this.attributes = values[0].attributes + this.unique = values[0].unique const temp = {} this.attributes.forEach((attr) => { temp[attr.id] = attr @@ -387,6 +419,7 @@ export default { group.attributes = group.attributes.filter((x) => !values.checkedAttributes.includes(x.id)) } }) + // this.CITypeGroups = this.CITypeGroups this.otherGroupAttributes.forEach((attributes) => { if (values.groupId === null) { @@ -523,20 +556,40 @@ export default { handleOpenUniqueConstraint() { 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> <style lang="less" scoped> +@import '~@/style/static.less'; + .fold { width: calc(100% - 216px); display: inline-block; } .ci-types-attributes { - padding: 16px 24px 24px; + padding: 0 20px; 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 { color: #40a9ff; width: calc(100% - 20px); diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/ciTypedetail.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/ciTypedetail.vue index 9d52aad..be8f020 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/ciTypedetail.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/ciTypedetail.vue @@ -1,6 +1,6 @@ <template> <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')"> <AttributesTable ref="attributesTable" :CITypeId="CITypeId" :CITypeName="CITypeName"></AttributesTable> </a-tab-pane> @@ -16,8 +16,10 @@ <a-tab-pane key="5" :tab="$t('cmdb.ciType.relationAD')"> <RelationAD :CITypeId="CITypeId"></RelationAD> </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> + <div class="citype-detail-title">{{ $t('cmdb.ciType.relation') }}</div> + <RelationTable isInGrantComp :CITypeId="CITypeId" :CITypeName="CITypeName"></RelationTable> </a-tab-pane> </a-tabs> </a-card> @@ -74,4 +76,13 @@ export default { } </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> diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/index.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/index.vue index 48bd5c4..832779c 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/index.vue @@ -8,11 +8,10 @@ </div> <SplitPane v-else - :min="280" + :min="220" :max="500" :paneLengthPixel.sync="paneLengthPixel" appName="cmdb-ci-types" - triggerColor="#F0F5FF" :triggerLength="18" > <template #one> @@ -22,22 +21,23 @@ :disabled="!permissions.includes('admin') && !permissions.includes('cmdb_admin')" type="primary" size="small" - icon="plus" + ghost @click="handleClickAddGroup" - class="ops-button-primary" - >{{ $t('cmdb.ciType.group') }}</a-button + class="ops-button-ghost" + ><ops-icon type="veops-increase" />{{ $t('cmdb.ciType.group') }}</a-button > <a-space> - <a + <span + :style="{ cursor: 'pointer' }" @click=" () => { $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><ops-icon type="ops-menu"/></a> + <ops-icon type="ops-menu" :style="{ cursor: 'pointer' }" /> <a-menu slot="overlay"> <a-menu-item key="0"> <a-upload @@ -83,7 +83,7 @@ <a-space> <a-tooltip> <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> <template v-if="g.id !== -1"> <a-tooltip> @@ -113,7 +113,7 @@ > <div> <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"> <template v-if="ci.icon"> @@ -134,18 +134,33 @@ </span> </div> <span class="ci-types-left-detail-title">{{ ci.alias || ci.name }}</span> - <a-space class="ci-types-left-detail-action"> - <a><a-icon type="user-add" @click="(e) => handlePerm(e, ci)"/></a> - <a><a-icon type="edit" @click="(e) => handleEdit(e, ci)"/></a> - <a - v-if="permissions.includes('admin') || permissions.includes('cmdb_admin')" - :disabled="ci.inherited" - @click="(e) => handleDownloadCiType(e, ci)" - > - <a-icon type="download" /> + <a-dropdown :getPopupContainer="(trigger) => trigger"> + <a class="ci-types-left-detail-action"> + <ops-icon type="veops-more" /> </a> - <a style="color: red" @click="(e) => handleDelete(e, ci)"><a-icon type="delete"/></a> - </a-space> + <a-menu slot="overlay"> + <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> </draggable> </div> @@ -270,17 +285,7 @@ </div> </el-select> </a-form-item> - <a-form-item> - <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> + <a-form-item :help="$t('cmdb.ciType.uniqueKeyTips')" :label="$t('cmdb.ciType.uniqueKey')"> <el-select size="small" filterable @@ -456,7 +461,6 @@ export default { }, currentCName() { if (this.currentId) { - console.log(this.currentId) if (this.currentId.split('%')[2] !== 'null') { return this.currentId.split('%')[2] } @@ -817,8 +821,8 @@ export default { }) }, handleDelete(e, record) { - e.preventDefault() - e.stopPropagation() + e.domEvent.preventDefault() + e.domEvent.stopPropagation() const that = this this.$confirm({ title: that.$t('warning'), @@ -833,8 +837,8 @@ export default { }) }, handleDownloadCiType(e, ci) { - e.preventDefault() - e.stopPropagation() + e.domEvent.preventDefault() + e.domEvent.stopPropagation() const x = new XMLHttpRequest() x.open('GET', `/api/v0.1/ci_types/${ci.id}/template/export`, true) x.responseType = 'blob' @@ -855,8 +859,8 @@ export default { }) }, async handleEdit(e, record) { - e.preventDefault() - e.stopPropagation() + e.domEvent.preventDefault() + e.domEvent.stopPropagation() this.drawerTitle = this.$t('cmdb.ciType.editCIType') this.drawerVisible = true await getCITypeAttributesById(record.id).then((res) => { @@ -909,8 +913,8 @@ export default { } }, handlePerm(e, ci) { - e.preventDefault() - e.stopPropagation() + e.domEvent.preventDefault() + e.domEvent.stopPropagation() roleHasPermissionToGrant({ app_id: 'cmdb', resource_type_name: 'CIType', @@ -942,6 +946,8 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .ci-types-wrap { margin: 0 0 -24px 0; .ci-types-empty { @@ -955,21 +961,24 @@ export default { width: 100%; overflow: auto; float: left; - border-top-left-radius: 20px; - border-top-right-radius: 20px; + .ci-types-left-content { max-height: calc(100% - 45px); - overflow: auto; + overflow: hidden; + &:hover { + overflow: auto; + } } .ci-types-left-title { - padding: 10px 15px; + padding: 10px 0; display: flex; flex-direction: row; justify-content: space-between; + color: @text-color_3; } .ci-types-left-group { position: relative; - padding: 8px 15px; + padding: 8px 0 8px 14px; color: rgb(99, 99, 99); cursor: pointer; font-size: 14px; @@ -982,7 +991,7 @@ export default { display: none; } &:hover { - background-color: #e1efff; + background-color: @primary-color_3; > div:nth-child(2) { display: inline-flex; } @@ -992,17 +1001,25 @@ export default { } } .ci-types-left-detail { - padding: 3px 14px 3px 36px; + padding: 3px 14px; cursor: pointer; position: relative; display: flex; flex-direction: row; justify-content: flex-start; + align-items: center; margin-bottom: 4px; + height: 32px; + line-height: 32px; .ci-types-left-detail-action { display: none; margin-left: auto; } + .ci-types-left-detail-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } .ci-types-left-detail-icon { display: flex; align-items: center; @@ -1019,7 +1036,7 @@ export default { } } &:hover { - background-color: #e1efff; + background-color: @primary-color_3; svg { display: inline !important; } @@ -1029,7 +1046,7 @@ export default { } } .selected { - background-color: #e1efff; + background-color: @primary-color_3; .ci-types-left-detail-title { font-weight: 700; } @@ -1038,6 +1055,7 @@ export default { .ci-types-right { width: 100%; position: relative; + background-color: #fff; .ci-types-right-empty { position: absolute; text-align: center; @@ -1049,7 +1067,6 @@ export default { .ci-types-left, .ci-types-right { height: 100%; - background-color: #fff; } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/relationAD.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/relationAD.vue index becf5ae..24cb511 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/relationAD.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/relationAD.vue @@ -1,5 +1,5 @@ <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"> <treeselect class="custom-treeselect" @@ -253,7 +253,7 @@ export default { <style lang="less" scoped> .relation-ad { overflow: auto; - padding: 24px; + padding: 0 20px; .relation-ad-item { display: inline-flex; justify-content: flex-start; diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/relationTable.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/relationTable.vue index 205aa16..5bbd1df 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/relationTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/relationTable.vue @@ -1,6 +1,7 @@ <template> - <div :style="{ padding: '16px 24px 24px' }"> + <div :style="{ padding: '0 20px 20px' }"> <a-button + v-if="!isInGrantComp" style="margin-bottom: 10px" @click="handleCreate" type="primary" @@ -43,7 +44,7 @@ <template #default="{row}"> <a-space v-if="!row.isParent && row.source_ci_type_id"> <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-popconfirm> </a-space> @@ -148,6 +149,10 @@ export default { type: String, default: '', }, + isInGrantComp: { + type: Boolean, + default: false, + }, }, data() { return { @@ -178,7 +183,9 @@ export default { async mounted() { this.getCITypes() this.getRelationTypes() - await this.getCITypeParent() + if (!this.isInGrantComp) { + await this.getCITypeParent() + } this.getData() }, methods: { diff --git a/cmdb-ui/src/modules/cmdb/views/ci_types/triggerTable.vue b/cmdb-ui/src/modules/cmdb/views/ci_types/triggerTable.vue index 501270b..6231930 100644 --- a/cmdb-ui/src/modules/cmdb/views/ci_types/triggerTable.vue +++ b/cmdb-ui/src/modules/cmdb/views/ci_types/triggerTable.vue @@ -134,6 +134,6 @@ export default { <style lang="less" scoped> .ci-types-triggers { - padding: 16px 24px 24px; + padding: 0 20px 20px; } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chartForm.vue b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chartForm.vue index 18f2276..0923545 100644 --- a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chartForm.vue +++ b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/chartForm.vue @@ -165,7 +165,7 @@ : '#fafafa', }" > - <div :style="{ color: fontColor }">{{ form.name }}</div> + <div v-if="chartType === 'count'" :style="{ color: fontColor }">{{ form.name }}</div> <Chart :ref="`chart_${item.id}`" :chartId="item.id" @@ -613,6 +613,7 @@ export default { } else { this.form.category = 1 } + console.log(this.chartType) }, showPreview() { this.$refs.chartForm.validate(async (valid) => { diff --git a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/customLayout.vue b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/customLayout.vue index 58b2237..861290e 100644 --- a/cmdb-ui/src/modules/cmdb/views/custom_dashboard/customLayout.vue +++ b/cmdb-ui/src/modules/cmdb/views/custom_dashboard/customLayout.vue @@ -15,7 +15,8 @@ @click="openChartForm('add', { options: { w: 3 } })" type="primary" icon="plus-circle" - class="ops-button-primary" + ghost + class="ops-button-ghost" >{{ $t('cmdb.custom_dashboard.newChart') }}</a-button > </div> @@ -44,7 +45,7 @@ ? Array.isArray(item.options.bgColor) ? `linear-gradient(to bottom, ${item.options.bgColor[0]} 0%, ${item.options.bgColor[1]} 100%)` : item.options.bgColor - : '#fafafa', + : '#fff', }" > <div class="cmdb-dashboard-grid-item-title"> @@ -78,7 +79,10 @@ ></a> <a-menu slot="overlay"> <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 @click="deleteChart(item)"><a-icon style="margin-right:5px" type="delete" />{{ $t('delete') }}</a> @@ -261,7 +265,7 @@ export default { text-align: center; } .cmdb-dashboard-grid-item { - border-radius: 8px; + border-radius: 2px; padding: 6px 12px; .cmdb-dashboard-grid-item-title { overflow: hidden; @@ -299,6 +303,7 @@ export default { display: inline-block; width: 16px; height: 16px; + line-height: 16px; font-size: 16px; text-align: center; margin-right: 5px; diff --git a/cmdb-ui/src/modules/cmdb/views/discovery/index.vue b/cmdb-ui/src/modules/cmdb/views/discovery/index.vue index f34b1fa..03952da 100644 --- a/cmdb-ui/src/modules/cmdb/views/discovery/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/discovery/index.vue @@ -144,6 +144,9 @@ export default { <style lang="less" scoped> @import '~@/style/static.less'; .setting-discovery { + background-color: #fff; + padding: 20px; + border-radius: @border-radius-box; .type-header { width: 100%; display: inline-flex; diff --git a/cmdb-ui/src/modules/cmdb/views/discoveryCI/index.vue b/cmdb-ui/src/modules/cmdb/views/discoveryCI/index.vue index 184c98e..f2fd987 100644 --- a/cmdb-ui/src/modules/cmdb/views/discoveryCI/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/discoveryCI/index.vue @@ -1,11 +1,11 @@ <template> <TwoColumnLayout appName="cmdb-adc"> <template #one> - <div v-for="group in ci_types_list" :key="group.id"> - <div> + <div class="cmdb-adc-group" v-for="group in ci_types_list" :key="group.id"> + <p> <strong>{{ group.name || $t('other') }}</strong ><span :style="{ color: 'rgb(195, 205, 215)' }">({{ group.ci_types.length }})</span> - </div> + </p> <div :class="{ 'cmdb-adc-side-item': true, 'cmdb-adc-side-item-selected': currentType === type.id }" v-for="type in group.ci_types" @@ -34,7 +34,6 @@ <div id="discovery-ci"> <a-input-search :placeholder="$t('cmdb.components.pleaseSearch')" - class="ops-input ops-input-radius" :style="{ width: '200px', marginRight: '20px', marginBottom: '10px' }" @search="handleSearch" allowClear @@ -102,7 +101,11 @@ sortable v-bind="columns.length ? { width: '130px' } : { minWidth: '130px' }" ></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}"> <a-space> <a-tooltip :title="$t('cmdb.ad.accept')"> @@ -312,6 +315,9 @@ export default { <style lang="less" scoped> @import '~@/style/static.less'; .cmdb-adc { + .cmdb-adc-group { + margin-bottom: 20px; + } .cmdb-adc-side-item { .ops_popover_item(); height: 32px; @@ -343,7 +349,7 @@ export default { } .cmdb-adc-side-item-selected { .ops_popover_item_selected(); - background-color: #custom_colors[color_2]; + background-color: @primary-color_3; } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/index.less b/cmdb-ui/src/modules/cmdb/views/index.less index 4182c87..36a5fa0 100644 --- a/cmdb-ui/src/modules/cmdb/views/index.less +++ b/cmdb-ui/src/modules/cmdb/views/index.less @@ -77,7 +77,7 @@ } .cmdb-views-header { - border-left: 4px solid #custom_colors[color_1]; + border-left: 4px solid @primary-color; height: 32px; display: flex; align-items: center; @@ -85,17 +85,17 @@ margin-bottom: 18px; .cmdb-views-header-title { font-size: 16px; - font-weight: 400; - color: rgba(0, 0, 0, 0.75); + font-weight: bold; + color: @text-color_1; margin-left: 10px; } .cmdb-views-header-metadata { cursor: pointer; font-size: 12px; - color: rgba(0, 0, 0, 0.55); + color: @text-color_3; margin-left: 20px; &:hover { - color: #custom_colors[color_1]; + color: @primary-color; } } } diff --git a/cmdb-ui/src/modules/cmdb/views/model_relation/index.vue b/cmdb-ui/src/modules/cmdb/views/model_relation/index.vue index 4275634..d4bd8b1 100644 --- a/cmdb-ui/src/modules/cmdb/views/model_relation/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/model_relation/index.vue @@ -242,9 +242,11 @@ export default { </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .model-relation { background-color: #fff; - border-radius: 15px; + border-radius: @border-radius-box; padding: 24px; height: calc(100vh - 64px); margin-bottom: -24px; diff --git a/cmdb-ui/src/modules/cmdb/views/preference/index.vue b/cmdb-ui/src/modules/cmdb/views/preference/index.vue index e42206c..bd48787 100644 --- a/cmdb-ui/src/modules/cmdb/views/preference/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/preference/index.vue @@ -51,7 +51,6 @@ :class="{ 'cmdb-preference-avatar': true, 'cmdb-preference-avatar-noicon': !ciType.icon, - 'cmdb-preference-avatar-noicon-is_subscribed': !ciType.icon && ciType.is_subscribed, }" :style="{ width: '30px', height: '30px', marginRight: '10px' }" > @@ -94,7 +93,12 @@ </div> </div> <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' }"> <a-icon :type="expandKeys.includes(group.id) ? 'caret-down' : 'caret-right'" />{{ group.name }}({{ group.ci_types ? group.ci_types.length : 0 @@ -108,7 +112,6 @@ :class="{ 'cmdb-preference-avatar': true, 'cmdb-preference-avatar-noicon': !item.icon, - 'cmdb-preference-avatar-noicon-is_subscribed': !item.icon && item.is_subscribed, }" > <template v-if="item.icon"> @@ -188,6 +191,7 @@ </template> <script> +import _ from 'lodash' import router, { resetRouter } from '@/router' import store from '@/store' import { mapState } from 'vuex' @@ -219,12 +223,29 @@ export default { }, type_id2users: {}, myPreferences: [], + searchValue: '', } }, computed: { ...mapState({ 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() { this.getCITypes(true) @@ -376,7 +397,6 @@ export default { .cmdb-preference { margin: -24px; overflow: auto; - background: url('../../assets/preference_background.png'); position: relative; display: flex; flex-direction: row; @@ -392,7 +412,7 @@ export default { .cmdb-preference-left { width: 300px; height: 100%; - padding: 12px 18px; + padding: 24px 18px; .cmdb-preference-left-card { background: url('../../assets/preference_card.png'); background-repeat: no-repeat; @@ -426,17 +446,19 @@ export default { .cmdb-preference-group-title { text-align: center; margin-bottom: 5px; + i { + color: @primary-color; + } > span { display: inline-block; - color: #fff; - background: linear-gradient(90deg, #305bec, #78cfff); + color: @text-color_2; border-radius: 16px; font-weight: 600; padding: 6px 12px; } } .cmdb-preference-group-content { - color: rgba(0, 0, 0, 0.75); + color: @text-color_1; font-weight: 400; display: flex; align-items: center; @@ -447,7 +469,11 @@ export default { &:hover { background: #ffffff; 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 { display: inline; white-space: nowrap; @@ -479,7 +505,7 @@ export default { .cmdb-preference-right { flex: 1; height: 100%; - padding-top: 18px; + padding-top: 24px; .cmdb-preference-content { display: flex; flex-direction: row; @@ -494,11 +520,11 @@ export default { display: inline-block; width: 195px; height: 155px; - border-radius: 8px; + border-radius: @border-radius-box; background-color: #fff; box-shadow: 0px 2px 8px rgba(149, 160, 208, 0.25); margin: 0 20px 20px 0; - padding: 8px; + padding: 12px; &:hover { box-shadow: 4px 25px 30px rgba(50, 89, 134, 0.25); transform: scale(1.1); @@ -550,7 +576,7 @@ export default { .cmdb-preference-progress-gray { height: 5px; border-radius: 5px; - background-color: #d9d9d9; + background-color: @text-color_6; margin-top: 5px; width: 100%; position: relative; @@ -560,7 +586,7 @@ export default { top: 0; left: 0; border-radius: 5px; - background: linear-gradient(90deg, #305bec, #78cfff); + background: @primary-color_8; } } } @@ -592,20 +618,17 @@ export default { display: flex; align-items: center; justify-content: center; - width: 40px; - height: 40px; + width: 34px; + height: 34px; box-shadow: 0px 4px 4px rgba(129, 140, 186, 0.25); - border-radius: 5px; + border-radius: 1px; + background-color: #fff; } .cmdb-preference-avatar-noicon { - background-color: #7f97fa; > span { - font-size: 24px; - color: #fff; + font-size: 18px; + color: @text-color_4; } } - .cmdb-preference-avatar-noicon-is_subscribed { - background-color: #47a964; - } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/preference_relation/index.vue b/cmdb-ui/src/modules/cmdb/views/preference_relation/index.vue index f0b246b..a6ecfe2 100644 --- a/cmdb-ui/src/modules/cmdb/views/preference_relation/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/preference_relation/index.vue @@ -15,9 +15,7 @@ >{{ $t('cmdb.preference_relation.newServiceTree') }}</a-button > <template v-else> - <a-input v-model="newRelationViewName" :placeholder="$t('cmdb.preference_relation.serviceTreeName')"></a-input> - <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 type="primary" size="small" @click="openServiceTreeModal({}, 'add')">{{ $t('save') }}</a-button> <a-button type="primary" size="small" @@ -26,13 +24,13 @@ () => { isEdit = false checkedNodes = [] - newRelationViewName = '' } " >{{ $t('cancel') }}</a-button > </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> </div> <SeeksRelationGraph v-if="isPullConfig" ref="ciTypeRelationGraph" :options="graphOptions"> @@ -46,7 +44,7 @@ </div> </SeeksRelationGraph> </div> - <template v-if="relationViews.views"> + <template v-if="relationViews.views && !loading"> <a-row :gutter="4"> <a-col :xl="12" @@ -54,11 +52,17 @@ :md="12" :sm="24" :xs="24" - :key="`${view}${idx}`" - v-for="(view, idx) in Object.keys(relationViews.views)" + :key="`${view}`" + v-for="view in Object.keys(relationViews.views)" > <div class="relation-views"> <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 class="relation-views-close"><a-icon type="close"/></a> </a-popconfirm> @@ -69,6 +73,7 @@ </a-col> </a-row> </template> + <ServiceTreeModal ref="serviceTreeModal" @submitServiceTree="submitServiceTree" /> </div> </template> @@ -78,11 +83,17 @@ import router, { resetRouter } from '@/router' import store from '@/store' import SeeksRelationGraph from '@/modules/cmdb/3rd/relation-graph' 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 ServiceTreeModal from './serviceTreeModal.vue' export default { name: 'PreferenceRelation', - components: { SeeksRelationGraph }, + components: { SeeksRelationGraph, ServiceTreeModal }, data() { const defaultOptions = { allowShowMiniToolBar: false, @@ -103,6 +114,7 @@ export default { } const relationViewOptions = { ...defaultOptions, + disableZoom: true, layouts: [ { layoutName: 'tree', @@ -116,11 +128,10 @@ export default { relationViewOptions, isEdit: false, relationViews: {}, - newRelationViewName: '', graphJsonData: {}, checkedNodes: [], - is_public: true, isPullConfig: false, + loading: false, } }, async created() { @@ -210,6 +221,7 @@ export default { return maxEle }, async getViewsData() { + this.loading = true const data = await getRelationView() this.relationViews = data const { views } = data @@ -240,6 +252,7 @@ export default { this.$nextTick(() => { this.$refs.relationViewsGraph[index].setJsonData(_graphJsonData) }) + this.loading = false }) }, checked(e, node) { @@ -288,32 +301,59 @@ export default { this.checkedNodes.push(node.id) } }, - async handleSaveRelationViews() { - if (!this.newRelationViewName) { - this.$message.warning(this.$t('cmdb.preference_relation.tips2')) - return - } - if (this.checkedNodes.length < 2) { + openServiceTreeModal(treeData, type) { + if (type === 'add' && this.checkedNodes.length < 2) { this.$message.warning(this.$t('cmdb.preference_relation.tips3')) return } - // eslint-disable-next-line camelcase - 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]) }) + let _treeData = { ...treeData } + if (type === 'edit') { + const { name } = _treeData + _treeData = { + ...treeData, + ...(this.relationViews?.views[name]?.option ?? {}), + is_public: this.relationViews?.views[name]?.is_public ?? true, } - }) - await subscribeRelationView({ - cr_ids, - name: this.newRelationViewName, - is_public: this.is_public, - }) + } + this.$refs.serviceTreeModal.open(_treeData, type) + }, + async submitServiceTree(treeData, type, originName) { + 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.getViewsData() this.isEdit = false this.checkedNodes = [] - this.newRelationViewName = '' }, async confirmDelete(viewName) { await deleteRelationView(viewName) @@ -359,7 +399,7 @@ export default { width: 100%; .ci-type-relation-header { position: absolute; - top: 10px; + top: 20px; left: 20px; z-index: 10; } @@ -368,12 +408,19 @@ export default { background-color: #fff; margin-top: 5px; position: relative; + .relation-views-edit, .relation-views-close { position: absolute; z-index: 10; - right: 20px; + right: 60px; top: 10px; } + .relation-views-edit { + right: 46px; + } + .relation-views-close { + right: 20px; + } } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/preference_relation/serviceTreeModal.vue b/cmdb-ui/src/modules/cmdb/views/preference_relation/serviceTreeModal.vue new file mode 100644 index 0000000..583206a --- /dev/null +++ b/cmdb-ui/src/modules/cmdb/views/preference_relation/serviceTreeModal.vue @@ -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> diff --git a/cmdb-ui/src/modules/cmdb/views/relation_views/index.vue b/cmdb-ui/src/modules/cmdb/views/relation_views/index.vue index 3afcc72..4facb88 100644 --- a/cmdb-ui/src/modules/cmdb/views/relation_views/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/relation_views/index.vue @@ -1,61 +1,51 @@ <template> <div :style="{ marginBottom: '-24px', overflow: 'hidden' }"> <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 :min="200" :max="500" :paneLengthPixel.sync="paneLengthPixel" :appName="`cmdb-relation-views-${viewId}`" - triggerColor="#F0F5FF" :triggerLength="18" > <template #one> <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 :selectedKeys="selectedKeys" :loadData="onLoadData" @@ -65,9 +55,10 @@ @drop="onDrop" :expandedKeys="expandedKeys" > - <template #title="{ key: treeKey, title, isLeaf }"> + <template #title="{ key: treeKey, title,number, isLeaf }"> <ContextMenu :title="title" + :number="number" :treeKey="treeKey" :levels="levels" :isLeaf="isLeaf" @@ -79,51 +70,69 @@ :showBatchLevel="showBatchLevel" :batchTreeKey="batchTreeKey" @clickCheckbox="clickCheckbox" + @updateTreeData="updateTreeData" /> </template> </a-tree> </div> </template> <template #two> - <div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 115}px` }"> - <a-tabs :activeKey="String(currentTypeId[0])" type="card" @change="changeCIType" class="ops-tab"> - <a-tab-pane v-for="item in showTypes" :key="`${item.id}`" :tab="item.alias || item.name"> </a-tab-pane> + <div id="relation-views-right" class="relation-views-right" :style="{ height: `${windowHeight - 64}px` }"> + <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-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> <SearchForm ref="search" @refresh="refreshTable" :preferenceAttrList="preferenceAttrList" - :isShowExpression="true" + :isShowExpression="!(isLeaf && isShowBatchIcon)" :typeId="Number(currentTypeId[0])" @copyExpression="copyExpression" type="relationView" - :style="{ padding: '0 12px', marginTop: '16px' }" - /> - <div class="relation-views-right-bar"> - <a-space> - <a-button v-if="isLeaf" type="primary" size="small" @click="$refs.create.handleOpen(true, 'create')">{{ - $t('create') - }}</a-button> - + > + <PreferenceSearch + v-if="!(isLeaf && isShowBatchIcon)" + ref="preferenceSearch" + @getQAndSort="getQAndSort" + @setParamsFromPreferenceSearch="setParamsFromPreferenceSearch" + /> + <a-space slot="extraContent"> <div class="ops-list-batch-action" v-if="isLeaf && isShowBatchIcon"> <template v-if="selectedRowKeys.length"> <span @click="$refs.create.handleOpen(true, 'update')">{{ $t('update') }}</span> - <a-divider type="vertical" /> <span @click="openBatchDownload">{{ $t('download') }}</span> - <a-divider type="vertical" /> <span @click="batchDelete">{{ $t('cmdb.ciType.deleteInstance') }}</span> - <a-divider type="vertical" /> <span @click="batchDeleteCIRelation">{{ $t('cmdb.history.deleteRelation') }}</span> <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> </template> </div> - <PreferenceSearch - ref="preferenceSearch" - @getQAndSort="getQAndSort" - @setParamsFromPreferenceSearch="setParamsFromPreferenceSearch" - /> </a-space> - </div> + </SearchForm> <vxe-table :id="`cmdb-relation-${viewId}-${currentTypeId}`" border @@ -283,14 +292,9 @@ </template> </template> </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> <span>{{ $t('operation') }}</span> - <EditAttrsPopover - :typeId="Number(currentTypeId[0])" - class="operation-icon" - @refresh="refreshAfterEditAttrs" - /> </template> <template #default="{ row }"> <a-space> @@ -482,6 +486,9 @@ export default { contextMenuKey: null, showBatchLevel: null, batchTreeKey: [], + + statisticsObj: {}, + viewOption: {}, } }, @@ -490,7 +497,7 @@ export default { return this.$store.state.windowHeight }, tableHeight() { - return this.windowHeight - 295 + return this.windowHeight - 244 }, selectedKeys() { if (this.treeKeys.length <= 1) { @@ -519,6 +526,15 @@ export default { .map((item) => item.split('%')[0]) .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() { return { @@ -638,6 +654,7 @@ export default { if (q && q[0] === ',') { q = q.slice(1) } + if (this.treeKeys.length === 0) { // await this.judgeCITypes(q) if (!refreshType) { @@ -684,8 +701,10 @@ export default { .map((item) => item.split('%')[0]) .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 = [] if (!this.leaf.includes(typeId)) { let startIdx = 0 @@ -705,8 +724,8 @@ export default { } else { level = [1] } - q += `&level=${level.join(',')}` - await this.judgeCITypes(q) + + q += `&level=${this.topo_flatten.includes(this.currentTypeId[0]) ? 1 : level.join(',')}` if (!refreshType) { this.loadNoRoot(this.treeKeys[this.treeKeys.length - 1], level) } @@ -793,69 +812,106 @@ export default { this.selectedRowKeys = [] this.currentTypeId = [typeId] this.loadColumns() - this.$nextTick(() => { - this.refreshTable() - }) + // this.$nextTick(() => { + // this.refreshTable() + // }) }, - async judgeCITypes(q) { - const showTypeIds = [] - let _showTypes = [] + async judgeCITypes() { let _showTypeIds = [] - + let _showTypes = [] 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.is_show_leaf_node) { + const typeId = parseInt(this.treeKeys[this.treeKeys.length - 1].split('%')[1]) + _showTypeIds = _.cloneDeep(this.origShowTypeIds) + _showTypes = _.cloneDeep(this.node2ShowTypes[typeId]) } - 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) + if (this.is_show_tree_node) { + const treeKeyTypeId = Number(this.treeKeys.slice(-1)[0].split('%')[1]) + const _idx = this.topo_flatten.findIndex((item) => item === treeKeyTypeId) + if (_idx > -1 && _idx < this.topo_flatten.length - 1) { + const _showTreeTypeId = this.topo_flatten[_idx + 1] + const _showTreeTypes = this.relationViews.id2type[_showTreeTypeId] + if (this.leaf_tree_sort === 1) { + _showTypeIds.push(_showTreeTypeId) + _showTypes.push(_showTreeTypes) + } else { + _showTypeIds.unshift(_showTreeTypeId) + _showTypes.unshift(_showTreeTypes) } - }) - 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() { @@ -980,7 +1036,8 @@ export default { const treeData = [] facet.forEach((item) => { 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]}"}`, isLeaf: this.leaf.includes(item[3]), id: item[2], @@ -1041,16 +1098,19 @@ export default { this.leaf = this.relationViews.views[this.viewName].leaf this.currentView = `${this.viewId}` this.typeId = this.levels[0][0] + this.viewOption = this.relationViews.views[this.viewName].option ?? {} this.refreshTable() } }) }, async loadColumns() { - this.getAttributeList() - const res = await getSubscribeAttributes(this.currentTypeId[0]) - this.preferenceAttrList = res.attributes - this.calcColumns() + if (this.currentTypeId[0]) { + this.getAttributeList() + const res = await getSubscribeAttributes(this.currentTypeId[0]) + this.preferenceAttrList = res.attributes + this.calcColumns() + } }, calcColumns() { @@ -1183,7 +1243,6 @@ export default { const dragId = _splitDragKey[_splitDragKey.length - 1].split('%')[0] // const targetObj = JSON.parse(_splitTargetKey[_splitTargetKey.length - 1].split('%')[2]) const targetId = _splitTargetKey[_splitTargetKey.length - 1].split('%')[0] - console.log(_splitDragKey) // TODO 拖拽这里不造咋弄 等等再说吧 batchUpdateCIRelationChildren([dragId], [targetId]).then((res) => { this.reload() @@ -1354,6 +1413,9 @@ export default { ...data, } this.initialInstanceList = _initialInstanceList + this.$nextTick(() => { + this.refreshTable() + }) }) .catch((err) => { console.log(err) @@ -1579,7 +1641,6 @@ export default { .split('@^@') .filter((item) => !!item) .reverse() - console.log(needGrantNodes) const needGrantRids = [...department, ...user] const floor = Math.ceil(needGrantRids.length / 6) @@ -1687,6 +1748,29 @@ export default { this.showBatchLevel = null 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> @@ -1701,15 +1785,24 @@ export default { width: 100%; float: left; position: relative; - // transition: all 0.3s; - background-color: #fff; overflow: hidden; - padding: 12px; - border-top-left-radius: 15px; - border-top-right-radius: 15px; + padding: 0; &:hover { 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 { padding: 2px 0; } @@ -1732,15 +1825,8 @@ export default { width: 100%; overflow: auto; background-color: #fff; - .relation-views-right-bar { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin-bottom: 5px; - height: 32px; - padding: 0 12px; - } + padding: 20px; + border-radius: @border-radius-box; } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/relation_views/modules/ContextMenu.vue b/cmdb-ui/src/modules/cmdb/views/relation_views/modules/ContextMenu.vue index c22ec7b..5d8f36e 100644 --- a/cmdb-ui/src/modules/cmdb/views/relation_views/modules/ContextMenu.vue +++ b/cmdb-ui/src/modules/cmdb/views/relation_views/modules/ContextMenu.vue @@ -6,7 +6,10 @@ }" @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" /> <template v-if="icon"> <img @@ -24,61 +27,82 @@ /> <span class="relation-views-node-icon" v-else>{{ icon ? icon[0].toUpperCase() : 'i' }}</span> </template> - <span class="relation-views-node-title">{{ this.title }}</span> - </span> - <a-dropdown> - <a-menu slot="overlay" @click="({ key: menuKey }) => this.onContextMenuClick(this.treeKey, menuKey)"> - <template v-if="showBatchLevel === null"> - <a-menu-item - v-for="item in menuList" - :key="item.id" - ><a-icon type="plus-circle" />{{ $t('new') }} {{ item.alias }}</a-menu-item - > - <a-menu-item - v-if="showDelete" - key="delete" - ><ops-icon type="icon-xianxing-delete" />{{ $t('cmdb.serviceTree.deleteNode') }}</a-menu-item - > - <a-menu-divider /> - <a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item> - <a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item> - <a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item> - <a-menu-divider /> - <a-menu-item - key="batch" - ><ops-icon type="icon-xianxing-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item - > - </template> - <template v-else> - <a-menu-item - :disabled="!batchTreeKey || !batchTreeKey.length" - key="batchGrant" - ><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item - > - <a-menu-item - :disabled="!batchTreeKey || !batchTreeKey.length" - key="batchRevoke" - ><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item - > - <a-menu-divider /> - <template v-if="showBatchLevel > 0"> + <span class="relation-views-node-title" v-if="!isEditNodeName" :title="title">{{ title }}</span> + <a-input + ref="input" + @blur="changeNodeName" + @pressEnter=" + () => { + $refs.input.blur() + } + " + size="small" + v-else + v-model="editNodeName" + :style="{ marginLeft: '5px' }" + /> + <span class="relation-views-node-number">{{ number }}</span> + <a-dropdown overlayClassName="relation-views-node-dropdown" :overlayStyle="{ width: '200px' }"> + <a-menu slot="overlay" @click="({ key: menuKey }) => onContextMenuClick(this.treeKey, menuKey)"> + <template v-if="showBatchLevel === null"> + <a-divider orientation="left">{{ $t('cmdb.relation') }}</a-divider> + <a-menu-item + v-for="item in menuList" + :key="item.id" + ><a-icon type="plus-circle" />{{ $t('add') }} {{ item.alias }}</a-menu-item + > + <a-menu-item + v-if="showDelete" + key="delete" + ><ops-icon type="icon-xianxing-delete" />{{ + $t('cmdb.serviceTree.deleteNode', { name: title }) + }}</a-menu-item + > + <a-divider orientation="left">{{ $t('cmdb.components.perm') }}</a-divider> + <a-menu-item key="grant"><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item> + <a-menu-item key="revoke"><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item> + <a-menu-item key="view"><a-icon type="eye" />{{ $t('cmdb.serviceTree.view') }}</a-menu-item> + <a-menu-divider /> + <a-menu-item + key="editNodeName" + ><ops-icon type="icon-xianxing-edit" />{{ $t('cmdb.serviceTree.editNodeName') }}</a-menu-item + > + <a-menu-item + key="batch" + ><ops-icon type="veops-copy" />{{ $t('cmdb.serviceTree.batch') }}</a-menu-item + > + </template> + <template v-else> <a-menu-item :disabled="!batchTreeKey || !batchTreeKey.length" - key="batchDelete" - ><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item + key="batchGrant" + ><a-icon type="user-add" />{{ $t('grant') }}</a-menu-item + > + <a-menu-item + :disabled="!batchTreeKey || !batchTreeKey.length" + key="batchRevoke" + ><a-icon type="user-delete" />{{ $t('revoke') }}</a-menu-item > <a-menu-divider /> + <template v-if="showBatchLevel > 0"> + <a-menu-item + :disabled="!batchTreeKey || !batchTreeKey.length" + key="batchDelete" + ><ops-icon type="icon-xianxing-delete" />{{ $t('delete') }}</a-menu-item + > + <a-menu-divider /> + </template> + <a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item> </template> - <a-menu-item key="batchCancel"><a-icon type="close-circle" />{{ $t('cancel') }}</a-menu-item> - </template> - </a-menu> - <a-icon class="relation-views-node-operation" type="ellipsis" /> - </a-dropdown> - <a-icon :style="{ fontSize: '10px' }" v-if="childLength && !isLeaf" :type="switchIcon"></a-icon> + </a-menu> + <a-icon class="relation-views-node-operation" type="ellipsis" /> + </a-dropdown> + </span> </div> </template> <script> +import { updateCI } from '../../../api/ci.js' export default { name: 'ContextMenu', props: { @@ -86,6 +110,10 @@ export default { type: String, default: '', }, + number: { + type: Number, + default: 0, + }, treeKey: { type: String, default: '', @@ -121,13 +149,14 @@ export default { }, data() { return { - switchIcon: 'down', + switchIcon: 'caret-right', + isEditNodeName: false, + editNodeName: '', } }, computed: { childLength() { - const reg = /(?<=\()\S+(?=\))/g - return Number(this.title.match(reg)[0]) + return this.number }, splitTreeKey() { return this.treeKey.split('@^@') @@ -175,26 +204,63 @@ export default { }, methods: { onContextMenuClick(treeKey, menuKey) { + if (menuKey === 'editNodeName') { + this.isEditNodeName = true + this.editNodeName = this.title + this.$nextTick(() => { + this.$refs.input.focus() + }) + return + } this.$emit('onContextMenuClick', treeKey, menuKey) }, clickNode() { this.$emit('onNodeClick', this.treeKey) - this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down' + this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right' }, clickCheckbox() { this.$emit('clickCheckbox', this.treeKey) }, + changeNodeName(e) { + const value = e.target.value + if (value !== this.title) { + const ci = this.treeKey + .split('@^@') + .slice(-1)[0] + .split('%') + const unique = Object.keys(JSON.parse(ci[2]))[0] + const ciId = Number(ci[0]) + + updateCI(ciId, { [unique]: value }).then((res) => { + this.$message.success(this.$t('updateSuccess')) + this.$emit('updateTreeData', ciId, value) + }) + } + this.isEditNodeName = false + this.editNodeName = '' + }, }, } </script> <style lang="less" scoped> +@import '~@/style/static.less'; + .relation-views-node { width: 100%; display: inline-flex; justify-content: space-between; 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; overflow: hidden; align-items: center; @@ -215,16 +281,21 @@ export default { text-overflow: ellipsis; white-space: nowrap; 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-moveright { +.relation-views-node-checkbox { > span { .relation-views-node-checkbox { 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 lang="less"> -.relation-views-left .ant-tree-node-content-wrapper:hover { - .relation-views-node-operation { - display: inline-block; - } -} +@import '~@/style/static.less'; .relation-views-left { ul:has(.relation-views-node-checkbox) > li > ul { margin-left: 26px; @@ -249,5 +322,28 @@ export default { ul:has(.relation-views-node-checkbox) { margin-left: 0 !important; } + .ant-tree-node-content-wrapper:hover { + .relation-views-node-operation { + opacity: 1; + } + } + .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected, + .ant-tree li .ant-tree-node-content-wrapper:hover { + background-color: @primary-color_3; + } +} + +.relation-views-node-dropdown { + .ant-divider { + margin: 0; + .ant-divider-inner-text { + font-size: 12px; + color: @text-color_3; + } + } + .ant-dropdown-menu-item { + overflow: hidden; + text-overflow: ellipsis; + } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/resource_search/index.vue b/cmdb-ui/src/modules/cmdb/views/resource_search/index.vue index 0468e84..b50e22d 100644 --- a/cmdb-ui/src/modules/cmdb/views/resource_search/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/resource_search/index.vue @@ -1,196 +1,194 @@ <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"> <span> <span class="cmdb-views-header-title">{{ $t('cmdb.menu.ciSearch') }}</span> </span> - </div> - <div :style="{ backgroundColor: '#fff', padding: '12px', borderRadius: '15px' }"> - <SearchForm - ref="search" - type="resourceSearch" - @refresh="handleSearch" - :preferenceAttrList="allAttributesList" - @updateAllAttributesList="updateAllAttributesList" - @copyExpression="copyExpression" - /> - <div + <a-button v-if="!fromCronJob" - :style="{ - display: 'flex', - justifyContent: 'space-between', - height: '32px', - marginBottom: '5px', - alignItems: 'center', - }" - > - <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 }" + icon="download" + type="primary" + class="ops-button-ghost" + ghost + @click="handleExport" + >{{ $t('download') }}</a-button > + </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 - 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 - v-for="(col, index) in colGroup.children" - :key="`${col.field}_${index}`" - :title="col.title" - :field="col.field" - :width="col.width" - :minWidth="100" - :cell-type="col.value_type === '2' ? 'string' : 'auto'" - > - <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> + v-for="(col, index) in colGroup.children" + :key="`${col.field}_${index}`" + :title="col.title" + :field="col.field" + :width="col.width" + :minWidth="100" + :cell-type="col.value_type === '2' ? 'string' : 'auto'" + > + <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-else + v-for="value in row[col.field]" + :key="value" :style="{ borderRadius: '4px', padding: '1px 5px', - margin: '2px 0', - ...getChoiceValueStyle(col, row[col.field]), + margin: '2px', + ...getChoiceValueStyle(col, value), }" - > - <ops-icon - :style="{ color: getChoiceValueIcon(col, row[col.field]).color }" - :type="getChoiceValueIcon(col, row[col.field]).name" - /> - {{ row[col.field] }}</span + ><ops-icon + :style="{ color: getChoiceValueIcon(col, value).color }" + :type="getChoiceValueIcon(col, value).name" + />{{ value }}</span > </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> - </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> - </a-pagination> - </div> + </vxe-column> + </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> <BatchDownload @@ -227,6 +225,7 @@ export default { data() { return { ciTypes: [], + originAllAttributesList: [], allAttributesList: [], // 当前选择的模型的全部attributes 默认全部 currentPage: 1, pageSizeOptions: ['50', '100', '200', '100000'], @@ -260,18 +259,19 @@ export default { this.ciTypes = res.ci_types }) }, - getAllAttr() { - searchAttributes({ page_size: 9999 }).then((res) => { + async getAllAttr() { + await searchAttributes({ page_size: 9999 }).then((res) => { this.allAttributesList = res.attributes + this.originAllAttributesList = res.attributes }) }, - updateAllAttributesList(value) { + async updateAllAttributesList(value) { if (value && value.length) { - getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => { + await getCITypeAttributesByTypeIds({ type_ids: value.join(',') }).then((res) => { this.allAttributesList = res.attributes }) } else { - this.getAllAttr() + this.allAttributesList = this.originAllAttributesList } }, async loadInstance(sortByTable = undefined) { @@ -373,7 +373,6 @@ export default { } } }) - const _commonColumnsGroup = Object.keys(commonObject).map((key) => { return { id: `parent-${key}`, @@ -411,7 +410,7 @@ export default { return { ...item, id: item.field, label: item.title } }) }, - handleSearch() { + async handleSearch() { this.currentPage = 1 this.loadInstance() }, @@ -534,3 +533,14 @@ export default { }, } </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> diff --git a/cmdb-ui/src/modules/cmdb/views/tree_views/index.vue b/cmdb-ui/src/modules/cmdb/views/tree_views/index.vue index 9ffa723..4c27f2c 100644 --- a/cmdb-ui/src/modules/cmdb/views/tree_views/index.vue +++ b/cmdb-ui/src/modules/cmdb/views/tree_views/index.vue @@ -4,34 +4,15 @@ <a-alert :message="$t('cmdb.tree.tips1')" banner></a-alert> </div> <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 :min="200" :max="500" :paneLengthPixel.sync="paneLengthPixel" appName="cmdb-tree-views" - triggerColor="#F0F5FF" :triggerLength="18" > <template #one> - <div class="tree-views-left" :style="{ height: `${windowHeight - 115}px` }"> + <div class="tree-views-left" :style="{ height: `${windowHeight - 64}px` }"> <draggable v-model="subscribeTreeViewCiTypes" :animation="300" @@ -89,12 +70,12 @@ :expandedKeys="expandedKeys" v-if="Number(ciType.type_id) === Number(typeId)" > - <a-icon slot="switcherIcon" type="down" /> - <template #title="{ key: treeKey, title, isLeaf }"> + <template #title="{ key: treeKey, title, isLeaf, childLength}"> <TreeViewsNode :title="title" :treeKey="treeKey" :levels="levels" + :childLength="childLength" :isLeaf="isLeaf" @onNodeClick="onNodeClick" /> @@ -105,16 +86,49 @@ </div> </template> <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 ref="search" @refresh="reloadData" :preferenceAttrList="currentAttrList" :typeId="Number(typeId)" @copyExpression="copyExpression" - /> - <div class="tree-views-right-bar"> + > <PreferenceSearch + v-show="!selectedRowKeys.length" ref="preferenceSearch" @getQAndSort="getQAndSort" @setParamsFromPreferenceSearch="setParamsFromPreferenceSearch" @@ -129,7 +143,7 @@ <span>{{ $t('cmdb.ci.selectRows', { rows: selectedRowKeys.length }) }}</span> </template> </div> - </div> + </SearchForm> <ops-table :id="`cmdb-tree-${typeId}`" border @@ -150,7 +164,7 @@ :cell-style="getCellStyle" :scroll-y="{ enabled: true, gt: 20 }" :scroll-x="{ enabled: true, gt: 0 }" - :height="`${windowHeight - 252}px`" + :height="`${windowHeight - 240}px`" @checkbox-change="onSelectChange" @checkbox-all="onSelectChange" @checkbox-range-end="onSelectRangeEnd" @@ -159,7 +173,6 @@ @edit-actived="handleEditActived" :edit-config="{ trigger: 'dblclick', mode: 'row', showIcon: false }" class="ops-unstripe-table" - :style="{ margin: '0 -12px' }" :custom-config="{ storage: true }" > <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"> <template #header> <span>{{ $t('operation') }}</span> - <EditAttrsPopover :typeId="Number(typeId)" class="operation-icon" @refresh="refreshAfterEditAttrs" /> </template> <template #default="{ row }"> <a-space> @@ -693,7 +705,8 @@ export default { console.log('facet', facet) const _treeData = Object.values(facet)[0].map((item) => { return { - title: `${item[0]} (${item[1]})`, + title: item[0], + childLength: item[1], key: this.treeKeys.join(this.keySplit) + this.keySplit + item[0], isLeaf: this.levels.length - 1 === this.treeKeys.length, } @@ -1228,12 +1241,8 @@ export default { .tree-views-left { float: left; position: relative; - background-color: #fff; overflow: hidden; width: 100%; - padding: 12px; - border-top-left-radius: 15px; - border-top-right-radius: 15px; &:hover { overflow: auto; } @@ -1249,7 +1258,7 @@ export default { border-radius: 2px; position: relative; &:hover { - background-color: #f0f5ff; + background-color: @primary-color_3; > .actions, > .move-icon { display: inherit; @@ -1270,9 +1279,7 @@ export default { width: 20px; height: 20px; border-radius: 2px; - box-shadow: 0px 1px 2px rgba(47, 84, 235, 0.2); margin-right: 6px; - background-color: #fff; } .tree-views-left-header-name { flex: 1; @@ -1281,6 +1288,7 @@ export default { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + color: @text-color_1; } .actions { display: none; @@ -1298,7 +1306,7 @@ export default { } } .custom-header-selected { - background-color: #d3e3fd !important; + background-color: @primary-color_3 !important; } .ant-tree li { padding: 2px 0; @@ -1317,23 +1325,18 @@ export default { padding: 0 6px; } } + .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected { + background-color: @primary-color_3; + } } .tree-views-right { background-color: #fff; display: flex; flex-direction: column; - padding: 12px; + padding: 20px; overflow: auto; width: 100%; - border-radius: 15px; - .tree-views-right-bar { - display: inline-flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - margin-bottom: 10px; - height: 36px; - } + border-radius: @border-radius-box; } } </style> diff --git a/cmdb-ui/src/modules/cmdb/views/tree_views/modules/treeViewsNode.vue b/cmdb-ui/src/modules/cmdb/views/tree_views/modules/treeViewsNode.vue index 4749b86..bba2f36 100644 --- a/cmdb-ui/src/modules/cmdb/views/tree_views/modules/treeViewsNode.vue +++ b/cmdb-ui/src/modules/cmdb/views/tree_views/modules/treeViewsNode.vue @@ -1,15 +1,11 @@ <template> - <div - :style="{ - width: '100%', - display: 'inline-flex', - justifyContent: 'space-between', - alignItems: 'center', - }" - @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 @click="clickNode" class="tree-views-node"> + <a-icon v-if="childLength && !isLeaf" :type="switchIcon"></a-icon> + <div v-else></div> + <div class="tree-views-node-content"> + <span>{{ this.title }}</span> + <span>{{ childLength }}</span> + </div> </div> </template> @@ -18,7 +14,7 @@ export default { name: 'TreeViewsNode', props: { title: { - type: String, + type: [String, Number], default: '', }, treeKey: { @@ -33,25 +29,57 @@ export default { type: Boolean, default: () => false, }, + childLength: { + type: Number, + default: 0, + }, }, data() { return { - switchIcon: 'down', + switchIcon: 'caret-right', } }, - computed: { - childLength() { - const reg = /(?<=\()\S+(?=\))/g - return Number(this.title.match(reg)[0]) - }, - }, + computed: {}, methods: { clickNode() { this.$emit('onNodeClick', this.treeKey) - this.switchIcon = this.switchIcon === 'down' ? 'up' : 'down' + this.switchIcon = this.switchIcon === 'caret-right' ? 'caret-down' : 'caret-right' }, }, } </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> diff --git a/cmdb-ui/src/style/global.less b/cmdb-ui/src/style/global.less index dcb0552..790f2e4 100644 --- a/cmdb-ui/src/style/global.less +++ b/cmdb-ui/src/style/global.less @@ -50,7 +50,7 @@ body { } .ant-layout { - background-color: #f7f8fa; + background-color: @layout-content-background; } .layout.ant-layout { @@ -161,7 +161,7 @@ body { transition: width 0.2s; &.ant-header-side-opened { - width: calc(100% - 200px); + width: calc(100% - 220px); } &.ant-header-side-closed { @@ -411,7 +411,6 @@ body { // 菜单样式 .sider { - box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); position: relative; z-index: 10; min-height: 100vh; @@ -431,19 +430,9 @@ body { &.ant-fixed-sidemenu { position: fixed; height: 100%; - background: @layout-background-color; 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 { position: relative; height: @layout-header-height; @@ -478,21 +467,11 @@ body { &.light { background-color: #fff; - box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05); .logo { 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 { transition: none; + .ant-menu-item { + text-overflow: initial !important; + } } .ops-side-bar.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { background: @layout-sidebar-selected-color; @@ -525,7 +507,7 @@ body { } .ops-side-bar.ant-menu-light { - border-right-color: transparent; + border-right-color: #e8e8e8; background: @layout-sidebar-color; background-repeat: no-repeat !important; background-size: cover; @@ -538,6 +520,9 @@ body { display: inline-flex; align-items: center; color: @layout-sidebar-font-color; + i { + color: @text-color_5; + } } &:hover { .scroll { @@ -590,8 +575,11 @@ body { .ant-menu-item-selected { a, a:hover { - color: @layout-sidebar-font-color; + color: @layout-sidebar-selected-font-color; font-weight: 600; + i { + color: @layout-sidebar-selected-font-color; + } } } .ant-menu-item::after, @@ -601,6 +589,9 @@ body { .ant-menu-submenu { color: @layout-sidebar-font-color; + i { + color: @text-color_5; + } } .ant-menu-submenu-title:hover { color: @layout-sidebar-font-color; @@ -613,8 +604,11 @@ body { } .ant-menu-submenu-selected { > .ant-menu-submenu-title { - color: @layout-sidebar-font-color; + color: @layout-sidebar-selected-font-color; font-weight: 800; + i { + color: @layout-sidebar-selected-font-color; + } } } .ant-menu-submenu-content .ant-menu-submenu-active .ant-menu-submenu-title { @@ -852,7 +846,7 @@ body { .ant-layout-sider { box-shadow: none; .ant-layout-sider-children { - background: @primary-color_5; + background: @primary-color_7; .ant-menu { 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; border: @border; } @@ -956,13 +950,13 @@ body { //非斑马纹 .ops-unstripe-table { .vxe-table--border-line { - border: none !important; + // border: none !important; } .vxe-table--header-wrapper { - background-color: @primary-color_5 !important; + background-color: @primary-color_6 !important; } .vxe-header--row .vxe-header--column:hover { - background: #2f54eb1f !important; + background: @primary-color_3; } } .ops-unstripe-table.vxe-table--render-default.border--full { @@ -983,7 +977,7 @@ body { border: none !important; } .vxe-table--header-wrapper { - background-color: @primary-color_5 !important; + background-color: @primary-color_6 !important; } // .vxe-table--header-wrapper.body--wrapper { // border-radius: 8px !important; @@ -1010,19 +1004,19 @@ body { } } .vxe-header--row .vxe-header--column:hover { - background: #2f54eb1f !important; + background: @primary-color_3; } } .ops-input { .ant-input, .ant-time-picker-input { - background-color: @primary-color_5; + background-color: @primary-color_7; border: none; } } .ops-input.ant-input { - background-color: @primary-color_5; + background-color: @primary-color_7; border: none; } .ops-input.ant-input[disabled] { @@ -1030,14 +1024,6 @@ body { color: #333; cursor: default; } -.ops-input-radius { - .ant-input { - border-radius: 20px; - } -} -.ops-input-radius.ant-input { - border-radius: 20px; -} // vxe-table checkbox 选中 highlight .vxe-table--render-default .vxe-body--row.row--checked, @@ -1110,12 +1096,12 @@ body { //批量操作 .ops-list-batch-action { display: inline-block; - background-color: @primary-color_5; + background-color: @primary-color_6; font-size: 12px; - color: rgba(0, 0, 0, 0.55); + color: @text-color_3; > span { display: inline-block; - padding: 4px 8px; + padding: 7px 8px; cursor: pointer; &:hover { color: @primary-color; @@ -1127,7 +1113,7 @@ body { } } -//tab +// card tab .ops-tab.ant-tabs.ant-tabs-card { .ant-tabs-card-bar { 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 { background-color: @primary-color_4; border-color: @primary-color_4; color: @primary-color; 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 { &.white { .ant-select-selection { @@ -1173,10 +1197,6 @@ body { } } } - .ant-select-selection { - background-color: @primary-color_5; - border-color: @primary-color_5; - } } //dropdown @@ -1190,9 +1210,7 @@ body { //modal .ant-modal-content { - border-radius: 15px; .ant-modal-header { - border-radius: 15px; border-bottom: none; .ant-modal-title { padding-left: 10px; @@ -1328,12 +1346,8 @@ body { border-bottom-color: @primary-color; } } -// .ant-menu.ant-menu-light { -// &.ops-menu { -// background-color: white; -// background: none; -// .ant-menu-submenu { -// color: rgba(0, 0, 0, 0.65); -// } -// } -// } + +.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; +} diff --git a/cmdb-ui/src/style/static.less b/cmdb-ui/src/style/static.less index 096a500..0e3b422 100644 --- a/cmdb-ui/src/style/static.less +++ b/cmdb-ui/src/style/static.less @@ -1,41 +1,48 @@ @border-radius-base: 2px; // 组件/浮层圆角 @border-radius-box: 4px; // big box radius -@primary-color: #2f54eb; // 全局主色 六大品牌色 +// primary color +@primary-color: #2f54eb; @primary-color_2: #7f97fa; -@primary-color_3: #d3e3fd; +@primary-color_3: #ebeff8; @primary-color_4: #e1efff; @primary-color_5: #f0f5ff; @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_3: #86909c; @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); +@layout-content-background: @primary-color_7; @layout-header-background: #fff; @layout-header-height: 40px; @layout-header-icon-height: 34px; @layout-header-font-color: #020000; -@layout-header-font-selected-color: #2857bc; +@layout-header-font-selected-color: @primary-color; -//dark -@layout-background-color: #182132; -@layout-background-color-light: #262f40; -//light -@layout-background-light-color: #fafafa; -@layout-background-light-color-light: #f0f0f0; - -@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); +@layout-sidebar-color: #ffffff; //bg +@layout-sidebar-sub-color: @primary-color_7; //bg +@layout-sidebar-selected-color: @primary-color_5; //selected bg +@layout-sidebar-arrow-color: @text-color_4; +@layout-sidebar-font-color: @text-color_2; +@layout-sidebar-selected-font-color: @primary-color; +@layout-sidebar-disabled-font-color: @text-color_4; #custom_colors() { color_1: #2f54eb; //primary color @@ -47,7 +54,7 @@ cursor: pointer; padding: 5px 8px; background-color: @backgroundColor; - border-radius: 5px; + border-radius: @border-radius-box; height: 30px; color: rgba(0, 0, 0, 0.6); white-space: nowrap; @@ -57,10 +64,10 @@ cursor: pointer; padding: 5px 10px; &:hover { - background-color: @primary-color_5; + background-color: @primary-color_3; } } .ops_popover_item_selected() { - background-color: @primary-color_5; + background-color: @primary-color_3; color: @primary-color; } diff --git a/cmdb-ui/src/views/setting/auth/index.vue b/cmdb-ui/src/views/setting/auth/index.vue index 1bfd111..65a2b59 100644 --- a/cmdb-ui/src/views/setting/auth/index.vue +++ b/cmdb-ui/src/views/setting/auth/index.vue @@ -17,8 +17,12 @@ <a-space> <a-button :loading="loading" type="primary" @click="handleSave">{{ $t('save') }}</a-button> <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('login')">{{ $t('cs.auth.testLogin') }}</a-button> + <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('login')">{{ + $t('cs.auth.testLogin') + }}</a-button> </template> <a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button> </a-space> @@ -56,28 +60,28 @@ export default { computed: { authList() { return [ - { - value: 'LDAP', - label: 'LDAP', - }, - { - value: 'CAS', - label: 'CAS', - }, - { - value: 'OAUTH2', - label: 'OAUTH2', - }, - { - value: 'OIDC', - label: 'OIDC', - }, - { - value: 'AuthCommonConfig', - label: this.$t('cs.auth.common'), - }, - ] - } + { + value: 'LDAP', + label: 'LDAP', + }, + { + value: 'CAS', + label: 'CAS', + }, + { + value: 'OAUTH2', + label: 'OAUTH2', + }, + { + value: 'OIDC', + label: 'OIDC', + }, + { + value: 'AuthCommonConfig', + label: this.$t('cs.auth.common'), + }, + ] + }, }, methods: { getAuthDataEnable() { @@ -129,7 +133,7 @@ export default { ...values, } } - testLDAP(type, { data: _data }) + testLDAP({ data: _data, test_type: type }) .then((res) => { this.$message.success(this.$t('cs.auth.testSuccess')) }) diff --git a/cmdb-ui/src/views/user/Login.vue b/cmdb-ui/src/views/user/Login.vue index a0d60aa..9353633 100644 --- a/cmdb-ui/src/views/user/Login.vue +++ b/cmdb-ui/src/views/user/Login.vue @@ -1,10 +1,12 @@ <template> <div class="ops-login"> <div class="ops-login-left"> - <span>维易科技<br />让运维更简单</span> + <span>OneOps统一运维平台</span> + <img src="../../assets/login_img.png" /> </div> <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 id="formLogin" ref="formLogin" @@ -43,7 +45,7 @@ <a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox> </a-form-item> - <a-form-item style="margin-top: 24px"> + <a-form-item style="margin-top:24px"> <a-button size="large" type="primary" @@ -53,13 +55,18 @@ :disabled="state.loginBtn" >登录</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> <template v-if="_enable_list && _enable_list.length >= 1"> <a-divider style="font-size:14px">其他登录方式</a-divider> <div style="text-align:center"> <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-divider v-if="index < _enable_list.length - 1" type="vertical" /> </span> @@ -93,6 +100,7 @@ export default { loginType: 0, smsSendBtn: false, }, + auth_with_ldap: false, } }, computed: { @@ -104,6 +112,18 @@ export default { 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() {}, async mounted() { await this.GetAuthDataEnable() @@ -124,10 +144,12 @@ export default { handleSubmit(e) { e.preventDefault() const { + enable_list, form: { validateFields }, state, customActiveKey, Login, + auth_with_ldap, } = this state.loginBtn = true @@ -136,11 +158,15 @@ export default { validateFields(validateFieldsKey, { force: true }, (err, values) => { if (!err) { - console.log('login form', values) const loginParams = { ...values } delete loginParams.username loginParams.username = values.username 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', '') Login({ userInfo: loginParams }) .then((res) => this.loginSuccess(res)) @@ -184,6 +210,13 @@ export default { background: url('../../assets/login_bg.png') no-repeat; background-position: center; background-size: cover; + > img { + width: 80%; + position: absolute; + top: 60%; + left: 50%; + transform: translate(-50%, -50%); + } > span { color: white; position: absolute; @@ -191,7 +224,6 @@ export default { left: 50%; transform: translateX(-50%); font-size: 1.75vw; - text-align: center; } } .ops-login-right { @@ -202,6 +234,14 @@ export default { width: 70%; 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 { width: 100%; }