Merge pull request from veops/dev_ui_240820

feat(ui): add bool and reference type
This commit is contained in:
Leo Song 2024-08-20 15:31:45 +08:00 committed by GitHub
commit ecb069cf14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 2318 additions and 1085 deletions

View File

@ -54,6 +54,12 @@
<div class="content unicode" style="display: block;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe997;</span>
<div class="name">duose-changwenben (1)</div>
<div class="code-name">&amp;#xe997;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe995;</span> <span class="icon iconfont">&#xe995;</span>
<div class="name">duose-quote</div> <div class="name">duose-quote</div>
@ -5508,9 +5514,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1723012344599') format('woff2'), src: url('iconfont.woff2?t=1724135954264') format('woff2'),
url('iconfont.woff?t=1723012344599') format('woff'), url('iconfont.woff?t=1724135954264') format('woff'),
url('iconfont.ttf?t=1723012344599') format('truetype'); url('iconfont.ttf?t=1724135954264') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -5536,6 +5542,15 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont duose-changwenben1"></span>
<div class="name">
duose-changwenben (1)
</div>
<div class="code-name">.duose-changwenben1
</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont duose-quote"></span> <span class="icon iconfont duose-quote"></span>
<div class="name"> <div class="name">
@ -13717,6 +13732,14 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#duose-changwenben1"></use>
</svg>
<div class="name">duose-changwenben (1)</div>
<div class="code-name">#duose-changwenben1</div>
</li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#duose-quote"></use> <use xlink:href="#duose-quote"></use>

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3857903 */ font-family: "iconfont"; /* Project id 3857903 */
src: url('iconfont.woff2?t=1723012344599') format('woff2'), src: url('iconfont.woff2?t=1724135954264') format('woff2'),
url('iconfont.woff?t=1723012344599') format('woff'), url('iconfont.woff?t=1724135954264') format('woff'),
url('iconfont.ttf?t=1723012344599') format('truetype'); url('iconfont.ttf?t=1724135954264') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.duose-changwenben1:before {
content: "\e997";
}
.duose-quote:before { .duose-quote:before {
content: "\e995"; content: "\e995";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "", "css_prefix_text": "",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "41437322",
"name": "duose-changwenben (1)",
"font_class": "duose-changwenben1",
"unicode": "e997",
"unicode_decimal": 59799
},
{ {
"icon_id": "41363381", "icon_id": "41363381",
"name": "duose-quote", "name": "duose-quote",

Binary file not shown.

18
cmdb-ui/src/api/cmdb.js Normal file
View File

@ -0,0 +1,18 @@
import { axios } from '@/utils/request'
export function searchCI(params, isShowMessage = true) {
return axios({
url: `/v0.1/ci/s`,
method: 'GET',
params: params,
isShowMessage
})
}
export function getCIType(CITypeName, parameter) {
return axios({
url: `/v0.1/ci_types/${CITypeName}`,
method: 'GET',
params: parameter
})
}

View File

@ -1,346 +1,389 @@
<template> <template>
<div> <div>
<a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id"> <a-space :style="{ display: 'flex', marginBottom: '10px' }" v-for="(item, index) in ruleList" :key="item.id">
<div :style="{ width: '70px', height: '24px', position: 'relative' }"> <div :style="{ width: '70px', height: '24px', position: 'relative' }">
<treeselect <treeselect
v-if="index" v-if="index"
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }" :style="{ width: '70px', '--custom-height': '24px', position: 'absolute', top: '-17px', left: 0 }"
v-model="item.type" v-model="item.type"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="ruleTypeList" :options="ruleTypeList"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.value, id: node.value,
label: node.label, label: node.label,
children: node.children, children: node.children,
} }
} }
" "
:disabled="disabled" :disabled="disabled"
> >
</treeselect> </treeselect>
</div> </div>
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '130px', '--custom-height': '24px' }" :style="{ width: '130px', '--custom-height': '24px' }"
v-model="item.property" v-model="item.property"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="canSearchPreferenceAttrList" :options="canSearchPreferenceAttrList"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.name, id: node.name,
label: node.alias || node.name, label: node.alias || node.name,
children: node.children, children: node.children,
} }
} }
" "
appendToBody appendToBody
:zIndex="1050" :zIndex="1050"
:disabled="disabled" :disabled="disabled"
> >
<div <div
:title="node.label" :title="node.label"
slot="option-label" slot="option-label"
slot-scope="{ node }" slot-scope="{ node }"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
> >
<ValueTypeMapIcon :attr="node.raw" /> <ValueTypeMapIcon :attr="node.raw" />
{{ node.label }} {{ node.label }}
</div> </div>
<div <div
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
slot="value-label" slot="value-label"
slot-scope="{ node }" slot-scope="{ node }"
> >
<ValueTypeMapIcon :attr="node.raw" /> {{ node.label }} <ValueTypeMapIcon :attr="node.raw" /> {{ node.label }}
</div> </div>
</treeselect> </treeselect>
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '100px', '--custom-height': '24px' }" :style="{ width: '100px', '--custom-height': '24px' }"
v-model="item.exp" v-model="item.exp"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="[...getExpListByProperty(item.property), ...advancedExpList]" :options="[...getExpListByProperty(item.property), ...advancedExpList]"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node.value, id: node.value,
label: node.label, label: node.label,
children: node.children, children: node.children,
} }
} }
" "
@select="(value) => handleChangeExp(value, item, index)" @select="(value) => handleChangeExp(value, item, index)"
appendToBody appendToBody
:zIndex="1050" :zIndex="1050"
:disabled="disabled" :disabled="disabled"
> >
</treeselect> </treeselect>
<treeselect <CIReferenceAttr
class="custom-treeselect" v-if="getAttr(item.property).is_reference && (item.exp === 'is' || item.exp === '~is')"
:style="{ width: '175px', '--custom-height': '24px' }" :style="{ width: '175px' }"
v-model="item.value" class="select-filter-component"
:multiple="false" :referenceTypeId="getAttr(item.property).reference_type_id"
:clearable="false" :disabled="disabled"
searchable v-model="item.value"
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')" />
:options="getChoiceValueByProperty(item.property)" <a-select
:placeholder="$t('placeholder2')" v-else-if="getAttr(item.property).is_bool && (item.exp === 'is' || item.exp === '~is')"
:normalizer=" v-model="item.value"
(node) => { class="select-filter-component"
return { :style="{ width: '175px' }"
id: node[0], :disabled="disabled"
label: node[0], :placeholder="$t('placeholder2')"
children: node.children, >
} <a-select-option key="1">
} true
" </a-select-option>
appendToBody <a-select-option key="0">
:zIndex="1050" false
:disabled="disabled" </a-select-option>
> </a-select>
<div <treeselect
:title="node.label" class="custom-treeselect"
slot="option-label" :style="{ width: '175px', '--custom-height': '24px' }"
slot-scope="{ node }" v-model="item.value"
:style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }" :multiple="false"
> :clearable="false"
{{ node.label }} searchable
</div> v-else-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
</treeselect> :options="getChoiceValueByProperty(item.property)"
<a-input-group :placeholder="$t('placeholder2')"
size="small" :normalizer="
compact (node) => {
v-else-if="item.exp === 'range' || item.exp === '~range'" return {
:style="{ width: '175px' }" id: node[0],
> label: node[0],
<a-input children: node.children,
class="ops-input" }
size="small" }
v-model="item.min" "
:style="{ width: '78px' }" appendToBody
:placeholder="$t('min')" :zIndex="1050"
:disabled="disabled" :disabled="disabled"
/> >
~ <div
<a-input :title="node.label"
class="ops-input" slot="option-label"
size="small" slot-scope="{ node }"
v-model="item.max" :style="{ width: '100%', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }"
:style="{ width: '78px' }" >
:placeholder="$t('max')" {{ node.label }}
:disabled="disabled" </div>
/> </treeselect>
</a-input-group> <a-input-group
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }"> size="small"
<treeselect compact
class="custom-treeselect" v-else-if="item.exp === 'range' || item.exp === '~range'"
:style="{ width: '60px', '--custom-height': '24px' }" :style="{ width: '175px' }"
v-model="item.compareType" >
:multiple="false" <a-input
:clearable="false" class="ops-input"
searchable size="small"
:options="compareTypeList" v-model="item.min"
:normalizer=" :style="{ width: '78px' }"
(node) => { :placeholder="$t('min')"
return { :disabled="disabled"
id: node.value, />
label: node.label, ~
children: node.children, <a-input
} class="ops-input"
} size="small"
" v-model="item.max"
appendToBody :style="{ width: '78px' }"
:zIndex="1050" :placeholder="$t('max')"
:disabled="disabled" :disabled="disabled"
> />
</treeselect> </a-input-group>
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" /> <a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
</a-input-group> <treeselect
<a-input class="custom-treeselect"
v-else-if="item.exp !== 'value' && item.exp !== '~value'" :style="{ width: '60px', '--custom-height': '24px' }"
size="small" v-model="item.compareType"
v-model="item.value" :multiple="false"
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''" :clearable="false"
class="ops-input" searchable
:style="{ width: '175px' }" :options="compareTypeList"
:disabled="disabled" :normalizer="
></a-input> (node) => {
<div v-else :style="{ width: '175px' }"></div> return {
<template v-if="!disabled"> id: node.value,
<a-tooltip :title="$t('copy')"> label: node.label,
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a> children: node.children,
</a-tooltip> }
<a-tooltip :title="$t('delete')"> }
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> "
</a-tooltip> appendToBody
<a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere"> :zIndex="1050"
<a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a> :disabled="disabled"
</a-tooltip> >
</template> </treeselect>
</a-space> <a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
<div class="table-filter-add" v-if="!disabled"> </a-input-group>
<a @click="handleAddRule">+ {{ $t('new') }}</a> <a-input
</div> v-else-if="item.exp !== 'value' && item.exp !== '~value'"
</div> size="small"
</template> v-model="item.value"
:placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
<script> class="ops-input"
import _ from 'lodash' :style="{ width: '175px' }"
import { v4 as uuidv4 } from 'uuid' :disabled="disabled"
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants' ></a-input>
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon' <div v-else :style="{ width: '175px' }"></div>
<template v-if="!disabled">
export default { <a-tooltip :title="$t('copy')">
name: 'Expression', <a class="operation" @click="handleCopyRule(item)"><ops-icon type="veops-copy"/></a>
components: { ValueTypeMapIcon }, </a-tooltip>
model: { <a-tooltip :title="$t('delete')">
prop: 'value', <a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
event: 'change', </a-tooltip>
}, <a-tooltip :title="$t('cmdbFilterComp.addHere')" v-if="needAddHere">
props: { <a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
value: { </a-tooltip>
type: Array, </template>
default: () => [], </a-space>
}, <div class="table-filter-add" v-if="!disabled">
canSearchPreferenceAttrList: { <a @click="handleAddRule">+ {{ $t('new') }}</a>
type: Array, </div>
required: true, </div>
default: () => [], </template>
},
needAddHere: { <script>
type: Boolean, import _ from 'lodash'
default: false, import { v4 as uuidv4 } from 'uuid'
}, import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
disabled: { import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
type: Boolean, import CIReferenceAttr from '../ciReferenceAttr/index.vue'
default: false,
}, export default {
}, name: 'Expression',
data() { components: { ValueTypeMapIcon, CIReferenceAttr },
return { model: {
compareTypeList, prop: 'value',
} event: 'change',
}, },
computed: { props: {
ruleList: { value: {
get() { type: Array,
return this.value default: () => [],
}, },
set(val) { canSearchPreferenceAttrList: {
this.$emit('change', val) type: Array,
return val required: true,
}, default: () => [],
}, },
ruleTypeList() { needAddHere: {
return ruleTypeList() type: Boolean,
}, default: false,
expList() { },
return expList() disabled: {
}, type: Boolean,
advancedExpList() { default: false,
return advancedExpList() },
}, },
}, data() {
methods: { return {
getExpListByProperty(property) { compareTypeList,
if (property) { }
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) },
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) { computed: {
return [ ruleList: {
{ value: 'is', label: this.$t('cmdbFilterComp.is') }, get() {
{ value: '~is', label: this.$t('cmdbFilterComp.~is') }, return this.value
{ value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕 },
{ value: 'value', label: this.$t('cmdbFilterComp.value') }, set(val) {
] this.$emit('change', val)
} return val
return this.expList },
} },
return this.expList ruleTypeList() {
}, return ruleTypeList()
isChoiceByProperty(property) { },
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) expList() {
if (_find) { return expList()
return _find.is_choice },
} advancedExpList() {
return false return advancedExpList()
}, },
handleAddRule() { },
this.ruleList.push({ methods: {
id: uuidv4(), getExpListByProperty(property) {
type: 'and', if (property) {
property: this.canSearchPreferenceAttrList[0]?.name, const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
exp: 'is', if (_find && (['0', '1', '3', '4', '5'].includes(_find.value_type) || _find.is_reference || _find.is_bool)) {
value: null, return [
}) { value: 'is', label: this.$t('cmdbFilterComp.is') },
this.$emit('change', this.ruleList) { value: '~is', label: this.$t('cmdbFilterComp.~is') },
}, { value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
handleCopyRule(item) { { value: 'value', label: this.$t('cmdbFilterComp.value') },
this.ruleList.push({ ...item, id: uuidv4() }) ]
this.$emit('change', this.ruleList) }
}, return this.expList
handleDeleteRule(item) { }
const idx = this.ruleList.findIndex((r) => r.id === item.id) return this.expList
if (idx > -1) { },
this.ruleList.splice(idx, 1) isChoiceByProperty(property) {
} const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
this.$emit('change', this.ruleList) if (_find) {
}, return _find.is_choice
handleAddRuleAt(item) { }
const idx = this.ruleList.findIndex((r) => r.id === item.id) return false
if (idx > -1) { },
this.ruleList.splice(idx, 0, { handleAddRule() {
id: uuidv4(), this.ruleList.push({
type: 'and', id: uuidv4(),
property: this.canSearchPreferenceAttrList[0]?.name, type: 'and',
exp: 'is', property: this.canSearchPreferenceAttrList[0]?.name,
value: null, exp: 'is',
}) value: null,
} })
this.$emit('change', this.ruleList) this.$emit('change', this.ruleList)
}, },
getChoiceValueByProperty(property) { handleCopyRule(item) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) this.ruleList.push({ ...item, id: uuidv4() })
if (_find) { this.$emit('change', this.ruleList)
return _find.choice_value },
} handleDeleteRule(item) {
return [] const idx = this.ruleList.findIndex((r) => r.id === item.id)
}, if (idx > -1) {
handleChangeExp({ value }, item, index) { this.ruleList.splice(idx, 1)
const _ruleList = _.cloneDeep(this.ruleList) }
if (value === 'range') { this.$emit('change', this.ruleList)
_ruleList[index] = { },
..._ruleList[index], handleAddRuleAt(item) {
min: '', const idx = this.ruleList.findIndex((r) => r.id === item.id)
max: '', if (idx > -1) {
exp: value, this.ruleList.splice(idx, 0, {
} id: uuidv4(),
} else if (value === 'compare') { type: 'and',
_ruleList[index] = { property: this.canSearchPreferenceAttrList[0]?.name,
..._ruleList[index], exp: 'is',
compareType: '1', value: null,
exp: value, })
} }
} else { this.$emit('change', this.ruleList)
_ruleList[index] = { },
..._ruleList[index], getChoiceValueByProperty(property) {
exp: value, const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
} if (_find) {
} return _find.choice_value
this.ruleList = _ruleList }
this.$emit('change', this.ruleList) return []
}, },
}, getAttr(property) {
} return this.canSearchPreferenceAttrList.find((item) => item.name === property) || {}
</script> },
handleChangeExp({ value }, item, index) {
<style></style> const _ruleList = _.cloneDeep(this.ruleList)
if (value === 'range') {
_ruleList[index] = {
..._ruleList[index],
min: '',
max: '',
exp: value,
}
} else if (value === 'compare') {
_ruleList[index] = {
..._ruleList[index],
compareType: '1',
exp: value,
}
} else {
_ruleList[index] = {
..._ruleList[index],
exp: value,
}
}
this.ruleList = _ruleList
this.$emit('change', this.ruleList)
},
},
}
</script>
<style lang="less" scoped>
.select-filter-component {
height: 24px;
/deep/ .ant-select-selection {
height: 24px;
background: #f7f8fa;
line-height: 24px;
border: none;
.ant-select-selection__rendered {
height: 24px;
line-height: 24px;
}
}
}
</style>

View File

@ -1,49 +1,77 @@
<template> <template>
<span> <span>
<ops-icon :type="getPropertyIcon(attr)" /> <ops-icon :type="getPropertyIcon(attr)" />
</span> </span>
</template> </template>
<script> <script>
export default { export default {
name: 'ValueTypeIcon', name: 'ValueTypeIcon',
props: { props: {
attr: { attr: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
}, },
methods: { methods: {
getPropertyIcon(attr) { getPropertyIcon(attr) {
switch (attr.value_type) { let valueType = attr.value_type
case '0':
return 'duose-shishu' if (valueType === '2') {
case '1': if (attr.is_password) {
return 'duose-fudianshu' valueType = '7'
case '2': } else if (attr.is_link) {
if (attr.is_password) { valueType = '8'
return 'duose-password' } else if (!attr.is_index) {
} valueType = '9'
if (attr.is_link) { }
return 'duose-link' }
}
return 'duose-wenben' if (
case '3': valueType === '7' &&
return 'duose-datetime' attr.is_bool
case '4': ) {
return 'duose-date' valueType = '10'
case '5': }
return 'duose-time'
case '6': if (
return 'duose-json' valueType === '0' &&
case '7': attr.is_reference
return 'duose-password' ) {
case '8': valueType = '11'
return 'duose-link' }
}
}, switch (valueType) {
}, case '0':
} return 'duose-shishu'
</script> case '1':
return 'duose-fudianshu'
<style></style> case '2':
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'
case '9':
return 'duose-changwenben1'
case '10':
return 'duose-boole'
case '11':
return 'duose-quote'
default:
return ''
}
},
},
}
</script>
<style></style>

View File

@ -61,7 +61,13 @@
</template> </template>
</div> </div>
</div> </div>
<a-input ref="regInput" :placeholder="$t('regexSelect.placeholder')" :value="current.label" @change="changeLabel"> <a-input
ref="regInput"
:placeholder="$t('regexSelect.placeholder')"
:value="current.label"
:disabled="disabled"
@change="changeLabel"
>
</a-input> </a-input>
</a-popover> </a-popover>
</template> </template>
@ -88,6 +94,10 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
disabled: {
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {

View File

@ -0,0 +1,178 @@
<template>
<div class="reference-attr-select-wrap">
<a-select
v-bind="$attrs"
v-model="selectCIIds"
optionFilterProp="title"
:mode="isList ? 'multiple' : 'default'"
showSearch
allowClear
:getPopupContainer="(trigger) => trigger.parentElement"
class="reference-attr-select"
:maxTagCount="2"
@dropdownVisibleChange="handleDropdownVisibleChange"
@search="handleSearch"
@change="handleChange"
>
<template v-if="!isInit">
<a-select-option
v-for="(item) in initSelectOption"
:key="item.key"
:title="item.title"
>
{{ item.title }}
</a-select-option>
</template>
<a-select-option
v-for="(item) in options"
:key="item.key"
:title="item.title"
>
{{ item.title }}
</a-select-option>
</a-select>
</div>
</template>
<script>
import _ from 'lodash'
import debounce from 'lodash/debounce'
import { searchCI, getCIType } from '@/api/cmdb'
export default {
name: 'CIReferenceAttr',
props: {
value: {
type: [Number, String, Array],
default: () => '',
},
isList: {
type: Boolean,
default: false,
},
referenceShowAttrName: {
type: String,
default: ''
},
referenceTypeId: {
type: [String, Number],
default: ''
},
initSelectOption: {
type: Array,
default: () => []
}
},
model: {
prop: 'value',
event: 'change',
},
data() {
return {
isInit: false,
options: [],
innerReferenceShowAttrName: ''
}
},
watch: {
referenceTypeId: {
immediate: true,
deep: true,
handler() {
this.isInit = false
}
}
},
computed: {
selectCIIds: {
get() {
if (this.isList) {
return this.value || []
} else {
return this.value ? Number(this.value) : ''
}
},
set(val) {
this.$emit('change', val ?? (this.isList ? [] : null))
return val
},
},
},
methods: {
async handleDropdownVisibleChange(open) {
if (!this.isInit && open && this.referenceTypeId) {
this.isInit = true
if (!this.referenceShowAttrName) {
const res = await getCIType(this.referenceTypeId)
const ciType = res?.ci_types?.[0]
this.innerReferenceShowAttrName = ciType?.show_name || ciType?.unique_name || ''
}
const attrName = this.referenceShowAttrName || this.innerReferenceShowAttrName || ''
if (!attrName) {
return
}
const res = await searchCI({
q: `_type:${this.referenceTypeId}`,
fl: attrName,
count: 25,
})
let options = res?.result?.map((item) => {
return {
key: item._id,
title: String(item?.[attrName] ?? '')
}
})
options = _.uniqBy([...this.initSelectOption, ...options], 'key')
this.options = options
}
},
handleSearch: debounce(async function(v) {
const attrName = this.referenceShowAttrName || this.innerReferenceShowAttrName || ''
if (!attrName || !this.referenceTypeId) {
return
}
const res = await searchCI({
q: `_type:${this.referenceTypeId}${v ? ',*' + v + '*' : ''}`,
fl: attrName,
count: v ? 100 : 25,
})
this.options = res?.result?.map((item) => {
return {
key: item._id,
title: String(item?.[attrName] ?? '')
}
})
}, 300),
handleChange(v) {
if (Array.isArray(v) ? !v.length : !v) {
this.handleSearch()
}
}
}
}
</script>
<style lang="less" scoped>
.reference-attr-select-wrap {
width: 100%;
.reference-attr-select {
width: 100%;
/deep/ .ant-select-dropdown {
z-index: 15;
}
}
}
</style>

View File

@ -8,6 +8,7 @@
resizable resizable
ref="xTable" ref="xTable"
size="small" size="small"
:data="data"
:loading="loading" :loading="loading"
:row-config="{ useKey: true, keyField: '_id' }" :row-config="{ useKey: true, keyField: '_id' }"
show-header-overflow show-header-overflow
@ -56,8 +57,20 @@
<span>{{ col.title }}</span> <span>{{ col.title }}</span>
</span> </span>
</template> </template>
<template v-if="col.is_choice || col.is_password" #edit="{ row }"> <template v-if="col.is_choice || col.is_password || col.is_bool || col.is_reference" #edit="{ row }">
<vxe-input v-if="col.is_password" v-model="passwordValue[col.field]" /> <CIReferenceAttr
v-if="col.is_reference"
:referenceTypeId="col.reference_type_id"
:isList="col.is_list"
:referenceShowAttrName="referenceShowAttrNameMap[col.reference_type_id] || ''"
:initSelectOption="getInitReferenceSelectOption(row[col.field], col)"
v-model="row[col.field]"
/>
<a-switch
v-else-if="col.is_bool"
v-model="row[col.field]"
/>
<vxe-input v-else-if="col.is_password" v-model="passwordValue[col.field]" />
<a-select <a-select
v-if="col.is_choice" v-if="col.is_choice"
v-model="row[col.field]" v-model="row[col.field]"
@ -100,10 +113,20 @@
</a-select> </a-select>
</template> </template>
<template <template
v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice" v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice || col.is_reference"
#default="{ row }" #default="{ row }"
> >
<span v-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span> <template v-if="col.is_reference" >
<a
v-for="(ciId) in (col.is_list ? row[col.field] : [row[col.field]])"
:key="ciId"
:href="`/cmdb/cidetail/${col.reference_type_id}/${ciId}`"
target="_blank"
>
{{ getReferenceAttrValue(ciId, col) }}
</a>
</template>
<span v-else-if="col.value_type === '6' && row[col.field]">{{ row[col.field] }}</span>
<template v-else-if="col.is_link && row[col.field]"> <template v-else-if="col.is_link && row[col.field]">
<a <a
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])" v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
@ -187,16 +210,21 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import { getCITypes } from '@/modules/cmdb/api/CIType'
import { searchCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../JsonEditor/jsonEditor.vue' import JsonEditor from '../JsonEditor/jsonEditor.vue'
import PasswordField from '../passwordField/index.vue' import PasswordField from '../passwordField/index.vue'
import { ops_move_icon as OpsMoveIcon } from '@/core/icons' import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
export default { export default {
name: 'CITable', name: 'CITable',
components: { components: {
JsonEditor, JsonEditor,
PasswordField, PasswordField,
OpsMoveIcon OpsMoveIcon,
CIReferenceAttr
}, },
props: { props: {
// table ID // table ID
@ -237,6 +265,18 @@ export default {
showDelete: { showDelete: {
type: Boolean, type: Boolean,
default: true default: true
},
// 表格数据
data: {
type: Array,
default: () => []
}
},
data() {
return {
referenceShowAttrNameMap: {},
referenceCIIdMap: {},
} }
}, },
@ -245,6 +285,46 @@ export default {
const idx = this.columns.findIndex((item) => item.is_fixed) const idx = this.columns.findIndex((item) => item.is_fixed)
return idx > -1 return idx > -1
}, },
tableDataWatch() {
return {
data: this.data,
columns: this.columns
}
},
referenceCIIdWatch() {
const referenceTypeCol = this.columns?.filter((col) => col?.is_reference && col?.reference_type_id) || []
if (!this.data?.length || !referenceTypeCol?.length) {
return []
}
const ids = []
this.data.forEach((row) => {
referenceTypeCol.forEach((col) => {
if (row[col.field]) {
ids.push(...(Array.isArray(row[col.field]) ? row[col.field] : [row[col.field]]))
}
})
})
return _.uniq(ids)
}
},
watch: {
columns: {
immediate: true,
deep: true,
handler(newVal) {
this.handleReferenceShowAttrName(newVal)
}
},
referenceCIIdWatch: {
immediate: true,
deep: true,
handler() {
this.handleReferenceCIIdMap()
}
}
}, },
methods: { methods: {
@ -330,6 +410,101 @@ export default {
getRowSeq(row) { getRowSeq(row) {
return this.getVxetableRef().getRowSeq(row) return this.getVxetableRef().getRowSeq(row)
},
async handleReferenceShowAttrName(columns) {
const needRequiredCITypeIds = columns?.filter((col) => col?.is_reference && col?.reference_type_id).map((col) => col.reference_type_id) || []
if (!needRequiredCITypeIds.length) {
this.referenceShowAttrNameMap = {}
return
}
const res = await getCITypes({
type_ids: needRequiredCITypeIds.join(',')
})
const map = {}
res.ci_types.forEach((ciType) => {
map[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
})
this.referenceShowAttrNameMap = map
},
async handleReferenceCIIdMap() {
const referenceTypeCol = this.columns.filter((col) => col?.is_reference && col?.reference_type_id) || []
if (!this.data?.length || !referenceTypeCol?.length) {
this.referenceCIIdMap = {}
return
}
const map = {}
this.data.forEach((row) => {
referenceTypeCol.forEach((col) => {
const ids = Array.isArray(row[col.field]) ? row[col.field] : row[col.field] ? [row[col.field]] : []
if (ids.length) {
if (!map?.[col.reference_type_id]) {
map[col.reference_type_id] = {}
}
ids.forEach((id) => {
map[col.reference_type_id][id] = {}
})
}
})
})
if (!Object.keys(map).length) {
this.referenceCIIdMap = {}
return
}
const allRes = await Promise.all(
Object.keys(map).map((key) => {
return searchCI({
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
count: 9999
})
})
)
allRes.forEach((res) => {
res.result.forEach((item) => {
if (map?.[item._type]?.[item._id]) {
map[item._type][item._id] = item
}
})
})
this.referenceCIIdMap = map
},
getReferenceAttrValue(id, col) {
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
if (!ci) {
return id
}
const attrName = this.referenceShowAttrNameMap?.[col.reference_type_id]
return ci?.[attrName] || id
},
getInitReferenceSelectOption(value, col) {
const ids = Array.isArray(value) ? value : value ? [value] : []
if (!ids.length) {
return []
}
const map = this?.referenceCIIdMap?.[col?.reference_type_id]
const attrName = this.referenceShowAttrNameMap?.[col?.reference_type_id]
const option = (Array.isArray(value) ? value : [value]).map((id) => {
return {
key: id,
title: map?.[id]?.[attrName] || id
}
})
return option
} }
} }
} }

View File

@ -113,6 +113,11 @@ export default {
this.editor.insertNode(node) this.editor.insertNode(node)
} }
}, },
destroy() {
const editor = this.editor
if (editor == null) return
editor.destroy()
}
}, },
} }
</script> </script>

View File

@ -189,6 +189,14 @@ const cmdb_en = {
confirmDeleteTrigger: 'Are you sure to delete this trigger?', confirmDeleteTrigger: 'Are you sure to delete this trigger?',
int: 'Integer', int: 'Integer',
float: 'Float', float: 'Float',
longText: 'Long Text',
shortText: 'Short Text',
shortTextTip: 'Text length <= 128',
referenceModel: 'Reference Model',
referenceModelTip: 'Please select reference model',
referenceModelTip1: 'For quick view of referenced model instances',
bool: 'Bool',
reference: 'Reference',
text: 'Text', text: 'Text',
datetime: 'DateTime', datetime: 'DateTime',
date: 'Date', date: 'Date',
@ -206,7 +214,7 @@ const cmdb_en = {
otherGroupTips: 'Non sortable within the other group', otherGroupTips: 'Non sortable within the other group',
filterTips: 'click to show {name}', filterTips: 'click to show {name}',
attributeAssociation: 'Attribute Association', attributeAssociation: 'Attribute Association',
attributeAssociationTip1: 'Automatically establish relationships through the attributes except password, json and multiple of two models', attributeAssociationTip1: 'Automatically establish relationships through attribute values (except password, json, multi-value, long text, boolean, reference) of two models',
attributeAssociationTip2: 'Double click to edit', attributeAssociationTip2: 'Double click to edit',
attributeAssociationTip3: 'Two Attributes must be selected', attributeAssociationTip3: 'Two Attributes must be selected',
attributeAssociationTip4: 'Please select a attribute from Source CIType', attributeAssociationTip4: 'Please select a attribute from Source CIType',
@ -282,6 +290,9 @@ const cmdb_en = {
rule: 'Rule', rule: 'Rule',
cascadeAttr: 'Cascade', cascadeAttr: 'Cascade',
cascadeAttrTip: 'Cascading attributes note the order', cascadeAttrTip: 'Cascading attributes note the order',
enumValue: 'Value',
label: 'Label',
valueInputTip: 'Please input value'
}, },
components: { components: {
unselectAttributes: 'Unselected', unselectAttributes: 'Unselected',
@ -323,7 +334,7 @@ const cmdb_en = {
pleaseSearch: 'Please search', pleaseSearch: 'Please search',
conditionFilter: 'Conditional filtering', conditionFilter: 'Conditional filtering',
attributeDesc: 'Attribute Description', attributeDesc: 'Attribute Description',
ciSearchTips: '1. JSON/password/link attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering', ciSearchTips: '1. JSON/password/link/longText/reference attributes cannot be searched\n2. If the search content includes commas, they need to be escaped\n3. Only index attributes are searched, non-index attributes use conditional filtering',
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*', ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
subCIType: 'Subscription CIType', subCIType: 'Subscription CIType',
already: 'already', already: 'already',
@ -548,7 +559,7 @@ class AutoDiscovery(object):
""" """
Define attribute fields Define attribute fields
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English. :return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
type: String Integer Float Date DateTime Time JSON type: String Integer Float Date DateTime Time JSON Bool Reference
For example: For example:
return [ return [
("ci_type", "String", "CIType name"), ("ci_type", "String", "CIType name"),

View File

@ -189,6 +189,14 @@ const cmdb_zh = {
confirmDeleteTrigger: '确认删除该触发器吗?', confirmDeleteTrigger: '确认删除该触发器吗?',
int: '整数', int: '整数',
float: '浮点数', float: '浮点数',
longText: '长文本',
shortText: '短文本',
shortTextTip: '文本长度 <= 128',
referenceModel: '引用模型',
referenceModelTip: '请选择引用模型',
referenceModelTip1: '用于快捷查看引用模型实例',
bool: '布尔',
reference: '引用',
text: '文本', text: '文本',
datetime: '日期时间', datetime: '日期时间',
date: '日期', date: '日期',
@ -206,7 +214,7 @@ const cmdb_zh = {
otherGroupTips: '其他分组属性不可排序', otherGroupTips: '其他分组属性不可排序',
filterTips: '点击可仅查看{name}属性', filterTips: '点击可仅查看{name}属性',
attributeAssociation: '属性关联', attributeAssociation: '属性关联',
attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值)来自动建立关系', attributeAssociationTip1: '通过2个模型的属性值(除密码、json、多值、长文本、布尔、引用)来自动建立关系',
attributeAssociationTip2: '双击可编辑', attributeAssociationTip2: '双击可编辑',
attributeAssociationTip3: '属性关联必须选择两个属性', attributeAssociationTip3: '属性关联必须选择两个属性',
attributeAssociationTip4: '请选择原模型属性', attributeAssociationTip4: '请选择原模型属性',
@ -282,6 +290,9 @@ const cmdb_zh = {
rule: '规则', rule: '规则',
cascadeAttr: '级联', cascadeAttr: '级联',
cascadeAttrTip: '级联属性注意顺序', cascadeAttrTip: '级联属性注意顺序',
enumValue: '枚举值',
label: '标签',
valueInputTip: '请输入枚举值'
}, },
components: { components: {
unselectAttributes: '未选属性', unselectAttributes: '未选属性',
@ -323,7 +334,7 @@ const cmdb_zh = {
pleaseSearch: '请查找', pleaseSearch: '请查找',
conditionFilter: '条件过滤', conditionFilter: '条件过滤',
attributeDesc: '属性说明', attributeDesc: '属性说明',
ciSearchTips: '1. json、密码、链接属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤', ciSearchTips: '1. json、密码、链接、长文本、引用属性不能搜索\n2. 搜索内容包括逗号, 则需转义\n3. 只搜索索引属性, 非索引属性使用条件过滤',
ciSearchTips2: '例: q=hostname:*0.0.0.0*', ciSearchTips2: '例: q=hostname:*0.0.0.0*',
subCIType: '订阅模型', subCIType: '订阅模型',
already: '已', already: '已',
@ -547,7 +558,7 @@ class AutoDiscovery(object):
""" """
Define attribute fields Define attribute fields
:return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English. :return: Returns a list of attribute fields. The list items are (name, type, description). The name must be in English.
type: String Integer Float Date DateTime Time JSON type: String Integer Float Date DateTime Time JSON Bool Reference
For example: For example:
return [ return [
("ci_type", "String", "CIType name"), ("ci_type", "String", "CIType name"),

View File

@ -4,13 +4,16 @@ export const valueTypeMap = () => {
return { return {
'0': i18n.t('cmdb.ciType.int'), '0': i18n.t('cmdb.ciType.int'),
'1': i18n.t('cmdb.ciType.float'), '1': i18n.t('cmdb.ciType.float'),
'2': i18n.t('cmdb.ciType.text'), '2': i18n.t('cmdb.ciType.shortText'),
'3': i18n.t('cmdb.ciType.datetime'), '3': i18n.t('cmdb.ciType.datetime'),
'4': i18n.t('cmdb.ciType.date'), '4': i18n.t('cmdb.ciType.date'),
'5': i18n.t('cmdb.ciType.time'), '5': i18n.t('cmdb.ciType.time'),
'6': 'JSON', '6': 'JSON',
'7': i18n.t('cmdb.ciType.password'), '7': i18n.t('cmdb.ciType.password'),
'8': i18n.t('cmdb.ciType.link') '8': i18n.t('cmdb.ciType.link'),
'9': i18n.t('cmdb.ciType.longText'),
'10': i18n.t('cmdb.ciType.bool'),
'11': i18n.t('cmdb.ciType.reference'),
} }
} }

View File

@ -1,219 +1,269 @@
/* eslint-disable */ /* eslint-disable */
import _ from 'lodash' import _ from 'lodash'
import XLSX from 'xlsx' import XLSX from 'xlsx'
import XLSXS from 'xlsx-js-style' import XLSXS from 'xlsx-js-style'
export function sum(arr) { export function sum(arr) {
if (!arr.length) { if (!arr.length) {
return 0 return 0
} }
return arr.reduce(function (prev, curr, idx, arr) { return arr.reduce(function (prev, curr, idx, arr) {
return prev + curr return prev + curr
}) })
} }
const strLength = (fData) => { const strLength = (fData) => {
if (!fData) { if (!fData) {
return 0 return 0
} }
if (fData.length && typeof fData === 'object') { if (fData.length && typeof fData === 'object') {
fData = fData.join(' ') fData = fData.join(' ')
} }
let intLength = 0 let intLength = 0
for (let i = 0; i < fData.length; i++) { for (let i = 0; i < fData.length; i++) {
if ((fData.charCodeAt(i) < 0) || (fData.charCodeAt(i) > 255)) { if ((fData.charCodeAt(i) < 0) || (fData.charCodeAt(i) > 255)) {
intLength = intLength + 2 intLength = intLength + 2
} }
else { else {
intLength = intLength + 1 intLength = intLength + 1
} }
} }
return Math.floor(intLength * 7) return Math.floor(intLength * 7)
} }
String.prototype.pxWidth = function (font) { String.prototype.pxWidth = function (font) {
// re-use canvas object for better performance // re-use canvas object for better performance
const canvas = String.prototype.pxWidth.canvas || (String.prototype.pxWidth.canvas = document.createElement("canvas")), const canvas = String.prototype.pxWidth.canvas || (String.prototype.pxWidth.canvas = document.createElement("canvas")),
context = canvas.getContext("2d"); context = canvas.getContext("2d");
font && (context.font = font); font && (context.font = font);
const metrics = context.measureText(this); const metrics = context.measureText(this);
return metrics.width; return metrics.width;
} }
export function getCITableColumns(data, attrList, width = 1600, height) { export function getCITableColumns(data, attrList, width = 1600, height) {
// 计算出来 主table的列表 布局属性 // 计算出来 主table的列表 布局属性
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc']) const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
const columns = [] const columns = []
for (let attr of _attrList) { for (let attr of _attrList) {
const editRender = { name: 'input' } const editRender = { name: 'input' }
switch (attr.value_type) { switch (attr.value_type) {
case '0': case '0':
editRender['props'] = { 'type': 'float' } editRender['props'] = { 'type': 'float' }
break break
case '1': case '1':
editRender['props'] = { 'type': 'float' } editRender['props'] = { 'type': 'float' }
break break
case '2': case '2':
editRender['attrs'] = { 'type': 'text' } editRender['attrs'] = { 'type': 'text' }
break break
case '3': case '3':
editRender['props'] = { 'type': 'datetime' } editRender['props'] = { 'type': 'datetime' }
break break
case "4": case "4":
editRender['props'] = { 'type': 'date' } editRender['props'] = { 'type': 'date' }
break break
case '5': case '5':
editRender['props'] = { 'type': 'time' } editRender['props'] = { 'type': 'time' }
break break
case '6': case '6':
editRender['props'] = { 'type': 'text' } editRender['props'] = { 'type': 'text' }
break break
default: default:
editRender['props'] = { 'type': 'text' } editRender['props'] = { 'type': 'text' }
break break
} }
if (attr.is_choice) { if (attr.is_choice) {
editRender.name = '$select' editRender.name = '$select'
editRender.options = attr.choice_value ? attr.choice_value.map(item => { return { label: item, value: item } }) : [] editRender.options = attr.choice_value ? attr.choice_value.map(item => { return { label: item, value: item } }) : []
delete editRender.props delete editRender.props
} }
columns.push({ columns.push({
attr_id: attr.id, attr_id: attr.id,
editRender, editRender,
title: attr.alias || attr.name, title: attr.alias || attr.name,
field: attr.name, field: attr.name,
value_type: attr.value_type, value_type: attr.value_type,
sortable: !!attr.is_sortable, sortable: !!attr.is_sortable,
filters: attr.is_choice ? attr.choice_value : null, filters: attr.is_choice ? attr.choice_value : null,
width: Math.min(Math.max(100, ...data.map(item => strLength(item[attr.name]))), 350), width: Math.min(Math.max(100, ...data.map(item => strLength(item[attr.name]))), 350),
is_link: attr.is_link, is_link: attr.is_link,
is_password: attr.is_password, is_password: attr.is_password,
is_list: attr.is_list, is_list: attr.is_list,
is_choice: attr.is_choice, is_choice: attr.is_choice,
is_fixed: attr.is_fixed, is_fixed: attr.is_fixed,
}) is_bool: attr.is_bool,
} is_reference: attr.is_reference,
reference_type_id: attr.reference_type_id
const totalWidth = sum(columns.map(col => col.width)) })
if (totalWidth < width) { }
columns.map(item => {
// if (item.width === 100) { const totalWidth = sum(columns.map(col => col.width))
delete item.width if (totalWidth < width) {
// } columns.map(item => {
}) // if (item.width === 100) {
} delete item.width
return columns // }
} })
}
export const getPropertyStyle = (attr) => { return columns
switch (attr.value_type) { }
case '0':
return { color: '#cf1322', backgroundColor: '#fff1f0' } export const getPropertyStyle = (attr) => {
case '1': switch (attr.value_type) {
return { color: '#d4b106', backgroundColor: '#feffe6' } case '0':
case '2': return { color: '#cf1322', backgroundColor: '#fff1f0' }
return { color: '#d46b08', backgroundColor: '#fff7e6' } case '1':
case '3': return { color: '#d4b106', backgroundColor: '#feffe6' }
return { color: '#531dab', backgroundColor: '#f9f0ff' } case '2':
case '4': return { color: '#d46b08', backgroundColor: '#fff7e6' }
return { color: '#389e0d', backgroundColor: '#f6ffed' } case '3':
case '5': return { color: '#531dab', backgroundColor: '#f9f0ff' }
return { color: '#08979c', backgroundColor: '#e6fffb' } case '4':
case '6': return { color: '#389e0d', backgroundColor: '#f6ffed' }
return { color: '#c41d7f', backgroundColor: '#fff0f6' } case '5':
case '7': return { color: '#08979c', backgroundColor: '#e6fffb' }
return { color: '#0390CC', backgroundColor: '#e6fffb' } case '6':
case '8': return { color: '#c41d7f', backgroundColor: '#fff0f6' }
return { color: '#144BD9', backgroundColor: '#fff0f6' } case '7':
} return { color: '#0390CC', backgroundColor: '#e6fffb' }
} case '8':
return { color: '#144BD9', backgroundColor: '#fff0f6' }
export const getPropertyIcon = (attr) => { }
switch (attr.value_type) { }
case '0':
return 'duose-shishu' export const getPropertyIcon = (attr) => {
case '1': switch (attr.value_type) {
return 'duose-fudianshu' case '0':
case '2': if (attr.is_reference) {
if (attr.is_password) { return 'duose-quote'
return 'duose-password' }
}
if (attr.is_link) { return 'duose-shishu'
return 'duose-link' case '1':
} return 'duose-fudianshu'
return 'duose-wenben' case '2':
case '3': if (attr.is_password) {
return 'duose-datetime' return 'duose-password'
case '4': }
return 'duose-date' if (attr.is_link) {
case '5': return 'duose-link'
return 'duose-time' }
case '6': if (attr.is_index === false) {
return 'duose-json' return 'duose-changwenben1'
case '7': }
return 'duose-password' return 'duose-wenben'
case '8': case '3':
return 'duose-link' return 'duose-datetime'
} case '4':
} return 'duose-date'
case '5':
export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => { return 'duose-time'
const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc']) case '6':
if (!_tempData.length) { return 'duose-json'
return { xLast: 0, yLast: 0, wLast: 0 } case '7':
} if (attr.is_bool) {
const { x, y, w } = _tempData[_tempData.length - 1] return 'duose-boole'
if (y < y1) { }
return { xLast: x1, yLast: y1, wLast: w1 } return 'duose-password'
} else if (y > y1) { case '8':
return { xLast: x, yLast: y, wLast: w } return 'duose-link'
} else { case '9':
const xLast = _.max([x, x1]) return 'duose-changwenben1'
return { xLast, yLast: y, wLast: xLast === x ? w : w1 } case '10':
} return 'duose-boole'
} case '11':
return 'duose-quote'
// 数字加逗号 default:
export const toThousands = (num = 0) => { return ''
return num.toString().replace(/\d+/, function (n) { }
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') }
})
} export const getPropertyType = (attr) => {
if (attr.is_password) {
export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH:mm:ss')}.xls`) => { return '7'
// STEP 1: Create a new workbook }
const wb = XLSXS.utils.book_new() if (attr.is_link) {
// STEP 2: Create data rows and styles return '8'
const rowArray = data }
// STEP 3: Create worksheet with rows; Add worksheet to workbook
const ws = XLSXS.utils.aoa_to_sheet(rowArray) switch (attr.value_type) {
XLSXS.utils.book_append_sheet(wb, ws, fileName) case '0':
if (attr.is_reference) {
let maxColumnNumber = 1 // 默认最大列数 return '11'
rowArray.forEach(item => { if (item.length > maxColumnNumber) { maxColumnNumber = item.length } }) }
return '0'
// 添加列宽 case '2':
ws['!cols'] = (rowArray[0].map(item => { if (!attr.is_index) {
return { width: 22 } return '9'
})) }
// // 添加行高 return '2'
// ws['!rows'] = [{ 'hpt': 80 }] case '7':
// STEP 4: Write Excel file to browser #导出 if (attr.is_bool) {
XLSXS.writeFile(wb, fileName + '.xlsx') return '10'
} }
return '7'
export const getAllParentNodesLabel = (node, label) => { default:
if (node.parentNode) { return attr?.value_type ?? ''
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`) }
} }
return label
} export const getLastLayout = (data, x1 = 0, y1 = 0, w1 = 0) => {
export const getTreeSelectLabel = (node) => { const _tempData = _.orderBy(data, ['y', 'x'], ['asc', 'asc'])
return `${getAllParentNodesLabel(node, node.label)}` if (!_tempData.length) {
return { xLast: 0, yLast: 0, wLast: 0 }
}
const { x, y, w } = _tempData[_tempData.length - 1]
if (y < y1) {
return { xLast: x1, yLast: y1, wLast: w1 }
} else if (y > y1) {
return { xLast: x, yLast: y, wLast: w }
} else {
const xLast = _.max([x, x1])
return { xLast, yLast: y, wLast: xLast === x ? w : w1 }
}
}
// 数字加逗号
export const toThousands = (num = 0) => {
return num.toString().replace(/\d+/, function (n) {
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
})
}
export const downloadExcel = (data, fileName = `${moment().format('YYYY-MM-DD HH:mm:ss')}.xls`) => {
// STEP 1: Create a new workbook
const wb = XLSXS.utils.book_new()
// STEP 2: Create data rows and styles
const rowArray = data
// STEP 3: Create worksheet with rows; Add worksheet to workbook
const ws = XLSXS.utils.aoa_to_sheet(rowArray)
XLSXS.utils.book_append_sheet(wb, ws, fileName)
let maxColumnNumber = 1 // 默认最大列数
rowArray.forEach(item => { if (item.length > maxColumnNumber) { maxColumnNumber = item.length } })
// 添加列宽
ws['!cols'] = (rowArray[0].map(item => {
return { width: 22 }
}))
// // 添加行高
// ws['!rows'] = [{ 'hpt': 80 }]
// STEP 4: Write Excel file to browser #导出
XLSXS.writeFile(wb, fileName + '.xlsx')
}
export const getAllParentNodesLabel = (node, label) => {
if (node.parentNode) {
return getAllParentNodesLabel(node.parentNode, `${node.parentNode.label}-${label}`)
}
return label
}
export const getTreeSelectLabel = (node) => {
return `${getAllParentNodesLabel(node, node.label)}`
} }

View File

@ -465,13 +465,12 @@ export default {
this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress') + '...' this.loadTip = this.$t('cmdb.ci.batchUpdateInProgress') + '...'
const payload = {} const payload = {}
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
if (values[key] || values[key] === 0) {
payload[key] = values[key]
}
// Field values support blanking // Field values support blanking
// There are currently field values that do not support blanking and will be returned by the backend. // There are currently field values that do not support blanking and will be returned by the backend.
if (values[key] === undefined || values[key] === null) { if (values[key] === undefined || values[key] === null) {
payload[key] = null payload[key] = null
} else {
payload[key] = values[key]
} }
}) })
this.$refs.create.visible = false this.$refs.create.visible = false

View File

@ -35,7 +35,7 @@
<a-select v-model="parentsForm[item.name].attr"> <a-select v-model="parentsForm[item.name].attr">
<a-select-option <a-select-option
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
v-for="attr in item.attributes" v-for="attr in filterAttributes(item.attributes)"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
> >
@ -87,11 +87,32 @@
</a-col> </a-col>
<a-col :span="showListOperation(list.name) ? 10 : 13"> <a-col :span="showListOperation(list.name) ? 10 : 13">
<a-form-item> <a-form-item>
<CIReferenceAttr
v-if="getAttr(list.name).is_reference"
:referenceTypeId="getAttr(list.name).reference_type_id"
:isList="getAttr(list.name).is_list"
v-decorator="[
list.name,
{
initialValue: getAttr(list.name).is_list ? [] : ''
}
]"
/>
<a-switch
v-else-if="getAttr(list.name).is_bool"
v-decorator="[
list.name,
{
valuePropName: 'checked',
initialValue: false
}
]"
/>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[list.name, { rules: getDecoratorRules(list) }]" v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
:placeholder="$t('placeholder2')" :placeholder="$t('placeholder2')"
v-if="getFieldType(list.name).split('%%')[0] === 'select'" v-else-if="getFieldType(list.name).split('%%')[0] === 'select'"
:mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'" :mode="getFieldType(list.name).split('%%')[1] === 'multiple' ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
@ -114,18 +135,18 @@
<a-input-number <a-input-number
v-decorator="[list.name, { rules: getDecoratorRules(list) }]" v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
style="width: 100%" style="width: 100%"
v-if="getFieldType(list.name) === 'input_number'" v-else-if="getFieldType(list.name) === 'input_number'"
/> />
<a-date-picker <a-date-picker
v-decorator="[list.name, { rules: getDecoratorRules(list) }]" v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
style="width: 100%" style="width: 100%"
:format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :valueFormat="getFieldType(list.name) == '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'" v-else-if="getFieldType(list.name) === '4' || getFieldType(list.name) === '3'"
:showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }" :showTime="getFieldType(list.name) === '4' ? false : { format: 'HH:mm:ss' }"
/> />
<a-input <a-input
v-if="getFieldType(list.name) === 'input'" v-else-if="getFieldType(list.name) === 'input'"
@focus="(e) => handleFocusInput(e, list)" @focus="(e) => handleFocusInput(e, list)"
v-decorator="[list.name, { rules: getDecoratorRules(list) }]" v-decorator="[list.name, { rules: getDecoratorRules(list) }]"
/> />
@ -156,6 +177,7 @@ import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import { valueTypeMap } from '../../../utils/const' import { valueTypeMap } from '../../../utils/const'
import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue' import CreateInstanceFormByGroup from './createInstanceFormByGroup.vue'
import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation' import { getCITypeParent, getCanEditByParentIdChildId } from '@/modules/cmdb/api/CITypeRelation'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
export default { export default {
name: 'CreateInstanceForm', name: 'CreateInstanceForm',
@ -164,6 +186,7 @@ export default {
ElOption: Option, ElOption: Option,
JsonEditor, JsonEditor,
CreateInstanceFormByGroup, CreateInstanceFormByGroup,
CIReferenceAttr
}, },
props: { props: {
typeIdFromRelation: { typeIdFromRelation: {
@ -261,6 +284,11 @@ export default {
} }
Object.keys(values).forEach((k) => { Object.keys(values).forEach((k) => {
const _tempFind = this.attributeList.find((item) => item.name === k) const _tempFind = this.attributeList.find((item) => item.name === k)
if (_tempFind.is_reference) {
values[k] = values[k] ? values[k] : null
}
if ( if (
_tempFind.value_type === '3' && _tempFind.value_type === '3' &&
values[k] && values[k] &&
@ -309,6 +337,11 @@ export default {
Object.keys(values).forEach((k) => { Object.keys(values).forEach((k) => {
const _tempFind = this.attributeList.find((item) => item.name === k) const _tempFind = this.attributeList.find((item) => item.name === k)
if (_tempFind.is_reference) {
values[k] = values[k] ? values[k] : null
}
if ( if (
_tempFind.value_type === '3' && _tempFind.value_type === '3' &&
values[k] && values[k] &&
@ -426,6 +459,9 @@ export default {
} }
return 'input' return 'input'
}, },
getAttr(name) {
return this.attributeList.find((item) => item.name === name) ?? {}
},
getSelectFieldOptions(name) { getSelectFieldOptions(name) {
const _find = this.attributeList.find((item) => item.name === name) const _find = this.attributeList.find((item) => item.name === name)
if (_find) { if (_find) {
@ -487,7 +523,12 @@ export default {
} }
return rules return rules
} },
filterAttributes(attributes) {
return attributes.filter((attr) => {
return !attr.is_bool && !attr.is_reference
})
},
}, },
} }
</script> </script>
@ -498,7 +539,7 @@ export default {
} }
.ant-drawer-body { .ant-drawer-body {
overflow-y: auto; overflow-y: auto;
max-height: calc(100vh - 110px); height: calc(100vh - 110px);
} }
} }
</style> </style>

View File

@ -79,6 +79,8 @@
import XEUtils from 'xe-utils' import XEUtils from 'xe-utils'
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr' import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
import { valueTypeMap } from '@/modules/cmdb/utils/const' import { valueTypeMap } from '@/modules/cmdb/utils/const'
import { getPropertyType } from '@/modules/cmdb/utils/helper'
export default { export default {
name: 'MetadataDrawer', name: 'MetadataDrawer',
data() { data() {
@ -187,12 +189,7 @@ export default {
this.loading = true this.loading = true
const { attributes = [] } = await getCITypeAttributesByName(this.typeId) const { attributes = [] } = await getCITypeAttributesByName(this.typeId)
this.tableData = attributes.map((attr) => { this.tableData = attributes.map((attr) => {
if (attr.is_password) { attr.value_type = getPropertyType(attr)
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
return attr return attr
}) })
this.loading = false this.loading = false

View File

@ -1,9 +1,19 @@
<template> <template>
<span :id="`ci-detail-attr-${attr.name}`"> <span :id="`ci-detail-attr-${attr.name}`">
<span v-if="!isEdit || attr.value_type === '6'"> <span v-if="!isEdit || attr.value_type === '6'">
<template v-if="attr.is_reference" >
<a
v-for="(ciId) in (attr.is_list ? ci[attr.name] : [ci[attr.name]])"
:key="ciId"
:href="`/cmdb/cidetail/${attr.reference_type_id}/${ciId}`"
target="_blank"
>
{{ attr.referenceShowAttrNameMap ? attr.referenceShowAttrNameMap[ciId] || ciId : ciId }}
</a>
</template>
<PasswordField <PasswordField
:style="{ display: 'inline-block' }" :style="{ display: 'inline-block' }"
v-if="attr.is_password && ci[attr.name]" v-else-if="attr.is_password && ci[attr.name]"
:ci_id="ci._id" :ci_id="ci._id"
:attr_id="attr.id" :attr_id="attr.id"
></PasswordField> ></PasswordField>
@ -67,6 +77,29 @@
<template v-else> <template v-else>
<a-form :form="form"> <a-form :form="form">
<a-form-item label="" :colon="false"> <a-form-item label="" :colon="false">
<CIReferenceAttr
v-if="attr.is_reference"
:referenceTypeId="attr.reference_type_id"
:isList="attr.is_list"
:referenceShowAttrName="attr.showAttrName"
:initSelectOption="getInitReferenceSelectOption(attr)"
v-decorator="[
attr.name,
{
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
}
]"
/>
<a-switch
v-else-if="attr.is_bool"
v-decorator="[
attr.name,
{
rules: [{ required: attr.is_required }],
valuePropName: 'checked',
}
]"
/>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
@ -76,7 +109,7 @@
}, },
]" ]"
:placeholder="$t('placeholder2')" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-else-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
@ -157,10 +190,11 @@ import { updateCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import PasswordField from '../../../components/passwordField/index.vue' import PasswordField from '../../../components/passwordField/index.vue'
import { getAttrPassword } from '../../../api/CITypeAttr' import { getAttrPassword } from '../../../api/CITypeAttr'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
export default { export default {
name: 'CiDetailAttrContent', name: 'CiDetailAttrContent',
components: { JsonEditor, PasswordField }, components: { JsonEditor, PasswordField, CIReferenceAttr },
props: { props: {
ci: { ci: {
type: Object, type: Object,
@ -209,7 +243,7 @@ export default {
} }
this.isEdit = true this.isEdit = true
this.$nextTick(async () => { this.$nextTick(async () => {
if (this.attr.is_list && !this.attr.is_choice) { if (this.attr.is_list && !this.attr.is_choice && !this.attr.is_reference) {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name]) [`${this.attr.name}`]: Array.isArray(this.ci[this.attr.name])
? this.ci[this.attr.name].join(',') ? this.ci[this.attr.name].join(',')
@ -237,6 +271,10 @@ export default {
.then(() => { .then(() => {
this.$message.success(this.$t('updateSuccess')) this.$message.success(this.$t('updateSuccess'))
this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name) this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name)
if (this.attr.is_reference) {
this.$emit('refreshReferenceAttr')
}
}) })
.catch(() => { .catch(() => {
this.$emit('refresh', this.attr.name) this.$emit('refresh', this.attr.name)
@ -283,6 +321,16 @@ export default {
getName(name) { getName(name) {
return name ?? '' return name ?? ''
}, },
getInitReferenceSelectOption(attr) {
const option = Object.keys(attr?.referenceShowAttrNameMap || {}).map((key) => {
return {
key: Number(key),
title: attr?.referenceShowAttrNameMap?.[key] ?? ''
}
})
return option
}
}, },
} }
</script> </script>

View File

@ -38,6 +38,16 @@
resizable resizable
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #reference_default="{ row, column }">
<a
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
:key="id"
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
target="_blank"
>
{{ id }}
</a>
</template>
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm <a-popconfirm
arrowPointAtCenter arrowPointAtCenter
@ -85,6 +95,16 @@
resizable resizable
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #reference_default="{ row, column }">
<a
v-for="(id) in (column.params.attr.is_list ? row[column.field] : [row[column.field]])"
:key="id"
:href="`/cmdb/cidetail/${column.params.attr.reference_type_id}/${id}`"
target="_blank"
>
{{ id }}
</a>
</template>
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm <a-popconfirm
arrowPointAtCenter arrowPointAtCenter
@ -258,7 +278,22 @@ export default {
const columns = [] const columns = []
const jsonAttr = [] const jsonAttr = []
item.attributes.forEach((attr) => { item.attributes.forEach((attr) => {
columns.push({ key: 'p_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' }) const column = {
key: 'p_' + attr.id,
field: attr.name,
title: attr.alias,
minWidth: '100px',
params: {
attr
},
}
if (attr.is_reference) {
column.slots = {
default: 'reference_default'
}
}
columns.push(column)
if (attr.value_type === '6') { if (attr.value_type === '6') {
jsonAttr.push(attr.name) jsonAttr.push(attr.name)
} }
@ -299,7 +334,22 @@ export default {
const columns = [] const columns = []
const jsonAttr = [] const jsonAttr = []
item.attributes.forEach((attr) => { item.attributes.forEach((attr) => {
columns.push({ key: 'c_' + attr.id, field: attr.name, title: attr.alias, minWidth: '100px' }) const column = {
key: 'c_' + attr.id,
field: attr.name,
title: attr.alias,
minWidth: '100px',
params: {
attr
},
}
if (attr.is_reference) {
column.slots = {
default: 'reference_default'
}
}
columns.push(column)
if (attr.value_type === '6') { if (attr.value_type === '6') {
jsonAttr.push(attr.name) jsonAttr.push(attr.name)
} }

View File

@ -20,7 +20,7 @@
:key="attr.name" :key="attr.name"
v-for="attr in group.attributes" v-for="attr in group.attributes"
> >
<ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" /> <ci-detail-attr-content :ci="ci" :attr="attr" @refresh="refresh" @updateCIByself="updateCIByself" @refreshReferenceAttr="handleReferenceAttr" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
@ -137,7 +137,7 @@ import _ from 'lodash'
import { Descriptions, DescriptionsItem } from 'element-ui' import { Descriptions, DescriptionsItem } from 'element-ui'
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType' import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history' import { getCIHistory, judgeItsmInstalled } from '@/modules/cmdb/api/history'
import { getCIById } from '@/modules/cmdb/api/ci' import { getCIById, searchCI } from '@/modules/cmdb/api/ci'
import CiDetailAttrContent from './ciDetailAttrContent.vue' import CiDetailAttrContent from './ciDetailAttrContent.vue'
import CiDetailRelation from './ciDetailRelation.vue' import CiDetailRelation from './ciDetailRelation.vue'
import TriggerTable from '../../operation_history/modules/triggerTable.vue' import TriggerTable from '../../operation_history/modules/triggerTable.vue'
@ -244,9 +244,78 @@ export default {
getCITypeGroupById(this.typeId, { need_other: 1 }) getCITypeGroupById(this.typeId, { need_other: 1 })
.then((res) => { .then((res) => {
this.attributeGroups = res this.attributeGroups = res
this.handleReferenceAttr()
}) })
.catch((e) => {}) .catch((e) => {})
}, },
async handleReferenceAttr() {
const map = {}
this.attributeGroups.forEach((group) => {
group.attributes.forEach((attr) => {
if (attr?.is_reference && attr?.reference_type_id && this.ci[attr.name]) {
const ids = Array.isArray(this.ci[attr.name]) ? this.ci[attr.name] : this.ci[attr.name] ? [this.ci[attr.name]] : []
if (ids.length) {
if (!map?.[attr.reference_type_id]) {
map[attr.reference_type_id] = {}
}
ids.forEach((id) => {
map[attr.reference_type_id][id] = {}
})
}
}
})
})
if (!Object.keys(map).length) {
return
}
const ciTypesRes = await getCITypes({
type_ids: Object.keys(map).join(',')
})
const showAttrNameMap = {}
ciTypesRes.ci_types.forEach((ciType) => {
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
})
const allRes = await Promise.all(
Object.keys(map).map((key) => {
return searchCI({
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
count: 9999
})
})
)
const ciNameMap = {}
allRes.forEach((res) => {
res.result.forEach((item) => {
ciNameMap[item._id] = item
})
})
const newAttrGroups = _.cloneDeep(this.attributeGroups)
newAttrGroups.forEach((group) => {
group.attributes.forEach((attr) => {
if (attr?.is_reference && attr?.reference_type_id) {
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
const referenceShowAttrNameMap = {}
const referenceCIIds = this.ci[attr.name];
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
})
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
}
})
})
this.$set(this, 'attributeGroups', newAttrGroups)
},
async getCI() { async getCI() {
await getCIById(this.ciId) await getCIById(this.ciId)
.then((res) => { .then((res) => {

View File

@ -16,6 +16,29 @@
:key="attr.name + attr_idx" :key="attr.name + attr_idx"
> >
<a-form-item :label="attr.alias || attr.name" :colon="false"> <a-form-item :label="attr.alias || attr.name" :colon="false">
<CIReferenceAttr
v-if="attr.is_reference"
:referenceTypeId="attr.reference_type_id"
:isList="attr.is_list"
v-decorator="[
attr.name,
{
rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.is_list ? [] : ''
}
]"
/>
<a-switch
v-else-if="attr.is_bool"
v-decorator="[
attr.name,
{
rules: [{ required: false }],
valuePropName: 'checked',
initialValue: attr.default ? Boolean(attr.default.default) : false
}
]"
/>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
@ -33,7 +56,7 @@
}, },
]" ]"
:placeholder="$t('placeholder2')" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-else-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
@ -133,10 +156,14 @@
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
export default { export default {
name: 'CreateInstanceFormByGroup', name: 'CreateInstanceFormByGroup',
components: { JsonEditor }, components: {
JsonEditor,
CIReferenceAttr
},
props: { props: {
group: { group: {
type: Object, type: Object,
@ -146,6 +173,10 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
ciTypeId: {
type: [Number, String],
default: ''
}
}, },
inject: ['getFieldType'], inject: ['getFieldType'],
data() { data() {

View File

@ -39,6 +39,7 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import _ from 'lodash' import _ from 'lodash'
import { valueTypeMap } from '@/modules/cmdb/utils/const' import { valueTypeMap } from '@/modules/cmdb/utils/const'
import { getPropertyType } from '@/modules/cmdb/utils/helper'
export default { export default {
name: 'AllAttrDrawer', name: 'AllAttrDrawer',
@ -84,12 +85,7 @@ export default {
}) })
otherAttrData.forEach((attr) => { otherAttrData.forEach((attr) => {
if (attr.is_password) { attr.value_type = getPropertyType(attr)
attr.value_type = '7'
}
if (attr.is_link) {
attr.value_type = '8'
}
attr.groupId = -1 attr.groupId = -1
attr.groupName = this.$t('other') attr.groupName = this.$t('other')

View File

@ -21,9 +21,7 @@
<div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }"> <div :class="{ 'attribute-card-name': true, 'attribute-card-name-default-show': property.default_show }">
{{ property.alias || property.name }} {{ property.alias || property.name }}
</div> </div>
<div v-if="property.is_password" class="attribute-card_value-type">{{ $t('cmdb.ciType.password') }}</div> <div class="attribute-card_value-type">{{ valueTypeMap[getPropertyType(property)] }}</div>
<div v-else-if="property.is_link" class="attribute-card_value-type">{{ $t('cmdb.ciType.link') }}</div>
<div v-else class="attribute-card_value-type">{{ valueTypeMap[property.value_type] }}</div>
</div> </div>
<div <div
class="attribute-card-trigger" class="attribute-card-trigger"
@ -74,7 +72,9 @@
!isUnique && !isUnique &&
!['6'].includes(property.value_type) && !['6'].includes(property.value_type) &&
!property.is_password && !property.is_password &&
!property.is_list !property.is_list &&
!property.is_reference &&
!property.is_bool
" "
:title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')" :title="$t(isShowId ? 'cmdb.ciType.cancelSetAsShow' : 'cmdb.ciType.setAsShow')"
> >
@ -101,6 +101,8 @@ import ValueTypeIcon from '@/components/CMDBValueTypeMapIcon'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import TriggerForm from './triggerForm.vue' import TriggerForm from './triggerForm.vue'
import { updateCIType } from '@/modules/cmdb/api/CIType' import { updateCIType } from '@/modules/cmdb/api/CIType'
import { getPropertyType } from '../../utils/helper'
export default { export default {
name: 'AttributeCard', name: 'AttributeCard',
inject: { inject: {
@ -191,6 +193,7 @@ export default {
}, },
}, },
methods: { methods: {
getPropertyType,
handleEdit() { handleEdit() {
this.$emit('edit') this.$emit('edit')
}, },

View File

@ -0,0 +1,78 @@
<template>
<a-form-item
:label="$t('cmdb.ciType.referenceModel')"
:extra="$t('cmdb.ciType.referenceModelTip1')"
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
>
<a-select
allowClear
v-decorator="['reference_type_id', {
rules: [{ required: true, message: $t('cmdb.ciType.referenceModelTip') }],
initialValue: ''
}]"
showSearch
optionFilterProp="title"
@dropdownVisibleChange="handleDropdownVisibleChange"
>
<a-select-option
v-for="(item) in options"
:key="item.value"
:title="item.label"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
</template>
<script>
import { getCITypes } from '@/modules/cmdb/api/CIType'
export default {
name: 'ReferenceModelSelect',
props: {
form: {
type: Object,
required: true,
},
isLazyRequire: {
type: Boolean,
default: true
},
formItemLayout: {
type: Object,
default: () => {}
}
},
data() {
return {
isInit: false,
options: []
}
},
mounted() {
if (!this.isLazyRequire) {
this.getSelectOptions()
}
},
methods: {
handleDropdownVisibleChange(open) {
if (!this.isInit && open) {
this.getSelectOptions()
}
},
async getSelectOptions() {
this.isInit = true
const res = await getCITypes()
this.options = res.ci_types.map((ciType) => {
return {
value: ciType.id,
label: ciType?.alias || ciType?.name || ''
}
})
}
}
}
</script>

View File

@ -60,11 +60,17 @@
v-decorator="['value_type', { rules: [{ required: true }] }]" v-decorator="['value_type', { rules: [{ required: true }] }]"
@change="handleChangeValueType" @change="handleChangeValueType"
> >
<a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap">{{ value }}</a-select-option> <a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap">
<ops-icon :type="getPropertyIcon({ value_type: key })" />
<span class="value-type-text">{{ value }}</span>
</a-select-option>
</a-select> </a-select>
</a-form-item></a-col </a-form-item></a-col
> >
<a-col :span="currentValueType === '6' ? 24 : 12"> <a-col
v-if="currentValueType !== '11'"
:span="currentValueType === '6' ? 24 : 12"
>
<a-form-item <a-form-item
:label-col="{ span: currentValueType === '6' ? 4 : 8 }" :label-col="{ span: currentValueType === '6' ? 4 : 8 }"
:wrapper-col="{ span: currentValueType === '6' ? 18 : 12 }" :wrapper-col="{ span: currentValueType === '6' ? 18 : 12 }"
@ -77,6 +83,10 @@
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-input>
<a-switch
v-else-if="currentValueType === '10'"
v-decorator="['default_value', { rules: [{ required: false }], valuePropName: 'checked' }]"
/>
<a-select <a-select
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
mode="tags" mode="tags"
@ -95,12 +105,7 @@
</a-input-number> </a-input-number>
<a-input <a-input
style="width: 100%" style="width: 100%"
v-else-if=" v-else-if="['2', '5', '7', '8', '9'].includes(currentValueType)"
currentValueType === '2' ||
currentValueType === '5' ||
currentValueType === '7' ||
currentValueType === '8'
"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-input>
@ -157,7 +162,18 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'"> <a-col
v-if="currentValueType === '11'"
:span="12"
>
<ReferenceModelSelect
:form="form"
:isLazyRequire="false"
:formItemLayout="formItemLayout"
/>
</a-col>
<!-- <a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
<a-form-item <a-form-item
:hidden="currentValueType === '2' ? false : true" :hidden="currentValueType === '2' ? false : true"
:label-col="horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
@ -189,10 +205,10 @@
v-decorator="['is_index', { rules: [], valuePropName: 'checked' }]" v-decorator="['is_index', { rules: [], valuePropName: 'checked' }]"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col> -->
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')" :label="$t('cmdb.ciType.unique')"
> >
@ -206,7 +222,7 @@
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-form-item <a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('required')" :label="$t('required')"
> >
@ -219,7 +235,7 @@
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 12 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
> >
<template slot="label"> <template slot="label">
@ -228,8 +244,8 @@
>{{ $t('cmdb.ciType.defaultShow') }} >{{ $t('cmdb.ciType.defaultShow') }}
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')"> <a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
<a-icon <a-icon
style="position:absolute;top:2px;left:-17px;color:#2f54eb;" style="position:absolute;top:2px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -250,7 +266,7 @@
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? horizontalFormItemLayout.labelCol : { span: 8 }" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.isSortable')" :label="$t('cmdb.ciType.isSortable')"
> >
@ -263,7 +279,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="!['6', '7', '10'].includes(currentValueType)">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
@ -274,8 +290,8 @@
>{{ $t('cmdb.ciType.list') }} >{{ $t('cmdb.ciType.list') }}
<a-tooltip :title="$t('cmdb.ciType.listTips')"> <a-tooltip :title="$t('cmdb.ciType.listTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -297,7 +313,7 @@
</a-col> </a-col>
<a-col span="6"> <a-col span="6">
<a-form-item <a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 12 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
> >
<template slot="label"> <template slot="label">
@ -306,8 +322,8 @@
>{{ $t('cmdb.ciType.isDynamic') }} >{{ $t('cmdb.ciType.isDynamic') }}
<a-tooltip :title="$t('cmdb.ciType.dynamicTips')"> <a-tooltip :title="$t('cmdb.ciType.dynamicTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -328,17 +344,22 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <a-row>
<a-col :span="24" v-if="!['6'].includes(currentValueType)"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" /> <RegSelect
:isShowErrorMsg="false"
:limitedFormat="getLimitedFormat()"
:disabled="['6', '10', '11'].includes(currentValueType)"
v-model="re_check"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" :fontColorDisabled="['8', '11'].includes(currentValueType)" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')">
<PreValueArea <PreValueArea
v-if="drawerVisible" v-if="drawerVisible"
@ -349,7 +370,7 @@
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<template slot="label"> <template slot="label">
<span <span
@ -357,8 +378,8 @@
>{{ $t('cmdb.ciType.computedAttribute') }} >{{ $t('cmdb.ciType.computedAttribute') }}
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')"> <a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -415,14 +436,16 @@ import {
calcComputedAttribute, calcComputedAttribute,
} from '@/modules/cmdb/api/CITypeAttr' } from '@/modules/cmdb/api/CITypeAttr'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import { getPropertyType, getPropertyIcon } from '../../utils/helper'
import ComputedArea from './computedArea.vue' import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect' import RegSelect from '@/components/RegexSelect'
import ReferenceModelSelect from './attributeEdit/referenceModelSelect.vue'
export default { export default {
name: 'AttributeEditForm', name: 'AttributeEditForm',
components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect }, components: { ComputedArea, PreValueArea, vueJsonEditor, FontArea, RegSelect, ReferenceModelSelect },
props: { props: {
CITypeId: { CITypeId: {
type: Number, type: Number,
@ -467,7 +490,7 @@ export default {
return formLayout === 'horizontal' return formLayout === 'horizontal'
? { ? {
labelCol: { span: 8 }, labelCol: { span: 8 },
wrapperCol: { span: 12 }, wrapperCol: { span: 15 },
} }
: {} : {}
}, },
@ -484,6 +507,7 @@ export default {
}, },
mounted() {}, mounted() {},
methods: { methods: {
getPropertyIcon,
async handleCreate() { async handleCreate() {
try { try {
await canDefineComputed() await canDefineComputed()
@ -516,9 +540,7 @@ export default {
} }
} }
if (property === 'is_list') { if (property === 'is_list') {
this.form.setFieldsValue({ this.handleSwitchIsList(checked)
default_value: checked ? [] : '',
})
} }
if (checked && property === 'is_sortable') { if (checked && property === 'is_sortable') {
this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1')) this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1'))
@ -536,6 +558,26 @@ export default {
} }
}, },
handleSwitchIsList(checked) {
let defaultValue = checked ? [] : ''
switch (this.currentValueType) {
case '2':
case '9':
defaultValue = ''
break
case '10':
defaultValue = checked ? '' : false
break
default:
break
}
this.form.setFieldsValue({
default_value: defaultValue,
})
},
async handleEdit(record, attributes) { async handleEdit(record, attributes) {
try { try {
await canDefineComputed() await canDefineComputed()
@ -544,12 +586,7 @@ export default {
this.canDefineComputed = false this.canDefineComputed = false
} }
const _record = _.cloneDeep(record) const _record = _.cloneDeep(record)
if (_record.is_password) { _record.value_type = getPropertyType(_record)
_record.value_type = '7'
}
if (_record.is_link) {
_record.value_type = '8'
}
this.drawerTitle = this.$t('cmdb.ciType.editAttribute') this.drawerTitle = this.$t('cmdb.ciType.editAttribute')
this.drawerVisible = true this.drawerVisible = true
this.record = _record this.record = _record
@ -573,8 +610,13 @@ export default {
is_dynamic: _record.is_dynamic, is_dynamic: _record.is_dynamic,
}) })
} }
if (_record.value_type === '11') {
this.form.setFieldsValue({
reference_type_id: _record.reference_type_id
})
}
console.log(_record) console.log(_record)
if (!['6'].includes(_record.value_type) && _record.re_check) { if (!['6', '10', '11'].includes(_record.value_type) && _record.re_check) {
this.re_check = { this.re_check = {
value: _record.re_check, value: _record.re_check,
} }
@ -583,7 +625,11 @@ export default {
} }
if (_record.default) { if (_record.default) {
this.$nextTick(() => { this.$nextTick(() => {
if (_record.value_type === '0') { if (_record.value_type === '10') {
this.form.setFieldsValue({
default_value: Boolean(_record.default.default),
})
} else if (_record.value_type === '0') {
if (_record.is_list) { if (_record.is_list) {
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue({ this.form.setFieldsValue({
@ -639,7 +685,7 @@ export default {
}) })
} }
const _find = attributes.find((item) => item.id === _record.id) const _find = attributes.find((item) => item.id === _record.id)
if (!['6', '7'].includes(_record.value_type)) { if (!['6', '7', '10', '11'].includes(_record.value_type)) {
this.$refs.preValueArea.setData({ this.$refs.preValueArea.setData({
choice_value: (_find || {}).choice_value || [], choice_value: (_find || {}).choice_value || [],
choice_web_hook: _record.choice_web_hook, choice_web_hook: _record.choice_web_hook,
@ -672,7 +718,9 @@ export default {
delete values['default_show'] delete values['default_show']
delete values['is_required'] delete values['is_required']
const { default_value } = values const { default_value } = values
if (values.value_type === '0' && default_value) { if (values.value_type === '10') {
values.default = { default: values.is_list ? default_value : Boolean(default_value) }
} else if (values.value_type === '0' && default_value) {
if (values.is_list) { if (values.is_list) {
values.default = { default: default_value || null } values.default = { default: default_value || null }
} else { } else {
@ -706,23 +754,42 @@ export default {
values = { ...values, ...computedAreaData } values = { ...values, ...computedAreaData }
} else { } else {
// If it is a non-computed attribute, check to see if there is a predefined value // If it is a non-computed attribute, check to see if there is a predefined value
if (!['6', '7'].includes(values.value_type)) { if (!['6', '7', '10', '11'].includes(values.value_type)) {
const preValueAreaData = this.$refs.preValueArea.getData() const preValueAreaData = this.$refs.preValueArea.getData()
values = { ...values, ...preValueAreaData } values = { ...values, ...preValueAreaData }
} }
} }
const fontOptions = this.$refs.fontArea.getData() const fontOptions = this.$refs.fontArea.getData()
if (values.value_type === '7') {
values.value_type = '2' if (!['6', '10', '11'].includes(values.value_type)) {
values.is_password = true
}
if (values.value_type === '8') {
values.value_type = '2'
values.is_link = true
}
if (values.value_type !== '6') {
values.re_check = this.re_check?.value ?? null values.re_check = this.re_check?.value ?? null
} }
// 重置数据类型
switch (values.value_type) {
case '7':
values.value_type = '2'
values.is_password = true
break
case '8':
values.value_type = '2'
values.is_link = true
break
case '9':
values.value_type = '2'
break
case '10':
values.value_type = '7'
values.is_bool = true
break
case '11':
values.value_type = '0'
values.is_reference = true
break
default:
break
}
if (values.id) { if (values.id) {
await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed) await this.updateAttribute(values.id, { ...values, option: { fontOptions } }, isCalcComputed)
} else { } else {
@ -806,6 +873,9 @@ export default {
line-height: 22px; line-height: 22px;
color: #a5a9bc; color: #a5a9bc;
} }
.value-type-text {
margin-left: 4px;
}
</style> </style>
<style lang="less"> <style lang="less">
.attribute-edit-form { .attribute-edit-form {

View File

@ -16,21 +16,21 @@
<a-space style="margin-bottom: 10px"> <a-space style="margin-bottom: 10px">
<a-button @click="handleAddGroup" size="small" icon="plus">{{ $t('cmdb.ciType.group') }}</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> <a-button @click="handleOpenUniqueConstraint" size="small">{{ $t('cmdb.ciType.uniqueConstraint') }}</a-button>
<div> <div class="ci-types-attributes-flex">
<a-tooltip <a-tooltip
v-for="typeKey in Object.keys(valueTypeMap)" v-for="item in valueTypeMap"
:key="typeKey" :key="item.key"
:title="$t('cmdb.ciType.filterTips', { name: valueTypeMap[typeKey] })" :title="$t('cmdb.ciType.filterTips', { name: item.value })"
> >
<span <span
@click="handleFilterType(typeKey)" @click="handleFilterType(item.key)"
:class="{ :class="{
'ci-types-attributes-filter': true, 'ci-types-attributes-filter': true,
'ci-types-attributes-filter-selected': attrTypeFilter.includes(typeKey), 'ci-types-attributes-filter-selected': attrTypeFilter.includes(item.key),
}" }"
> >
<ops-icon :type="getPropertyIcon({ value_type: typeKey })" /> <ops-icon :type="getPropertyIcon({ value_type: item.key })" />
{{ valueTypeMap[typeKey] }} {{ item.value }}
</span> </span>
</a-tooltip> </a-tooltip>
</div> </div>
@ -200,7 +200,7 @@ import AttributeEditForm from './attributeEditForm.vue'
import NewCiTypeAttrModal from './newCiTypeAttrModal.vue' import NewCiTypeAttrModal from './newCiTypeAttrModal.vue'
import UniqueConstraint from './uniqueConstraint.vue' import UniqueConstraint from './uniqueConstraint.vue'
import { valueTypeMap } from '../../utils/const' import { valueTypeMap } from '../../utils/const'
import { getPropertyIcon } from '../../utils/helper' import { getPropertyIcon, getPropertyType } from '../../utils/helper'
export default { export default {
name: 'AttributesTable', name: 'AttributesTable',
@ -243,7 +243,12 @@ export default {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
valueTypeMap() { valueTypeMap() {
return valueTypeMap() const map = valueTypeMap()
const keys = ['0', '1', '2', '9', '3', '4', '5', '6', '7', '8', '10', '11']
return keys.map((key) => ({
key,
value: map[key]
}))
}, },
}, },
provide() { provide() {
@ -585,23 +590,8 @@ export default {
if (!attrTypeFilter.length) { if (!attrTypeFilter.length) {
return true return true
} else { } else {
if (attrTypeFilter.includes('7') && attr.is_password) { const valueType = getPropertyType(attr)
return true return attrTypeFilter.includes(valueType)
}
if (attrTypeFilter.includes('8') && attr.is_link) {
return true
}
if (
attrTypeFilter.includes(attr.value_type) &&
attr.value_type === '2' &&
(attr.is_password || attr.is_link)
) {
return false
}
if (attrTypeFilter.includes(attr.value_type)) {
return true
}
return false
} }
}) })
}, },
@ -618,6 +608,12 @@ export default {
.ci-types-attributes { .ci-types-attributes {
padding: 0 20px; padding: 0 20px;
overflow-y: auto; overflow-y: auto;
&-flex {
display: flex;
flex-wrap: wrap;
}
.ci-types-attributes-filter { .ci-types-attributes-filter {
color: @text-color_4; color: @text-color_4;
cursor: pointer; cursor: pointer;

View File

@ -46,16 +46,21 @@
v-decorator="['value_type', { rules: [{ required: true }], initialValue: '2' }]" v-decorator="['value_type', { rules: [{ required: true }], initialValue: '2' }]"
@change="handleChangeValueType" @change="handleChangeValueType"
> >
<a-select-option :value="key" :key="key" v-for="(value, key) in valueTypeMap"> <a-select-option :value="item.key" :key="item.key" v-for="(item) in valueTypeMap">
{{ value }} <ops-icon :type="getPropertyIcon({ value_type: item.key })" />
<span class="value-type-des" v-if="key === '3'">yyyy-mm-dd HH:MM:SS</span> <span class="value-type-text">{{ item.value }}</span>
<span class="value-type-des" v-if="key === '4'">yyyy-mm-dd</span> <span class="value-type-des" v-if="item.key === '2'">{{ $t('cmdb.ciType.shortTextTip') }}</span>
<span class="value-type-des" v-if="key === '5'">HH:MM:SS</span> <span class="value-type-des" v-if="item.key === '3'">yyyy-mm-dd HH:MM:SS</span>
<span class="value-type-des" v-if="item.key === '4'">yyyy-mm-dd</span>
<span class="value-type-des" v-if="item.key === '5'">HH:MM:SS</span>
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="currentValueType === '6' ? 24 : 12"> <a-col
v-if="currentValueType !== '11'"
:span="currentValueType === '6' ? 24 : 12"
>
<a-form-item <a-form-item
:label-col="{ span: currentValueType === '6' ? 4 : 8 }" :label-col="{ span: currentValueType === '6' ? 4 : 8 }"
:wrapper-col="{ span: currentValueType === '6' ? 18 : 15 }" :wrapper-col="{ span: currentValueType === '6' ? 18 : 15 }"
@ -68,6 +73,10 @@
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-input>
<a-switch
v-else-if="currentValueType === '10'"
v-decorator="['default_value', { rules: [{ required: false }], valuePropName: 'checked' }]"
/>
<a-input-number <a-input-number
style="width: 100%" style="width: 100%"
v-else-if="currentValueType === '1'" v-else-if="currentValueType === '1'"
@ -86,12 +95,7 @@
</a-select> </a-select>
<a-input <a-input
style="width: 100%" style="width: 100%"
v-else-if=" v-else-if="['2', '5', '7', '8', '9'].includes(currentValueType)"
currentValueType === '2' ||
currentValueType === '5' ||
currentValueType === '7' ||
currentValueType === '8'
"
v-decorator="['default_value', { rules: [{ required: false }] }]" v-decorator="['default_value', { rules: [{ required: false }] }]"
> >
</a-input> </a-input>
@ -148,9 +152,19 @@
</template> </template>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col
v-if="currentValueType === '11'"
:span="12"
>
<ReferenceModelSelect
:form="form"
:formItemLayout="formItemLayout"
/>
</a-col>
</a-row> </a-row>
<a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'"> <!-- <a-col :span="currentValueType === '2' ? 6 : 0" v-if="currentValueType !== '6'">
<a-form-item <a-form-item
:hidden="currentValueType === '2' ? false : true" :hidden="currentValueType === '2' ? false : true"
:label-col="horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
@ -182,10 +196,10 @@
v-decorator="['is_index', { rules: [], valuePropName: 'checked', initialValue: true }]" v-decorator="['is_index', { rules: [], valuePropName: 'checked', initialValue: true }]"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col> -->
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.unique')" :label="$t('cmdb.ciType.unique')"
> >
@ -199,7 +213,7 @@
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-form-item <a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('required')" :label="$t('required')"
> >
@ -212,7 +226,7 @@
</a-col> </a-col>
<a-col :span="6"> <a-col :span="6">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 12 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
> >
<template slot="label"> <template slot="label">
@ -221,8 +235,8 @@
>{{ $t('cmdb.ciType.defaultShow') }} >{{ $t('cmdb.ciType.defaultShow') }}
<a-tooltip :title="$t('cmdb.ciType.defaultShowTips')"> <a-tooltip :title="$t('cmdb.ciType.defaultShowTips')">
<a-icon <a-icon
style="position:absolute;top:2px;left:-17px;color:#2f54eb;" style="position:absolute;top:2px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -243,7 +257,7 @@
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? horizontalFormItemLayout.labelCol : { span: 8 }" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
:label="$t('cmdb.ciType.isSortable')" :label="$t('cmdb.ciType.isSortable')"
> >
@ -256,7 +270,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="6" v-if="currentValueType !== '6' && currentValueType !== '7'"> <a-col :span="6" v-if="!['6', '7', '10'].includes(currentValueType)">
<a-form-item <a-form-item
:label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol" :label-col="currentValueType === '2' ? { span: 8 } : horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
@ -264,11 +278,11 @@
<template slot="label"> <template slot="label">
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.list') }} >
<a-tooltip :title="$t('cmdb.ciType.listTips')"> <a-tooltip :title="$t('cmdb.ciType.listTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -278,6 +292,7 @@
" "
/> />
</a-tooltip> </a-tooltip>
{{ $t('cmdb.ciType.list') }}
</span> </span>
</template> </template>
<a-switch <a-switch
@ -290,17 +305,17 @@
</a-col> </a-col>
<a-col span="6"> <a-col span="6">
<a-form-item <a-form-item
:label-col="['2', '6', '7'].findIndex(i => currentValueType === i) === -1 ? { span: 12 } : horizontalFormItemLayout.labelCol" :label-col="horizontalFormItemLayout.labelCol"
:wrapper-col="horizontalFormItemLayout.wrapperCol" :wrapper-col="horizontalFormItemLayout.wrapperCol"
> >
<template slot="label"> <template slot="label">
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.isDynamic') }} >
<a-tooltip :title="$t('cmdb.ciType.dynamicTips')"> <a-tooltip :title="$t('cmdb.ciType.dynamicTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -310,6 +325,7 @@
" "
/> />
</a-tooltip> </a-tooltip>
{{ $t('cmdb.ciType.isDynamic') }}
</span> </span>
</template> </template>
<a-switch <a-switch
@ -321,17 +337,22 @@
</a-col> </a-col>
<a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider> <a-divider style="font-size:14px;margin-top:6px;">{{ $t('cmdb.ciType.advancedSettings') }}</a-divider>
<a-row> <a-row>
<a-col :span="24" v-if="!['6'].includes(currentValueType)"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 12 }" :label="$t('cmdb.ciType.reg')">
<RegSelect :isShowErrorMsg="false" v-model="re_check" :limitedFormat="getLimitedFormat()" /> <RegSelect
v-model="re_check"
:isShowErrorMsg="false"
:limitedFormat="getLimitedFormat()"
:disabled="['6', '10', '11'].includes(currentValueType)"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24"> <a-col :span="24">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.font')">
<FontArea ref="fontArea" /> <FontArea ref="fontArea" :fontColorDisabled="['8', '11'].includes(currentValueType)" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" :label="$t('cmdb.ciType.choiceValue')">
<PreValueArea <PreValueArea
ref="preValueArea" ref="preValueArea"
@ -341,16 +362,16 @@
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="24" v-if="!['6', '7'].includes(currentValueType)"> <a-col :span="24" v-if="!['6', '7', '10', '11'].includes(currentValueType)">
<a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form-item :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<template slot="label"> <template slot="label">
<span <span
style="position:relative;white-space:pre;" style="position:relative;white-space:pre;"
>{{ $t('cmdb.ciType.computedAttribute') }} >
<a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')"> <a-tooltip :title="$t('cmdb.ciType.computedAttributeTips')">
<a-icon <a-icon
style="position:absolute;top:3px;left:-17px;color:#2f54eb;" style="position:absolute;top:3px;left:-17px;color:#A5A9BC;"
type="question-circle" type="info-circle"
theme="filled" theme="filled"
@click=" @click="
(e) => { (e) => {
@ -360,6 +381,7 @@
" "
/> />
</a-tooltip> </a-tooltip>
{{ $t('cmdb.ciType.computedAttribute') }}
</span> </span>
</template> </template>
<a-switch <a-switch
@ -392,6 +414,8 @@ import ComputedArea from './computedArea.vue'
import PreValueArea from './preValueArea.vue' import PreValueArea from './preValueArea.vue'
import FontArea from './fontArea.vue' import FontArea from './fontArea.vue'
import RegSelect from '@/components/RegexSelect' import RegSelect from '@/components/RegexSelect'
import { getPropertyIcon } from '../../utils/helper'
import ReferenceModelSelect from './attributeEdit/referenceModelSelect.vue'
export default { export default {
name: 'CreateNewAttribute', name: 'CreateNewAttribute',
@ -401,6 +425,7 @@ export default {
vueJsonEditor, vueJsonEditor,
FontArea, FontArea,
RegSelect, RegSelect,
ReferenceModelSelect,
}, },
props: { props: {
hasFooter: { hasFooter: {
@ -437,13 +462,19 @@ export default {
}, },
computed: { computed: {
valueTypeMap() { valueTypeMap() {
return valueTypeMap() const map = valueTypeMap()
const keys = ['0', '1', '2', '9', '3', '4', '5', '6', '7', '8', '10', '11']
return keys.map((key) => ({
key,
value: map[key]
}))
}, },
canDefineScript() { canDefineScript() {
return this.canDefineComputed return this.canDefineComputed
}, },
}, },
methods: { methods: {
getPropertyIcon,
handleSubmit(isCloseModal = true) { handleSubmit(isCloseModal = true) {
this.form.validateFields(async (err, values) => { this.form.validateFields(async (err, values) => {
if (!err) { if (!err) {
@ -456,7 +487,9 @@ export default {
const data = { is_required, default_show, is_dynamic } const data = { is_required, default_show, is_dynamic }
delete values.is_required delete values.is_required
delete values.default_show delete values.default_show
if (values.value_type === '0' && default_value) { if (values.value_type === '10') {
values.default = { default: values.is_list ? (default_value || null) : Boolean(default_value) }
} else if (values.value_type === '0' && default_value) {
if (values.is_list) { if (values.is_list) {
values.default = { default: default_value || null } values.default = { default: default_value || null }
} else { } else {
@ -489,39 +522,48 @@ export default {
values = { ...values, ...computedAreaData } values = { ...values, ...computedAreaData }
} else { } else {
// If it is a non-computed attribute, check to see if there is a predefined value // If it is a non-computed attribute, check to see if there is a predefined value
if (!['6', '7'].includes(values.value_type)) { if (!['6', '7', '10', '11'].includes(values.value_type)) {
const preValueAreaData = this.$refs.preValueArea.getData() const preValueAreaData = this.$refs.preValueArea.getData()
values = { ...values, ...preValueAreaData } values = { ...values, ...preValueAreaData }
} }
} }
const fontOptions = this.$refs.fontArea.getData() const fontOptions = this.$refs.fontArea.getData()
// is_index: except for text, the index is hidden, text index default is true // 索引
// 5 types in the box, is_index=true values.is_index = !['6', '7', '8', '9', '11'].includes(values.value_type)
// json, password, link is_index=false
if (['6', '7', '8'].includes(values.value_type)) { // 重置数据类型
values.is_index = false switch (values.value_type) {
} else if (values.value_type !== '2') { case '7':
values.is_index = true values.value_type = '2'
} values.is_password = true
if (values.value_type === '7') { break
values.value_type = '2' case '8':
values.is_password = true values.value_type = '2'
} values.is_link = true
if (values.value_type === '8') { break
values.value_type = '2' case '9':
values.is_link = true values.value_type = '2'
} break
if (values.value_type !== '6') { case '10':
values.re_check = this.re_check?.value ?? null values.value_type = '7'
values.is_bool = true
break
case '11':
values.value_type = '0'
values.is_reference = true
break
default:
break
} }
const { attr_id } = await createAttribute({ ...values, option: { fontOptions } }) const { attr_id } = await createAttribute({ ...values, option: { fontOptions } })
this.form.resetFields() this.form.resetFields()
this.currentValueType = '2' if (this?.$refs?.preValueArea) {
if (!['6'].includes(values.value_type) && !values.is_password) {
this.$refs.preValueArea.valueList = [] this.$refs.preValueArea.valueList = []
} }
this.currentValueType = '2'
this.$emit('done', attr_id, data, isCloseModal) this.$emit('done', attr_id, data, isCloseModal)
} else { } else {
throw new Error() throw new Error()
@ -540,11 +582,12 @@ export default {
} }
}, },
handleChangeValueType(value) { handleChangeValueType(value) {
this.currentValueType = value
this.$nextTick(() => { this.$nextTick(() => {
this.form.setFieldsValue({ this.currentValueType = value
default_value: this.form.getFieldValue('is_list') || value === '0' ? [] : null, if (['6', '10', '11'].includes(value)) {
}) this.re_check = {}
}
this.handleSwitchType({ valueType: value })
}) })
}, },
onChange(checked, property) { onChange(checked, property) {
@ -560,9 +603,7 @@ export default {
} }
} }
if (property === 'is_list') { if (property === 'is_list') {
this.form.setFieldsValue({ this.handleSwitchType({ checked })
default_value: checked ? [] : '',
})
} }
if (checked && property === 'is_sortable') { if (checked && property === 'is_sortable') {
this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1')) this.$message.warning(this.$t('cmdb.ciType.addAttributeTips1'))
@ -579,6 +620,33 @@ export default {
}) })
} }
}, },
handleSwitchType({
checked,
valueType
}) {
checked = checked ?? this.form.getFieldValue('is_list')
valueType = valueType ?? this.currentValueType
let defaultValue = checked || valueType === '0' ? [] : ''
switch (valueType) {
case '2':
case '9':
defaultValue = ''
break
case '10':
defaultValue = checked ? '' : false
break
default:
break
}
this.form.setFieldsValue({
default_value: defaultValue,
})
},
onJsonChange(value) { onJsonChange(value) {
this.default_value_json_right = true this.default_value_json_right = true
}, },
@ -629,6 +697,9 @@ export default {
line-height: 22px; line-height: 22px;
color: #a5a9bc; color: #a5a9bc;
} }
.value-type-text {
margin: 0 4px;
}
</style> </style>
<style lang="less"> <style lang="less">
.create-new-attribute { .create-new-attribute {

View File

@ -21,7 +21,13 @@
<a-icon type="underline" /> <a-icon type="underline" />
</div> </div>
<div :style="{ width: '100px', marginLeft: '10px', display: 'inline-flex', alignItems: 'center' }"> <div :style="{ width: '100px', marginLeft: '10px', display: 'inline-flex', alignItems: 'center' }">
<a-icon type="font-colors" /><el-color-picker size="mini" v-model="fontOptions.color"> </el-color-picker> <a-icon type="font-colors" />
<el-color-picker
size="mini"
:disabled="fontColorDisabled"
v-model="fontOptions.color"
>
</el-color-picker>
</div> </div>
</div> </div>
</template> </template>
@ -30,6 +36,12 @@
import _ from 'lodash' import _ from 'lodash'
export default { export default {
name: 'FontArea', name: 'FontArea',
props: {
fontColorDisabled: {
type: Boolean,
default: false
}
},
data() { data() {
return { return {
fontOptions: { fontOptions: {
@ -57,7 +69,11 @@ export default {
if (flag) { if (flag) {
return undefined return undefined
} else { } else {
return this.fontOptions const fontOptions = _.cloneDeep(this.fontOptions)
if (this.fontColorDisabled) {
Reflect.deleteProperty(fontOptions, 'color')
}
return fontOptions
} }
}, },
setData({ fontOptions = {} }) { setData({ fontOptions = {} }) {

View File

@ -545,7 +545,7 @@ export default {
}, },
showIdSelectOptions() { showIdSelectOptions() {
const _showIdSelectOptions = this.currentTypeAttrs.filter( const _showIdSelectOptions = this.currentTypeAttrs.filter(
(item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list (item) => item.id !== this.unique_id && !['6'].includes(item.value_type) && !item.is_password && !item.is_list && !item.is_bool && !item.is_reference
) )
if (this.showIdFilterInput) { if (this.showIdFilterInput) {
return _showIdSelectOptions.filter( return _showIdSelectOptions.filter(
@ -898,6 +898,7 @@ export default {
this.loadCITypes() this.loadCITypes()
this.loading = false this.loading = false
this.drawerVisible = false this.drawerVisible = false
this.isInherit = false
}, 1000) }, 1000)
}, },
async updateCIType(CITypeId, data) { async updateCIType(CITypeId, data) {
@ -916,6 +917,7 @@ export default {
this.loadCITypes() this.loadCITypes()
this.loading = false this.loading = false
this.drawerVisible = false this.drawerVisible = false
this.isInherit = false
}, 1000) }, 1000)
}) })
}) })

View File

@ -47,9 +47,32 @@
> >
<ops-icon class="text-group-icon" type="veops-text" /> <ops-icon class="text-group-icon" type="veops-text" />
</div> </div>
<CIReferenceAttr
v-if="getAttr(rule.property).is_reference && (rule.exp === 'is' || rule.exp === '~is')"
class="select-filter"
:referenceTypeId="getAttr(rule.property).reference_type_id"
:value="rule.value"
:disabled="disabled"
@change="(value) => handleChange('value', value)"
/>
<a-select
v-else-if="getAttr(rule.property).is_bool && (rule.exp === 'is' || rule.exp === '~is')"
class="select-filter"
:disabled="disabled"
:placeholder="$t('placeholder2')"
:value="rule.value"
@change="(value) => handleChange('value', value)"
>
<a-select-option key="1">
true
</a-select-option>
<a-select-option key="0">
false
</a-select-option>
</a-select>
<div <div
class="input-group" class="input-group"
v-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')" v-else-if="isChoiceByProperty(rule.property) && (rule.exp === 'is' || rule.exp === '~is')"
> >
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
@ -148,9 +171,13 @@
<script> <script>
import { compareTypeList } from '../constants.js' import { compareTypeList } from '../constants.js'
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
export default { export default {
name: 'ValueControls', name: 'ValueControls',
components: {
CIReferenceAttr
},
props: { props: {
rule: { rule: {
type: Object, type: Object,
@ -215,7 +242,10 @@ export default {
...this.rule, ...this.rule,
[key]: value [key]: value
}) })
} },
getAttr(property) {
return this.attrList.find((item) => item.name === property) || {}
},
} }
} }
</script> </script>
@ -270,4 +300,21 @@ export default {
color: #FFFFFF; color: #FFFFFF;
} }
} }
.select-filter {
height: 36px;
width: 136px;
/deep/ .ant-select-selection {
height: 36px;
background: #f7f8fa;
line-height: 36px;
border: none;
.ant-select-selection__rendered {
height: 36px;
line-height: 36px;
}
}
}
</style> </style>

View File

@ -137,6 +137,7 @@ import {
getCITypeChildren, getCITypeChildren,
getCITypeParent getCITypeParent
} from '../../api/CITypeRelation.js' } from '../../api/CITypeRelation.js'
import { getCITypeAttributesById } from '../../api/CITypeAttr'
export default { export default {
name: 'RelationAutoDiscovery', name: 'RelationAutoDiscovery',
@ -169,7 +170,18 @@ export default {
methods: { methods: {
async getCITypeAttributes() { async getCITypeAttributes() {
const res = await getCITypeAttributes(this.CITypeId) const res = await getCITypeAttributes(this.CITypeId)
this.ciTypeADTAttributes = res.map((item) => { const attr = await getCITypeAttributesById(this.CITypeId)
const filterAttr = res.filter((name) => {
const currentAttr = attr?.attributes?.find((item) => item?.name === name)
if (!currentAttr) {
return true
}
return this.filterAttributes(name)
})
this.ciTypeADTAttributes = filterAttr.map((item) => {
return { return {
id: item, id: item,
value: item, value: item,
@ -239,7 +251,7 @@ export default {
const peer_type_id = item.peer_type_id const peer_type_id = item.peer_type_id
const attributes = this?.relationOptions?.find((option) => option?.value === peer_type_id)?.attributes const attributes = this?.relationOptions?.find((option) => option?.value === peer_type_id)?.attributes
item.attributes = attributes item.attributes = attributes.filter((attr) => this.filterAttributes(attr))
item.peer_attr_id = undefined item.peer_attr_id = undefined
}) })
}, },
@ -288,6 +300,15 @@ export default {
this.getCITypeRelations() this.getCITypeRelations()
} }
}, },
filterAttributes(attr) {
// filter password/json/is_list/longText/bool/reference
if (attr?.value_type === '2' && !attr?.is_index) {
return false
}
return !attr?.is_password && !attr?.is_list && attr?.value_type !== '6' && !attr?.is_bool && !attr?.is_reference
},
}, },
} }
</script> </script>

View File

@ -598,8 +598,14 @@ export default {
}) })
}, },
filterAttributes(attributes) { filterAttributes(attributes) {
// filter password/json/is_list // filter password/json/is_list/longText/bool/reference
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6') return attributes.filter((attr) => {
if (attr.value_type === '2' && !attr.is_index) {
return false
}
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
})
}, },
addTableAttr() { addTableAttr() {
this.tableAttrList.push({ this.tableAttrList.push({

View File

@ -471,10 +471,15 @@ export default {
this.dateForm = _.cloneDeep(this.defaultDateForm) this.dateForm = _.cloneDeep(this.defaultDateForm)
this.notifies = _.cloneDeep(this.defaultNotify) this.notifies = _.cloneDeep(this.defaultNotify)
this.category = 1 this.category = 1
this.triggerAction = '1'
this.filterExp = '' this.filterExp = ''
this.selectedBot = undefined this.selectedBot = undefined
this.visible = false if (this.$refs.noticeContent) {
this.$refs.noticeContent.destroy()
}
this.$nextTick(() => {
this.visible = false
})
}, },
filterChange(value) { filterChange(value) {
this.filterValue = value this.filterValue = value

View File

@ -364,8 +364,14 @@ export default {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}, },
filterAttributes(attributes) { filterAttributes(attributes) {
// filter password/json/is_list // filter password/json/is_list/longText/bool/reference
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6') return attributes.filter((attr) => {
if (attr.value_type === '2' && !attr.is_index) {
return false
}
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
})
}, },
addModalAttr() { addModalAttr() {

View File

@ -306,8 +306,14 @@ export default {
return _find?.alias ?? _find?.name ?? id return _find?.alias ?? _find?.name ?? id
}, },
filterAttributes(attributes) { filterAttributes(attributes) {
// filter password/json/is_list // filter password/json/is_list/longText/bool/reference
return attributes.filter((attr) => !attr.is_password && !attr.is_list && attr.value_type !== '6') return attributes.filter((attr) => {
if (attr.value_type === '2' && !attr.is_index) {
return false
}
return !attr.is_password && !attr.is_list && attr.value_type !== '6' && !attr.is_bool && !attr.is_reference
})
}, },
addTableAttr() { addTableAttr() {

View File

@ -1,250 +1,260 @@
<template> <template>
<a-modal <a-modal
v-model="visible" v-model="visible"
width="90%" width="90%"
:closable="false" :closable="false"
:centered="true" :centered="true"
:maskClosable="false" :maskClosable="false"
:destroyOnClose="true" :destroyOnClose="true"
@cancel="handleClose" @cancel="handleClose"
@ok="handleOk" @ok="handleOk"
> >
<div :style="{ width: '100%' }" id="add-table-modal"> <div :style="{ width: '100%' }" id="add-table-modal">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<SearchForm <SearchForm
ref="searchForm" ref="searchForm"
:typeId="addTypeId" :typeId="addTypeId"
:preferenceAttrList="preferenceAttrList" :preferenceAttrList="preferenceAttrList"
@refresh="handleSearch" @refresh="handleSearch"
> >
<a-button <a-button
@click=" @click="
() => { () => {
$refs.createInstanceForm.handleOpen(true, 'create') $refs.createInstanceForm.handleOpen(true, 'create')
} }
" "
slot="extraContent" slot="extraContent"
type="primary" type="primary"
size="small" size="small"
>新增</a-button >新增</a-button
> >
</SearchForm> </SearchForm>
<vxe-table <vxe-table
ref="xTable" ref="xTable"
row-id="_id" row-id="_id"
:data="tableData" :data="tableData"
:height="tableHeight" :height="tableHeight"
highlight-hover-row highlight-hover-row
:checkbox-config="{ reserve: true }" :checkbox-config="{ reserve: true }"
@checkbox-change="onSelectChange" @checkbox-change="onSelectChange"
@checkbox-all="onSelectChange" @checkbox-all="onSelectChange"
show-overflow="tooltip" show-overflow="tooltip"
show-header-overflow="tooltip" show-header-overflow="tooltip"
:scroll-y="{ enabled: true, gt: 50 }" :scroll-y="{ enabled: true, gt: 50 }"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column> <vxe-column align="center" type="checkbox" width="60" fixed="left"></vxe-column>
<vxe-table-column <vxe-table-column
v-for="col in columns" v-for="col in columns"
:key="col.field" :key="col.field"
:title="col.title" :title="col.title"
:field="col.field" :field="col.field"
:width="col.width" :width="col.width"
:sortable="col.sortable" :sortable="col.sortable"
> >
<template #default="{row}" v-if="col.value_type === '6'"> <template v-if="col.is_reference" #default="{row}">
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span> <a
</template> v-for="(id) in (col.is_list ? row[col.field] : [row[col.field]])"
</vxe-table-column> :key="id"
</vxe-table> :href="`/cmdb/cidetail/${col.reference_type_id}/${id}`"
<a-pagination target="_blank"
v-model="currentPage" >
size="small" {{ id }}
:total="totalNumber" </a>
show-quick-jumper </template>
:page-size="50" <template #default="{row}" v-else-if="col.value_type == '6'">
:show-total=" <span v-if="col.value_type == '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
(total, range) => </template>
$t('pagination.total', { </vxe-table-column>
range0: range[0], </vxe-table>
range1: range[1], <a-pagination
total, v-model="currentPage"
}) size="small"
" :total="totalNumber"
:style="{ textAlign: 'right', marginTop: '10px' }" show-quick-jumper
@change="handleChangePage" :page-size="50"
/> :show-total="
</a-spin> (total, range) =>
</div> $t('pagination.total', {
<CreateInstanceForm range0: range[0],
ref="createInstanceForm" range1: range[1],
:typeIdFromRelation="addTypeId" total,
@reload=" })
() => { "
currentPage = 1 :style="{ textAlign: 'right', marginTop: '10px' }"
getTableData(true) @change="handleChangePage"
} />
" </a-spin>
/> </div>
</a-modal> <CreateInstanceForm
</template> ref="createInstanceForm"
:typeIdFromRelation="addTypeId"
<script> @reload="
import { searchCI } from '@/modules/cmdb/api/ci' () => {
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference' currentPage = 1
import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation' getTableData(true)
import { getCITableColumns } from '../../../utils/helper' }
import SearchForm from '../../../components/searchForm/SearchForm.vue' "
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue' />
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr' </a-modal>
</template>
export default {
name: 'AddTableModal', <script>
components: { SearchForm, CreateInstanceForm }, import { searchCI } from '@/modules/cmdb/api/ci'
data() { import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
return { import { batchUpdateCIRelationChildren, batchUpdateCIRelationParents } from '@/modules/cmdb/api/CIRelation'
visible: false, import { getCITableColumns } from '../../../utils/helper'
currentPage: 1, import SearchForm from '../../../components/searchForm/SearchForm.vue'
totalNumber: 0, import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
tableData: [], import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
columns: [],
ciObj: {}, export default {
ciId: null, name: 'AddTableModal',
addTypeId: null, components: { SearchForm, CreateInstanceForm },
loading: false, data() {
expression: '', return {
isFocusExpression: false, visible: false,
type: 'children', currentPage: 1,
preferenceAttrList: [], totalNumber: 0,
ancestor_ids: undefined, tableData: [],
attrList1: [], columns: [],
} ciObj: {},
}, ciId: null,
computed: { addTypeId: null,
tableHeight() { loading: false,
return this.$store.state.windowHeight - 250 expression: '',
}, isFocusExpression: false,
placeholder() { type: 'children',
return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2') preferenceAttrList: [],
}, ancestor_ids: undefined,
width() { attrList1: [],
return this.isFocusExpression ? '500px' : '100px' }
}, },
}, computed: {
provide() { tableHeight() {
return { return this.$store.state.windowHeight - 250
attrList: () => { },
return this.attrList placeholder() {
}, return this.isFocusExpression ? this.$t('cmdb.serviceTreetips1') : this.$t('cmdb.serviceTreetips2')
} },
}, width() {
watch: {}, return this.isFocusExpression ? '500px' : '100px'
methods: { },
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) { },
console.log(ciObj, ciId, addTypeId, type) provide() {
this.visible = true return {
this.ciObj = ciObj attrList: () => {
this.ciId = ciId return this.attrList
this.addTypeId = addTypeId },
this.type = type }
this.ancestor_ids = ancestor_ids },
await getSubscribeAttributes(addTypeId).then((res) => { watch: {},
this.preferenceAttrList = res.attributes // 已经订阅的全部列 methods: {
}) async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
getCITypeAttributesById(addTypeId).then((res) => { console.log(ciObj, ciId, addTypeId, type)
this.attrList = res.attributes this.visible = true
}) this.ciObj = ciObj
this.getTableData(true) this.ciId = ciId
}, this.addTypeId = addTypeId
async getTableData(isInit) { this.type = type
if (this.addTypeId) { this.ancestor_ids = ancestor_ids
await this.fetchData(isInit) await getSubscribeAttributes(addTypeId).then((res) => {
} this.preferenceAttrList = res.attributes // 已经订阅的全部列
}, })
async fetchData(isInit) { getCITypeAttributesById(addTypeId).then((res) => {
this.loading = true this.attrList = res.attributes
// if (isInit) { })
// const subscribed = await getSubscribeAttributes(this.addTypeId) this.getTableData(true)
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列 },
// } async getTableData(isInit) {
let sort, fuzzySearch, expression, exp if (this.addTypeId) {
if (!isInit) { await this.fetchData(isInit)
fuzzySearch = this.$refs['searchForm'].fuzzySearch }
expression = this.$refs['searchForm'].expression || '' },
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g async fetchData(isInit) {
this.loading = true
exp = expression.match(regQ) ? expression.match(regQ)[0] : null // if (isInit) {
} // const subscribed = await getSubscribeAttributes(this.addTypeId)
// this.preferenceAttrList = subscribed.attributes // 已经订阅的全部列
await searchCI({ // }
q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`, let sort, fuzzySearch, expression, exp
count: 50, if (!isInit) {
page: this.currentPage, fuzzySearch = this.$refs['searchForm'].fuzzySearch
sort, expression = this.$refs['searchForm'].expression || ''
}) const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
.then((res) => {
this.tableData = res.result exp = expression.match(regQ) ? expression.match(regQ)[0] : null
this.totalNumber = res.numfound }
this.columns = this.getColumns(res.result, this.preferenceAttrList)
this.$nextTick(() => { await searchCI({
const _table = this.$refs.xTable q: `_type:${this.addTypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
if (_table) { count: 50,
_table.refreshColumn() page: this.currentPage,
} sort,
this.loading = false })
}) .then((res) => {
}) this.tableData = res.result
.catch(() => { this.totalNumber = res.numfound
this.loading = false this.columns = this.getColumns(res.result, this.preferenceAttrList)
}) this.$nextTick(() => {
}, const _table = this.$refs.xTable
getColumns(data, attrList) { if (_table) {
const modalDom = document.getElementById('add-table-modal') _table.refreshColumn()
if (modalDom) { }
const width = modalDom.clientWidth - 50 this.loading = false
return getCITableColumns(data, attrList, width) })
} })
return [] .catch(() => {
}, this.loading = false
onSelectChange() {}, })
handleClose() { },
this.$refs.xTable.clearCheckboxRow() getColumns(data, attrList) {
this.currentPage = 1 const modalDom = document.getElementById('add-table-modal')
this.expression = '' if (modalDom) {
this.isFocusExpression = false const width = modalDom.clientWidth - 50
this.visible = false return getCITableColumns(data, attrList, width)
}, }
async handleOk() { return []
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords() },
const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords() onSelectChange() {},
const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id) handleClose() {
if (ciIds.length) { this.$refs.xTable.clearCheckboxRow()
if (this.type === 'children') { this.currentPage = 1
await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids) this.expression = ''
} else { this.isFocusExpression = false
await batchUpdateCIRelationParents(ciIds, [this.ciId]) this.visible = false
} },
setTimeout(() => { async handleOk() {
this.$message.success(this.$t('addSuccess')) const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
this.handleClose() const selectRecordsReserved = this.$refs.xTable.getCheckboxReserveRecords()
this.$emit('reload') const ciIds = [...selectRecordsCurrent, ...selectRecordsReserved].map((record) => record._id)
}, 500) if (ciIds.length) {
} else { if (this.type === 'children') {
this.handleClose() await batchUpdateCIRelationChildren(ciIds, [this.ciId], this.ancestor_ids)
this.$emit('reload') } else {
} await batchUpdateCIRelationParents(ciIds, [this.ciId])
}, }
handleSearch() { setTimeout(() => {
this.currentPage = 1 this.$message.success(this.$t('addSuccess'))
this.fetchData() this.handleClose()
}, this.$emit('reload')
handleChangePage(page, pageSize) { }, 500)
this.currentPage = page } else {
this.fetchData() this.handleClose()
}, this.$emit('reload')
}, }
} },
</script> handleSearch() {
this.currentPage = 1
<style lang="less" scoped></style> this.fetchData()
},
handleChangePage(page, pageSize) {
this.currentPage = page
this.fetchData()
},
},
}
</script>
<style lang="less" scoped></style>

View File

@ -101,8 +101,18 @@
:minWidth="100" :minWidth="100"
:cell-type="col.value_type === '2' ? 'string' : 'auto'" :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}"> <template v-if="col.value_type === '6' || col.is_link || col.is_password || col.is_choice || col.is_reference" #default="{row}">
<span v-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span> <template v-if="col.is_reference && row[col.field]" >
<a
v-for="(ciId) in (col.is_list ? row[col.field] : [row[col.field]])"
:key="ciId"
:href="`/cmdb/cidetail/${col.reference_type_id}/${ciId}`"
target="_blank"
>
{{ getReferenceAttrValue(ciId, col) }}
</a>
</template>
<span v-else-if="col.value_type === '6' && row[col.field]">{{ JSON.stringify(row[col.field]) }}</span>
<template v-else-if="col.is_link && row[col.field]"> <template v-else-if="col.is_link && row[col.field]">
<a <a
v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])" v-for="(item, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
@ -254,6 +264,8 @@ export default {
sortByTable: undefined, sortByTable: undefined,
loading: false, loading: false,
columnsGroup: [], columnsGroup: [],
referenceShowAttrNameMap: {},
referenceCIIdMap: {},
} }
}, },
computed: { computed: {
@ -433,12 +445,99 @@ export default {
await Promise.all(promises1).then(() => { await Promise.all(promises1).then(() => {
this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup] this.columnsGroup = [..._commonColumnsGroup, ..._columnsGroup]
this.instanceList = res['result'] this.instanceList = res['result']
this.handlePerference()
}) })
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
}) })
}, },
handlePerference() {
let needRequiredCIType = []
this.columnsGroup.forEach((group) => {
group.children.forEach((col) => {
if (col?.is_reference && col?.reference_type_id) {
needRequiredCIType.push(col)
}
})
})
needRequiredCIType = _.uniq(needRequiredCIType)
if (!needRequiredCIType.length) {
this.referenceShowAttrNameMap = {}
this.referenceCIIdMap = {}
return
}
this.handleReferenceShowAttrName(needRequiredCIType)
this.handleReferenceCIIdMap(needRequiredCIType)
},
async handleReferenceShowAttrName(needRequiredCIType) {
const res = await getCITypes({
type_ids: needRequiredCIType.map((col) => col.reference_type_id).join(',')
})
const map = {}
res.ci_types.forEach((ciType) => {
map[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
})
this.referenceShowAttrNameMap = map
},
async handleReferenceCIIdMap(needRequiredCIType) {
const map = {}
this.instanceList.forEach((row) => {
needRequiredCIType.forEach((col) => {
const ids = Array.isArray(row[col.field]) ? row[col.field] : row[col.field] ? [row[col.field]] : []
if (ids.length) {
if (!map?.[col.reference_type_id]) {
map[col.reference_type_id] = {}
}
ids.forEach((id) => {
map[col.reference_type_id][id] = {}
})
}
})
})
if (!Object.keys(map).length) {
this.referenceCIIdMap = {}
return
}
const allRes = await Promise.all(
Object.keys(map).map((key) => {
return searchCI({
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
count: 9999
})
})
)
allRes.forEach((res) => {
res.result.forEach((item) => {
if (map?.[item._type]?.[item._id]) {
map[item._type][item._id] = item
}
})
})
this.referenceCIIdMap = map
},
getReferenceAttrValue(id, col) {
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
if (!ci) {
return id
}
const attrName = this.referenceShowAttrNameMap?.[col.reference_type_id]
return ci?.[attrName] || id
},
getColumns(data, attrList) { getColumns(data, attrList) {
const width = document.getElementById('resource_search').clientWidth - 50 const width = document.getElementById('resource_search').clientWidth - 50
return getCITableColumns(data, attrList, width).map((item) => { return getCITableColumns(data, attrList, width).map((item) => {

View File

@ -14,7 +14,7 @@ export default {
name: 'TreeViewsNode', name: 'TreeViewsNode',
props: { props: {
title: { title: {
type: [String, Number], type: [String, Number, Boolean],
default: '', default: '',
}, },
treeKey: { treeKey: {