Commit 19cea903 authored by qinj's avatar qinj

Merge branch 'dev' of http://120.25.63.219:6088/rex/portalhtml into dev

parents 4e34cf47 9cc27b5c
<template>
<div
ref="wrapper"
class="ele-form"
:class="{ 'ele-form--inline': inline }"
>
<el-row
justify="center"
type="flex"
>
<el-col :span="computedSpan">
<el-form
ref="form"
:label-position="computedLabelPosition"
:label-width="computedLabelWidth"
:model="formData"
:rules="computedRules"
:validate-on-rule-change="false"
:disabled="disabled"
v-bind="formAttrs"
@submit.native.prevent="handleSubmitForm"
>
<!-- 默认插槽作为表单项 -->
<slot />
<el-row :gutter="20">
<slot
:formData="formData"
:formDesc="orderedFormDesc"
:formErrorObj="formErrorObj"
:props="$props"
name="form-content"
>
<template v-for="(formItem, field) of orderedFormDesc">
<slot
:name="field + '-wrapper'"
:data="formData[field]"
:desc="formItem"
:field="field"
:props="$props"
:formData="formData"
:disabled="formItem._disabled"
:type="formItem._type"
:options="formItem._options"
>
<el-col
v-if="formItem._vif"
:key="field"
v-bind="formItem._colAttrs"
:class="{ 'ele-form-col--break': formItem.break }"
>
<el-form-item
:error="formErrorObj ? formErrorObj[field] : null"
:label="
isShowLabel && formItem.isShowLabel !== false
? (formItem._label ? formItem._label + ':' : null)
: null
"
:label-width="formItem.labelWidth || null"
:prop="field"
>
<!-- 具名 作用域插槽(用于用户自定义显示) -->
<slot
:data="formData[field]"
:desc="formItem"
:props="$props"
:field="field"
:formData="formData"
:name="field"
:disabled="formItem._disabled"
:type="formItem._type"
:options="formItem._options"
>
<component
:is="formItem._type"
:ref="field"
:disabled="formItem._disabled"
:readonly="readonly"
:desc="formItem"
:options="formItem._options"
:field="field"
:form-data="formData"
:value="formData[field]"
@input="setValue(field, $event)"
/>
</slot>
<div
v-if="formItem._tip"
class="ele-form-tip"
v-html="formItem._tip"
/>
</el-form-item>
</el-col>
</slot>
</template>
</slot>
<slot name="form-footer" />
<!-- 操作按钮区 -->
<el-col
v-if="btns.length"
class="ele-form-btns"
>
<el-form-item :label-width="inline ? '10px' : null">
<!-- 按钮插槽 -->
<slot
:btns="btns"
name="form-btn"
>
<el-button
v-for="(btn, index) of btns"
:key="index"
v-bind="btn.attrs"
@click="btn.click"
>{{ btn.text }}</el-button>
</slot>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import responsiveMixin from './mixins/responsiveMixin'
import { isUnDef, is, castArray, isEmpty } from './tools/utils'
import { throttle } from 'throttle-debounce'
import localeMixin from './mixins/locale'
import { t } from './locale'
import { loadMockJs } from './tools/mock'
import fetchDictionary from '@/utils/fetch-dictionary' // 请求枚举值方法
const isNumber = require('is-number')
const cloneDeep = require('clone')
export default {
name: 'EleForm',
// 响应式单独抽离出来作为mixin, 具体实现请到 responsiveMixin 中查看
mixins: [responsiveMixin, localeMixin],
model: {
prop: 'formData',
event: 'input'
},
provide() {
return {
EleForm: this
}
},
props: {
// 表单描述
formDesc: {
type: Object,
required: true
},
// 表单数据
formData: {
type: Object,
required: true
},
// 行内模式
inline: {
type: Boolean,
default: false
},
// 表单自身属性
formAttrs: {
type: Object,
default: () => { }
},
// 校检规则
rules: {
type: Object,
default() {
return {}
}
},
// 模拟数据
mock: {
type: Boolean,
default: false
},
// 提交状态
isLoading: {
type: Boolean,
default: false
},
// 表单错误信息
formError: {
type: Object,
default: () => { }
},
// 提交函数
requestFn: {
type: Function,
default: () => { }
},
// 自定义表单按钮
formBtns: {
type: Array,
default: () => []
},
// 表单按钮大小
formBtnSize: {
type: String,
default: ''
},
// 是否显示submit按钮
isShowSubmitBtn: {
type: Boolean,
default: true
},
// 是否显示 cancel 取消按钮
// 默认值: isDialog = true 时, 默认值为 true, 具体查看: computedIsShowCancelBtn
isShowCancelBtn: {
type: Boolean,
default: null
},
// 是否显示back按钮
// 默认值: 当 inline = true OR isDialog = true, 默认值为 false; 其它情况true. 具体请看计算属性: computedIsShowBackBtn
isShowBackBtn: {
type: Boolean,
default: null
},
// 是否显示reset按钮
isShowResetBtn: {
type: Boolean,
default: false
},
// 提交按钮文本
// 默认值: 当 inline 为true时, 值为 '查询'; inline 为 false 时, 值为 '提交'. 具体请看计算属性: computedSubmitBtnText
submitBtnText: {
type: String,
default: null
},
// 返回按钮
backBtnText: {
type: String,
default: ''
},
// 重置按钮
resetBtnText: {
type: String,
default: ''
},
// 取消按钮
cancelBtnText: {
type: String,
default: ''
},
// 是否显示标签
isShowLabel: {
type: Boolean,
default: true
},
// 标签宽度
labelWidth: {
type: [Number, String],
default: 'auto'
},
// 全局禁用表单
disabled: {
type: Boolean,
default: false
},
// 全局的readonly
readonly: {
type: Boolean,
default: false
},
// 是否可清空
clearable: {
type: Boolean,
default: true
},
// 是否为弹窗
isDialog: {
type: Boolean,
default: false
},
// 弹窗变量控制
visible: {
type: Boolean,
default: false
},
// options 的请求方法
optionsFn: {
type: Function,
default: function() { }
},
// 表单项顺序数组
// 数组项为formDesc中的key
order: {
type: Array,
default: () => []
},
// 是否显示错误后的 notify
isShowErrorNotify: {
type: Boolean,
default: true
},
// 一些钩子
beforeValidate: {
type: Function,
default: () => { }
},
beforeRequest: {
type: Function,
default: () => { }
},
requestSuccess: {
type: Function,
default: () => { }
},
requestError: {
type: Function,
default: () => { }
},
requestEnd: {
type: Function,
default: () => { }
}
},
data() {
return {
formDescData: {},
oldFormData: {},
// 是否正在请求中
innerIsLoading: false,
// 内部请求出错
innerFormError: {}
}
},
computed: {
isMock() {
return (
this.mock || Object.values(this.formDescData).some(item => item.mock)
)
},
// 按钮
btns() {
const formBtnSize = this.formBtnSize
let btns = []
// 模拟数据
if (this.isMock) {
btns.push({
attrs: {
type: 'primary',
size: formBtnSize
},
text: t('ele-form.mockBtnText'),
click: this.reMockData
})
}
// 提交按钮
if (this.isShowSubmitBtn) {
btns.push({
attrs: {
type: 'primary',
size: formBtnSize,
loading: this.isLoading || this.innerIsLoading,
'native-type': 'submit'
},
text: this.computedSubmitBtnText,
click() { }
})
}
// 自定义按钮
if (this.formBtns) {
const customBtns = this.formBtns.map(btn => ({
attrs: {
type: btn.type,
size: formBtnSize
},
text: btn.text,
click: btn.click
}))
btns = [...btns, ...customBtns]
}
// 返回按钮
if (this.computedIsShowBackBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.backBtnText || t('ele-form.backBtnText'),
click: this.goBack
})
}
// 取消按钮
if (this.computedIsShowCancelBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.cancelBtnText || t('ele-form.cancelBtnText'),
click: this.handleCancelClick
})
}
// 重置按钮
if (this.isShowResetBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.resetBtnText || t('ele-form.resetBtnText'),
click: this.resetForm
})
}
return btns
},
computedIsShowCancelBtn() {
if (is(this.isShowCancelBtn, 'Boolean')) {
// 如果指定了, 则使用指定的值
return this.isShowCancelBtn
} else {
// 如果未指定, 根据 isDialog
return this.isDialog
}
},
// 是否显示返回按钮(inline和layout模式下不同)
computedIsShowBackBtn() {
if (is(this.isShowBackBtn, 'Boolean')) {
return this.isShowBackBtn
} else {
return !(this.inline || this.isDialog)
}
},
// 提交按钮默认值(inline和layout模式下不同)
computedSubmitBtnText() {
if (is(this.submitBtnText, 'String')) {
return this.submitBtnText
} else {
return this.inline
? t('ele-form.submitBtnTextInline')
: t('ele-form.submitBtnText')
}
},
// 标签宽度(数字和字符串两种处理)
computedLabelWidth() {
if (isNumber(this.labelWidth)) {
return this.labelWidth + 'px'
} else {
return this.labelWidth
}
},
// 表单错误信息
formErrorObj() {
return Object.assign({}, this.innerFormError, this.formError)
},
// 校检规则 (支持局部定义和全局定义)
// 即: rules: { rules: { a: [xxx, xxx], b:{ xxx } } } 和 formDesc: { name: { rules: {xxx} }, age: { rules: [xxx] } }
// 此函数即将局部定义转为全局定义
computedRules() {
return this.formDescKeys.reduce((rules, field) => {
// 合并 (全局 和 局部) 的rules
const formRules = castArray(this.rules[field])
const formItemRules = castArray(this.formDescData[field].rules)
rules[field] = [...formRules, ...formItemRules]
// 为每个规则的validator绑定当前this,方便取得this.formData的值
rules[field].forEach(item => {
if (item && typeof item.validator === 'function') {
item.validator = item.validator.bind(this)
}
})
// 如果采用required, 则判断已有的规则有无, 如果没有, 则添加
if (
this.formDescData[field].required &&
!rules[field].some(rule => rule.required)
) {
rules[field].push({
required: true,
message: this.formDescData[field]._label + t('ele-form.required')
})
}
return rules
}, {})
},
// formDesc的key
formDescKeys() {
return Object.keys(this.formDescData)
},
// 通过order数组排序后的formDesc
orderedFormDesc() {
if (this.order && this.order.length > 0) {
const orderedFormDesc = {}
// 根据order遍历,先添加到orderedFormDesc的key在之后遍历的时候,会先遍历,从而实现排序的目的。
this.order.forEach(field => {
if (this.formDescData[field]) {
orderedFormDesc[field] = this.formDescData[field]
} else {
throw new Error('order中定义的key在formDesc中不存在')
}
})
// 如果key不在order数组的时候,按照原序添加到orderedFormDesc
Object.keys(this.formDescData).forEach(field => {
// 当key不在order数组的时候
if (!orderedFormDesc[field]) {
orderedFormDesc[field] = this.formDescData[field]
}
})
return orderedFormDesc
} else {
return this.formDescData
}
}
},
watch: {
disabled(val) {
if (val) {
this.$refs.form.clearValidate()
}
},
// 同步数据
formDesc: {
handler(formDesc) {
const oldFormDescData = {}
// 去除被删除字段
Object.keys(this.formDescData)
.filter(key => formDesc[key])
.forEach(key => {
oldFormDescData[key] = this.formDescData[key]
})
this.formDescData = Object.assign(
{},
oldFormDescData,
cloneDeep(formDesc)
)
},
immediate: true,
deep: true
},
formDescData: {
handler(desc) {
if (desc) {
Object.keys(desc).forEach(field => {
// 当全局设置 mock 为 true 时, 所有子项都标记为 true
if (this.mock && isUnDef(desc[field].mock)) {
desc[field].mock = true
}
// 设置默认值
this.setDefaultvalue(desc[field], field)
// 转换 tip, 内部属性不显示
if (desc[field].tip) {
desc[field]._tip = String(desc[field].tip).replace(
/`(.+?)`/g,
'<code>$1</code>'
)
}
// layout值, 内部属性不显示
desc[field]._colAttrs = this.getColAttrs(desc[field].layout)
// 老数据, 用于options切换不同类型和type切换不懂类型时, 保留旧数据
// 例如 原type为 switch, 后改为 input, 出现类型和值不兼容情况, 就需要保留原数据
if (!desc[field]._oldValue) {
desc[field]._oldValue = {}
}
this.setVif(desc[field], field)
if (desc[field]._vif) {
// 设置 options
this.changeOptions(desc[field].options, field)
}
})
// 检查联动
this.checkLinkage()
}
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate()
})
},
immediate: true
},
formErrorObj(obj) {
// 后端异常的弹窗警告
if (obj) {
this.processError(obj)
}
},
formData() {
this.checkLinkage()
}
},
mounted() {
if (this.isMock && !window.Mock) {
loadMockJs()
}
},
methods: {
getValue(field) {
return this.formData[field]
},
handleChange(field, val) {
this.oldFormData = cloneDeep(this.formData)
if (this.formDescData[field].type === 'lov' && Object.prototype.toString.call(val) === '[object Object]') {
Object.keys(val).forEach(key => {
this.$set(this.formData, key, val[key])
})
} else {
this.$set(this.formData, field, val)
}
this.$emit('input', this.formData)
},
setValue(field, val) {
this.handleChange(field, val)
this.checkLinkage()
},
// 获取col的属性(是否为inline模式)
getColAttrs(layout) {
return this.inline ? { span: layout || 6 } : { md: layout || 24, xs: 24 }
},
// 重新模拟数据
reMockData() {
this.formDescKeys.forEach(field => {
this.$refs[field][0].mockData()
})
},
// 当类型为函数时的请求
getFunctionAttr(fn, field) {
return fn(this.formData, this.formDescData[field], this.formDescData)
},
// 获取动态属性
getDynamicAttr(attr, field) {
return typeof attr === 'function'
? this.getFunctionAttr(attr, field)
: attr
},
// 检测联动
checkLinkage() {
if (this.checkVifFn) {
this.checkLinkageFn()
} else {
this.checkLinkageFn = throttle(300, () => {
const formDescData = this.formDescData
const formData = this.formData
Object.keys(formDescData).forEach(field => {
const formItem = formDescData[field]
// 1.设置 type
let type = formItem.type
if (typeof formItem.type === 'function') {
type = this.getComponentName(
this.getFunctionAttr(formItem.type, field)
)
if (formItem._type && formItem._type !== type) {
// 获取此类型的以前值
const newVal = formItem._oldValue['type-' + type] || null
// 保存现在的数据作为老数据
this.formDescData[field]._oldValue['type-' + formItem._type] =
formData[field]
// 类型改变, 则删除原数据
this.handleChange(field, newVal)
this.setDefaultvalue(this.formDescData[field], field)
}
} else {
type = this.getComponentName(formItem.type)
}
// 2.触发 v-if 显示 / 隐藏
this.setVif(formItem, field)
// 3.触发 disabled 禁用 / 启用
let disabled = null
if (typeof formItem.disabled === 'function') {
disabled = this.getFunctionAttr(formItem.disabled, field)
} else if (typeof formItem.disabled === 'boolean') {
disabled = formItem.disabled
}
// 4.动态属性
let attrs = this.getDynamicAttr(formItem.attrs, field)
var defAttrs = { clearable: this.clearable }
if (formItem.type === 'select') {
defAttrs = { clearable: this.clearable, filterable: true }
} else if (formItem.type === 'date') {
defAttrs = { clearable: this.clearable, filterable: true, valueFormat: 'yyyy-MM-ddTHH:mm:ss' }
}
attrs = Object.assign(defAttrs, attrs)
// 5.动态 label
const label = this.getDynamicAttr(formItem.label, field)
// 6.动态 prop
const prop = this.getDynamicAttr(formItem.prop, field)
// 7.动态 optionsLinkageFields
const optionsLinkageFields = castArray(
this.getDynamicAttr(formItem.optionsLinkageFields, field)
)
this.$set(formItem, '_type', type)
this.$set(formItem, '_disabled', disabled)
this.$set(formItem, '_attrs', attrs)
this.$set(formItem, '_label', label)
this.$set(formItem, '_prop', prop)
this.$set(formItem, '_optionsLinkageFields', optionsLinkageFields)
// 4.重新获取 options
if (formItem._vif) {
this.changeOptions(formItem.options || formItem._options, field)
}
})
})
this.checkLinkageFn()
}
},
setVif(formItem, field) {
let vif = true
if (typeof formItem.vif === 'function') {
vif = Boolean(this.getFunctionAttr(formItem.vif, field))
if (!vif) {
// 如果隐藏, 则使用其默认值
this.handleChange(field, formItem._defaultValue)
}
} else if (typeof formItem.vif === 'boolean') {
vif = formItem.vif
}
this.$set(formItem, '_vif', vif)
},
// 设置默认值
setDefaultvalue(formItem, field) {
let defaultValue =
typeof formItem.default === 'function'
? formItem.default(this.formData)
: formItem.default
// 默认值不为空 & (值为空 || 老值和当前值)
if (!isEmpty(defaultValue) && isEmpty(this.formData[field])) {
// 判断是否有格式化函数
if (formItem.displayFormatter) {
defaultValue = formItem.displayFormatter(defaultValue, this.formData)
}
this.handleChange(field, defaultValue)
}
this.$set(formItem, '_defaultValue', defaultValue)
},
// 组件名称
getComponentName(type) {
if (this.$EleFormBuiltInNames.includes(type)) {
// 内置组件
return 'ele-form-' + type
} else {
// 外部组件
return type
}
},
// 转对象的key
// 例如 option: { label: '女', val: 1 }, prop: { text: 'label', value: 'val' }
// 转换后 -> option: { text: '女', value: 1 }
changeProp(options, prop) {
if (prop) {
return options.map(option => ({
text: option[prop.text || 'text'],
value: option[prop.value || 'value']
}))
} else {
return options
}
},
// 将options转为对象数组
getObjArrOptions(options) {
return options.map(option => {
if (is(option, ['Number', 'String', 'Boolean'])) {
// 例如 ['男', '女'] => [ { text: '男', value: '男' }, { text: '女', value: '女' } ]
return {
text: option,
value: option
}
} else {
// 对象 直接返回
return option
}
})
},
shouldRequest(field) {
const formItem = this.formDescData[field]
// 如果 _options 不存在,则代表第一次进入,需要请求
if (!formItem._options) return true
// 如果关联字段不存在,则直接返回 false
if (!formItem._optionsLinkageFields.length) {
return false
}
// 判断关联字段的值有无更新,有不同的,则更新
return formItem._optionsLinkageFields.some(
field => this.formData[field] !== this.oldFormData[field]
)
},
// 将四种类型: 字符串数组, 对象数组, Promise对象和函数统一为 对象数组
changeOptions(options, field) {
if (options) {
if (options instanceof Array) {
// 当options为数组时: 直接获取
this.setOptions(options, field)
} else if (options instanceof Function) {
// 当options为Promise时: 等待Promise结束, 并获取值
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
const res = this.getFunctionAttr(options, field)
if (res instanceof Promise) {
this.formDescData[field]._isLoadingOptions = true
}
// 当options为函数: 执行函数并递归
this.changeOptions(res, field)
} else if (options instanceof Promise) {
options.then(options => {
this.formDescData[field]._isLoadingOptions = false
this.changeOptions(options, field)
})
} else if (options.toString() === '[object Object]') {
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
this.formDescData[field]._isLoadingOptions = true
this.changeOptions(fetchDictionary(options), field)
} else if (typeof options === 'string' && this.optionsFn) {
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
this.formDescData[field]._isLoadingOptions = true
// options为url地址
this.changeOptions(this.optionsFn(options), field)
} else {
if (typeof options === 'string') {
throw new TypeError(
`options值为: ${options}, 为字符串, 但是未配置options-fn参数, 具体请参考: https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav#ZVYtf`
)
} else {
// 其他报错
throw new TypeError(
'options的类型不正确, options及options请求结果类型可为: 字符串数组, 对象数组, 函数和Promise或者URL地址, 当前值为: ' +
options +
', 不属于以上四种类型, 具体请参考: https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav'
)
}
}
} else {
if (this.formDescData[field]._options) {
// 如果new options为null / undefined, 且 old Options 存在, 则设置
this.setOptions([], field)
}
}
},
// 设置options
setOptions(options, field) {
const formItem = this.formDescData[field]
const prop = formItem._prop
// 将options每一项转为对象
let newOptions = this.getObjArrOptions(options)
const oldOptionsValues = (formItem._options || [])
.map(item => item.value)
.join(',')
// 改变prop为规定的prop
newOptions = this.changeProp(newOptions, prop)
const newOptionsValues = newOptions.map(item => item.value).join(',')
this.$set(this.formDescData[field], '_options', newOptions)
// 新 options 和老 options 不同时,触发值的改变
if (formItem.isRestValByOptions !== false) {
if (oldOptionsValues && newOptionsValues !== oldOptionsValues) {
this.setValue(field, null)
}
}
},
// 验证表单
validateForm() {
return new Promise((resolve, reject) => {
if (this.computedRules) {
// 当传递了验证规则
this.$refs.form.validate((valid, invalidFields) => {
if (valid) {
// 验证通过
resolve()
} else {
// 显示错误
reject(invalidFields)
}
})
} else {
resolve()
}
})
},
// 验证所有组件的内置验证方法
validateComponent() {
const validators = this.formDescKeys
.map(key =>
this.$refs[key] && this.$refs[key][0]
? this.$refs[key][0].validate
: null
)
.filter(Boolean)
return Promise.all(validators.map(fn => fn()))
},
// 处理错误
processError(errObj) {
if (!this.isShowErrorNotify) return
try {
const messageArr = Object.keys(errObj).reduce((acc, key) => {
const formItem = this.formDescData[key]
const label =
formItem && formItem._label ? formItem._label + ': ' : key + ': '
if (errObj[key] instanceof Array) {
// errorObj: { name: [ { filed: 'name', message: 'name is required' }] }
// 内部校检结果返回的错误信息样式
errObj[key].forEach(item => {
acc.push(label + item.message)
})
} else {
// errorObj: { name: 'name is required' }
// 外部校检返回的错误信息
acc.push(label + errObj[key])
}
return acc
}, [])
this.showError(messageArr)
// eslint-disable-next-line
} catch { }
},
// 显示错误
showError(messageArr) {
if (messageArr.length) {
this.$message({
message: messageArr[0],
type: 'warning'
})
// const h = this.$createElement
// messageArr = messageArr.map(msg => {
// return h('div', { style: { marginBottom: '8px' } }, msg)
// })
// this.$notify.error({
// title: t('ele-form.formError'),
// message: h('div', { style: { marginTop: '12px' } }, messageArr)
// })
}
},
// 验证表单
async validate() {
try {
await this.validateForm()
await this.validateComponent()
} catch (error) {
console.log(error)
this.processError(error)
await Promise.reject(error)
}
},
// 提交表单
async handleSubmitForm() {
try {
// 自定义处理
this.$emit('before-validate', this.formData)
if (this.beforeValidate) {
const isPass = await this.beforeValidate(this.formData)
if (isPass === false) return
}
await this.validate()
// 为了不影响原值, 这里进行 clone
let data = cloneDeep(this.formData)
// valueFormatter的处理
for (const field in data) {
const formItem = this.formDescData[field]
if (formItem && formItem.valueFormatter) {
data[field] = formItem.valueFormatter(data[field], data)
}
}
this.$emit('before-request', data)
if (this.beforeRequest) {
const beforeRequestData = this.beforeRequest(data)
if (beforeRequestData === false) return
if (typeof beforeRequestData === 'object') {
data = beforeRequestData
}
}
if (this.requestFn) {
// 在内部请求
if (this.innerIsLoading) return
this.innerIsLoading = true
try {
const response = await this.requestFn(data)
this.$nextTick(() => {
this.$emit('request-success', response)
if (this.requestSuccess) {
this.requestSuccess(response)
}
})
} catch (error) {
if (this.requestError) {
this.requestError(error)
}
console.error(error)
// 处理异常情况
if (error instanceof Error) {
// 返回的是Error类型, 则进行解析
try {
const msg = JSON.parse(error.message)
if (msg instanceof Object) {
this.innerFormError = msg
}
// eslint-disable-next-line
} catch { }
} else if (error instanceof Object) {
// 返回的是对象类型, 则直接设置
this.innerFormError = error
}
this.$emit('request-error')
} finally {
this.innerIsLoading = false
if (this.requestEnd) {
this.requestEnd()
}
this.$emit('request-end')
}
} else {
// 在外部请求
if (this.isLoading) return
this.$emit('request', data)
}
} catch (error) {
return this.processError(error)
}
},
// 返回按钮
goBack() {
this.$emit('back')
if (this.$router) {
// vue-router
this.$router.back()
} else {
// 浏览器history API
history.back()
}
},
// 点击取消按钮
handleCancelClick() {
this.$emit('close')
this.$emit('cancel')
this.$emit('update:visible', false)
},
// 重置表单
resetForm() {
this.$emit('reset')
this.$refs.form.resetFields()
// 调用内部方法进行值的重置
this.$refs.form.fields.forEach(field => {
this.formData[field.prop] = field.initialValue
})
}
}
}
</script>
<style>
.ele-form--inline .ele-form-btns {
width: auto;
}
.ele-form-col--break {
clear: both;
}
.ele-form-tip {
color: #909399;
line-height: 1.5em;
margin-top: 3px;
}
.ele-form-tip code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
}
.ele-form-full-line.el-date-editor.el-input,
.ele-form-full-line.el-date-editor .el-input__inner,
.ele-form-full-line.el-date-editor--daterange.el-input__inner,
.ele-form-full-line.el-date-editor--datetimerange.el-input__inner,
.ele-form-full-line.el-date-editor--timerange.el-input__inner,
.ele-form-full-line.el-date-editor--monthrange.el-input__inner,
.ele-form-full-line.el-cascader,
.ele-form-full-line.el-select,
.ele-form-full-line.el-autocomplete {
width: 100%;
}
.el-radio-group {
line-height: inherit;
border: 0.5px solid transparent;
}
</style>
<template>
<div
ref="wrapper"
class="ele-form"
:class="{ 'ele-form--inline': inline }"
>
<el-row
justify="center"
type="flex"
>
<el-col :span="computedSpan">
<el-form
ref="form"
:label-position="computedLabelPosition"
:label-width="computedLabelWidth"
:model="formData"
:rules="computedRules"
:validate-on-rule-change="false"
:disabled="disabled"
v-bind="formAttrs"
@submit.native.prevent="handleSubmitForm"
>
<!-- 默认插槽作为表单项 -->
<slot />
<el-row :gutter="20">
<slot
:formData="formData"
:formDesc="orderedFormDesc"
:formErrorObj="formErrorObj"
:props="$props"
name="form-content"
>
<template v-for="(formItem, field) of orderedFormDesc">
<slot
:name="field + '-wrapper'"
:data="formData[field]"
:desc="formItem"
:field="field"
:props="$props"
:formData="formData"
:disabled="formItem._disabled"
:type="formItem._type"
:options="formItem._options"
>
<el-col
v-if="formItem._vif"
:key="field"
v-bind="formItem._colAttrs"
:class="{ 'ele-form-col--break': formItem.break }"
>
<el-form-item
:error="formErrorObj ? formErrorObj[field] : null"
:label="
isShowLabel && formItem.isShowLabel !== false
? (formItem._label ? formItem._label + ':' : null)
: null
"
:label-width="formItem.labelWidth || null"
:prop="field"
>
<!-- 具名 作用域插槽(用于用户自定义显示) -->
<slot
:data="formData[field]"
:desc="formItem"
:props="$props"
:field="field"
:formData="formData"
:name="field"
:disabled="formItem._disabled"
:type="formItem._type"
:options="formItem._options"
>
<component
:is="formItem._type"
:ref="field"
:disabled="formItem._disabled"
:readonly="readonly"
:desc="formItem"
:options="formItem._options"
:field="field"
:form-data="formData"
:value="formData[field]"
@input="setValue(field, $event)"
/>
</slot>
<div
v-if="formItem._tip"
class="ele-form-tip"
v-html="formItem._tip"
/>
</el-form-item>
</el-col>
</slot>
</template>
</slot>
<slot name="form-footer" />
<!-- 操作按钮区 -->
<el-col
v-if="btns.length"
class="ele-form-btns"
>
<el-form-item :label-width="inline ? '10px' : null">
<!-- 按钮插槽 -->
<slot
:btns="btns"
name="form-btn"
>
<el-button
v-for="(btn, index) of btns"
:key="index"
v-bind="btn.attrs"
@click="btn.click"
>{{ btn.text }}</el-button>
</slot>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script>
import responsiveMixin from './mixins/responsiveMixin'
import { isUnDef, is, castArray, isEmpty } from './tools/utils'
import { throttle } from 'throttle-debounce'
import localeMixin from './mixins/locale'
import { t } from './locale'
import { loadMockJs } from './tools/mock'
import fetchDictionary from '@/utils/fetch-dictionary' // 请求枚举值方法
const isNumber = require('is-number')
const cloneDeep = require('clone')
export default {
name: 'EleForm',
// 响应式单独抽离出来作为mixin, 具体实现请到 responsiveMixin 中查看
mixins: [responsiveMixin, localeMixin],
model: {
prop: 'formData',
event: 'input'
},
provide() {
return {
EleForm: this
}
},
props: {
// 表单描述
formDesc: {
type: Object,
required: true
},
// 表单数据
formData: {
type: Object,
required: true
},
// 行内模式
inline: {
type: Boolean,
default: false
},
// 表单自身属性
formAttrs: {
type: Object,
default: () => { }
},
// 校检规则
rules: {
type: Object,
default() {
return {}
}
},
// 模拟数据
mock: {
type: Boolean,
default: false
},
// 提交状态
isLoading: {
type: Boolean,
default: false
},
// 表单错误信息
formError: {
type: Object,
default: () => { }
},
// 提交函数
requestFn: {
type: Function,
default: () => { }
},
// 自定义表单按钮
formBtns: {
type: Array,
default: () => []
},
// 表单按钮大小
formBtnSize: {
type: String,
default: ''
},
// 是否显示submit按钮
isShowSubmitBtn: {
type: Boolean,
default: true
},
// 是否显示 cancel 取消按钮
// 默认值: isDialog = true 时, 默认值为 true, 具体查看: computedIsShowCancelBtn
isShowCancelBtn: {
type: Boolean,
default: null
},
// 是否显示back按钮
// 默认值: 当 inline = true OR isDialog = true, 默认值为 false; 其它情况true. 具体请看计算属性: computedIsShowBackBtn
isShowBackBtn: {
type: Boolean,
default: null
},
// 是否显示reset按钮
isShowResetBtn: {
type: Boolean,
default: false
},
// 提交按钮文本
// 默认值: 当 inline 为true时, 值为 '查询'; inline 为 false 时, 值为 '提交'. 具体请看计算属性: computedSubmitBtnText
submitBtnText: {
type: String,
default: null
},
// 返回按钮
backBtnText: {
type: String,
default: ''
},
// 重置按钮
resetBtnText: {
type: String,
default: ''
},
// 取消按钮
cancelBtnText: {
type: String,
default: ''
},
// 是否显示标签
isShowLabel: {
type: Boolean,
default: true
},
// 标签宽度
labelWidth: {
type: [Number, String],
default: 'auto'
},
// 全局禁用表单
disabled: {
type: Boolean,
default: false
},
// 全局的readonly
readonly: {
type: Boolean,
default: false
},
// 是否可清空
clearable: {
type: Boolean,
default: true
},
// 是否为弹窗
isDialog: {
type: Boolean,
default: false
},
// 弹窗变量控制
visible: {
type: Boolean,
default: false
},
// options 的请求方法
optionsFn: {
type: Function,
default: function() { }
},
// 表单项顺序数组
// 数组项为formDesc中的key
order: {
type: Array,
default: () => []
},
// 是否显示错误后的 notify
isShowErrorNotify: {
type: Boolean,
default: true
},
// 一些钩子
beforeValidate: {
type: Function,
default: () => { }
},
beforeRequest: {
type: Function,
default: () => { }
},
requestSuccess: {
type: Function,
default: () => { }
},
requestError: {
type: Function,
default: () => { }
},
requestEnd: {
type: Function,
default: () => { }
}
},
data() {
return {
formDescData: {},
oldFormData: {},
// 是否正在请求中
innerIsLoading: false,
// 内部请求出错
innerFormError: {}
}
},
computed: {
isMock() {
return (
this.mock || Object.values(this.formDescData).some(item => item.mock)
)
},
// 按钮
btns() {
const formBtnSize = this.formBtnSize
let btns = []
// 模拟数据
if (this.isMock) {
btns.push({
attrs: {
type: 'primary',
size: formBtnSize
},
text: t('ele-form.mockBtnText'),
click: this.reMockData
})
}
// 提交按钮
if (this.isShowSubmitBtn) {
btns.push({
attrs: {
type: 'primary',
size: formBtnSize,
loading: this.isLoading || this.innerIsLoading,
'native-type': 'submit'
},
text: this.computedSubmitBtnText,
click() { }
})
}
// 自定义按钮
if (this.formBtns) {
const customBtns = this.formBtns.map(btn => ({
attrs: {
type: btn.type,
size: formBtnSize
},
text: btn.text,
click: btn.click
}))
btns = [...btns, ...customBtns]
}
// 返回按钮
if (this.computedIsShowBackBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.backBtnText || t('ele-form.backBtnText'),
click: this.goBack
})
}
// 取消按钮
if (this.computedIsShowCancelBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.cancelBtnText || t('ele-form.cancelBtnText'),
click: this.handleCancelClick
})
}
// 重置按钮
if (this.isShowResetBtn) {
btns.push({
attrs: {
size: formBtnSize
},
text: this.resetBtnText || t('ele-form.resetBtnText'),
click: this.resetForm
})
}
return btns
},
computedIsShowCancelBtn() {
if (is(this.isShowCancelBtn, 'Boolean')) {
// 如果指定了, 则使用指定的值
return this.isShowCancelBtn
} else {
// 如果未指定, 根据 isDialog
return this.isDialog
}
},
// 是否显示返回按钮(inline和layout模式下不同)
computedIsShowBackBtn() {
if (is(this.isShowBackBtn, 'Boolean')) {
return this.isShowBackBtn
} else {
return !(this.inline || this.isDialog)
}
},
// 提交按钮默认值(inline和layout模式下不同)
computedSubmitBtnText() {
if (is(this.submitBtnText, 'String')) {
return this.submitBtnText
} else {
return this.inline
? t('ele-form.submitBtnTextInline')
: t('ele-form.submitBtnText')
}
},
// 标签宽度(数字和字符串两种处理)
computedLabelWidth() {
if (isNumber(this.labelWidth)) {
return this.labelWidth + 'px'
} else {
return this.labelWidth
}
},
// 表单错误信息
formErrorObj() {
return Object.assign({}, this.innerFormError, this.formError)
},
// 校检规则 (支持局部定义和全局定义)
// 即: rules: { rules: { a: [xxx, xxx], b:{ xxx } } } 和 formDesc: { name: { rules: {xxx} }, age: { rules: [xxx] } }
// 此函数即将局部定义转为全局定义
computedRules() {
return this.formDescKeys.reduce((rules, field) => {
// 合并 (全局 和 局部) 的rules
const formRules = castArray(this.rules[field])
const formItemRules = castArray(this.formDescData[field].rules)
rules[field] = [...formRules, ...formItemRules]
// 为每个规则的validator绑定当前this,方便取得this.formData的值
rules[field].forEach(item => {
if (item && typeof item.validator === 'function') {
item.validator = item.validator.bind(this)
}
})
// 如果采用required, 则判断已有的规则有无, 如果没有, 则添加
if (
this.formDescData[field].required &&
!rules[field].some(rule => rule.required)
) {
rules[field].push({
required: true,
message: this.formDescData[field]._label + t('ele-form.required')
})
}
return rules
}, {})
},
// formDesc的key
formDescKeys() {
return Object.keys(this.formDescData)
},
// 通过order数组排序后的formDesc
orderedFormDesc() {
if (this.order && this.order.length > 0) {
const orderedFormDesc = {}
// 根据order遍历,先添加到orderedFormDesc的key在之后遍历的时候,会先遍历,从而实现排序的目的。
this.order.forEach(field => {
if (this.formDescData[field]) {
orderedFormDesc[field] = this.formDescData[field]
} else {
throw new Error('order中定义的key在formDesc中不存在')
}
})
// 如果key不在order数组的时候,按照原序添加到orderedFormDesc
Object.keys(this.formDescData).forEach(field => {
// 当key不在order数组的时候
if (!orderedFormDesc[field]) {
orderedFormDesc[field] = this.formDescData[field]
}
})
return orderedFormDesc
} else {
return this.formDescData
}
}
},
watch: {
disabled(val) {
if (val) {
this.$refs.form.clearValidate()
}
},
// 同步数据
formDesc: {
handler(formDesc) {
const oldFormDescData = {}
// 去除被删除字段
Object.keys(this.formDescData)
.filter(key => formDesc[key])
.forEach(key => {
oldFormDescData[key] = this.formDescData[key]
})
this.formDescData = Object.assign(
{},
oldFormDescData,
cloneDeep(formDesc)
)
},
immediate: true,
deep: true
},
formDescData: {
handler(desc) {
if (desc) {
Object.keys(desc).forEach(field => {
// 当全局设置 mock 为 true 时, 所有子项都标记为 true
if (this.mock && isUnDef(desc[field].mock)) {
desc[field].mock = true
}
// 设置默认值
this.setDefaultvalue(desc[field], field)
// 转换 tip, 内部属性不显示
if (desc[field].tip) {
desc[field]._tip = String(desc[field].tip).replace(
/`(.+?)`/g,
'<code>$1</code>'
)
}
// layout值, 内部属性不显示
desc[field]._colAttrs = this.getColAttrs(desc[field].layout)
// 老数据, 用于options切换不同类型和type切换不懂类型时, 保留旧数据
// 例如 原type为 switch, 后改为 input, 出现类型和值不兼容情况, 就需要保留原数据
if (!desc[field]._oldValue) {
desc[field]._oldValue = {}
}
this.setVif(desc[field], field)
if (desc[field]._vif) {
// 设置 options
this.changeOptions(desc[field].options, field)
}
})
// 检查联动
this.checkLinkage()
}
this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate()
})
},
immediate: true
},
formErrorObj(obj) {
// 后端异常的弹窗警告
if (obj) {
this.processError(obj)
}
},
formData() {
this.checkLinkage()
}
},
mounted() {
if (this.isMock && !window.Mock) {
loadMockJs()
}
},
methods: {
getValue(field) {
return this.formData[field]
},
handleChange(field, val) {
this.oldFormData = cloneDeep(this.formData)
if (this.formDescData[field].type === 'lov' && Object.prototype.toString.call(val) === '[object Object]') {
Object.keys(val).forEach(key => {
this.$set(this.formData, key, val[key])
})
} else if (this.formDescData[field].type === 'select' && Object.prototype.toString.call(val) === '[object Object]') {
this.$set(this.formData, field, val.value)
var text = this.formDescData[field].value
if (text) {
this.$set(this.formData, text, val.text)
}
} else {
this.$set(this.formData, field, val)
}
this.$emit('input', this.formData)
},
setValue(field, val) {
this.handleChange(field, val)
this.checkLinkage()
},
// 获取col的属性(是否为inline模式)
getColAttrs(layout) {
return this.inline ? { span: layout || 6 } : { md: layout || 24, xs: 24 }
},
// 重新模拟数据
reMockData() {
this.formDescKeys.forEach(field => {
this.$refs[field][0].mockData()
})
},
// 当类型为函数时的请求
getFunctionAttr(fn, field) {
return fn(this.formData, this.formDescData[field], this.formDescData)
},
// 获取动态属性
getDynamicAttr(attr, field) {
return typeof attr === 'function'
? this.getFunctionAttr(attr, field)
: attr
},
// 检测联动
checkLinkage() {
if (this.checkVifFn) {
this.checkLinkageFn()
} else {
this.checkLinkageFn = throttle(300, () => {
const formDescData = this.formDescData
const formData = this.formData
Object.keys(formDescData).forEach(field => {
const formItem = formDescData[field]
// 1.设置 type
let type = formItem.type
if (typeof formItem.type === 'function') {
type = this.getComponentName(
this.getFunctionAttr(formItem.type, field)
)
if (formItem._type && formItem._type !== type) {
// 获取此类型的以前值
const newVal = formItem._oldValue['type-' + type] || null
// 保存现在的数据作为老数据
this.formDescData[field]._oldValue['type-' + formItem._type] =
formData[field]
const val = {
value: newVal
}
// 类型改变, 则删除原数据
this.handleChange(field, val)
this.setDefaultvalue(this.formDescData[field], field)
}
} else {
type = this.getComponentName(formItem.type)
}
// 2.触发 v-if 显示 / 隐藏
this.setVif(formItem, field)
// 3.触发 disabled 禁用 / 启用
let disabled = null
if (typeof formItem.disabled === 'function') {
disabled = this.getFunctionAttr(formItem.disabled, field)
} else if (typeof formItem.disabled === 'boolean') {
disabled = formItem.disabled
}
// 4.动态属性
let attrs = this.getDynamicAttr(formItem.attrs, field)
var defAttrs = { clearable: this.clearable }
if (formItem.type === 'select') {
defAttrs = { clearable: this.clearable, filterable: true }
} else if (formItem.type === 'date') {
defAttrs = { clearable: this.clearable, filterable: true, valueFormat: 'yyyy-MM-ddTHH:mm:ss' }
}
attrs = Object.assign(defAttrs, attrs)
// 5.动态 label
const label = this.getDynamicAttr(formItem.label, field)
// 6.动态 prop
const prop = this.getDynamicAttr(formItem.prop, field)
// 7.动态 optionsLinkageFields
const optionsLinkageFields = castArray(
this.getDynamicAttr(formItem.optionsLinkageFields, field)
)
this.$set(formItem, '_type', type)
this.$set(formItem, '_disabled', disabled)
this.$set(formItem, '_attrs', attrs)
this.$set(formItem, '_label', label)
this.$set(formItem, '_prop', prop)
this.$set(formItem, '_optionsLinkageFields', optionsLinkageFields)
// 4.重新获取 options
if (formItem._vif) {
this.changeOptions(formItem.options || formItem._options, field)
}
})
})
this.checkLinkageFn()
}
},
setVif(formItem, field) {
let vif = true
if (typeof formItem.vif === 'function') {
vif = Boolean(this.getFunctionAttr(formItem.vif, field))
if (!vif) {
// 如果隐藏, 则使用其默认值
this.handleChange(field, formItem._defaultValue)
}
} else if (typeof formItem.vif === 'boolean') {
vif = formItem.vif
}
this.$set(formItem, '_vif', vif)
},
// 设置默认值
setDefaultvalue(formItem, field) {
let defaultValue =
typeof formItem.default === 'function'
? formItem.default(this.formData)
: formItem.default
// 默认值不为空 & (值为空 || 老值和当前值)
if (!isEmpty(defaultValue) && isEmpty(this.formData[field])) {
// 判断是否有格式化函数
if (formItem.displayFormatter) {
defaultValue = formItem.displayFormatter(defaultValue, this.formData)
}
this.handleChange(field, defaultValue)
}
this.$set(formItem, '_defaultValue', defaultValue)
},
// 组件名称
getComponentName(type) {
if (this.$EleFormBuiltInNames.includes(type)) {
// 内置组件
return 'ele-form-' + type
} else {
// 外部组件
return type
}
},
// 转对象的key
// 例如 option: { label: '女', val: 1 }, prop: { text: 'label', value: 'val' }
// 转换后 -> option: { text: '女', value: 1 }
changeProp(options, prop) {
if (prop) {
return options.map(option => ({
text: option[prop.text || 'text'],
value: option[prop.value || 'value']
}))
} else {
return options
}
},
// 将options转为对象数组
getObjArrOptions(options) {
return options.map(option => {
if (is(option, ['Number', 'String', 'Boolean'])) {
// 例如 ['男', '女'] => [ { text: '男', value: '男' }, { text: '女', value: '女' } ]
return {
text: option,
value: option
}
} else {
// 对象 直接返回
return option
}
})
},
shouldRequest(field) {
const formItem = this.formDescData[field]
// 如果 _options 不存在,则代表第一次进入,需要请求
if (!formItem._options) return true
// 如果关联字段不存在,则直接返回 false
if (!formItem._optionsLinkageFields.length) {
return false
}
// 判断关联字段的值有无更新,有不同的,则更新
return formItem._optionsLinkageFields.some(
field => this.formData[field] !== this.oldFormData[field]
)
},
// 将四种类型: 字符串数组, 对象数组, Promise对象和函数统一为 对象数组
changeOptions(options, field) {
if (options) {
if (options instanceof Array) {
// 当options为数组时: 直接获取
this.setOptions(options, field)
} else if (options instanceof Function) {
// 当options为Promise时: 等待Promise结束, 并获取值
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
const res = this.getFunctionAttr(options, field)
if (res instanceof Promise) {
this.formDescData[field]._isLoadingOptions = true
}
// 当options为函数: 执行函数并递归
this.changeOptions(res, field)
} else if (options instanceof Promise) {
options.then(options => {
this.formDescData[field]._isLoadingOptions = false
this.changeOptions(options, field)
})
} else if (options.toString() === '[object Object]') {
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
this.formDescData[field]._isLoadingOptions = true
this.changeOptions(fetchDictionary(options), field)
} else if (typeof options === 'string' && this.optionsFn) {
if (this.formDescData[field]._isLoadingOptions) return
if (!this.shouldRequest(field)) return
this.formDescData[field]._isLoadingOptions = true
// options为url地址
this.changeOptions(this.optionsFn(options), field)
} else {
if (typeof options === 'string') {
throw new TypeError(
`options值为: ${options}, 为字符串, 但是未配置options-fn参数, 具体请参考: https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav#ZVYtf`
)
} else {
// 其他报错
throw new TypeError(
'options的类型不正确, options及options请求结果类型可为: 字符串数组, 对象数组, 函数和Promise或者URL地址, 当前值为: ' +
options +
', 不属于以上四种类型, 具体请参考: https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav'
)
}
}
} else {
if (this.formDescData[field]._options) {
// 如果new options为null / undefined, 且 old Options 存在, 则设置
this.setOptions([], field)
}
}
},
// 设置options
setOptions(options, field) {
const formItem = this.formDescData[field]
const prop = formItem._prop
// 将options每一项转为对象
let newOptions = this.getObjArrOptions(options)
const oldOptionsValues = (formItem._options || [])
.map(item => item.value)
.join(',')
// 改变prop为规定的prop
newOptions = this.changeProp(newOptions, prop)
const newOptionsValues = newOptions.map(item => item.value).join(',')
this.$set(this.formDescData[field], '_options', newOptions)
// 新 options 和老 options 不同时,触发值的改变
if (formItem.isRestValByOptions !== false) {
if (oldOptionsValues && newOptionsValues !== oldOptionsValues) {
this.setValue(field, null)
}
}
},
// 验证表单
validateForm() {
return new Promise((resolve, reject) => {
if (this.computedRules) {
// 当传递了验证规则
this.$refs.form.validate((valid, invalidFields) => {
if (valid) {
// 验证通过
resolve()
} else {
// 显示错误
reject(invalidFields)
}
})
} else {
resolve()
}
})
},
// 验证所有组件的内置验证方法
validateComponent() {
const validators = this.formDescKeys
.map(key =>
this.$refs[key] && this.$refs[key][0]
? this.$refs[key][0].validate
: null
)
.filter(Boolean)
return Promise.all(validators.map(fn => fn()))
},
// 处理错误
processError(errObj) {
if (!this.isShowErrorNotify) return
try {
const messageArr = Object.keys(errObj).reduce((acc, key) => {
const formItem = this.formDescData[key]
const label =
formItem && formItem._label ? formItem._label + ': ' : key + ': '
if (errObj[key] instanceof Array) {
// errorObj: { name: [ { filed: 'name', message: 'name is required' }] }
// 内部校检结果返回的错误信息样式
errObj[key].forEach(item => {
acc.push(label + item.message)
})
} else {
// errorObj: { name: 'name is required' }
// 外部校检返回的错误信息
acc.push(label + errObj[key])
}
return acc
}, [])
this.showError(messageArr)
// eslint-disable-next-line
} catch { }
},
// 显示错误
showError(messageArr) {
if (messageArr.length) {
this.$message({
message: messageArr[0],
type: 'warning'
})
// const h = this.$createElement
// messageArr = messageArr.map(msg => {
// return h('div', { style: { marginBottom: '8px' } }, msg)
// })
// this.$notify.error({
// title: t('ele-form.formError'),
// message: h('div', { style: { marginTop: '12px' } }, messageArr)
// })
}
},
// 验证表单
async validate() {
try {
await this.validateForm()
await this.validateComponent()
} catch (error) {
console.log(error)
this.processError(error)
await Promise.reject(error)
}
},
// 提交表单
async handleSubmitForm() {
try {
// 自定义处理
this.$emit('before-validate', this.formData)
if (this.beforeValidate) {
const isPass = await this.beforeValidate(this.formData)
if (isPass === false) return
}
await this.validate()
// 为了不影响原值, 这里进行 clone
let data = cloneDeep(this.formData)
// valueFormatter的处理
for (const field in data) {
const formItem = this.formDescData[field]
if (formItem && formItem.valueFormatter) {
data[field] = formItem.valueFormatter(data[field], data)
}
}
this.$emit('before-request', data)
if (this.beforeRequest) {
const beforeRequestData = this.beforeRequest(data)
if (beforeRequestData === false) return
if (typeof beforeRequestData === 'object') {
data = beforeRequestData
}
}
if (this.requestFn) {
// 在内部请求
if (this.innerIsLoading) return
this.innerIsLoading = true
try {
const response = await this.requestFn(data)
this.$nextTick(() => {
this.$emit('request-success', response)
if (this.requestSuccess) {
this.requestSuccess(response)
}
})
} catch (error) {
if (this.requestError) {
this.requestError(error)
}
console.error(error)
// 处理异常情况
if (error instanceof Error) {
// 返回的是Error类型, 则进行解析
try {
const msg = JSON.parse(error.message)
if (msg instanceof Object) {
this.innerFormError = msg
}
// eslint-disable-next-line
} catch { }
} else if (error instanceof Object) {
// 返回的是对象类型, 则直接设置
this.innerFormError = error
}
this.$emit('request-error')
} finally {
this.innerIsLoading = false
if (this.requestEnd) {
this.requestEnd()
}
this.$emit('request-end')
}
} else {
// 在外部请求
if (this.isLoading) return
this.$emit('request', data)
}
} catch (error) {
return this.processError(error)
}
},
// 返回按钮
goBack() {
this.$emit('back')
if (this.$router) {
// vue-router
this.$router.back()
} else {
// 浏览器history API
history.back()
}
},
// 点击取消按钮
handleCancelClick() {
this.$emit('close')
this.$emit('cancel')
this.$emit('update:visible', false)
},
// 重置表单
resetForm() {
this.$emit('reset')
this.$refs.form.resetFields()
// 调用内部方法进行值的重置
this.$refs.form.fields.forEach(field => {
this.formData[field.prop] = field.initialValue
})
}
}
}
</script>
<style>
.ele-form--inline .ele-form-btns {
width: auto;
}
.ele-form-col--break {
clear: both;
}
.ele-form-tip {
color: #909399;
line-height: 1.5em;
margin-top: 3px;
}
.ele-form-tip code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
}
.ele-form-full-line.el-date-editor.el-input,
.ele-form-full-line.el-date-editor .el-input__inner,
.ele-form-full-line.el-date-editor--daterange.el-input__inner,
.ele-form-full-line.el-date-editor--datetimerange.el-input__inner,
.ele-form-full-line.el-date-editor--timerange.el-input__inner,
.ele-form-full-line.el-date-editor--monthrange.el-input__inner,
.ele-form-full-line.el-cascader,
.ele-form-full-line.el-select,
.ele-form-full-line.el-autocomplete {
width: 100%;
}
.el-radio-group {
line-height: inherit;
border: 0.5px solid transparent;
}
</style>
......@@ -68,30 +68,30 @@ export default {
},
methods: {
// 变化处理
// handleChange(value) {
// var result = {}
// if (!this.attrs.multiple) {
// this.options.forEach(item => {
// if (item.value === value) {
// result = item.value
// }
// })
// } else {
// result = {
// value: [],
// text: []
// }
// this.options.forEach(item => {
// value.forEach(jtem => {
// if (item.value === jtem) {
// result.value.push(item.value)
// result.text.push(item.text)
// }
// })
// })
// }
// this.$emit('input', result)
// },
handleChange(value) {
var result = {}
if (!this.attrs.multiple) {
this.options.forEach(item => {
if (item.value === value) {
result = item
}
})
} else {
result = {
value: [],
text: []
}
this.options.forEach(item => {
value.forEach(jtem => {
if (item.value === jtem) {
result.value.push(item.value)
result.text.push(item.text)
}
})
})
}
this.$emit('input', result)
},
changeOptions(q) {
if (this.remoteMethod) {
this.loading = true
......
......@@ -98,7 +98,7 @@ const actions = {
var permissions = []
var paths = []
treeToArray(menus, permissions, paths)
console.log(paths)
// console.log(paths)
localStorage.setItem('PERMISSIONS', JSON.stringify(permissions))
localStorage.setItem('PATHS', JSON.stringify(paths))
commit('SET_PERMISSIONS', permissions)
......
......@@ -76,7 +76,7 @@ export default {
type: 'input',
label: '社会统一信用代码',
layout: 12,
disabled: this.displayBtn
disabled: true
},
ExtRegisteredCapital_SDK: {
type: 'input',
......@@ -105,6 +105,10 @@ export default {
type: 'select',
label: '省份',
layout: 12,
value: 'ExtProvinceName_SDK',
attrs: {
multiple: false
},
options: async data => {
const res = await provinceSearch({})
var result = res.results.map(item => {
......@@ -122,6 +126,10 @@ export default {
label: '地市',
layout: 12,
isOptions: true,
value: 'ExtCityName_SDK',
attrs: {
multiple: false
},
optionsLinkageFields: ['ExtProvince_SDK'],
options: async data => {
if (!data.ExtProvince_SDK) {
......@@ -145,6 +153,10 @@ export default {
label: '县市',
layout: 12,
isOptions: true,
attrs: {
multiple: false
},
value: 'ExtDistrictName_SDK',
optionsLinkageFields: ['ExtProvince_SDK', 'ExtCity_SDK'],
options: async data => {
if (!data.ExtCity_SDK) {
......@@ -240,8 +252,8 @@ export default {
// ExtRegisteredCapital_SDK: {required: true, message: '注册资本必填' },
ExtCorporateName_SDK: { required: true, message: '法人必填' },
ExtProvince_SDK: { required: true, message: '省份必填' },
ExtDistrict_SDK: {required: true, message: '城市必填' },
ExtCity_SDK: { required: true, message: '县区必填' },
ExtCity_SDK: {required: true, message: '地市必填' },
ExtDistrict_SDK: { required: true, message: '县区必填' },
ExtLeader_SDK: { required: true, message: '公司负责人姓名' },
ExtLeaderPhone_SDK: { required: true, message: '公司负责人联系方式' },
ExtLeaderEmail_SDK: { required: true, message: '公司负责人邮箱' },
......@@ -265,7 +277,7 @@ export default {
methods: {
handleSubmit (data) {
const formData = this.$translateToC4CData(data)
formData.extCustomerType_SDK = '121'
console.log(formData)
this.loading = true
customerCreate(this.paramsToFormData(formData)).then(res => {
this.addBtnStart = true
......
......@@ -23,7 +23,7 @@
</el-button>
</div>
<div>
<PersonalInFo :addBtnStart='addBtnStart' :dedeleBtnStart="dedeleBtnStart" :type-code="typeCode" :isShowBtn='isShowBtn' :isShowEditBtn="isShowEditBtn"/>
<PersonalInFo :addBtnStart='addBtnStart' :dedeleBtnStart="dedeleBtnStart" :type-code="typeCode" :isShowBtn='isShowBtn' :isShowEditBtn="isShowEditBtn" @showAuthentication="showStart"/>
</div>
<!-- <PersonalInFo :addBtnStart='addBtnStart' :dedeleBtnStart="dedeleBtnStart" :type-code="typeCode" :isShowEditBtn='btn'/> -->
</div>
......@@ -63,7 +63,7 @@ export default {
formData: {},
isShowSubmitBtn: false,
isShowBackBtn: false,
showAuthentication: false,
showAuthentication: true,
sections: [
{
title: '基本信息',
......@@ -300,18 +300,19 @@ export default {
rules: {}
}
},
// watch: {
// 'formData.ExtSocialUnifiedCreditCode_SDK': {
// handler(newValue) {
// console.log("单个属性监听", newValue)
// qccGetOne({searchKey: newValue}).then(res =>{
// this.sections[0].formDesc.ExtRegisteredCapital_SDK.default = res.results.Result.RegistCapi
// console.log(this.sections[0].formDesc.ExtRegisteredCapital_SDK.default)
// })
// }
// }
// },
watch: {
// 'formData.ExtSocialUnifiedCreditCode_SDK': {
// handler(newValue) {
// console.log("单个属性监听", newValue)
// qccGetOne({searchKey: newValue}).then(res =>{
// this.sections[0].formDesc.ExtRegisteredCapital_SDK.default = res.results.Result.RegistCapi
// console.log(this.sections[0].formDesc.ExtRegisteredCapital_SDK.default)
// })
// }
// }
},
created() {
console.log(this.$refs)
this.getOneData()
},
methods: {
......@@ -331,6 +332,9 @@ export default {
});
return formData;
},
showStart(val) {
this.showAuthentication = val
},
getOneData() {
this.loading = true
const dataId = this.$route.query.objectID
......
......@@ -143,6 +143,7 @@ export default {
}
},
created() {
console.log(this.$refs.eleTable)
this.tableConfig.columns.handle.vif = this.isShowBtn
constant.tableConfig.initialParams = {
BusinessObjectID: this.objectID || this.$route.query.objectID,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment