perf: stash json

This commit is contained in:
ibuler
2023-05-17 18:50:16 +08:00
parent 09bd49941f
commit 936de3c9fb
8 changed files with 308 additions and 53 deletions

View File

@@ -12,8 +12,8 @@
<slot />
<div slot="footer" class="dialog-footer">
<slot name="footer">
<el-button v-if="showCancel" @click="onCancel">{{ cancelTitle }}</el-button>
<el-button v-if="showConfirm" :loading="loadingStatus" type="primary" @click="onConfirm">
<el-button v-if="showCancel && showButtons" @click="onCancel">{{ cancelTitle }}</el-button>
<el-button v-if="showConfirm && showButtons" :loading="loadingStatus" type="primary" @click="onConfirm">
{{ confirmTitle }}
</el-button>
</slot>
@@ -29,16 +29,6 @@ export default {
type: String,
default: 'Title'
},
showCancel: {
type: Boolean,
default: true
},
cancelTitle: {
type: String,
default() {
return this.$t('common.Cancel')
}
},
top: {
type: String,
default: '3vh'
@@ -51,16 +41,30 @@ export default {
type: Boolean,
default: true
},
loadingStatus: {
type: Boolean,
default: false
},
confirmTitle: {
type: String,
default() {
return this.$t('common.Confirm')
}
},
showCancel: {
type: Boolean,
default: true
},
cancelTitle: {
type: String,
default() {
return this.$t('common.Cancel')
}
},
showButtons: {
type: Boolean,
default: true
},
loadingStatus: {
type: Boolean,
default: false
},
maxWidth: {
type: String,
default: '1200px'

View File

@@ -1,16 +1,20 @@
<template>
<div>
<TagInput v-if="Array.isArray(value)" :value="value" @input="handleInput" />
<div v-if="!loading">
<TagInput v-if="type === 'array'" :value="value" @input="handleInput" />
<Select2 v-else-if="type === 'select'" :value="value" v-bind="attr.el" @change="handleInput" @input="handleInput" />
<Switcher v-else-if="type === 'bool'" :value="value" @change="handleInput" />
<el-input v-else :value="value" @input="handleInput" />
</div>
</template>
<script>
import TagInput from '@/components/FormFields/TagInput.vue'
import Select2 from '@/components/FormFields/Select2.vue'
import Switcher from '@/components/FormFields/Switcher.vue'
export default {
name: 'ValueField',
components: { TagInput },
components: { Switcher, TagInput, Select2 },
props: {
value: {
type: [String, Number, Boolean, Array, Object],
@@ -18,11 +22,44 @@ export default {
},
match: {
type: String,
default: ''
default: 'exact'
},
attr: {
type: Object,
default: () => ({})
}
},
data() {
return {
loading: false
}
},
computed: {
type() {
const attrType = this.attr.type
if (attrType === 'm2m') {
return 'select'
} else if (attrType === 'bool') {
return 'bool'
} else if (attrType === 'select') {
return 'select'
}
if (this.match in ['in', 'ip_in']) {
return 'array'
} else {
return 'string'
}
}
},
watch: {
attr: {
handler() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
deep: true
}
},
methods: {

View File

@@ -0,0 +1,76 @@
<template>
<span v-if="attr.type === 'bool'">
<i v-if="value" class="fa fa-check text-primary" />
<i v-else class="fa fa-times text-danger" />
</span>
<span v-else :title="value">{{ value }}</span>
</template>
<script>
import BaseFormatter from '@/components/TableFormatters/base.vue'
import { setUrlParam } from '@/utils/common'
export default {
name: 'ValueFormatter',
extends: BaseFormatter,
props: {
formatterArgsDefault: {
type: Object,
default() {
return {
attrs: {}
}
}
}
},
data() {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
return {
formatterArgs: formatterArgs,
loading: true,
value: ''
}
},
computed: {
attr() {
return this.formatterArgs.attrs.find(attr => attr.name === this.row.name) || {}
}
},
watch: {
cellValue: {
handler(val) {
this.getValue()
},
immediate: true,
deep: true
}
},
mounted() {
this.getValue()
},
methods: {
async getValue() {
if (this.attr.type === 'm2m') {
const url = setUrlParam(this.attr.el.url, 'ids', this.cellValue.join(','))
const data = await this.$axios.get(url)
if (data.length > 0) {
const displayField = this.attr.el.displayField || 'name'
this.value = data.map(item => item[displayField]).join(', ')
}
} else if (this.attr.type === 'select') {
this.value = this.attr.el.options
.filter(item => this.cellValue.includes(item.value))
.map(item => item.label).join(',')
} else {
this.value = this.cellValue
}
this.loading = false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -14,7 +14,7 @@
</el-button>
</div>
</div>
<Dialog v-if="visible" :show-cancel="false" :show-confirm="false" :visible.sync="visible" title="选择属性">
<Dialog v-if="visible" :show-buttons="false" :visible.sync="visible" title="选择属性">
<DataForm class="attr-form" v-bind="formConfig" @submit="onAttrDialogConfirm" />
</Dialog>
</div>
@@ -25,6 +25,7 @@ import Select2 from '../Select2.vue'
import DataTable from '@/components/DataTable/index.vue'
import Dialog from '@/components/Dialog/index.vue'
import DataForm from '@/components/DataForm/index.vue'
import ValueFormatter from './ValueFormatter.vue'
import ValueField from './ValueField.vue'
export default {
@@ -61,14 +62,24 @@ export default {
{ label: this.$t('common.Startswith'), value: 'startswith' },
{ label: this.$t('common.Endswith'), value: 'endswith' },
{ label: this.$t('common.Regex'), value: 'regex' },
{ label: this.$t('common.IPMatch'), value: 'ip_in' }
{ label: this.$t('common.BelongTo'), value: 'm2m' },
{ label: this.$t('common.IPMatch'), value: 'ip_in' },
{ label: this.$t('common.GreatEqualThan'), value: 'gte' },
{ label: this.$t('common.LessEqualThan'), value: 'lte' }
]
const attrRelOptions = [
{ label: this.$t('common.RelAnd'), value: 'and' },
{ label: this.$t('common.RelOr'), value: 'or' },
{ label: this.$t('common.RelNot'), value: 'not' }
]
const attrNameOptions = this.attrs.map(attr => ({ label: attr.label, value: attr.name }))
const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex']
const typeMatchMapper = {
str: strMatchValues,
bool: ['exact', 'not'],
m2m: ['m2m'],
ip: strMatchValues + ['ip_in'],
int: strMatchValues + ['gte', 'lte'],
select: ['in']
}
attrMatchOptions.forEach((option) => {
option.hidden = !typeMatchMapper[this.attrs[0].type || 'str'].includes(option.value)
})
const tableFormatter = (colName) => {
return (row, col, cellValue) => {
const value = cellValue
@@ -77,8 +88,6 @@ export default {
return this.attrs.find(attr => attr.name === value)?.label || value
case 'match':
return attrMatchOptions.find(opt => opt.value === value).label || value
case 'rel':
return attrRelOptions.find(opt => opt.value === value)?.label || value
case 'value':
return Array.isArray(value) ? value.join(', ') : value
default:
@@ -98,8 +107,7 @@ export default {
columns: [
{ prop: 'name', label: this.$t('common.AttrName'), formatter: tableFormatter('name') },
{ prop: 'match', label: this.$t('common.Match'), formatter: tableFormatter('match') },
{ prop: 'value', label: this.$t('common.AttrValue'), formatter: tableFormatter('value') },
{ prop: 'rel', label: this.$t('common.Relation'), formatter: tableFormatter('rel') },
{ prop: 'value', label: this.$t('common.AttrValue'), formatter: ValueFormatter, formatterArgs: { attrs: this.attrs }},
{ prop: 'action', label: this.$t('common.Action'), formatter: (row, col, cellValue, index) => {
return (
<div className='input-button'>
@@ -126,46 +134,48 @@ export default {
},
formConfig: {
// 为了方便更新,避免去取 fields 的索引
attrNameOptions: attrNameOptions,
hasSaveContinue: false,
editRowIndex: -1,
form: {},
fields: [
{
id: 'name',
label: '属性',
label: this.$t('common.AttrName'),
type: 'select',
options: attrNameOptions
options: this.attrs.map(attr => ({ label: attr.label, value: attr.name })),
on: {
change: ([val], updateForm) => {
const attr = this.attrs.find(attr => attr.name === val)
if (!attr) return
this.formConfig.fields[2].el.attr = attr
const attrType = attr.type || 'str'
const matchSupports = typeMatchMapper[attrType]
attrMatchOptions.forEach((option) => {
option.hidden = !matchSupports.includes(option.value)
})
setTimeout(() => updateForm({ match: matchSupports[0] }), 0.1)
}
}
},
{
id: 'match',
label: '匹配方式',
label: this.$t('common.Match'),
type: 'select',
options: attrMatchOptions,
on: {
change: ([value], updateForm) => {
this.formConfig.fields[2].el.match = value
if (['in', 'ip_in'].includes(value)) {
updateForm({ value: [] })
} else {
updateForm({ value: '' })
}
}
}
},
{
id: 'value',
label: '值',
label: this.$t('common.AttrValue'),
component: ValueField,
el: {
match: 'exact'
match: attrMatchOptions[0].value,
attr: this.attrs[0]
}
},
{
id: 'rel',
label: '关系',
type: 'radio-group',
options: attrRelOptions
}
]
},
@@ -209,9 +219,8 @@ export default {
}
},
setAttrNameOptionUsed() {
const options = this.formConfig.attrNameOptions
const options = this.formConfig.fields[0].options
const used = this.tableConfig.totalData.map(attr => attr.name)
console.log('Used: ', used)
options.forEach(opt => {
if (used.includes(opt.value)) {
opt.disabled = true
@@ -219,11 +228,11 @@ export default {
delete opt.disabled
}
})
console.log('Options: ', options)
},
handleAttrEdit({ row, index }) {
return () => {
this.formConfig.editRowIndex = index
this.formConfig.fields[2].el.attr = this.attrs.find(attr => attr.name === row.name)
this.formConfig.form = Object.assign({ index }, row)
this.setAttrNameOptionUsed()
this.visible = true
@@ -236,6 +245,7 @@ export default {
},
handleAttrAdd() {
this.formConfig.form = this.getDefaultAttrForm()
this.formConfig.fields[2].el.attr = this.attrs.find(attr => attr.name === this.formConfig.form.name)
this.setAttrNameOptionUsed()
this.visible = true
},

View File

@@ -456,6 +456,11 @@
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
},
"common": {
"GreatEqualThan": "大于等于",
"LessEqualThan": "小于等于",
"BelongTo": "所属",
"Email": "邮箱",
"IsActive": "激活",
"All": "所有",
"Spec": "指定",
"SelectByAttr": "属性筛选",

View File

@@ -140,6 +140,23 @@ export const assetFieldsMeta = (vm) => {
}
export const assetJSONSelectMeta = (vm) => {
const categories = []
const types = []
const protocols = []
vm.$axios.get('/api/v1/assets/categories/').then((res) => {
const _types = []
const _protocols = []
for (const category of res) {
categories.push({ value: category.value, label: category.label })
_types.push(...category.types.map(item => ({ value: item.value, label: item.label })))
for (const type of category.types) {
_protocols.push(...type.constraints.protocols?.map(item => ({ value: item.name, label: item.name.toUpperCase() })))
}
}
types.push(..._.uniqBy(_types, 'value'))
protocols.push(..._.uniqBy(_protocols, 'value'))
})
return {
component: JSONManyToManySelect,
el: {
@@ -160,7 +177,63 @@ export const assetJSONSelectMeta = (vm) => {
},
{
name: 'address',
label: vm.$t('assets.Address')
label: vm.$t('assets.Address'),
type: 'ip'
},
{
name: 'nodes',
label: vm.$t('assets.Node'),
type: 'm2m',
el: {
url: '/api/v1/assets/nodes/',
ajax: {
transformOption: (item) => {
return { label: item.full_value, value: item.id }
}
}
}
},
{
name: 'platform',
label: vm.$t('assets.Platform'),
type: 'm2m',
el: {
multiple: false,
url: '/api/v1/assets/platforms/'
}
},
{
name: 'category',
label: vm.$t('assets.Category'),
type: 'select',
el: {
options: categories
}
},
{
name: 'type',
label: vm.$t('assets.Type'),
type: 'select',
el: {
options: types
}
},
{
name: 'protocols',
label: vm.$t('assets.Protocols'),
type: 'select',
el: {
options: protocols
}
},
{
name: 'labels',
label: vm.$t('assets.Label'),
type: 'm2m',
el: {
multiple: true,
url: '/api/v1/assets/labels/'
}
}
]
}

View File

@@ -176,6 +176,7 @@ export default {
},
mounted() {
this.initDefaultChoice()
this.$emit('input', this.value)
},
methods: {
initDefaultChoice() {

View File

@@ -22,6 +22,55 @@ export const userJSONSelectMeta = (vm) => {
{
name: 'username',
label: vm.$t('common.Username')
},
{
name: 'email',
label: vm.$t('common.Email')
},
{
name: 'comment',
label: vm.$t('common.Comment')
},
{
name: 'is_active',
label: vm.$t('common.IsActive'),
type: 'bool'
},
{
name: 'system_roles',
label: vm.$t('users.SystemRoles'),
type: 'm2m',
el: {
url: '/api/v1/rbac/system-roles/?fields_size=mini',
ajax: {
transformOption: (item) => {
return { label: item.display_name, value: item.id }
}
},
displayField: 'display_name'
}
},
{
name: 'org_roles',
label: vm.$t('users.OrgRoles'),
type: 'm2m',
el: {
url: '/api/v1/rbac/org-roles/',
ajax: {
transformOption: (item) => {
return { label: item.display_name, value: item.id }
}
},
displayField: 'display_name'
}
},
{
name: 'groups',
label: vm.$t('users.UserGroups'),
type: 'm2m',
el: {
url: '/api/v1/users/groups/?fields_size=mini'
}
}
]
}