mirror of
https://github.com/jumpserver/lina.git
synced 2025-06-04 04:49:51 +00:00
460 lines
12 KiB
Vue
460 lines
12 KiB
Vue
<template>
|
||
<div v-loading="loading">
|
||
<AutoDataForm
|
||
v-if="!loading"
|
||
ref="form"
|
||
:form="form"
|
||
:has-reset="iHasReset"
|
||
:has-save-continue="iHasSaveContinue"
|
||
:is-submitting="isSubmitting"
|
||
:method="method"
|
||
:url="iUrl"
|
||
v-bind="$attrs"
|
||
@afterRemoteMeta="handleAfterRemoteMeta"
|
||
@submit="handleSubmit"
|
||
v-on="$listeners"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import AutoDataForm from '@/components/Form/AutoDataForm'
|
||
import { getUpdateObjURL } from '@/utils/common'
|
||
import { encryptPassword } from '@/utils/crypto'
|
||
import deepmerge from 'deepmerge'
|
||
|
||
export default {
|
||
name: 'GenericCreateUpdateForm',
|
||
components: {
|
||
AutoDataForm
|
||
},
|
||
props: {
|
||
// 创建对象的地址
|
||
url: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 更新的对象
|
||
object: {
|
||
type: Object,
|
||
default: null
|
||
},
|
||
// form的默认值
|
||
initial: {
|
||
type: Object,
|
||
default: () => ({})
|
||
},
|
||
afterGetFormValue: {
|
||
type: Function,
|
||
default: (value) => value
|
||
},
|
||
// 提交前,清理form的值
|
||
cleanFormValue: {
|
||
type: Function,
|
||
default: (value) => value
|
||
},
|
||
// 获取 meta
|
||
afterGetRemoteMeta: {
|
||
type: Function,
|
||
default: null
|
||
},
|
||
// 当提交的时候,怎么处理
|
||
onSubmit: {
|
||
type: Function,
|
||
default: null
|
||
},
|
||
hasReset: {
|
||
type: Boolean,
|
||
default: null
|
||
},
|
||
// 如何提交数据
|
||
performSubmit: {
|
||
type: Function,
|
||
default(validValues) {
|
||
return this.$axios[this.method](this.iUrl, validValues)
|
||
}
|
||
},
|
||
// 创建成功的msg
|
||
createSuccessMsg: {
|
||
type: String,
|
||
default: function() {
|
||
return this.$t('CreateSuccessMsg')
|
||
}
|
||
},
|
||
// 保存成功,继续添加的msg
|
||
saveSuccessContinueMsg: {
|
||
type: String,
|
||
default: function() {
|
||
return this.$t('SaveSuccessContinueMsg')
|
||
}
|
||
},
|
||
// 更新成功的msg
|
||
updateSuccessMsg: {
|
||
type: String,
|
||
default: function() {
|
||
return this.$t('UpdateSuccessMsg')
|
||
}
|
||
},
|
||
// 创建成功的跳转路由
|
||
createSuccessNextRoute: {
|
||
type: Object,
|
||
default: function() {
|
||
const routeName = this.$route.name?.replace('Create', 'List')
|
||
return { name: routeName }
|
||
}
|
||
},
|
||
// 更新成功的跳转路由
|
||
updateSuccessNextRoute: {
|
||
type: Object,
|
||
default: function() {
|
||
const routeName = this.$route.name?.replace('Update', 'List')
|
||
return { name: routeName }
|
||
}
|
||
},
|
||
objectDetailRoute: {
|
||
type: Object,
|
||
default: function() {
|
||
const routeName = this.$route.name?.replace('Update', 'Detail')
|
||
.replace('Create', 'Detail')
|
||
return { name: routeName }
|
||
}
|
||
},
|
||
// 获取下一个路由
|
||
getNextRoute: {
|
||
type: Function,
|
||
default(res, method) {
|
||
return method === 'post' ? this.createSuccessNextRoute : this.updateSuccessNextRoute
|
||
}
|
||
},
|
||
cloneNameSuffix: {
|
||
type: [String, Number],
|
||
default: function() {
|
||
return this.$t('Duplicate').toLowerCase()
|
||
}
|
||
},
|
||
// 获取提交的方法
|
||
submitMethod: {
|
||
type: [Function, String],
|
||
default: null
|
||
},
|
||
// 获取创建和更新的url function
|
||
getUrl: {
|
||
type: Function,
|
||
default: function() {
|
||
const objectId = this.getUpdateId()
|
||
let url = this.url
|
||
if (objectId) {
|
||
url = getUpdateObjURL(url, objectId)
|
||
}
|
||
|
||
const clone_from = this.getCloneId()
|
||
const query = clone_from ? `clone_from=${clone_from}` : ''
|
||
if (query) {
|
||
if (url.indexOf('?') === -1) {
|
||
url = `${url}?${query}`
|
||
} else {
|
||
url = `${url}&${query}`
|
||
}
|
||
}
|
||
return url
|
||
}
|
||
},
|
||
extraQueryOrder: {
|
||
type: String,
|
||
default: '-date_updated'
|
||
},
|
||
emitPerformSuccessMsg: {
|
||
type: Function,
|
||
default(method, res, addContinue) {
|
||
let msg = method === 'post' ? this.createSuccessMsg : this.updateSuccessMsg
|
||
if (addContinue) {
|
||
msg = this.saveSuccessContinueMsg
|
||
}
|
||
let msgLinkName = ''
|
||
if (res.name) {
|
||
msgLinkName = res.name
|
||
}
|
||
const h = this.$createElement
|
||
const detailRoute = this.objectDetailRoute
|
||
detailRoute.params = { id: res.id }
|
||
if (this.hasDetailInMsg) {
|
||
msg = msg[0].toLowerCase() + msg.slice(1)
|
||
this.$message({
|
||
message: h('p', null, [
|
||
h('el-link', {
|
||
on: {
|
||
click: () => this.$router.push(detailRoute)
|
||
},
|
||
style: { 'vertical-align': 'top', 'margin-right': '5px' }
|
||
}, msgLinkName),
|
||
h('span', {}, msg)
|
||
]),
|
||
type: 'success'
|
||
})
|
||
} else {
|
||
this.$message.success(msg)
|
||
}
|
||
}
|
||
},
|
||
onPerformSuccess: {
|
||
type: Function,
|
||
default(res, method, vm, addContinue) {
|
||
const route = this.getNextRoute(res, method)
|
||
if (!(route.params && route.params.id)) {
|
||
route['params'] = deepmerge(route['params'] || {}, { 'id': res.id })
|
||
}
|
||
route['query'] = deepmerge(route['query'], { 'order': this.extraQueryOrder, 'updated': new Date().getTime() })
|
||
|
||
this.$emit('submitSuccess', res)
|
||
|
||
this.emitPerformSuccessMsg(method, res, addContinue)
|
||
if (addContinue) {
|
||
return
|
||
}
|
||
|
||
if (!vm.drawer) {
|
||
if (this.$router.currentRoute.name !== route?.name) {
|
||
setTimeout(() => this.$router.push(route), 100)
|
||
}
|
||
} else {
|
||
this.$store.dispatch('common/finishDrawerActionMeta', { action: vm.action, row: res })
|
||
}
|
||
}
|
||
},
|
||
onPerformError: {
|
||
type: Function,
|
||
default(error, method, vm) {
|
||
const response = error.response
|
||
const data = response.data
|
||
if (response.status === 400) {
|
||
for (const key of Object.keys(data)) {
|
||
let err = ''
|
||
let errorTips = data[key]
|
||
if (errorTips instanceof Array) {
|
||
errorTips = _.filter(errorTips, (item) => Object.keys(item).length > 0)
|
||
for (const i of errorTips) {
|
||
if (i instanceof Object) {
|
||
err += i?.port?.join(',')
|
||
} else {
|
||
err += i
|
||
}
|
||
}
|
||
} else {
|
||
err = errorTips
|
||
}
|
||
this.$refs.form.setFieldError(key, err)
|
||
}
|
||
}
|
||
this.$emit('performError', data)
|
||
}
|
||
},
|
||
hasSaveContinue: {
|
||
type: Boolean,
|
||
default: null
|
||
},
|
||
hasDetailInMsg: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
encryptedFields: {
|
||
type: Array,
|
||
default: () => ['password', 'token', 'private_key']
|
||
},
|
||
needGetObjectDetail: {
|
||
type: Boolean,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
form: {},
|
||
loading: true,
|
||
isSubmitting: false,
|
||
clone: false,
|
||
drawer: false,
|
||
action: '',
|
||
actionId: '',
|
||
row: {},
|
||
method: 'post'
|
||
}
|
||
},
|
||
computed: {
|
||
iUrl() {
|
||
// 更新或创建的url
|
||
return this.getUrl()
|
||
},
|
||
iHasSaveContinue() {
|
||
if (this.hasSaveContinue != null) {
|
||
return this.hasSaveContinue
|
||
}
|
||
return this.method === 'post'
|
||
},
|
||
iHasReset() {
|
||
if (this.hasReset != null) {
|
||
return this.hasReset
|
||
}
|
||
return this.isUpdateMethod()
|
||
}
|
||
},
|
||
async created() {
|
||
this.loading = true
|
||
this.$log.debug('Object init is: ', this.object, this.method)
|
||
await this.setDrawerMeta()
|
||
this.setMethod()
|
||
// this.$log.debug('Set method: ', this.method, this.action)
|
||
|
||
try {
|
||
const values = await this.getFormValue()
|
||
this.$log.debug('Final object is: ', values)
|
||
const formValue = Object.assign(this.form, values)
|
||
this.form = this.afterGetFormValue(formValue)
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
methods: {
|
||
async setDrawerMeta() {
|
||
const drawActionMeta = await this.$store.dispatch('common/getDrawerActionMeta')
|
||
if (drawActionMeta && drawActionMeta.action) {
|
||
this.drawer = true
|
||
this.action = drawActionMeta.action
|
||
this.row = drawActionMeta.row
|
||
this.actionId = this.row?.id
|
||
}
|
||
},
|
||
setMethod() {
|
||
if (this.submitMethod instanceof Function) {
|
||
this.method = this.submitMethod(this)
|
||
} else {
|
||
this.method = this.submitMethod
|
||
}
|
||
// this.$log.debug('Drawer: ', this.drawer, this.submitMethod, this.action)
|
||
if (!this.drawer && !this.method) {
|
||
this.method = this.$route.params['id'] ? 'put' : 'post'
|
||
}
|
||
if (this.drawer && !this.submitMethod) {
|
||
if (this.action === 'clone' || this.action === 'create') {
|
||
this.method = 'post'
|
||
} else {
|
||
this.method = 'put'
|
||
}
|
||
}
|
||
},
|
||
getUpdateId() {
|
||
if (this.actionId && this.action === 'update') {
|
||
return this.actionId
|
||
} else {
|
||
return this.$route.params['id']
|
||
}
|
||
},
|
||
getCloneId() {
|
||
if (this.actionId && this.action === 'clone') {
|
||
return this.actionId
|
||
} else {
|
||
return this.$route.query['clone_from']
|
||
}
|
||
},
|
||
isUpdateMethod() {
|
||
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
|
||
},
|
||
encryptFields(values) {
|
||
// 批量提交,clean 后可能是个数组
|
||
if (values instanceof Array) {
|
||
return values.map((item) => this.encryptFields(item))
|
||
}
|
||
values = { ...values }
|
||
for (const field of this.encryptedFields) {
|
||
let value = values[field]
|
||
if (!value || typeof value !== 'string') {
|
||
continue
|
||
}
|
||
value = encryptPassword(value)
|
||
values[field] = value
|
||
}
|
||
return values
|
||
},
|
||
handleAfterRemoteMeta(meta) {
|
||
if (this.afterGetRemoteMeta) {
|
||
return this.afterGetRemoteMeta(meta)
|
||
}
|
||
},
|
||
handleSubmit(values, formName, addContinue) {
|
||
let handler = this.onSubmit || this.defaultOnSubmit
|
||
handler = handler.bind(this)
|
||
values = this.cleanFormValue(values)
|
||
values = this.encryptFields(values)
|
||
return handler(values, formName, addContinue)
|
||
},
|
||
defaultOnSubmit(validValues, formName, addContinue) {
|
||
this.isSubmitting = true
|
||
this.performSubmit(validValues)
|
||
.then((res) => this.onPerformSuccess.bind(this)(res, this.method, this, addContinue))
|
||
.catch((error) => this.onPerformError(error, this.method, this))
|
||
.finally(() => {
|
||
setTimeout(() => {
|
||
this.isSubmitting = false
|
||
this.$emit('performFinished')
|
||
}, 200)
|
||
})
|
||
},
|
||
async getCloneForm(cloneFrom) {
|
||
const [curUrl, query] = this.url.split('?')
|
||
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
|
||
try {
|
||
const object = await this.getObjectDetail(url)
|
||
let name = ''
|
||
let attr = ''
|
||
if (object['name']) {
|
||
name = object['name']
|
||
attr = 'name'
|
||
} else if (object['hostname']) {
|
||
name = object['hostname']
|
||
attr = 'hostname'
|
||
}
|
||
object[attr] = name + '-' + this.cloneNameSuffix.toString()
|
||
return object
|
||
} catch (e) {
|
||
throw new Error(`Error for reason: ${e.message}`)
|
||
}
|
||
},
|
||
async getFormValue() {
|
||
let needGetObjectDetail = this.needGetObjectDetail
|
||
if (needGetObjectDetail === null) {
|
||
needGetObjectDetail = this.isUpdateMethod() || this.action === 'clone'
|
||
}
|
||
// this.$log.debug('Get form value: ', needGetObjectDetail, this.needGetObjectDetail, this.isUpdateMethod(), this.action)
|
||
if (!needGetObjectDetail) {
|
||
return Object.assign(this.form, this.initial)
|
||
}
|
||
let object = this.object
|
||
|
||
if (!object || Object.keys(object).length === 0) {
|
||
if (this.action === 'clone') {
|
||
object = await this.getCloneForm(this.actionId)
|
||
} else {
|
||
object = await this.getObjectDetail(this.iUrl, this.actionId)
|
||
}
|
||
}
|
||
if (object) {
|
||
object = _.cloneDeep(object)
|
||
this.$emit('update:object', object)
|
||
this.$emit('getObjectDone', object)
|
||
}
|
||
return object
|
||
},
|
||
async getObjectDetail(url, id) {
|
||
this.$log.debug('Get object detail: ', url)
|
||
let data = await this.$axios.get(url, { params: { id }})
|
||
if (Array.isArray(data)) {
|
||
data = {}
|
||
}
|
||
return data
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ibox ::v-deep .el-card__body {
|
||
}
|
||
</style>
|