lina/src/layout/components/GenericCreateUpdateForm/index.vue

460 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>