feat(ui):i18n (#347)

* feat(acl-ui):i18n

* feat(base-ui):i18n

* feat(cmdb-ui):i18n
This commit is contained in:
wang-liang0615 2024-01-02 17:53:07 +08:00 committed by GitHub
parent ace160ae19
commit e429ad59ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
178 changed files with 21927 additions and 19433 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<a-config-provider :locale="locale"> <a-config-provider :locale="antdLocale">
<div id="app" :class="{ 'ops-fullscreen': isOpsFullScreen, 'ops-only-topmenu': isOpsOnlyTopMenu }"> <div id="app" :class="{ 'ops-fullscreen': isOpsFullScreen, 'ops-only-topmenu': isOpsOnlyTopMenu }">
<router-view v-if="alive" /> <router-view v-if="alive" />
</div> </div>
@ -7,8 +7,9 @@
</template> </template>
<script> <script>
import { mapActions } from 'vuex' import { mapState, mapActions, mapMutations } from 'vuex'
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN' import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import enUS from 'ant-design-vue/lib/locale-provider/en_US'
import { AppDeviceEnquire } from '@/utils/mixin' import { AppDeviceEnquire } from '@/utils/mixin'
import { debounce } from './utils/util' import { debounce } from './utils/util'
@ -24,20 +25,28 @@ export default {
}, },
data() { data() {
return { return {
locale: zhCN,
alive: true, alive: true,
timer: null, timer: null,
} }
}, },
computed: { computed: {
...mapState(['locale']),
antdLocale() {
if (this.locale === 'zh') {
return zhCN
}
return enUS
},
isOpsFullScreen() { isOpsFullScreen() {
return this.$route.name === 'cmdb_screen' return ['cmdb_screen'].includes(this.$route.name)
}, },
isOpsOnlyTopMenu() { isOpsOnlyTopMenu() {
return ['fullscreen_index', 'setting_person'].includes(this.$route.name) return ['fullscreen_index', 'setting_person', 'notice_center'].includes(this.$route.name)
}, },
}, },
created() { created() {
this.SET_LOCALE(localStorage.getItem('ops_locale') || 'zh')
this.$i18n.locale = localStorage.getItem('ops_locale') || 'zh'
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.setTime(new Date().getTime()) this.setTime(new Date().getTime())
}, 1000) }, 1000)
@ -184,6 +193,7 @@ export default {
}, },
methods: { methods: {
...mapActions(['setTime']), ...mapActions(['setTime']),
...mapMutations(['SET_LOCALE']),
reload() { reload() {
this.alive = false this.alive = false
this.$nextTick(() => { this.$nextTick(() => {

View File

@ -1,29 +1,37 @@
export const ruleTypeList = [ import i18n from '@/lang'
{ value: 'and', label: '与' },
{ value: 'or', label: '或' },
// { value: 'not', label: '非' },
]
export const expList = [ export const ruleTypeList = () => {
{ value: 'is', label: '等于' }, return [
{ value: '~is', label: '不等于' }, { value: 'and', label: i18n.t('cmdbFilterComp.and') },
{ value: 'contain', label: '包含' }, { value: 'or', label: i18n.t('cmdbFilterComp.or') },
{ value: '~contain', label: '不包含' }, // { value: 'not', label: '非' },
{ value: 'start_with', label: '以...开始' }, ]
{ value: '~start_with', label: '不以...开始' }, }
{ value: 'end_with', label: '以...结束' },
{ value: '~end_with', label: '不以...结束' },
{ value: '~value', label: '为空' }, // 为空的定义有点绕
{ value: 'value', label: '不为空' },
]
export const advancedExpList = [ export const expList = () => {
{ value: 'in', label: 'in查询' }, return [
{ value: '~in', label: '非in查询' }, { value: 'is', label: i18n.t('cmdbFilterComp.is') },
{ value: 'range', label: '范围' }, { value: '~is', label: i18n.t('cmdbFilterComp.~is') },
{ value: '~range', label: '范围外' }, { value: 'contain', label: i18n.t('cmdbFilterComp.contain') },
{ value: 'compare', label: '比较' }, { value: '~contain', label: i18n.t('cmdbFilterComp.~contain') },
] { value: 'start_with', label: i18n.t('cmdbFilterComp.start_with') },
{ value: '~start_with', label: i18n.t('cmdbFilterComp.~start_with') },
{ value: 'end_with', label: i18n.t('cmdbFilterComp.end_with') },
{ value: '~end_with', label: i18n.t('cmdbFilterComp.~end_with') },
{ value: '~value', label: i18n.t('cmdbFilterComp.~value') }, // 为空的定义有点绕
{ value: 'value', label: i18n.t('cmdbFilterComp.value') },
]
}
export const advancedExpList = () => {
return [
{ value: 'in', label: i18n.t('cmdbFilterComp.in') },
{ value: '~in', label: i18n.t('cmdbFilterComp.~in') },
{ value: 'range', label: i18n.t('cmdbFilterComp.range') },
{ value: '~range', label: i18n.t('cmdbFilterComp.~range') },
{ value: 'compare', label: i18n.t('cmdbFilterComp.compare') },
]
}
export const compareTypeList = [ export const compareTypeList = [
{ value: '1', label: '>' }, { value: '1', label: '>' },

View File

@ -1,293 +1,332 @@
<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: '50px', 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: '50px', '--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,
} }
} }
" "
> >
</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"
> >
<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"
> >
</treeselect> </treeselect>
<treeselect <treeselect
class="custom-treeselect" class="custom-treeselect"
:style="{ width: '175px', '--custom-height': '24px' }" :style="{ width: '175px', '--custom-height': '24px' }"
v-model="item.value" v-model="item.value"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')" v-if="isChoiceByProperty(item.property) && (item.exp === 'is' || item.exp === '~is')"
:options="getChoiceValueByProperty(item.property)" :options="getChoiceValueByProperty(item.property)"
placeholder="请选择" :placeholder="$t('placeholder2')"
:normalizer=" :normalizer="
(node) => { (node) => {
return { return {
id: node[0], id: node[0],
label: node[0], label: node[0],
children: node.children, children: node.children,
} }
} }
" "
appendToBody appendToBody
:zIndex="1050" :zIndex="1050"
> >
<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' }"
> >
{{ node.label }} {{ node.label }}
</div> </div>
</treeselect> </treeselect>
<a-input-group <a-input-group
size="small" size="small"
compact compact
v-else-if="item.exp === 'range' || item.exp === '~range'" v-else-if="item.exp === 'range' || item.exp === '~range'"
:style="{ width: '175px' }" :style="{ width: '175px' }"
> >
<a-input class="ops-input" size="small" v-model="item.min" :style="{ width: '78px' }" placeholder="最小值" /> <a-input
~ class="ops-input"
<a-input class="ops-input" size="small" v-model="item.max" :style="{ width: '78px' }" placeholder="最大值" /> size="small"
</a-input-group> v-model="item.min"
<a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }"> :style="{ width: '78px' }"
<treeselect :placeholder="$t('min')"
class="custom-treeselect" />
:style="{ width: '60px', '--custom-height': '24px' }" ~
v-model="item.compareType" <a-input
:multiple="false" class="ops-input"
:clearable="false" size="small"
searchable v-model="item.max"
:options="compareTypeList" :style="{ width: '78px' }"
:normalizer=" :placeholder="$t('max')"
(node) => { />
return { </a-input-group>
id: node.value, <a-input-group size="small" compact v-else-if="item.exp === 'compare'" :style="{ width: '175px' }">
label: node.label, <treeselect
children: node.children, class="custom-treeselect"
} :style="{ width: '60px', '--custom-height': '24px' }"
} v-model="item.compareType"
" :multiple="false"
appendToBody :clearable="false"
:zIndex="1050" searchable
> :options="compareTypeList"
</treeselect> :normalizer="
<a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" /> (node) => {
</a-input-group> return {
<a-input id: node.value,
v-else-if="item.exp !== 'value' && item.exp !== '~value'" label: node.label,
size="small" children: node.children,
v-model="item.value" }
:placeholder="item.exp === 'in' || item.exp === '~in' ? '以 ; 分隔' : ''" }
class="ops-input" "
></a-input> appendToBody
<div v-else :style="{ width: '175px' }"></div> :zIndex="1050"
<a-tooltip title="复制"> >
<a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a> </treeselect>
</a-tooltip> <a-input class="ops-input" v-model="item.value" size="small" style="width: 113px" />
<a-tooltip title="删除"> </a-input-group>
<a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a> <a-input
</a-tooltip> v-else-if="item.exp !== 'value' && item.exp !== '~value'"
</a-space> size="small"
<div class="table-filter-add"> v-model="item.value"
<a @click="handleAddRule">+ 新增</a> :placeholder="item.exp === 'in' || item.exp === '~in' ? $t('cmdbFilterComp.split', { separator: ';' }) : ''"
</div> class="ops-input"
</div> :style="{ width: '175px' }"
</template> ></a-input>
<div v-else :style="{ width: '175px' }"></div>
<script> <a-tooltip :title="$t('copy')">
import _ from 'lodash' <a class="operation" @click="handleCopyRule(item)"><ops-icon type="icon-xianxing-copy"/></a>
import { v4 as uuidv4 } from 'uuid' </a-tooltip>
import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants' <a-tooltip :title="$t('delete')">
import ValueTypeMapIcon from '../CMDBValueTypeMapIcon' <a class="operation" @click="handleDeleteRule(item)"><ops-icon type="icon-xianxing-delete"/></a>
</a-tooltip>
export default { <a-tooltip :title="$t('cmdbFilterComp.addHere')" :needAddHere="needAddHere">
name: 'Expression', <a class="operation" @click="handleAddRuleAt(item)"><a-icon type="plus-circle"/></a>
components: { ValueTypeMapIcon }, </a-tooltip>
model: { </a-space>
prop: 'value', <div class="table-filter-add">
event: 'change', <a @click="handleAddRule">+ {{ $t('new') }}</a>
}, </div>
props: { </div>
value: { </template>
type: Array,
default: () => [], <script>
}, import _ from 'lodash'
canSearchPreferenceAttrList: { import { v4 as uuidv4 } from 'uuid'
type: Array, import { ruleTypeList, expList, advancedExpList, compareTypeList } from './constants'
required: true, import ValueTypeMapIcon from '../CMDBValueTypeMapIcon'
default: () => [],
}, export default {
}, name: 'Expression',
data() { components: { ValueTypeMapIcon },
return { model: {
ruleTypeList, prop: 'value',
expList, event: 'change',
advancedExpList, },
compareTypeList, props: {
} value: {
}, type: Array,
computed: { default: () => [],
ruleList: { },
get() { canSearchPreferenceAttrList: {
return this.value type: Array,
}, required: true,
set(val) { default: () => [],
this.$emit('change', val) },
return val needAddHere: {
}, type: Boolean,
}, default: false,
}, },
methods: { },
getExpListByProperty(property) { data() {
if (property) { return {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) compareTypeList,
if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) { }
return [ },
{ value: 'is', label: '等于' }, computed: {
{ value: '~is', label: '不等于' }, ruleList: {
{ value: '~value', label: '为空' }, // 为空的定义有点绕 get() {
{ value: 'value', label: '不为空' }, return this.value
] },
} set(val) {
return this.expList this.$emit('change', val)
} return val
return this.expList },
}, },
isChoiceByProperty(property) { ruleTypeList() {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) return ruleTypeList()
if (_find) { },
return _find.is_choice expList() {
} return expList()
return false },
}, advancedExpList() {
handleAddRule() { return advancedExpList()
this.ruleList.push({ },
id: uuidv4(), },
type: 'and', methods: {
property: this.canSearchPreferenceAttrList[0]?.name, getExpListByProperty(property) {
exp: 'is', if (property) {
value: null, const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
}) if (_find && ['0', '1', '3', '4', '5'].includes(_find.value_type)) {
this.$emit('change', this.ruleList) return [
}, { value: 'is', label: this.$t('cmdbFilterComp.is') },
handleCopyRule(item) { { value: '~is', label: this.$t('cmdbFilterComp.~is') },
this.ruleList.push({ ...item, id: uuidv4() }) { value: '~value', label: this.$t('cmdbFilterComp.~value') }, // 为空的定义有点绕
this.$emit('change', this.ruleList) { value: 'value', label: this.$t('cmdbFilterComp.value') },
}, ]
handleDeleteRule(item) { }
const idx = this.ruleList.findIndex((r) => r.id === item.id) return this.expList
if (idx > -1) { }
this.ruleList.splice(idx, 1) return this.expList
} },
this.$emit('change', this.ruleList) isChoiceByProperty(property) {
}, const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
getChoiceValueByProperty(property) { if (_find) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property) return _find.is_choice
if (_find) { }
return _find.choice_value return false
} },
return [] handleAddRule() {
}, this.ruleList.push({
handleChangeExp({ value }, item, index) { id: uuidv4(),
const _ruleList = _.cloneDeep(this.ruleList) type: 'and',
if (value === 'range') { property: this.canSearchPreferenceAttrList[0]?.name,
_ruleList[index] = { exp: 'is',
..._ruleList[index], value: null,
min: '', })
max: '', this.$emit('change', this.ruleList)
exp: value, },
} handleCopyRule(item) {
} else if (value === 'compare') { this.ruleList.push({ ...item, id: uuidv4() })
_ruleList[index] = { this.$emit('change', this.ruleList)
..._ruleList[index], },
compareType: '1', handleDeleteRule(item) {
exp: value, const idx = this.ruleList.findIndex((r) => r.id === item.id)
} if (idx > -1) {
} else { this.ruleList.splice(idx, 1)
_ruleList[index] = { }
..._ruleList[index], this.$emit('change', this.ruleList)
exp: value, },
} handleAddRuleAt(item) {
} const idx = this.ruleList.findIndex((r) => r.id === item.id)
this.ruleList = _ruleList if (idx > -1) {
this.$emit('change', this.ruleList) this.ruleList.splice(idx, 0, {
}, id: uuidv4(),
}, type: 'and',
} property: this.canSearchPreferenceAttrList[0]?.name,
</script> exp: 'is',
value: null,
<style></style> })
}
this.$emit('change', this.ruleList)
},
getChoiceValueByProperty(property) {
const _find = this.canSearchPreferenceAttrList.find((item) => item.name === property)
if (_find) {
return _find.choice_value
}
return []
},
handleChangeExp({ value }, item, index) {
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></style>

View File

@ -1,290 +1,296 @@
<template> <template>
<div> <div>
<a-popover <a-popover
v-if="isDropdown" v-if="isDropdown"
v-model="visible" v-model="visible"
trigger="click" trigger="click"
:placement="placement" :placement="placement"
overlayClassName="table-filter" overlayClassName="table-filter"
@visibleChange="visibleChange" @visibleChange="visibleChange"
> >
<slot name="popover_item"> <slot name="popover_item">
<a-button type="primary" ghost>条件过滤<a-icon type="filter"/></a-button> <a-button type="primary" ghost>{{ $t('cmdbFilterComp.conditionFilter') }}<a-icon type="filter"/></a-button>
</slot> </slot>
<template slot="content"> <template slot="content">
<Expression <Expression
v-model="ruleList" :needAddHere="needAddHere"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)" v-model="ruleList"
/> :canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
<a-divider :style="{ margin: '10px 0' }" /> />
<div style="width:534px"> <a-divider :style="{ margin: '10px 0' }" />
<a-space :style="{ display: 'flex', justifyContent: 'flex-end' }"> <div style="width:554px">
<a-button type="primary" size="small" @click="handleSubmit">确定</a-button> <a-space :style="{ display: 'flex', justifyContent: 'flex-end' }">
<a-button size="small" @click="handleClear">清空</a-button> <a-button type="primary" size="small" @click="handleSubmit">{{ $t('confirm') }}</a-button>
</a-space> <a-button size="small" @click="handleClear">{{ $t('clear') }}</a-button>
</div> </a-space>
</template> </div>
</a-popover> </template>
<Expression </a-popover>
v-else <Expression
v-model="ruleList" :needAddHere="needAddHere"
:canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)" v-else
/> v-model="ruleList"
</div> :canSearchPreferenceAttrList="canSearchPreferenceAttrList.filter((attr) => !attr.is_password)"
</template> />
</div>
<script> </template>
import { v4 as uuidv4 } from 'uuid'
import Expression from './expression.vue' <script>
import { advancedExpList, compareTypeList } from './constants' import { v4 as uuidv4 } from 'uuid'
import Expression from './expression.vue'
export default { import { advancedExpList, compareTypeList } from './constants'
name: 'FilterComp',
components: { Expression }, export default {
props: { name: 'FilterComp',
canSearchPreferenceAttrList: { components: { Expression },
type: Array, props: {
required: true, canSearchPreferenceAttrList: {
default: () => [], type: Array,
}, required: true,
expression: { default: () => [],
type: String, },
default: '', expression: {
}, type: String,
regQ: { default: '',
type: String, },
default: '(?<=q=).+(?=&)|(?<=q=).+$', regQ: {
}, type: String,
placement: { default: '(?<=q=).+(?=&)|(?<=q=).+$',
type: String, },
default: 'bottomRight', placement: {
}, type: String,
isDropdown: { default: 'bottomRight',
type: Boolean, },
default: true, isDropdown: {
}, type: Boolean,
}, default: true,
data() { },
return { needAddHere: {
advancedExpList, type: Boolean,
compareTypeList, default: false,
visible: false, },
ruleList: [], },
filterExp: '', data() {
} return {
}, advancedExpList,
compareTypeList,
methods: { visible: false,
visibleChange(open, isInitOne = true) { ruleList: [],
// isInitOne 初始化exp为空时ruleList是否默认给一条 filterExp: '',
// const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g }
const exp = this.expression.match(new RegExp(this.regQ, 'g')) },
? this.expression.match(new RegExp(this.regQ, 'g'))[0]
: null methods: {
if (open && exp) { visibleChange(open, isInitOne = true) {
const expArray = exp.split(',').map((item) => { // isInitOne 初始化exp为空时ruleList是否默认给一条
let has_not = '' // const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
const key = item.split(':')[0] const exp = this.expression.match(new RegExp(this.regQ, 'g'))
const val = item ? this.expression.match(new RegExp(this.regQ, 'g'))[0]
.split(':') : null
.slice(1) if (open && exp) {
.join(':') const expArray = exp.split(',').map((item) => {
let type, property, exp, value, min, max, compareType let has_not = ''
if (key.includes('-')) { const key = item.split(':')[0]
type = 'or' const val = item
if (key.includes('~')) { .split(':')
property = key.substring(2) .slice(1)
has_not = '~' .join(':')
} else { let type, property, exp, value, min, max, compareType
property = key.substring(1) if (key.includes('-')) {
} type = 'or'
} else { if (key.includes('~')) {
type = 'and' property = key.substring(2)
if (key.includes('~')) { has_not = '~'
property = key.substring(1) } else {
has_not = '~' property = key.substring(1)
} else { }
property = key } else {
} type = 'and'
} if (key.includes('~')) {
property = key.substring(1)
const in_reg = /(?<=\().+(?=\))/g has_not = '~'
const range_reg = /(?<=\[).+(?=\])/g } else {
const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/ property = key
if (val === '*') { }
exp = has_not + 'value' }
value = ''
} else if (in_reg.test(val)) { const in_reg = /(?<=\().+(?=\))/g
exp = has_not + 'in' const range_reg = /(?<=\[).+(?=\])/g
value = val.match(in_reg)[0] const compare_reg = /(?<=>=|<=|>(?!=)|<(?!=)).+/
} else if (range_reg.test(val)) { if (val === '*') {
exp = has_not + 'range' exp = has_not + 'value'
value = val.match(range_reg)[0] value = ''
min = value.split('_TO_')[0] } else if (in_reg.test(val)) {
max = value.split('_TO_')[1] exp = has_not + 'in'
} else if (compare_reg.test(val)) { value = val.match(in_reg)[0]
exp = has_not + 'compare' } else if (range_reg.test(val)) {
value = val.match(compare_reg)[0] exp = has_not + 'range'
const _compareType = val.substring(0, val.match(compare_reg)['index']) value = val.match(range_reg)[0]
const idx = compareTypeList.findIndex((item) => item.label === _compareType) min = value.split('_TO_')[0]
compareType = compareTypeList[idx].value max = value.split('_TO_')[1]
} else if (!val.includes('*')) { } else if (compare_reg.test(val)) {
exp = has_not + 'is' exp = has_not + 'compare'
value = val value = val.match(compare_reg)[0]
} else { const _compareType = val.substring(0, val.match(compare_reg)['index'])
const resList = [ const idx = compareTypeList.findIndex((item) => item.label === _compareType)
['contain', /(?<=\*).*(?=\*)/g], compareType = compareTypeList[idx].value
['end_with', /(?<=\*).+/g], } else if (!val.includes('*')) {
['start_with', /.+(?=\*)/g], exp = has_not + 'is'
] value = val
for (let i = 0; i < 3; i++) { } else {
const reg = resList[i] const resList = [
if (reg[1].test(val)) { ['contain', /(?<=\*).*(?=\*)/g],
exp = has_not + reg[0] ['end_with', /(?<=\*).+/g],
value = val.match(reg[1])[0] ['start_with', /.+(?=\*)/g],
break ]
} for (let i = 0; i < 3; i++) {
} const reg = resList[i]
} if (reg[1].test(val)) {
return { exp = has_not + reg[0]
id: uuidv4(), value = val.match(reg[1])[0]
type, break
property, }
exp, }
value, }
min, return {
max, id: uuidv4(),
compareType, type,
} property,
}) exp,
this.ruleList = [...expArray] value,
} else if (open) { min,
const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password) max,
this.ruleList = isInitOne compareType,
? [ }
{ })
id: uuidv4(), this.ruleList = [...expArray]
type: 'and', } else if (open) {
property: const _canSearchPreferenceAttrList = this.canSearchPreferenceAttrList.filter((attr) => !attr.is_password)
_canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length this.ruleList = isInitOne
? _canSearchPreferenceAttrList[0].name ? [
: undefined, {
exp: 'is', id: uuidv4(),
value: null, type: 'and',
}, property:
] _canSearchPreferenceAttrList && _canSearchPreferenceAttrList.length
: [] ? _canSearchPreferenceAttrList[0].name
} : undefined,
}, exp: 'is',
handleClear() { value: null,
this.ruleList = [ },
{ ]
id: uuidv4(), : []
type: 'and', }
property: this.canSearchPreferenceAttrList[0].name, },
exp: 'is', handleClear() {
value: null, this.ruleList = [
}, {
] id: uuidv4(),
this.filterExp = '' type: 'and',
this.visible = false property: this.canSearchPreferenceAttrList[0].name,
this.$emit('setExpFromFilter', this.filterExp) exp: 'is',
}, value: null,
handleSubmit() { },
if (this.ruleList && this.ruleList.length) { ]
this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and this.filterExp = ''
this.filterExp = '' this.visible = false
const expList = this.ruleList.map((rule) => { this.$emit('setExpFromFilter', this.filterExp)
let singleRuleExp = '' },
let _exp = rule.exp handleSubmit() {
if (rule.type === 'or') { if (this.ruleList && this.ruleList.length) {
singleRuleExp += '-' this.ruleList[0].type = 'and' // 增删后以防万一第一个不是and
} this.filterExp = ''
if (rule.exp.includes('~')) { const expList = this.ruleList.map((rule) => {
singleRuleExp += '~' let singleRuleExp = ''
_exp = rule.exp.split('~')[1] let _exp = rule.exp
} if (rule.type === 'or') {
singleRuleExp += `${rule.property}:` singleRuleExp += '-'
if (_exp === 'is') { }
singleRuleExp += `${rule.value ?? ''}` if (rule.exp.includes('~')) {
} singleRuleExp += '~'
if (_exp === 'contain') { _exp = rule.exp.split('~')[1]
singleRuleExp += `*${rule.value ?? ''}*` }
} singleRuleExp += `${rule.property}:`
if (_exp === 'start_with') { if (_exp === 'is') {
singleRuleExp += `${rule.value ?? ''}*` singleRuleExp += `${rule.value ?? ''}`
} }
if (_exp === 'end_with') { if (_exp === 'contain') {
singleRuleExp += `*${rule.value ?? ''}` singleRuleExp += `*${rule.value ?? ''}*`
} }
if (_exp === 'value') { if (_exp === 'start_with') {
singleRuleExp += `*` singleRuleExp += `${rule.value ?? ''}*`
} }
if (_exp === 'in') { if (_exp === 'end_with') {
singleRuleExp += `(${rule.value ?? ''})` singleRuleExp += `*${rule.value ?? ''}`
} }
if (_exp === 'range') { if (_exp === 'value') {
singleRuleExp += `[${rule.min}_TO_${rule.max}]` singleRuleExp += `*`
} }
if (_exp === 'compare') { if (_exp === 'in') {
const idx = compareTypeList.findIndex((item) => item.value === rule.compareType) singleRuleExp += `(${rule.value ?? ''})`
singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}` }
} if (_exp === 'range') {
return singleRuleExp singleRuleExp += `[${rule.min}_TO_${rule.max}]`
}) }
this.filterExp = expList.join(',') if (_exp === 'compare') {
this.$emit('setExpFromFilter', this.filterExp) const idx = compareTypeList.findIndex((item) => item.value === rule.compareType)
} else { singleRuleExp += `${compareTypeList[idx].label}${rule.value ?? ''}`
this.$emit('setExpFromFilter', '') }
} return singleRuleExp
this.visible = false })
}, this.filterExp = expList.join(',')
}, this.$emit('setExpFromFilter', this.filterExp)
} } else {
</script> this.$emit('setExpFromFilter', '')
}
<style lang="less" scoped> this.visible = false
.table-filter { },
.table-filter-add { },
margin-top: 10px; }
& > a { </script>
padding: 2px 8px;
&:hover { <style lang="less" scoped>
background-color: #f0faff; .table-filter {
border-radius: 5px; .table-filter-add {
} margin-top: 10px;
} & > a {
} padding: 2px 8px;
.table-filter-extra-icon { &:hover {
padding: 0px 2px; background-color: #f0faff;
&:hover { border-radius: 5px;
display: inline-block; }
border-radius: 5px; }
background-color: #f0faff; }
} .table-filter-extra-icon {
} padding: 0px 2px;
} &:hover {
</style> display: inline-block;
border-radius: 5px;
<style lang="less"> background-color: #f0faff;
.table-filter-extra-operation { }
.ant-popover-inner-content { }
padding: 3px 4px; }
.operation { </style>
cursor: pointer;
width: 90px; <style lang="less">
height: 30px; .table-filter-extra-operation {
line-height: 30px; .ant-popover-inner-content {
padding: 3px 4px; padding: 3px 4px;
border-radius: 5px; .operation {
transition: all 0.3s; cursor: pointer;
&:hover { width: 90px;
background-color: #f0faff; height: 30px;
} line-height: 30px;
> .anticon { padding: 3px 4px;
margin-right: 10px; border-radius: 5px;
} transition: all 0.3s;
} &:hover {
} background-color: #f0faff;
} }
</style> > .anticon {
margin-right: 10px;
}
}
}
}
</style>

View File

@ -1,8 +1,10 @@
export const iconTypeList = [ import i18n from '@/lang'
export const iconTypeList = () => [
// { value: '0', label: '常用' }, // { value: '0', label: '常用' },
{ value: '1', label: '线性' }, { value: '1', label: i18n.t('customIconSelect.outlined') },
{ value: '2', label: '实底' }, { value: '2', label: i18n.t('customIconSelect.filled') },
{ value: '3', label: '多色' } { value: '3', label: i18n.t('customIconSelect.multicolor') }
] ]
export const commonIconList = ['changyong-ubuntu', export const commonIconList = ['changyong-ubuntu',

View File

@ -16,7 +16,7 @@
{{ item.label }} {{ item.label }}
</div> </div>
<div :class="`${currentIconType === '4' ? 'selected' : ''}`" @click="handleChangeIconType('4')"> <div :class="`${currentIconType === '4' ? 'selected' : ''}`" @click="handleChangeIconType('4')">
自定义 {{ this.$t('customIconSelect.custom') }}
</div> </div>
<a-upload <a-upload
slot="description" slot="description"
@ -26,7 +26,7 @@
accept=".svg,.png,.jpg,.jpeg" accept=".svg,.png,.jpg,.jpeg"
v-if="currentIconType === '4'" v-if="currentIconType === '4'"
> >
<a-button icon="plus" size="small" type="primary">添加</a-button> <a-button icon="plus" size="small" type="primary">{{ $t('add') }}</a-button>
</a-upload> </a-upload>
</div> </div>
<div class="custom-icon-select-popover-content"> <div class="custom-icon-select-popover-content">
@ -55,11 +55,11 @@
@click="clickCustomIcon(icon)" @click="clickCustomIcon(icon)"
> >
<div class="custom-icon-select-popover-content-img-box"> <div class="custom-icon-select-popover-content-img-box">
<img :src="`/api/common-setting/v1/file/${icon.data.url}`" /> <img v-if="icon.data && icon.data.url" :src="`/api/common-setting/v1/file/${icon.data.url}`" />
<a-popconfirm <a-popconfirm
overlayClassName="custom-icon-select-confirm-popover" overlayClassName="custom-icon-select-confirm-popover"
:getPopupContainer="(trigger) => trigger.parentNode" :getPopupContainer="(trigger) => trigger.parentNode"
title="确认删除?" :title="$t('confirmDelete')"
@confirm="(e) => deleteIcon(e, icon)" @confirm="(e) => deleteIcon(e, icon)"
@cancel=" @cancel="
(e) => { (e) => {
@ -102,27 +102,27 @@
</template> </template>
<a-form class="custom-icon-select-form" :form="form" v-show="currentIconType === '4' && formVisible"> <a-form class="custom-icon-select-form" :form="form" v-show="currentIconType === '4' && formVisible">
<a-form-item <a-form-item
label="名称" :label="$t('name')"
:labelCol="{ span: 4 }" :labelCol="{ span: 4 }"
:wrapperCol="{ span: 16 }" :wrapperCol="{ span: 16 }"
><a-input ><a-input
v-decorator="['name', { rules: [{ required: true, message: '请输入名称' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('placeholder1') }] }]"
/></a-form-item> /></a-form-item>
<a-form-item label="预览" :labelCol="{ span: 4 }"> <a-form-item :label="$t('customIconSelect.preview')" :labelCol="{ span: 4 }">
<div class="custom-icon-select-form-img"> <div class="custom-icon-select-form-img">
<img :src="formImg" /> <img :src="formImg" />
</div> </div>
</a-form-item> </a-form-item>
<a-form-item label=" " :colon="false" :labelCol="{ span: 16 }"> <a-form-item label=" " :colon="false" :labelCol="{ span: 16 }">
<a-space> <a-space>
<a-button size="small" @click="handleCancel">取消</a-button> <a-button size="small" @click="handleCancel">{{ $t('cancel') }}</a-button>
<a-button size="small" type="primary" @click="handleOk">确定</a-button> <a-button size="small" type="primary" @click="handleOk">{{ $t('confirm') }}</a-button>
</a-space> </a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>
<div class="custom-icon-select-block" id="custom-icon-select-block" @click="showSelect"> <div class="custom-icon-select-block" :id="`custom-icon-select-block-${uuid}`" @click="showSelect">
<img v-if="value.id && value.url" :src="`/api/common-setting/v1/file/${value.url}`" /> <img v-if="value.id && value.url" :src="`/api/common-setting/v1/file/${value.url}`" />
<ops-icon <ops-icon
v-else v-else
@ -134,6 +134,7 @@
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid'
import { ColorPicker } from 'element-ui' import { ColorPicker } from 'element-ui'
import { import {
iconTypeList, iconTypeList,
@ -166,7 +167,6 @@ export default {
data() { data() {
return { return {
form: this.$form.createForm(this), form: this.$form.createForm(this),
iconTypeList,
commonIconList, commonIconList,
linearIconList, linearIconList,
fillIconList, fillIconList,
@ -177,6 +177,7 @@ export default {
formVisible: false, formVisible: false,
formImg: null, formImg: null,
file: null, file: null,
uuid: uuidv4(),
} }
}, },
computed: { computed: {
@ -200,6 +201,9 @@ export default {
const splitFileName = this.file.name.split('.') const splitFileName = this.file.name.split('.')
return splitFileName.splice(0, splitFileName.length - 1).join('') return splitFileName.splice(0, splitFileName.length - 1).join('')
}, },
iconTypeList() {
return iconTypeList()
},
}, },
mounted() { mounted() {
document.addEventListener('click', this.eventListener) document.addEventListener('click', this.eventListener)
@ -217,7 +221,7 @@ export default {
eventListener(e) { eventListener(e) {
if (this.visible) { if (this.visible) {
const dom = document.getElementById(`custom-icon-select-popover`) const dom = document.getElementById(`custom-icon-select-popover`)
const dom_icon = document.getElementById(`custom-icon-select-block`) const dom_icon = document.getElementById(`custom-icon-select-block-${this.uuid}`)
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (dom) { if (dom) {
@ -249,12 +253,11 @@ export default {
color: '', color: '',
}) })
} else { } else {
this.$emit('change', { name: icon.data.name, id: icon.id, url: icon.data.url }) this.$emit('change', { name: icon.data.name, id: icon.id, url: icon?.data?.url })
} }
}, },
showSelect() { showSelect() {
this.visible = true this.visible = true
console.log(this.value)
if (!this.value.name) { if (!this.value.name) {
this.currentIconType = '3' this.currentIconType = '3'
return return
@ -278,7 +281,7 @@ export default {
beforeUpload(file) { beforeUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2 const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) { if (!isLt2M) {
this.$message.error('图片大小不可超过2MB!') this.$message.error(this.$t('customIconSelect.sizeLimit'))
return false return false
} }
@ -306,7 +309,7 @@ export default {
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
addFileData('ops-custom-icon', { data: { name: values.name, url: res.file_name } }).then(() => { addFileData('ops-custom-icon', { data: { name: values.name, url: res.file_name } }).then(() => {
this.$message.success('上传成功!') this.$message.success(this.$t('uploadSuccess'))
this.handleCancel() this.handleCancel()
this.getFileData() this.getFileData()
}) })
@ -318,7 +321,7 @@ export default {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
deleteFileData('ops-custom-icon', icon.id).then(() => { deleteFileData('ops-custom-icon', icon.id).then(() => {
this.$message.success('删除成功!') this.$message.success(this.$t('deleteSuccess'))
this.handleCancel() this.handleCancel()
this.getFileData() this.getFileData()
}) })

View File

@ -6,17 +6,17 @@
:flat="true" :flat="true"
:multiple="true" :multiple="true"
:options="employeeTreeSelectOption" :options="employeeTreeSelectOption"
placeholder="请输入搜索内容" :placeholder="$t('placeholderSearch')"
v-model="treeValue" v-model="treeValue"
:max-height="height - 50" :max-height="height - 50"
noChildrenText="" noChildrenText=""
noOptionsText="" noOptionsText=""
:clearable="false" :clearable="false"
:always-open="true" :always-open="true"
:default-expand-level="1" :default-expand-level="showInternship ? 0 : 1"
:class="{ 'employee-transfer': true, 'employee-transfer-has-input': !!inputValue }" :class="{ 'employee-transfer': true, 'employee-transfer-has-input': !!inputValue }"
@search-change="changeInputValue" @search-change="changeInputValue"
noResultsText="暂无数据" :noResultsText="$t('noData')"
openDirection="below" openDirection="below"
> >
</treeselect> </treeselect>
@ -85,6 +85,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
showInternship: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -99,13 +103,22 @@ export default {
}, },
computed: { computed: {
employeeTreeSelectOption() { employeeTreeSelectOption() {
return formatOption( const formatOptions = formatOption(
this.allTreeDepAndEmp, this.allTreeDepAndEmp,
2, 2,
this.isDisabledAllCompany, this.isDisabledAllCompany,
this.uniqueKey || 'department_id', this.uniqueKey || 'department_id',
this.uniqueKey || 'employee_id' this.uniqueKey || 'employee_id'
) )
if (this.showInternship) {
formatOptions.push(
...[
{ id: -2, label: '全职' },
{ id: -3, label: '实习生' },
]
)
}
return formatOptions
}, },
allTreeDepAndEmp() { allTreeDepAndEmp() {
if (this.getDataBySelf) { if (this.getDataBySelf) {
@ -148,11 +161,15 @@ export default {
const department = [] const department = []
const user = [] const user = []
this.rightData.forEach((item) => { this.rightData.forEach((item) => {
const _split = item.split('-') if (item === -2 || item === -3) {
if (_split[0] === 'department') { department.push(item)
department.push(Number(_split[1]))
} else { } else {
user.push(Number(_split[1])) const _split = item.split('-')
if (_split[0] === 'department') {
department.push(Number(_split[1]))
} else {
user.push(Number(_split[1]))
}
} }
}) })
const _idx = department.findIndex((item) => item === 0) const _idx = department.findIndex((item) => item === 0)
@ -191,6 +208,12 @@ export default {
} }
}, },
getLabel(id) { getLabel(id) {
if (id === -2) {
return '全职'
}
if (id === -3) {
return '实习生'
}
const _split = id.split('-') const _split = id.split('-')
const type = _split[0] const type = _split[0]
const _id = Number(_split[1]) const _id = Number(_split[1])

View File

@ -161,6 +161,9 @@ export default {
} }
return null return null
}, },
renderI18n(title) {
return this.$t(`${title}`)
},
renderMenuItem(menu) { renderMenuItem(menu) {
const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/' const isShowDot = menu.path.substr(0, 22) === '/cmdb/instances/types/'
const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/' const isShowGrant = menu.path.substr(0, 20) === '/cmdb/relationviews/'
@ -183,7 +186,7 @@ export default {
<tag {...{ props, attrs }}> <tag {...{ props, attrs }}>
{this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })} {this.renderIcon({ icon: menu.meta.icon, customIcon: menu.meta.customIcon, name: menu.meta.name, typeId: menu.meta.typeId, routeName: menu.name, selectedIcon: menu.meta.selectedIcon, })}
<span> <span>
<span class={menu.meta.title.length > 10 ? 'scroll' : ''}>{menu.meta.title}</span> <span class={this.renderI18n(menu.meta.title).length > 10 ? 'scroll' : ''}>{this.renderI18n(menu.meta.title)}</span>
{isShowDot && {isShowDot &&
<a-popover <a-popover
overlayClassName="custom-menu-extra-submenu" overlayClassName="custom-menu-extra-submenu"
@ -217,7 +220,7 @@ export default {
<SubMenu {...{ key: menu.path }}> <SubMenu {...{ key: menu.path }}>
<span slot="title"> <span slot="title">
{this.renderIcon({ icon: menu.meta.icon, selectedIcon: menu.meta.selectedIcon, routeName: menu.name })} {this.renderIcon({ icon: menu.meta.icon, selectedIcon: menu.meta.selectedIcon, routeName: menu.name })}
<span>{menu.meta.title}</span> <span>{this.renderI18n(menu.meta.title)}</span>
</span> </span>
{itemArr} {itemArr}
</SubMenu> </SubMenu>

View File

@ -3,9 +3,9 @@
<slot></slot> <slot></slot>
<template #empty> <template #empty>
<slot name="empty"> <slot name="empty">
<div> <div :style="{ paddingTop: '10px' }">
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px', height: '90px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</slot> </slot>
</template> </template>

View File

@ -3,12 +3,12 @@
<a-switch <a-switch
class="role-transfer-switch" class="role-transfer-switch"
v-model="isUserRole" v-model="isUserRole"
checked-children="用户" :checked-children="$t('user')"
un-checked-children="虚拟" :un-checked-children="$t('visual')"
@change="loadRoles" @change="loadRoles"
/> />
<div class="role-transfer-left"> <div class="role-transfer-left">
<a-input placeholder="请输入搜索内容" v-model="searchValue" /> <a-input :placeholder="$t('placeholderSearch')" v-model="searchValue" />
<div v-for="item in filterAllRoles" :key="item.id" @click="handleSelectedLeft(item.id)"> <div v-for="item in filterAllRoles" :key="item.id" @click="handleSelectedLeft(item.id)">
<a-checkbox :checked="selectedLeft.includes(item.id)" /> <a-checkbox :checked="selectedLeft.includes(item.id)" />
<div :title="item.name" class="role-transfer-left-role">{{ item.name }}</div> <div :title="item.name" class="role-transfer-left-role">{{ item.name }}</div>

View File

@ -10,9 +10,10 @@
> >
<a-icon type="setting" /> <a-icon type="setting" />
</span> </span>
<span class="locale" @click="changeLang">{{ locale === 'zh' ? 'English' : '中文' }}</span>
<a-popover <a-popover
trigger="click" trigger="click"
:overlayStyle="{ width: '120px' }" :overlayStyle="{ width: '150px' }"
placement="bottomRight" placement="bottomRight"
overlayClassName="custom-user" overlayClassName="custom-user"
> >
@ -20,12 +21,12 @@
<router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }"> <router-link :to="{ name: 'setting_person' }" :style="{ color: '#000000a6' }">
<div class="custom-user-item"> <div class="custom-user-item">
<a-icon type="user" :style="{ marginRight: '10px' }" /> <a-icon type="user" :style="{ marginRight: '10px' }" />
<span>个人中心</span> <span>{{ $t('topMenu.personalCenter') }}</span>
</div> </div>
</router-link> </router-link>
<div @click="handleLogout" class="custom-user-item"> <div @click="handleLogout" class="custom-user-item">
<a-icon type="logout" :style="{ marginRight: '10px' }" /> <a-icon type="logout" :style="{ marginRight: '10px' }" />
<span>退出登录</span> <span>{{ $t('topMenu.logout') }}</span>
</div> </div>
</template> </template>
<span class="action ant-dropdown-link user-dropdown-menu"> <span class="action ant-dropdown-link user-dropdown-menu">
@ -44,8 +45,9 @@
</template> </template>
<script> <script>
import { mapState, mapActions, mapGetters, mapMutations } from 'vuex'
import DocumentLink from './DocumentLink.vue' import DocumentLink from './DocumentLink.vue'
import { mapState, mapActions, mapGetters } from 'vuex' import { setDocumentTitle, domTitle } from '@/utils/domUtil'
export default { export default {
name: 'UserMenu', name: 'UserMenu',
@ -53,21 +55,24 @@ export default {
DocumentLink, DocumentLink,
}, },
computed: { computed: {
...mapState(['user']), ...mapState(['user', 'locale']),
hasBackendPermission() { hasBackendPermission() {
return this.user?.roles?.permissions.includes('acl_admin', 'backend_admin') || false return this.user?.detailPermissions?.backend?.length
}, },
}, },
methods: { methods: {
...mapActions(['Logout']), ...mapActions(['Logout']),
...mapGetters(['nickname', 'avatar']), ...mapGetters(['nickname', 'avatar']),
...mapMutations(['SET_LOCALE']),
handleLogout() { handleLogout() {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '提示', title: '提示',
content: '确认注销登录 ?', content: '真的要注销登录吗 ?',
onOk() { onOk() {
// localStorage.removeItem('ops_cityps_currentId')
localStorage.clear()
return that.Logout() return that.Logout()
}, },
onCancel() {}, onCancel() {},
@ -76,9 +81,22 @@ export default {
handleClick() { handleClick() {
this.$router.push('/setting') this.$router.push('/setting')
}, },
changeLang() {
if (this.locale === 'zh') {
this.SET_LOCALE('en')
this.$i18n.locale = 'en'
} else {
this.SET_LOCALE('zh')
this.$i18n.locale = 'zh'
}
this.$nextTick(() => {
setDocumentTitle(`${this.$t(this.$route.meta.title)} - ${domTitle}`)
})
},
}, },
} }
</script> </script>
<style lang="less"> <style lang="less">
@import '~@/style/static.less'; @import '~@/style/static.less';
.color { .color {
@ -98,4 +116,11 @@ export default {
color: #000000a6; color: #000000a6;
} }
} }
.locale {
cursor: pointer;
&:hover {
color: #custom_colors[color_1];
}
}
</style> </style>

View File

@ -38,11 +38,18 @@ import CardTitle from '@/components/CardTitle'
import ElementUI from 'element-ui' import ElementUI from 'element-ui'
import Treeselect from '@riophae/vue-treeselect' import Treeselect from '@riophae/vue-treeselect'
import OpsTable from '@/components/OpsTable' import OpsTable from '@/components/OpsTable'
import VueI18n from 'vue-i18n'
import i18n from '@/lang'
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.prototype.$bus = EventBus Vue.prototype.$bus = EventBus
VXETable.setup({
i18n: (key, args) => i18n.t(key, args)
})
Vue.use(VXETable) Vue.use(VXETable)
VXETable.use(VXETablePluginExportXLSX) VXETable.use(VXETablePluginExportXLSX)
Vue.use(VueI18n)
Vue.config.productionTip = false Vue.config.productionTip = false
@ -75,4 +82,3 @@ Vue.component('CustomRadio', CustomRadio)
Vue.component('CardTitle', CardTitle) Vue.component('CardTitle', CardTitle)
Vue.component('Treeselect', Treeselect) Vue.component('Treeselect', Treeselect)
Vue.component('OpsTable', OpsTable) Vue.component('OpsTable', OpsTable)

View File

@ -7,6 +7,7 @@ import NProgress from 'nprogress'
import 'nprogress/nprogress.css' import 'nprogress/nprogress.css'
import { setDocumentTitle, domTitle } from '@/utils/domUtil' import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN } from './store/global/mutation-types' import { ACCESS_TOKEN } from './store/global/mutation-types'
import i18n from '@/lang'
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
@ -17,7 +18,7 @@ const whitePath = ['/user/login', '/user/logout', '/user/register', '/api/sso/lo
// 登录页面处理处理 是否使用单点登录 // 登录页面处理处理 是否使用单点登录
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start() // start progress bar NProgress.start() // start progress bar
to.meta && (!!to.meta.title && setDocumentTitle(`${to.meta.title} - ${domTitle}`)) to.meta && (!!to.meta.title && setDocumentTitle(`${i18n.t(to.meta.title)} - ${domTitle}`))
const authed = store.state.authed const authed = store.state.authed
const auth_type = localStorage.getItem('ops_auth_type') const auth_type = localStorage.getItem('ops_auth_type')

140
cmdb-ui/src/lang/en.js Normal file
View File

@ -0,0 +1,140 @@
import cmdb_en from '@/modules/cmdb/lang/en.js'
import cs_en from '../views/setting/lang/en.js'
import acl_en from '@/modules/acl/lang/en.js'
export default {
screen: 'Big Screen',
dashboard: 'Dashboard',
admin: 'Admin',
user: 'User',
role: 'Role',
operation: 'Operation',
login: 'Login',
refresh: 'Refresh',
cancel: 'Cancel',
confirm: 'Confirm',
create: 'Create',
edit: 'Edit',
deleting: 'Deleting',
deletingTip: 'Deleting, total of {total}, {successNum} succeeded, {errorNum} failed',
grant: 'Grant',
login_at: 'Login At',
logout_at: 'Logout At',
createSuccess: 'Create Success',
editSuccess: 'edit Success',
warning: 'Warning',
export: 'Export',
placeholderSearch: 'Please Search',
success: 'Success',
fail: 'Fail',
browser: 'Browser',
status: 'Status',
type: 'Type',
description: 'Description',
new: 'New',
add: 'Add',
define: 'Define',
update: 'Update',
clear: 'Clear',
delete: 'Delete',
copy: 'Copy',
created_at: 'Created At',
updated_at: 'Updated At',
placeholder1: 'Please Input',
placeholder2: 'Please Select',
confirmDelete: 'Confirm delete?',
confirmDelete2: 'Confirm delete [{name}]?',
query: 'Query',
search: 'Search',
hide: 'Hide',
expand: 'Expand',
save: 'Save',
submit: 'Submit',
upload: 'Import',
download: 'Export',
name: 'Name',
alias: 'Alias',
desc: 'Description',
other: 'Other',
icon: 'Icon',
addSuccess: 'Added successfully',
uploadSuccess: 'Import successfully',
saveSuccess: 'Save successfully',
copySuccess: 'Copy successfully',
updateSuccess: 'Updated successfully',
deleteSuccess: 'Deleted successfully',
operateSuccess: 'The operation was successful',
noPermission: 'No Permission',
noData: 'No Data',
seconds: 'Seconds',
createdAt: 'Created At',
updatedAt: 'Updated At',
deletedAt: 'Deleted At',
required: 'required',
email: 'Email',
wechat: 'Wechat',
dingding: 'DingTalk',
feishu: 'Feishu',
bot: 'Robot',
checkAll: 'Select All',
loading: 'Loading...',
view: 'View',
reset: 'Reset',
yes: 'Yes',
no: 'No',
all: 'All',
selectRows: 'Selected: {rows} items',
itemsPerPage: '/page',
'星期一': 'Monday',
'星期二': 'Tuesday',
'星期三': 'Wednesday',
'星期四': 'Thursday',
'星期五': 'Friday',
'星期六': 'Saturday',
'星期日': 'Sunday',
hour: 'hour',
'items/page': '{items} items/page',
max: 'Max',
min: 'Min',
visual: 'Visual',
pagination: {
total: '{range0}-{range1} of {total} items'
},
topMenu: {
personalCenter: 'Personal Center',
logout: 'logout',
},
cmdbFilterComp: {
conditionFilter: 'Conditional filtering',
and: 'and',
or: 'or',
is: 'equal',
'~is': 'not equal',
contain: 'contain',
'~contain': 'not contain',
start_with: 'start_with',
'~start_with': 'not start_with',
end_with: 'end_with',
'~end_with': 'not end_with',
'~value': 'null',
value: 'not null',
in: 'in',
'~in': 'not in',
range: 'range',
'~range': 'out of range',
compare: 'compare',
addHere: 'Add Here',
split: 'split by {separator}'
},
customIconSelect: {
outlined: 'Outlined',
filled: 'Filled',
multicolor: 'Multicolor',
custom: 'Custom',
preview: 'Preview',
sizeLimit: 'The image size cannot exceed 2MB!'
},
cmdb: cmdb_en,
cs: cs_en,
acl: acl_en,
}

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

@ -0,0 +1,18 @@
import VueI18n from 'vue-i18n'
import zh from './zh'
import en from './en'
import Vue from 'vue'
import zhCN from 'vxe-table/lib/locale/lang/zh-CN'
import enUS from 'vxe-table/lib/locale/lang/en-US'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'zh', // 初始化中文
messages: {
'zh': { ...zh, ...zhCN },
'en': { ...en, ...enUS },
},
silentTranslationWarn: true
})
export default i18n

140
cmdb-ui/src/lang/zh.js Normal file
View File

@ -0,0 +1,140 @@
import cmdb_zh from '@/modules/cmdb/lang/zh.js'
import cs_zh from '../views/setting/lang/zh.js'
import acl_zh from '@/modules/acl/lang/zh.js'
export default {
screen: '大屏',
dashboard: '仪表盘',
admin: '管理员',
user: '用户',
role: '角色',
operation: '操作',
login: '登录',
refresh: '刷新',
cancel: '取消',
confirm: '确定',
create: '创建',
edit: '编辑',
deleting: '正在删除',
deletingTip: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
grant: '授权',
login_at: '登录时间',
logout_at: '登出时间',
createSuccess: '创建成功',
editSuccess: '修改成功',
warning: '警告',
export: '导出',
placeholderSearch: '请查找',
success: '成功',
fail: '失败',
browser: '浏览器',
status: '状态',
type: '类型',
description: '描述',
new: '新增',
add: '添加',
define: '定义',
update: '修改',
clear: '清空',
delete: '删除',
copy: '复制',
created_at: '创建日期',
updated_at: '更新日期',
placeholder1: '请输入',
placeholder2: '请选择',
confirmDelete: '确认删除?',
confirmDelete2: '确认删除【{name}】?',
query: '查询',
search: '搜索',
hide: '隐藏',
expand: '展开',
save: '保存',
submit: '提交',
upload: '导入',
download: '导出',
name: '名称',
alias: '别名',
desc: '描述',
other: '其他',
icon: '图标',
addSuccess: '新增成功',
uploadSuccess: '导入成功',
saveSuccess: '保存成功',
copySuccess: '复制成功',
updateSuccess: '更新成功',
deleteSuccess: '删除成功',
operateSuccess: '操作成功',
noPermission: '权限不足',
noData: '暂无数据',
seconds: '秒',
createdAt: '创建时间',
updatedAt: '更新时间',
deletedAt: '删除时间',
required: '必须',
email: '邮件',
wechat: '企业微信',
dingding: '钉钉',
feishu: '飞书',
bot: '机器人',
checkAll: '全选',
loading: '加载中...',
view: '查看',
reset: '重置',
yes: '是',
no: '否',
all: '全部',
selectRows: '选取:{rows} 项',
itemsPerPage: '/页',
'星期一': '星期一',
'星期二': '星期二',
'星期三': '星期三',
'星期四': '星期四',
'星期五': '星期五',
'星期六': '星期六',
'星期日': '星期日',
hour: '小时',
'items/page': '{items} 条/页',
max: '最大值',
min: '最小值',
visual: '虚拟',
pagination: {
total: '当前展示 {range0}-{range1} 条数据, 共 {total} 条'
},
topMenu: {
personalCenter: '个人中心',
logout: '退出登录',
},
cmdbFilterComp: {
conditionFilter: '条件过滤',
and: '与',
or: '或',
is: '等于',
'~is': '不等于',
contain: '包含',
'~contain': '不包含',
start_with: '以...开始',
'~start_with': '不以...开始',
end_with: '以...结束',
'~end_with': '不以...结束',
'~value': '为空',
value: '不为空',
in: 'in查询',
'~in': '非in查询',
range: '范围',
'~range': '范围外',
compare: '比较',
addHere: '在此处添加',
split: '以 {separator} 分隔'
},
customIconSelect: {
outlined: '线框',
filled: '实底',
multicolor: '多色',
custom: '自定义',
preview: '预览',
sizeLimit: '图片大小不可超过2MB'
},
cmdb: cmdb_zh,
cs: cs_zh,
acl: acl_zh,
}

View File

@ -10,6 +10,7 @@ import './guard' // guard permission control
import './utils/filter' // global filter import './utils/filter' // global filter
import Setting from './config/setting' import Setting from './config/setting'
import { Icon } from 'ant-design-vue' import { Icon } from 'ant-design-vue'
import i18n from './lang'
import iconFont from '../public/iconfont/iconfont' import iconFont from '../public/iconfont/iconfont'
@ -22,6 +23,7 @@ async function start() {
const _vue = new Vue({ const _vue = new Vue({
router, router,
store, store,
i18n,
created: bootstrap, created: bootstrap,
render: h => h(App) render: h => h(App)
}).$mount('#app') }).$mount('#app')

View File

@ -0,0 +1,125 @@
const acl_en = {
date: 'Date',
operator: 'Operator',
resource: 'Resource',
resourceType: 'Resource Type',
addResourceType: 'Add Resource Type',
app: 'App',
operateTime: 'Operate Time',
permission: 'Permission',
permission_placeholder: 'please select permission',
permissionList: 'Permission List',
summaryPermissions: 'Summary of permissions',
source: 'Source',
username: 'Username',
username_placeholder: 'please input username',
userList: 'User List',
groupUser: 'Group User',
addUser: 'Add User',
subordinateUsers: 'Subordinate Users',
nickname: 'Nickname',
nickname_placeholder: 'please input nickname',
password: 'Password',
password_placeholder: 'please input password',
department: 'Department',
group: 'Group',
email: 'Email',
email_placeholder: 'please input email',
mobile: 'Mobile',
isBlock: 'Is Block',
block: 'Block',
joined_at: 'Joined At',
role: 'Role',
role_placeholder1: 'please input role',
role_placeholder2: 'please select role',
role_placeholder3: 'please select a role name, multiple choices are allowed',
allRole: 'All Roles',
visualRole: 'Virtual Role',
addVisualRole: 'Add Virtual Role',
inheritedFrom: 'Inherited from',
heir: 'Inherit Roles',
permissionChange: 'Permissions',
roleChange: 'Roles',
resourceChange: 'Resources',
resourceTypeChange: 'Resource Type',
trigger: 'Triggers',
triggerNameInput: 'Please enter trigger name',
triggerChange: 'Triggers',
roleManage: 'Roles',
userManage: 'Users',
appManage: 'Applications',
resourceManage: 'Resources',
history: 'Audits',
userSecret: 'Secrets',
none: 'none',
danger: 'Dangerous',
confirmDeleteApp: 'Are you sure you want to delete this app?',
revoke: 'Revoke',
convenient: 'Quick Grant',
group2: 'Group',
groupName: 'Group Name',
resourceName: 'Resource Name',
creator: 'Creator',
member: 'Members',
viewAuth: 'view Auth',
addTypeTips: 'There is no type information yet, please add the resource type first!',
addResource: 'Add Resource',
resourceList: 'Resource List',
confirmResetSecret: 'Are you sure you want to reset the user secrets?',
addTrigger: 'Add Trigger',
deleteTrigger: 'Delete Trigger',
applyTrigger: 'Apply Trigger',
cancelTrigger: 'Cancel Trigger',
enable: 'Enable',
disable: 'Disable',
viewMatchResult: 'View regular matching results',
confirmDeleteTrigger: 'Are you sure you want to delete this trigger?',
ruleApply: 'Apply',
triggerTip1: 'Are you sure you want to apply this trigger?',
triggerTip2: 'Cancel applying this trigger?',
appNameInput: 'Please enter an application name',
descInput: 'Please enter a description',
addApp: 'Add',
updateApp: 'Update',
cancel: 'Cancel',
typeName: 'Name',
typeNameInput: 'Please enter a type name',
resourceNameInput: 'Please enter resource name',
pressEnter: 'Press Enter to confirm filtering',
groupMember: 'Group Members:',
isGroup: 'Group?',
errorTips: 'Error message',
roleList: 'Role List',
virtual: 'Virtual',
resourceBatchTips: 'Please enter the resource name, separated by newlines',
memberManage: 'Members: ',
newResource: 'New Resource: ',
deleteResource: 'Delete Resource: ',
deleteResourceType: 'Delete Resource Type: ',
noChange: 'No change',
batchOperate: 'Batch Operations',
batchGrant: 'Batch Grant',
batchRevoke: 'Batch Revoke',
editPerm: 'Add authorization: ',
permInput: 'Please enter permission name',
resourceTypeName: 'Resource Type Name',
selectedParents: 'Optionally inherit roles',
isAppAdmin: 'is app admin',
addRole: 'Add Role',
roleRelation: 'Role Relation',
roleRelationAdd: 'Add Role Relation',
roleRelationDelete: 'Delete Role Relation',
role2: 'Role',
admin: 'Admin',
involvingRP: 'Involving resources and permissions',
startAt: 'Start Time',
endAt: 'End Time',
triggerTips1: 'Priority regular pattern (secondary wildcard)',
pleaseSelectType: 'Please select resource type',
apply: 'Apply',
mobileTips: 'Please enter the correct phone number',
remove: 'Remove',
deleteUserConfirm: 'Are you sure you want to remove this user?',
copyResource: 'Copy resource name'
}
export default acl_en

View File

@ -0,0 +1,125 @@
const acl_zh = {
date: '日期',
operator: '操作员',
resource: '资源',
resourceType: '资源类型',
addResourceType: '新增资源类型',
app: '应用',
operateTime: '操作时间',
permission: '权限',
permission_placeholder: '请选择权限',
permissionList: '权限列表',
summaryPermissions: '权限汇总',
source: '来源',
username: '用户名',
username_placeholder: '请输入用户名',
userList: '用户列表',
groupUser: '组用户',
addUser: '新增用户',
subordinateUsers: '下属用户',
nickname: '中文名',
nickname_placeholder: '请输入中文名',
password: '密码',
password_placeholder: '请输入密码',
department: '部门',
group: '小组',
email: '邮箱',
email_placeholder: '请输入邮箱',
mobile: '手机号',
isBlock: '是否锁定',
block: '锁定',
joined_at: '加入时间',
role: '角色名',
role_placeholder1: '请输入角色名',
role_placeholder2: '请选择角色名称',
role_placeholder3: '请选择角色名称,可多选',
allRole: '所有角色',
visualRole: '虚拟角色',
addVisualRole: '新增虚拟角色',
inheritedFrom: '继承自',
heir: '继承者',
permissionChange: '权限变更',
roleChange: '角色变更',
resourceChange: '资源变更',
resourceTypeChange: '资源类型变更',
trigger: '触发器',
triggerNameInput: '请输入触发器名',
triggerChange: '触发器变更',
roleManage: '角色管理',
userManage: '用户管理',
appManage: '应用管理',
resourceManage: '资源管理',
history: '操作审计',
userSecret: '用户密钥',
none: '无',
danger: '危险操作',
confirmDeleteApp: '确定要删除该App吗',
revoke: '权限回收',
convenient: '便捷授权',
group2: '组',
groupName: '资源组名',
resourceName: '资源名',
creator: '创建者',
member: '成员',
viewAuth: '查看授权',
addTypeTips: '暂无类型信息,请先添加资源类型!',
addResource: '新增资源',
resourceList: '资源列表',
confirmResetSecret: '确定重置用户密钥?',
addTrigger: '新增触发器',
deleteTrigger: '删除触发器',
applyTrigger: '应用触发器',
cancelTrigger: '取消触发器',
enable: '启用',
disable: '禁用',
viewMatchResult: '查看正则匹配结果',
confirmDeleteTrigger: '确认删除该触发器吗?',
ruleApply: '规则应用',
triggerTip1: '是否确定应用该触发器?',
triggerTip2: '是否取消应用该触发器?',
appNameInput: '请输入应用名称',
descInput: '请输入描述',
addApp: '创建应用',
updateApp: '更新应用',
cancel: '撤销',
typeName: '类型名',
typeNameInput: '请输入类型名',
resourceNameInput: '请输入资源名',
pressEnter: '按回车确认筛选',
groupMember: '组成员:',
isGroup: '是否组',
errorTips: '错误提示',
roleList: '角色列表',
virtual: '虚拟',
resourceBatchTips: '请输入资源名,换行分隔',
memberManage: '成员管理:',
newResource: '新建资源:',
deleteResource: '删除资源:',
deleteResourceType: '删除资源类型:',
noChange: '没有修改',
batchOperate: '批量操作',
batchGrant: '批量授权',
batchRevoke: '批量权限回收',
editPerm: '添加授权:',
permInput: '请输入权限名',
resourceTypeName: '资源类型名',
selectedParents: '可选择继承角色',
isAppAdmin: '是否应用管理员',
addRole: '新增角色',
roleRelation: '角色关系',
roleRelationAdd: '添加角色关系',
roleRelationDelete: '删除角色关系',
role2: '角色',
admin: '管理员',
involvingRP: '涉及资源及权限',
startAt: '开始时间',
endAt: '结束时间',
triggerTips1: '优先正则模式(次通配符)',
pleaseSelectType: '请选择资源类型',
apply: '应用',
mobileTips: '请输入正确的手机号码',
remove: '移除',
deleteUserConfirm: '是否确定要移除该用户',
copyResource: '复制资源名'
}
export default acl_zh

View File

@ -12,35 +12,35 @@ const genAppRoute = ({ name }) => {
name: `${name}_roles_acl`, name: `${name}_roles_acl`,
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/roles'), component: () => import('../views/roles'),
meta: { title: '角色管理', icon: 'team', keepAlive: true } meta: { title: 'acl.roleManage', icon: 'team', keepAlive: true }
}, },
{ {
path: `/acl/${name}/resources`, path: `/acl/${name}/resources`,
name: `${name}_resources_acl`, name: `${name}_resources_acl`,
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/resources'), component: () => import('../views/resources'),
meta: { title: '资源管理', icon: 'credit-card', keepAlive: false } meta: { title: 'acl.resourceManage', icon: 'credit-card', keepAlive: false }
}, },
{ {
path: `/acl/${name}/resource_types`, path: `/acl/${name}/resource_types`,
name: `${name}_resource_types_acl`, name: `${name}_resource_types_acl`,
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/resource_types'), component: () => import('../views/resource_types'),
meta: { title: '资源类型', icon: 'file-text', keepAlive: true } meta: { title: 'acl.resourceType', icon: 'file-text', keepAlive: true }
}, },
{ {
path: `/acl/${name}/trigger`, path: `/acl/${name}/trigger`,
name: `${name}_trigger_acl`, name: `${name}_trigger_acl`,
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/trigger'), component: () => import('../views/trigger'),
meta: { title: '触发器', icon: 'clock-circle', keepAlive: true } meta: { title: 'acl.trigger', icon: 'clock-circle', keepAlive: true }
}, },
{ {
path: `/acl/${name}/history`, path: `/acl/${name}/history`,
name: `${name}_history_acl`, name: `${name}_history_acl`,
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/history'), component: () => import('../views/history'),
meta: { title: '操作审计', icon: 'search', keepAlive: false } meta: { title: 'acl.history', icon: 'search', keepAlive: false }
} }
] ]
} }
@ -59,31 +59,31 @@ const genAclRoutes = async () => {
path: `/acl/secret_key`, path: `/acl/secret_key`,
name: 'acl_secret_key', name: 'acl_secret_key',
component: () => import('../views/secretKey'), component: () => import('../views/secretKey'),
meta: { title: '用户密钥', icon: 'key' } meta: { title: 'acl.userSecret', icon: 'key' }
}, },
{ {
path: `/acl/operate_history`, path: `/acl/operate_history`,
name: 'acl_operate_history', name: 'acl_operate_history',
component: () => import('../views/operation_history/index.vue'), component: () => import('../views/operation_history/index.vue'),
meta: { title: '操作审计', icon: 'search', permission: ['acl_admin'] }, meta: { title: 'acl.history', icon: 'search', permission: ['acl_admin'] }
}, },
{ {
path: `/acl/user`, path: `/acl/user`,
name: 'acl_user', name: 'acl_user',
component: () => import('../views/users'), component: () => import('../views/users'),
meta: { title: '用户管理', icon: 'user', permission: ['acl_admin'] } meta: { title: 'acl.userManage', icon: 'user', permission: ['acl_admin'] }
}, },
{ {
path: `/acl/roles`, path: `/acl/roles`,
name: `acl_roles`, name: `acl_roles`,
component: () => import('../views/roles'), component: () => import('../views/roles'),
meta: { title: '角色管理', icon: 'team', keepAlive: true, permission: ['acl_admin'] } meta: { title: 'acl.roleManage', icon: 'team', keepAlive: true, permission: ['acl_admin'] }
}, },
{ {
path: `/acl/apps`, path: `/acl/apps`,
name: 'acl_apps', name: 'acl_apps',
component: () => import('../views/apps'), component: () => import('../views/apps'),
meta: { title: '应用管理', icon: 'appstore', permission: ['acl_admin'] } meta: { title: 'acl.appManage', icon: 'appstore', permission: ['acl_admin'] }
} }
] ]
} }

View File

@ -11,7 +11,7 @@
:xs="24"> :xs="24">
<a-card> <a-card>
<a-card-meta :title="app.name"> <a-card-meta :title="app.name">
<div slot="description" :title="app.description || ''">{{ app.description || '' }}</div> <div slot="description" :title="app.description || ''">{{ app.description || $t('none') }}</div>
<a-avatar style="background-color: #5dc2f1" slot="avatar">{{ app.name[0].toUpperCase() }}</a-avatar> <a-avatar style="background-color: #5dc2f1" slot="avatar">{{ app.name[0].toUpperCase() }}</a-avatar>
</a-card-meta> </a-card-meta>
<template slot="actions"> <template slot="actions">
@ -65,11 +65,11 @@ export default {
handleDeleteApp(app) { handleDeleteApp(app) {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '危险操作', title: that.$t('danger'),
content: '确定要删除该App吗', content: that.$t('confirmDeleteApp'),
onOk() { onOk() {
deleteApp(app.id).then((res) => { deleteApp(app.id).then((res) => {
that.$message.success(`删除成功${app.name}`) that.$message.success(that.$t('deleteSuccess'))
that.loadApps() that.loadApps()
}) })
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="acl-history"> <div class="acl-history">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab="权限变更"> <a-tab-pane key="1" :tab="$t('acl.permissionChange')">
<permisson-history-table <permisson-history-table
v-if="isloaded" v-if="isloaded"
:allResourceTypes="allResourceTypes" :allResourceTypes="allResourceTypes"
@ -17,7 +17,7 @@
@resourceClear="resourceClear" @resourceClear="resourceClear"
></permisson-history-table> ></permisson-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="角色变更"> <a-tab-pane key="2" :tab="$t('acl.roleChange')">
<role-history-table <role-history-table
v-if="isloaded" v-if="isloaded"
:allUsers="allUsers" :allUsers="allUsers"
@ -26,7 +26,7 @@
:allUsersMap="allUsersMap" :allUsersMap="allUsersMap"
></role-history-table> ></role-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab="资源变更"> <a-tab-pane key="3" :tab="$t('acl.resourceChange')">
<resource-history-table <resource-history-table
v-if="isloaded" v-if="isloaded"
:allResources="allResources" :allResources="allResources"
@ -41,7 +41,7 @@
@resourceClear="resourceClear" @resourceClear="resourceClear"
></resource-history-table> ></resource-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab="资源类型变更"> <a-tab-pane key="4" :tab="$t('acl.resourceTypeChange')">
<resource-type-history-table <resource-type-history-table
v-if="isloaded" v-if="isloaded"
:allResourceTypes="allResourceTypes" :allResourceTypes="allResourceTypes"
@ -52,7 +52,7 @@
:allResourceTypesMap="allResourceTypesMap" :allResourceTypesMap="allResourceTypesMap"
></resource-type-history-table> ></resource-type-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab="触发器变更"> <a-tab-pane key="5" :tab="$t('acl.triggerChange')">
<trigger-history-table <trigger-history-table
v-if="isloaded" v-if="isloaded"
:allTriggers="allTriggers" :allTriggers="allTriggers"

View File

@ -1,11 +1,12 @@
<template> <template>
<CustomDrawer @close="handleClose" width="500" :title="title" :visible="visible" :closable="false"> <CustomDrawer @close="handleClose" width="500" :title="title" :visible="visible" :closable="false">
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="应用名称"> <a-form-item :label="$t('acl.app')">
<a-input v-decorator="['name', { rules: [{ required: true, message: '请输入应用名称' }] }]"> </a-input> <a-input v-decorator="['name', { rules: [{ required: true, message: $t('acl.appNameInput') }] }]"> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="描述"> <a-form-item :label="$t('desc')">
<a-input v-decorator="['description', { rules: [{ required: true, message: '请输入描述' }] }]"> </a-input> <a-input v-decorator="['description', { rules: [{ required: true, message: $t('acl.descInput') }] }]">
</a-input>
</a-form-item> </a-form-item>
<a-form-item label="AppId"> <a-form-item label="AppId">
<a-input v-decorator="['app_id', { rules: [{ required: false }] }]" :disabled="mode === 'update'"> </a-input> <a-input v-decorator="['app_id', { rules: [{ required: false }] }]" :disabled="mode === 'update'"> </a-input>
@ -19,8 +20,8 @@
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">取消</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">提交</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('submit') }}</a-button>
</div> </div>
</CustomDrawer> </CustomDrawer>
</template> </template>
@ -32,10 +33,14 @@ export default {
data() { data() {
return { return {
visible: false, visible: false,
title: '创建应用',
mode: 'create', mode: 'create',
} }
}, },
computed: {
title() {
return this.$t('acl.addApp')
},
},
beforeCreate() { beforeCreate() {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
}, },
@ -43,7 +48,7 @@ export default {
handleEdit(ele) { handleEdit(ele) {
this.visible = true this.visible = true
if (ele) { if (ele) {
this.title = '修改应用' this.title = this.$t('updateApp')
this.mode = 'update' this.mode = 'update'
console.log(ele) console.log(ele)
const { name, description } = ele const { name, description } = ele
@ -55,7 +60,7 @@ export default {
}) })
} else { } else {
this.mode = 'create' this.mode = 'create'
this.title = '创建应用' this.title = this.$t('acl.addApp')
} }
}, },
handleClose() { handleClose() {
@ -69,11 +74,11 @@ export default {
} }
if (values.id) { if (values.id) {
await updateApp(values.id, values).then((res) => { await updateApp(values.id, values).then((res) => {
this.$message.success('修改成功!') this.$message.success(this.$t('updateSuccess'))
}) })
} else { } else {
await addApp(values).then((res) => { await addApp(values).then((res) => {
this.$message.success('创建成功!') this.$message.success(this.$t('addSuccess'))
}) })
} }
this.handleClose() this.handleClose()

View File

@ -9,10 +9,10 @@
<a-dropdown class="dropdown" size="small" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled"> <a-dropdown class="dropdown" size="small" placement="topCenter" :trigger="['click']" :disabled="dropdownIsDisabled">
<a-menu slot="overlay"> <a-menu slot="overlay">
<a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)"> <a-menu-item v-for="(size,index) in pageSizes" :key="index" @click="handleItemClick(size)">
{{ size }}/ {{ size }}{{ $t('itemsPerPage') }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<a-button size="small"> {{ pageSize }}/ <a-icon type="down" /> </a-button> <a-button size="small"> {{ pageSize }}{{ $t('itemsPerPage') }}<a-icon type="down" /> </a-button>
</a-dropdown> </a-dropdown>
</a-space> </a-space>
</a-col> </a-col>

View File

@ -1,6 +1,6 @@
<template> <template>
<CustomDrawer <CustomDrawer
:title="`权限汇总: ${user.nickname}`" :title="`${$t('acl.summaryPermissions')}: ${user.nickname}`"
:visible="visible" :visible="visible"
placement="left" placement="left"
width="100%" width="100%"
@ -24,7 +24,7 @@
<vxe-table :max-height="`${windowHeight - 230}px`" :data="resources" ref="rTable"> <vxe-table :max-height="`${windowHeight - 230}px`" :data="resources" ref="rTable">
<vxe-column <vxe-column
field="name" field="name"
title="资源名" :title="$t('acl.resourceName')"
width="30%" width="30%"
:filters="[{ data: '' }]" :filters="[{ data: '' }]"
:filter-method="filterNameMethod" :filter-method="filterNameMethod"
@ -39,62 +39,17 @@
v-model="option.data" v-model="option.data"
@input="$panel.changeOption($event, !!option.data, option)" @input="$panel.changeOption($event, !!option.data, option)"
@keyup.enter="$panel.confirmFilter()" @keyup.enter="$panel.confirmFilter()"
placeholder="按回车确认筛选" :placeholder="$t('acl.pressEnter')"
/> />
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="permissions" title="权限列表" width="70%"> <vxe-column field="permissions" :title="$t('acl.permissionList')" width="70%">
<template #default="{row}"> <template #default="{row}">
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag> <a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<!-- <a-table
:columns="tableColumns"
:dataSource="resources"
:rowKey="record=>record.id"
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
showPagination="auto"
ref="rTable"
size="middle">
<div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
<a-input
v-ant-ref="c => searchInput = c"
:placeholder="` ${column.title}`"
:value="selectedKeys[0]"
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
style="width: 188px; margin-bottom: 8px; display: block;"
/>
<a-button
type="primary"
@click="() => handleSearch(selectedKeys, confirm, column)"
icon="search"
size="small"
style="width: 90px; margin-right: 8px"
>搜索</a-button>
<a-button
@click="() => handleReset(clearFilters, column)"
size="small"
style="width: 90px"
>重置</a-button>
</div>
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
<template slot="nameSearchRender" slot-scope="text">
<span v-if="columnSearchText.name">
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
<template v-else>{{ fragment }}</template>
</template>
</span>
<template v-else>{{ text }}</template>
</template>
<template slot="permissions" slot-scope="record">
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
</template>
</a-table> -->
</a-spin> </a-spin>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@ -122,33 +77,6 @@ export default {
resourceTypes: [], resourceTypes: [],
resourceTypePerms: [], resourceTypePerms: [],
resources: [], resources: [],
// tableColumns: [
// {
// title: '资源名',
// dataIndex: 'name',
// sorter: false,
// width: 150,
// // scopedSlots: {
// // customRender: 'nameSearchRender',
// // filterDropdown: 'filterDropdown',
// // filterIcon: 'filterIcon'
// // },
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
// // onFilterDropdownVisibleChange: (visible) => {
// // if (visible) {
// // setTimeout(() => {
// // this.searchInput.focus()
// // }, 0)
// // }
// // }
// },
// {
// title: '权限列表',
// dataIndex: 'permissions',
// width: 300,
// scopedSlots: { customRender: 'permissions' },
// },
// ],
columnSearchText: { columnSearchText: {
name: '', name: '',
}, },
@ -156,14 +84,14 @@ export default {
}, },
computed: { computed: {
...mapState({ ...mapState({
windowHeight: state => state.windowHeight, windowHeight: (state) => state.windowHeight,
}), }),
displayApps() { displayApps() {
const roles = this.$store.state.user.roles.permissions const roles = this.$store.state.user.roles.permissions
if (roles.includes('acl_admin')) { if (roles.includes('acl_admin')) {
return this.apps return this.apps
} }
return this.apps.filter(item => { return this.apps.filter((item) => {
if (roles.includes(`${item.name}_admin`)) { if (roles.includes(`${item.name}_admin`)) {
return true return true
} }
@ -192,7 +120,7 @@ export default {
}, },
async loadRoles(_appId) { async loadRoles(_appId) {
const res = await searchRole({ app_id: _appId, page_size: 9999, is_all: true }) const res = await searchRole({ app_id: _appId, page_size: 9999, is_all: true })
this.roles = res.roles.filter(item => item.uid) this.roles = res.roles.filter((item) => item.uid)
}, },
async handleSwitchApp(appId) { async handleSwitchApp(appId) {
this.currentAppId = appId this.currentAppId = appId
@ -218,7 +146,7 @@ export default {
}, },
async loadResource() { async loadResource() {
this.spinning = true this.spinning = true
const fil = this.roles.filter(role => role.uid === this.user.uid) const fil = this.roles.filter((role) => role.uid === this.user.uid)
if (!fil[0]) { if (!fil[0]) {
return return
} }

View File

@ -1,29 +1,33 @@
<template> <template>
<CustomDrawer <CustomDrawer
:closable="false" :closable="false"
:title="drawerTitle" :title="$t('acl.addReourceType')"
:visible="drawerVisible" :visible="drawerVisible"
@close="onClose" @close="onClose"
placement="right" placement="right"
width="30%" width="30%"
> >
<a-form :form="form" :layout="formLayout" @submit="handleSubmit"> <a-form :form="form" :layout="formLayout" @submit="handleSubmit">
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="类型名"> <a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('acl.typeName')">
<a-input <a-input
name="name" name="name"
placeholder="" placeholder=""
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('acl.resourceNameInput') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="描述"> <a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" :label="$t('desc')">
<a-textarea placeholder="请输入描述信息..." name="description" :rows="4" /> <a-textarea :placeholder="$t('acl.descInput')" name="description" :rows="4" />
</a-form-item> </a-form-item>
<a-form-item :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol" label="权限"> <a-form-item
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
:label="$t('acl.permission')"
>
<div :style="{ borderBottom: '1px solid #E9E9E9' }"> <div :style="{ borderBottom: '1px solid #E9E9E9' }">
<a-checkbox :indeterminate="indeterminate" @change="onCheckAllChange" :checked="checkAll"> <a-checkbox :indeterminate="indeterminate" @change="onCheckAllChange" :checked="checkAll">
全选 {{ $t('checkAll') }}
</a-checkbox> </a-checkbox>
</div> </div>
<br /> <br />
@ -35,8 +39,8 @@
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -49,7 +53,6 @@ export default {
name: 'ResourceForm', name: 'ResourceForm',
data() { data() {
return { return {
drawerTitle: '新增资源类型',
drawerVisible: false, drawerVisible: false,
formLayout: 'vertical', formLayout: 'vertical',
perms: ['1'], perms: ['1'],
@ -142,8 +145,8 @@ export default {
}) })
}, },
updateResourceType(id, data) { updateResourceType(id, data) {
updateResourceTypeById(id, data).then(res => { updateResourceTypeById(id, data).then((res) => {
this.$message.success(`更新成功`) this.$message.success(this.$t('updateSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
@ -151,18 +154,12 @@ export default {
}, },
createResourceType(data) { createResourceType(data) {
addResourceType(data).then(res => { addResourceType(data).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
// .catch(err => this.requestFailed(err))
}, },
// requestFailed (err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// }
}, },
watch: {}, watch: {},
props: { props: {

View File

@ -21,22 +21,22 @@
:height="`${windowHeight - windowHeightMinus}px`" :height="`${windowHeight - windowHeightMinus}px`"
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
> >
<vxe-column field="created_at" width="144px" title="操作时间"> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')">
<template #default="{ row }"> <template #default="{ row }">
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span> <span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{ <a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
operateTypeMap.get(row.operate_type) operateTypeMap.get(row.operate_type)
}}</a-tag> }}</a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="rid" title="用户"></vxe-column> <vxe-column field="rid" :title="$t('user')"></vxe-column>
<vxe-column field="resource_type_id" title="资源类型"></vxe-column> <vxe-column field="resource_type_id" :title="$t('acl.resourceType')"></vxe-column>
<vxe-column field="resources" title="资源"> <vxe-column field="resources" :title="$t('acl.resource')">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="row.resource_ids.length > 0"> <template v-if="row.resource_ids.length > 0">
<a-tooltip placement="top"> <a-tooltip placement="top">
@ -55,14 +55,14 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="权限"> <vxe-column :title="$t('acl.permission')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index"> <a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
{{ perm }} {{ perm }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -122,10 +122,6 @@ export default {
loading: true, loading: true,
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
tableData: [], tableData: [],
operateTypeMap: new Map([
['grant', '授权'],
['revoke', '撤销'],
]),
queryParams: { queryParams: {
page: 1, page: 1,
page_size: 50, page_size: 50,
@ -133,49 +129,6 @@ export default {
start: '', start: '',
end: '', end: '',
}, },
permissionTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: '用户',
is_choice: true,
name: 'rid',
value_type: '2',
choice_value: this.allRoles,
},
{
alias: '资源类型',
is_choice: true,
name: 'resource_type_id',
value_type: '2',
choice_value: this.allResourceTypes,
},
{
alias: '资源',
is_choice: true,
name: 'resource_id',
value_type: '2',
choice_value: this.allResources,
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }],
},
],
} }
}, },
async created() { async created() {
@ -198,6 +151,12 @@ export default {
}, },
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['grant', this.$t('grant')],
['revoke', this.$t('acl.cancel')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -207,6 +166,51 @@ export default {
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
permissionTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: this.$t('user'),
is_choice: true,
name: 'rid',
value_type: '2',
choice_value: this.allRoles,
},
{
alias: this.$t('acl.resourceType'),
is_choice: true,
name: 'resource_type_id',
value_type: '2',
choice_value: this.allResourceTypes,
},
{
alias: this.$t('acl.resource'),
is_choice: true,
name: 'resource_id',
value_type: '2',
choice_value: this.allResources,
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ [this.$t('grant')]: 'grant' }, { [this.$t('acl.cancel')]: 'revoke' }],
},
]
},
}, },
methods: { methods: {
// 获取数据 // 获取数据

View File

@ -1,6 +1,6 @@
<template> <template>
<CustomDrawer <CustomDrawer
title="便捷授权" :title="$t('acl.convenient')"
width="500px" width="500px"
:maskClosable="false" :maskClosable="false"
:closable="true" :closable="true"
@ -10,12 +10,12 @@
<a-form :form="form"> <a-form :form="form">
<a-form-item> <a-form-item>
<div slot="label" style="display: inline-block"> <div slot="label" style="display: inline-block">
<span>角色列表</span> <span>{{ $t('acl.roleList') }}</span>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-switch <a-switch
style="display: inline-block" style="display: inline-block"
checked-children="用户" :checked-children="$t('user')"
un-checked-children="虚拟" :un-checked-children="$t('acl.virtual')"
@change="handleRoleTypeChange" @change="handleRoleTypeChange"
v-model="roleType" v-model="roleType"
/> />
@ -23,37 +23,37 @@
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
size="small" size="small"
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]" v-decorator="['roleIdList', { rules: [{ required: true, message: $t('acl.role_placeholder2') }] }]"
multiple multiple
filterable filterable
placeholder="请选择角色名称,可多选!" :placeholder="$t('acl.role_placeholder3')"
> >
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option> <el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="权限列表"> <a-form-item :label="$t('acl.permissionList')">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
size="small" size="small"
name="permName" name="permName"
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]" v-decorator="['permName', { rules: [{ required: true, message: this.$t('acl.permission_placeholder') }] }]"
multiple multiple
placeholder="请选择权限,可多选!" :placeholder="this.$t('acl.permission_placeholder')"
> >
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option> <el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="资源名"> <a-form-item :label="$t('acl.resourceName')">
<a-textarea <a-textarea
v-decorator="['resource_names', { rules: [{ required: true, message: '请输入资源名,换行分隔' }] }]" v-decorator="['resource_names', { rules: [{ required: true, message: $t('acl.resourceBatchTips') }] }]"
:autoSize="{ minRows: 4 }" :autoSize="{ minRows: 4 }"
placeholder="请输入资源名,换行分隔" :placeholder="$t('acl.resourceBatchTips')"
/> />
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleRevoke" type="danger" ghost>权限回收</a-button> <a-button @click="handleRevoke" type="danger" ghost>{{ $t('acl.revoke') }}</a-button>
<a-button @click="handleSubmit" type="primary">授权</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('grant') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -98,12 +98,12 @@ export default {
this.loadRoles(Number(target)) this.loadRoles(Number(target))
}, },
loadRoles(isUserRole) { loadRoles(isUserRole) {
searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then(res => { searchRole({ page_size: 9999, app_id: this.$route.name.split('_')[0], user_role: isUserRole }).then((res) => {
this.allRoles = res.roles this.allRoles = res.roles
}) })
}, },
loadPerm(resourceTypeId) { loadPerm(resourceTypeId) {
getResourceTypePerms(resourceTypeId).then(res => { getResourceTypePerms(resourceTypeId).then((res) => {
this.allPerms = res this.allPerms = res
}) })
}, },
@ -111,13 +111,13 @@ export default {
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
console.log(values) console.log(values)
values.roleIdList.forEach(roleId => { values.roleIdList.forEach((roleId) => {
setBatchRoleResourceByResourceName(roleId, { setBatchRoleResourceByResourceName(roleId, {
resource_names: values.resource_names.split('\n'), resource_names: values.resource_names.split('\n'),
perms: values.permName, perms: values.permName,
resource_type_id: this.resource_type_id, resource_type_id: this.resource_type_id,
}).then(res => { }).then((res) => {
this.$message.success('授权成功') this.$message.success(this.$t('operateSuccess'))
this.form.resetFields() this.form.resetFields()
}) })
}) })
@ -128,13 +128,13 @@ export default {
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
console.log(values) console.log(values)
values.roleIdList.forEach(roleId => { values.roleIdList.forEach((roleId) => {
setBatchRoleResourceRevokeByResourceName(roleId, { setBatchRoleResourceRevokeByResourceName(roleId, {
resource_names: values.resource_names.split('\n'), resource_names: values.resource_names.split('\n'),
perms: values.permName, perms: values.permName,
resource_type_id: this.resource_type_id, resource_type_id: this.resource_type_id,
}).then(res => { }).then((res) => {
this.$message.success('权限回收成功') this.$message.success(this.$t('operateSuccess'))
this.form.resetFields() this.form.resetFields()
}) })
}) })

View File

@ -8,27 +8,27 @@
width="30%" width="30%"
> >
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="资源名"> <a-form-item :label="$t('acl.resourceName')">
<a-input <a-input
name="name" name="name"
placeholder="" placeholder=""
v-decorator="['name', { rules: [{ required: true, message: '请输入资源名' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('acl.resourceNameInput') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="资源类型"> <a-form-item :label="$t('acl.resourceType')">
<a-select v-model="selectedTypeId"> <a-select v-model="selectedTypeId">
<a-select-option v-for="type in allTypes" :key="type.id">{{ type.name }}</a-select-option> <a-select-option v-for="type in allTypes" :key="type.id">{{ type.name }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="是否组"> <a-form-item :label="$t('acl.isGroup')">
<a-radio-group v-model="isGroup"> <a-radio-group v-model="isGroup">
<a-radio :value="true"> <a-radio :value="true">
{{ $t('yes') }}
</a-radio> </a-radio>
<a-radio :value="false"> <a-radio :value="false">
{{ $t('no') }}
</a-radio> </a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
@ -36,8 +36,8 @@
<a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" /> <a-input name="id" type="hidden" v-decorator="['id', { rules: [] }]" />
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -52,7 +52,6 @@ export default {
name: 'ResourceForm', name: 'ResourceForm',
data() { data() {
return { return {
drawerTitle: '新增资源',
drawerVisible: false, drawerVisible: false,
allTypes: [], allTypes: [],
isGroup: false, isGroup: false,
@ -68,7 +67,11 @@ export default {
this.getAllResourceTypes() this.getAllResourceTypes()
}, },
computed: {}, computed: {
drawerTitle() {
return this.$t('acl.addResource')
},
},
mounted() {}, mounted() {},
methods: { methods: {
getAllResourceTypes() { getAllResourceTypes() {
@ -95,7 +98,7 @@ export default {
values.type_id = this.selectedTypeId values.type_id = this.selectedTypeId
values.app_id = this.$route.name.split('_')[0] values.app_id = this.$route.name.split('_')[0]
if (values.id) { if (values.id) {
this.$message.error('错误提示') this.$message.error(this.$t('acl.errorTips'))
} else { } else {
this.createResource(values) this.createResource(values)
} }
@ -105,23 +108,17 @@ export default {
createResource(data) { createResource(data) {
if (!this.isGroup) { if (!this.isGroup) {
addResource(data).then((res) => { addResource(data).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.onClose() this.onClose()
}) })
// .catch(err => this.requestFailed(err)) // .catch(err => this.requestFailed(err))
} else { } else {
addResourceGroup(data).then((res) => { addResourceGroup(data).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.onClose() this.onClose()
}) })
// .catch(err => this.requestFailed(err))
} }
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
}, },
watch: { watch: {
'$route.name': function(newValue, oldValue) { '$route.name': function(newValue, oldValue) {

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal v-model="visible" :title="`组成员:${editRecord.name}`" :width="800" :footer="null"> <a-modal v-model="visible" :title="`${$t('acl.groupMember')}${editRecord.name}`" :width="800" :footer="null">
<div :style="{ maxHeight: '500px', overflow: 'auto' }"> <div :style="{ maxHeight: '500px', overflow: 'auto' }">
<a-tag :style="{ marginBottom: '5px' }" v-for="mem in members" :key="mem.name"> <a-tag :style="{ marginBottom: '5px' }" v-for="mem in members" :key="mem.name">
{{ mem.name }} {{ mem.name }}

View File

@ -1,5 +1,5 @@
<template> <template>
<a-modal v-model="visible" :title="`成员管理:${editRecord.name}`" @ok="handleSubmit" :width="690"> <a-modal v-model="visible" :title="`${$t('acl.memberManage')}${editRecord.name}`" @ok="handleSubmit" :width="690">
<!-- <CustomTransfer <!-- <CustomTransfer
ref="customTransfer" ref="customTransfer"
:show-search="true" :show-search="true"
@ -26,7 +26,7 @@
@selectChange="selectChange" @selectChange="selectChange"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
> >
<span slot="notFoundContent">暂无数据</span> <span slot="notFoundContent">{{ $t('noData') }}</span>
<template slot="children" slot-scope="{ props: { direction, filteredItems } }"> <template slot="children" slot-scope="{ props: { direction, filteredItems } }">
<div class="ant-transfer-list-content" v-if="direction === 'right'"> <div class="ant-transfer-list-content" v-if="direction === 'right'">
<div <div
@ -104,7 +104,7 @@ export default {
}) })
updateResourceGroup(this.editRecord['id'], { items: items.join(',') }).then(() => { updateResourceGroup(this.editRecord['id'], { items: items.join(',') }).then(() => {
this.visible = false this.visible = false
this.$message.success('更新成功!') this.$message.success(this.$t('updateSuccess'))
}) })
// .catch(err => this.$httpError(err)) // .catch(err => this.$httpError(err))
}, },

View File

@ -4,7 +4,7 @@
ref="child" ref="child"
:attrList="resourceTableAttrList" :attrList="resourceTableAttrList"
:hasSwitch="true" :hasSwitch="true"
switchValue="" :switchValue="$t('acl.group2')"
@onSwitchChange="onSwitchChange" @onSwitchChange="onSwitchChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@ -22,30 +22,30 @@
resizable resizable
:height="`${windowHeight - 310}px`" :height="`${windowHeight - 310}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="link_id" title="资源名"> <vxe-column field="link_id" :title="$t('acl.resourceName')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.description }} {{ row.description }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -99,40 +99,6 @@ export default {
checked: false, checked: false,
tableData: [], tableData: [],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
resourceTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
},
{
alias: '资源名',
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: this.allResources,
},
],
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['update', 'orange'], ['update', 'orange'],
@ -170,9 +136,47 @@ export default {
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
])
},
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
resourceTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ [this.$t('create')]: 'create' }, { [this.$t('update')]: 'update' }, { [this.$t('delete')]: 'delete' }],
},
{
alias: this.$t('acl.resourceName'),
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: this.allResources,
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -280,7 +284,7 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
item.description = `新建资源${item.current.name}` item.description = `${this.$t('acl.newResource')}${item.current.name}`
break break
} }
case 'update': { case 'update': {
@ -290,9 +294,9 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
item.description += ` ${key} : 改为 ${newVal} ` item.description += ` ${key} : -> ${newVal} `
} else { } else {
item.description += ` ${key} : ${oldVal} 改为 ${newVal} ` item.description += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }
} }
@ -300,18 +304,18 @@ export default {
const currentResource_ids = item.currentResource_ids const currentResource_ids = item.currentResource_ids
if (!_.isEqual(originResource_ids, currentResource_ids)) { if (!_.isEqual(originResource_ids, currentResource_ids)) {
if (originResource_ids.length === 0) { if (originResource_ids.length === 0) {
const str = ` resource_ids : 新增 ${currentResource_ids} ` const str = ` resource_ids : ${this.$t('new')} ${currentResource_ids} `
item.description += str item.description += str
} else { } else {
const str = ` resource_ids : ${originResource_ids} 改为 ${currentResource_ids} ` const str = ` resource_ids : ${originResource_ids} -> ${currentResource_ids} `
item.description += str item.description += str
} }
} }
if (!item.description) item.description = '没有修改' if (!item.description) item.description = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
item.description = `删除资源${item.origin.name}` item.description = `${this.$t('acl.deleteResource')}${item.origin.name}`
break break
} }
} }

View File

@ -17,7 +17,7 @@
> >
<vxe-column <vxe-column
field="name" field="name"
title="角色名" :title="$t('acl.role')"
width="20%" width="20%"
:filters="[{ data: '' }]" :filters="[{ data: '' }]"
:filter-method="filterNameMethod" :filter-method="filterNameMethod"
@ -32,19 +32,19 @@
v-model="option.data" v-model="option.data"
@input="$panel.changeOption($event, !!option.data, option)" @input="$panel.changeOption($event, !!option.data, option)"
@keyup.enter="$panel.confirmFilter()" @keyup.enter="$panel.confirmFilter()"
placeholder="按回车确认筛选" :placeholder="$t('acl.pressEnter')"
/> />
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="users" title="下属用户" width="35%"> <vxe-column field="users" :title="$t('acl.subordinateUsers')" width="35%">
<template #default="{row}"> <template #default="{row}">
<a-tag color="green" v-for="user in row.users" :key="user.nickname"> <a-tag color="green" v-for="user in row.users" :key="user.nickname">
{{ user.nickname }} {{ user.nickname }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="perms" title="权限列表" width="35%"> <vxe-column field="perms" :title="$t('acl.permissionList')" width="35%">
<template #default="{row}"> <template #default="{row}">
<a-tag <a-tag
closable closable
@ -57,43 +57,18 @@
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate" title="批量操作"> <vxe-column field="operate" :title="$t('batchOperate')">
<template #default="{row}"> <template #default="{row}">
<a-button size="small" type="danger" @click="handleClearAll(row)"> <a-button size="small" type="danger" @click="handleClearAll(row)">
清空 {{ $t('clear') }}
</a-button> </a-button>
</template> </template>
</vxe-column> </vxe-column>
<template slot="empty"> <template slot="empty">
<img :src="require(`@/assets/data_empty.png`)" /> <img :src="require(`@/assets/data_empty.png`)" />
<p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">暂无数据</p> <p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">{{ $t('noData') }}</p>
</template> </template>
</vxe-table> </vxe-table>
<!-- <a-table
:columns="columns"
:dataSource="resPerms"
:rowKey="record => record.name"
:pagination="{ showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条记录` }"
showPagination="auto"
ref="rTable"
size="middle"
>
<div slot="perms" slot-scope="text">
<a-tag closable color="cyan" v-for="perm in text" :key="perm.name" @close="deletePerm(perm.rid, perm.name)">
{{ perm.name }}
</a-tag>
</div>
<div slot="users" slot-scope="text">
<a-tag color="green" v-for="user in text" :key="user.nickname">
{{ user.nickname }}
</a-tag>
</div>
<div slot="operate" slot-scope="text">
<a-button size="small" type="danger" @click="handleClearAll(text)">
清空
</a-button>
</div>
</a-table> -->
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
@ -112,40 +87,11 @@ export default {
data() { data() {
return { return {
isGroup: false, isGroup: false,
drawerTitle: '权限列表',
drawerVisible: false, drawerVisible: false,
record: null, record: null,
allPerms: [], allPerms: [],
resPerms: [], resPerms: [],
roleID: null, roleID: null,
// columns: [
// {
// title: '角色名',
// dataIndex: 'name',
// sorter: false,
// width: 50,
// },
// {
// title: '下属用户',
// dataIndex: 'users',
// sorter: false,
// width: 150,
// scopedSlots: { customRender: 'users' },
// },
// {
// title: '权限列表',
// dataIndex: 'perms',
// sorter: false,
// width: 150,
// scopedSlots: { customRender: 'perms' },
// },
// {
// title: '批量操作',
// sorter: false,
// width: 50,
// scopedSlots: { customRender: 'operate' },
// },
// ],
childrenDrawer: false, childrenDrawer: false,
allRoles: [], allRoles: [],
} }
@ -154,6 +100,9 @@ export default {
...mapState({ ...mapState({
windowHeight: (state) => state.windowHeight, windowHeight: (state) => state.windowHeight,
}), }),
drawerTitle() {
return this.$t('acl.permissionList')
}
}, },
beforeCreate() { beforeCreate() {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
@ -175,7 +124,7 @@ export default {
perms: [], perms: [],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}).then((res) => { }).then((res) => {
this.$message.success('删除成功') this.$message.success(this.$t('deleteSuccess'))
this.$nextTick(() => { this.$nextTick(() => {
this.getResPerms(this.record.id) this.getResPerms(this.record.id)
}) })
@ -185,7 +134,7 @@ export default {
perms: [], perms: [],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}).then((res) => { }).then((res) => {
this.$message.success('删除成功') this.$message.success(this.$t('deleteSuccess'))
// for (let i = 0; i < this.resPerms.length; i++) { // for (let i = 0; i < this.resPerms.length; i++) {
// if (this.resPerms[i].name === text.name) { // if (this.resPerms[i].name === text.name) {
// this.resPerms[i].perms = [] // this.resPerms[i].perms = []
@ -227,7 +176,7 @@ export default {
perms: [permName], perms: [permName],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}).then((res) => { }).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
}) })
// .catch(err => this.requestFailed(err)) // .catch(err => this.requestFailed(err))
} else { } else {
@ -235,18 +184,13 @@ export default {
perms: [permName], perms: [permName],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}).then((res) => { }).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
}) })
// .catch(err => this.requestFailed(err))
} }
}, },
handleCancel(e) { handleCancel(e) {
this.drawerVisible = false this.drawerVisible = false
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
filterNameMethod({ option, row }) { filterNameMethod({ option, row }) {
return row.name.toLowerCase().includes(option.data.toLowerCase()) return row.name.toLowerCase().includes(option.data.toLowerCase())
}, },

View File

@ -3,42 +3,42 @@
<a-form :form="form"> <a-form :form="form">
<a-form-item> <a-form-item>
<div slot="label" style="display: inline-block"> <div slot="label" style="display: inline-block">
<span>角色列表</span> <span>{{ $t('acl.roleList') }}</span>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-switch <a-switch
style="display: inline-block" style="display: inline-block"
checked-children="用户" :checked-children="$t('user')"
un-checked-children="虚拟" :un-checked-children="$t('acl.virtual')"
@change="handleRoleTypeChange" @change="handleRoleTypeChange"
/> />
</div> </div>
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
size="small" size="small"
v-decorator="['roleIdList', { rules: [{ required: true, message: '请选择角色名称' }] }]" v-decorator="['roleIdList', { rules: [{ required: true, message: $t('acl.role_placeholder2') }] }]"
multiple multiple
filterable filterable
placeholder="请选择角色名称,可多选!" :placeholder="$t('acl.role_placeholder3')"
> >
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option> <el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="权限列表"> <a-form-item :label="$t('acl.permissionList')">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
size="small" size="small"
name="permName" name="permName"
v-decorator="['permName', { rules: [{ required: true, message: '请选择权限' }] }]" v-decorator="['permName', { rules: [{ required: true, message: $t('acl.permission_placeholder') }] }]"
multiple multiple
placeholder="请选择权限,可多选!" :placeholder="$t('acl.permission_placeholder') "
> >
<el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option> <el-option v-for="perm in allPerms" :key="perm.name" :value="perm.name" :label="perm.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="closeForm">取消</a-button> <a-button @click="closeForm">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary" :loading="loading">确定</a-button> <a-button @click="handleSubmit" type="primary" :loading="loading">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -117,16 +117,12 @@ export default {
this.type = type this.type = type
if (Array.isArray(record)) { if (Array.isArray(record)) {
this.loadPerm(record[0]['resource_type_id']) this.loadPerm(record[0]['resource_type_id'])
this.title = `${type === 'grant' ? '批量授权' : '批量权限回收'}` this.title = `${type === 'grant' ? this.$t('acl.batchGrant') : this.$t('acl.batchRevoke')}`
} else { } else {
this.title = `添加授权${record.name}` this.title = `${this.$t('acl.editPerm')}${record.name}`
this.loadPerm(record['resource_type_id']) this.loadPerm(record['resource_type_id'])
} }
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
handleSubmit(e) { handleSubmit(e) {
e.preventDefault() e.preventDefault()
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
@ -136,16 +132,10 @@ export default {
this.loading = true this.loading = true
if (!this.isGroup) { if (!this.isGroup) {
if (Array.isArray(this.instance)) { if (Array.isArray(this.instance)) {
// const promises = this.instance.map(item => {
// return setRoleResourcePerm(roleId, item.id, params)
// })
// Promise.all(promises).then(() => {
// this.$message.success('添加授权成功')
// })
if (this.type === 'grant') { if (this.type === 'grant') {
setBatchRoleResourcePerm(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) }) setBatchRoleResourcePerm(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
.then((res) => { .then((res) => {
this.$message.success('添加授权成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@ -153,7 +143,7 @@ export default {
} else { } else {
setBatchRoleResourceRevoke(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) }) setBatchRoleResourceRevoke(roleId, { ...params, resource_ids: this.instance.map((a) => a.id) })
.then((res) => { .then((res) => {
this.$message.success('批量权限回收成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@ -162,7 +152,7 @@ export default {
} else { } else {
setRoleResourcePerm(roleId, this.instance.id, params) setRoleResourcePerm(roleId, this.instance.id, params)
.then((res) => { .then((res) => {
this.$message.success('添加授权成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@ -170,16 +160,10 @@ export default {
} }
} else { } else {
if (Array.isArray(this.instance)) { if (Array.isArray(this.instance)) {
// const promises = this.instance.map(item => {
// return setRoleResourceGroupPerm(roleId, item.id, params)
// })
// Promise.all(promises).then(() => {
// this.$message.success('添加授权成功')
// })
if (this.type === 'grant') { if (this.type === 'grant') {
setBatchRoleResourceGroupPerm(roleId, { ...params, group_ids: this.instance.map((a) => a.id) }) setBatchRoleResourceGroupPerm(roleId, { ...params, group_ids: this.instance.map((a) => a.id) })
.then((res) => { .then((res) => {
this.$message.success('添加授权成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@ -190,7 +174,7 @@ export default {
group_ids: this.instance.map((a) => a.id), group_ids: this.instance.map((a) => a.id),
}) })
.then((res) => { .then((res) => {
this.$message.success('批量权限回收成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false
@ -199,7 +183,7 @@ export default {
} else { } else {
setRoleResourceGroupPerm(roleId, this.instance.id, params) setRoleResourceGroupPerm(roleId, this.instance.id, params)
.then((res) => { .then((res) => {
this.$message.success('添加授权成功') this.$message.success(this.$t('operateSuccess'))
}) })
.finally(() => { .finally(() => {
this.loading = false this.loading = false

View File

@ -1,32 +1,32 @@
<template> <template>
<CustomDrawer <CustomDrawer
:closable="false" :closable="false"
:title="drawerTitle" :title="$t('acl.addReourceType')"
:visible="drawerVisible" :visible="drawerVisible"
@close="onClose" @close="onClose"
placement="right" placement="right"
width="500px" width="500px"
> >
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="类型名"> <a-form-item :label="$t('acl.typeName')">
<a-input <a-input
name="name" name="name"
placeholder="类型名称" :placeholder="$t('acl.typeName')"
v-decorator="['name', { rules: [{ required: true, message: '请输入类型名' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('acl.typeNameInput') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="描述"> <a-form-item :label="$t('desc')">
<a-textarea <a-textarea
placeholder="请输入描述信息..." :placeholder="$t('acl.descInput')"
name="description" name="description"
:rows="4" :rows="4"
v-decorator="['description', { rules: [] }]" v-decorator="['description', { rules: [] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="权限"> <a-form-item :label="$t('acl.permission')">
<a-select mode="tags" v-model="perms" style="width: 100%" placeholder="请输入权限名..."> </a-select> <a-select mode="tags" v-model="perms" style="width: 100%" :placeholder="$t('acl.permInput')"> </a-select>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
@ -34,8 +34,8 @@
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -48,7 +48,6 @@ export default {
name: 'ResourceForm', name: 'ResourceForm',
data() { data() {
return { return {
drawerTitle: '新增资源类型',
drawerVisible: false, drawerVisible: false,
perms: [], perms: [],
} }
@ -104,7 +103,7 @@ export default {
}, },
updateResourceType(id, data) { updateResourceType(id, data) {
updateResourceTypeById(id, data).then((res) => { updateResourceTypeById(id, data).then((res) => {
this.$message.success(`更新成功`) this.$message.success(this.$t('updateSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
@ -113,17 +112,11 @@ export default {
createResourceType(data) { createResourceType(data) {
addResourceType(data).then((res) => { addResourceType(data).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
// .catch(err => this.requestFailed(err))
}, },
// requestFailed (err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// }
}, },
watch: {}, watch: {},
props: { props: {

View File

@ -16,30 +16,30 @@
:loading="loading" :loading="loading"
:height="`${windowHeight - 310}px`" :height="`${windowHeight - 310}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="link_id" title="资源类型名"> <vxe-column field="link_id" :title="$t('acl.resourceTypeName')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.changeDescription }} {{ row.changeDescription }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -93,40 +93,6 @@ export default {
checked: false, checked: false,
tableData: [], tableData: [],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
resourceTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
},
{
alias: '资源类型',
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: this.allResourceTypes,
},
],
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['update', 'orange'], ['update', 'orange'],
@ -155,12 +121,50 @@ export default {
}, },
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
resourceTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ [this.$t('create')]: 'create' }, { [this.$t('update')]: 'update' }, { [this.$t('delete')]: 'delete' }],
},
{
alias: this.$t('acl.resourceType'),
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: this.allResourceTypes,
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -236,7 +240,9 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
item.changeDescription = `新增资源类型${item.current.name}\n描述${item.current.description}\n权限${item.extra.permission_ids.current}` item.changeDescription = `${this.$t('acl.addReourceType')}${item.current.name}\n${this.$t('desc')}${
item.current.description
}\n${this.$t('acl.permission')}: ${item.extra.permission_ids.current}`
break break
} }
case 'update': { case 'update': {
@ -246,10 +252,10 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null || oldVal === '') { if (oldVal === null || oldVal === '') {
const str = ` ${key} : 改为 ${newVal} \n` const str = ` ${key} : -> ${newVal} \n`
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} \n` const str = ` ${key} : ${oldVal} -> ${newVal} \n`
item.changeDescription += str item.changeDescription += str
} }
} }
@ -257,13 +263,13 @@ export default {
const currentPerms = item.extra.permission_ids.current const currentPerms = item.extra.permission_ids.current
const originPerms = item.extra.permission_ids.origin const originPerms = item.extra.permission_ids.origin
if (!_.isEqual(currentPerms, originPerms)) { if (!_.isEqual(currentPerms, originPerms)) {
item.changeDescription += ` permission_ids : ${originPerms} 改为 ${currentPerms} ` item.changeDescription += ` permission_ids : ${originPerms} -> ${currentPerms} `
} }
if (!item.changeDescription) item.changeDescription = '没有修改' if (!item.changeDescription) item.changeDescription = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
item.changeDescription = `删除资源类型${item.origin.name}\n描述${item.origin.description}\n权限${item.extra.permission_ids.origin}` item.changeDescription = `${this.$t('acl.deleteResourceType')}${item.origin.name}\n${this.$t('desc')}: ${item.origin.description}\n${this.$t('acl.permission')}: ${item.extra.permission_ids.origin}`
break break
} }
} }

View File

@ -2,12 +2,12 @@
<CustomDrawer <CustomDrawer
width="800px" width="800px"
placement="left" placement="left"
title="资源列表" :title="$t('acl.resourceList')"
@close="handleCancel" @close="handleCancel"
:visible="visible" :visible="visible"
:hasFooter="false" :hasFooter="false"
> >
<a-form-item label="资源类型" :label-col="{ span: 2 }" :wrapper-col="{ span: 14 }"> <a-form-item :label="$t('acl.resourceType')" :label-col="{ span: 4 }" :wrapper-col="{ span: 14 }">
<a-select v-model="typeSelected" style="width:100%" @change="refresh"> <a-select v-model="typeSelected" style="width:100%" @change="refresh">
<a-select-option v-for="type in resourceTypes" :value="type.id" :key="type.id">{{ type.name }}</a-select-option> <a-select-option v-for="type in resourceTypes" :value="type.id" :key="type.id">{{ type.name }}</a-select-option>
</a-select> </a-select>
@ -22,7 +22,7 @@
> >
<vxe-column <vxe-column
field="name" field="name"
title="资源名" :title="$t('acl.resourceName')"
width="30%" width="30%"
:filters="[{ data: '' }]" :filters="[{ data: '' }]"
:filter-method="filterNameMethod" :filter-method="filterNameMethod"
@ -30,7 +30,7 @@
> >
<template #header="{ column }"> <template #header="{ column }">
<span>{{ column.title }}</span> <span>{{ column.title }}</span>
<a-tooltip title="复制资源名"> <a-tooltip :title="$t('acl.copyResource')">
<a-icon @click="copyResourceName" class="resource-user-form-copy" theme="filled" type="copy" /> <a-icon @click="copyResourceName" class="resource-user-form-copy" theme="filled" type="copy" />
</a-tooltip> </a-tooltip>
</template> </template>
@ -48,7 +48,7 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="permissions" title="权限列表" width="70%"> <vxe-column field="permissions" :title="$t('acl.permissionList')" width="70%">
<template #default="{row}"> <template #default="{row}">
<a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag> <a-tag color="cyan" v-for="(r, index) in row.permissions" :key="index">{{ r }}</a-tag>
</template> </template>
@ -58,69 +58,6 @@
<p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">暂无数据</p> <p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">暂无数据</p>
</template> </template>
</vxe-table> </vxe-table>
<!-- <a-table
:columns="columns"
:dataSource="records"
:rowKey="record => record.id"
:pagination="false"
ref="rTable"
size="middle"
:scroll="{ y: 300 }"
> -->
<!-- <div slot="filterDropdown" slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }" class="custom-filter-dropdown">
<a-input
v-ant-ref="c => searchInput = c"
:placeholder="` ${column.title}`"
:value="selectedKeys[0]"
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="() => handleSearch(selectedKeys, confirm, column)"
style="width: 188px; margin-bottom: 8px; display: block;"
/>
<a-button
type="primary"
@click="() => handleSearch(selectedKeys, confirm, column)"
icon="search"
size="small"
style="width: 90px; margin-right: 8px"
>搜索</a-button>
<a-button
@click="() => handleReset(clearFilters, column)"
size="small"
style="width: 90px"
>重置</a-button>
</div>
<a-icon slot="filterIcon" slot-scope="filtered" type="search" :style="{ color: filtered ? '#108ee9' : undefined }" />
<template slot="nameSearchRender" slot-scope="text">
<span v-if="columnSearchText.name">
<template v-for="(fragment, i) in text.toString().split(new RegExp(`(?<=${columnSearchText.name})|(?=${columnSearchText.name})`, 'i'))">
<mark v-if="fragment.toLowerCase() === columnSearchText.name.toLowerCase()" :key="i" class="highlight">{{ fragment }}</mark>
<template v-else>{{ fragment }}</template>
</template>
</span>
<template v-else>{{ text }}</template>
</template> -->
<!-- <template slot="permissions" slot-scope="record">
<a-tag color="cyan" v-for="(r, index) in record" :key="index">{{ r }}</a-tag>
</template>
</a-table> -->
<!-- <div
:style="{
position: 'absolute',
left: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
}"
>
<a-button :style="{marginRight: '8px'}" @click="handleCancel">
取消
</a-button>
<a-button @click="handleOk" type="primary">确定</a-button>
</div> -->
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
@ -141,33 +78,6 @@ export default {
name: '', name: '',
}, },
filterName: '', filterName: '',
// columns: [
// {
// title: '资源名',
// field: 'name',
// sorter: false,
// width: '30%',
// // scopedSlots: {
// // customRender: 'nameSearchRender',
// // filterDropdown: 'filterDropdown',
// // filterIcon: 'filterIcon'
// // },
// // onFilter: (value, record) => record.name && record.name.toLowerCase().includes(value.toLowerCase()),
// // onFilterDropdownVisibleChange: (visible) => {
// // if (visible) {
// // setTimeout(() => {
// // this.searchInput.focus()
// // }, 0)
// // }
// // }
// },
// {
// title: '权限列表',
// field: 'permissions',
// width: '70%',
// slots: { default: 'permissions_default' },
// },
// ],
} }
}, },
computed: { computed: {
@ -184,14 +94,6 @@ export default {
this.rid = record.id this.rid = record.id
this.refresh() this.refresh()
}, },
// handleSearch(selectedKeys, confirm, column) {
// confirm()
// this.columnSearchText[column.dataIndex] = selectedKeys[0]
// },
// handleReset(clearFilters, column) {
// clearFilters()
// this.columnSearchText[column.dataIndex] = ''
// },
loadResourceTypes() { loadResourceTypes() {
this.resourceTypes = [] this.resourceTypes = []
const appId = this.$route.name.split('_')[0] const appId = this.$route.name.split('_')[0]
@ -204,7 +106,6 @@ export default {
this.typeSelected = null this.typeSelected = null
} }
}) })
// .catch(err => this.$httpError(err))
}, },
handleOk() { handleOk() {
this.visible = false this.visible = false
@ -217,7 +118,6 @@ export default {
}).then(res => { }).then(res => {
this.records = res.resources this.records = res.resources
}) })
// .catch(err=>this.$httpError(err))
} }
}, },
handleCancel() { handleCancel() {
@ -238,7 +138,7 @@ export default {
.join('\n') .join('\n')
this.copy(val, () => { this.copy(val, () => {
this.$message.success('复制成功') this.$message.success(this.$t('copySuccess'))
}) })
}, },
copy(value, cb) { copy(value, cb) {

View File

@ -8,40 +8,29 @@
width="500px" width="500px"
> >
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" @submit="handleSubmit"> <a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" @submit="handleSubmit">
<a-form-item label="角色名"> <a-form-item :label="$t('acl.role')">
<a-input <a-input
name="name" name="name"
placeholder="角色名" :placeholder="$t('acl.role_placeholder1')"
v-decorator="['name', { rules: [{ required: true, message: '请输入角色名' }] }]" v-decorator="['name', { rules: [{ required: true, message: $t('acl.role_placeholder1') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item v-if="$route.name.split('_')[0] !== 'acl'" label="密码"> <a-form-item v-if="$route.name.split('_')[0] !== 'acl'" :label="$t('acl.password')">
<a-input name="password" placeholder="密码" v-decorator="['password', { rules: [{ required: false }] }]" /> <a-input name="password" :placeholder="$t('acl.password')" v-decorator="['password', { rules: [{ required: false }] }]" />
</a-form-item> </a-form-item>
<!-- <a-form-item label="继承自"> <a-form-item :label="$t('acl.inheritedFrom')">
<a-select
showSearch
v-model="selectedParents"
:filterOption="false"
placeholder="可选择继承角色"
mode="multiple"
>
<a-select-option v-for="role in scrollData" :key="role.id">{{ role.name }}</a-select-option>
</a-select>
</a-form-item> -->
<a-form-item label="继承自">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
size="small" size="small"
v-model="selectedParents" v-model="selectedParents"
multiple multiple
filterable filterable
placeholder="可选择继承角色" :placeholder="$t('acl.selectedParents')"
> >
<el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option> <el-option v-for="role in allRoles" :key="role.id" :value="role.id" :label="role.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="是否应用管理员"> <a-form-item :label="$t('acl.isAppAdmin')">
<a-switch <a-switch
@change="onChange" @change="onChange"
name="is_app_admin" name="is_app_admin"
@ -53,8 +42,8 @@
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -93,7 +82,7 @@ export default {
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 // return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
// }, // },
handleCreate() { handleCreate() {
this.drawerTitle = '新增角色' this.drawerTitle = this.$t('acl.addRole')
this.drawerVisible = true this.drawerVisible = true
}, },
onClose() { onClose() {
@ -107,7 +96,7 @@ export default {
}, },
handleEdit(record) { handleEdit(record) {
this.drawerTitle = `编辑${record.name}` this.drawerTitle = `${this.$t('edit')}: ${record.name}`
this.drawerVisible = true this.drawerVisible = true
this.current_id = record.id this.current_id = record.id
const _parents = this.id2parents[record.id] const _parents = this.id2parents[record.id]
@ -142,7 +131,7 @@ export default {
updateRole(id, data) { updateRole(id, data) {
this.updateParents(id) this.updateParents(id)
updateRoleById(id, { ...data, app_id: this.$route.name.split('_')[0] }).then((res) => { updateRoleById(id, { ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
this.$message.success(`更新成功`) this.$message.success(this.$t('updateSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
@ -151,7 +140,7 @@ export default {
createRole(data) { createRole(data) {
addRole({ ...data, app_id: this.$route.name.split('_')[0] }).then((res) => { addRole({ ...data, app_id: this.$route.name.split('_')[0] }).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.updateParents(res.id) this.updateParents(res.id)
this.handleOk() this.handleOk()
this.onClose() this.onClose()
@ -171,16 +160,9 @@ export default {
child_ids: [id], child_ids: [id],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}) })
// addParentRole(id, item, { app_id: this.$route.name.split('_')[0] })
// .catch(err => this.requestFailed(err))
} }
}) })
}, },
// requestFailed(err) {
// console.log(err)
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
}, },
watch: {}, watch: {},
props: { props: {

View File

@ -4,7 +4,7 @@
ref="child" ref="child"
:attrList="roleTableAttrList" :attrList="roleTableAttrList"
:hasSwitch="true" :hasSwitch="true"
switchValue="角色关系" :switchValue="$t('acl.roleRelation')"
@onSwitchChange="onSwitchChange" @onSwitchChange="onSwitchChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@ -19,16 +19,16 @@
resizable resizable
:height="`${windowHeight - 310}px`" :height="`${windowHeight - 310}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" title="操作员" width="130px"></vxe-column> <vxe-column field="operate_uid" :title="$t('acl.operator')" width="130px"></vxe-column>
<vxe-column field="operate_type" title="操作" width="112px"> <vxe-column field="operate_type" :title="$t('operation')" width="112px">
<template #default="{ row }"> <template #default="{ row }">
<template> <template>
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag> <a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column :title="checked ? '角色' : '角色'"> <vxe-column :title="checked ? $t('acl.role2') : $t('acl.role2')">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="!checked"> <template v-if="!checked">
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag> <a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
@ -40,7 +40,7 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'"> <vxe-column :title="checked ? $t('acl.inheritedFrom') : $t('acl.admin')" :width="checked ? '350px' : '80px'">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="!checked"> <template v-if="!checked">
<a-icon type="check" v-if="row.current.is_app_admin" /> <a-icon type="check" v-if="row.current.is_app_admin" />
@ -52,14 +52,14 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述" v-if="!checked"> <vxe-column :title="$t('desc')" v-if="!checked">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.description }} {{ row.description }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" title="来源" width="100px"></vxe-column> <vxe-column field="source" :title="$t('acl.source')" width="100px"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -105,13 +105,6 @@ export default {
checked: false, checked: false,
tableData: [], tableData: [],
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
operateTypeMap: new Map([
['create', '新建'],
['delete', '删除'],
['update', '修改'],
['role_relation_add', '添加角色关系'],
['role_relation_delete', '删除角色关系'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['delete', 'red'], ['delete', 'red'],
@ -127,42 +120,53 @@ export default {
start: '', start: '',
end: '', end: '',
}, },
roleTableAttrList: [ }
},
computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
['role_relation_add', this.$t('acl.roleRelationAdd')],
['role_relation_delete', this.$t('acl.roleRelationDelete')],
])
},
windowHeight() {
return this.$store.state.windowHeight
},
tableDataLength() {
return this.tableData.length
},
roleTableAttrList() {
return [
{ {
alias: '日期', alias: this.$t('acl.date'),
is_choice: false, is_choice: false,
name: 'datetime', name: 'datetime',
value_type: '3', value_type: '3',
}, },
{ {
alias: '操作员', alias: this.$t('acl.operator'),
is_choice: true, is_choice: true,
name: 'operate_uid', name: 'operate_uid',
value_type: '2', value_type: '2',
choice_value: this.allUsers, choice_value: this.allUsers,
}, },
{ {
alias: '操作', alias: this.$t('operation'),
is_choice: true, is_choice: true,
name: 'operate_type', name: 'operate_type',
value_type: '2', value_type: '2',
choice_value: [ choice_value: [
{ 新建: 'create' }, { [this.$t('create')]: 'create' },
{ 修改: 'update' }, { [this.$t('update')]: 'update' },
{ 删除: 'delete' }, { [this.$t('delete')]: 'delete' },
{ 添加角色关系: 'role_relation_add' }, { [this.$t('acl.roleRelationAdd')]: 'role_relation_add' },
{ 删除角色关系: 'role_relation_delete' }, { [this.$t('acl.roleRelationDelete')]: 'role_relation_delete' },
], ],
}, },
], ]
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
tableDataLength() {
return this.tableData.length
}, },
}, },
async created() { async created() {
@ -273,7 +277,7 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
item.description = `新建角色${item.current.name}` item.description = `${this.$t('acl.addRole')}${item.current.name}`
break break
} }
case 'update': { case 'update': {
@ -283,15 +287,15 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
const str = ` ${key} : 改为 ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.description += str item.description += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} ` const str = ` ${key} : ${oldVal} -> ${newVal} `
item.description += str item.description += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }
} }
if (!item.description) item.description = '没有修改' if (!item.description) item.description = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
@ -321,7 +325,7 @@ export default {
resourceMap.forEach((value, key) => { resourceMap.forEach((value, key) => {
permsArr.push(`${id2resources[key].name}${value}`) permsArr.push(`${id2resources[key].name}${value}`)
}) })
item.description = `继承者${child_ids}\n继承自${parent_ids}\n涉及资源及权限\n${permsArr.join(`\n`)}` item.description = `${this.$t('acl.heir')}${child_ids}\n${this.$t('acl.inheritedFrom')}${parent_ids}\n${this.$t('acl.involvingRP')}\n${permsArr.join(`\n`)}`
break break
} }
} }

View File

@ -19,7 +19,7 @@
@popupScroll="loadMoreData(attr.name, $event)" @popupScroll="loadMoreData(attr.name, $event)"
@search="(value) => fetchData(value, attr.name)" @search="(value) => fetchData(value, attr.name)"
v-model="queryParams[attr.name]" v-model="queryParams[attr.name]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-if="attr.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
@ -38,7 +38,6 @@
@change="onChange" @change="onChange"
:style="{ width: '100%' }" :style="{ width: '100%' }"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
@ -67,7 +66,7 @@
@popupScroll="loadMoreData(item.name, $event)" @popupScroll="loadMoreData(item.name, $event)"
@search="(value) => fetchData(value, item.name)" @search="(value) => fetchData(value, item.name)"
v-model="queryParams[item.name]" v-model="queryParams[item.name]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="item.is_choice" v-if="item.is_choice"
show-search show-search
:filter-option="filterOption" :filter-option="filterOption"
@ -85,7 +84,7 @@
:style="{ width: '100%' }" :style="{ width: '100%' }"
@change="onChange" @change="onChange"
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
:placeholder="['开始时间', '结束时间']" :placeholder="[$t('acl.startAt'), $t('acl.endAt')]"
v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'" v-else-if="valueTypeMap[item.value_type] == 'date' || valueTypeMap[item.value_type] == 'datetime'"
:show-time="{ :show-time="{
hideDisabledOptions: true, hideDisabledOptions: true,
@ -107,13 +106,13 @@
v-model="checked" v-model="checked"
/> />
<a-button :style="{ marginLeft: '8px' }" type="primary" html-type="submit" @click="handleSearch"> <a-button :style="{ marginLeft: '8px' }" type="primary" html-type="submit" @click="handleSearch">
查询 {{ $t('query') }}
</a-button> </a-button>
<a-button :style="{ marginLeft: '8px' }" @click="handleReset"> <a-button :style="{ marginLeft: '8px' }" @click="handleReset">
重置 {{ $t('reset') }}
</a-button> </a-button>
<a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 5"> <a :style="{ marginLeft: '8px', fontSize: '12px' }" @click="toggle" v-if="attrList.length >= 5">
{{ expand ? '隐藏' : '展开' }} <a-icon :type="expand ? 'up' : 'down'" /> {{ expand ? $t('expand') : $t('expand') }} <a-icon :type="expand ? 'up' : 'down'" />
</a> </a>
</a-col> </a-col>
</a-row> </a-row>

View File

@ -2,30 +2,30 @@
<CustomDrawer <CustomDrawer
@close="handleClose" @close="handleClose"
width="500" width="500"
:title="`${triggerId ? '修改' : '新建'}触发器`" :title="`${triggerId ? $t('update') : $t('create')}${$t('acl.trigger')}`"
:visible="visible" :visible="visible"
:maskClosable="false" :maskClosable="false"
> >
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }"> <a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
<a-form-item label="触发器名"> <a-form-item :label="$t('name')">
<a-input size="large" v-decorator="['name', { rules: [{ required: true, message: '请输入触发器名' }] }]"> <a-input size="large" v-decorator="['name', { rules: [{ required: true, message: $t('acl.triggerNameInput') }] }]">
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="资源名"> <a-form-item :label="$t('acl.resourceName')">
<a-input size="large" v-decorator="['wildcard']" placeholder="优先正则模式(次通配符)"> </a-input> <a-input size="large" v-decorator="['wildcard']" :placeholder="$t('acl.triggerTips1')"> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="创建人"> <a-form-item :label="$t('acl.creator')">
<el-select :style="{ width: '100%' }" filterable multiple v-decorator="['uid']"> <el-select :style="{ width: '100%' }" filterable multiple v-decorator="['uid']">
<template v-for="role in roles"> <template v-for="role in roles">
<el-option v-if="role.uid" :key="role.id" :value="role.uid" :label="role.name">{{ role.name }}</el-option> <el-option v-if="role.uid" :key="role.id" :value="role.uid" :label="role.name">{{ role.name }}</el-option>
</template> </template>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="资源类型"> <a-form-item :label="$t('acl.resourceType')">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
@change="handleRTChange" @change="handleRTChange"
v-decorator="['resource_type_id', { rules: [{ required: true, message: '请选择资源类型' }] }]" v-decorator="['resource_type_id', { rules: [{ required: true, message: $t('acl.pleaseSelectType') }] }]"
> >
<el-option <el-option
v-for="resourceType in resourceTypeList" v-for="resourceType in resourceTypeList"
@ -34,25 +34,25 @@
:label="resourceType.name" :label="resourceType.name"
></el-option> ></el-option>
</el-select> </el-select>
<a-tooltip title="查看正则匹配结果"> <a-tooltip :title="$t('acl.viewMatchResult')">
<a class="trigger-form-pattern" @click="handlePattern"><a-icon type="eye"/></a> <a class="trigger-form-pattern" @click="handlePattern"><a-icon type="eye"/></a>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
<a-form-item label="角色"> <a-form-item :label="$t('acl.role2')">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable filterable
multiple multiple
v-decorator="['roles', { rules: [{ required: true, message: '请选择角色' }] }]" v-decorator="['roles', { rules: [{ required: true, message: $t('acl.role_placeholder2') }] }]"
> >
<el-option v-for="role in roles" :key="role.id" :value="role.id" :label="role.name"></el-option> <el-option v-for="role in roles" :key="role.id" :value="role.id" :label="role.name"></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="权限"> <a-form-item :label="$t('acl.permission')">
<el-select <el-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
multiple multiple
v-decorator="['permissions', { rules: [{ required: true, message: '请选择权限' }] }]" v-decorator="['permissions', { rules: [{ required: true, message: $t('acl.permission_placeholder') }] }]"
> >
<el-option <el-option
v-for="perm in selectResourceTypePerms" v-for="perm in selectResourceTypePerms"
@ -62,13 +62,13 @@
></el-option> ></el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
<a-form-item label="启用/禁用"> <a-form-item :label="$t('acl.enable')/$t('acl.disable')">
<a-switch v-decorator="['enabled', { rules: [], valuePropName: 'checked', initialValue: true }]" /> <a-switch v-decorator="['enabled', { rules: [], valuePropName: 'checked', initialValue: true }]" />
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">取消</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">提交</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('submit') }}</a-button>
</div> </div>
<TriggerPattern ref="triggerPattern" :roles="roles" /> <TriggerPattern ref="triggerPattern" :roles="roles" />
</CustomDrawer> </CustomDrawer>
@ -159,13 +159,13 @@ export default {
if (this.triggerId) { if (this.triggerId) {
updateTrigger(this.triggerId, { ...values, app_id: this.app_id }).then((res) => { updateTrigger(this.triggerId, { ...values, app_id: this.app_id }).then((res) => {
this.visible = false this.visible = false
this.$message.success('修改成功!') this.$message.success(this.$t('updateSuccess'))
this.$emit('refresh') this.$emit('refresh')
}) })
} else { } else {
addTrigger({ ...values, app_id: this.app_id }).then((res) => { addTrigger({ ...values, app_id: this.app_id }).then((res) => {
this.visible = false this.visible = false
this.$message.success('创建成功!') this.$message.success(this.$t('addSuccess'))
this.$emit('refresh') this.$emit('refresh')
}) })
} }

View File

@ -16,23 +16,23 @@
resizable resizable
:height="`${windowHeight - 310}px`" :height="`${windowHeight - 310}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="trigger_id" width="250px" title="触发器"> <vxe-column field="trigger_id" width="250px" :title="$t('acl.trigger')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.changeDescription }} {{ row.changeDescription }}
@ -95,13 +95,6 @@ export default {
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
loading: true, loading: true,
tableData: [], tableData: [],
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
['trigger_apply', '应用'],
['trigger_cancel', '取消'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['delete', 'red'], ['delete', 'red'],
@ -109,41 +102,6 @@ export default {
['trigger_apply', 'green'], ['trigger_apply', 'green'],
['trigger_cancel', 'red'], ['trigger_cancel', 'red'],
]), ]),
triggerTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [
{ 新建: 'create' },
{ 修改: 'update' },
{ 删除: 'delete' },
{ 应用: 'trigger_apply' },
{ 取消: 'trigger_cancel' },
],
},
{
alias: '触发器',
is_choice: true,
name: 'trigger_id',
value_type: '2',
choice_value: this.allTriggers,
},
],
queryParams: { queryParams: {
page: 1, page: 1,
page_size: 50, page_size: 50,
@ -164,12 +122,58 @@ export default {
}, },
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
['trigger_apply', this.$t('acl.apply')],
['trigger_cancel', this.$t('cancel')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
triggerTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: this.allUsers,
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [
{ [this.$t('create')]: 'create' },
{ [this.$t('update')]: 'update' },
{ [this.$t('delete')]: 'delete' },
{ [this.$t('acl.apply')]: 'trigger_apply' },
{ [this.$t('cancel')]: 'trigger_cancel' },
],
},
{
alias: this.$t('acl.trigger'),
is_choice: true,
name: 'trigger_id',
value_type: '2',
choice_value: this.allTriggers,
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -220,9 +224,9 @@ export default {
const str = item.current.roles const str = item.current.roles
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('') const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('')
item.changeDescription = `新增触发器${item.current.name}\n资源类型${this.allResourceTypesMap.get( item.changeDescription = `${this.$t('acl.addTrigger')}: ${item.current.name}\n${this.$t('acl.resourceType')}: ${this.allResourceTypesMap.get(
item.current.resource_type_id item.current.resource_type_id
)}资源名${item.current.wildcard}角色[${newStr}]\n权限${item.current.permissions}\n状态${ )}this.$t('acl.resourceName')${item.current.wildcard}${this.$t('acl.role2')}: [${newStr}]\n${this.$t('acl.permission')}: ${item.current.permissions}\n${this.$t('status')}: ${
item.current.enabled item.current.enabled
}` }`
break break
@ -234,24 +238,24 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
const str = ` ${key} : 改为 ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} ` const str = ` ${key} : ${oldVal} -> ${newVal} `
item.changeDescription += str item.changeDescription += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }
} }
if (!item.changeDescription) item.changeDescription = '没有修改' if (!item.changeDescription) item.changeDescription = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
const str = item.origin.roles const str = item.origin.roles
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('') const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('')
item.changeDescription = `删除触发器${item.origin.name}\n资源类型${this.allResourceTypesMap.get( item.changeDescription = `${this.$t('acl.deleteTrigger')}: ${item.origin.name}\n${this.$t('acl.resourceType')}: ${this.allResourceTypesMap.get(
item.origin.resource_type_id item.origin.resource_type_id
)}资源名${item.origin.wildcard}角色[${newStr}]\n权限${item.origin.permissions}\n状态${ )}this.$t('acl.resourceName')${item.origin.wildcard}${this.$t('acl.role2')}: [${newStr}]\n${this.$t('acl.permission')}: ${item.origin.permissions}\n${this.$t('status')}: ${
item.origin.enabled item.origin.enabled
}` }`
break break
@ -260,9 +264,9 @@ export default {
const str = item.current.roles const str = item.current.roles
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('') const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('')
item.changeDescription = `应用触发器${item.current.name}\n资源类型${this.allResourceTypesMap.get( item.changeDescription = `${this.$t('acl.applyTrigger')}: ${item.current.name}\n${this.$t('acl.resourceType')}: ${this.allResourceTypesMap.get(
item.current.resource_type_id item.current.resource_type_id
)}资源名${item.current.wildcard}角色[${newStr}]\n权限${item.current.permissions}\n状态${ )}this.$t('acl.resourceName')${item.current.wildcard}${this.$t('acl.role2')}: [${newStr}]\n${this.$t('acl.permission')}: ${item.current.permissions}\n${this.$t('status')}: ${
item.current.enabled item.current.enabled
}` }`
break break
@ -271,9 +275,9 @@ export default {
const str = item.current.roles const str = item.current.roles
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('') const newStr = newArr.map((i) => this.allRolesMap.get(Number(i))).join('')
item.changeDescription = `取消触发器${item.current.name}\n资源类型${this.allResourceTypesMap.get( item.changeDescription = `${this.$t('acl.cancelTrigger')}: ${item.current.name}\n${this.$t('acl.resourceType')}: ${this.allResourceTypesMap.get(
item.current.resource_type_id item.current.resource_type_id
)}资源名${item.current.wildcard}角色[${newStr}]\n权限${item.current.permissions}\n状态${ )}this.$t('acl.resourceName')${item.current.wildcard}${this.$t('acl.role2')}: [${newStr}]\n${this.$t('acl.permission')}: ${item.current.permissions}\n${this.$t('status')}: ${
item.current.enabled item.current.enabled
}` }`
break break

View File

@ -1,7 +1,7 @@
<template> <template>
<CustomDrawer <CustomDrawer
:hasFooter="false" :hasFooter="false"
title="正则匹配结果" :title="$t('acl.viewMatchResult')"
:visible="patternVisible" :visible="patternVisible"
width="500" width="500"
@close=" @close="
@ -16,16 +16,16 @@
class="ops-stripe-table" class="ops-stripe-table"
:data="tableData" :data="tableData"
:max-height="`${windowHeight - 110}px`"> :max-height="`${windowHeight - 110}px`">
<vxe-table-column field="name" title="资源名"></vxe-table-column> <vxe-table-column field="name" :title="$t('acl.resourceName')"></vxe-table-column>
<vxe-table-column field="uid" title="创建人"> <vxe-table-column field="uid" :title="$t('acl.creator')">
<template #default="{row}"> <template #default="{row}">
{{ getRoleName(row.uid) }} {{ getRoleName(row.uid) }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="created_at" title="创建时间"></vxe-table-column> <vxe-table-column field="created_at" :title="$t('created_at')"></vxe-table-column>
<template slot="empty"> <template slot="empty">
<img :src="require(`@/assets/data_empty.png`)" /> <img :src="require(`@/assets/data_empty.png`)" />
<p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">暂无数据</p> <p style="font-size: 14px; line-height: 17px; color: rgba(0, 0, 0, 0.6)">{{ $t('noData') }}</p>
</template> </template>
</vxe-table> </vxe-table>
</CustomDrawer> </CustomDrawer>

View File

@ -8,33 +8,38 @@
width="500px" width="500px"
> >
<a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> <a-form :form="form" @submit="handleSubmit" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="用户名(英文)"> <a-form-item :label="$t('acl.username')">
<a-input <a-input
name="username" name="username"
placeholder="英文名" :placeholder="$t('acl.username_placeholder')"
v-decorator="['username', { rules: [{ required: true, message: '请输入用户名' }] }]" v-decorator="['username', { rules: [{ required: true, message: $t('acl.username_placeholder') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="中文名"> <a-form-item :label="$t('acl.nickname')">
<a-input name="nickname" v-decorator="['nickname', { rules: [] }]" /> <a-input
name="nickname"
:placeholder="$t('acl.nickname_placeholder')"
v-decorator="['nickname', { rules: [] }]"
/>
</a-form-item> </a-form-item>
<a-form-item label="密码"> <a-form-item :label="$t('acl.password')">
<a-input <a-input
type="password" type="password"
name="password" name="password"
v-decorator="['password', { rules: [{ required: true, message: '请输入密码' }] }]" :placeholder="$t('acl.password_placeholder')"
v-decorator="['password', { rules: [{ required: true, message: $t('acl.password_placeholder') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="部门"> <a-form-item :label="$t('acl.department')">
<a-input name="department" v-decorator="['department', { rules: [] }]" /> <a-input name="department" v-decorator="['department', { rules: [] }]" />
</a-form-item> </a-form-item>
<a-form-item label="小组"> <a-form-item :label="$t('acl.group')">
<a-input name="catalog" v-decorator="['catalog', { rules: [] }]" /> <a-input name="catalog" v-decorator="['catalog', { rules: [] }]" />
</a-form-item> </a-form-item>
<a-form-item label="邮箱"> <a-form-item :label="$t('acl.email')">
<a-input <a-input
name="email" name="email"
v-decorator="[ v-decorator="[
@ -43,11 +48,11 @@
rules: [ rules: [
{ {
type: 'email', type: 'email',
message: '请输入正确的邮箱!', message: $t('acl.email_placeholder'),
}, },
{ {
required: true, required: true,
message: '请输入邮箱', message: $t('acl.email_placeholder'),
}, },
], ],
}, },
@ -55,14 +60,14 @@
/> />
</a-form-item> </a-form-item>
<a-form-item label="手机号码"> <a-form-item :label="$t('acl.mobile')">
<a-input <a-input
name="mobile" name="mobile"
v-decorator="['mobile', { rules: [{ message: '请输入正确的手机号码', pattern: /^1\d{10}$/ }] }]" v-decorator="['mobile', { rules: [{ message: $t('acl.mobileTips'), pattern: /^1\d{10}$/ }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="是否锁定"> <a-form-item :label="$t('acl.isBlock')">
<a-switch @change="onChange" name="block" v-decorator="['block', { rules: [], valuePropName: 'checked' }]" /> <a-switch @change="onChange" name="block" v-decorator="['block', { rules: [], valuePropName: 'checked' }]" />
</a-form-item> </a-form-item>
@ -71,8 +76,8 @@
</a-form-item> </a-form-item>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="onClose">取消</a-button> <a-button @click="onClose">{{ $t('cancel') }}</a-button>
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</a-form> </a-form>
</CustomDrawer> </CustomDrawer>
@ -85,7 +90,6 @@ export default {
name: 'AttributeForm', name: 'AttributeForm',
data() { data() {
return { return {
drawerTitle: '新增用户',
drawerVisible: false, drawerVisible: false,
} }
}, },
@ -94,7 +98,11 @@ export default {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
}, },
computed: {}, computed: {
drawerTitle() {
return this.$t('acl.addUser')
},
},
mounted() {}, mounted() {},
methods: { methods: {
handleCreate() { handleCreate() {
@ -142,7 +150,7 @@ export default {
}, },
updateUser(attrId, data) { updateUser(attrId, data) {
updateUserById(attrId, data).then((res) => { updateUserById(attrId, data).then((res) => {
this.$message.success(`更新成功`) this.$message.success(this.$t('updateSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
@ -151,17 +159,11 @@ export default {
createUser(data) { createUser(data) {
addUser(data).then((res) => { addUser(data).then((res) => {
this.$message.success(`添加成功`) this.$message.success(this.$t('addSuccess'))
this.handleOk() this.handleOk()
this.onClose() this.onClose()
}) })
// .catch(err => this.requestFailed(err))
}, },
// requestFailed (err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// }
}, },
watch: {}, watch: {},
props: { props: {

View File

@ -1,6 +1,6 @@
<template> <template>
<CustomDrawer :closable="true" :visible="visible" width="500px" @close="handleClose" title="组用户"> <CustomDrawer :closable="true" :visible="visible" width="500px" @close="handleClose" :title="$t('acl.groupUser')">
<a-form-item label="添加用户" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> <a-form-item :label="$t('acl.addUser')" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-row> <a-row>
<a-col span="15"> <a-col span="15">
<el-select v-model="selectedChildrenRole" multiple collapse-tags size="small" filterable> <el-select v-model="selectedChildrenRole" multiple collapse-tags size="small" filterable>
@ -14,14 +14,14 @@
</el-select> </el-select>
</a-col> </a-col>
<a-col span="5" offset="1"> <a-col span="5" offset="1">
<a-button style="display: inline-block" @click="handleAddRole">确定</a-button> <a-button style="display: inline-block" @click="handleAddRole">{{ $t('confirm') }}</a-button>
</a-col> </a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
<a-card> <a-card>
<a-row :gutter="24" v-for="(record, index) in records" :key="record.id" :style="{ marginBottom: '5px' }"> <a-row :gutter="24" v-for="(record, index) in records" :key="record.id" :style="{ marginBottom: '5px' }">
<a-col :span="20">{{ index + 1 }}{{ record.nickname }}</a-col> <a-col :span="20">{{ index + 1 }}{{ record.nickname }}</a-col>
<a-col :span="4"><a-button type="danger" size="small" @click="handleRevokeUser(record)">移除</a-button></a-col> <a-col :span="4"><a-button type="danger" size="small" @click="handleRevokeUser(record)">{{ $t('acl.remove') }}</a-button></a-col>
</a-row> </a-row>
</a-card> </a-card>
</CustomDrawer> </CustomDrawer>
@ -63,34 +63,22 @@ export default {
this.visible = true this.visible = true
this.loadRecords(roleId) this.loadRecords(roleId)
}, },
// filterOption(input, option) {
// return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
// },
async handleAddRole() { async handleAddRole() {
// await this.selectedChildrenRole.forEach(item => {
// addParentRole(item, this.roleId, { app_id: this.$route.name.split('_')[0] }).then(res => {
// this.$message.success('添加成功')
// })
// // .catch(err=>{
// // this.$httpError(err)
// // })
// })
await addBatchParentRole(this.roleId, { await addBatchParentRole(this.roleId, {
child_ids: this.selectedChildrenRole, child_ids: this.selectedChildrenRole,
app_id: this.$route.name.split('_')[0], app_id: this.$route.name.split('_')[0],
}) })
this.loadRecords(this.roleId) this.loadRecords(this.roleId)
this.$message.success('添加完成') this.$message.success(this.$t('addSuccess'))
this.selectedChildrenRole = [] this.selectedChildrenRole = []
}, },
handleRevokeUser(record) { handleRevokeUser(record) {
const that = this const that = this
this.$confirm({ this.$confirm({
content: '是否确定要移除该用户', content: that.$t('acl.deleteUserConfirm'),
onOk() { onOk() {
delParentRole(record.role.id, that.roleId, { app_id: that.$route.name.split('_')[0] }).then((res) => { delParentRole(record.role.id, that.roleId, { app_id: that.$route.name.split('_')[0] }).then((res) => {
that.$message.success('删除成功!') that.$message.success(that.$t('deleteSuccess'))
that.loadRecords(that.roleId) that.loadRecords(that.roleId)
}) })
// .catch(err=>that.$httpError(err)) // .catch(err=>that.$httpError(err))

View File

@ -1,19 +1,19 @@
<template> <template>
<div class="acl-operation-history"> <div class="acl-operation-history">
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab="权限变更"> <a-tab-pane key="1" :tab="$t('acl.permissionChange')">
<permisson-table></permisson-table> <permisson-table></permisson-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="角色变更"> <a-tab-pane key="2" :tab="$t('acl.roleChange')">
<role-history-table></role-history-table> <role-history-table></role-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3" tab="资源变更"> <a-tab-pane key="3" :tab="$t('acl.resourceChange')">
<resource-history-table></resource-history-table> <resource-history-table></resource-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="4" tab="资源类型变更"> <a-tab-pane key="4" :tab="$t('acl.resourceTypeChange')">
<resource-type-history-table></resource-type-history-table> <resource-type-history-table></resource-type-history-table>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab="触发器变更"> <a-tab-pane key="5" :tab="$t('acl.triggerChange')">
<trigger-history-table></trigger-history-table> <trigger-history-table></trigger-history-table>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>

View File

@ -22,22 +22,22 @@
:height="`${windowHeight - windowHeightMinus}px`" :height="`${windowHeight - windowHeightMinus}px`"
:scroll-y="{ enabled: false }" :scroll-y="{ enabled: false }"
> >
<vxe-column field="created_at" width="144px" title="操作时间"> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')">
<template #default="{ row }"> <template #default="{ row }">
<span>{{ row.deleted_at || row.updated_at || row.created_at }}</span> <span>{{ row.deleted_at || row.updated_at || row.created_at }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{ <a-tag :color="row.operate_type === 'grant' ? 'green' : 'red'">{{
operateTypeMap.get(row.operate_type) operateTypeMap.get(row.operate_type)
}}</a-tag> }}</a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="rid" title="用户"></vxe-column> <vxe-column field="rid" :title="$t('user')"></vxe-column>
<vxe-column field="resource_type_id" title="资源类型"></vxe-column> <vxe-column field="resource_type_id" :title="$t('acl.resourceType')"></vxe-column>
<vxe-column field="resources" title="资源"> <vxe-column field="resources" :title="$t('acl.resource')">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="row.resource_ids.length > 0"> <template v-if="row.resource_ids.length > 0">
<a-tooltip placement="top"> <a-tooltip placement="top">
@ -56,14 +56,14 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="权限"> <vxe-column :title="$t('acl.permission')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index"> <a-tag v-for="(perm, index) in row.permission_ids" :key="'perms_' + perm + index">
{{ perm }} {{ perm }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -108,10 +108,6 @@ export default {
allRolesMap: new Map(), allRolesMap: new Map(),
allUsersMap: new Map(), allUsersMap: new Map(),
allResourceTypesMap: new Map(), allResourceTypesMap: new Map(),
operateTypeMap: new Map([
['grant', '授权'],
['revoke', '撤销'],
]),
queryParams: { queryParams: {
page: 1, page: 1,
page_size: 50, page_size: 50,
@ -120,57 +116,63 @@ export default {
}, },
permissionTableAttrList: [ permissionTableAttrList: [
{ {
alias: '日期', alias: this.$t('acl.date'),
is_choice: false, is_choice: false,
name: 'datetime', name: 'datetime',
value_type: '3', value_type: '3',
}, },
{ {
alias: '应用', alias: this.$t('acl.app'),
is_choice: true, is_choice: true,
name: 'app_id', name: 'app_id',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '操作员', alias: this.$t('acl.operator'),
is_choice: true, is_choice: true,
name: 'operate_uid', name: 'operate_uid',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '用户', alias: this.$t('user'),
is_choice: true, is_choice: true,
name: 'rid', name: 'rid',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '资源类型', alias: this.$t('acl.resourceType'),
is_choice: true, is_choice: true,
name: 'resource_type_id', name: 'resource_type_id',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '资源', alias: this.$t('acl.resource'),
is_choice: true, is_choice: true,
name: 'resource_id', name: 'resource_id',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '操作', alias: this.$t('operation'),
is_choice: true, is_choice: true,
name: 'operate_type', name: 'operate_type',
value_type: '2', value_type: '2',
choice_value: [{ 授权: 'grant' }, { 撤销: 'revoke' }], choice_value: [{ [this.$t('grant')]: 'grant' }, { [this.$t('acl.cancel')]: 'revoke' }],
}, },
], ],
} }
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['grant', this.$t('grant')],
['revoke', this.$t('acl.cancel')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -183,10 +185,10 @@ export default {
}, },
async created() { async created() {
this.$watch( this.$watch(
function() { function () {
return this.permissionTableAttrList[3].choice_value return this.permissionTableAttrList[3].choice_value
}, },
function() { function () {
delete this.$refs.child.queryParams.rid delete this.$refs.child.queryParams.rid
delete this.$refs.child.queryParams.resource_type_id delete this.$refs.child.queryParams.resource_type_id
delete this.$refs.child.queryParams.resource_id delete this.$refs.child.queryParams.resource_id

View File

@ -4,7 +4,7 @@
ref="child" ref="child"
:attrList="resourceTableAttrList" :attrList="resourceTableAttrList"
:hasSwitch="true" :hasSwitch="true"
switchValue="" :switchValue="$t('acl.group2')"
@onSwitchChange="onSwitchChange" @onSwitchChange="onSwitchChange"
@expandChange="handleExpandChange" @expandChange="handleExpandChange"
@search="handleSearch" @search="handleSearch"
@ -24,30 +24,30 @@
:data="tableData" :data="tableData"
:height="`${windowHeight - windowHeightMinus}px`" :height="`${windowHeight - windowHeightMinus}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="link_id" title="资源名"> <vxe-column field="link_id" :title="$t('acl.resouceName')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.description }} {{ row.description }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -87,47 +87,6 @@ export default {
allApps: [], allApps: [],
allUsersMap: new Map(), allUsersMap: new Map(),
allResourcesMap: new Map(), allResourcesMap: new Map(),
resourceTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '应用',
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: '资源名',
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
},
],
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['update', 'orange'], ['update', 'orange'],
@ -144,10 +103,10 @@ export default {
}, },
async created() { async created() {
this.$watch( this.$watch(
function() { function () {
return this.resourceTableAttrList[3].choice_value return this.resourceTableAttrList[3].choice_value
}, },
function() { function () {
delete this.$refs.child.queryParams.link_id delete this.$refs.child.queryParams.link_id
} }
) )
@ -158,6 +117,13 @@ export default {
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -167,6 +133,44 @@ export default {
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
resourceTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.app'),
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.resourceName'),
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ [this.$t('create')]: 'create' }, { [this.$t('update')]: 'update' }, { [this.$t('delete')]: 'delete' }],
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -347,7 +351,7 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
item.description = `新建资源${item.current.name}` item.description = `${this.$t('acl.newResource')}${item.current.name}`
break break
} }
case 'update': { case 'update': {
@ -357,10 +361,10 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
const str = ` ${key} : 改为 ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.description += str item.description += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} ` const str = ` ${key} : ${oldVal} -> ${newVal} `
item.description += str item.description += str
} }
} }
@ -369,18 +373,18 @@ export default {
const currentResource_ids = item.currentResource_ids const currentResource_ids = item.currentResource_ids
if (!_.isEqual(originResource_ids, currentResource_ids)) { if (!_.isEqual(originResource_ids, currentResource_ids)) {
if (originResource_ids.length === 0) { if (originResource_ids.length === 0) {
const str = ` resource_ids : 新增 ${currentResource_ids} ` const str = ` resource_ids : ${this.$t('new')} ${currentResource_ids} `
item.description += str item.description += str
} else { } else {
const str = ` resource_ids : ${originResource_ids} 改为 ${currentResource_ids} ` const str = ` resource_ids : ${originResource_ids} -> ${currentResource_ids} `
item.description += str item.description += str
} }
} }
if (!item.description) item.description = '没有修改' if (!item.description) item.description = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
item.description = `删除资源${item.origin.name}` item.description = `${this.$t('acl.deleteResource')}${item.origin.name}`
break break
} }
} }

View File

@ -18,30 +18,30 @@
:loading="loading" :loading="loading"
:height="`${windowHeight - windowHeightMinus}px`" :height="`${windowHeight - windowHeightMinus}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="link_id" width="159px" title="资源类型名"> <vxe-column field="link_id" width="159px" :title="$t('acl.resourceTypeName')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.changeDescription }} {{ row.changeDescription }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -78,47 +78,6 @@ export default {
allApps: [], allApps: [],
allUsersMap: new Map(), allUsersMap: new Map(),
allResourcesMap: new Map(), allResourcesMap: new Map(),
resourceTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '应用',
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: '资源类型',
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ 新建: 'create' }, { 修改: 'update' }, { 删除: 'delete' }],
},
],
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['update', 'orange'], ['update', 'orange'],
@ -135,10 +94,10 @@ export default {
}, },
async created() { async created() {
this.$watch( this.$watch(
function() { function () {
return this.resourceTableAttrList[3].choice_value return this.resourceTableAttrList[3].choice_value
}, },
function() { function () {
delete this.$refs.child.queryParams.link_id delete this.$refs.child.queryParams.link_id
} }
) )
@ -149,6 +108,13 @@ export default {
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -158,6 +124,44 @@ export default {
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
resourceTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.app'),
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.resourceType'),
is_choice: true,
name: 'link_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [{ [this.$t('create')]: 'create' }, { [this.$t('update')]: 'update' }, { [this.$t('delete')]: 'delete' }],
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -283,10 +287,12 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
const description = item.current?.description === undefined ? '' : item.current?.description const description = item.current?.description === undefined ? this.$t('acl.none') : item.current?.description
const permission = const permission =
item.extra.permission_ids?.current === undefined ? '' : item.extra.permission_ids?.current item.extra.permission_ids?.current === undefined ? this.$t('acl.none') : item.extra.permission_ids?.current
item.changeDescription = `新增资源类型${item.current.name}\n描述${description}\n权限${permission}` item.changeDescription = `${this.$t('acl.addReourceType')}${item.current.name}\n${this.$t(
'desc'
)}${description}\n${this.$t('acl.permission')}${permission}`
break break
} }
case 'update': { case 'update': {
@ -296,27 +302,29 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null || oldVal === '') { if (oldVal === null || oldVal === '') {
const str = ` ${key} : 改为 ${newVal} \n` const str = ` ${key} : -> ${newVal} \n`
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} \n` const str = ` ${key} : ${oldVal} -> ${newVal} \n`
item.changeDescription += str item.changeDescription += str
} }
} }
} }
const currentPerms = const currentPerms =
item.extra.permission_ids?.current === undefined ? '' : item.extra.permission_ids?.current item.extra.permission_ids?.current === undefined ? this.$t('acl.none') : item.extra.permission_ids?.current
const originPerms = item.extra.permission_ids?.origin === undefined ? '' : item.extra.permission_ids?.origin const originPerms = item.extra.permission_ids?.origin === undefined ? this.$t('acl.none') : item.extra.permission_ids?.origin
if (!_.isEqual(currentPerms, originPerms)) { if (!_.isEqual(currentPerms, originPerms)) {
item.changeDescription += ` permission_ids : ${originPerms} 改为 ${currentPerms} ` item.changeDescription += ` permission_ids : ${originPerms} -> ${currentPerms} `
} }
if (!item.changeDescription) item.changeDescription = '没有修改' if (!item.changeDescription) item.changeDescription = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
const description = item.origin?.description === undefined ? '' : item.origin?.description const description = item.origin?.description === undefined ? this.$t('acl.none') : item.origin?.description
const permission = item.extra.permission_ids?.origin === undefined ? '' : item.extra.permission_ids?.origin const permission = item.extra.permission_ids?.origin === undefined ? this.$t('acl.none') : item.extra.permission_ids?.origin
item.changeDescription = `删除资源类型${item.origin.name}\n描述${description}\n权限${permission}` item.changeDescription = `${this.$t('acl.deleteResourceType')}: ${item.origin.name}\n${this.$t(
'desc'
)}${description}\n${this.$t('acl.permission')}: ${permission}`
break break
} }
} }

View File

@ -4,7 +4,7 @@
ref="child" ref="child"
:attrList="roleTableAttrList" :attrList="roleTableAttrList"
:hasSwitch="true" :hasSwitch="true"
switchValue="角色关系" :switchValue="$t('acl.roleRelation')"
@onSwitchChange="onSwitchChange" @onSwitchChange="onSwitchChange"
@search="handleSearch" @search="handleSearch"
@searchFormReset="searchFormReset" @searchFormReset="searchFormReset"
@ -19,16 +19,16 @@
:loading="loading" :loading="loading"
:height="`${windowHeight - 310}px`" :height="`${windowHeight - 310}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="112px" title="操作"> <vxe-column field="operate_type" width="112px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<template> <template>
<a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag> <a-tag :color="handleTagColor(row.operate_type)">{{ operateTypeMap.get(row.operate_type) }}</a-tag>
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column :title="checked ? '角色' : '角色'"> <vxe-column :title="checked ? $t('acl.role2') : $t('acl.role2')">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="!checked"> <template v-if="!checked">
<a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag> <a-tag color="blue">{{ row.current.name || row.origin.name }}</a-tag>
@ -40,7 +40,7 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column :title="checked ? '继承自' : '管理员'" :width="checked ? '350px' : '80px'"> <vxe-column :title="checked ? $t('acl.inheritedFrom') : $t('acl.admin')" :width="checked ? '350px' : '80px'">
<template #default="{ row }"> <template #default="{ row }">
<template v-if="!checked"> <template v-if="!checked">
<a-icon type="check" v-if="row.current.is_app_admin" /> <a-icon type="check" v-if="row.current.is_app_admin" />
@ -52,14 +52,14 @@
</template> </template>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述" v-if="!checked"> <vxe-column :title="$t('desc')" v-if="!checked">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.description }} {{ row.description }}
</p> </p>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="source" width="100px" title="来源"></vxe-column> <vxe-column field="source" width="100px" :title="$t('acl.source')"></vxe-column>
</vxe-table> </vxe-table>
<pager <pager
:current-page.sync="queryParams.page" :current-page.sync="queryParams.page"
@ -94,13 +94,6 @@ export default {
allRoles: [], allRoles: [],
allRolesMap: new Map(), allRolesMap: new Map(),
allUsersMap: new Map(), allUsersMap: new Map(),
operateTypeMap: new Map([
['create', '新建'],
['delete', '删除'],
['update', '修改'],
['role_relation_add', '添加角色关系'],
['role_relation_delete', '删除角色关系'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['delete', 'red'], ['delete', 'red'],
@ -115,49 +108,60 @@ export default {
start: '', start: '',
end: '', end: '',
}, },
roleTableAttrList: [ }
},
computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
['role_relation_add', this.$t('acl.roleRelationAdd')],
['role_relation_delete', this.$t('acl.roleRelationDelete')],
])
},
windowHeight() {
return this.$store.state.windowHeight
},
tableDataLength() {
return this.tableData.length
},
roleTableAttrList() {
return [
{ {
alias: '日期', alias: this.$t('acl.date'),
is_choice: false, is_choice: false,
name: 'datetime', name: 'datetime',
value_type: '3', value_type: '3',
}, },
{ {
alias: '应用', alias: this.$t('acl.app'),
is_choice: true, is_choice: true,
name: 'app_id', name: 'app_id',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '操作员', alias: this.$t('acl.operator'),
is_choice: true, is_choice: true,
name: 'operate_uid', name: 'operate_uid',
value_type: '2', value_type: '2',
choice_value: [], choice_value: [],
}, },
{ {
alias: '操作', alias: this.$t('operation'),
is_choice: true, is_choice: true,
name: 'operate_type', name: 'operate_type',
value_type: '2', value_type: '2',
choice_value: [ choice_value: [
{ 新建: 'create' }, { [this.$t('create')]: 'create' },
{ 修改: 'update' }, { [this.$t('update')]: 'update' },
{ 删除: 'delete' }, { [this.$t('delete')]: 'delete' },
{ 添加角色关系: 'role_relation_add' }, { [this.$t('acl.roleRelationAdd')]: 'role_relation_add' },
{ 删除角色关系: 'role_relation_delete' }, { [this.$t('acl.roleRelationDelete')]: 'role_relation_delete' },
], ],
}, },
], ]
}
},
computed: {
windowHeight() {
return this.$store.state.windowHeight
},
tableDataLength() {
return this.tableData.length
}, },
}, },
async created() { async created() {
@ -277,7 +281,7 @@ export default {
switch (operate_type) { switch (operate_type) {
// create // create
case 'create': { case 'create': {
item.description = `新建角色${item.current.name}` item.description = `${this.$t('acl.addRole')}${item.current.name}`
break break
} }
case 'update': { case 'update': {
@ -287,15 +291,15 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
const str = ` ${key} : 改为 ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.description += str item.description += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} ` const str = ` ${key} : ${oldVal} -> ${newVal} `
item.description += str item.description += ` ${key} : ${oldVal} -> ${newVal} `
} }
} }
} }
if (!item.description) item.description = '没有修改' if (!item.description) item.description = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
@ -325,7 +329,7 @@ export default {
resourceMap.forEach((value, key) => { resourceMap.forEach((value, key) => {
permsArr.push(`${id2resources[key].name}${value}`) permsArr.push(`${id2resources[key].name}${value}`)
}) })
item.description = `继承者${child_ids}\n继承自${parent_ids}\n涉及资源及权限\n${permsArr.join(`\n`)}` item.description = `${this.$t('acl.heir')}${child_ids}\n${this.$t('acl.inheritedFrom')}${parent_ids}\n${this.$t('acl.involvingRP')}\n${permsArr.join(`\n`)}`
break break
} }
} }

View File

@ -18,23 +18,23 @@
:loading="loading" :loading="loading"
:height="`${windowHeight - windowHeightMinus}px`" :height="`${windowHeight - windowHeightMinus}px`"
> >
<vxe-column field="created_at" width="144px" title="操作时间"></vxe-column> <vxe-column field="created_at" width="144px" :title="$t('acl.operateTime')"></vxe-column>
<vxe-column field="operate_uid" width="130px" title="操作员"></vxe-column> <vxe-column field="operate_uid" width="130px" :title="$t('acl.operator')"></vxe-column>
<vxe-column field="operate_type" width="80px" title="操作"> <vxe-column field="operate_type" width="100px" :title="$t('operation')">
<template #default="{ row }"> <template #default="{ row }">
<a-tag :color="handleTagColor(row.operate_type)"> <a-tag :color="handleTagColor(row.operate_type)">
{{ operateTypeMap.get(row.operate_type) }} {{ operateTypeMap.get(row.operate_type) }}
</a-tag> </a-tag>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="trigger_id" width="250px" title="触发器"> <vxe-column field="trigger_id" width="250px" :title="$t('acl.trigger')">
<template #default="{ row }"> <template #default="{ row }">
<span> <span>
{{ row.current.name || row.origin.name }} {{ row.current.name || row.origin.name }}
</span> </span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column title="描述"> <vxe-column :title="$t('desc')">
<template #default="{ row }"> <template #default="{ row }">
<p> <p>
{{ row.changeDescription }} {{ row.changeDescription }}
@ -64,6 +64,7 @@ import { getTriggers } from '@/modules/acl/api/trigger'
import { searchUser } from '@/modules/acl/api/user' import { searchUser } from '@/modules/acl/api/user'
import { searchApp } from '@/modules/acl/api/app' import { searchApp } from '@/modules/acl/api/app'
export default { export default {
name: 'TriggerHistoryTable',
components: { SearchForm, Pager }, components: { SearchForm, Pager },
data() { data() {
return { return {
@ -82,13 +83,6 @@ export default {
allResourceTypesMap: new Map(), allResourceTypesMap: new Map(),
allResourcesMap: new Map(), allResourcesMap: new Map(),
allTriggersMap: new Map(), allTriggersMap: new Map(),
operateTypeMap: new Map([
['create', '新建'],
['update', '修改'],
['delete', '删除'],
['trigger_apply', '应用'],
['trigger_cancel', '取消'],
]),
colorMap: new Map([ colorMap: new Map([
['create', 'green'], ['create', 'green'],
['delete', 'red'], ['delete', 'red'],
@ -96,48 +90,6 @@ export default {
['trigger_apply', 'green'], ['trigger_apply', 'green'],
['trigger_cancel', 'red'], ['trigger_cancel', 'red'],
]), ]),
triggerTableAttrList: [
{
alias: '日期',
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: '应用',
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作员',
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: '触发器',
is_choice: true,
name: 'trigger_id',
value_type: '2',
choice_value: [],
},
{
alias: '操作',
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [
{ 新建: 'create' },
{ 修改: 'update' },
{ 删除: 'delete' },
{ 应用: 'trigger_apply' },
{ 取消: 'trigger_cancel' },
],
},
],
queryParams: { queryParams: {
page: 1, page: 1,
page_size: 50, page_size: 50,
@ -162,6 +114,15 @@ export default {
this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0 this.$refs.xTable.$el.querySelector('.vxe-table--body-wrapper').scrollTop = 0
}, },
computed: { computed: {
operateTypeMap() {
return new Map([
['create', this.$t('create')],
['update', this.$t('update')],
['delete', this.$t('delete')],
['trigger_apply', this.$t('acl.apply')],
['trigger_cancel', this.$t('cancel')],
])
},
windowHeight() { windowHeight() {
return this.$store.state.windowHeight return this.$store.state.windowHeight
}, },
@ -171,6 +132,50 @@ export default {
tableDataLength() { tableDataLength() {
return this.tableData.length return this.tableData.length
}, },
triggerTableAttrList() {
return [
{
alias: this.$t('acl.date'),
is_choice: false,
name: 'datetime',
value_type: '3',
},
{
alias: this.$t('acl.app'),
is_choice: true,
name: 'app_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.operator'),
is_choice: true,
name: 'operate_uid',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('acl.trigger'),
is_choice: true,
name: 'trigger_id',
value_type: '2',
choice_value: [],
},
{
alias: this.$t('operation'),
is_choice: true,
name: 'operate_type',
value_type: '2',
choice_value: [
{ [this.$t('create')]: 'create' },
{ [this.$t('update')]: 'update' },
{ [this.$t('delete')]: 'delete' },
{ [this.$t('acl.apply')]: 'trigger_apply' },
{ [this.$t('cancel')]: 'trigger_cancel' },
],
},
]
},
}, },
methods: { methods: {
async getTable(queryParams) { async getTable(queryParams) {
@ -270,9 +275,11 @@ export default {
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => id2roles[i].name).join('') const newStr = newArr.map((i) => id2roles[i].name).join('')
const { name, resource_type_id, wildcard, permissions, enabled } = item.current const { name, resource_type_id, wildcard, permissions, enabled } = item.current
item.changeDescription = `新增触发器${name}\n资源类型${ item.changeDescription = `${this.$t('acl.addTrigger')}:${name}\n${this.$t('acl.resourceType')}: ${
id2resource_types[resource_type_id].name id2resource_types[resource_type_id].name
}资源名${wildcard || ''}角色[${newStr}]\n权限${permissions}\n状态${enabled}` }this.$t('acl.resourceName')${wildcard || ''}${this.$t(
'acl.role2'
)}:[${newStr}]\nthis.$t('acl.permssion')}: ${permissions}\n${this.$t('status')}: ${enabled}`
break break
} }
case 'update': { case 'update': {
@ -282,15 +289,15 @@ export default {
const oldVal = item.origin[key] const oldVal = item.origin[key]
if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') { if (!_.isEqual(newVal, oldVal) && key !== 'updated_at' && key !== 'deleted_at' && key !== 'created_at') {
if (oldVal === null) { if (oldVal === null) {
const str = ` ${key} : 改为 ${newVal} ` const str = ` ${key} : -> ${newVal} `
item.changeDescription += str item.changeDescription += str
} else { } else {
const str = ` ${key} : ${oldVal} 改为 ${newVal} ` const str = ` ${key} : ${oldVal} -> ${newVal} `
item.changeDescription += str item.changeDescription += ` ${key} :${oldVal} -> ${newVal} `
} }
} }
} }
if (!item.changeDescription) item.changeDescription = '没有修改' if (!item.changeDescription) item.changeDescription = this.$t('acl.noChange')
break break
} }
case 'delete': { case 'delete': {
@ -298,9 +305,11 @@ export default {
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => id2roles[i].name).join('') const newStr = newArr.map((i) => id2roles[i].name).join('')
const { name, resource_type_id, wildcard, permissions, enabled } = item.origin const { name, resource_type_id, wildcard, permissions, enabled } = item.origin
item.changeDescription = `删除触发器${name}\n资源类型${ item.changeDescription = `${this.$t('acl.deleteTrigger')}: ${name}\n${this.$t('acl.resourceType')}: ${
id2resource_types[resource_type_id].name id2resource_types[resource_type_id].name
}资源名${wildcard || ''}角色[${newStr}]\n权限${permissions}\n状态${enabled}` }${this.$t('acl.resourceName')}: ${wildcard || ''}${this.$t(
'acl.role2'
)}:[${newStr}]\nthis.$t('acl.permssion')}: ${permissions}\n${this.$t('status')}: ${enabled}`
break break
} }
case 'trigger_apply': { case 'trigger_apply': {
@ -308,9 +317,11 @@ export default {
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => id2roles[i].name).join('') const newStr = newArr.map((i) => id2roles[i].name).join('')
const { name, resource_type_id, wildcard, permissions, enabled } = item.current const { name, resource_type_id, wildcard, permissions, enabled } = item.current
item.changeDescription = `应用触发器${name}\n资源类型${ item.changeDescription = `${this.$t('acl.applyTrigger')}: ${name}\n${this.$t('acl.resourceType')}: ${
id2resource_types[resource_type_id].name id2resource_types[resource_type_id].name
}资源名${wildcard || ''}角色[${newStr}]\n权限${permissions}\n状态${enabled}` }${this.$t('acl.resourceName')}: ${wildcard || ''}${this.$t(
'acl.role2'
)}:[${newStr}]\nthis.$t('acl.permssion')}: ${permissions}\n${this.$t('status')}: ${enabled}`
break break
} }
case 'trigger_cancel': { case 'trigger_cancel': {
@ -318,9 +329,11 @@ export default {
const newArr = str.slice(1, str.length - 1).split(', ') const newArr = str.slice(1, str.length - 1).split(', ')
const newStr = newArr.map((i) => id2roles[i].name).join('') const newStr = newArr.map((i) => id2roles[i].name).join('')
const { name, resource_type_id, wildcard, permissions, enabled } = item.current const { name, resource_type_id, wildcard, permissions, enabled } = item.current
item.changeDescription = `取消触发器${name}\n资源类型${ item.changeDescription = `${this.$t('acl.cancelTrigger')}: ${name}\n${this.$t('acl.resourceType')}: ${
id2resource_types[resource_type_id].name id2resource_types[resource_type_id].name
}资源名${wildcard || ''}角色[${newStr}]\n权限${permissions}\n状态${enabled}` }${this.$t('acl.resourceName')}: ${wildcard || ''}${this.$t(
'acl.role2'
)}:[${newStr}]\nthis.$t('acl.permssion')}: ${permissions}\n${this.$t('status')}: ${enabled}`
break break
} }
} }

View File

@ -1,11 +1,13 @@
<template> <template>
<div class="acl-resource-types"> <div class="acl-resource-types">
<div class="acl-resource-types-header"> <div class="acl-resource-types-header">
<a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem">{{ btnName }}</a-button> <a-button @click="handleCreate" type="primary" style="margin-right: 0.3rem">{{
$t('acl.addReourceType')
}}</a-button>
<a-input-search <a-input-search
class="ops-input" class="ops-input"
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }" :style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
placeholder="搜索 | 资源类型名" :placeholder="`${$t('search')} | ${$t('acl.resourceType')}`"
v-model="searchName" v-model="searchName"
allowClear allowClear
@search=" @search="
@ -28,28 +30,28 @@
<!-- 1 --> <!-- 1 -->
<vxe-table-column <vxe-table-column
field="name" field="name"
title="资源类型名" :title="$t('acl.resoureType')"
:min-width="175" :min-width="175"
fixed="left" fixed="left"
show-overflow show-overflow
></vxe-table-column> ></vxe-table-column>
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="description" title="描述" :min-width="175"></vxe-table-column> <vxe-table-column field="description" :title="$t('desc')" :min-width="175"></vxe-table-column>
<!-- 3 --> <!-- 3 -->
<vxe-table-column field="id" title="权限" :min-width="300"> <vxe-table-column field="id" :title="$t('acl.permission')" :min-width="300">
<template #default="{ row }"> <template #default="{ row }">
<a-tag color="cyan" v-for="perm in id2perms[row.id]" :key="perm.id">{{ perm.name }}</a-tag> <a-tag color="cyan" v-for="perm in id2perms[row.id]" :key="perm.id">{{ perm.name }}</a-tag>
</template> </template>
</vxe-table-column> </vxe-table-column>
<!-- 4 --> <!-- 4 -->
<vxe-table-column field="action" title="操作" :width="100" fixed="right"> <vxe-table-column field="action" :title="$t('operation')" :width="100" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<a @click="handleEdit(row)"><a-icon type="edit"/></a> <a @click="handleEdit(row)"><a-icon type="edit"/></a>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" okText="" cancelText=""> <a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)">
<a style="color: red"><a-icon type="delete"/></a> <a style="color: red"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</template> </template>
@ -87,42 +89,12 @@ export default {
loading: false, loading: false,
groups: [], groups: [],
id2perms: {}, id2perms: {},
btnName: '新增资源类型',
pageSizeOptions: [10, 25, 50, 100], pageSizeOptions: [10, 25, 50, 100],
tablePage: { tablePage: {
total: 0, total: 0,
currentPage: 1, currentPage: 1,
pageSize: 50, pageSize: 50,
}, },
tableColumns: [
{
title: '资源类型名',
field: 'name',
minWidth: '175px',
fixed: 'left',
showOverflow: 'tooltip',
},
{
title: '描述',
field: 'description',
minWidth: '175px',
},
{
title: '权限',
field: 'id',
minWidth: '300px',
slots: {
default: 'id_default',
},
},
{
title: '操作',
field: 'action',
minWidth: '175px',
slots: { default: 'action_default' },
fixed: 'right',
},
],
searchName: '', searchName: '',
} }
}, },
@ -200,15 +172,10 @@ export default {
}, },
deleteResourceType(id) { deleteResourceType(id) {
deleteResourceTypeById(id).then((res) => { deleteResourceTypeById(id).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })
// .catch(err => this.requestFailed(err))
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
handlePageChange({ currentPage, pageSize }) { handlePageChange({ currentPage, pageSize }) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
this.tablePage.pageSize = pageSize this.tablePage.pageSize = pageSize

View File

@ -6,10 +6,10 @@
</a-tabs> </a-tabs>
<div class="acl-resources-header"> <div class="acl-resources-header">
<a-space> <a-space>
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button> <a-button @click="handleCreate" type="primary">{{ $t('acl.addResource') }}</a-button>
<a-input-search <a-input-search
class="ops-input" class="ops-input"
placeholder="搜索 | 资源名" :placeholder="`${$t('search')} | ${$t('acl.resource')}`"
v-model="searchName" v-model="searchName"
@search=" @search="
() => { () => {
@ -20,10 +20,10 @@
></a-input-search> ></a-input-search>
<div v-if="!!selectedRows.length" class="ops-list-batch-action"> <div v-if="!!selectedRows.length" class="ops-list-batch-action">
<span @click="handleBatchPerm">授权</span> <span @click="handleBatchPerm">{{ $t('grant') }}</span>
<a-divider type="vertical" /> <a-divider type="vertical" />
<span @click="handleBatchRevoke">权限回收</span> <span @click="handleBatchRevoke">{{ $t('acl.revoke') }}</span>
<span>选取: {{ selectedRows.length }} </span> <span>{{ $t('selectRows', { rows: selectedRows.length }) }}</span>
</div> </div>
</a-space> </a-space>
@ -36,7 +36,7 @@
$refs.resourceBatchPerm.open(currentType.id) $refs.resourceBatchPerm.open(currentType.id)
} }
" "
>便捷授权</a-button >{{ $t('acl.convenient') }}</a-button
> >
<a-switch <a-switch
v-model="isGroup" v-model="isGroup"
@ -50,7 +50,7 @@
$refs.xTable && $refs.xTable.getVxetableRef().clearCheckboxReserve() $refs.xTable && $refs.xTable.getVxetableRef().clearCheckboxReserve()
} }
" "
un-checked-children="" :un-checked-children="$t('acl.group2')"
></a-switch> ></a-switch>
</a-space> </a-space>
</div> </div>
@ -62,62 +62,63 @@
:data="tableData" :data="tableData"
highlight-hover-row highlight-hover-row
:height="`${windowHeight - 250}px`" :height="`${windowHeight - 250}px`"
:checkbox-config="{ reserve: true }" :checkbox-config="{ reserve: true, highlight: true, range: true }"
@checkbox-change="changeCheckbox" @checkbox-change="changeCheckbox"
@checkbox-all="changeCheckbox" @checkbox-all="changeCheckbox"
@checkbox-range-end="onSelectRangeEnd"
ref="xTable" ref="xTable"
row-id="id" row-id="id"
show-overflow show-overflow
resizable resizable
> >
<!-- 1 --> <!-- 1 -->
<vxe-table-column type="checkbox" fixed="left" :width="45"></vxe-table-column> <vxe-table-column type="checkbox" fixed="left" :width="60"></vxe-table-column>
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="name" title="资源名" :min-widh="150" fixed="left" show-overflow> <vxe-table-column field="name" :title="$t('acl.resourceName')" :min-widh="150" fixed="left" show-overflow>
<template #title="{ row }"> <template #title="{row}">
{{ row.isGroup ? '资源组名' : '资源名' }} {{ row.isGroup ? $t('acl.groupName') : $t('acl.resourceName') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<!-- 3 --> <!-- 3 -->
<vxe-table-column field="user" title="创建者" :min-widh="100"> </vxe-table-column> <vxe-table-column field="user" :title="$t('acl.creator')" :min-widh="100"> </vxe-table-column>
<!-- 4 --> <!-- 4 -->
<vxe-table-column field="created_at" title="创建时间" :min-widh="220" align="center"> </vxe-table-column> <vxe-table-column field="created_at" :title="$t('created_at')" :min-widh="220" align="center"> </vxe-table-column>
<!-- 5 --> <!-- 5 -->
<vxe-table-column field="updated_at" title="最后修改时间" :min-widh="220" fixed="center"> </vxe-table-column> <vxe-table-column field="updated_at" :title="$t('updated_at')" :min-widh="220" fixed="center"> </vxe-table-column>
<!-- 6 --> <!-- 6 -->
<vxe-table-column <vxe-table-column
field="action" field="action"
title="操作" :title="$t('operation')"
:min-widh="200" :min-widh="200"
fixed="right" fixed="right"
align="center" align="center"
show-overflow> show-overflow>
<template #default="{ row }"> <template #default="{row}">
<span v-show="isGroup"> <span v-show="isGroup">
<a @click="handleDisplayMember(row)">成员</a> <a @click="handleDisplayMember(row)">{{ $t('acl.member') }}</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a @click="handleGroupEdit(row)">编辑</a> <a @click="handleGroupEdit(row)">{{ $t('edit') }}</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
</span> </span>
<a-tooltip title="查看授权"> <a-tooltip :title="$t('acl.viewAuth')">
<a @click="handlePerm(row)"><a-icon type="eye" /></a> <a @click="handlePerm(row)"><a-icon type="eye"/></a>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-tooltip title="授权"> <a-tooltip :title="$t('grant')">
<a :style="{ color: '#4bbb13' }" @click="handlePermManage(row)"> <a :style="{ color: '#4bbb13' }" @click="handlePermManage(row)">
<a-icon type="usergroup-add" /> <a-icon type="usergroup-add" />
</a> </a>
</a-tooltip> </a-tooltip>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="" cancelText=""> <a-popconfirm :title="$t('confirmDelete')" @confirm="handleDelete(row)" @cancel="cancel" :okText="$t('yes')" :cancelText="$t('no')">
<a style="color: red"><a-icon type="delete" /></a> <a style="color: red"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</template> </template>
</vxe-table-column> </vxe-table-column>
@ -135,9 +136,9 @@
</vxe-pager> </vxe-pager>
</a-spin> </a-spin>
</div> </div>
<div v-else style="text-align: center; margin-top: 20%"> <div v-else style="text-align: center;margin-top:20%">
<a-icon style="font-size: 50px; margin-bottom: 20px; color: orange" type="info-circle" /> <a-icon style="font-size:50px; margin-bottom: 20px; color: orange" type="info-circle" />
<h3>暂无类型信息请先添加资源类型</h3> <h3>{{ $t('acl.addTypeTips') }}</h3>
</div> </div>
<resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm> <resourceForm ref="resourceForm" @fresh="handleOk"> </resourceForm>
<resourcePermForm ref="resourcePermForm"> </resourcePermForm> <resourcePermForm ref="resourcePermForm"> </resourcePermForm>
@ -187,7 +188,6 @@ export default {
pageSize: 50, pageSize: 50,
}, },
tableData: [], tableData: [],
btnName: '新增资源',
isGroup: false, isGroup: false,
allResourceTypes: [], allResourceTypes: [],
currentType: { id: 0 }, currentType: { id: 0 },
@ -196,7 +196,6 @@ export default {
selectedRows: [], selectedRows: [],
} }
}, },
beforeCreate() { beforeCreate() {
this.form = this.$form.createForm(this) this.form = this.$form.createForm(this)
}, },
@ -281,22 +280,16 @@ export default {
deleteResource(id) { deleteResource(id) {
if (!this.isGroup) { if (!this.isGroup) {
deleteResourceById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => { deleteResourceById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })
// .catch(err => this.requestFailed(err))
} else { } else {
deleteResourceGroup(id).then((res) => { deleteResourceGroup(id).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })
// .catch(err => this.requestFailed(err))
} }
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
cancel() {}, cancel() {},
handlePageChange({ currentPage, pageSize }) { handlePageChange({ currentPage, pageSize }) {
this.tablePage.currentPage = currentPage this.tablePage.currentPage = currentPage
@ -309,6 +302,10 @@ export default {
.getVxetableRef() .getVxetableRef()
.getCheckboxRecords() .getCheckboxRecords()
.concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords()) .concat(this.$refs.xTable.getVxetableRef().getCheckboxReserveRecords())
console.log(this.selectedRows)
},
onSelectRangeEnd({ records }) {
this.selectedRows = records
}, },
handleBatchPerm() { handleBatchPerm() {
this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup) this.$refs['resourcePermManageForm'].editPerm(this.selectedRows, this.isGroup)
@ -322,7 +319,7 @@ export default {
}, },
}, },
watch: { watch: {
'$route.name': function (newName, oldName) { '$route.name': function(newName, oldName) {
this.isGroup = false this.isGroup = false
this.tablePage = { this.tablePage = {
total: 0, total: 0,

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="acl-roles"> <div class="acl-roles">
<div class="acl-roles-header"> <div class="acl-roles-header">
<a-button @click="handleCreate" type="primary">{{ btnName }}</a-button> <a-button @click="handleCreate" type="primary">{{ $t('acl.addVisualRole') }}</a-button>
<a-input-search <a-input-search
class="ops-input" class="ops-input"
allowClear allowClear
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }" :style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
placeholder="搜索 | 角色名" :placeholder="`${$t('search')} | ${$t('acl.role')}`"
v-model="searchName" v-model="searchName"
@search=" @search="
() => { () => {
@ -15,7 +15,7 @@
} }
" "
></a-input-search> ></a-input-search>
<a-checkbox :checked="is_all" @click="handleClickBoxChange">所有角色</a-checkbox> <a-checkbox :checked="is_all" @click="handleClickBoxChange">{{ $t('acl.allRole') }}</a-checkbox>
</div> </div>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<ops-table <ops-table
@ -31,22 +31,23 @@
> >
<vxe-table-column <vxe-table-column
field="name" field="name"
title="角色名" :title="$t('acl.role')"
:min-width="150" :min-width="150"
align="left" align="left"
fixed="left" fixed="left"
sortable sortable
show-overflow> show-overflow
>
</vxe-table-column> </vxe-table-column>
<!-- 2 --> <!-- 2 -->
<vxe-table-column field="is_app_admin" title="管理员" :min-width="100" align="center"> <vxe-table-column field="is_app_admin" :title="$t('admin')" :min-width="100" align="center">
<template #default="{row}"> <template #default="{row}">
<a-icon type="check" v-if="row.is_app_admin" /> <a-icon type="check" v-if="row.is_app_admin" />
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="id" title="继承自" :min-width="150"> <vxe-table-column field="id" :title="$t('acl.inheritedFrom')" :min-width="150">
<template #default="{row}"> <template #default="{row}">
<a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag> <a-tag color="cyan" v-for="role in id2parents[row.id]" :key="role.id">{{ role.name }}</a-tag>
</template> </template>
@ -54,12 +55,12 @@
<vxe-table-column <vxe-table-column
field="uid" field="uid"
title="虚拟角色" :title="$t('acl.visualRole')"
:width="100" :width="120"
align="center" align="center"
:filters="[ :filters="[
{ label: '', value: 1 }, { label: $t('yes'), value: 1 },
{ label: '', value: 0 }, { label: $t('no'), value: 0 },
]" ]"
:filterMultiple="false" :filterMultiple="false"
:filter-method=" :filter-method="
@ -69,14 +70,14 @@
" "
> >
<template #default="{row}"> <template #default="{row}">
{{ row.uid ? '' : '' }} {{ row.uid ? $t('no') : $t('yes') }}
</template> </template>
</vxe-table-column> </vxe-table-column>
<vxe-table-column field="action" title="操作" :width="120" fixed="right"> <vxe-table-column field="action" :title="$t('operation')" :width="120" fixed="right">
<template #default="{row}"> <template #default="{row}">
<a-space> <a-space>
<a-tooltip title="资源列表"> <a-tooltip :title="$t('acl.resourceList')">
<a <a
v-if="$route.name !== 'acl_roles'" v-if="$route.name !== 'acl_roles'"
@click="handleDisplayUserResource(row)" @click="handleDisplayUserResource(row)"
@ -85,12 +86,18 @@
/></a> /></a>
</a-tooltip> </a-tooltip>
<a-tooltip <a-tooltip
title="用户列表" :title="$t('acl.userList')"
v-if="!row.uid" v-if="!row.uid"
><a @click="handleDisplayUserUnderRole(row)"><a-icon type="team"/></a ><a @click="handleDisplayUserUnderRole(row)"><a-icon type="team"/></a
></a-tooltip> ></a-tooltip>
<a @click="handleEdit(row)"><a-icon type="edit"/></a> <a @click="handleEdit(row)"><a-icon type="edit"/></a>
<a-popconfirm title="确认删除?" @confirm="handleDelete(row)" @cancel="cancel" okText="" cancelText=""> <a-popconfirm
:title="$t('confirmDelete')"
@confirm="handleDelete(row)"
@cancel="cancel"
:okText="$t('yes')"
:cancelText="$t('no')"
>
<a style="color: red"><a-icon type="delete"/></a> <a style="color: red"><a-icon type="delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
@ -138,52 +145,6 @@ export default {
currentPage: 1, currentPage: 1,
pageSize: 50, pageSize: 50,
}, },
tableColumns: [
{
title: '角色名',
field: 'name',
sortable: true,
minWidth: '150px',
fixed: 'left',
showOverflow: 'tooltip',
},
{
title: '管理员',
field: 'is_app_admin',
minWidth: '100px',
align: 'center',
slots: { default: 'is_app_admin_default' },
},
{
title: '继承自',
field: 'id',
minWidth: '150px',
slots: { default: 'inherit_default' },
},
{
title: '虚拟角色',
field: 'uid',
minWidth: '100px',
align: 'center',
filters: [
{ label: '', value: 1 },
{ label: '', value: 0 },
],
filterMultiple: false,
filterMethod: ({ value, row }) => {
return value === !row.uid
},
slots: { default: 'isVisualRole_default' },
},
{
title: '操作',
minWidth: '280px',
field: 'action',
fixed: 'right',
slots: { default: 'action_default' },
},
],
btnName: '新增虚拟角色',
is_all: this.$route.name === 'acl_roles', is_all: this.$route.name === 'acl_roles',
tableData: [], tableData: [],
allRoles: [], allRoles: [],
@ -286,15 +247,10 @@ export default {
deleteRole(id) { deleteRole(id) {
deleteRoleById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => { deleteRoleById(id, { app_id: this.$route.name.split('_')[0] }).then((res) => {
this.$message.success(`删除成功`) this.$message.success(this.$t('deleteSuccess'))
this.handleOk() this.handleOk()
}) })
// .catch(err => this.requestFailed(err))
}, },
// requestFailed(err) {
// const msg = ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试'
// this.$message.error(`${msg}`)
// },
cancel(e) { cancel(e) {
return false return false
}, },

View File

@ -15,9 +15,9 @@
</a-form-model-item> </a-form-model-item>
<a-form-model-item label=" " :colon="false"> <a-form-model-item label=" " :colon="false">
<a-space> <a-space>
<a-button type="primary" @click="changeVisible">{{ !visible ? '查看' : '隐藏' }}</a-button> <a-button type="primary" @click="changeVisible">{{ !visible ? $t('view') : $t('hide') }}</a-button>
<a-button type="danger" ghost @click="handleSumbit">重置</a-button> <a-button type="danger" ghost @click="handleSumbit">{{ $t('reset') }}</a-button>
<!-- <a-button @click="handleCancel">取消</a-button> --> <!-- <a-button @click="handleCancel">{{ $t('cancel') }}</a-button> -->
</a-space> </a-space>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
@ -62,13 +62,13 @@ export default {
handleSumbit() { handleSumbit() {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '重置', title: that.$t('reset'),
content: '确定重置用户密钥?', content: that.$t('acl.confirmResetSecret'),
onOk() { onOk() {
that.$refs.secretKeyForm.validate((valid) => { that.$refs.secretKeyForm.validate((valid) => {
if (valid) { if (valid) {
updateSecret().then((res) => { updateSecret().then((res) => {
that.$message.success('重置成功') that.$message.success(that.$t('operateSuccess'))
const { key, secret } = res const { key, secret } = res
that.form = { key, secret } that.form = { key, secret }
}) })

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="acl-trigger"> <div class="acl-trigger">
<div class="acl-trigger-header"> <div class="acl-trigger-header">
<a-button type="primary" @click="handleCreateTrigger">新增触发器</a-button> <a-button type="primary" @click="handleCreateTrigger">{{ $t('acl.addTrigger') }}</a-button>
<a-input-search <a-input-search
class="ops-input" class="ops-input"
:style="{ display: 'inline', marginLeft: '10px', width: '200px' }" :style="{ display: 'inline', marginLeft: '10px', width: '200px' }"
placeholder="搜索 | 名称" :placeholder="`${$t('search')} | ${$t('name')}`"
v-model="searchName" v-model="searchName"
allowClear allowClear
@search="filter" @search="filter"
@ -46,18 +46,18 @@
<a-tag v-for="(p, index) in row.permissions" :key="index">{{ p }}</a-tag> <a-tag v-for="(p, index) in row.permissions" :key="index">{{ p }}</a-tag>
</template> </template>
<template #enabled_default="{row}"> <template #enabled_default="{row}">
<a-tag v-if="row.enabled" color="#2db7f5">启用</a-tag> <a-tag v-if="row.enabled" color="#2db7f5">{{ $t('acl.enable') }}</a-tag>
<a-tag v-else color="grey">禁用</a-tag> <a-tag v-else color="grey">{{ $t('acl.disable') }}</a-tag>
</template> </template>
<template #action_default="{row}"> <template #action_default="{row}">
<a-space> <a-space>
<a-tooltip title="应用"> <a-tooltip :title="$t('acl.apply')">
<a @click="handleApplyTrigger(row)" :style="{ color: '#0f9d58' }"><a-icon type="appstore"/></a> <a @click="handleApplyTrigger(row)" :style="{ color: '#0f9d58' }"><a-icon type="appstore"/></a>
</a-tooltip> </a-tooltip>
<a-tooltip title="取消"> <a-tooltip :title="$t('cancel')">
<a @click="handleCancelTrigger(row)" :style="{ color: 'orange' }"><a-icon type="stop"/></a> <a @click="handleCancelTrigger(row)" :style="{ color: 'orange' }"><a-icon type="stop"/></a>
</a-tooltip> </a-tooltip>
<a-tooltip title="查看正则匹配结果"> <a-tooltip :title="$t('acl.viewMatchResult')">
<a @click="handlePattern(row)" :style="{ color: 'purple' }"><a-icon type="eye"/></a> <a @click="handlePattern(row)" :style="{ color: 'purple' }"><a-icon type="eye"/></a>
</a-tooltip> </a-tooltip>
<a @click="handleEditTrigger(row)"><a-icon type="edit"/></a> <a @click="handleEditTrigger(row)"><a-icon type="edit"/></a>
@ -67,7 +67,7 @@
<template slot="empty"> <template slot="empty">
<div> <div>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</vxe-grid> </vxe-grid>
@ -102,9 +102,19 @@ export default {
triggers: [], triggers: [],
id2parents: [], id2parents: [],
id2perms: {}, id2perms: {},
tableColumns: [ }
},
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
app_id() {
return this.$route.name.split('_')[0]
},
tableColumns() {
return [
{ {
title: '名称', title: this.$t('name'),
field: 'name', field: 'name',
sortable: true, sortable: true,
minWidth: '150px', minWidth: '150px',
@ -112,7 +122,7 @@ export default {
showOverflow: 'tooltip', showOverflow: 'tooltip',
}, },
{ {
title: '资源名', title: this.$t('acl.resource'),
field: 'wildcard', field: 'wildcard',
minWidth: '250px', minWidth: '250px',
showOverflow: 'tooltip', showOverflow: 'tooltip',
@ -121,15 +131,15 @@ export default {
}, },
}, },
{ {
title: '资源类型', title: this.$t('acl.resourceType'),
field: 'resource_type_id', field: 'resource_type_id',
minWidth: '100px', minWidth: '120px',
slots: { slots: {
default: 'resourceTypeRender_default', default: 'resourceTypeRender_default',
}, },
}, },
{ {
title: '创建人', title: this.$t('acl.creator'),
field: 'users', field: 'users',
minWidth: '150px', minWidth: '150px',
showOverflow: 'tooltip', showOverflow: 'tooltip',
@ -138,7 +148,7 @@ export default {
}, },
}, },
{ {
title: '角色', title: this.$t('acl.allRole'),
field: 'roles', field: 'roles',
minWidth: '150px', minWidth: '150px',
slots: { slots: {
@ -172,7 +182,7 @@ export default {
}, },
}, },
{ {
title: '权限', title: this.$t('acl.permission'),
field: 'permissions', field: 'permissions',
minWidth: '250px', minWidth: '250px',
slots: { slots: {
@ -180,7 +190,7 @@ export default {
}, },
}, },
{ {
title: '状态', title: this.$t('status'),
field: 'enabled', field: 'enabled',
minWidth: '100px', minWidth: '100px',
slots: { slots: {
@ -188,7 +198,7 @@ export default {
}, },
}, },
{ {
title: '操作', title: this.$t('operation'),
field: 'action', field: 'action',
width: '120px', width: '120px',
fixed: 'right', fixed: 'right',
@ -196,8 +206,8 @@ export default {
default: 'action_default', default: 'action_default',
}, },
}, },
], ]
} },
}, },
created() { created() {
this.loadRoles() this.loadRoles()
@ -206,15 +216,6 @@ export default {
beforeMount() { beforeMount() {
this.loadTriggers() this.loadTriggers()
}, },
computed: {
...mapState({
windowHeight: (state) => state.windowHeight,
}),
app_id() {
return this.$route.name.split('_')[0]
},
},
methods: { methods: {
loadTriggers() { loadTriggers() {
this.searchName = '' this.searchName = ''
@ -244,11 +245,11 @@ export default {
handleDeleteTrigger(record) { handleDeleteTrigger(record) {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '删除', title: that.$t('warning'),
content: '确认删除该触发器吗?', content: that.$t('acl.confirmDeleteTrigger'),
onOk() { onOk() {
deleteTrigger(record.id).then((res) => { deleteTrigger(record.id).then((res) => {
that.$message.success('删除成功') that.$message.success(that.$t('deleteSuccess'))
that.loadTriggers() that.loadTriggers()
}) })
// .catch(err => that.$httpError(err)) // .catch(err => that.$httpError(err))
@ -258,11 +259,11 @@ export default {
handleApplyTrigger(record) { handleApplyTrigger(record) {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '规则应用', title: that.$t('acl.ruleApply'),
content: '是否确定应用该触发器?', content: that.$t('acl.triggerTip1'),
onOk() { onOk() {
applyTrigger(record.id).then((res) => { applyTrigger(record.id).then((res) => {
that.$message.success('提交成功!') that.$message.success(that.$t('operateSuccess'))
}) })
// .catch(err => that.$httpError(err)) // .catch(err => that.$httpError(err))
}, },
@ -271,11 +272,11 @@ export default {
handleCancelTrigger(record) { handleCancelTrigger(record) {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '规则应用', title: that.$t('acl.ruleApply'),
content: '是否取消应用该触发器?', content: that.$t('acl.triggerTip2'),
onOk() { onOk() {
cancelTrigger(record.id).then((res) => { cancelTrigger(record.id).then((res) => {
that.$message.success('提交成功!') that.$message.success(that.$t('operateSuccess'))
}) })
// .catch(err => that.$httpError(err)) // .catch(err => that.$httpError(err))
}, },

View File

@ -1,198 +1,202 @@
<template> <template>
<div class="acl-users"> <div class="acl-users">
<div class="acl-users-header"> <div class="acl-users-header">
<a-button v-if="isAclAdmin" @click="handleCreate" type="primary">{{ btnName }}</a-button> <a-button v-if="isAclAdmin" @click="handleCreate" type="primary">{{ btnName }}</a-button>
<a-input-search <a-input-search
class="ops-input" class="ops-input"
allowClear allowClear
:style="{ display: 'inline', marginLeft: '10px' }" :style="{ width: '300px', display: 'inline', marginLeft: '10px' }"
placeholder="搜索 | 用户名、中文名" :placeholder="`${$t('search')} | ${$t('acl.nickname')} 、 ${$t('acl.username')}`"
v-model="searchName" v-model="searchName"
></a-input-search> ></a-input-search>
</div> </div>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<vxe-grid <vxe-grid
stripe stripe
class="ops-stripe-table" class="ops-stripe-table"
:columns="tableColumns" :columns="tableColumns"
:data="tableData" :data="tableData"
show-overflow show-overflow
highlight-hover-row highlight-hover-row
:height="`${windowHeight - 165}px`" :height="`${windowHeight - 165}px`"
size="small" size="small"
> >
<template #block_default="{row}"> <template #block_default="{row}">
<a-icon type="lock" v-if="row.block" /> <a-icon type="lock" v-if="row.block" />
</template> </template>
<template #action_default="{row}"> <template #action_default="{row}">
<a-space> <a-space>
<a :disabled="isAclAdmin ? false : true" @click="handleEdit(row)"> <a :disabled="isAclAdmin ? false : true" @click="handleEdit(row)">
<a-icon type="edit" /> <a-icon type="edit" />
</a> </a>
<a-tooltip title="权限汇总"> <a-tooltip :title="$t('acl.summaryPermissions')">
<a @click="handlePermCollect(row)"><a-icon type="solution"/></a> <a @click="handlePermCollect(row)"><a-icon type="solution"/></a>
</a-tooltip> </a-tooltip>
<a-popconfirm :title="`确认删除【${row.nickname || row.username}】?`" @confirm="deleteUser(row.uid)"> <a-popconfirm :title="$t('confirmDelete')" @confirm="deleteUser(row.uid)">
<a :style="{ color: 'red' }"><ops-icon type="icon-xianxing-delete"/></a> <a :style="{ color: 'red' }"><ops-icon type="icon-xianxing-delete"/></a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
</template> </template>
</vxe-grid> </vxe-grid>
</a-spin> </a-spin>
<userForm ref="userForm" :handleOk="handleOk"> </userForm> <userForm ref="userForm" :handleOk="handleOk"> </userForm>
<perm-collect-form ref="permCollectForm"></perm-collect-form> <perm-collect-form ref="permCollectForm"></perm-collect-form>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import userForm from './module/userForm' import userForm from './module/userForm'
import PermCollectForm from './module/permCollectForm' import PermCollectForm from './module/permCollectForm'
import { deleteUserById, searchUser, getOnDutyUser } from '@/modules/acl/api/user' import { deleteUserById, searchUser, getOnDutyUser } from '@/modules/acl/api/user'
export default { export default {
name: 'Users', name: 'Users',
components: { components: {
userForm, userForm,
PermCollectForm, PermCollectForm,
}, },
data() { data() {
return { return {
loading: false, loading: false,
tableColumns: [ onDutuUids: [],
{ allUsers: [],
title: '用户名', tableData: [],
field: 'username', searchName: '',
sortable: true, }
minWidth: '100px', },
fixed: 'left', beforeCreate() {
}, this.form = this.$form.createForm(this)
{ },
title: '中文名', async beforeMount() {
field: 'nickname', this.loading = true
minWidth: '100px', await this.getOnDutyUser()
}, this.search()
{ },
title: '加入时间', computed: {
field: 'date_joined', ...mapState({
minWidth: '160px', windowHeight: (state) => state.windowHeight,
align: 'center', }),
sortable: true, isAclAdmin: function() {
}, if (this.$store.state.user.roles.permissions.filter((item) => item === 'acl_admin').length > 0) {
{ return true
title: '锁定', } else {
field: 'block', return false
width: '150px', }
align: 'center', },
slots: { tableColumns() {
default: 'block_default', return [
}, {
}, title: this.$t('acl.username'),
{ field: 'username',
title: '操作', sortable: true,
field: 'action', minWidth: '100px',
width: '150px', fixed: 'left',
fixed: 'right', },
align: 'center', {
slots: { title: this.$t('acl.nickname'),
default: 'action_default', field: 'nickname',
}, minWidth: '100px',
}, },
], {
onDutuUids: [], title: this.$t('acl.joined_at'),
btnName: '新增用户', field: 'date_joined',
allUsers: [], minWidth: '160px',
tableData: [], align: 'center',
searchName: '', sortable: true,
} },
}, {
beforeCreate() { title: this.$t('acl.block'),
this.form = this.$form.createForm(this) field: 'block',
}, width: '150px',
async beforeMount() { align: 'center',
this.loading = true slots: {
await this.getOnDutyUser() default: 'block_default',
this.search() },
}, },
computed: { {
...mapState({ title: this.$t('operation'),
windowHeight: (state) => state.windowHeight, field: 'action',
}), width: '150px',
isAclAdmin: function() { fixed: 'right',
if (this.$store.state.user.roles.permissions.filter((item) => item === 'acl_admin').length > 0) { align: 'center',
return true slots: {
} else { default: 'action_default',
return false },
} },
}, ]
}, },
watch: { btnName() {
searchName: { return this.$t('acl.addUser')
immediate: true, },
handler(newVal, oldVal) { },
if (newVal) { watch: {
this.tableData = this.allUsers.filter( searchName: {
(item) => immediate: true,
(item.username && item.username.toLowerCase().includes(newVal.toLowerCase())) || handler(newVal, oldVal) {
(item.nickname && item.nickname.toLowerCase().includes(newVal.toLowerCase())) if (newVal) {
) this.tableData = this.allUsers.filter(
} else { (item) =>
this.tableData = this.allUsers (item.username && item.username.toLowerCase().includes(newVal.toLowerCase())) ||
} (item.nickname && item.nickname.toLowerCase().includes(newVal.toLowerCase()))
}, )
}, } else {
}, this.tableData = this.allUsers
mounted() {}, }
inject: ['reload'], },
},
methods: { },
async getOnDutyUser() { mounted() {},
await getOnDutyUser().then((res) => { inject: ['reload'],
this.onDutuUids = res.map((i) => i.uid)
}) methods: {
}, async getOnDutyUser() {
search() { await getOnDutyUser().then((res) => {
searchUser({ page_size: 10000 }).then((res) => { this.onDutuUids = res.map((i) => i.uid)
const ret = res.users.filter((u) => this.onDutuUids.includes(u.uid)) })
this.allUsers = ret },
this.tableData = ret search() {
this.loading = false searchUser({ page_size: 10000 }).then((res) => {
}) const ret = res.users.filter((u) => this.onDutuUids.includes(u.uid))
}, this.allUsers = ret
handlePermCollect(record) { this.tableData = ret
this.$refs['permCollectForm'].collect(record) this.loading = false
}, })
handleEdit(record) { },
this.$refs.userForm.handleEdit(record) handlePermCollect(record) {
}, this.$refs['permCollectForm'].collect(record)
async handleOk() { },
this.searchName = '' handleEdit(record) {
await this.getOnDutyUser() this.$refs.userForm.handleEdit(record)
this.search() },
}, async handleOk() {
handleCreate() { this.searchName = ''
this.$refs.userForm.handleCreate() await this.getOnDutyUser()
}, this.search()
deleteUser(uid) { },
deleteUserById(uid).then((res) => { handleCreate() {
this.$message.success(`删除成功`) this.$refs.userForm.handleCreate()
this.handleOk() },
}) deleteUser(uid) {
}, deleteUserById(uid).then((res) => {
}, this.$message.success(this.$t('deleteSuccess'))
} this.handleOk()
</script> })
},
<style lang="less" scoped> },
.acl-users { }
border-radius: 15px; </script>
background-color: #fff;
height: calc(100vh - 64px); <style lang="less" scoped>
margin-bottom: -24px; .acl-users {
padding: 24px; border-radius: 15px;
.acl-users-header { background-color: #fff;
display: inline-flex; height: calc(100vh - 64px);
margin-bottom: 15px; margin-bottom: -24px;
} padding: 24px;
} .acl-users-header {
</style> display: inline-flex;
margin-bottom: 15px;
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -63,7 +63,7 @@ export default {
updateCI(this.row.ci_id || this.row._id, { updateCI(this.row.ci_id || this.row._id, {
[`${this.column.property}`]: this.default_value_json_right ? this.jsonData : {}, [`${this.column.property}`]: this.default_value_json_right ? this.jsonData : {},
}).then(() => { }).then(() => {
this.$message.success('保存成功!') this.$message.success(this.$t('saveSuccess'))
this.handleCancel() this.handleCancel()
this.$emit('jsonEditorOk', this.row, this.column, this.default_value_json_right ? this.jsonData : {}) this.$emit('jsonEditorOk', this.row, this.column, this.default_value_json_right ? this.jsonData : {})
}) })

View File

@ -7,7 +7,7 @@
width: '200px', width: '200px',
height: `${height}px`, height: `${height}px`,
}" }"
:titles="['未选属性', '已选属性']" :titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
:render="(item) => item.title" :render="(item) => item.title"
:targetKeys="targetKeys" :targetKeys="targetKeys"
@change="handleChange" @change="handleChange"
@ -16,7 +16,7 @@
:filterOption="filterOption" :filterOption="filterOption"
class="cmdb-transfer" class="cmdb-transfer"
> >
<span slot="notFoundContent">暂无数据</span> <span slot="notFoundContent">{{ $t('noData') }}</span>
<template slot="children" slot-scope="{ props: { direction, filteredItems } }"> <template slot="children" slot-scope="{ props: { direction, filteredItems } }">
<div class="ant-transfer-list-content" v-if="direction === 'right'"> <div class="ant-transfer-list-content" v-if="direction === 'right'">
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable"> <draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
@ -27,10 +27,11 @@
:style="{ height: '38px' }" :style="{ height: '38px' }"
> >
<li <li
:class="{ :class="
'ant-transfer-list-content-item': true, `ant-transfer-list-content-item ${
'ant-transfer-list-content-item-selected': selectedKeys.includes(item.key), selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
}" }`
"
@click="setSelectedKeys(item)" @click="setSelectedKeys(item)"
> >
<OpsMoveIcon class="move-icon" /> <OpsMoveIcon class="move-icon" />
@ -62,9 +63,11 @@
:style="{ height: '38px' }" :style="{ height: '38px' }"
> >
<li <li
:class="`ant-transfer-list-content-item ${ :class="
selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : '' `ant-transfer-list-content-item ${
}`" selectedKeys.includes(item.key) ? 'ant-transfer-list-content-item-selected' : ''
}`
"
@click="setSelectedKeys(item)" @click="setSelectedKeys(item)"
> >
<div class="ant-transfer-list-content-item-text" style="display: inline"> <div class="ant-transfer-list-content-item-text" style="display: inline">
@ -83,7 +86,7 @@
</template> </template>
</a-transfer> </a-transfer>
<div v-if="hasFooter" :style="{ marginTop: '5px', height: '20px' }"> <div v-if="hasFooter" :style="{ marginTop: '5px', height: '20px' }">
<a-button :style="{ float: 'right' }" size="small" @click="handleSubmit" type="primary">确定</a-button> <a-button :style="{ float: 'right' }" size="small" @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</div> </div>
</template> </template>
@ -110,12 +113,12 @@ export default {
default: true, default: true,
}, },
isSortable: { isSortable: {
// 右侧是否可排序 // Is the right side sortable?
type: Boolean, type: Boolean,
default: true, default: true,
}, },
isFixable: { isFixable: {
// 右侧是否可固定 // Can the right side be fixed?
type: Boolean, type: Boolean,
default: true, default: true,
}, },

View File

@ -1,21 +1,30 @@
<template> <template>
<a-modal :visible="visible" title="导出数据" @cancel="handleCancel" okText="导出" @ok="handleOk"> <a-modal
:visible="visible"
:title="$t('cmdb.components.downloadCI')"
@cancel="handleCancel"
@ok="handleOk"
width="700px"
>
<a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }"> <a-form :form="form" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }">
<a-form-item label="文件名"> <a-form-item :label="$t('cmdb.components.filename')">
<a-input <a-input
placeholder="请输入文件名" :placeholder="$t('cmdb.components.filenameInputTips')"
v-decorator="['filename', { rules: [{ required: true, message: '请输入文件名' }] }]" v-decorator="['filename', { rules: [{ required: true, message: $t('cmdb.components.filenameInputTips') }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="保存类型"> <a-form-item :label="$t('cmdb.components.saveType')">
<a-select <a-select
placeholder="请选择保存类型" :placeholder="$t('cmdb.components.saveTypeTips')"
v-decorator="['type', { rules: [{ required: true, message: '请选择保存类型' }], initialValue: 'xlsx' }]" v-decorator="[
'type',
{ rules: [{ required: true, message: $t('cmdb.components.saveTypeTips') }], initialValue: 'xlsx' },
]"
> >
<a-select-option v-for="item in typeList" :key="item.id" :values="item.id">{{ item.label }}</a-select-option> <a-select-option v-for="item in typeList" :key="item.id" :values="item.id">{{ item.label }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="选择字段"> <a-form-item :label="$t('cmdb.ciType.selectAttributes')">
<div <div
:style="{ :style="{
paddingLeft: '26px', paddingLeft: '26px',
@ -29,7 +38,7 @@
:checked="checkAll" :checked="checkAll"
@change="onCheckAllChange" @change="onCheckAllChange"
:style="{ marginRight: '10px' }" :style="{ marginRight: '10px' }"
/>全选 />{{ $t('checkAll') }}
</div> </div>
<div <div
:style="{ :style="{
@ -76,30 +85,7 @@ export default {
}, },
}, },
data() { data() {
const typeList = [
{
id: 'xlsx',
label: 'Excel工作簿(*.xlsx)',
},
{
id: 'csv',
label: 'CSV(逗号分隔)(*.csv)',
},
{
id: 'html',
label: '网页(*.html)',
},
{
id: 'xml',
label: 'XML数据(*.xml)',
},
{
id: 'txt',
label: '文本文件(制表符分隔)(*.txt)',
},
]
return { return {
typeList,
visible: false, visible: false,
form: this.$form.createForm(this), form: this.$form.createForm(this),
preferenceAttrList: [], preferenceAttrList: [],
@ -109,6 +95,32 @@ export default {
defaultChecked: [], defaultChecked: [],
} }
}, },
computed: {
typeList() {
return [
{
id: 'xlsx',
label: this.$t('cmdb.components.xlsx'),
},
{
id: 'csv',
label: this.$t('cmdb.components.csv'),
},
{
id: 'html',
label: this.$t('cmdb.components.html'),
},
{
id: 'xml',
label: this.$t('cmdb.components.xml'),
},
{
id: 'txt',
label: this.$t('cmdb.components.txt'),
},
]
},
},
methods: { methods: {
...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']), ...mapMutations('cmdbStore', ['SET_IS_TABLE_LOADING']),
open({ preferenceAttrList, ciTypeName = undefined }) { open({ preferenceAttrList, ciTypeName = undefined }) {

View File

@ -25,17 +25,17 @@
</vxe-column> </vxe-column>
<template #empty> <template #empty>
<div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB"> <div v-if="loading()" style="height: 200px; line-height: 200px;color:#2F54EB">
<a-icon type="loading" /> 加载中... <a-icon type="loading" /> {{ $t('loading') }}
</div> </div>
<div v-else> <div v-else>
<img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" /> <img :style="{ width: '100px' }" :src="require('@/assets/data_empty.png')" />
<div>暂无数据</div> <div>{{ $t('noData') }}</div>
</div> </div>
</template> </template>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -95,10 +95,8 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
}, permMap() {
data() { return permMap()
return {
permMap,
} }
}, },
methods: { methods: {
@ -131,8 +129,8 @@ export default {
} else { } else {
const that = this const that = this
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: `确认删除 ${row.name} 授权 权限`, content: that.$t('cmdb.components.confirmRevoke', { name: `${row.name}` }),
onOk() { onOk() {
that.handleChange({ target: { checked: false } }, col, row) that.handleChange({ target: { checked: false } }, col, row)
const _idx = that.tableData.findIndex((item) => item.rid === row.rid) const _idx = that.tableData.findIndex((item) => item.rid === row.rid)

View File

@ -1,11 +1,15 @@
export const permMap = { import i18n from '@/lang'
read: '查看',
add: '新增', export const permMap = () => {
create: '新增', return {
update: '修改', read: i18n.t('view'),
delete: '删除', add: i18n.t('new'),
config: '配置', create: i18n.t('new'),
grant: '授权', update: i18n.t('update'),
'read_attr': '查看字段', delete: i18n.t('delete'),
'read_ci': '查看实例' config: i18n.t('cmdb.components.config'),
grant: i18n.t('grant'),
'read_attr': i18n.t('cmdb.components.readAttribute'),
'read_ci': i18n.t('cmdb.components.readCI')
}
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }"> <div class="cmdb-grant" :style="{ maxHeight: `${windowHeight - 104}px` }">
<template v-if="cmdbGrantType.includes('ci_type')"> <template v-if="cmdbGrantType.includes('ci_type')">
<div class="cmdb-grant-title">模型权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciTypeGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
@ -18,7 +18,7 @@
cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type')) cmdbGrantType.includes('ci_type,ci') || (cmdbGrantType.includes('ci') && !cmdbGrantType.includes('ci_type'))
" "
> >
<div class="cmdb-grant-title">实例权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.ciGrant') }}</div>
<CiTypeGrant <CiTypeGrant
:CITypeId="CITypeId" :CITypeId="CITypeId"
:tableData="tableData" :tableData="tableData"
@ -32,7 +32,7 @@
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('type_relation')"> <template v-if="cmdbGrantType.includes('type_relation')">
<div class="cmdb-grant-title">关系权限</div> <div class="cmdb-grant-title">{{ $t('cmdb.components.relationGrant') }}</div>
<TypeRelationGrant <TypeRelationGrant
:typeRelationIds="typeRelationIds" :typeRelationIds="typeRelationIds"
:tableData="tableData" :tableData="tableData"
@ -45,7 +45,7 @@
/> />
</template> </template>
<template v-if="cmdbGrantType.includes('relation_view')"> <template v-if="cmdbGrantType.includes('relation_view')">
<div class="cmdb-grant-title">{{ resourceTypeName }}权限</div> <div class="cmdb-grant-title">{{ resourceTypeName }}{{ $t('cmdb.components.perm') }}</div>
<RelationViewGrant <RelationViewGrant
:resourceTypeName="resourceTypeName" :resourceTypeName="resourceTypeName"
:tableData="tableData" :tableData="tableData"
@ -116,7 +116,7 @@ export default {
attrGroup: [], attrGroup: [],
filerPerimissions: {}, filerPerimissions: {},
loading: false, loading: false,
addedRids: [], // 本次新增的rid addedRids: [], // added rid this time
} }
}, },
computed: { computed: {
@ -203,12 +203,12 @@ export default {
this.tableData = perms this.tableData = perms
this.loading = false this.loading = false
}, },
// 授权common-setting中的部门 从中拿到roleid // Grant the department in common-setting and get the roleid from it
grantDepart(grantType) { grantDepart(grantType) {
this.$refs.grantModal.open('depart') this.$refs.grantModal.open('depart')
this.grantType = grantType this.grantType = grantType
}, },
// 授权最古老的角色权限 // Grant the oldest role permissions
grantRole(grantType) { grantRole(grantType) {
this.$refs.grantModal.open('role') this.$refs.grantModal.open('role')
this.grantType = grantType this.grantType = grantType

View File

@ -26,9 +26,9 @@ export default {
computed: { computed: {
title() { title() {
if (this.type === 'depart') { if (this.type === 'depart') {
return '授权用户/部门' return this.$t('cmdb.components.grantUser')
} }
return '授权角色' return this.$t('cmdb.components.grantRole')
}, },
}, },
methods: { methods: {

View File

@ -2,9 +2,9 @@
<a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel"> <a-modal :width="680" :title="title" :visible="visible" @ok="handleOk" @cancel="handleCancel">
<CustomRadio <CustomRadio
:radioList="[ :radioList="[
{ value: 1, label: '全部' }, { value: 1, label: $t('cmdb.components.all') },
{ value: 2, label: '自定义', layout: 'vertical' }, { value: 2, label: $t('cmdb.components.customize'), layout: 'vertical' },
{ value: 3, label: '' }, { value: 3, label: $t('cmdb.components.none') },
]" ]"
v-model="radioValue" v-model="radioValue"
> >
@ -16,7 +16,7 @@
:clearable="true" :clearable="true"
searchable searchable
:options="attrGroup" :options="attrGroup"
placeholder="请选择属性字段" :placeholder="$t('cmdb.ciType.selectAttributes')"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
:limit="10" :limit="10"
:limitText="(count) => `+ ${count}`" :limitText="(count) => `+ ${count}`"
@ -24,8 +24,8 @@
(node) => { (node) => {
return { return {
id: node.name || -1, id: node.name || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.attributes, children: node.attributes,
} }
} }
@ -42,7 +42,7 @@
:wrapperCol="{ span: 10 }" :wrapperCol="{ span: 10 }"
ref="form" ref="form"
> >
<a-form-model-item label="名称" prop="name"> <a-form-model-item :label="$t('name')" prop="name">
<a-input v-model="form.name" /> <a-input v-model="form.name" />
</a-form-model-item> </a-form-model-item>
<FilterComp <FilterComp
@ -99,16 +99,16 @@ export default {
name: '', name: '',
}, },
rules: { rules: {
name: [{ required: true, message: '请输入自定义筛选条件名' }], name: [{ required: true, message: this.$t('cmdb.components.customizeFilterName') }],
}, },
} }
}, },
computed: { computed: {
title() { title() {
if (this.colType === 'read_attr') { if (this.colType === 'read_attr') {
return '字段权限' return this.$t('cmdb.components.attributeGrant')
} }
return '实例权限' return this.$t('cmdb.components.ciGrant')
}, },
attrGroup() { attrGroup() {
return this.provide_attrGroup() return this.provide_attrGroup()

View File

@ -17,8 +17,8 @@
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -51,7 +51,6 @@ export default {
}, },
data() { data() {
return { return {
permMap,
columns: ['read', 'grant'], columns: ['read', 'grant'],
} }
}, },
@ -65,6 +64,9 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() {
return permMap()
}
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,

View File

@ -17,8 +17,8 @@
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
<a-space> <a-space>
<span class="grant-button" @click="grantDepart">授权用户/部门</span> <span class="grant-button" @click="grantDepart">{{ $t('cmdb.components.grantUser') }}</span>
<span class="grant-button" @click="grantRole">授权角色</span> <span class="grant-button" @click="grantRole">{{ $t('cmdb.components.grantRole') }}</span>
</a-space> </a-space>
</div> </div>
</template> </template>
@ -51,7 +51,6 @@ export default {
}, },
data() { data() {
return { return {
permMap,
columns: ['create', 'grant', 'delete'], columns: ['create', 'grant', 'delete'],
} }
}, },
@ -65,6 +64,9 @@ export default {
} }
return (this.windowHeight - 104) / 2 - 116 return (this.windowHeight - 104) / 2 - 116
}, },
permMap() {
return permMap()
}
}, },
methods: { methods: {
getCurrentRowStyle, getCurrentRowStyle,

View File

@ -132,7 +132,7 @@ export default {
} else if (color.indexOf('rgb') !== -1) { } else if (color.indexOf('rgb') !== -1) {
hsvObj = this.rgbToHSV(color) hsvObj = this.rgbToHSV(color)
} else { } else {
throw new Error('初始化颜色格式错误,使用#fff或rgb格式') throw new Error(this.$t('cmdb.components.colorPickerError'))
// this.$message.error('颜色格式错误使用16进制格式') // this.$message.error('颜色格式错误使用16进制格式')
} }
if (hsvObj) { if (hsvObj) {

View File

@ -15,19 +15,19 @@
:edit-config="isEdit ? { trigger: 'click', mode: 'cell' } : {}" :edit-config="isEdit ? { trigger: 'click', mode: 'cell' } : {}"
> >
<template v-if="isEdit"> <template v-if="isEdit">
<vxe-colgroup title="自动发现"> <vxe-colgroup :title="$t('cmdb.ciType.autoDiscovery')">
<vxe-column field="name" title="名称" width="100"> </vxe-column> <vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" title="类型" width="80"> </vxe-column> <vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" title="示例值"> <vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}"> <template #default="{row}">
<span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span> <span v-if="row.type === 'json'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span> <span v-else>{{ row.example }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="desc" title="描述"> </vxe-column> <vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</vxe-colgroup> </vxe-colgroup>
<vxe-colgroup title="模型属性"> <vxe-colgroup :title="$t('cmdb.ciType.attributes')">
<vxe-column field="attr" title="名称" :edit-render="{}"> <vxe-column field="attr" :title="$t('name')" :edit-render="{}">
<template #default="{row}"> <template #default="{row}">
{{ row.attr }} {{ row.attr }}
</template> </template>
@ -45,15 +45,15 @@
</vxe-colgroup> </vxe-colgroup>
</template> </template>
<template v-else> <template v-else>
<vxe-column field="name" title="名称" width="100"> </vxe-column> <vxe-column field="name" :title="$t('name')" width="100"> </vxe-column>
<vxe-column field="type" title="类型" width="80"> </vxe-column> <vxe-column field="type" :title="$t('type')" width="80"> </vxe-column>
<vxe-column field="example" title="示例值"> <vxe-column field="example" :title="$t('cmdb.components.example')">
<template #default="{row}"> <template #default="{row}">
<span v-if="row.type === 'object'">{{ JSON.stringify(row.example) }}</span> <span v-if="row.type === 'object'">{{ JSON.stringify(row.example) }}</span>
<span v-else>{{ row.example }}</span> <span v-else>{{ row.example }}</span>
</template> </template>
</vxe-column> </vxe-column>
<vxe-column field="desc" title="描述"> </vxe-column> <vxe-column field="desc" :title="$t('desc')"> </vxe-column>
</template> </template>
</vxe-table> </vxe-table>
</div> </div>
@ -61,12 +61,6 @@
<script> <script>
import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery' import { getHttpCategories, getHttpAttributes, getSnmpAttributes } from '../../api/discovery'
const httpMap = {
阿里云: { name: 'aliyun' },
腾讯云: { name: 'tencentcloud' },
华为云: { name: 'huaweicloud' },
AWS: { name: 'aws' },
}
export default { export default {
name: 'HttpSnmpAD', name: 'HttpSnmpAD',
props: { props: {
@ -107,13 +101,21 @@ export default {
const { ruleType, ruleName } = this const { ruleType, ruleName } = this
return { ruleType, ruleName } return { ruleType, ruleName }
}, },
httpMap() {
return {
[this.$t('cmdb.components.aliyun')]: { name: 'aliyun' },
[this.$t('cmdb.components.tencentcloud')]: { name: 'tencentcloud' },
[this.$t('cmdb.components.huaweicloud')]: { name: 'huaweicloud' },
AWS: { name: 'aws' },
}
},
}, },
watch: { watch: {
currentCate: { currentCate: {
immediate: true, immediate: true,
handler(newVal) { handler(newVal) {
if (newVal) { if (newVal) {
getHttpAttributes(httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => { getHttpAttributes(this.httpMap[`${this.ruleName}`].name, { category: newVal }).then((res) => {
if (this.isEdit) { if (this.isEdit) {
this.formatTableData(res) this.formatTableData(res)
} else { } else {
@ -139,7 +141,7 @@ export default {
}) })
} }
if (ruleType === 'http' && ruleName) { if (ruleType === 'http' && ruleName) {
getHttpCategories(httpMap[`${this.ruleName}`].name).then((res) => { getHttpCategories(this.httpMap[`${this.ruleName}`].name).then((res) => {
this.categories = res this.categories = res
if (res && res.length) { if (res && res.length) {
this.currentCate = res[0] this.currentCate = res[0]

View File

@ -1,199 +1,199 @@
<template> <template>
<div class="notice-content"> <div class="notice-content">
<div class="notice-content-main"> <div class="notice-content-main">
<Toolbar <Toolbar
:editor="editor" :editor="editor"
:defaultConfig="{ :defaultConfig="{
excludeKeys: [ excludeKeys: [
'emotion', 'emotion',
'group-image', 'group-image',
'group-video', 'group-video',
'insertTable', 'insertTable',
'codeBlock', 'codeBlock',
'blockquote', 'blockquote',
'fullScreen', 'fullScreen',
], ],
}" }"
mode="default" mode="default"
/> />
<Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" /> <Editor class="notice-content-editor" :defaultConfig="editorConfig" mode="simple" @onCreated="onCreated" />
<div class="notice-content-sidebar"> <div class="notice-content-sidebar">
<template v-if="needOld"> <template v-if="needOld">
<div class="notice-content-sidebar-divider">变更前</div> <div class="notice-content-sidebar-divider">{{ $t('cmdb.components.beforeChange') }}</div>
<div <div
@dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)" @dblclick="dblclickSidebar(`old_${attr.name}`, attr.alias || attr.name)"
class="notice-content-sidebar-item" class="notice-content-sidebar-item"
v-for="attr in attrList" v-for="attr in attrList"
:key="`old_${attr.id}`" :key="`old_${attr.id}`"
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</div> </div>
<div class="notice-content-sidebar-divider">变更后</div> <div class="notice-content-sidebar-divider">{{ $t('cmdb.components.afterChange') }}</div>
</template> </template>
<div <div
@dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)" @dblclick="dblclickSidebar(attr.name, attr.alias || attr.name)"
class="notice-content-sidebar-item" class="notice-content-sidebar-item"
v-for="attr in attrList" v-for="attr in attrList"
:key="attr.id" :key="attr.id"
:title="attr.alias || attr.name" :title="attr.alias || attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import '@wangeditor/editor/dist/css/style.css' import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
export default { export default {
name: 'NoticeContent', name: 'NoticeContent',
components: { Editor, Toolbar }, components: { Editor, Toolbar },
props: { props: {
attrList: { attrList: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
needOld: { needOld: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
data() { data() {
return { return {
editor: null, editor: null,
editorConfig: { placeholder: '请输入通知内容', readOnly: this.readOnly }, editorConfig: { placeholder: this.$t('cmdb.components.noticeContentTips'), readOnly: this.readOnly },
content: '', content: '',
defaultParams: [], defaultParams: [],
value2LabelMap: {}, value2LabelMap: {},
} }
}, },
beforeDestroy() { beforeDestroy() {
const editor = this.editor const editor = this.editor
if (editor == null) return if (editor == null) return
editor.destroy() // 组件销毁时及时销毁编辑器 editor.destroy() // When the component is destroyed, destroy the editor in time
}, },
methods: { methods: {
onCreated(editor) { onCreated(editor) {
this.editor = Object.seal(editor) // 一定要用 Object.seal() 否则会报错 this.editor = Object.seal(editor) // Be sure to use Object.seal(), otherwise an error will be reported
}, },
getContent() { getContent() {
const html = _.cloneDeep(this.editor.getHtml()) const html = _.cloneDeep(this.editor.getHtml())
const _html = html.replace( const _html = html.replace(
/<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm, /<span data-w-e-type="attachment" (data-w-e-is-void|data-w-e-is-void="") (data-w-e-is-inline|data-w-e-is-inline="").*?<\/span>/gm,
(value) => { (value) => {
const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/) const _match = value.match(/(?<=data-attachment(V|v)alue=").*?(?=")/)
return `{{${_match[0]}}}` return `{{${_match[0]}}}`
} }
) )
return { body_html: html, body: _html } return { body_html: html, body: _html }
}, },
setContent(html) { setContent(html) {
this.editor.setHtml(html) this.editor.setHtml(html)
}, },
dblclickSidebar(value, label) { dblclickSidebar(value, label) {
if (!this.readOnly) { if (!this.readOnly) {
this.editor.restoreSelection() this.editor.restoreSelection()
const node = { const node = {
type: 'attachment', type: 'attachment',
attachmentValue: value, attachmentValue: value,
attachmentLabel: `${label}`, attachmentLabel: `${label}`,
children: [{ text: '' }], children: [{ text: '' }],
} }
this.editor.insertNode(node) this.editor.insertNode(node)
} }
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@import '~@/style/static.less'; @import '~@/style/static.less';
.notice-content { .notice-content {
width: 100%; width: 100%;
& &-main { & &-main {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
position: relative; position: relative;
.notice-content-editor { .notice-content-editor {
height: 300px; height: 300px;
width: 75%; width: 75%;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-top: none; border-top: none;
overflow: hidden; overflow: hidden;
} }
.notice-content-sidebar { .notice-content-sidebar {
width: 25%; width: 25%;
position: absolute; position: absolute;
height: 300px; height: 300px;
bottom: 0; bottom: 0;
left: 0; left: 0;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-top: none; border-top: none;
border-right: none; border-right: none;
overflow: auto; overflow: auto;
.notice-content-sidebar-divider { .notice-content-sidebar-divider {
position: sticky; position: sticky;
top: 0; top: 0;
margin: 0; margin: 0;
font-size: 12px; font-size: 12px;
color: #afafaf; color: #afafaf;
background-color: #fff; background-color: #fff;
line-height: 20px; line-height: 20px;
padding-left: 12px; padding-left: 12px;
&::before, &::before,
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
border-top: 1px solid #d1d1d1; border-top: 1px solid #d1d1d1;
top: 50%; top: 50%;
transition: translateY(-50%); transition: translateY(-50%);
} }
&::before { &::before {
left: 3px; left: 3px;
width: 5px; width: 5px;
} }
&::after { &::after {
right: 3px; right: 3px;
width: 78px; width: 78px;
} }
} }
.notice-content-sidebar-item:first-child { .notice-content-sidebar-item:first-child {
margin-top: 10px; margin-top: 10px;
} }
.notice-content-sidebar-item { .notice-content-sidebar-item {
line-height: 1.5; line-height: 1.5;
padding: 4px 12px; padding: 4px 12px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
background-color: #custom_colors[color_2]; background-color: #custom_colors[color_2];
color: #custom_colors[color_1]; color: #custom_colors[color_1];
} }
} }
} }
} }
} }
</style> </style>
<style lang="less"> <style lang="less">
@import '~@/style/static.less'; @import '~@/style/static.less';
.notice-content { .notice-content {
.w-e-bar { .w-e-bar {
background-color: #custom_colors[color_2]; background-color: #custom_colors[color_2];
} }
.w-e-text-placeholder { .w-e-text-placeholder {
line-height: 1.5; line-height: 1.5;
} }
} }
</style> </style>

View File

@ -11,7 +11,7 @@
@blur="handleInputConfirm" @blur="handleInputConfirm"
@keyup.enter="handleInputConfirm" @keyup.enter="handleInputConfirm"
/> />
<a-button v-else type="primary" size="small" ghost @click="showInput">保存筛选条件</a-button> <a-button v-else type="primary" size="small" ghost @click="showInput">{{ $t('cmdb.components.saveQuery') }}</a-button>
</span> </span>
<template v-for="(item, index) in preferenceSearchList.slice(0, 3)"> <template v-for="(item, index) in preferenceSearchList.slice(0, 3)">
<span <span
@ -26,7 +26,7 @@
<a-tooltip :title="item.name"> <a-tooltip :title="item.name">
<span @click="clickPreferenceSearch(item)">{{ `${item.name.slice(0, 6)}...` }}</span> <span @click="clickPreferenceSearch(item)">{{ `${item.name.slice(0, 6)}...` }}</span>
</a-tooltip> </a-tooltip>
<a-popconfirm title="确认删除?" @confirm="deletePreferenceSearch(item)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deletePreferenceSearch(item)">
<a-icon type="close" /> <a-icon type="close" />
</a-popconfirm> </a-popconfirm>
</span> </span>
@ -40,7 +40,7 @@
}" }"
> >
<span @click="clickPreferenceSearch(item)">{{ item.name }}</span> <span @click="clickPreferenceSearch(item)">{{ item.name }}</span>
<a-popconfirm title="确认删除?" @confirm="deletePreferenceSearch(item)"> <a-popconfirm :title="$t('cmdb.ciType.confirmDelete2')" @confirm="deletePreferenceSearch(item)">
<a-icon type="close" /> <a-icon type="close" />
</a-popconfirm> </a-popconfirm>
</span> </span>
@ -73,7 +73,7 @@
{{ item.name }} {{ item.name }}
</div> </div>
<a-popconfirm <a-popconfirm
title="确认删除?" :title="$t('cmdb.ciType.confirmDelete2')"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
placement="left" placement="left"
@confirm=" @confirm="

View File

@ -15,7 +15,7 @@
:limit="1" :limit="1"
:limitText="(count) => `+ ${count}`" :limitText="(count) => `+ ${count}`"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="模型" :placeholder="$t('cmdb.ciType.ciType')"
@close="closeCiTypeGroup" @close="closeCiTypeGroup"
@open="openCiTypeGroup" @open="openCiTypeGroup"
@input="inputCiTypeGroup" @input="inputCiTypeGroup"
@ -23,8 +23,8 @@
(node) => { (node) => {
return { return {
id: node.id || -1, id: node.id || -1,
label: node.alias || node.name || '其他', label: node.alias || node.name || $t('other'),
title: node.alias || node.name || '其他', title: node.alias || node.name || $t('other'),
children: node.ci_types, children: node.ci_types,
} }
} }
@ -42,7 +42,7 @@
<a-input <a-input
v-model="fuzzySearch" v-model="fuzzySearch"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '244px' }"
placeholder="请查找" :placeholder="$t('cmdb.components.pleaseSearch')"
@pressEnter="emitRefresh" @pressEnter="emitRefresh"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
> >
@ -54,7 +54,7 @@
/> />
<a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }"> <a-tooltip slot="prefix" placement="bottom" :overlayStyle="{ maxWidth: '550px' }">
<template slot="title"> <template slot="title">
1json属性不能搜索<br />2搜索内容包括逗号则需转义 \,<br />3只搜索索引属性非索引属性使用条件过滤 {{ $t('cmdb.components.ciSearchTips') }}
</template> </template>
<a><a-icon type="question-circle"/></a> <a><a-icon type="question-circle"/></a>
</a-tooltip> </a-tooltip>
@ -68,7 +68,7 @@
> >
<div slot="popover_item" class="search-form-bar-filter"> <div slot="popover_item" class="search-form-bar-filter">
<a-icon class="search-form-bar-filter-icon" type="filter" /> <a-icon class="search-form-bar-filter-icon" type="filter" />
条件过滤 {{ $t('cmdb.components.conditionFilter') }}
<a-icon class="search-form-bar-filter-icon" type="down" /> <a-icon class="search-form-bar-filter-icon" type="down" />
</div> </div>
</FilterComp> </FilterComp>
@ -97,8 +97,8 @@
</a-space> </a-space>
</div> </div>
<a-space> <a-space>
<a-button @click="reset" size="small">重置</a-button> <a-button @click="reset" size="small">{{ $t('reset') }}</a-button>
<a-tooltip title="属性说明" v-if="type === 'relationView'"> <a-tooltip :title="$t('cmdb.components.attributeDesc')" v-if="type === 'relationView'">
<a <a
@click=" @click="
() => { () => {
@ -149,7 +149,7 @@ export default {
}, },
data() { data() {
return { return {
// 高级搜索 展开/关闭 // Advanced Search Expand/Close
advanced: false, advanced: false,
queryParam: {}, queryParam: {},
isFocusExpression: false, isFocusExpression: false,
@ -163,7 +163,7 @@ export default {
computed: { computed: {
placeholder() { placeholder() {
return this.isFocusExpression ? 'q=hostname:*0.0.0.0*' : '表达式' return this.isFocusExpression ? this.$t('cmdb.components.ciSearchTips2') : this.$t('cmdb.ciType.expr')
}, },
width() { width() {
return '200px' return '200px'

View File

@ -11,13 +11,13 @@
> >
<a-tabs v-model="activeKey"> <a-tabs v-model="activeKey">
<a-tab-pane key="1"> <a-tab-pane key="1">
<span slot="tab"><ops-icon type="cmdb-ci" />资源数据</span> <span slot="tab"><ops-icon type="cmdb-ci" />{{ $t('cmdb.menu.ciTable') }}</span>
<div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }"> <div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }">
<div class="cmdb-subscribe-drawer-container-title"> <div class="cmdb-subscribe-drawer-container-title">
<span>订阅模型{{ ciType.alias || ciType.name }}</span> <span>{{ $t('cmdb.components.subCIType') }}: {{ ciType.alias || ciType.name }}</span>
<span :style="{ fontWeight: 500, color: instanceSubscribed ? 'green' : 'red' }">{{ <span :style="{ fontWeight: 500, color: instanceSubscribed ? 'green' : 'red' }">{{
`${instanceSubscribed ? '' : ''}订阅` `${instanceSubscribed ? $t('cmdb.components.already') : $t('cmdb.components.not')}`
}}</span> }}{{ $t('cmdb.components.sub') }})</span>
</div> </div>
<template> <template>
<AttributesTransfer <AttributesTransfer
@ -31,22 +31,22 @@
:height="windowHeight - 170" :height="windowHeight - 170"
/> />
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="subInstanceSubmit" type="primary">订阅</a-button> <a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
</div> </div>
</template> </template>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" force-render> <a-tab-pane key="2" force-render>
<span slot="tab"><ops-icon type="cmdb-tree" />资源层级</span> <span slot="tab"><ops-icon type="cmdb-tree" />{{ $t('cmdb.menu.ciTree') }}</span>
<div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }"> <div class="cmdb-subscribe-drawer-container" :style="{ height: `${windowHeight - 60}px` }">
<div class="cmdb-subscribe-drawer-container-title"> <div class="cmdb-subscribe-drawer-container-title">
<span>订阅模型{{ ciType.alias || ciType.name }}</span> <span>{{ $t('cmdb.components.subCIType') }}: {{ ciType.alias || ciType.name }}</span>
<span :style="{ fontWeight: 500, color: treeSubscribed ? 'green' : 'red' }">{{ <span :style="{ fontWeight: 500, color: treeSubscribed ? 'green' : 'red' }">{{
`${treeSubscribed ? '' : ''}订阅` `${treeSubscribed ? $t('cmdb.components.already') : $t('cmdb.components.not')}`
}}</span> }}{{ $t('cmdb.components.sub') }})</span>
</div> </div>
<div class="cmdb-subscribe-drawer-tree-header" :style="{ maxHeight: `${(windowHeight - 170) / 3 - 20}px` }"> <div class="cmdb-subscribe-drawer-tree-header" :style="{ maxHeight: `${(windowHeight - 170) / 3 - 20}px` }">
<span v-if="!treeViews.length">请在下方进行选择</span> <span v-if="!treeViews.length">{{ $t('cmdb.components.selectBelow') }}</span>
<div <div
class="cmdb-subscribe-drawer-tree-header-selected" class="cmdb-subscribe-drawer-tree-header-selected"
:style="{ marginLeft: `${18 * index}px` }" :style="{ marginLeft: `${18 * index}px` }"
@ -70,7 +70,7 @@
</div> </div>
</div> </div>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="subTreeSubmit" type="primary">订阅</a-button> <a-button @click="subTreeSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
</div> </div>
</div> </div>
</a-tab-pane> </a-tab-pane>
@ -179,7 +179,7 @@ export default {
}, },
subTreeSubmit() { subTreeSubmit() {
subscribeTreeView(this.ciType.type_id, this.treeViews).then((res) => { subscribeTreeView(this.ciType.type_id, this.treeViews).then((res) => {
this.$message.success('订阅成功') this.$message.success(this.$t('cmdb.components.subSuccess'))
if (this.treeViews.length > 0) { if (this.treeViews.length > 0) {
this.treeSubscribed = true this.treeSubscribed = true
} else { } else {
@ -194,7 +194,7 @@ export default {
return [item, !!this.fixedList.includes(item)] return [item, !!this.fixedList.includes(item)]
}) })
).then((res) => { ).then((res) => {
this.$message.success('订阅成功') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.resetRoute() this.resetRoute()
if (this.selectedAttrList.length > 0) { if (this.selectedAttrList.length > 0) {
this.instanceSubscribed = true this.instanceSubscribed = true

View File

@ -1,144 +1,144 @@
<template> <template>
<div class="authorization-wrapper"> <div class="authorization-wrapper">
<div class="authorization-header"> <div class="authorization-header">
<a-space> <a-space>
<span>Authorization Type</span> <span>Authorization Type</span>
<a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true"> <a-select size="small" v-model="authorizationType" style="width: 200px" :showSearch="true">
<a-select-option value="none"> <a-select-option value="none">
None None
</a-select-option> </a-select-option>
<a-select-option value="BasicAuth"> <a-select-option value="BasicAuth">
Basic Auth Basic Auth
</a-select-option> </a-select-option>
<a-select-option value="Bearer"> <a-select-option value="Bearer">
Bearer Bearer
</a-select-option> </a-select-option>
<a-select-option value="APIKey"> <a-select-option value="APIKey">
APIKey APIKey
</a-select-option> </a-select-option>
<a-select-option value="OAuth2.0"> <a-select-option value="OAuth2.0">
OAuth2.0 OAuth2.0
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-space> </a-space>
</div> </div>
<div style="margin-top:10px"> <div style="margin-top:10px">
<table v-if="authorizationType === 'BasicAuth'"> <table v-if="authorizationType === 'BasicAuth'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="BasicAuth.username" placeholder="用户名" /></td> <td><a-input class="authorization-input" v-model="BasicAuth.username" :placeholder="$t('cmdb.ciType.username')" /></td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="BasicAuth.password" placeholder="密码" /></td> <td><a-input class="authorization-input" v-model="BasicAuth.password" :placeholder="$t('cmdb.ciType.password')" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'Bearer'"> <table v-else-if="authorizationType === 'Bearer'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td> <td><a-input class="authorization-input" v-model="Bearer.token" placeholder="token" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'APIKey'"> <table v-else-if="authorizationType === 'APIKey'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td> <td><a-input class="authorization-input" v-model="APIKey.key" placeholder="key" /></td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td> <td><a-input class="authorization-input" v-model="APIKey.value" placeholder="value" /></td>
</tr> </tr>
</table> </table>
<table v-else-if="authorizationType === 'OAuth2.0'"> <table v-else-if="authorizationType === 'OAuth2.0'">
<tr> <tr>
<td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td> <td><a-input class="authorization-input" v-model="OAuth2.client_id" placeholder="client_id" /></td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" /> <a-input class="authorization-input" v-model="OAuth2.client_secret" placeholder="client_secret" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input <a-input
class="authorization-input" class="authorization-input"
v-model="OAuth2.authorization_base_url" v-model="OAuth2.authorization_base_url"
placeholder="authorization_base_url" placeholder="authorization_base_url"
/> />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" /> <a-input class="authorization-input" v-model="OAuth2.token_url" placeholder="token_url" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td> <td><a-input class="authorization-input" v-model="OAuth2.redirect_url" placeholder="redirect_url" /></td>
</tr> </tr>
<tr> <tr>
<td> <td>
<a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" /> <a-input class="authorization-input" v-model="OAuth2.scope" placeholder="scope" />
</td> </td>
</tr> </tr>
</table> </table>
<a-empty <a-empty
v-else v-else
:image-style="{ :image-style="{
height: '60px', height: '60px',
}" }"
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无请求认证 </span> <span slot="description"> {{ $t('cmdb.components.noAuthRequest') }} </span>
</a-empty> </a-empty>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Authorization', name: 'Authorization',
data() { data() {
return { return {
authorizationType: 'none', authorizationType: 'none',
BasicAuth: { BasicAuth: {
username: '', username: '',
password: '', password: '',
}, },
Bearer: { Bearer: {
token: '', token: '',
}, },
APIKey: { APIKey: {
key: '', key: '',
value: '', value: '',
}, },
OAuth2: { OAuth2: {
client_id: '', client_id: '',
client_secret: '', client_secret: '',
authorization_base_url: '', authorization_base_url: '',
token_url: '', token_url: '',
redirect_url: '', redirect_url: '',
scope: '', scope: '',
}, },
} }
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.authorization-wrapper { .authorization-wrapper {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.authorization-input { .authorization-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -1,101 +1,101 @@
<template> <template>
<div> <div>
<div class="headers-header"> <div class="headers-header">
<span>请求参数</span> <span>{{ $t('cmdb.components.requestParam') }}</span>
<a-space> <a-space>
<a-tooltip title="清空"> <a-tooltip :title="$t('cmdb.components.clear')">
<ops-icon <ops-icon
type="icon-xianxing-delete" type="icon-xianxing-delete"
@click=" @click="
() => { () => {
headers = [ headers = [
{ {
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}, },
] ]
} }
" "
/> />
</a-tooltip> </a-tooltip>
<a-tooltip title="新增"> <a-tooltip :title="$t('new')">
<a-icon type="plus" @click="add" /> <a-icon type="plus" @click="add" />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
<div class="headers-box"> <div class="headers-box">
<table> <table>
<tr v-for="(item, index) in headers" :key="item.id"> <tr v-for="(item, index) in headers" :key="item.id">
<td><a-input class="headers-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td> <td><a-input class="headers-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
<td><a-input class="headers-input" v-model="item.value" :placeholder="`${index + 1}`" /></td> <td><a-input class="headers-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
<td> <td>
<a style="color:red"> <a style="color:red">
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" /> <ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
export default { export default {
name: 'Header', name: 'Header',
data() { data() {
return { return {
headers: [ headers: [
{ {
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}, },
], ],
} }
}, },
methods: { methods: {
uuidv4, uuidv4,
add() { add() {
this.headers.push({ this.headers.push({
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}) })
}, },
deleteParam(index) { deleteParam(index) {
this.headers.splice(index, 1) this.headers.splice(index, 1)
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.headers-header { .headers-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
i { i {
cursor: pointer; cursor: pointer;
} }
} }
.headers-box { .headers-box {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.headers-input { .headers-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -1,147 +1,147 @@
<template> <template>
<div> <div>
<a-input-group compact> <a-input-group compact>
<treeselect <treeselect
:disable-branch-nodes="true" :disable-branch-nodes="true"
class="custom-treeselect custom-treeselect-bgcAndBorder" class="custom-treeselect custom-treeselect-bgcAndBorder"
:style="{ :style="{
'--custom-height': '30px', '--custom-height': '30px',
lineHeight: '30px', lineHeight: '30px',
'--custom-bg-color': '#fff', '--custom-bg-color': '#fff',
'--custom-border': '1px solid #d9d9d9', '--custom-border': '1px solid #d9d9d9',
display: 'inline-block', display: 'inline-block',
width: '100px', width: '100px',
}" }"
v-model="method" v-model="method"
:multiple="false" :multiple="false"
:clearable="false" :clearable="false"
searchable searchable
:options="methodList" :options="methodList"
value-consists-of="LEAF_PRIORITY" value-consists-of="LEAF_PRIORITY"
placeholder="请选择方式" :placeholder="$t('cmdb.components.selectMethods')"
> >
</treeselect> </treeselect>
<a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" /> <a-input :style="{ display: 'inline-block', width: 'calc(100% - 100px)' }" v-model="url" />
</a-input-group> </a-input-group>
<a-tabs> <a-tabs>
<a-tab-pane key="Parameters" tab="Parameters"> <a-tab-pane key="Parameters" tab="Parameters">
<Parameters ref="Parameters" /> <Parameters ref="Parameters" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Body" tab="Body" force-render> <a-tab-pane key="Body" tab="Body" force-render>
<Body ref="Body" /> <Body ref="Body" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Headers" tab="Headers" force-render> <a-tab-pane key="Headers" tab="Headers" force-render>
<Header ref="Header" /> <Header ref="Header" />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="Authorization" tab="Authorization" force-render> <a-tab-pane key="Authorization" tab="Authorization" force-render>
<Authorization ref="Authorization" /> <Authorization ref="Authorization" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import Parameters from './paramaters.vue' import Parameters from './paramaters.vue'
import Body from './body.vue' import Body from './body.vue'
import Header from './header.vue' import Header from './header.vue'
import Authorization from './authorization.vue' import Authorization from './authorization.vue'
export default { export default {
name: 'Webhook', name: 'Webhook',
components: { Parameters, Body, Header, Authorization }, components: { Parameters, Body, Header, Authorization },
data() { data() {
const methodList = [ const methodList = [
{ {
id: 'GET', id: 'GET',
label: 'GET', label: 'GET',
}, },
{ {
id: 'POST', id: 'POST',
label: 'POST', label: 'POST',
}, },
{ {
id: 'PUT', id: 'PUT',
label: 'PUT', label: 'PUT',
}, },
{ {
id: 'DELETE', id: 'DELETE',
label: 'DELETE', label: 'DELETE',
}, },
] ]
return { return {
methodList, methodList,
method: 'GET', method: 'GET',
url: '', url: '',
} }
}, },
methods: { methods: {
getParams() { getParams() {
const parameters = {} const parameters = {}
this.$refs.Parameters.parameters.forEach((item) => { this.$refs.Parameters.parameters.forEach((item) => {
parameters[item.key] = item.value parameters[item.key] = item.value
}) })
let body = this.$refs.Body.jsonData let body = this.$refs.Body.jsonData
try { try {
JSON.parse(body) JSON.parse(body)
body = JSON.parse(body) body = JSON.parse(body)
} catch {} } catch {}
const headers = {} const headers = {}
this.$refs.Header.headers.forEach((item) => { this.$refs.Header.headers.forEach((item) => {
headers[item.key] = item.value headers[item.key] = item.value
}) })
let authorization = {} let authorization = {}
const type = this.$refs.Authorization.authorizationType const type = this.$refs.Authorization.authorizationType
if (type !== 'none') { if (type !== 'none') {
if (type === 'OAuth2.0') { if (type === 'OAuth2.0') {
authorization = { ...this.$refs.Authorization['OAuth2'], type } authorization = { ...this.$refs.Authorization['OAuth2'], type }
} else { } else {
authorization = { ...this.$refs.Authorization[type], type } authorization = { ...this.$refs.Authorization[type], type }
} }
} }
const { method, url } = this const { method, url } = this
return { method, url, parameters, body, headers, authorization } return { method, url, parameters, body, headers, authorization }
}, },
setParams(params) { setParams(params) {
const { method, url, parameters, body, headers, authorization = {} } = params ?? {} const { method, url, parameters, body, headers, authorization = {} } = params ?? {}
this.method = method this.method = method
this.url = url this.url = url
this.$refs.Parameters.parameters = this.$refs.Parameters.parameters =
Object.keys(parameters).map((key) => { Object.keys(parameters).map((key) => {
return { return {
id: uuidv4(), id: uuidv4(),
key: key, key: key,
value: parameters[key], value: parameters[key],
} }
}) || [] }) || []
if (body && Object.prototype.toString.call(body) === '[object Object]') { if (body && Object.prototype.toString.call(body) === '[object Object]') {
this.$refs.Body.jsonData = JSON.stringify(body) this.$refs.Body.jsonData = JSON.stringify(body)
} else { } else {
this.$refs.Body.jsonData = body this.$refs.Body.jsonData = body
} }
this.$refs.Header.headers = this.$refs.Header.headers =
Object.keys(headers).map((key) => { Object.keys(headers).map((key) => {
return { return {
id: uuidv4(), id: uuidv4(),
key: key, key: key,
value: headers[key], value: headers[key],
} }
}) || [] }) || []
const { type = 'none' } = authorization const { type = 'none' } = authorization
console.log(type) console.log(type)
this.$refs.Authorization.authorizationType = type this.$refs.Authorization.authorizationType = type
if (type !== 'none') { if (type !== 'none') {
const _authorization = _.cloneDeep(authorization) const _authorization = _.cloneDeep(authorization)
delete _authorization.type delete _authorization.type
if (type === 'OAuth2.0') { if (type === 'OAuth2.0') {
this.$refs.Authorization.OAuth2 = _authorization this.$refs.Authorization.OAuth2 = _authorization
} else { } else {
this.$refs.Authorization[type] = _authorization this.$refs.Authorization[type] = _authorization
} }
} }
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,100 +1,100 @@
<template> <template>
<div> <div>
<div class="parameters-header"> <div class="parameters-header">
<span>请求参数</span> <span>{{ $t('cmdb.components.requestParam') }}</span>
<a-space> <a-space>
<a-tooltip title="清空"> <a-tooltip :title="$t('cmdb.components.clear')">
<ops-icon <ops-icon
type="icon-xianxing-delete" type="icon-xianxing-delete"
@click=" @click="
() => { () => {
parameters = [] parameters = []
} }
" "
/> />
</a-tooltip> </a-tooltip>
<a-tooltip title="新增"> <a-tooltip :title="$t('new')">
<a-icon type="plus" @click="add" /> <a-icon type="plus" @click="add" />
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
<div class="parameters-box" v-if="parameters && parameters.length"> <div class="parameters-box" v-if="parameters && parameters.length">
<table> <table>
<tr v-for="(item, index) in parameters" :key="item.id"> <tr v-for="(item, index) in parameters" :key="item.id">
<td><a-input class="parameters-input" v-model="item.key" :placeholder="`参数${index + 1}`" /></td> <td><a-input class="parameters-input" v-model="item.key" :placeholder="$t('cmdb.components.param', { param: `${index + 1}` })" /></td>
<td><a-input class="parameters-input" v-model="item.value" :placeholder="`${index + 1}`" /></td> <td><a-input class="parameters-input" v-model="item.value" :placeholder="$t('cmdb.components.value', { value: `${index + 1}` })" /></td>
<td> <td>
<a style="color:red"> <a style="color:red">
<ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" /> <ops-icon type="icon-xianxing-delete" @click="deleteParam(index)" />
</a> </a>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<a-empty <a-empty
v-else v-else
:image-style="{ :image-style="{
height: '60px', height: '60px',
}" }"
> >
<img slot="image" :src="require('@/assets/data_empty.png')" /> <img slot="image" :src="require('@/assets/data_empty.png')" />
<span slot="description"> 暂无请求参数 </span> <span slot="description"> {{ $t('cmdb.components.noParamRequest') }} </span>
<a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary"> <a-button @click="add" type="primary" size="small" icon="plus" class="ops-button-primary">
添加 {{ $t('add') }}
</a-button> </a-button>
</a-empty> </a-empty>
</div> </div>
</template> </template>
<script> <script>
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
export default { export default {
name: 'Parameters', name: 'Parameters',
data() { data() {
return { return {
parameters: [], parameters: [],
} }
}, },
methods: { methods: {
add() { add() {
this.parameters.push({ this.parameters.push({
id: uuidv4(), id: uuidv4(),
key: '', key: '',
value: '', value: '',
}) })
}, },
deleteParam(index) { deleteParam(index) {
this.parameters.splice(index, 1) this.parameters.splice(index, 1)
}, },
}, },
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.parameters-header { .parameters-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
i { i {
cursor: pointer; cursor: pointer;
} }
} }
.parameters-box { .parameters-box {
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
table, table,
td, td,
th { th {
border: 1px solid #f3f4f6; border: 1px solid #f3f4f6;
} }
.parameters-input { .parameters-input {
border: none; border: none;
&:focus { &:focus {
box-shadow: none; box-shadow: none;
} }
} }
} }
</style> </style>

View File

@ -0,0 +1,483 @@
const cmdb_en = {
relation: 'Relation',
attribute: 'Attributes',
menu: {
views: 'Views',
config: 'Configuration',
backend: 'Management',
ciTable: 'Resource Views',
ciTree: 'Tree Views',
ciSearch: 'Search',
adCIs: 'AutoDiscovery Pool',
preference: 'Preference',
batchUpload: 'Batch Import',
citypeManage: 'Modeling',
backendManage: 'Backend',
customDashboard: 'Custom Dashboard',
serviceTreeDefine: 'Service Tree',
citypeRelation: 'CIType Relation',
operationHistory: 'Operation Audit',
relationType: 'Relation Type',
ad: 'AutoDiscovery',
},
ciType: {
ciType: 'CIType',
attributes: 'Attributes',
relation: 'Relation',
trigger: 'Triggers',
attributeAD: 'Attributes AutoDiscovery',
relationAD: 'Relation AutoDiscovery',
grant: 'Grant',
addGroup: 'New Group',
editGroup: 'Edit Group',
group: 'Group',
attributeLibray: 'Attribute Library',
addCITypeInGroup: 'Add a new CIType to the group',
addCIType: 'Add CIType',
editGroupName: 'Edit group name',
deleteGroup: 'Delete this group',
CITypeName: 'Name(English)',
English: 'English',
inputAttributeName: 'Please enter the attribute name',
attributeNameTips: 'It cannot start with a number, it can be English numbers and underscores (_)',
editCIType: 'Edit CIType',
defaultSort: 'Default sort',
selectDefaultOrderAttr: 'Select default sorting attributes',
asec: 'Forward order',
desc: 'Reverse order',
uniqueKey: 'Uniquely Identifies',
uniqueKeySelect: 'Please select a unique identifier',
notfound: 'Can\'t find what you want?',
cannotDeleteGroupTips: 'There is data under this group and cannot be deleted!',
confirmDeleteGroup: 'Are you sure you want to delete group [{groupName}]?',
confirmDeleteCIType: 'Are you sure you want to delete model [{typeName}]?',
uploading: 'Uploading',
uploadFailed: 'Upload failed, please try again later',
addPlugin: 'New plugin',
deletePlugin: 'Delete plugin',
confirmDeleteADT: 'Do you confirm to delete [{pluginName}]',
attributeMap: 'Attribute mapping',
autoDiscovery: 'AutoDiscovery',
node: 'Node',
adExecConfig: 'Execute configuration',
adExecTarget: 'Execute targets',
oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x',
selectFromCMDBTips: 'Select from CMDB ',
adAutoInLib: 'Save as CI auto',
adInterval: 'Collection frequency',
byInterval: 'by interval',
allNodes: 'All nodes',
specifyNodes: 'Specify Node',
specifyNodesTips: 'Please fill in the specify node!',
username: 'Username',
password: 'Password',
link: 'Link',
list: 'List',
listTips: 'The value of the field is one or more, and the type of the value returned by the interface is list.',
computeForAllCITips: 'All CI trigger computes',
confirmcomputeForAllCITips: 'Confirm triggering computes for all CIs?',
isUnique: 'Is it unique',
unique: 'Unique',
isChoice: 'Choiced',
defaultShow: 'Default Display',
defaultShowTips: 'The CI instance table displays this field by default',
isSortable: 'Sortable',
isIndex: 'Indexed',
index: 'Index',
indexTips: 'Fields can be used for retrieval to speed up queries',
confirmDelete: 'Confirm to delete [{name}]?',
confirmDelete2: 'Confirm to delete?',
computeSuccess: 'Triggered successfully!',
basicConfig: 'Basic Settings',
AttributeName: 'Name(English)',
DataType: 'Data Type',
defaultValue: 'Default value',
autoIncID: 'Auto-increment ID',
customTime: 'Custom time',
advancedSettings: 'Advanced Settings',
font: 'Font',
color: 'Color',
choiceValue: 'Predefined value',
computedAttribute: 'Computed Attribute',
computedAttributeTips: 'The value of this attribute is calculated through an expression constructed from other attributes of the CIType or by executing a piece of code. The reference method of the attribute is: {{ attribute name }}',
addAttribute: 'New attribute',
existedAttributes: 'Already have attributes',
editAttribute: 'Edit attribute',
addAttributeTips1: 'If sorting is selected, it must also be selected!',
uniqueConstraint: 'Unique Constraint',
up: 'Move up',
down: 'Move down',
selectAttribute: 'Select Attribute',
groupExisted: 'Group name already exists',
attributeSortedTips: 'Attributes in other groups cannot be sorted. If you need to sort, please drag them to a custom group first!',
buildinAttribute: 'built-in attributes',
expr: 'Expression',
code: 'Code',
apply: 'apply',
continueAdd: 'Keep adding',
filter: 'Filter',
choiceOther: 'Other CIType Attributes',
choiceWebhookTips: 'The returned results are filtered by fields, and the hierarchical nesting is separated by ##, such as k1##k2. The web request returns {k1: [{k2: 1}, {k2: 2}]}, and the parsing result is [1, 2 ]',
selectCIType: 'Please select a CMDB CIType',
selectCITypeAttributes: 'Please select CIType attributes',
selectAttributes: 'Please select attributes',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n Execution entry, returns predefined value\n :return: Returns a list, the type of the value is the same as the type of the attribute\n For example:\n return ["online", "offline"]\n """\n return []',
valueExisted: 'The current value already exists!',
addRelation: 'Add Relation',
sourceCIType: 'Source CIType',
sourceCITypeTips: 'Please select Source CIType',
dstCIType: 'Target CIType',
dstCITypeTips: 'Please select target CIType',
relationType: 'Relation Type',
relationTypeTips: 'Please select relation type',
isParent: 'is parent',
relationConstraint: 'Constraints',
relationConstraintTips: 'please select a relationship constraint',
one2Many: 'One to Many',
one2One: 'One to One',
many2Many: 'Many to Many',
basicInfo: 'Basic Information',
nameInputTips: 'Please enter name',
triggerDataChange: 'Data changes',
triggerDate: 'Date attribute',
triggerEnable: 'Turn on',
descInput: 'Please enter remarks',
triggerCondition: 'Triggering conditions',
addInstance: 'Add new instance',
deleteInstance: 'Delete instance',
changeInstance: 'Instance changes',
selectMutipleAttributes: 'Please select attributes (multiple selections)',
selectSingleAttribute: 'Please select an attribute (single choice)',
beforeDays: 'ahead of time',
days: 'Days',
notifyAt: 'Send time',
notify: 'Notify',
triggerAction: 'Trigger action',
receivers: 'Recipients',
emailTips: 'Please enter your email address, separate multiple email addresses with ;',
customEmail: 'Custom recipients',
notifySubject: 'Notification title',
notifySubjectTips: 'Please enter notification title',
notifyContent: 'Content',
notifyMethod: 'Notify methods',
botSelect: 'Please select a robot',
refAttributeTips: 'The title and content can reference the attribute value of the CIType. The reference method is: {{ attr_name }}',
webhookRefAttributeTips: 'Request parameters can reference the attribute value of the model. The reference method is: {{ attr_name }}',
newTrigger: 'Add trigger',
editTriggerTitle: 'Edit trigger {name}',
newTriggerTitle: 'Add trigger {name}',
confirmDeleteTrigger: 'Are you sure to delete this trigger?',
int: 'Integer',
float: 'Float',
text: 'Text',
datetime: 'DateTime',
date: 'Date',
time: 'Time',
json: 'JSON',
event: 'Event'
},
components: {
unselectAttributes: 'Unselected',
selectAttributes: 'Selected',
downloadCI: 'Export data',
filename: 'Filename',
filenameInputTips: 'Please enter filename',
saveType: 'Save type',
saveTypeTips: 'Please select save type',
xlsx: 'Excel workbook (*.xlsx)',
csv: 'CSV (comma separated) (*.csv)',
html: 'Web page (*.html)',
xml: 'XML data (*.xml)',
txt: 'Text file (tab delimited) (*.txt)',
grantUser: 'Grant User/Department',
grantRole: 'Grant Role',
confirmRevoke: 'Confirm to delete the [Authorization] permission of [{name}]?',
readAttribute: 'View Attributes',
readCI: 'View CIs',
config: 'Configuration',
ciTypeGrant: 'Grant CIType',
ciGrant: 'Grant CI',
attributeGrant: 'Grant Attribute',
relationGrant: 'Grant Relation',
perm: 'Permissions',
all: 'All',
customize: 'Customize',
none: 'None',
customizeFilterName: 'Please enter a custom filter name',
colorPickerError: 'Initialization color format error, use #fff or rgb format',
example: 'Example value',
aliyun: 'aliyun',
tencentcloud: 'Tencent Cloud',
huaweicloud: 'Huawei Cloud',
beforeChange: 'Before change',
afterChange: 'After change',
noticeContentTips: 'Please enter notification content',
saveQuery: 'Save Filters',
pleaseSearch: 'Please search',
conditionFilter: 'Conditional filtering',
attributeDesc: 'Attribute Description',
ciSearchTips: '1. JSON attributes cannot be searched<br />2. If the search content includes commas, they need to be escaped,<br />3. Only index attributes are searched, non-index attributes use conditional filtering',
ciSearchTips2: 'For example: q=hostname:*0.0.0.0*',
subCIType: 'Subscription CIType',
already: 'already',
not: 'not',
sub: 'subscription',
selectBelow: 'Please select below',
subSuccess: 'Subscription successful',
selectMethods: 'Please select a method',
noAuthRequest: 'No certification requested yet',
noParamRequest: 'No parameter certification yet',
requestParam: 'Request parameters',
param: 'Parameter{param}',
value: 'Value{value}',
clear: 'Clear',
},
batch: {
downloadFailed: 'Download failed',
unselectCIType: 'No CIType selected yet',
pleaseUploadFile: 'Please upload files',
batchUploadCanceled: 'Batch upload canceled',
selectCITypeTips: 'Please select CIType',
downloadTemplate: 'Download Template',
drawTips: 'Click or drag files here to upload!',
supportFileTypes: 'Supported file types: xls, xlsx',
uploadResult: 'Upload results',
total: 'total',
successItems: 'items, succeeded',
failedItems: 'items, failed',
items: 'items',
errorTips: 'Error message',
requestFailedTips: 'An error occurred with the request, please try again later',
requestSuccessTips: 'Upload completed',
},
preference: {
mySub: 'My Subscription',
sub: 'Subscribe',
cancelSub: 'Unsubscribe',
editSub: 'Edit subscription',
peopleSub: ' people subscribed',
noSub: 'No subscribed',
cancelSubSuccess: 'Unsubscribe successfully',
confirmcancelSub: 'Are you sure to cancel your subscription?',
confirmcancelSub2: 'Are you sure you want to unsubscribe {name}?',
of: 'of',
hoursAgo: 'hours ago',
daysAgo: 'days ago',
monthsAgo: 'month ago',
yearsAgo: 'years ago',
just: 'just now',
},
custom_dashboard: {
charts: 'Chart',
newChart: 'Add Chart',
editChart: 'Edit Chart',
title: 'Title',
titleTips: 'Please enter a chart title',
calcIndicators: 'Counter',
dimensions: 'Dimensions',
selectDimensions: 'Please select a dimension',
quantity: 'Quantity',
childCIType: 'Relational CIType',
level: 'Level',
levelTips: 'Please enter the relationship level',
preview: 'Preview',
showIcon: 'Display icon',
chartType: 'Chart Type',
dataFilter: 'Data Filtering',
format: 'Formats',
fontColor: 'Font Color',
backgroundColor: 'Background',
chartColor: 'Chart Color',
chartLength: 'Length',
barType: 'Bar Type',
stackedBar: 'Stacked Bar',
multipleSeriesBar: 'Multiple Series Bar ',
axis: 'Axis',
direction: 'Direction',
lowerShadow: 'Lower Shadow',
count: 'Indicator',
bar: 'Bar',
line: 'Line',
pie: 'Pie',
table: 'Table',
default: 'default',
relation: 'Relation',
noCustomDashboard: 'The administrator has not customized the dashboard yet',
},
preference_relation: {
newServiceTree: 'Add ServiceTree',
serviceTreeName: 'Name',
public: 'Public',
saveLayout: 'Save Layout',
childNodesNotFound: 'There are no child nodes and no business relationship can be formed. Please select again!',
tips1: 'Cannot form a view with the currently selected node, please select again!',
tips2: 'Please enter the new serviceTree name!',
tips3: 'Please select at least two nodes!',
},
history: {
ciChange: 'CI',
relationChange: 'Relation',
ciTypeChange: 'CIType',
triggerHistory: 'Triggers',
opreateTime: 'Operate Time',
user: 'User',
userTips: 'Enter filter username',
filter: 'Search',
filterOperate: 'fitler operation',
attribute: 'Attribute',
old: 'Old',
new: 'New',
noUpdate: 'No update',
itemsPerPage: '/page',
triggerName: 'Name',
event: 'Event',
action: 'Actoin',
status: 'Status',
done: 'Done',
undone: 'Undone',
triggerTime: 'Trigger Time',
totalItems: '{total} records in total',
pleaseSelect: 'Please select',
startTime: 'Start Time',
endTime: 'End Time',
deleteCIType: 'Delete CIType',
addCIType: 'Add CIType',
updateCIType: 'Update CIType',
addAttribute: 'Add Attribute',
updateAttribute: 'Update Attribute',
deleteAttribute: 'Delete Attribute',
addTrigger: 'Add Trigger',
updateTrigger: 'Update Trigger',
deleteTrigger: 'Delete Trigger',
addUniqueConstraint: 'Add Unique Constraint',
updateUniqueConstraint: 'Update Unique Constraint',
deleteUniqueConstraint: 'Delete Unique Constraint',
addRelation: 'Add Relation',
deleteRelation: 'Delete Relation',
noModifications: 'No Modifications',
attr: 'attribute',
attrId: 'attribute id',
changeDescription: 'attribute id: {attr_id}, {before_days} day(s) in advance, Subject: {subject}\nContent: {body}\nNotify At: {notify_at}'
},
relation_type: {
addRelationType: 'New',
nameTips: 'Please enter a type name',
},
ad: {
upload: 'Import',
download: 'Export',
accpet: 'Accept',
accpetBy: 'Accept By',
acceptTime: 'Accept Time',
confirmAccept: 'Confirm Accept?',
accpetSuccess: 'Accept successfully',
isAccpet: 'Is accept',
deleteADC: 'Confirm to delete this data?',
batchDelete: 'Confirm to delete this data?',
agent: 'Built-in & Plug-ins',
snmp: 'Network Devices',
http: 'Public Clouds',
rule: 'AutoDiscovery Rules',
timeout: 'Timeout error',
mode: 'Mode',
collectSettings: 'Collection Settings',
updateFields: 'Update Field',
pluginScript: `# -*- coding:utf-8 -*-
import json
class AutoDiscovery(object):
@property
def unique_key(self):
"""
:return: Returns the name of a unique attribute
"""
return
@staticmethod
def attributes():
"""
Define attribute fields
: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
For example:
return [
("ci_type", "String", "CIType name"),
("private_ip", "String", "Internal IP, multiple values separated by commas")
]
"""
return []
@staticmethod
def run():
"""
Execution entry, returns collected attribute values
:return:
Returns a list, the list item is a dictionary, the dictionary key is the attribute name, and the value is the attribute value
For example:
return [dict(ci_type="server", private_ip="192.168.1.1")]
"""
return []
if __name__ == "__main__":
result = AutoDiscovery().run()
if isinstance(result, list):
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
else:
print("ERROR: The collection return must be a list")
`,
server: 'Server',
vserver: 'VServer',
nic: 'NIC',
disk: 'harddisk',
},
ci: {
attributeDesc: 'Attribute Description',
selectRows: 'Select: {rows} items',
addRelation: 'Add Relation',
all: 'All',
batchUpdate: 'Batch Update',
batchUpdateConfirm: 'Are you sure you want to make batch updates?',
batchUpdateInProgress: 'Currently being updated in batches',
batchUpdateInProgress2: 'Updating in batches, {total} in total, {successNum} successful, {errorNum} failed',
batchDeleting: 'Deleting...',
batchDeleting2: 'Deleting {total} items in total, {successNum} items successful, {errorNum} items failed',
copyFailed: 'Copy failed',
noLevel: 'No hierarchical relationship!',
batchAddRelation: 'Batch Add Relation',
history: 'History',
topo: 'Topology',
table: 'Table',
m2mTips: 'The current CIType relationship is many-to-many, please go to the SerivceTree(relation view) to add or delete',
confirmDeleteRelation: 'Confirm to delete the relationship?',
tips1: 'Use commas to separate multiple values',
tips2: 'The field can be modified as needed. When the value is empty, the field will be left empty.',
tips3: 'Please select the fields that need to be modified',
tips4: 'At least one field must be selected',
tips5: 'Search name | alias',
tips6: 'Speed up retrieval, full-text search possible, no need to use conditional filtering\n\n json currently does not support indexing \n\nText characters longer than 190 cannot be indexed',
tips7: 'The form of expression is a drop-down box, and the value must be in the predefined value',
tips8: 'Multiple values, such as intranet IP',
tips9: 'For front-end only',
tips10: 'Other attributes of the CIType are computed using expressions\n\nA code snippet computes the returned value.',
newUpdateField: 'Add a Attribute',
attributeSettings: 'Attribute Settings',
},
serviceTree: {
deleteNode: 'Delete Node',
tips1: 'For example: q=os_version:centos&sort=os_version',
tips2: 'Expression search',
alert1: 'The administrator has not configured the ServiceTree(relation view), or you do not have permission to access it!',
copyFailed: 'Copy failed',
deleteRelationConfirm: 'Confirm to remove selected {name} from current relationship?',
},
tree: {
tips1: 'Please go to Preference page first to complete your subscription!',
subSettings: 'Settings',
}
}
export default cmdb_en

View File

@ -0,0 +1,482 @@
const cmdb_zh = {
relation: '关系',
attribute: '属性',
menu: {
views: '视图',
config: '配置',
backend: '管理端',
ciTable: '资源数据',
ciTree: '资源层级',
ciSearch: '资源搜索',
adCIs: '自动发现池',
preference: '我的订阅',
batchUpload: '批量导入',
citypeManage: '模型配置',
backendManage: '后台管理',
customDashboard: '定制仪表盘',
serviceTreeDefine: '服务树定义',
citypeRelation: '模型关系',
operationHistory: '操作审计',
relationType: '关系类型',
ad: '自动发现',
},
ciType: {
ciType: '模型',
attributes: '模型属性',
relation: '模型关联',
trigger: '触发器',
attributeAD: '属性自动发现',
relationAD: '关系自动发现',
grant: '权限配置',
addGroup: '新增分组',
editGroup: '修改分组',
group: '分组',
attributeLibray: '属性库',
addCITypeInGroup: '在该组中新增CI模型',
addCIType: '新增CI模型',
editGroupName: '编辑组名称',
deleteGroup: '删除该组',
CITypeName: '模型名(英文)',
English: '英文',
inputAttributeName: '请输入属性名',
attributeNameTips: '不能以数字开头,可以是英文 数字以及下划线 (_)',
editCIType: '编辑模型',
defaultSort: '默认排序',
selectDefaultOrderAttr: '选择默认排序属性',
asec: '正序',
desc: '倒序',
uniqueKey: '唯一标识',
uniqueKeySelect: '请选择唯一标识',
notfound: '找不到想要的?',
cannotDeleteGroupTips: '该分组下有数据, 不能删除!',
confirmDeleteGroup: '确定要删除分组 【{groupName}】 吗?',
confirmDeleteCIType: '确定要删除模型 【{typeName}】 吗?',
uploading: '正在导入中',
uploadFailed: '导入失败,请稍后重试',
addPlugin: '新建plugin',
deletePlugin: '删除plugin',
confirmDeleteADT: '确认删除 【{pluginName}】',
attributeMap: '字段映射',
autoDiscovery: '自动发现',
node: '节点',
adExecConfig: '执行配置',
adExecTarget: '执行机器',
oneagentIdTips: '请输入以0x开头的16进制OneAgent ID',
selectFromCMDBTips: '从CMDB中选择 ',
adAutoInLib: '自动入库',
adInterval: '采集频率',
byInterval: '按间隔',
allNodes: '所有节点',
specifyNodes: '指定节点',
specifyNodesTips: '请填写指定节点!',
username: '用户名',
password: '密码',
link: '链接',
list: '多值',
listTips: '字段的值是1个或者多个接口返回的值的类型是list',
computeForAllCITips: '所有CI触发计算',
confirmcomputeForAllCITips: '确认触发所有CI的计算',
isUnique: '是否唯一',
unique: '唯一',
isChoice: '是否选择',
defaultShow: '默认显示',
defaultShowTips: 'CI实例表格默认展示该字段',
isSortable: '可排序',
isIndex: '是否索引',
index: '索引',
indexTips: '字段可被用于检索,加速查询',
confirmDelete: '确认删除【{name}】?',
confirmDelete2: '确认删除?',
computeSuccess: '触发成功!',
basicConfig: '基础设置',
AttributeName: '属性名(英文)',
DataType: '数据类型',
defaultValue: '默认值',
autoIncID: '自增ID',
customTime: '自定义时间',
advancedSettings: '高级设置',
font: '字体',
color: '颜色',
choiceValue: '预定义值',
computedAttribute: '计算属性',
computedAttributeTips: '该属性的值是通过模型的其它属性构建的表达式或者执行一段代码的方式计算而来,属性的引用方法为: {{ 属性名 }}',
addAttribute: '新增属性',
existedAttributes: '已有属性',
editAttribute: '编辑属性',
addAttributeTips1: '选中排序,则必须也要选中!',
uniqueConstraint: '唯一校验',
up: '上移',
down: '下移',
selectAttribute: '添加属性',
groupExisted: '分组名称已存在',
attributeSortedTips: '其他分组中的属性不能进行排序,如需排序请先拖至自定义的分组!',
buildinAttribute: '内置字段',
expr: '表达式',
code: '代码',
apply: '应用',
continueAdd: '继续添加',
filter: '过滤',
choiceOther: '其他模型属性',
choiceWebhookTips: '返回的结果按字段来过滤,层级嵌套用##分隔比如k1##k2web请求返回{k1: [{k2: 1}, {k2: 2}]}, 解析结果为[1, 2]',
selectCIType: '请选择CMDB模型',
selectCITypeAttributes: '请选择模型属性',
selectAttributes: '请选择属性',
choiceScriptDemo: 'class ChoiceValue(object):\n @staticmethod\n def values():\n """\n 执行入口, 返回预定义值\n :return: 返回一个列表, 值的类型同属性的类型\n 例如:\n return ["在线", "下线"]\n """\n return []',
valueExisted: '当前值已存在!',
addRelation: '新增关系',
sourceCIType: '源模型',
sourceCITypeTips: '请选择源模型',
dstCIType: '目标模型名',
dstCITypeTips: '请选择目标模型',
relationType: '关联类型',
relationTypeTips: '请选择关联类型',
isParent: '被',
relationConstraint: '关系约束',
relationConstraintTips: '请选择关系约束',
one2Many: '一对多',
one2One: '一对一',
many2Many: '多对多',
basicInfo: '基本信息',
nameInputTips: '请输入名称',
triggerDataChange: '数据变更',
triggerDate: '日期属性',
triggerEnable: '开启',
descInput: '请输入备注',
triggerCondition: '触发条件',
addInstance: '新增实例',
deleteInstance: '删除实例',
changeInstance: '实例变更',
selectMutipleAttributes: '请选择属性(多选)',
selectSingleAttribute: '请选择属性(单选)',
beforeDays: '提前',
days: '天',
notifyAt: '发送时间',
notify: '通知',
triggerAction: '触发动作',
receivers: '收件人',
emailTips: '请输入邮箱,多个邮箱用;分隔',
customEmail: '自定义收件人',
notifySubject: '通知标题',
notifySubjectTips: '请输入通知标题',
notifyContent: '内容',
notifyMethod: '通知方式',
botSelect: '请选择机器人',
refAttributeTips: '标题、内容可以引用该模型的属性值,引用方法为: {{ attr_name }}',
webhookRefAttributeTips: '请求参数可以引用该模型的属性值,引用方法为: {{ attr_name }}',
newTrigger: '新增触发器',
editTriggerTitle: '编辑触发器 {name}',
newTriggerTitle: '新增触发器 {name}',
confirmDeleteTrigger: '确认删除该触发器吗?',
int: '整数',
float: '浮点数',
text: '文本',
datetime: '日期时间',
date: '日期',
time: '时间',
json: 'JSON',
event: '事件'
},
components: {
unselectAttributes: '未选属性',
selectAttributes: '已选属性',
downloadCI: '导出数据',
filename: '文件名',
filenameInputTips: '请输入文件名',
saveType: '保存类型',
saveTypeTips: '请选择保存类型',
xlsx: 'Excel工作簿(*.xlsx)',
csv: 'CSV(逗号分隔)(*.csv)',
html: '网页(*.html)',
xml: 'XML数据(*.xml)',
txt: '文本文件(制表符分隔)(*.txt)',
grantUser: '授权用户/部门',
grantRole: '授权角色',
confirmRevoke: '确认删除 【{name}】 的 【授权】 权限?',
readAttribute: '查看字段',
readCI: '查看实例',
config: '配置',
ciTypeGrant: '模型权限',
ciGrant: '实例权限',
attributeGrant: '字段权限',
relationGrant: '关系权限',
perm: '权限',
all: '全部',
customize: '自定义',
none: '无',
customizeFilterName: '请输入自定义筛选条件名',
colorPickerError: '初始化颜色格式错误,使用#fff或rgb格式',
example: '示例值',
aliyun: '阿里云',
tencentcloud: '腾讯云',
huaweicloud: '华为云',
beforeChange: '变更前',
afterChange: '变更后',
noticeContentTips: '请输入通知内容',
saveQuery: '保存筛选条件',
pleaseSearch: '请查找',
conditionFilter: '条件过滤',
attributeDesc: '属性说明',
ciSearchTips: '1. json属性不能搜索<br />2. 搜索内容包括逗号, 则需转义 ,<br />3. 只搜索索引属性, 非索引属性使用条件过滤',
ciSearchTips2: '例: q=hostname:*0.0.0.0*',
subCIType: '订阅模型',
already: '已',
not: '未',
sub: '订阅',
selectBelow: '请在下方进行选择',
subSuccess: '订阅成功',
selectMethods: '请选择方式',
noAuthRequest: '暂无请求认证',
noParamRequest: '暂无参数认证',
requestParam: '请求参数',
param: '参数{param}',
value: '值{value}',
clear: '清空',
},
batch: {
downloadFailed: '失败下载',
unselectCIType: '尚未选择模板类型',
pleaseUploadFile: '请上传文件',
batchUploadCanceled: '批量上传已取消',
selectCITypeTips: '请选择模板类型',
downloadTemplate: '下载模板',
drawTips: '点击或拖拽文件至此上传!',
supportFileTypes: '支持文件类型xlsxlsx',
uploadResult: '上传结果',
total: '共',
successItems: '条,已成功',
failedItems: '条,失败',
items: '条',
errorTips: '错误信息',
requestFailedTips: '请求出现错误,请稍后再试',
requestSuccessTips: '批量上传已完成',
},
preference: {
mySub: '我的订阅',
sub: '订阅',
cancelSub: '取消订阅',
editSub: '编辑订阅',
peopleSub: '位同事已订阅',
noSub: '暂无同事订阅',
cancelSubSuccess: '取消订阅成功',
confirmcancelSub: '确认取消订阅',
confirmcancelSub2: '确认取消订阅 {name} 吗?',
of: '的',
hoursAgo: '小时前',
daysAgo: '天前',
monthsAgo: '月前',
yearsAgo: '年前',
just: '刚刚',
},
custom_dashboard: {
charts: '图表',
newChart: '新增图表',
editChart: '编辑图表',
title: '标题',
titleTips: '请输入图表标题',
calcIndicators: '计算指标',
dimensions: '维度',
selectDimensions: '请选择维度',
quantity: '数量',
childCIType: '关系模型',
level: '层级',
levelTips: '请输入关系层级',
preview: '预览',
showIcon: '是否显示icon',
chartType: '图表类型',
dataFilter: '数据筛选',
format: '格式',
fontColor: '字体颜色',
backgroundColor: '背景颜色',
chartColor: '图表颜色',
chartLength: '图表长度',
barType: '柱状图类型',
stackedBar: '堆积柱状图',
multipleSeriesBar: '多系列柱状图',
axis: '轴',
direction: '方向',
lowerShadow: '下方阴影',
count: '指标',
bar: '柱状图',
line: '折线图',
pie: '饼状图',
table: '表格',
default: '默认',
relation: '关系',
noCustomDashboard: '管理员暂未定制仪表盘',
},
preference_relation: {
newServiceTree: '新增服务树',
serviceTreeName: '服务树名',
public: '公开',
saveLayout: '保存布局',
childNodesNotFound: '不存在子节点,不能形成业务关系,请重新选择!',
tips1: '不能与当前选中节点形成视图,请重新选择!',
tips2: '请输入新增服务树名!',
tips3: '请选择至少两个节点!',
},
history: {
ciChange: 'CI变更',
relationChange: '关系变更',
ciTypeChange: '模型变更',
triggerHistory: '触发历史',
opreateTime: '操作时间',
user: '用户',
userTips: '输入筛选用户名',
filter: '筛选',
filterOperate: '筛选操作',
attribute: '属性',
old: '旧',
new: '新',
noUpdate: '没有修改',
itemsPerPage: '/页',
triggerName: '触发器名称',
event: '事件',
action: '动作',
status: '状态',
done: '已完成',
undone: '未完成',
triggerTime: '触发时间',
totalItems: '共 {total} 条记录',
pleaseSelect: '请选择',
startTime: '开始时间',
endTime: '结束时间',
deleteCIType: '删除模型',
addCIType: '新增模型',
updateCIType: '修改模型',
addAttribute: '新增属性',
updateAttribute: '修改属性',
deleteAttribute: '删除属性',
addTrigger: '新增触发器',
updateTrigger: '修改触发器',
deleteTrigger: '删除触发器',
addUniqueConstraint: '新增联合唯一',
updateUniqueConstraint: '修改联合唯一',
deleteUniqueConstraint: '删除联合唯一',
addRelation: '新增关系',
deleteRelation: '删除关系',
noModifications: '没有修改',
attr: '属性名',
attrId: '属性ID',
changeDescription: '属性ID{attr_id},提前:{before_days}天,主题:{subject}\n内容{body}\n通知时间{notify_at}'
},
relation_type: {
addRelationType: '新增关系类型',
nameTips: '请输入类型名',
},
ad: {
upload: '规则导入',
download: '规则导出',
accpet: '入库',
accpetBy: '入库人',
acceptTime: '入库时间',
confirmAccept: '确认入库?',
accpetSuccess: '入库成功',
isAccpet: '是否入库',
deleteADC: '确认删除该条数据?',
batchDelete: '确认删除这些数据?',
agent: '内置 & 插件',
snmp: '网络设备',
http: '公有云资源',
rule: '自动发现规则',
timeout: '超时错误',
mode: '模式',
collectSettings: '采集设置',
updateFields: '更新字段',
pluginScript: `# -*- coding:utf-8 -*-
import json
class AutoDiscovery(object):
@property
def unique_key(self):
"""
:return: 返回唯一属性的名字
"""
return
@staticmethod
def attributes():
"""
定义属性字段
:return: 返回属性字段列表, 列表项是(名称, 类型, 描述), 名称必须是英文
类型: String Integer Float Date DateTime Time JSON
例如:
return [
("ci_type", "String", "模型名称"),
("private_ip", "String", "内网IP, 多值逗号分隔")
]
"""
return []
@staticmethod
def run():
"""
执行入口, 返回采集的属性值
:return: 返回一个列表, 列表项是字典, 字典key是属性名称, value是属性值
例如:
return [dict(ci_type="server", private_ip="192.168.1.1")]
"""
return []
if __name__ == "__main__":
result = AutoDiscovery().run()
if isinstance(result, list):
print("AutoDiscovery::Result::{}".format(json.dumps(result)))
else:
print("ERROR: 采集返回必须是列表")
`,
server: '物理机',
vserver: '虚拟机',
nic: '网卡',
disk: '硬盘',
},
ci: {
attributeDesc: '属性说明',
selectRows: '选取:{rows} 项',
addRelation: '添加关系',
all: '全部',
batchUpdate: '批量修改',
batchUpdateConfirm: '确认要批量修改吗?',
batchUpdateInProgress: '正在批量修改',
batchUpdateInProgress2: '正在批量修改,共{total}个,成功{successNum}个,失败{errorNum}个',
batchDeleting: '正在删除...',
batchDeleting2: '正在删除,共{total}个,成功{successNum}个,失败{errorNum}个',
copyFailed: '复制失败!',
noLevel: '无层级关系!',
batchAddRelation: '批量添加关系',
history: '操作历史',
topo: '拓扑',
table: '表格',
m2mTips: '当前模型关系为多对多,请前往关系视图进行增删操作',
confirmDeleteRelation: '确认删除关系?',
tips1: '多个值使用,分割',
tips2: '可根据需要修改字段,当值为 空 时,则该字段 置空',
tips3: '请选择需要修改的字段',
tips4: '必须至少选择一个字段',
tips5: '搜索 名称 | 别名',
tips6: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引',
tips7: '表现形式是下拉框, 值必须在预定义值里',
tips8: '多值, 比如内网IP',
tips9: '仅针对前端',
tips10: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值',
newUpdateField: '新增修改字段',
attributeSettings: '字段设置',
},
serviceTree: {
deleteNode: '删除节点',
tips1: '例q=os_version:centos&sort=os_version',
tips2: '表达式搜索',
alert1: '管理员 还未配置业务关系, 或者你无权限访问!',
copyFailed: '复制失败',
deleteRelationConfirm: '确认将选中的 {name} 从当前关系中删除?',
},
tree: {
tips1: '请先到 我的订阅 页面完成订阅!',
subSettings: '订阅设置',
}
}
export default cmdb_zh

View File

@ -13,19 +13,19 @@ const genCmdbRoutes = async () => {
{ {
path: '/cmdb/dashboard', path: '/cmdb/dashboard',
name: 'cmdb_dashboard', name: 'cmdb_dashboard',
meta: { title: '仪表盘', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false }, meta: { title: 'dashboard', icon: 'ops-cmdb-dashboard', selectedIcon: 'ops-cmdb-dashboard-selected', keepAlive: false },
component: () => import('../views/dashboard/index_v2.vue') component: () => import('../views/dashboard/index_v2.vue')
}, },
{ {
path: '/cmdb/disabled1', path: '/cmdb/disabled1',
name: 'cmdb_disabled1', name: 'cmdb_disabled1',
meta: { title: '视图', disabled: true }, meta: { title: 'cmdb.menu.views', disabled: true },
}, },
{ {
path: '/cmdb/resourceviews', path: '/cmdb/resourceviews',
name: 'cmdb_resource_views', name: 'cmdb_resource_views',
component: RouteView, component: RouteView,
meta: { title: '资源数据', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true }, meta: { title: 'cmdb.menu.ciTable', icon: 'ops-cmdb-resource', selectedIcon: 'ops-cmdb-resource-selected', keepAlive: true },
hideChildrenInMenu: false, hideChildrenInMenu: false,
children: [] children: []
}, },
@ -33,108 +33,108 @@ const genCmdbRoutes = async () => {
path: '/cmdb/tree_views', path: '/cmdb/tree_views',
component: () => import('../views/tree_views'), component: () => import('../views/tree_views'),
name: 'cmdb_tree_views', name: 'cmdb_tree_views',
meta: { title: '资源层级', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false }, meta: { title: 'cmdb.menu.ciTree', icon: 'ops-cmdb-tree', selectedIcon: 'ops-cmdb-tree-selected', keepAlive: false },
hideChildrenInMenu: true, hideChildrenInMenu: true,
children: [ children: [
{ {
path: '/cmdb/tree_views/:typeId', path: '/cmdb/tree_views/:typeId',
name: 'cmdb_tree_views_item', name: 'cmdb_tree_views_item',
component: () => import('../views/tree_views'), component: () => import('../views/tree_views'),
meta: { title: '资源层级', keepAlive: false }, meta: { title: 'cmdb.menu.ciTree', keepAlive: false },
hidden: true hidden: true
}] }]
}, },
{ {
path: '/cmdb/resourcesearch', path: '/cmdb/resourcesearch',
name: 'cmdb_resource_search', name: 'cmdb_resource_search',
meta: { title: '资源搜索', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false }, meta: { title: 'cmdb.menu.ciSearch', icon: 'ops-cmdb-search', selectedIcon: 'ops-cmdb-search-selected', keepAlive: false },
component: () => import('../views/resource_search/index.vue') component: () => import('../views/resource_search/index.vue')
}, },
{ {
path: '/cmdb/adc', path: '/cmdb/adc',
name: 'cmdb_auto_discovery_ci', name: 'cmdb_auto_discovery_ci',
meta: { title: '自动发现池', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false }, meta: { title: 'cmdb.menu.adCIs', icon: 'ops-cmdb-adc', selectedIcon: 'ops-cmdb-adc-selected', keepAlive: false },
component: () => import('../views/discoveryCI/index.vue') component: () => import('../views/discoveryCI/index.vue')
}, },
{ {
path: '/cmdb/disabled2', path: '/cmdb/disabled2',
name: 'cmdb_disabled2', name: 'cmdb_disabled2',
meta: { title: '配置', disabled: true, }, meta: { title: 'cmdb.menu.config', disabled: true, },
}, },
{ {
path: '/cmdb/preference', path: '/cmdb/preference',
component: () => import('../views/preference/index'), component: () => import('../views/preference/index'),
name: 'cmdb_preference', name: 'cmdb_preference',
meta: { title: '我的订阅', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false } meta: { title: 'cmdb.menu.preference', icon: 'ops-cmdb-preference', selectedIcon: 'ops-cmdb-preference-selected', keepAlive: false }
}, },
{ {
path: '/cmdb/batch', path: '/cmdb/batch',
component: () => import('../views/batch'), component: () => import('../views/batch'),
name: 'cmdb_batch', name: 'cmdb_batch',
meta: { 'title': '批量导入', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false } meta: { 'title': 'cmdb.menu.batchUpload', icon: 'ops-cmdb-batch', selectedIcon: 'ops-cmdb-batch-selected', keepAlive: false }
}, },
{ {
path: '/cmdb/ci_types', path: '/cmdb/ci_types',
name: 'ci_type', name: 'ci_type',
component: () => import('../views/ci_types/index'), component: () => import('../views/ci_types/index'),
meta: { title: '模型配置', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] } meta: { title: 'cmdb.menu.citypeManage', icon: 'ops-cmdb-citype', selectedIcon: 'ops-cmdb-citype-selected', keepAlive: false, permission: ['cmdb_admin', 'admin'] }
}, },
{ {
path: '/cmdb/disabled3', path: '/cmdb/disabled3',
name: 'cmdb_disabled3', name: 'cmdb_disabled3',
meta: { title: '管理端', disabled: true, permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], }, meta: { title: 'cmdb.menu.backend', disabled: true, permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
}, },
{ {
path: '/cmdb/citypes', path: '/cmdb/citypes',
name: 'cmdb_ci_type', name: 'cmdb_ci_type',
component: RouteView, component: RouteView,
redirect: '/cmdb/ci_type', redirect: '/cmdb/ci_type',
meta: { title: '后台管理', icon: 'setting', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], }, meta: { title: 'cmdb.menu.backendManage', icon: 'setting', permission: ['cmdb_admin', 'OneOPS_Application_Admin', 'admin'], },
children: [ children: [
{ {
path: '/cmdb/customdashboard', path: '/cmdb/customdashboard',
name: 'cmdb_custom_dashboard', name: 'cmdb_custom_dashboard',
component: () => import('../views/custom_dashboard/index'), component: () => import('../views/custom_dashboard/index'),
meta: { title: '定制仪表盘', keepAlive: false, icon: 'ops-cmdb-customdashboard', selectedIcon: 'ops-cmdb-customdashboard-selected' } meta: { title: 'cmdb.menu.customDashboard', keepAlive: false, icon: 'ops-cmdb-customdashboard', selectedIcon: 'ops-cmdb-customdashboard-selected' }
}, },
{ {
path: '/cmdb/preferencerelation', path: '/cmdb/preferencerelation',
name: 'preference_relation', name: 'preference_relation',
component: () => import('../views/preference_relation/index'), component: () => import('../views/preference_relation/index'),
meta: { title: '业务关系定义', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' } meta: { title: 'cmdb.menu.serviceTreeDefine', keepAlive: false, icon: 'ops-cmdb-preferencerelation', selectedIcon: 'ops-cmdb-preferencerelation-selected' }
}, },
{ {
path: '/cmdb/modelrelation', path: '/cmdb/modelrelation',
name: 'model_relation', name: 'model_relation',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/model_relation/index'), component: () => import('../views/model_relation/index'),
meta: { title: '模型关系', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' } meta: { title: 'cmdb.menu.citypeRelation', keepAlive: false, icon: 'ops-cmdb-modelrelation', selectedIcon: 'ops-cmdb-modelrelation-selected' }
}, },
{ {
path: '/cmdb/operationhistory', path: '/cmdb/operationhistory',
name: 'operation_history', name: 'operation_history',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/operation_history/index'), component: () => import('../views/operation_history/index'),
meta: { title: '操作审计', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' } meta: { title: 'cmdb.menu.operationHistory', keepAlive: false, icon: 'ops-cmdb-operation', selectedIcon: 'ops-cmdb-operation-selected' }
}, },
{ {
path: '/cmdb/relationtype', path: '/cmdb/relationtype',
name: 'relation_type', name: 'relation_type',
hideChildrenInMenu: true, hideChildrenInMenu: true,
component: () => import('../views/relation_type/index'), component: () => import('../views/relation_type/index'),
meta: { title: '关系类型', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' } meta: { title: 'cmdb.menu.relationType', keepAlive: false, icon: 'ops-cmdb-relationtype', selectedIcon: 'ops-cmdb-relationtype-selected' }
}, },
{ {
path: '/cmdb/discovery', path: '/cmdb/discovery',
name: 'discovery', name: 'discovery',
component: () => import('../views/discovery/index'), component: () => import('../views/discovery/index'),
meta: { title: '自动发现', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' } meta: { title: 'cmdb.menu.ad', keepAlive: false, icon: 'ops-cmdb-adr', selectedIcon: 'ops-cmdb-adr-selected' }
}, },
] ]
} }
] ]
} }
// 动态添加订阅的条目及业务关系 // Dynamically add subscription items and business relationships
const [preference, relation] = await Promise.all([getPreference(), getRelationView()]) const [preference, relation] = await Promise.all([getPreference(), getRelationView()])
preference.forEach(item => { preference.forEach(item => {
@ -143,7 +143,7 @@ const genCmdbRoutes = async () => {
component: () => import(`../views/ci/index`), component: () => import(`../views/ci/index`),
name: `cmdb_${item.id}`, name: `cmdb_${item.id}`,
meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon }, meta: { title: item.alias, keepAlive: false, typeId: item.id, name: item.name, customIcon: item.icon },
// hideChildrenInMenu: true // 强制显示 MenuItem 而不是 SubMenu // hideChildrenInMenu: true // Force display of MenuItem instead of SubMenu
}) })
}) })
const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined const lastTypeId = window.localStorage.getItem('ops_ci_typeid') || undefined

View File

@ -1,28 +1,32 @@
export const valueTypeMap = { import i18n from '@/lang'
'0': '整数',
'1': '浮点数', export const valueTypeMap = () => {
'2': '文本', return {
'3': '日期时间', '0': i18n.t('cmdb.ciType.int'),
'4': '日期', '1': i18n.t('cmdb.ciType.float'),
'5': '时间', '2': i18n.t('cmdb.ciType.text'),
'6': 'JSON', '3': i18n.t('cmdb.ciType.datetime'),
'7': '密码', '4': i18n.t('cmdb.ciType.date'),
'8': '链接' '5': i18n.t('cmdb.ciType.time'),
} '6': 'JSON',
'7': i18n.t('cmdb.ciType.password'),
export const defautValueColor = [ '8': i18n.t('cmdb.ciType.link')
{ value: '#d9d9d9' }, }
{ value: '#ffccc7' }, }
{ value: '#ffd8bf' },
{ value: '#ffe7ba' }, export const defautValueColor = [
{ value: '#fff1b8' }, { value: '#d9d9d9' },
{ value: '#f4ffb8' }, { value: '#ffccc7' },
{ value: '#d9f7be' }, { value: '#ffd8bf' },
{ value: '#b5f5ec' }, { value: '#ffe7ba' },
{ value: '#bae7ff' }, { value: '#fff1b8' },
{ value: '#d6e4ff' }, { value: '#f4ffb8' },
{ value: '#efdbff' }, { value: '#d9f7be' },
{ value: '#ffd6e7' }, { value: '#b5f5ec' },
] { value: '#bae7ff' },
{ value: '#d6e4ff' },
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7'] { value: '#efdbff' },
{ value: '#ffd6e7' },
]
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7']

View File

@ -16,9 +16,9 @@
<CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable> <CiUploadTable :ciTypeAttrs="ciTypeAttrs" ref="ciUploadTable" :uploadData="uploadData"></CiUploadTable>
<div class="cmdb-batch-upload-action"> <div class="cmdb-batch-upload-action">
<a-space size="large"> <a-space size="large">
<a-button type="primary" ghost @click="handleCancel">取消</a-button> <a-button type="primary" ghost @click="handleCancel">{{ $t('cancel') }}</a-button>
<a-button @click="handleUpload" type="primary">上传</a-button> <a-button @click="handleUpload" type="primary">{{ $t('upload') }}</a-button>
<a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">失败下载</a-button> <a-button v-if="hasError && !isUploading" @click="downloadError" type="primary">{{ $t('cmdb.batch.downloadFailed') }}</a-button>
</a-space> </a-space>
</div> </div>
</a-col> </a-col>
@ -109,7 +109,7 @@ export default {
}, },
handleUpload() { handleUpload() {
if (!this.ciType) { if (!this.ciType) {
this.$message.error('尚未选择模板类型') this.$message.error(this.$t('cmdb.batch.unselectCIType'))
return return
} }
if (this.uploadData && this.uploadData.length > 0) { if (this.uploadData && this.uploadData.length > 0) {
@ -118,7 +118,7 @@ export default {
this.$refs.uploadResult.upload2Server() this.$refs.uploadResult.upload2Server()
}) })
} else { } else {
this.$message.error('请上传文件') this.$message.error(this.$t('cmdb.batch.pleaseUploadFile'))
} }
}, },
handleCancel() { handleCancel() {
@ -127,7 +127,7 @@ export default {
this.$refs.ciTypeChoice.selectNum = null this.$refs.ciTypeChoice.selectNum = null
this.hasError = false this.hasError = false
} else { } else {
this.$message.warning('批量上传已取消') this.$message.warning(this.$t('cmdb.batch.batchUploadCanceled'))
this.isUploading = false this.isUploading = false
} }
}, },

View File

@ -1,9 +1,9 @@
<template> <template>
<a-space> <a-space>
<span>模板类型</span> <span>{{ $t('cmdb.ciType.ciType') }}: </span>
<a-select <a-select
showSearch showSearch
placeholder="请选择模板类型" :placeholder="$t('cmdb.batch.selectCITypeTips')"
@change="selectCiType" @change="selectCiType"
:style="{ width: '300px' }" :style="{ width: '300px' }"
class="ops-select" class="ops-select"
@ -20,7 +20,7 @@
type="primary" type="primary"
class="ops-button-primary" class="ops-button-primary"
icon="download" icon="download"
>下载模板</a-button >{{ $t('cmdb.batch.downloadTemplate') }}</a-button
> >
<a-modal <a-modal
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
@ -31,14 +31,14 @@
@ok="handleOk" @ok="handleOk"
wrapClassName="ci-type-choice-modal" wrapClassName="ci-type-choice-modal"
> >
<a-divider orientation="left">模型属性</a-divider> <a-divider orientation="left">{{ $t('cmdb.ciType.attributes') }}</a-divider>
<a-checkbox <a-checkbox
@change="changeCheckAll" @change="changeCheckAll"
:style="{ marginBottom: '20px' }" :style="{ marginBottom: '20px' }"
:indeterminate="indeterminate" :indeterminate="indeterminate"
:checked="checkAll" :checked="checkAll"
> >
全选 {{ $t('checkAll') }}
</a-checkbox> </a-checkbox>
<br /> <br />
<a-checkbox-group style="width:100%" v-model="checkedAttrs"> <a-checkbox-group style="width:100%" v-model="checkedAttrs">
@ -52,7 +52,7 @@
</a-row> </a-row>
</a-checkbox-group> </a-checkbox-group>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider orientation="left">模型关联</a-divider> <a-divider orientation="left">{{ $t('cmdb.ciType.relation') }}</a-divider>
<a-row :gutter="[24, 24]" align="top" type="flex"> <a-row :gutter="[24, 24]" align="top" type="flex">
<a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id"> <a-col :style="{ display: 'inline-flex' }" :span="12" v-for="item in parentsType" :key="item.id">
<a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)"> <a-checkbox @click="clickParent(item)" :checked="checkedParents.includes(item.alias || item.name)">
@ -150,7 +150,7 @@ export default {
}, },
methods: { methods: {
selectCiType(el) { selectCiType(el) {
// 当选择好模板类型时的回调函数 // Callback function when a template type is selected
getCITypeAttributesById(el).then((res) => { getCITypeAttributesById(el).then((res) => {
this.$emit('getCiTypeAttr', res) this.$emit('getCiTypeAttr', res)
this.selectCiTypeAttrList = res this.selectCiTypeAttrList = res

View File

@ -10,8 +10,8 @@
:disabled="!ciType || isUploading" :disabled="!ciType || isUploading"
> >
<img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" /> <img :style="{ width: '80px', height: '80px' }" src="@/assets/file_upload.png" />
<p class="ant-upload-text">点击或拖拽文件至此上传</p> <p class="ant-upload-text">{{ $t('cmdb.batch.drawTips') }}</p>
<p class="ant-upload-hint">支持文件类型xlsxlsx</p> <p class="ant-upload-hint">{{ $t('cmdb.batch.supportFileTypes') }}</p>
</a-upload-dragger> </a-upload-dragger>
<div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file"> <div v-for="item in fileList" :key="item.name" class="cmdb-batch-upload-dragger-file">
<span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span> <span><a-icon type="file" :style="{ color: '#2F54EB', marginRight: '5px' }" />{{ item.name }}</span>

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="cmdb-batch-upload-result" v-if="visible"> <div class="cmdb-batch-upload-result" v-if="visible">
<h3 class="cmdb-batch-upload-result-title">上传结果</h3> <h3 class="cmdb-batch-upload-result-title">{{ $t('cmdb.batch.uploadResult') }}</h3>
<div class="cmdb-batch-upload-result-content"> <div class="cmdb-batch-upload-result-content">
<h4> <h4>
&nbsp;<span style="color: blue">{{ total }}</span> 已成功 {{ $t('cmdb.batch.total') }}&nbsp;<span style="color: blue">{{ total }}</span> {{ $t('cmdb.batch.successItems') }}
<span style="color: lightgreen">{{ success }}</span> 失败 <span style="color: red">{{ errorNum }} </span> <span style="color: lightgreen">{{ success }}</span> {{ $t('cmdb.batch.failedItems') }} <span style="color: red">{{ errorNum }} </span>{{ $t('cmdb.batch.items') }}
</h4> </h4>
<div> <div>
<span>错误信息</span> <span>{{ $t('cmdb.batch.errorTips') }}: </span>
<ol> <ol>
<li :key="item + index" v-for="(item, index) in errorItems">{{ item }}</li> <li :key="item + index" v-for="(item, index) in errorItems">{{ item }}</li>
</ol> </ol>
@ -71,7 +71,7 @@ export default {
if (r.status === 'fulfilled') { if (r.status === 'fulfilled') {
this.success += 1 this.success += 1
} else { } else {
this.errorItems.push(r?.reason?.response?.data.message ?? '请求出现错误,请稍后再试') this.errorItems.push(r?.reason?.response?.data.message ?? this.$t('cmdb.batch.requestFailedTips'))
this.errorNum += 1 this.errorNum += 1
this.$emit('uploadResultError', 6 * i + j) this.$emit('uploadResultError', 6 * i + j)
} }
@ -86,7 +86,7 @@ export default {
} }
if (this.isUploading) { if (this.isUploading) {
this.$emit('uploadResultDone') this.$emit('uploadResultDone')
this.$message.success('批量上传已完成') this.$message.success(this.$t('cmdb.batch.requestSuccessTips'))
} }
}, },
}, },

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
<template> <template>
<a-drawer <a-drawer
title="批量添加关系" :title="$t('cmdb.ci.batchAddRelation')"
width="50%" width="50%"
@close="() => { visible = false; $emit('refresh', true) }" @close="() => { visible = false; $emit('refresh', true) }"
:visible="visible" :visible="visible"

View File

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

View File

@ -1,386 +1,388 @@
<template> <template>
<CustomDrawer <CustomDrawer
:title="title + CIType.alias" :title="title + CIType.alias"
width="800" width="800"
@close="handleClose" @close="handleClose"
:maskClosable="false" :maskClosable="false"
:visible="visible" :visible="visible"
wrapClassName="create-instance-form" wrapClassName="create-instance-form"
:bodyStyle="{ paddingTop: 0 }" :bodyStyle="{ paddingTop: 0 }"
:headerStyle="{ borderBottom: 'none' }" :headerStyle="{ borderBottom: 'none' }"
> >
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleClose">取消</a-button> <a-button @click="handleClose">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="createInstance">提交</a-button> <a-button type="primary" @click="createInstance">{{ $t('submit') }}</a-button>
</div> </div>
<template v-if="action === 'create'"> <template v-if="action === 'create'">
<template v-for="group in attributesByGroup"> <template v-for="group in attributesByGroup">
<CreateInstanceFormByGroup <CreateInstanceFormByGroup
:ref="`createInstanceFormByGroup_${group.id}`" :ref="`createInstanceFormByGroup_${group.id}`"
:key="group.id || group.name" :key="group.id || group.name"
:group="group" :group="group"
@handleFocusInput="handleFocusInput" @handleFocusInput="handleFocusInput"
:attributeList="attributeList" :attributeList="attributeList"
/> />
</template> </template>
<template v-if="parentsType && parentsType.length"> <template v-if="parentsType && parentsType.length">
<a-divider style="font-size:14px;margin:14px 0;font-weight:700;">模型关系</a-divider> <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ $t('cmdb.menu.citypeRelation') }}</a-divider>
<a-form> <a-form>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col :span="12" v-for="item in parentsType" :key="item.id"> <a-col :span="12" v-for="item in parentsType" :key="item.id">
<a-form-item :label="item.alias || item.name" :colon="false"> <a-form-item :label="item.alias || item.name" :colon="false">
<a-input-group compact style="width: 100%"> <a-input-group compact style="width: 100%">
<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 item.attributes"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
> >
{{ attr.alias || attr.name }} {{ attr.alias || attr.name }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input placeholder="多个值使用,分割" v-model="parentsForm[item.name].value" style="width: 50%" /> <a-input :placeholder="$t('cmdb.ci.tips1')" v-model="parentsForm[item.name].value" style="width: 50%" />
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</template> </template>
</template> </template>
<template v-if="action === 'update'"> <template v-if="action === 'update'">
<a-form :form="form"> <a-form :form="form">
<p>可根据需要修改字段当值为<strong></strong>则该字段<strong>置空</strong></p> <p>{{ $t('cmdb.ci.tips2') }}</p>
<a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name"> <a-row :gutter="24" v-for="list in batchUpdateLists" :key="list.name">
<a-col :span="11"> <a-col :span="11">
<a-form-item> <a-form-item>
<el-select showSearch size="small" filterable v-model="list.name" placeholder="请选择需要修改的字段"> <el-select showSearch size="small" filterable v-model="list.name" :placeholder="$t('cmdb.ci.tips3')">
<el-option <el-option
v-for="attr in attributeList" v-for="attr in attributeList"
:key="attr.name" :key="attr.name"
:value="attr.name" :value="attr.name"
:disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1" :disabled="batchUpdateLists.findIndex((item) => item.name === attr.name) > -1"
:label="attr.alias || attr.name" :label="attr.alias || attr.name"
> >
</el-option> </el-option>
</el-select> </el-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="11"> <a-col :span="11">
<a-form-item> <a-form-item>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="getFieldType(list.name).split('%%')[0] === 'select'" v-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
> >
<a-select-option <a-select-option
:value="choice[0]" :value="choice[0]"
:key="'New_' + choice + choice_idx" :key="'New_' + choice + choice_idx"
v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)" v-for="(choice, choice_idx) in getSelectFieldOptions(list.name)"
> >
<span :style="choice[1] ? choice[1].style || {} : {}"> <span :style="choice[1] ? choice[1].style || {} : {}">
<ops-icon <ops-icon
:style="{ color: choice[1].icon.color }" :style="{ color: choice[1].icon.color }"
v-if="choice[1] && choice[1].icon && choice[1].icon.name" v-if="choice[1] && choice[1].icon && choice[1].icon.name"
:type="choice[1].icon.name" :type="choice[1].icon.name"
/> />
{{ choice[0] }} {{ choice[0] }}
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input-number <a-input-number
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
style="width: 100%" style="width: 100%"
v-if="getFieldType(list.name) === 'input_number'" v-if="getFieldType(list.name) === 'input_number'"
/> />
<a-date-picker <a-date-picker
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
style="width: 100%" style="width: 100%"
:format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="getFieldType(list.name) == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'" v-if="getFieldType(list.name) === 'date' || getFieldType(list.name) === 'datetime'"
:showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }" :showTime="getFieldType(list.name) === 'date' ? false : { format: 'HH:mm:ss' }"
/> />
<a-input <a-input
v-if="getFieldType(list.name) === 'input'" v-if="getFieldType(list.name) === 'input'"
@focus="(e) => handleFocusInput(e, list)" @focus="(e) => handleFocusInput(e, list)"
v-decorator="[list.name, { rules: [{ required: false }] }]" v-decorator="[list.name, { rules: [{ required: false }] }]"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="2"> <a-col :span="2">
<a-form-item> <a-form-item>
<a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)"> <a :style="{ color: 'red', marginTop: '2px' }" @click="handleDelete(list.name)">
<a-icon type="delete" /> <a-icon type="delete" />
</a> </a>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-button type="primary" ghost icon="plus" @click="handleAdd">新增修改字段</a-button> <a-button type="primary" ghost icon="plus" @click="handleAdd">{{ $t('cmdb.ci.newUpdateField') }}</a-button>
</a-form> </a-form>
</template> </template>
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import { Select, Option } from 'element-ui' import { Select, Option } from 'element-ui'
import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType' import { getCIType, getCITypeGroupById } from '@/modules/cmdb/api/CIType'
import { addCI } from '@/modules/cmdb/api/ci' import { addCI } from '@/modules/cmdb/api/ci'
import JsonEditor from '../../../components/JsonEditor/jsonEditor.vue' 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'
export default { export default {
name: 'CreateInstanceForm', name: 'CreateInstanceForm',
components: { components: {
ElSelect: Select, ElSelect: Select,
ElOption: Option, ElOption: Option,
JsonEditor, JsonEditor,
CreateInstanceFormByGroup, CreateInstanceFormByGroup,
}, },
props: { props: {
typeIdFromRelation: { typeIdFromRelation: {
type: Number, type: Number,
default: 0, default: 0,
}, },
}, },
data() { data() {
return { return {
valueTypeMap, action: '',
action: '', form: this.$form.createForm(this),
form: this.$form.createForm(this), visible: false,
visible: false, attributeList: [],
attributeList: [],
CIType: {},
CIType: {},
batchUpdateLists: [],
batchUpdateLists: [], editAttr: null,
editAttr: null, attributesByGroup: [],
attributesByGroup: [], parentsType: [],
parentsType: [], parentsForm: {},
parentsForm: {}, canEdit: {},
canEdit: {}, }
} },
}, computed: {
computed: { title() {
title() { return this.action === 'create' ? this.$t('create') + ' ' : this.$t('cmdb.ci.batchUpdate') + ' '
return this.action === 'create' ? '创建 ' : '批量修改 ' },
}, typeId() {
typeId() { if (this.typeIdFromRelation) {
if (this.typeIdFromRelation) { return this.typeIdFromRelation
return this.typeIdFromRelation }
} return this.$router.currentRoute.meta.typeId
return this.$router.currentRoute.meta.typeId },
}, valueTypeMap() {
}, return valueTypeMap()
provide() { },
return { },
getFieldType: this.getFieldType, provide() {
} return {
}, getFieldType: this.getFieldType,
inject: ['attrList'], }
methods: { },
moment, inject: ['attrList'],
async getCIType() { methods: {
await getCIType(this.typeId).then((res) => { moment,
this.CIType = res.ci_types[0] async getCIType() {
}) await getCIType(this.typeId).then((res) => {
}, this.CIType = res.ci_types[0]
async getAttributeList() { })
const _attrList = this.attrList() },
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required) async getAttributeList() {
await getCITypeGroupById(this.typeId).then((res1) => { const _attrList = this.attrList()
const _attributesByGroup = res1.map((g) => { this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
g.attributes = g.attributes.filter((attr) => !attr.is_computed) await getCITypeGroupById(this.typeId).then((res1) => {
return g const _attributesByGroup = res1.map((g) => {
}) g.attributes = g.attributes.filter((attr) => !attr.is_computed)
const attrHasGroupIds = [] return g
res1.forEach((g) => { })
const id = g.attributes.map((attr) => attr.id) const attrHasGroupIds = []
attrHasGroupIds.push(...id) res1.forEach((g) => {
}) const id = g.attributes.map((attr) => attr.id)
const otherGroupAttr = this.attributeList.filter( attrHasGroupIds.push(...id)
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed })
) const otherGroupAttr = this.attributeList.filter(
if (otherGroupAttr.length) { (attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
_attributesByGroup.push({ id: -1, name: '其他', attributes: otherGroupAttr }) )
} if (otherGroupAttr.length) {
this.attributesByGroup = _attributesByGroup _attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
}) }
}, this.attributesByGroup = _attributesByGroup
createInstance() { })
const _this = this },
if (_this.action === 'update') { createInstance() {
this.form.validateFields((err, values) => { const _this = this
if (err) { if (_this.action === 'update') {
return this.form.validateFields((err, values) => {
} if (err) {
Object.keys(values).forEach((k) => { return
const _tempFind = this.attributeList.find((item) => item.name === k) }
if ( Object.keys(values).forEach((k) => {
_tempFind.value_type === '3' && const _tempFind = this.attributeList.find((item) => item.name === k)
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '3' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if ( values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
_tempFind.value_type === '4' && }
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '4' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if (_tempFind.value_type === '6') { values[k] = values[k].format('YYYY-MM-DD')
values[k] = values[k] ? JSON.parse(values[k]) : undefined }
} if (_tempFind.value_type === '6') {
}) values[k] = values[k] ? JSON.parse(values[k]) : undefined
}
_this.$emit('submit', values) })
})
} else { _this.$emit('submit', values)
let values = {} })
for (let i = 0; i < this.attributesByGroup.length; i++) { } else {
const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData() let values = {}
if (data === 'error') { for (let i = 0; i < this.attributesByGroup.length; i++) {
return const data = this.$refs[`createInstanceFormByGroup_${this.attributesByGroup[i].id}`][0].getData()
} if (data === 'error') {
values = { ...values, ...data } return
} }
values = { ...values, ...data }
Object.keys(values).forEach((k) => { }
const _tempFind = this.attributeList.find((item) => item.name === k)
if ( Object.keys(values).forEach((k) => {
_tempFind.value_type === '3' && const _tempFind = this.attributeList.find((item) => item.name === k)
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '3' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD HH:mm:ss') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if ( values[k] = values[k].format('YYYY-MM-DD HH:mm:ss')
_tempFind.value_type === '4' && }
values[k] && if (
Object.prototype.toString.call(values[k]) === '[object Object]' _tempFind.value_type === '4' &&
) { values[k] &&
values[k] = values[k].format('YYYY-MM-DD') Object.prototype.toString.call(values[k]) === '[object Object]'
} ) {
if (_tempFind.value_type === '6') { values[k] = values[k].format('YYYY-MM-DD')
values[k] = values[k] ? JSON.parse(values[k]) : undefined }
} if (_tempFind.value_type === '6') {
}) values[k] = values[k] ? JSON.parse(values[k]) : undefined
values.ci_type = _this.typeId }
console.log(this.parentsForm) })
Object.keys(this.parentsForm).forEach((type) => { values.ci_type = _this.typeId
if (this.parentsForm[type].value) { console.log(this.parentsForm)
values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value Object.keys(this.parentsForm).forEach((type) => {
} if (this.parentsForm[type].value) {
}) values[`$${type}.${this.parentsForm[type].attr}`] = this.parentsForm[type].value
addCI(values).then((res) => { }
_this.$message.success('新增成功!') })
_this.visible = false addCI(values).then((res) => {
_this.$emit('reload', { ci_id: res.ci_id }) _this.$message.success(this.$t('addSuccess'))
}) _this.visible = false
} _this.$emit('reload', { ci_id: res.ci_id })
}, })
handleClose() { }
this.visible = false },
}, handleClose() {
handleOpen(visible, action) { this.visible = false
this.visible = visible },
this.action = action handleOpen(visible, action) {
this.$nextTick(() => { this.visible = visible
this.form.resetFields() this.action = action
Promise.all([this.getCIType(), this.getAttributeList()]).then(() => { this.$nextTick(() => {
this.batchUpdateLists = [{ name: this.attributeList[0].name }] this.form.resetFields()
}) Promise.all([this.getCIType(), this.getAttributeList()]).then(() => {
if (action === 'create') { this.batchUpdateLists = [{ name: this.attributeList[0].name }]
getCITypeParent(this.typeId).then(async (res) => { })
for (let i = 0; i < res.parents.length; i++) { if (action === 'create') {
await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => { getCITypeParent(this.typeId).then(async (res) => {
this.canEdit = { for (let i = 0; i < res.parents.length; i++) {
..._.cloneDeep(this.canEdit), await getCanEditByParentIdChildId(res.parents[i].id, this.typeId).then((p_res) => {
[res.parents[i].id]: p_res.result, this.canEdit = {
} ..._.cloneDeep(this.canEdit),
}) [res.parents[i].id]: p_res.result,
} }
this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id]) })
const _parentsForm = {} }
res.parents.forEach((item) => { this.parentsType = res.parents.filter((parent) => this.canEdit[parent.id])
const _find = item.attributes.find((attr) => attr.id === item.unique_id) const _parentsForm = {}
_parentsForm[item.name] = { attr: _find.name, value: '' } res.parents.forEach((item) => {
}) const _find = item.attributes.find((attr) => attr.id === item.unique_id)
this.parentsForm = _parentsForm _parentsForm[item.name] = { attr: _find.name, value: '' }
}) })
} this.parentsForm = _parentsForm
}) })
}, }
getFieldType(name) { })
const _find = this.attributeList.find((item) => item.name === name) },
if (_find) { getFieldType(name) {
if (_find.is_choice) { const _find = this.attributeList.find((item) => item.name === name)
if (_find.is_list) { if (_find) {
return 'select%%multiple' if (_find.is_choice) {
} if (_find.is_list) {
return 'select' return 'select%%multiple'
} else if (_find.value_type === '0' || _find.value_type === '1') { }
return 'input_number' return 'select'
} else if (_find.value_type === '4' || _find.value_type === '3') { } else if (_find.value_type === '0' || _find.value_type === '1') {
return valueTypeMap[_find.value_type] return 'input_number'
} else { } else if (_find.value_type === '4' || _find.value_type === '3') {
return 'input' return this.valueTypeMap[_find.value_type]
} } else {
} return 'input'
return 'input' }
}, }
getSelectFieldOptions(name) { return 'input'
const _find = this.attributeList.find((item) => item.name === name) },
if (_find) { getSelectFieldOptions(name) {
return _find.choice_value const _find = this.attributeList.find((item) => item.name === name)
} if (_find) {
return [] return _find.choice_value
}, }
handleAdd() { return []
this.batchUpdateLists.push({ name: undefined }) },
}, handleAdd() {
handleDelete(name) { this.batchUpdateLists.push({ name: undefined })
const _idx = this.batchUpdateLists.findIndex((item) => item.name === name) },
if (_idx > -1) { handleDelete(name) {
this.batchUpdateLists.splice(_idx, 1) const _idx = this.batchUpdateLists.findIndex((item) => item.name === name)
} if (_idx > -1) {
}, this.batchUpdateLists.splice(_idx, 1)
handleFocusInput(e, attr) { }
console.log(attr) },
const _tempFind = this.attributeList.find((item) => item.name === attr.name) handleFocusInput(e, attr) {
if (_tempFind.value_type === '6') { console.log(attr)
this.editAttr = attr const _tempFind = this.attributeList.find((item) => item.name === attr.name)
e.srcElement.blur() if (_tempFind.value_type === '6') {
const jsonData = this.form.getFieldValue(attr.name) this.editAttr = attr
this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {}) e.srcElement.blur()
} else { const jsonData = this.form.getFieldValue(attr.name)
this.editAttr = null this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
} } else {
}, this.editAttr = null
jsonEditorOk(jsonData) { }
this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) }) },
}, jsonEditorOk(jsonData) {
}, this.form.setFieldsValue({ [this.editAttr.name]: JSON.stringify(jsonData) })
} },
</script> },
<style lang="less"> }
.create-instance-form { </script>
.ant-form-item { <style lang="less">
margin-bottom: 5px; .create-instance-form {
} .ant-form-item {
.ant-drawer-body { margin-bottom: 5px;
overflow-y: auto; }
max-height: calc(100vh - 110px); .ant-drawer-body {
} overflow-y: auto;
} max-height: calc(100vh - 110px);
</style> }
}
</style>

View File

@ -1,225 +1,228 @@
<template> <template>
<CustomDrawer <CustomDrawer
:visible="visible" :visible="visible"
:hasFooter="false" :hasFooter="false"
@close=" @close="
() => { () => {
visible = false visible = false
} }
" "
title="属性说明" :title="$t('cmdb.ci.attributeDesc')"
width="72%" width="72%"
:bodyStyle="{ height: '100vh' }" :bodyStyle="{ height: '100vh' }"
> >
<vxe-toolbar> <vxe-toolbar>
<template #buttons> <template #buttons>
<a-input <a-input
v-model="searchKey" v-model="searchKey"
:style="{ display: 'inline-block', width: '244px' }" :style="{ display: 'inline-block', width: '244px' }"
class="ops-input ops-input-radius" class="ops-input ops-input-radius"
type="search" type="search"
placeholder="搜索 名称 | 别名" :placeholder="$t('cmdb.ci.tips5')"
@keyup="searchAttributes" @keyup="searchAttributes"
> >
<a-icon type="search" slot="suffix" /> <a-icon type="search" slot="suffix" />
</a-input> </a-input>
</template> </template>
</vxe-toolbar> </vxe-toolbar>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<vxe-table <vxe-table
resizable resizable
border border
size="mini" size="mini"
:height="windowHeight - 160" :height="windowHeight - 160"
:data="list" :data="list"
:scroll-x="{ enabled: true, gt: 0 }" :scroll-x="{ enabled: true, gt: 0 }"
show-overflow show-overflow
show-header-overflow show-header-overflow
align="center" align="center"
highlight-hover-row highlight-hover-row
class="ops-stripe-table" class="ops-stripe-table"
> >
<vxe-column <vxe-column
v-for="(column, index) in columns" v-for="(column, index) in columns"
:field="column.field" :field="column.field"
:title="column.title" :title="column.title"
:min-width="column.width" :min-width="column.width"
:align="column.align" :align="column.align"
:key="column.field" :key="column.field"
:fixed="index < 3 ? 'left' : ''" :fixed="index < 3 ? 'left' : ''"
:sortable="index < 3 ? true : false" :sortable="index < 3 ? true : false"
:title-help="column.help !== null ? { message: column.help } : null" :title-help="column.help !== null ? { message: column.help } : null"
:filters=" :filters="
index < 2 index < 2
? null ? null
: index === 2 : index === 2
? valueTypeFilters ? valueTypeFilters
: [ : [
{ label: '', value: true }, { label: $t('yes'), value: true },
{ label: '', value: false }, { label: $t('no'), value: false },
] ]
" "
type="html" type="html"
> >
<template #default="{ row }"> <template #default="{ row }">
<span v-if="column.field !== 'name' && column.field !== 'alias' && column.field !== 'value_type'"> <span v-if="column.field !== 'name' && column.field !== 'alias' && column.field !== 'value_type'">
<a-icon :style="{ color: '#1fb51f' }" type="check" v-if="row[column.field]" /> <a-icon :style="{ color: '#1fb51f' }" type="check" v-if="row[column.field]" />
</span> </span>
<span v-else-if="column.field === 'value_type'" v-html="valueTypeMap[row.value_type]"> </span> <span v-else-if="column.field === 'value_type'" v-html="valueTypeMap[row.value_type]"> </span>
<span v-else v-html="row[column.field]"> </span> <span v-else v-html="row[column.field]"> </span>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
</a-spin> </a-spin>
</CustomDrawer> </CustomDrawer>
</template> </template>
<script> <script>
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'
export default { export default {
name: 'MetadataDrawer', name: 'MetadataDrawer',
data() { data() {
const columns = [ return {
{ visible: false,
field: 'name', list: [],
title: '名称', tableData: [],
width: 150, loading: false,
align: 'left', valueTypeFilters: [],
help: null, searchKey: '',
}, }
{ },
field: 'alias', computed: {
title: '别名', windowHeight() {
width: 150, return this.$store.state.windowHeight
align: 'left', },
help: null, valueTypeMap() {
}, return valueTypeMap()
{ },
field: 'value_type', columns() {
title: '类型', return [
width: 100, {
align: 'left', field: 'name',
help: null, title: this.$t('name'),
}, width: 150,
{ align: 'left',
field: 'is_index', help: null,
title: '是否索引', },
width: 110, {
help: '加快检索, 可以全文搜索, 无需使用条件过滤\n\n json目前不支持建索引 \n\n文本字符长度超过190不能建索引', field: 'alias',
}, title: this.$t('alias'),
{ width: 150,
field: 'default_show', align: 'left',
title: '默认显示', help: null,
width: 110, },
help: '订阅CI默认显示在table里的属性', {
}, field: 'value_type',
{ title: this.$t('type'),
field: 'is_unique', width: 100,
title: '是否唯一', align: 'left',
width: 110, help: null,
help: null, },
}, {
{ field: 'is_index',
field: 'is_choice', title: this.$t('cmdb.ciType.isIndex'),
title: '是否选择', width: 110,
width: 110, help: this.$t('cmdb.ci.tips6'),
help: '表现形式是下拉框, 值必须在预定义值里', },
}, {
{ field: 'default_show',
field: 'is_list', title: this.$t('cmdb.ciType.defaultShow'),
title: '是否列表', width: 110,
width: 110, help: this.$t('cmdb.ciType.defaultShowTips'),
help: '多值, 比如内网IP', },
}, {
{ field: 'is_unique',
field: 'is_sortable', title: this.$t('cmdb.ciType.isUnique'),
title: '可排序', width: 110,
width: 100, help: null,
help: '仅针对前端', },
}, {
{ field: 'is_choice',
field: 'is_computed', title: this.$t('cmdb.ciType.isChoice'),
title: '计算属性', width: 110,
width: 110, help: this.$t('cmdb.ci.tips7'),
help: '模型的其他属性通过表达式的方式计算出来\n\n一个代码片段计算返回的值', },
}, {
] field: 'is_list',
return { title: this.$t('cmdb.ciType.list'),
columns, width: 110,
visible: false, help: this.$t('cmdb.ci.tips8'),
list: [], },
tableData: [], {
loading: false, field: 'is_sortable',
valueTypeMap, title: this.$t('cmdb.ciType.isSortable'),
valueTypeFilters: [], width: 100,
searchKey: '', help: this.$t('cmdb.ci.tips9'),
} },
}, {
computed: { field: 'is_computed',
windowHeight() { title: this.$t('cmdb.ciType.computedAttribute'),
return this.$store.state.windowHeight width: 110,
}, help: this.$t('cmdb.ci.tips10'),
}, },
created: function() { ]
this.valueTypeFilters = Object.keys(this.valueTypeMap).map((key) => { },
return { label: this.valueTypeMap[key], value: key } },
}) created: function() {
}, this.valueTypeFilters = Object.keys(this.valueTypeMap).map((key) => {
methods: { return { label: this.valueTypeMap[key], value: key }
open(typeId) { })
this.visible = true },
this.typeId = typeId methods: {
this.getAttrs() open(typeId) {
}, this.visible = true
async getAttrs() { this.typeId = typeId
this.loading = true this.getAttrs()
const { attributes = [] } = await getCITypeAttributesByName(this.typeId) },
this.tableData = attributes.map((attr) => { async getAttrs() {
if (attr.is_password) { this.loading = true
attr.value_type = '7' const { attributes = [] } = await getCITypeAttributesByName(this.typeId)
} this.tableData = attributes.map((attr) => {
if (attr.is_link) { if (attr.is_password) {
attr.value_type = '8' attr.value_type = '7'
} }
return attr if (attr.is_link) {
}) attr.value_type = '8'
this.loading = false }
this.searchAttributes() return attr
}, })
searchAttributes() { this.loading = false
const filterName = XEUtils.toValueString(this.searchKey) this.searchAttributes()
.trim() },
.toLowerCase() searchAttributes() {
if (filterName) { const filterName = XEUtils.toValueString(this.searchKey)
const filterRE = new RegExp(filterName, 'gi') .trim()
const searchProps = ['name', 'alias', 'value_type'] .toLowerCase()
const rest = this.tableData.filter((item) => if (filterName) {
searchProps.some( const filterRE = new RegExp(filterName, 'gi')
(key) => const searchProps = ['name', 'alias', 'value_type']
XEUtils.toValueString(item[key]) const rest = this.tableData.filter((item) =>
.toLowerCase() searchProps.some(
.indexOf(filterName) > -1 (key) =>
) XEUtils.toValueString(item[key])
) .toLowerCase()
this.list = rest.map((row) => { .indexOf(filterName) > -1
const item = Object.assign({}, row) )
searchProps.forEach((key) => { )
item[key] = XEUtils.toValueString(item[key]).replace( this.list = rest.map((row) => {
filterRE, const item = Object.assign({}, row)
(match) => `<span style='background: yellow'>${match}</span>` searchProps.forEach((key) => {
) item[key] = XEUtils.toValueString(item[key]).replace(
}) filterRE,
return item (match) => `<span style='background: yellow'>${match}</span>`
}) )
} else { })
this.list = this.tableData return item
} })
}, } else {
}, this.list = this.tableData
} }
</script> },
},
<style></style> }
</script>
<style></style>

View File

@ -1,318 +1,318 @@
<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'">
<PasswordField <PasswordField
:style="{ display: 'inline-block' }" :style="{ display: 'inline-block' }"
v-if="attr.is_password && ci[attr.name]" v-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>
<template v-else-if="attr.value_type === '6'">{{ JSON.stringify(ci[attr.name] || {}) }}</template> <template v-else-if="attr.value_type === '6'">{{ JSON.stringify(ci[attr.name] || {}) }}</template>
<template v-else-if="attr.is_choice"> <template v-else-if="attr.is_choice">
<template v-if="attr.is_list"> <template v-if="attr.is_list">
<span <span
v-for="value in ci[attr.name]" v-for="value in ci[attr.name]"
:key="value" :key="value"
:style="{ :style="{
borderRadius: '4px', borderRadius: '4px',
padding: '1px 5px', padding: '1px 5px',
margin: '2px', margin: '2px',
...getChoiceValueStyle(attr, value), ...getChoiceValueStyle(attr, value),
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
}" }"
> >
<img <img
v-if="getChoiceValueIcon(attr, value).id && getChoiceValueIcon(attr, value).url" v-if="getChoiceValueIcon(attr, value).id && getChoiceValueIcon(attr, value).url"
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, value).url}`" :src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, value).url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: getChoiceValueIcon(attr, value).color, marginRight: '5px' }" :style="{ color: getChoiceValueIcon(attr, value).color, marginRight: '5px' }"
:type="getChoiceValueIcon(attr, value).name" :type="getChoiceValueIcon(attr, value).name"
/> />
{{ value }}</span {{ value }}</span
> >
</template> </template>
<span <span
v-else v-else
:style="{ :style="{
borderRadius: '4px', borderRadius: '4px',
padding: '1px 5px', padding: '1px 5px',
margin: '2px 0', margin: '2px 0',
...getChoiceValueStyle(attr, ci[attr.name]), ...getChoiceValueStyle(attr, ci[attr.name]),
display: 'inline-flex', display: 'inline-flex',
alignItems: 'center', alignItems: 'center',
}" }"
> >
<img <img
v-if="getChoiceValueIcon(attr, ci[attr.name]).id && getChoiceValueIcon(attr, ci[attr.name]).url" v-if="getChoiceValueIcon(attr, ci[attr.name]).id && getChoiceValueIcon(attr, ci[attr.name]).url"
:src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, ci[attr.name]).url}`" :src="`/api/common-setting/v1/file/${getChoiceValueIcon(attr, ci[attr.name]).url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: getChoiceValueIcon(attr, ci[attr.name]).color, marginRight: '5px' }" :style="{ color: getChoiceValueIcon(attr, ci[attr.name]).color, marginRight: '5px' }"
:type="getChoiceValueIcon(attr, ci[attr.name]).name" :type="getChoiceValueIcon(attr, ci[attr.name]).name"
/> />
{{ ci[attr.name] }} {{ ci[attr.name] }}
</span> </span>
</template> </template>
<template v-else-if="attr.is_list"> <template v-else-if="attr.is_list">
<span> {{ ci[attr.name].join(',') }}</span> <span> {{ ci[attr.name].join(',') }}</span>
</template> </template>
<template v-else>{{ getName(ci[attr.name]) }}</template> <template v-else>{{ getName(ci[attr.name]) }}</template>
</span> </span>
<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">
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
allowClear allowClear
size="small" size="small"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
> >
<a-select-option <a-select-option
:value="choice[0]" :value="choice[0]"
:key="'New_' + attr.name + choice_idx" :key="'New_' + attr.name + choice_idx"
v-for="(choice, choice_idx) in attr.choice_value" v-for="(choice, choice_idx) in attr.choice_value"
> >
<span :style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }"> <span :style="{ ...(choice[1] ? choice[1].style : {}), display: 'inline-flex', alignItems: 'center' }">
<template v-if="choice[1] && choice[1].icon && choice[1].icon.name"> <template v-if="choice[1] && choice[1].icon && choice[1].icon.name">
<img <img
v-if="choice[1].icon.id && choice[1].icon.url" v-if="choice[1].icon.id && choice[1].icon.url"
:src="`/api/common-setting/v1/file/${choice[1].icon.url}`" :src="`/api/common-setting/v1/file/${choice[1].icon.url}`"
:style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }" :style="{ maxHeight: '13px', maxWidth: '13px', marginRight: '5px' }"
/> />
<ops-icon <ops-icon
v-else v-else
:style="{ color: choice[1].icon.color, marginRight: '5px' }" :style="{ color: choice[1].icon.color, marginRight: '5px' }"
:type="choice[1].icon.name" :type="choice[1].icon.name"
/> />
</template> </template>
{{ choice[0] }} {{ choice[0] }}
</span> </span>
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-select <a-select
:style="{ width: '100%' }" :style="{ width: '100%' }"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-else-if="attr.is_list" v-else-if="attr.is_list"
mode="tags" mode="tags"
showSearch showSearch
allowClear allowClear
size="small" size="small"
:getPopupContainer="(trigger) => trigger.parentElement" :getPopupContainer="(trigger) => trigger.parentElement"
> >
</a-select> </a-select>
<a-input-number <a-input-number
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="attr.value_type === '0' || attr.value_type === '1'" v-else-if="attr.value_type === '0' || attr.value_type === '1'"
/> />
<a-date-picker <a-date-picker
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
:format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :format="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'" :valueFormat="attr.value_type === '4' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
v-else-if="attr.value_type === '4' || attr.value_type === '3'" v-else-if="attr.value_type === '4' || attr.value_type === '3'"
:showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }" :showTime="attr.value_type === '4' ? false : { format: 'HH:mm:ss' }"
/> />
<!-- <a-input <!-- <a-input
size="small" size="small"
@focus="(e) => handleFocusInput(e, attr)" @focus="(e) => handleFocusInput(e, attr)"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else-if="attr.value_type === '6'" v-else-if="attr.value_type === '6'"
/> --> /> -->
<a-input <a-input
size="small" size="small"
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required }], rules: [{ required: attr.is_required }],
}, },
]" ]"
style="width: 100%" style="width: 100%"
v-else v-else
/> />
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
<a v-if="!isEdit && !attr.is_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a> <a v-if="!isEdit && !attr.is_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a>
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" /> <JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
</span> </span>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import moment from 'moment' import moment from 'moment'
import { updateCI } from '@/modules/cmdb/api/ci' 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'
export default { export default {
name: 'CiDetailAttrContent', name: 'CiDetailAttrContent',
components: { JsonEditor, PasswordField }, components: { JsonEditor, PasswordField },
props: { props: {
ci: { ci: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
attr: { attr: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
}, },
data() { data() {
return { return {
isEdit: false, isEdit: false,
form: this.$form.createForm(this, this.attr.name), form: this.$form.createForm(this, this.attr.name),
} }
}, },
mounted() { mounted() {
document.addEventListener('click', this.eventListener) document.addEventListener('click', this.eventListener)
}, },
beforeDestroy() { beforeDestroy() {
document.removeEventListener('click', this.eventListener) document.removeEventListener('click', this.eventListener)
}, },
methods: { methods: {
moment, moment,
eventListener(e) { eventListener(e) {
const datePickerContainer = document.getElementsByClassName('ant-calendar-picker-container') const datePickerContainer = document.getElementsByClassName('ant-calendar-picker-container')
if (this.isEdit && !datePickerContainer.length) { if (this.isEdit && !datePickerContainer.length) {
const dom = document.getElementById(`ci-detail-attr-${this.attr.name}`) const dom = document.getElementById(`ci-detail-attr-${this.attr.name}`)
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (dom) { if (dom) {
const isSelf = dom.contains(e.target) const isSelf = dom.contains(e.target)
if (!isSelf) { if (!isSelf) {
this.handleCloseEdit() this.handleCloseEdit()
} }
} }
} }
}, },
handleEdit(e) { handleEdit(e) {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
if (this.attr.value_type === '6') { if (this.attr.value_type === '6') {
const jsonData = this.ci[this.attr.name] const jsonData = this.ci[this.attr.name]
this.$refs.jsonEditor.open(null, null, jsonData || {}) this.$refs.jsonEditor.open(null, null, jsonData || {})
return return
} }
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.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] || null, [`${this.attr.name}`]: this.ci[this.attr.name] || null,
}) })
return return
} }
if (this.attr.is_password) { if (this.attr.is_password) {
await getAttrPassword(this.ci._id, this.attr.id).then((res) => { await getAttrPassword(this.ci._id, this.attr.id).then((res) => {
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: res.value ?? null, [`${this.attr.name}`]: res.value ?? null,
}) })
}) })
return return
} }
this.form.setFieldsValue({ this.form.setFieldsValue({
[`${this.attr.name}`]: this.ci[this.attr.name] ?? null, [`${this.attr.name}`]: this.ci[this.attr.name] ?? null,
}) })
}) })
}, },
async handleCloseEdit() { async handleCloseEdit() {
const newData = this.form.getFieldValue(this.attr.name) const newData = this.form.getFieldValue(this.attr.name)
if (!_.isEqual(this.ci[this.attr.name], newData)) { if (!_.isEqual(this.ci[this.attr.name], newData)) {
await updateCI(this.ci._id, { [`${this.attr.name}`]: newData }) await updateCI(this.ci._id, { [`${this.attr.name}`]: newData })
.then(() => { .then(() => {
this.$message.success('更新成功!') 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)
}) })
.catch(() => { .catch(() => {
this.$emit('refresh', this.attr.name) this.$emit('refresh', this.attr.name)
}) })
} }
this.isEdit = false this.isEdit = false
}, },
// handleFocusInput(e, attr) { // handleFocusInput(e, attr) {
// console.log('focus') // console.log('focus')
// if (this.attr.value_type === '6') { // if (this.attr.value_type === '6') {
// e.preventDefault() // e.preventDefault()
// e.stopPropagation() // e.stopPropagation()
// // e.srcElement.blur() // // e.srcElement.blur()
// const jsonData = this.form.getFieldValue(attr.name) // const jsonData = this.form.getFieldValue(attr.name)
// this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {}) // this.$refs.jsonEditor.open(null, null, jsonData ? JSON.parse(jsonData) : {})
// } // }
// }, // },
jsonEditorOk(jsonData) { jsonEditorOk(jsonData) {
if (!_.isEqual(this.ci[this.attr.name], jsonData)) { if (!_.isEqual(this.ci[this.attr.name], jsonData)) {
updateCI(this.ci._id, { [`${this.attr.name}`]: jsonData }) updateCI(this.ci._id, { [`${this.attr.name}`]: jsonData })
.then(() => { .then(() => {
this.$message.success('更新成功!') this.$message.success(this.$t('updateSuccess'))
this.$emit('updateCIByself', { [`${this.attr.name}`]: jsonData }, this.attr.name) this.$emit('updateCIByself', { [`${this.attr.name}`]: jsonData }, this.attr.name)
}) })
.catch(() => { .catch(() => {
this.$emit('refresh', this.attr.name) this.$emit('refresh', this.attr.name)
}) })
} }
}, },
getChoiceValueStyle(col, colValue) { getChoiceValueStyle(col, colValue) {
const _find = col.choice_value.find((item) => String(item[0]) === String(colValue)) const _find = col.choice_value.find((item) => String(item[0]) === String(colValue))
if (_find) { if (_find) {
return _find[1]?.style || {} return _find[1]?.style || {}
} }
return {} return {}
}, },
getChoiceValueIcon(col, colValue) { getChoiceValueIcon(col, colValue) {
const _find = col.choice_value.find((item) => String(item[0]) === String(colValue)) const _find = col.choice_value.find((item) => String(item[0]) === String(colValue))
if (_find) { if (_find) {
return _find[1]?.icon || {} return _find[1]?.icon || {}
} }
return {} return {}
}, },
getName(name) { getName(name) {
return name ?? '' return name ?? ''
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -2,10 +2,10 @@
<div class="ci-detail-relation"> <div class="ci-detail-relation">
<a-radio-group v-model="activeKey" size="small" @change="handleChangeActiveKey"> <a-radio-group v-model="activeKey" size="small" @change="handleChangeActiveKey">
<a-radio-button value="1"> <a-radio-button value="1">
拓扑 {{ $t('cmdb.ci.topo') }}
</a-radio-button> </a-radio-button>
<a-radio-button value="2"> <a-radio-button value="2">
表格 {{ $t('cmdb.ci.table') }}
</a-radio-button> </a-radio-button>
</a-radio-group> </a-radio-group>
<CiDetailRelationTopo ref="ciDetailRelationTopo" v-if="activeKey === '1'" /> <CiDetailRelationTopo ref="ciDetailRelationTopo" v-if="activeKey === '1'" />
@ -24,7 +24,7 @@
><a-icon ><a-icon
type="plus-square" type="plus-square"
/></a> /></a>
<span v-if="!canEdit[parent.id]">当前模型关系为多对多请前往关系视图进行增删操作</span> <span v-if="!canEdit[parent.id]">{{ $t('cmdb.ci.m2mTips') }}</span>
</div> </div>
<vxe-grid <vxe-grid
v-if="firstCIs[parent.name]" v-if="firstCIs[parent.name]"
@ -39,7 +39,7 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(row._id, ciId)"> <a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(row._id, ciId)">
<a <a
:disabled="!canEdit[parent.id]" :disabled="!canEdit[parent.id]"
:style="{ :style="{
@ -68,7 +68,7 @@
><a-icon ><a-icon
type="plus-square" type="plus-square"
/></a> /></a>
<span v-if="!canEdit[child.id]">当前模型关系为多对多请前往关系视图进行增删操作</span> <span v-if="!canEdit[child.id]">{{ $t('cmdb.ci.m2mTips') }}</span>
</div> </div>
<vxe-grid <vxe-grid
v-if="secondCIs[child.name]" v-if="secondCIs[child.name]"
@ -82,7 +82,7 @@
class="ops-stripe-table" class="ops-stripe-table"
> >
<template #operation_default="{ row }"> <template #operation_default="{ row }">
<a-popconfirm arrowPointAtCenter title="确认删除关系?" @confirm="deleteRelation(ciId, row._id)"> <a-popconfirm arrowPointAtCenter :title="$t('cmdb.ci.confirmDeleteRelation')" @confirm="deleteRelation(ciId, row._id)">
<a <a
:disabled="!canEdit[child.id]" :disabled="!canEdit[child.id]"
:style="{ :style="{
@ -338,7 +338,7 @@ export default {
firstCIColumns[item.id].push({ firstCIColumns[item.id].push({
key: 'p_operation', key: 'p_operation',
field: 'operation', field: 'operation',
title: '操作', title: this.$t('operation'),
width: '60px', width: '60px',
fixed: 'right', fixed: 'right',
slots: { slots: {
@ -379,7 +379,7 @@ export default {
secondCIColumns[item.id].push({ secondCIColumns[item.id].push({
key: 'c_operation', key: 'c_operation',
field: 'operation', field: 'operation',
title: '操作', title: this.$t('operation'),
width: '60px', width: '60px',
fixed: 'right', fixed: 'right',
slots: { slots: {

View File

@ -1,168 +1,168 @@
<template> <template>
<div <div
id="ci-detail-relation-topo" id="ci-detail-relation-topo"
class="ci-detail-relation-topo" class="ci-detail-relation-topo"
:style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }" :style="{ width: '100%', marginTop: '20px', height: 'calc(100vh - 136px)' }"
></div> ></div>
</template> </template>
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import { TreeCanvas } from 'butterfly-dag' import { TreeCanvas } from 'butterfly-dag'
import { searchCIRelation } from '@/modules/cmdb/api/CIRelation' import { searchCIRelation } from '@/modules/cmdb/api/CIRelation'
import Node from './node.js' import Node from './node.js'
import 'butterfly-dag/dist/index.css' import 'butterfly-dag/dist/index.css'
import './index.less' import './index.less'
export default { export default {
name: 'CiDetailRelationTopo', name: 'CiDetailRelationTopo',
data() { data() {
return { return {
topoData: {}, topoData: {},
exsited_ci: [], exsited_ci: [],
} }
}, },
inject: ['ci_types'], inject: ['ci_types'],
mounted() {}, mounted() {},
methods: { methods: {
init() { init() {
const root = document.getElementById('ci-detail-relation-topo') const root = document.getElementById('ci-detail-relation-topo')
this.canvas = new TreeCanvas({ this.canvas = new TreeCanvas({
root: root, root: root,
disLinkable: false, // 可删除连线 disLinkable: false, // 可删除连线
linkable: false, // 可连线 linkable: false, // 可连线
draggable: true, // 可拖动 draggable: true, // 可拖动
zoomable: true, // 可放大 zoomable: true, // 可放大
moveable: true, // 可平移 moveable: true, // 可平移
theme: { theme: {
edge: { edge: {
shapeType: 'AdvancedBezier', shapeType: 'AdvancedBezier',
arrow: true, arrow: true,
arrowPosition: 1, arrowPosition: 1,
}, },
}, },
layout: { layout: {
type: 'mindmap', type: 'mindmap',
options: { options: {
direction: 'H', direction: 'H',
getSide(d) { getSide(d) {
return d.data.side || 'right' return d.data.side || 'right'
}, },
getHeight(d) { getHeight(d) {
return 10 return 10
}, },
getWidth(d) { getWidth(d) {
return 40 return 40
}, },
getHGap(d) { getHGap(d) {
return 80 return 80
}, },
getVGap(d) { getVGap(d) {
return 40 return 40
}, },
}, },
}, },
}) })
this.canvas.setZoomable(true, true) this.canvas.setZoomable(true, true)
this.canvas.on('events', ({ type, data }) => { this.canvas.on('events', ({ type, data }) => {
const sourceNode = data?.id || null const sourceNode = data?.id || null
if (type === 'custom:clickLeft') { if (type === 'custom:clickLeft') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => { searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=1&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'left') this.redrawData(res, sourceNode, 'left')
}) })
} }
if (type === 'custom:clickRight') { if (type === 'custom:clickRight') {
searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => { searchCIRelation(`root_id=${Number(sourceNode)}&&level=1&&reverse=0&&count=10000`).then((res) => {
this.redrawData(res, sourceNode, 'right') this.redrawData(res, sourceNode, 'right')
}) })
} }
}) })
}, },
setTopoData(data) { setTopoData(data) {
this.canvas = null this.canvas = null
this.init() this.init()
this.topoData = _.cloneDeep(data) this.topoData = _.cloneDeep(data)
this.canvas.draw(data, {}, () => { this.canvas.draw(data, {}, () => {
this.canvas.focusCenterWithAnimate() this.canvas.focusCenterWithAnimate()
}) })
}, },
redrawData(res, sourceNode, side) { redrawData(res, sourceNode, side) {
const newNodes = [] const newNodes = []
const newEdges = [] const newEdges = []
if (!res.result.length) { if (!res.result.length) {
this.$message.info('无层级关系!') this.$message.info(this.$t('cmdb.ci.noLevel'))
return return
} }
const ci_types_list = this.ci_types() const ci_types_list = this.ci_types()
res.result.forEach((r) => { res.result.forEach((r) => {
if (!this.exsited_ci.includes(r._id)) { if (!this.exsited_ci.includes(r._id)) {
const _findCiType = ci_types_list.find((item) => item.id === r._type) const _findCiType = ci_types_list.find((item) => item.id === r._type)
newNodes.push({ newNodes.push({
id: `${r._id}`, id: `${r._id}`,
Class: Node, Class: Node,
title: r.ci_type_alias || r.ci_type, title: r.ci_type_alias || r.ci_type,
name: r.ci_type, name: r.ci_type,
side: side, side: side,
unique_alias: r.unique_alias, unique_alias: r.unique_alias,
unique_name: r.unique, unique_name: r.unique,
unique_value: r[r.unique], unique_value: r[r.unique],
children: [], children: [],
icon: _findCiType?.icon || '', icon: _findCiType?.icon || '',
endpoints: [ endpoints: [
{ {
id: 'left', id: 'left',
orientation: [-1, 0], orientation: [-1, 0],
pos: [0, 0.5], pos: [0, 0.5],
}, },
{ {
id: 'right', id: 'right',
orientation: [1, 0], orientation: [1, 0],
pos: [0, 0.5], pos: [0, 0.5],
}, },
], ],
}) })
} }
newEdges.push({ newEdges.push({
id: `${r._id}`, id: `${r._id}`,
source: 'right', source: 'right',
target: 'left', target: 'left',
sourceNode: side === 'right' ? sourceNode : `${r._id}`, sourceNode: side === 'right' ? sourceNode : `${r._id}`,
targetNode: side === 'right' ? `${r._id}` : sourceNode, targetNode: side === 'right' ? `${r._id}` : sourceNode,
type: 'endpoint', type: 'endpoint',
}) })
}) })
const { nodes, edges } = this.canvas.getDataMap() const { nodes, edges } = this.canvas.getDataMap()
// 删除原节点和边 // 删除原节点和边
this.canvas.removeNodes(nodes.map((node) => node.id)) this.canvas.removeNodes(nodes.map((node) => node.id))
this.canvas.removeEdges(edges) this.canvas.removeEdges(edges)
const _topoData = _.cloneDeep(this.topoData) const _topoData = _.cloneDeep(this.topoData)
_topoData.edges.push(...newEdges) _topoData.edges.push(...newEdges)
let result let result
const getTreeItem = (data, id) => { const getTreeItem = (data, id) => {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
if (data[i].id === id) { if (data[i].id === id) {
result = data[i] // 结果赋值 result = data[i] // 结果赋值
result.edges = _topoData.edges result.edges = _topoData.edges
break break
} else { } else {
if (data[i].children && data[i].children.length) { if (data[i].children && data[i].children.length) {
getTreeItem(data[i].children, id) getTreeItem(data[i].children, id)
} }
} }
} }
} }
getTreeItem(_topoData.nodes.children, sourceNode) getTreeItem(_topoData.nodes.children, sourceNode)
result.children.push(...newNodes) result.children.push(...newNodes)
this.topoData = _topoData this.topoData = _topoData
this.canvas.draw(_topoData, {}, () => {}) this.canvas.draw(_topoData, {}, () => {})
this.exsited_ci = [...new Set([...this.exsited_ci, ...res.result.map((r) => r._id)])] this.exsited_ci = [...new Set([...this.exsited_ci, ...res.result.map((r) => r._id)])]
}, },
}, },
} }
</script> </script>
<style></style> <style></style>

View File

@ -1,56 +1,57 @@
/* eslint-disable no-useless-constructor */ /* eslint-disable no-useless-constructor */
import { TreeNode } from 'butterfly-dag' import { TreeNode } from 'butterfly-dag'
import i18n from '@/lang'
import $ from 'jquery'
import $ from 'jquery'
class BaseNode extends TreeNode {
constructor(opts) { class BaseNode extends TreeNode {
super(opts) constructor(opts) {
} super(opts)
}
draw = (opts) => {
const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`) draw = (opts) => {
.css('top', opts.top) const container = $(`<div class="${opts.id.startsWith('Root') ? 'root' : ''} ci-detail-relation-topo-node"></div>`)
.css('left', opts.left) .css('top', opts.top)
.attr('id', opts.id) .css('left', opts.left)
let icon .attr('id', opts.id)
if (opts.options.icon) { let icon
if (opts.options.icon.split('$$')[2]) { if (opts.options.icon) {
icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`) if (opts.options.icon.split('$$')[2]) {
} else { icon = $(`<img style="max-width:16px;max-height:16px;" src="/api/common-setting/v1/file/${opts.options.icon.split('$$')[3]}" />`)
icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`) } else {
} icon = $(`<svg class="icon" style="color:${opts.options.icon.split('$$')[1]}" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><use data-v-5bd421da="" xlink:href="#${opts.options.icon.split('$$')[0]}"></use></svg>`)
} else { }
icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`) } else {
} icon = $(`<span class="icon icon-default">${opts.options.name[0].toUpperCase()}</span>`)
}
const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`)
const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}${opts.options.unique_value}<div>`) const titleContent = $(`<div title=${opts.options.title} class="title">${opts.options.title}</div>`)
container.append(icon) const uniqueDom = $(`<div class="unique">${opts.options.unique_alias || opts.options.unique_name}${opts.options.unique_value}<div>`)
container.append(titleContent) container.append(icon)
container.append(uniqueDom) container.append(titleContent)
container.append(uniqueDom)
if (opts.options.side && (!opts.options.children.length && !(opts.options.edges && opts.options.edges.length && opts.options.edges.find(e => e.source === opts.options.side && e.sourceNode === opts.options.id)))) {
const addIcon = $(`<i aria-label="图标: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`) if (opts.options.side && (!opts.options.children.length && !(opts.options.edges && opts.options.edges.length && opts.options.edges.find(e => e.source === opts.options.side && e.sourceNode === opts.options.id)))) {
container.append(addIcon) const addIcon = $(`<i aria-label="${i18n.t('icon')}: plus-square" class="anticon anticon-plus-square add-icon-${opts.options.side}"><svg viewBox="64 64 896 896" data-icon="plus-square" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"></path></svg></i>`)
addIcon.on('click', () => { container.append(addIcon)
if (opts.options.side === 'left') { addIcon.on('click', () => {
this.emit('events', { if (opts.options.side === 'left') {
type: 'custom:clickLeft', this.emit('events', {
data: { ...this } type: 'custom:clickLeft',
}) data: { ...this }
} })
if (opts.options.side === 'right') { }
this.emit('events', { if (opts.options.side === 'right') {
type: 'custom:clickRight', this.emit('events', {
data: { ...this } type: 'custom:clickRight',
}) data: { ...this }
} })
}) }
} })
}
return container[0]
} return container[0]
} }
}
export default BaseNode
export default BaseNode

View File

@ -1,6 +1,6 @@
<template> <template>
<a-form :form="form"> <a-form :form="form">
<a-divider style="font-size: 14px; margin: 14px 0; font-weight: 700">{{ group.name || '其他' }}</a-divider> <a-divider style="font-size:14px;margin:14px 0;font-weight:700;">{{ group.name || $t('other') }}</a-divider>
<a-row :gutter="24" align="top" type="flex"> <a-row :gutter="24" align="top" type="flex">
<a-col <a-col
:span="12" :span="12"
@ -21,11 +21,11 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
}, },
]" ]"
placeholder="请选择" :placeholder="$t('placeholder2')"
v-if="attr.is_choice" v-if="attr.is_choice"
:mode="attr.is_list ? 'multiple' : 'default'" :mode="attr.is_list ? 'multiple' : 'default'"
showSearch showSearch
@ -60,7 +60,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null, initialValue: attr.default && attr.default.default ? attr.default.default : attr.is_list ? [] : null,
}, },
]" ]"
@ -70,7 +70,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : null, initialValue: attr.default && attr.default.default ? attr.default.default : null,
}, },
]" ]"
@ -81,7 +81,7 @@
v-decorator="[ v-decorator="[
attr.name, attr.name,
{ {
rules: [{ required: attr.is_required, message: `请选择${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder2') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? moment(attr.default.default) : null, initialValue: attr.default && attr.default.default ? moment(attr.default.default) : null,
}, },
]" ]"
@ -96,7 +96,7 @@
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? JSON.stringify(attr.default.default) : '', initialValue: attr.default && attr.default.default ? JSON.stringify(attr.default.default) : '',
}, },
]" ]"
@ -109,7 +109,7 @@
attr.name, attr.name,
{ {
validateTrigger: ['submit'], validateTrigger: ['submit'],
rules: [{ required: attr.is_required, message: `请输入${attr.alias || attr.name}` }], rules: [{ required: attr.is_required, message: $t('placeholder1') + `${attr.alias || attr.name}` }],
initialValue: attr.default && attr.default.default ? attr.default.default : null, initialValue: attr.default && attr.default.default ? attr.default.default : null,
}, },
]" ]"

View File

@ -7,7 +7,7 @@
visible = false visible = false
} }
" "
title="字段设置" :title="$t('cmdb.ci.attributeSettings')"
> >
<CustomTransfer <CustomTransfer
ref="customTransfer" ref="customTransfer"
@ -17,16 +17,16 @@
width: '230px', width: '230px',
height: '500px', height: '500px',
}" }"
:titles="['未选属性', '已选属性']" :titles="[$t('cmdb.components.unselectAttributes'), $t('cmdb.components.selectAttributes')]"
:render="item => item.title" :render="item => item.title"
:targetKeys="selectedAttrList" :targetKeys="selectedAttrList"
@change="handleChange" @change="handleChange"
@selectChange="selectChange" @selectChange="selectChange"
> >
<span slot="notFoundContent">没数据</span> <span slot="notFoundContent">{{ $t('noData') }}</span>
</CustomTransfer> </CustomTransfer>
<div class="custom-drawer-bottom-action"> <div class="custom-drawer-bottom-action">
<a-button @click="handleSubmit" type="primary">确定</a-button> <a-button @click="handleSubmit" type="primary">{{ $t('confirm') }}</a-button>
</div> </div>
</CustomDrawer> </CustomDrawer>
</template> </template>
@ -79,16 +79,17 @@ export default {
this.selectedAttrList = targetKeys this.selectedAttrList = targetKeys
}, },
handleSubmit() { handleSubmit() {
const that = this
if (this.selectedAttrList.length) { if (this.selectedAttrList.length) {
subscribeCIType(this.typeId, this.selectedAttrList).then(res => { subscribeCIType(this.typeId, this.selectedAttrList).then(res => {
this.$message.success('订阅成功!') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.visible = false this.visible = false
this.$emit('refresh') this.$emit('refresh')
}) })
} else { } else {
this.$confirm({ this.$confirm({
title: '警告', title: that.$t('warning'),
content: '必须至少选择一个字段', content: that.$t('cmdb.ci.tips4'),
}) })
} }
}, },

View File

@ -75,12 +75,12 @@ export default {
return [item, !!this.fixedList.includes(item)] return [item, !!this.fixedList.includes(item)]
}) })
).then((res) => { ).then((res) => {
this.$message.success('订阅成功!') this.$message.success(this.$t('cmdb.components.subSuccess'))
this.visible = false this.visible = false
this.$emit('refresh') this.$emit('refresh')
}) })
} else { } else {
this.$message.error('请至少选择一个字段!') this.$message.error(this.$t('cmdb.ci.tips4'))
} }
}, },
setTargetKeys(targetKeys) { setTargetKeys(targetKeys) {

View File

@ -3,9 +3,9 @@
<Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" /> <Discovery :isSelected="true" :style="{ maxHeight: '75vh', overflow: 'auto' }" />
<template #footer> <template #footer>
<a-space> <a-space>
<a-button @click="handleCancel">取消</a-button> <a-button @click="handleCancel">{{ $t('cancel') }}</a-button>
<a-button type="primary" @click="handleOK">确认</a-button> <a-button type="primary" @click="handleOK">{{ $t('confirm') }}</a-button>
<a-button type="primary" @click="addPlugin">新建plugin</a-button> <a-button type="primary" @click="addPlugin">{{ $t('cmdb.ciType.addPlugin') }}</a-button>
</a-space> </a-space>
</template> </template>
</a-modal> </a-modal>
@ -55,7 +55,7 @@ export default {
await Promise.all(promises) await Promise.all(promises)
.then((res) => { .then((res) => {
this.getCITypeDiscovery(res[0].id) this.getCITypeDiscovery(res[0].id)
this.$message.success('添加成功') this.$message.success(this.$t('addSuccess'))
}) })
.catch(() => { .catch(() => {
this.getCITypeDiscovery() this.getCITypeDiscovery()

Some files were not shown because too many files have changed in this diff Show More