mirror of
https://github.com/jumpserver/lina.git
synced 2025-10-22 08:08:39 +00:00
fix: 修复编辑出问题的bug
perf: 优化嵌套的form perf: 优化嵌套的form perf: 优化其那套form perf: 优化nestfield, 但报错存在问题 perf: 优化
This commit is contained in:
43
src/components/AutoDataForm/components/NestedField.vue
Normal file
43
src/components/AutoDataForm/components/NestedField.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<DataForm
|
||||
:fields="fields"
|
||||
:form="value"
|
||||
style="margin-left: -26%;margin-right: -6%"
|
||||
v-bind="kwargs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DataForm } from '@/components'
|
||||
|
||||
export default {
|
||||
name: 'NestedField',
|
||||
components: {
|
||||
DataForm
|
||||
},
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
kwargs: {
|
||||
hasReset: false,
|
||||
hasSaveContinue: false,
|
||||
defaultButton: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,15 +1,13 @@
|
||||
<template>
|
||||
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
|
||||
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i != 0" />
|
||||
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" :form="iForm" v-bind="$attrs" v-on="$listeners">
|
||||
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i !== 0" />
|
||||
</DataForm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataForm from '../DataForm'
|
||||
import FormGroupHeader from '@/components/FormGroupHeader'
|
||||
// import { optionUrlMeta } from '@/api/common'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import Select2 from '@/components/Select2'
|
||||
import { FormFieldGenerator } from '@/components/AutoDataForm/utils'
|
||||
export default {
|
||||
name: 'AutoDataForm',
|
||||
components: {
|
||||
@@ -31,6 +29,10 @@ export default {
|
||||
return []
|
||||
}
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
fieldsMeta: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
@@ -38,175 +40,51 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
meta: {},
|
||||
remoteMeta: {},
|
||||
totalFields: [],
|
||||
loading: true,
|
||||
groups: []
|
||||
groups: [],
|
||||
iForm: this.form
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.optionUrlMeta()
|
||||
this.optionUrlMetaAndGenerateColumns()
|
||||
},
|
||||
methods: {
|
||||
optionUrlMeta() {
|
||||
optionUrlMetaAndGenerateColumns() {
|
||||
this.$store.dispatch('common/getUrlMeta', { url: this.url }).then(data => {
|
||||
this.meta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.remoteMeta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.generateColumns()
|
||||
this.cleanFormValue()
|
||||
}).catch(err => {
|
||||
this.$log.error(err)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
generateFieldByType(type, field, fieldMeta) {
|
||||
switch (type) {
|
||||
case 'choice':
|
||||
type = 'radio-group'
|
||||
if (!fieldMeta.read_only) {
|
||||
field.options = fieldMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'datetime':
|
||||
type = 'date-picker'
|
||||
field.el = {
|
||||
type: 'datetime'
|
||||
}
|
||||
break
|
||||
case 'field':
|
||||
type = ''
|
||||
field.component = Select2
|
||||
if (fieldMeta.required) {
|
||||
field.el.clearable = false
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
type = 'input'
|
||||
if (!fieldMeta.max_length) {
|
||||
field.el.type = 'textarea'
|
||||
field.el.rows = 3
|
||||
}
|
||||
if (fieldMeta.write_only) {
|
||||
field.el.type = 'password'
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
type = 'checkbox'
|
||||
break
|
||||
default:
|
||||
type = 'input'
|
||||
break
|
||||
}
|
||||
if (type === 'radio-group') {
|
||||
if (!fieldMeta.read_only) {
|
||||
const options = fieldMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
if (options.length > 4) {
|
||||
type = 'select'
|
||||
field.el.filterable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
field.type = type
|
||||
return field
|
||||
},
|
||||
generateFieldByName(name, field) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
field.el.type = 'email'
|
||||
break
|
||||
case 'password':
|
||||
field.el.type = 'password'
|
||||
break
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
}
|
||||
return field
|
||||
},
|
||||
generateFieldByOther(field, fieldMeta) {
|
||||
const filedRules = field.rules || []
|
||||
if (fieldMeta.required) {
|
||||
if (field.type === 'input') {
|
||||
filedRules.push(rules.Required)
|
||||
} else {
|
||||
filedRules.push(rules.RequiredChange)
|
||||
}
|
||||
}
|
||||
field.rules = filedRules
|
||||
return field
|
||||
},
|
||||
generateField(name) {
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}}
|
||||
const fieldMeta = this.meta[name] ||
|
||||
((this.meta['attrs']) ? (this.meta['attrs']['children'][name]) : false) ||
|
||||
((this.meta['meta']) ? (this.meta['meta']['children'][name]) : {})
|
||||
field.label = fieldMeta.label
|
||||
field.helpText = fieldMeta.help_text
|
||||
field = this.generateFieldByType(fieldMeta.type, field, fieldMeta)
|
||||
field = this.generateFieldByName(name, field)
|
||||
field = this.generateFieldByOther(field, fieldMeta)
|
||||
field = Object.assign(field, this.fieldsMeta[name] || {})
|
||||
|
||||
_.set(field, 'attrs.error', '')
|
||||
return field
|
||||
},
|
||||
generateFieldGroup(data) {
|
||||
const [groupTitle, fields] = data
|
||||
this.groups.push({
|
||||
id: groupTitle,
|
||||
title: groupTitle,
|
||||
name: fields[0]
|
||||
})
|
||||
return this.generateFields(fields)
|
||||
},
|
||||
generateFieldAttrs(name) {
|
||||
const fields = []
|
||||
Object.keys(this.meta[name]['children']).forEach((key, i) => {
|
||||
const filed = this.generateField(key)
|
||||
fields.push(filed)
|
||||
})
|
||||
return fields
|
||||
},
|
||||
generateFields(data) {
|
||||
let fields = []
|
||||
for (let field of data) {
|
||||
if (field instanceof Array) {
|
||||
const items = this.generateFieldGroup(field)
|
||||
fields = [...fields, ...items]
|
||||
} else if (typeof field === 'string') {
|
||||
field = this.generateField(field)
|
||||
fields.push(field)
|
||||
} else if (field instanceof Object) {
|
||||
this.errors[field.prop] = ''
|
||||
_.set(field, 'attrs.error', '')
|
||||
fields.push(field)
|
||||
}
|
||||
}
|
||||
return fields
|
||||
},
|
||||
generateColumns() {
|
||||
this.totalFields = this.generateFields(this.fields)
|
||||
const generator = new FormFieldGenerator()
|
||||
this.totalFields = generator.generateFields(this.fields, this.fieldsMeta, this.remoteMeta)
|
||||
this.groups = generator.groups
|
||||
this.$log.debug('Total fields: ', this.totalFields)
|
||||
},
|
||||
setFieldError(name, error) {
|
||||
const field = this.totalFields.find((v) => v.prop === name)
|
||||
if (!field) {
|
||||
return
|
||||
_cleanFormValue(form, remoteMeta) {
|
||||
for (const [k, v] of Object.entries(remoteMeta)) {
|
||||
if (v.default === undefined) {
|
||||
continue
|
||||
}
|
||||
if (typeof error === 'object') {
|
||||
const str = error
|
||||
error = ''
|
||||
Object.keys(str).forEach(key => {
|
||||
error += `${parseInt(key) + 1}.${str[key][0]} `
|
||||
})
|
||||
const valueSet = form[k]
|
||||
if (valueSet !== undefined) {
|
||||
continue
|
||||
}
|
||||
// if (field.attrs.error === error) {
|
||||
// error += '.'
|
||||
// }
|
||||
field.attrs.error = error
|
||||
if (v.type === 'nested object' && typeof valueSet === 'object') {
|
||||
this._cleanFormValue(valueSet, v.children)
|
||||
}
|
||||
form[k] = v.default
|
||||
}
|
||||
},
|
||||
cleanFormValue() {
|
||||
this._cleanFormValue(this.iForm, this.remoteMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
149
src/components/AutoDataForm/utils.js
Normal file
149
src/components/AutoDataForm/utils.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import Vue from 'vue'
|
||||
import Select2 from '@/components/Select2'
|
||||
import NestedField from '@/components/AutoDataForm/components/NestedField'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
|
||||
export class FormFieldGenerator {
|
||||
constructor() {
|
||||
this.groups = []
|
||||
}
|
||||
generateFieldByType(type, field, fieldMeta, fieldRemoteMeta) {
|
||||
switch (type) {
|
||||
case 'choice':
|
||||
type = 'radio-group'
|
||||
if (!fieldRemoteMeta.read_only) {
|
||||
field.options = fieldRemoteMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'datetime':
|
||||
type = 'date-picker'
|
||||
field.el = {
|
||||
type: 'datetime'
|
||||
}
|
||||
break
|
||||
case 'field':
|
||||
type = ''
|
||||
field.component = Select2
|
||||
if (fieldRemoteMeta.required) {
|
||||
field.el.clearable = false
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
type = 'input'
|
||||
if (!fieldRemoteMeta.max_length) {
|
||||
field.el.type = 'textarea'
|
||||
field.el.rows = 3
|
||||
}
|
||||
if (fieldRemoteMeta.write_only) {
|
||||
field.el.type = 'password'
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
type = 'checkbox'
|
||||
break
|
||||
case 'nested object':
|
||||
field.component = NestedField
|
||||
field.label = ''
|
||||
field.labelWidth = 0
|
||||
field.el.fields = this.generateNestFields(field, fieldMeta, fieldRemoteMeta)
|
||||
Vue.$log.debug('All fields in generate: ', field.el.allFields)
|
||||
break
|
||||
default:
|
||||
type = 'input'
|
||||
break
|
||||
}
|
||||
if (type === 'radio-group') {
|
||||
if (!fieldRemoteMeta.read_only) {
|
||||
const options = fieldRemoteMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
if (options.length > 4) {
|
||||
type = 'select'
|
||||
field.el.filterable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
field.type = type
|
||||
return field
|
||||
}
|
||||
generateNestFields(field, fieldMeta, fieldRemoteMeta) {
|
||||
const fields = []
|
||||
const nestedFields = fieldMeta.fields || []
|
||||
const nestedFieldsMeta = fieldMeta.fieldsMeta || {}
|
||||
const nestedFieldsRemoteMeta = fieldRemoteMeta.children || {}
|
||||
for (const name of nestedFields) {
|
||||
const f = this.generateField(name, nestedFieldsMeta, nestedFieldsRemoteMeta)
|
||||
fields.push(f)
|
||||
}
|
||||
Vue.$log.debug('NestFields: ', fields)
|
||||
return fields
|
||||
}
|
||||
generateFieldByName(name, field) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
field.el.type = 'email'
|
||||
break
|
||||
case 'password':
|
||||
field.el.type = 'password'
|
||||
break
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
}
|
||||
return field
|
||||
}
|
||||
generateFieldByOther(field, fieldMeta, fieldRemoteMeta) {
|
||||
const filedRules = field.rules || []
|
||||
if (fieldRemoteMeta.required) {
|
||||
if (field.type === 'input') {
|
||||
filedRules.push(rules.Required)
|
||||
} else {
|
||||
filedRules.push(rules.RequiredChange)
|
||||
}
|
||||
}
|
||||
field.rules = filedRules
|
||||
return field
|
||||
}
|
||||
generateField(name, fieldsMeta, remoteFieldsMeta) {
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}}
|
||||
const remoteFieldMeta = remoteFieldsMeta[name] || {}
|
||||
Vue.$log.debug('FieldsMeta: ', fieldsMeta, name)
|
||||
const fieldMeta = fieldsMeta[name] || {}
|
||||
Vue.$log.debug('FieldMeta is: ', fieldMeta)
|
||||
field.label = remoteFieldMeta.label
|
||||
field.helpText = remoteFieldMeta.help_text
|
||||
field = this.generateFieldByType(remoteFieldMeta.type, field, fieldMeta, remoteFieldMeta)
|
||||
field = this.generateFieldByName(name, field)
|
||||
field = this.generateFieldByOther(field, fieldMeta, remoteFieldMeta)
|
||||
const el = Object.assign(field.el || {}, fieldMeta.el || {})
|
||||
field = Object.assign(field, fieldMeta || {}, { el: el })
|
||||
return field
|
||||
}
|
||||
generateFieldGroup(field, fieldsMeta, remoteFieldsMeta) {
|
||||
const [groupTitle, fields] = field
|
||||
this.groups.push({
|
||||
id: groupTitle,
|
||||
title: groupTitle,
|
||||
name: fields[0]
|
||||
})
|
||||
return this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
|
||||
}
|
||||
generateFields(_fields, fieldsMeta, remoteFieldsMeta) {
|
||||
let fields = []
|
||||
for (let field of _fields) {
|
||||
if (field instanceof Array) {
|
||||
const items = this.generateFieldGroup(field, fieldsMeta, remoteFieldsMeta)
|
||||
fields = [...fields, ...items]
|
||||
} else if (typeof field === 'string') {
|
||||
field = this.generateField(field, fieldsMeta, remoteFieldsMeta)
|
||||
fields.push(field)
|
||||
} else if (field instanceof Object) {
|
||||
this.errors[field.prop] = ''
|
||||
fields.push(field)
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
}
|
@@ -47,7 +47,7 @@ export default {
|
||||
// 初始值
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => { return {} }
|
||||
default: () => ({})
|
||||
},
|
||||
moreButtons: {
|
||||
type: Array,
|
||||
|
@@ -12,7 +12,7 @@ export default {
|
||||
return {
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'type', 'domain']],
|
||||
[this.$t('applications.DBInfo'), ['host', 'port', 'database']],
|
||||
[this.$t('applications.DBInfo'), ['attrs']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
|
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'type', 'domain']],
|
||||
[this.$t('applications.kubernetes'), ['cluster']],
|
||||
[this.$t('applications.kubernetes'), ['attrs']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
|
@@ -15,6 +15,7 @@ export default {
|
||||
const fieldsMap = REMOTE_APP_TYPE_FIELDS_MAP[appType]
|
||||
const appTypeMeta = REMOTE_APP_TYPE_META_MAP[appType]
|
||||
const pathInitial = REMOTE_APP_PATH_DEFAULT_MAP[appType]
|
||||
console.log(fieldsMap)
|
||||
|
||||
return {
|
||||
initial: {
|
||||
@@ -23,71 +24,15 @@ export default {
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'type']],
|
||||
[appTypeMeta.title, fieldsMap],
|
||||
[appTypeMeta.title, ['attrs']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
url: '/api/v1/applications/applications/',
|
||||
getUrl() {
|
||||
const params = this.$route.params
|
||||
let url = `/api/v1/applications/applications/`
|
||||
if (params.id) {
|
||||
url = `${url}${params.id}/`
|
||||
}
|
||||
return `${url}?type=${this.$route.query.type}`
|
||||
},
|
||||
performSubmit(validValues) {
|
||||
const params = this.$route.params
|
||||
const baseUrl = `/api/v1/applications/applications/`
|
||||
const url = (params.id) ? `${baseUrl}${params.id}/` : baseUrl
|
||||
const method = this.getMethod()
|
||||
switch (validValues.type) {
|
||||
case 'chrome': {
|
||||
validValues.attrs = {
|
||||
chrome_target: validValues.chrome_target,
|
||||
chrome_username: validValues.chrome_username,
|
||||
chrome_password: validValues.chrome_password,
|
||||
asset: validValues.asset,
|
||||
path: validValues.path
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'mysql_workbench': {
|
||||
validValues.attrs = {
|
||||
mysql_workbench_ip: validValues.mysql_workbench_ip,
|
||||
mysql_workbench_port: validValues.mysql_workbench_port,
|
||||
mysql_workbench_name: validValues.mysql_workbench_name,
|
||||
mysql_workbench_username: validValues.mysql_workbench_username,
|
||||
mysql_workbench_password: validValues.mysql_workbench_password,
|
||||
asset: validValues.asset,
|
||||
path: validValues.path
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'vmware_client': {
|
||||
validValues.attrs = {
|
||||
vmware_password: validValues.vmware_password,
|
||||
vmware_username: validValues.vmware_username,
|
||||
vmware_target: validValues.vmware_target,
|
||||
asset: validValues.asset,
|
||||
path: validValues.path
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'custom': {
|
||||
validValues.attrs = {
|
||||
custom_cmdline: validValues.custom_cmdline,
|
||||
custom_target: validValues.custom_target,
|
||||
custom_username: validValues.custom_username,
|
||||
custom_password: validValues.custom_password,
|
||||
asset: validValues.asset,
|
||||
path: validValues.path
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
validValues.category = 'remote_app'
|
||||
return this.$axios[method](`${url}?type=${validValues.type}`, validValues)
|
||||
fieldsMeta: {
|
||||
type: {
|
||||
readonly: true
|
||||
},
|
||||
attrs: {
|
||||
fields: fieldsMap,
|
||||
fieldsMeta: {
|
||||
asset: {
|
||||
rules: [{ required: true }],
|
||||
@@ -100,34 +45,27 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: appTypeMeta.title,
|
||||
value: appTypeMeta.name
|
||||
}
|
||||
],
|
||||
disabled: true
|
||||
},
|
||||
asset_info: {
|
||||
type: 'input',
|
||||
hidden: () => true
|
||||
},
|
||||
hello: {
|
||||
type: 'input'
|
||||
},
|
||||
domain: {
|
||||
el: {
|
||||
multiple: false,
|
||||
clearable: true,
|
||||
ajax: {
|
||||
url: '/api/v1/assets/domains/'
|
||||
}
|
||||
}
|
||||
},
|
||||
...fieldsMap
|
||||
url: '/api/v1/applications/applications/',
|
||||
getUrl() {
|
||||
const params = this.$route.params
|
||||
let url = `/api/v1/applications/applications/`
|
||||
if (params.id) {
|
||||
url = `${url}${params.id}/`
|
||||
}
|
||||
return `${url}?type=${this.$route.query.type}`
|
||||
},
|
||||
performSubmit(validValues) {
|
||||
this.$log.debug('Validated data: ', validValues)
|
||||
const params = this.$route.params
|
||||
const baseUrl = `/api/v1/applications/applications/`
|
||||
const url = (params.id) ? `${baseUrl}${params.id}/` : baseUrl
|
||||
const method = this.getMethod()
|
||||
validValues.category = 'remote_app'
|
||||
return this.$axios[method](`${url}?type=${validValues.type}`, validValues)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -9,7 +9,6 @@
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import UserPassword from '@/components/UserPassword'
|
||||
import RoleCheckbox from '@/views/users/User/components/RoleCheckbox'
|
||||
import { getDayFuture } from '@/utils/common'
|
||||
import { mapGetters } from 'vuex'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
|
||||
@@ -20,12 +19,12 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
initial: {
|
||||
password_strategy: 0,
|
||||
mfa_level: 0,
|
||||
role: 'User',
|
||||
source: 'local',
|
||||
org_roles: ['User'],
|
||||
date_expired: getDayFuture(36500, new Date()).toISOString()
|
||||
// password_strategy: 0,
|
||||
// mfa_level: 0,
|
||||
// role: 'User',
|
||||
// source: 'local',
|
||||
// org_roles: ['User'],
|
||||
// date_expired: getDayFuture(36500, new Date()).toISOString()
|
||||
},
|
||||
fields: [
|
||||
[this.$t('users.Account'), ['name', 'username', 'email', 'groups']],
|
||||
|
Reference in New Issue
Block a user