<template>
<el-form
ref="elform"
:model="formData"
:inline="inline"
:label-width="formLabelWidth"
:size="size"
v-bind="$attrs"
:rules="rules"
v-on="$listeners"
@submit.native.prevent
>
<!-- {{ notebookAndModuleInfo }} -->
<!-- {{ formWidth }} -->
<!-- {{ autoLayout }}
{{ isAutoComputedColumns }}
{{ Object.keys(formView).length }}
{{ columns }}
{{ itemcolumns }} -->
<!-- {{ checkboxMultiple }} -->
<!-- {{ formView }} -->
<!-- {{ formData }} -->
<!-- {{ rules }} -->
<!-- {{ proxyMethods }} -->
<!-- {{ tableRowIndex }} -->
<!-- {{ groupCodeOptions }} -->
<draggable
:group="dragGroup"
animation="300"
:scroll="true"
@end="dragEnd"
ghostClass="ghost"
:disabled="!draggable"
:class="[layoutClass, hiddenLabelClass, labelPosition ? `el-form--label-${labelPosition}` : '', dragFormClass]"
:style="styleObj"
>
<!-- :label="item.itemlabel" -->
<template v-for="(item, key) in formView">
<el-form-item
v-if="
item.showInEditForm ||
(typeof item.hidden === 'function'
? !item.hidden({
item,
formView,
formData,
editTableRowIndex,
tableRowIndex
})
: !item.hidden &&
!hiddenKeys.includes(item.prop || key) &&
!hiddenTypes.includes(item.itemtype) &&
item?.itemAttr != 'hidden')
"
v-show="(item.showInEditForm || item.show) ?? true"
:key="item.key || key"
:prop="item.prop || key"
:show-message="item.showMessage"
:item-key="key"
:label-width="item.labelWidth"
:label="$t(item.itemlabel) || item.itemlabel"
:readonly="item.readonly"
:class="[
{
selectedFormItem: selectedKey === key,
hiddenLabelClass: item.hiddenLabel,
itemIsDisabled:
typeof item.itemDisabled === 'function'
? item.itemDisabled({
item,
formView,
formData,
editTableRowIndex,
tableRowIndex
})
: item.itemDisabled,
contentOverHidden: ['Table'].includes(item.itemtype) ? true : false
},
item.itemClassName
]"
@click.native.stop="
handleClickFormItem({
key,
item,
formView,
formData,
editTableRowIndex,
tableRowIndex
})
"
:style="[setStyle(item, key), item.itemStyle]"
>
<!-- TODO注意key和prop,要考虑具体绑定的值和验证的值在一些场景是否会区分 -->
<!--TODO需要插件语法支持,默认为 :clearable="item.clearable ?? true" -->
<!-- <template slot="label" v-if="!item.hiddenLabel">
{{ item.itemlabel }}
</template> -->
<template #label v-if="item.customFormLabel">
<div>
<span>{{ item.itemlabel }}</span>
<el-tooltip :content="item.formLabelContent" placement="top">
<i class="el-icon-question" style="color: #e6a23c; margin-left: 2px" v-if="item.formLabelContent"></i>
</el-tooltip>
</div>
</template>
<span slot="label" v-if="item.clickable">
<span :class="item.clickable ? 'clickable' : ''">{{ $t(item.i18nkey || item.itemlabel) || ' ' }}</span>
</span>
<!-- ReadonlyInput类型仅仅用来展示,需要配合bindkey一起使用 -->
<el-input
v-if="item.itemtype === 'ReadonlyInput'"
v-model="formData[item.bindkey]"
v-bind="item"
v-on="item"
:placeholder="$t(item.placeholder || `${$t('请输入')} ${$t(item.label || item.itemlabel || '')}`)"
:title="formData[item.bindkey] ?? ''"
>
</el-input>
<el-input
v-else-if="item.itemtype === 'Input'"
:maxlength="item.verify && !item?.needMaxLength ? false : item.maxlength || 255"
:show-word-limit="item.showWordLimit === false || !item.maxlength ? false : true"
ref="formInput"
:id="item?.ref || 'formInput'"
v-model="formData[key]"
v-bind="item"
v-on="item"
v-verify="item.verify"
:clearable="item.clearable ?? true"
:readonly="item?.readonly || item?.isReadonlyInputView"
@clear="handleClear"
@click.native="clickHandler(key, item)"
@keyup.enter.native="keyupEnterHandler(key, item)"
:placeholder="
$t(
item.placeholder ||
`${showPlaceholderPrefix ? $t('请输入') : ''} ${$t(item.label || item.itemlabel || '')}`
)
"
:title="item.itemlabel && !item.itemlabel.includes('密码') && formData[key] ? formData[key] : ''"
:class="item.showWordLimit === false || !item.maxlength ? '' : 'showWordLimitInput'"
>
<component :is="item.prefix" slot="prefix" v-if="item.prefix"></component>
<component :is="item.suffix" slot="suffix" v-if="item.suffix"></component>
<component
:is="item.prepend"
slot="prepend"
v-if="item.prepend"
v-bind="item.prependAttrs"
v-on="item.prependListeners"
></component>
<component :is="item.append" slot="append" v-if="item.append"></component>
<template v-if="item.child">
{{ typeof item.child === 'function' ? item.child({ key, formData, itemConfig: item }) : item.child }}
</template>
<template slot="append" v-if="item.appendTemplate">
{{
typeof item.appendTemplate === 'function'
? item.appendTemplate({ key, formData, itemConfig: item })
: item.appendTemplate
}}
</template>
</el-input>
<el-input
v-else-if="item.itemtype === 'Password'"
v-model="formData[key]"
type="password"
autocomplete="new-password"
:maxlength="item.verify ? false : item.maxlength || 255"
:show-word-limit="item.showWordLimit === false || !item.maxlength ? false : true"
:placeholder="$t(item.placeholder || `${$t('请输入')} ${$t(item.label || item.itemlabel || '')}`)"
>
<!-- <i style="cursor: pointer; line-height: 30px" slot="suffix" :class="iconClass" @click="showPwd(key)"></i>-->
</el-input>
<toggle-password v-else-if="item.itemtype === 'TogglePassword'" v-model="formData[key]" v-bind="item">
</toggle-password>
<el-button
v-else-if="item.itemtype === 'Button'"
v-bind="item"
:menuCode="item.menuCode"
:disabled="
typeof item.disabled === 'function'
? item.disabled({
item,
formView,
formData,
editTableRowIndex,
tableRowIndex
})
: item.disabled
"
@click="item.click({ key, tableRowIndex, tableColIndex, formData })"
>
{{
typeof item.buttonText === 'function'
? item.buttonText({ key, tableRowIndex, tableColIndex, formData })
: $t(item.buttonText)
}}
</el-button>
<template v-else-if="item.itemtype === 'NativeFile'">
<!-- <input type="file" @change="item.change && item.change($event)" class="nativeFile" /> -->
<input
ref="nativeFile"
type="file"
id="nativeFile"
style="display: none"
@change="nativeFileChange(item, $event)"
/>
<input ref="nativeFileName" id="nativeFileName" readonly />
<input
type="button"
:value="$t('选择文件')"
style="border-radius: 2px; padding: 0 4px; margin-left: 6px"
@click="nativeFileClick"
/>
</template>
<template v-else-if="item.itemtype === 'Autocomplete'">
<el-autocomplete
v-bind="item"
v-on="item"
v-model="formData[key]"
:title="formData[key]"
@select="changeAutoComplete()"
>
<template slot-scope="{ item }">
<span>{{ item.label || item.value }}</span>
</template>
</el-autocomplete>
</template>
<template v-else-if="item.itemtype === 'CustomAutocomplete'">
<customAutocomplete
:formData="formData"
:itemkey="key"
:formItem="item"
v-model="formData[key]"
:title="formData[key]"
>
</customAutocomplete>
</template>
<el-input
v-else-if="item.itemtype === 'SelectInput'"
v-model="formData[key]"
v-bind="item"
v-on="item"
v-verify="item.verify"
clearable
@clear="handleClear"
:placeholder="$t(item.placeholder || `${$t('请输入')} ${$t(item.label || item.itemlabel || '')}`)"
:title="formData[key]"
>
<!-- ReadonlyInput类型仅仅用来展示,需要配合bindkey一起使用 -->
<template v-if="item.selectConfig.itemtype === 'ReadonlyInput'">
<span :slot="item.selectConfig.slot || 'append'">{{ formData[item.selectConfig.bindkey] }}</span>
</template>
<template v-else>
<el-select
v-if="item.selectConfig"
v-model="formData[item.selectConfig.prop]"
:slot="item.selectConfig.slot || 'append'"
:style="[{ minWidth: '80px' }, item.selectConfig.itemStyle]"
:placeholder="$t('请选择')"
v-bind="item.selectConfig"
@clear="handleClear(item, key)"
@change="handleChangeSelectInput(item.selectConfig, item.selectConfig.prop)"
>
<el-option
v-for="(selitem, index) in item.selectConfig.groupCode
? groupCodeOptions[item.selectConfig.groupCode]
: item.selectConfig.options"
:key="index"
:label="selitem.label"
:value="selitem[item.selectConfig.bindValueField || 'value' || 'label']"
:disabled="selitem.disabled"
@click.native="handleSelect(selitem, key, item)"
></el-option>
</el-select>
</template>
</el-input>
<template v-else-if="item.itemtype === 'Select'">
<div
v-if="item.multiple"
v-selectloadmore="item.infiniteScrollMethods ? item.infiniteScrollMethods : ''"
style="width: 100%; display: grid"
>
<el-select
v-model="checkboxMultiple[key]"
v-bind="item"
v-on="item"
:collapse-tags="item.collapseTags ? true : false"
ref="SelectMultiple"
:placeholder="$t(item.placeholder || `${$t('请选择')} ${$t(item.label || item.itemlabel || '')}`)"
:clearable="item.clearable ?? true"
:filterable="item.filterable ?? true"
:title="renderTitle('select', { key, item })"
@clear="handleClear(item, key)"
>
<li
class="el-select-dropdown__item"
:class="{
selected: selmultiAll(key, 2),
optionBtnType: item.optionBtnType
}"
style="user-select: none"
v-if="item.multiple"
@click="selmultiAll(key, 1, item, item.groupCode ? groupCodeOptions[item.groupCode] : item.options)"
>
<span>{{ selmultiAll(key, 2) ? $t('全不选') : $t('全选') }}</span>
</li>
<template v-if="item.slotTitle">
<li v-html="item.slotTitle" class="el-select-dropdown__item" style="user-select: none"></li>
</template>
<el-option
v-for="(selitem, index) in item.groupCode ? groupCodeOptions[item.groupCode] : item.options"
:key="selitem.value + index"
:label="selitem.label"
:value="selitem[item.bindValueField || 'value' || 'label']"
:disabled="selitem.disabled ?? (selitem.optionDisabled && selitem.optionDisabled(selitem))"
@click.native="
selectOptionsRow(
key,
selitem,
item,
item.groupCode ? groupCodeOptions[item.groupCode] : item.options
)
"
>
<div v-if="item.slotHtml" v-html="item.slotHtml(selitem)"></div>
</el-option>
</el-select>
</div>
<div
v-else
v-selectloadmore="item.infiniteScrollMethods ? item.infiniteScrollMethods : ''"
style="width: 100%; display: grid"
>
<el-select
@focus="handleFocus(item, key)"
:ref="'selectSingle' + key"
v-model="formData[key]"
v-bind="item"
v-on="item"
:placeholder="$t(item.placeholder || `${$t('请选择')} ${$t(item.label || item.itemlabel || '')}`)"
:clearable="item.clearable ?? true"
:filterable="item.filterable ?? true"
:title="renderTitle('select', { key, item })"
@clear="handleClear(item, key)"
:bindKey="key"
@filter-change="handleFilterChange"
@visible-change="item.shouldInput ? selectFocus(key) : item.visibleChange"
>
<template v-if="item.slotTitle">
<li v-html="item.slotTitle" class="el-select-dropdown__item" style="user-select: none"></li>
</template>
<template v-if="item.customOption">
<li
:style="item.customOption.style || ''"
@click="item.customOption.click && item.customOption.click($refs['selectSingle' + key][0])"
>
<span> {{ item.customOption.label }} </span>
</li>
<el-option
v-if="
typeof item.options === 'function'
? item.options(key, formData, formView) && item.options(key, formData, formView).length === 0
: item.options && item.options.length === 0
"
style="height: 0"
:value="''"
@click.native="selectOptionsRow(key, selitem, item)"
></el-option>
</template>
<el-option
v-for="(selitem, index) in typeof item.options === 'function'
? item.options(key, formData, formView)
: item.groupCode
? groupCodeOptions[item.groupCode]
: item.options"
:key="key + index"
:optionData="selitem"
:filterVal="selectFilterObj[key]"
:customFilterMethod="item.customFilterMethod"
:label="selitem[item.bindLabelField || 'label' || 'value']"
:value="selitem[item.bindValueField || 'value' || 'label']"
:disabled="selitem.disabled"
@click.native="selectOptionsRow(key, selitem, item)"
>
<span v-if="selitem.html" v-html="selitem.html"></span>
<div v-if="item.slotHtml" v-html="item.slotHtml(selitem)"></div>
</el-option>
</el-select>
</div>
</template>
<!-- todo支持多选和下拉多选返回的值的类型,数组或者逗号隔开的字符串 -->
<template v-else-if="item.itemtype === 'Checkbox'">
<span
v-if="item?.class === 'custom_check_box' || item?.class === 'custom_label'"
:style="{ 'font-size': '12px', color: formData[key] ? 'red' : '' }"
>{{ formData[key] ? $t('是') : $t('否') }}</span
>
<el-checkbox
v-else
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
:disabled="
typeof item.disabled === 'function'
? item.disabled({
item,
formView,
formData,
editTableRowIndex,
tableRowIndex
})
: item.disabled
"
>
{{ item.label }}
</el-checkbox>
</template>
<el-checkbox-group
v-else-if="item.itemtype === 'CheckboxGroup'"
v-model="checkboxMultiple[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
>
<template v-if="checkboxMultiple[key]">
<el-checkbox
v-for="(checkitem, checkboxindex) in item.options"
:label="checkitem.value"
:disabled="checkitem.disabled"
:key="checkboxindex"
>
{{ checkitem.label }}
</el-checkbox>
</template>
</el-checkbox-group>
<el-radio-group
v-else-if="item.itemtype === 'Radio'"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
>
<el-radio
v-for="(radioItem, index) in getSelectOptions(item, item.params)"
:label="radioItem.value"
:key="index"
>
{{ $t(radioItem.label) }}
</el-radio>
</el-radio-group>
<el-switch
v-else-if="item.itemtype === 'Switch'"
v-model="formData[key]"
v-bind="item"
style="width: min-content"
v-on="item"
:title="formData[key]"
></el-switch>
<el-date-picker
v-else-if="item.itemtype === 'DatePicker'"
:type="item.type || 'date'"
:value-format="item.format || 'yyyy-MM-dd'"
:placeholder="$t(item.placeholder) || $t('请选择日期')"
:start-placeholder="$t('开始日期')"
:end-placeholder="$t('结束日期')"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
:picker-options="item.pickerOptions ?? datePickerOptions"
@clear="handleClear"
></el-date-picker>
<el-date-picker
v-else-if="item.itemtype === 'DateTimePicker'"
:type="item.type || 'datetime'"
:value-format="item.format || 'yyyy-MM-dd'"
:placeholder="item.placeholder || $t('请选择日期')"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
:picker-options="item.pickerOptions ?? datePickerOptions"
@clear="handleClear"
></el-date-picker>
<el-time-picker
v-else-if="item.itemtype === 'TimePicker'"
:placeholder="item.placeholder || $t('请选择时间')"
:value-format="item.format || 'HH:mm:ss'"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
@clear="handleClear"
></el-time-picker>
<el-button-group v-else-if="item.itemtype === 'ButtonGroup'">
<el-button
v-for="(buttonItem, buttonIndex) in item.options"
@click="formData[key] = buttonItem.value"
:type="formData[key] == buttonItem.value ? item.checkedType || 'primary' : buttonItem.type"
:key="buttonIndex"
:title="formData[key]"
>
{{ buttonItem.label }}
</el-button>
</el-button-group>
<el-dropdown v-else-if="item.itemtype === 'Dropdown'" v-bind="item" v-on="item">
<span class="el-dropdown-link"> {{ item.text }}<i class="el-icon-arrow-down el-icon--right"></i> </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="dropdownItem in item.options" :key="dropdownItem.label || dropdownItem.value">
{{ dropdownItem.label || dropdownItem.value }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-input
v-else-if="item.itemtype === 'Textarea'"
type="textarea"
show-word-limit
:rows="item.rows || 3"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
:placeholder="$t(item.placeholder || `${$t('请输入')} ${$t(item.label || item.itemlabel || '')}`)"
/>
<el-input-number
v-else-if="item.itemtype === 'InputNumber' && !item.isNativeAction"
v-model="formData[key]"
v-bind="item"
v-on="item"
:title="formData[key]"
@clear="handleClear"
:max="item.max || 1000000000"
></el-input-number>
<el-input-number
v-else-if="item.itemtype === 'InputNumber' && item.isNativeAction"
v-model="formData[key]"
v-bind="item"
:title="formData[key]"
@input.native="item.input"
:max="item.max || 1000000000"
@clear="handleClear"
></el-input-number>
<el-divider v-else-if="item.itemtype === 'Divider'" v-bind="item"
><span v-html="item.content"></span
></el-divider>
<selectTree
v-else-if="item.itemtype === 'SelectTree'"
v-bind="item"
v-on="item"
:formData="formData"
v-model="formData[key]"
:data="item.groupCode ? getSelectOptions(item) : item.data"
></selectTree>
<el-color-picker
v-else-if="item.itemtype === 'ColorPicker'"
v-bind="item"
v-on="item"
v-model="formData[key]"
></el-color-picker>
<!-- <el-upload v-else-if="item.itemtype === 'Upload'" class="my-el-upload" v-bind="item" v-on="item">
<span style="cursor:pointer" class="el-icon-upload2"></span>
<div slot="tip" class="el-upload__tip" v-if="item.tip">{{ item.tip }}</div>
</el-upload> -->
<custom-file
v-else-if="item.itemtype === 'CustomFile'"
v-bind="item"
v-on="item"
v-model="formData[key]"
></custom-file>
<upload-file
v-else-if="item.itemtype === 'UploadFile'"
v-bind="item"
v-on="item"
v-model="formData[key]"
></upload-file>
<page-select
v-else-if="item.itemtype === 'PageSelect'"
style="width: 100%"
v-bind="item"
v-on="item"
:modelval.sync="formData[key]"
:formData="formData"
></page-select>
<mingdu-form
v-else-if="item.itemtype === 'SlotForm'"
class="slotform"
:form-data="item.formData ? formData[item.formData] : formData"
:form-view="item.formView"
v-bind="item.slotFormConfig"
ref="SlotForm"
></mingdu-form>
<template v-else-if="item.itemtype === 'Table'">
<mingdu-table v-bind="item" :data="formData[key]" v-on="item" :ref="key + 'Mdtable'"></mingdu-table>
</template>
<!-- 配置选项 -->
<template v-else-if="item.itemtype === 'ConfigOptions'">
<config-options v-model="formData[key]"></config-options>
</template>
<template v-else-if="item.itemtype === 'TableConfigOptions'">
<table-config-options v-model="formData[key]"></table-config-options>
</template>
<template v-else-if="item.itemtype === 'CustomElement'">
<slot
v-if="item.slotName"
:name="item.slotName"
:form-item="item"
:form-data="formData"
:prop-name="key"
></slot>
<div
v-else-if="item.html"
v-html="
typeof item.html === 'function'
? item.html({
formItem: item,
formView,
formData,
propName: key
})
: item.html
"
></div>
<component
:formItem="item"
:formView="formView"
:formData="formData"
:propName="key"
v-on="item.emitMethods"
v-bind="item"
:is="item.component"
v-else-if="item.component"
></component>
<component
:scope="{ formItem: item, formView, formData, propName: key }"
v-bind="item"
:is="
typeof item.render === 'function'
? handleRendel(item.render, {
formItem: item,
formView,
formData,
propName: key
})
: item.render
"
v-else-if="item.render"
></component>
</template>
<el-input
class="dialogSelect"
v-else-if="item.itemtype === 'DialogSelect'"
v-model="formData[`${key}_label`]"
v-bind="item"
v-on="item"
@focus="openSelectDialog(item, key)"
@input="inputDialogSelect($event, item, key)"
id="dialogSelectInput"
:placeholder="$t(item.placeholder || `${$t('请选择')} ${$t(item.label || item.itemlabel || '')}`)"
>
<el-button
slot="append"
icon="el-icon-circle-close"
v-if="!item.required"
@click="dialogSelectClear(item, key)"
></el-button>
<el-button
slot="append"
icon="el-icon-folder-opened"
v-if="item.shouldInput"
@click="openSelectDialog(item, key, 'click')"
></el-button>
</el-input>
<!-- 仅显示文本 -->
<span v-else-if="item.itemtype === 'Text'" :class="item.class || ''">{{
item.formatter && typeof item.formatter === 'function'
? item.formatter(formData[key], {
formItem: item,
formView,
formData,
propName: key
})
: formData[key]
}}</span>
<span v-else>{{ formData[key] }}</span>
<el-button
v-if="selectedKey === key"
@click.stop="deleteSelectedKeyEvent(key)"
class="deleteSelectedKey"
type="danger"
icon="el-icon-delete"
></el-button>
</el-form-item>
</template>
<slot name="rightContent"></slot>
<!-- <el-button type="primary" style="width: 100px" v-if="formKey" @click="$saveFormView(formView, formKey)"
>保存表单配置</el-button
> -->
</draggable>
<!-- 选择用户 -->
<SelectUser
ref="refSelectUser"
:defaultCheckedList="defaultCheckedUserList"
@on-select="handleSelectUser"
></SelectUser>
<!-- 库位选择-->
<location-select ref="locationSelect" @locationInfo="getLocationInfo"></location-select>
</el-form>
</template>
<script>
import { formItemRules } from './js/form-rules.js'
import mixin from '../mixins/commonUi.js'
import draggable from 'vuedraggable'
import ConfigOptions from './basicComponent/config-options.vue'
import TableConfigOptions from './basicComponent/table-config-options.vue'
import { cloneDeep } from 'lodash'
import selectTree from './basicComponent/selectTree'
import CustomFile from '@/components/customFileUpload/index.vue'
import uploadFile from '@/components/uploadFile'
import PageSelect from './basicComponent/pageSelect.vue'
import customAutocomplete from './basicComponent/myCustomAutocomplete.vue'
import togglePassword from './basicComponent/myPassword.vue'
// import { createRulesBasicPage } from '@/util/validate';
import { optionsConfigs } from '@/common/getBasicDate.js'
import { nanoid } from 'nanoid'
import i18n from '@/lang'
import { mapGetters } from 'vuex'
function validateEmpty(message = '') {
return function (rule, value, callback) {
// console.log('validate Empty= >', value)
typeof value === 'string' && value !== '' && value.trim() === ''
? callback(message || i18n.t('请勿全部输入空格'))
: callback()
}
}
const validatorRange = function ({ type = 'text', min = 0, max = 0, data = {}, required = false }) {
const { itemtype, value, options } = data
// 判断表单项是否必填
if (required === false && (value === '' || value === undefined)) {
return { success: true }
}
if (itemtype === 'Select') {
// const option = (options ?? []).find((item) => item.value === value)
// const len = `${option?.label ?? ''}`.length
// if (len < min || len > max) {
// const message = `请选择长度【${min}~${max}】的数据`
// return { success: false, message }
// } else {
// return { success: true }
// }
return { success: true }
} else if (itemtype === 'Input' || itemtype === 'SelectInput') {
if (type === 'text') {
const len = `${value ?? ''}`.length
if (len >= min && len <= max) {
return { success: true }
} else {
const message = `请输入长度【${min}~${max}】的字符`
return { success: false, message }
}
} else if (type === 'number') {
const nValue = Number(value)
if (isNaN(nValue)) {
return { success: false, message: i18n.t('请输入正确的数字') }
} else if (value !== '' && value !== '0' && /^[0]+$/.test(nValue)) {
return { success: false, message: i18n.t('请勿输入全为0的数字') }
} else if (nValue >= min && nValue <= max) {
return { success: true }
} else {
const message = i18n.t('请输入范围【{0}~{1}】的{2}', [min, max, '数字'])
return { success: false, message }
}
}
} else {
return { success: true }
}
}
export default {
inheritAttrs: false,
name: 'MdForm',
mixins: [mixin],
components: {
draggable,
ConfigOptions,
TableConfigOptions,
selectTree,
CustomFile,
PageSelect,
uploadFile,
customAutocomplete,
SelectUser: () => import('@/components/selectUser'),
locationSelect: () => import('@/components/locationSelect'),
togglePassword
},
props: {
/**
* 表单绑定的值
*@param {paraName}
*/
formData: {
type: Object,
default: () => ({})
},
// 用于查询条件时,去掉值为undefined null 和空字符串的数据
searchData: {
type: Object,
default: () => ({})
},
// 表单是否可拖动
draggable: {
type: Boolean,
default: false
},
// 拖动时的group名
dragGroup: {
type: String,
default: ''
},
formKey: {
type: String,
default: ''
},
isUpdateCustomLabel: {
type: Boolean,
default: true
},
formView: {
type: Object,
default: () => ({})
},
inline: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
// label的宽度
labelWidth: {
type: [String, Number],
default: '104px'
},
layoutClass: {
// 可维护多种备选布局方式
type: String,
default: 'default-layout'
},
labelPosition: {
// left right top
type: String,
default: 'right'
},
formColumns: {
// 默认共12列,
type: Number,
default: 12
},
columns: {
// 默认每个表单组件所占的列数,
type: Number,
default: 12
},
updateClearValidate: {
// 默认更新view会校验表单
type: Boolean,
default: true
},
isAutoComputedColumns: {
// 是否自动计算columns
type: Boolean,
default: true
},
// 是否自适应布局
autoLayout: {
type: Boolean,
default: false
},
formItemMinWidth: {
// form-item的最小宽度
type: Number,
default: 0
},
selectedKey: {
type: [Number, String],
default: ''
},
// 设置表单可拖动时的样式名
dragFormClass: {
type: String,
default: ''
},
// 隐藏表单的所有label
hiddenLabel: {
type: Boolean,
default: false
},
// 隐藏某些 form-item
hiddenKeys: {
type: Array,
default: () => []
},
// 隐藏某些类型的表单
hiddenTypes: {
type: Array,
default: () => []
},
// 列间距
columnGap: {
type: [Number, String],
default: 6
},
// 行间距
rowGap: {
type: [Number, String],
default: 16
},
editTableRowIndex: {
// 当用于表格中的单元格的表单的时候的编辑行的索引
type: [Number, String],
default: -1
},
tableRowIndex: {
// 当用于表格中的单元格的表单的时候的行索引
type: [Number, String],
default: -1
},
tableColIndex: {
// 当用于表格中的单元格的表单的时候的行索引
type: [Number, String],
default: -1
},
editFormType: {
// left right top
type: Number,
default: 1
},
showPlaceholderPrefix: {
//placeholder前缀
type: Boolean,
default: true
}
},
data() {
return {
refsName: 'elform',
proxyMethodsArr: ['validate', 'validateField', 'resetFields', 'clearValidate'],
checkboxMultiple: {}, // 代理多选框组数据
formWidth: null,
rules: {},
formDataBackup: {},
focusElement: null,
selectAuto: true,
selectOptions: {}, // 记录选择框的选项的行数据
groupCodeOptions: {}, // 存储groupCode对应的options选项,可以直接通过groupCodeOptions['groupCode']渠道缓存的选项值
selectFilterObj: {},
datePickerOptions: {
disabledDate(time) {
return (
time.getTime() <= new Date('1900-01-01 00:00:00').getTime() ||
time.getTime() >= new Date('2073-12-30 23:59:59').getTime()
)
}
},
defaultCheckedUserList: [], // 已选择的用户
currentFocusData: {} //记住当前操作数据
}
},
inject: {
notebookAndModuleInfo: {
default: {}
}
},
watch: {
autoLayout: {
handler(val) {
// window.removeEventListener('resize', this.onResize)
// console.log('%c 第372行', 'color:red;font-size:2em')
// console.log(val)
if (val) {
// this.$nextTick(() => {
// window.addEventListener('resize', this.onResize)
// this.onResize()
// })
}
},
immediate: true
}
},
created() {
// this.rewriteEventMethods()
},
beforeMount() {
this.initConfig()
},
mounted() {
// console.log('%c 第839行 mounted', 'color:red;font-size:2em')
// console.log(this.formView)
this.$watch(
() => {
return this.formView
},
async () => {
if (!this.formView) {
return
}
console.log('表单json变化')
await this.queryGroupCodeOptions().then(async (res) => {
this.initFormItemConfig()
this.initConfig()
await this.rewriteEventMethods()
})
},
{ deep: true, immediate: true }
)
this.$watch(
() => {
return this.formData
},
(val) => {
let changeField = null
let searchData = {}
Object.keys(this.formData).forEach((i) => {
let v = this.formData[i]
if (
this.formView &&
this.formView[i] &&
(this.formView[i].type === 'Number' || this.formView[i].verify === 'integer')
) {
const nVal = isNaN(Number(v)) || Number(v) === 0 ? '' : Number(v)
this.$set(this.formData, i, nVal)
}
if (this.formView && this.formView[i]) {
// console.log('2222', this.editFormType)
//修改的时候调接口查询对应中文名
if (
this.formView[i].itemtype === 'DialogSelect' &&
(this.formView[i].dialogType === 'selectUser' || this.formView[i].dialogType === 'selectLocation') &&
this.editFormType == 2 &&
!this.formView[i].shouldInput
) {
if (this.formData.hasOwnProperty(i)) {
if (!isNaN(this.formData[i])) {
//数字类型 为userid
this.$request.getUserInfoListById([this.formData[i]]).then((res) => {
let data = res?.data
this.$set(this.formData, i + '_label', data[this.formData[i]])
})
} else {
if (this.formView[i].dialogType === 'selectLocation') {
this.$set(
this.formData,
i + '_label',
this.formData[this.formView[i]['linkSetField']] ||
this.formData[this.formView[i]['labelShowField']] ||
this.formData[i]
)
} else {
this.$set(this.formData, i + '_label', this.formData[i])
}
}
}
}
}
if (v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && v.length === 0)) {
searchData[i] = v
}
if ((v || this.formDataBackup[i]) && JSON.stringify(v) !== JSON.stringify(this.formDataBackup[i])) {
changeField = i
}
})
this.$emit('update:searchData', searchData)
// 不用失焦而是立即执行的input类型
let inputTypes = ['Radio']
if (
document.activeElement &&
document.activeElement !== document.body &&
!this.focusElement &&
!inputTypes.includes(this.formView[changeField]?.itemtype)
) {
if (this.$refs.elform.$el.contains(document.activeElement)) {
this.focusElement = document.activeElement
this.focusElement.addEventListener('blur', this.elementBlur) // 只能监听部分类型的失去焦点事件,一些类型的数据改变应当立即抛出时间
}
} else {
let types = ['Switch', 'DatePicker', 'DateTimePicker'].concat(inputTypes)
if (changeField && types.includes(this.formView[changeField]?.itemtype)) {
this.elementBlur()
}
}
this.formDataBackup = cloneDeep(val)
// if (this.notebookAndModuleInfo?.formUsedToExperimentNotebook) {
// // 当表单用于实验记录页面的时候,监听数据变化,在离开实验记录页面的时候保存数据
// this.$store.commit('record/SET_VALUECHANGE', true)
// }
},
{ deep: true, immediate: true }
)
if (this.autoLayout) {
window.addEventListener('resize', this.onResize)
this.onResize()
}
if (this.draggable) {
document.addEventListener('click', this.documentClick)
}
},
directives: {
selectloadmore: {
bind(el, binding) {
if (binding.value) {
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap')
SELECTWRAP_DOM.addEventListener('scroll', function () {
const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight
if (CONDITION) {
binding.value()
}
})
}
}
}
},
methods: {
findForm(el) {
if (el.parentNode.tagName === 'FORM') {
return el.parentNode
} else {
return this.findForm(el.parentNode)
}
},
initConfig() {
try {
if (!this.isUpdateCustomLabel) {
return
}
// console.log('formKey', this.formKey)
let tarConfig = (this.sysFormConfig || {})[this.formKey] // 获取当前模块配置
console.log('tarConfig', tarConfig)
if (tarConfig) {
for (let key in this.formView) {
let configView = tarConfig[key]
if (!configView) continue
if (configView.itemCustomLabel) {
this.formView[key].itemlabel = configView.itemCustomLabel
}
if (configView.itemAttr === 'required') {
this.$set(this.formView[key], 'required', true)
}
if (configView.itemAttr === 'disabled') {
this.formView[key].disabled = true
}
if (configView.itemAttr === 'readonly') {
this.formView[key].readonly = true
}
if (configView.itemAttr === 'hidden') {
this.formView[key].show = false
}
if (configView.itemAttr === 'normal') {
this.$set(this.formView[key], 'required', false)
}
if (configView?.itemAttr != 'hidden') {
if (this.formView[key].hasOwnProperty('itemAttr')) {
delete this.formView[key].itemAttr
}
// this.$set(this.formView[key], 'itemAttr', '')
}
}
}
} catch {}
},
nativeFileClick() {
this.$refs.nativeFile[0].click()
},
nativeFileChange(item, e) {
this.$refs.nativeFileName[0].value = e.target.files[0].name
item.change && item.change(e)
},
inputDialogSelect(e, item, key) {
if (item.dialogType === 'selectUser') {
this.$set(this.formData, key, e)
}
},
openSelectDialog(data, key, type = 'focus') {
this.currentFocusData = Object.assign({}, data, { key: key }) //记住当前操作数据
if (data.dialogType === 'selectUser') {
//选择用户组件
if (type === 'focus' && data.shouldInput) return
let dataList = Number(this.formData[key]) ? [Number(this.formData[key])].map((item) => ({ userId: item })) : []
this.defaultCheckedUserList = this.formData.hasOwnProperty(key) ? dataList : []
console.log('this.defaultCheckedUserList', this.defaultCheckedUserList)
this.$refs.refSelectUser.show()
//手动失焦,为了解决键盘输入还是会输入值在输入框中
if (type === 'focus') {
let formInput = document.getElementById('dialogSelectInput')
formInput && formInput.blur()
}
} else if (data.dialogType === 'selectLocation') {
if (type === 'focus' && data.shouldInput) return
this.$refs.locationSelect.show()
}
},
dialogSelectClear(data, key) {
this.currentFocusData = Object.assign({}, data, { key: key }) //记住当前操作数据
const item = this.currentFocusData
if (data.dialogType === 'selectUser' || data.dialogType === 'selectLocation') {
this.$set(this.formData, key, '')
this.$set(this.formData, `${key}_label`, '')
if (item.linkSetField) {
// 需要关联的设置的值
if (typeof item.linkSetField === 'string') {
this.$set(this.formData, item.linkSetField, '')
}
if (typeof item.linkSetField === 'object') {
// console.log('%c 第1228行', 'color: red; font-size: 2em')
// console.log(item)
this.$set(this.formData, item.linkSetField.setField, '')
}
if (Array.isArray(item.linkSetField)) {
item.linkSetField.forEach((oItem) => {
this.$set(this.formData, oItem.setField, '')
})
}
}
//选择用户组件
item.change && item.change('')
console.log('formData', this.formData)
}
},
handleSelectUser(user = {}) {
const item = this.currentFocusData
let row = {
label: user.userName,
value: user.id,
account: user.account,
userCode: user.userCode,
info: user
}
let value = row[item['bindValueField'] || 'value' || 'label']
let key = item.key
this.$set(this.formData, key, value)
console.log('row, ', row)
this.$set(this.formData, `${key}_label`, row.label)
console.log('formDatahandleSelectUser', this.formData)
if (item.linkSetField) {
// 需要关联的设置的值
if (typeof item.linkSetField === 'string') {
this.$set(this.formData, item.linkSetField, row[item.linkSetFieldBindKey] || row.label)
}
if (typeof item.linkSetField === 'object') {
// console.log('%c 第1228行', 'color: red; font-size: 2em')
// console.log(item)
this.$set(this.formData, item.linkSetField.setField, row[item.linkSetField.bindField] || row.label)
}
if (Array.isArray(item.linkSetField)) {
item.linkSetField.forEach((oItem) => {
this.$set(this.formData, oItem.setField, row[oItem.bindField])
})
}
}
item.afterSelect &&
item.afterSelect({
value: this.formData[key],
key,
formView: this.formView,
formData: this.formData,
selectOption: row
})
},
getLocationInfo(data) {
if (!data) return
// console.log('getLocationInfo-data---------', data)
const item = this.currentFocusData
let row = {
label: data.name,
showName: data.name,
value: data.id,
info: data
}
let value = row[item['bindValueField'] || 'value' || 'label']
// console.log('item', item, row, value)
let key = item.key
this.$set(this.formData, key, value)
this.$set(this.formData, `${key}_label`, row.label)
if (item.linkSetField) {
// 需要关联的设置的值
if (typeof item.linkSetField === 'string') {
this.$set(this.formData, item.linkSetField, row.label)
}
if (typeof item.linkSetField === 'object') {
// console.log('%c 第1228行', 'color: red; font-size: 2em')
// console.log(item)
this.$set(this.formData, item.linkSetField.setField, row[item.linkSetField.bindField] || row.label)
}
if (Array.isArray(item.linkSetField)) {
item.linkSetField.forEach((oItem) => {
this.$set(this.formData, oItem.setField, row[oItem.bindField])
})
}
}
if (item.recordSelectedRowInfo) {
this.$set(this.formData, item.recordSelectedRowInfo, row)
}
item.afterSelect &&
item.afterSelect({
value: this.formData[key],
key,
formView: this.formView,
formData: this.formData,
selectOption: row
})
},
renderTitle(type = '', data = {}) {
// console.log('render title =>', type, data)
let title = ''
if (type === 'select') {
const { key, item } = data
const array = (item.groupCode ? this.groupCodeOptions[item.groupCode] : item.options) ?? []
const options = typeof item.options === 'function' ? item.options(key, this.formData, this.formView) : array
if (Array.isArray(options)) {
const value = this.formData[key]
const values = Array.isArray(value) ? value : [value]
const matched = options.filter((e) => values.includes(e.value))
title = matched.map((e) => e?.label ?? '').join('、')
}
}
return title
},
handleFilterChange(filter, key) {
this.$set(this.selectFilterObj, key, filter)
},
/**
* formItem 点击时触发
* @param { String } key formItem对应的key值
* @param { Object } item formItem对应的项目
* @param { Object } formView formView数据
* @param { Object } formData formData数据
*/
handleClickFormItem({ key, item, formView, formData }) {
// console.log(key, item, formView, formData)
this.draggable ? this.updateSelectedKey(key) : null
if (item.clickable) {
this.$emit('itemClick', { key, item, formView, formData })
}
},
changeAutoComplete() {
// console.log('changeAutoComplete')
// 选中后让输入框失焦,不然下拉的数据可能还在
document.body.click()
},
clickHandler(key, item) {
typeof item.click === 'function' && item.click(key, item)
},
keyupEnterHandler(key, item) {
typeof item.enterSearch === 'function' && item.enterSearch(item)
this.$emit('enter', { key, itemData: item })
},
keydownHandler(e, key, item) {
if (item.type === 'number') {
let keyCode = e.keyCode
if (keyCode === 69) {
//数字类型输入框禁止输入e
e.returnValue = false
return false
}
return true
}
},
handleClear(item, key) {
console.log('🚀 ~ file: index.vue:932 ~ handleClear ~ item, key:', item, key)
if (item && key) {
item.clear && item.clear(item, this.formData[key], this.formData)
}
this.$nextTick(() => {
this.elementBlur()
})
},
handleChangeSelectInput(item, key) {
if (item && key) {
item.change && item.change(item, this.formData[key], this.formData)
}
},
handleFocus(item, key) {
if (item && key && item?.readonly) {
let select = this.$refs['selectSingle' + key][0]
select.blur()
}
},
/**
* Select 目前的选中值,选中值发生变化时触发
* @param { Object } selitem
* @param { String } key
* @param { Object } item
*/
handleSelect(selitem, key, item) {
// console.log(selitem, key, item)
const afterSelect = item.afterSelect ?? item.selectConfig.afterSelect ?? undefined
if (typeof afterSelect === 'function') {
afterSelect({
value: this.formData[key],
key,
formView: this.formView,
formData: this.formData,
selectOption: selitem
})
}
},
elementBlur() {
this.$emit('on-form-data-change', this.formData)
if (this.focusElement) {
this.focusElement.removeEventListener('blur', this.elementBlur)
this.focusElement = null
}
},
queryGroupCodeOptions() {
return new Promise(async (resolve, reject) => {
// 开始批量查询字典值
const existOptionsConfigKeys = Object.keys(optionsConfigs)
let groupCodeKeys = []
const object = this.formView
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
const item = object[key]
const groupCode = item?.groupCode ?? item?.selectConfig?.groupCode ?? undefined
if (groupCode) {
// console.log('query =>', key, groupCode, isDictCode, this.groupCodeOptions)
const isDictCode = !existOptionsConfigKeys.includes(groupCode) // 判断是否为维护的字典项数据
if (isDictCode && !this.groupCodeOptions[groupCode]) {
groupCodeKeys.push(groupCode)
}
if (!isDictCode && item?.selectConfig?.groupCode && !this.groupCodeOptions[groupCode]) {
//selectInput下拉是本地数据的
await this.setSelectOptions(groupCode, {}, {})
}
}
}
}
// console.log(this.formView, existOptionsConfigKeys, groupCodeKeys)
if (groupCodeKeys.length > 0) {
const params = {
dictCodes: groupCodeKeys,
uuid: nanoid() // 随机字符串,无实义
}
let labelProp = localStorage.getItem('language') === 'en_US' ? 'dictItemNameEn' : 'dictItemName'
const res = await this.$request.apiDictQueryListByCodes(params)
const data = res?.data ?? {}
for (const key in data) {
if (groupCodeKeys.includes(key)) {
const list = (data[key] || []).map((i) => {
return {
label: i[labelProp],
value: i.dictItemCode
}
})
this.$set(this.groupCodeOptions, key, list)
}
}
}
// 结束批量查询字典值
resolve(this.groupCodeOptions)
})
},
async initFormItemConfig() {
// await this.queryGroupCodeOptions()
let rules = {}
for (let [key, item] of Object.entries(this.formView)) {
// console.log('%c 第798行', 'color:red;font-size:2em')
// console.log(key, item.groupCode, item.params)
// if (item.watch) {
// this.setFieldWatch(key, item)
// }
if (item.groupCode && item.params && item.groupCode in optionsConfigs) {
// 对于在本地维护的带参数的groupCode需要在params变化的时候重新查询
this.$set(this.groupCodeOptions, item.groupCode, '')
}
// 多选框的值代理
if (item.itemtype === 'CheckboxGroup') {
this.setCheckboxMultiple(key)
}
if (item.itemtype === 'Select' && item.multiple) {
this.setCheckboxMultiple(key)
}
if (item.itemtype === 'Select' && item.groupCode) {
// console.log('%c 第722行', 'color:red;font-size:2em')
// console.log(item)
if (!this.groupCodeOptions[item.groupCode]) {
await this.setSelectOptions(item.groupCode, item.params, item)
}
}
if (item.deepBindSeparator) {
this.setDeepBindValue(key, item.deepBindSeparator)
}
if (item.itemtype === 'SlotForm' && item.formData) {
// console.log(item.formData)
this.$set(this.formData, item.formData, this.formData[item.formData] || {})
}
// 设置选项的默认值
if ('defaultValue' in item && (this.formData[key] === null || this.formData[key] === undefined)) {
this.$set(
this.formData,
key,
typeof item.defaultValue === 'function' ? item.defaultValue() : cloneDeep(item.defaultValue)
)
}
// 设置表单的验证
if (item.rules) {
if (typeof item.rules === 'function') {
rules[key] = item.rules({ formData: this.formData })
} else {
rules[key] = item.rules
}
} else {
rules[key] = []
}
if (item.rulesType) {
// 数组或者逗号隔开的字符串
let rulesfields = Array.isArray(item.rulesType)
? item.rulesType
: typeof item.rulesType === 'string'
? item.rulesType.split(',')
: []
let addrules = []
rulesfields.forEach((i) => {
addrules.push(formItemRules[i])
})
rules[key] = [...rules[key], ...addrules]
}
if (item.required) {
// if (typeof(item.required) === 'boolean') { //仅输入框控制不为空
rules[key] = [
...rules[key],
item.itemtype === 'CheckboxGroup' || (item.itemtype === 'Select' && item.multiple)
? {
validator: (rule, value, callback) => {
let nVal = this.checkboxMultiple[key] || []
nVal.length > 0 ? callback() : callback(new Error(this.$t('请选择')))
},
required: true,
message: `${this.$t('请选择')}${this.$t(item.itemlabel) ?? ''}`,
trigger: 'change'
}
: {
required: true,
message: `${this.$t(item.itemlabel) || ''} ${this.$t('不能为空')}`,
// pattern: '[^ \x22]+',
trigger: item.trigger || (['Input', 'InputNumber'].includes(item.itemtype) ? 'blur' : 'change')
}
]
// } else { //其他情况
// rules[key] = [
// ...rules[key]
// ].concat(createRulesBasicPage(item.required))
// }
} else {
this.$nextTick(() => {
this.$refs.elform && this.$refs.elform.clearValidate()
})
}
if (item?.selectConfig?.required === true) {
rules[key].push({
validator: (rule, value, callback) => {
let nVal = this.formData[item?.selectConfig?.prop]
nVal ? callback() : callback(new Error(this.$t('请选择')))
},
message: `${this.$t('请选择')}${item?.selectConfig?.itemlabel ?? ''}`,
trigger: item?.selectConfig?.trigger || 'change'
})
}
// 判断输入框是否开启空格校验,默认开启校验
if (
item.itemtype === 'Input' ||
item.itemtype === 'Textarea' ||
(item.itemtype === 'CustomElement' && item?.customElementType === 'Input')
) {
if (item.validateSpace !== false) {
const message = item.validateSpaceMsg ?? `${item.itemlabel || ''}${this.$t('不能全为空格')}`
const validator = {
key: 'validateEmpty',
validator: validateEmpty.call(this, message),
trigger: 'blur'
}
if (Array.isArray(rules[key])) {
const ruleKeys = rules[key].map((e) => e.key)
const isExist = ruleKeys.includes('validateEmpty')
if (!isExist) {
rules[key].push(validator)
}
}
}
}
// 判断是否传入 validateRange
const itemtype = item.itemtype ?? ''
if (Array.isArray(item.validateRange) && itemtype !== '') {
const range = item.validateRange ?? []
const type = range[0] ?? 'text'
const first = isNaN(Number(range[1])) ? 0 : Number(range[1])
const second = isNaN(Number(range[2])) ? 0 : Number(range[2])
const [min = 0, max = 0] = [Math.min(first, second), Math.max(first, second)]
const validateRangeKey = 'validateRange-' + key
const validator = {
key: validateRangeKey,
validator: (rule, value, callback) => {
const params = {
type,
key,
data: { itemtype, value, options: item.options ?? [] },
min,
max,
required: item.required ?? false
}
const { success, message } = validatorRange(params) ?? {}
success ? callback() : callback(new Error(message))
},
trigger: 'change'
}
if (Array.isArray(rules[key])) {
const ruleKeys = rules[key].map((e) => e.key)
const isExist = ruleKeys.includes(validateRangeKey)
if (!isExist) {
rules[key].push(cloneDeep(validator))
}
} else {
rules[key] = [cloneDeep(validator)]
}
}
}
this.rules = rules
this.$nextTick(() => {
if (this.updateClearValidate) {
this.$refs.elform && this.$refs.elform.clearValidate()
}
})
this.$emit('update:formView', this.formView)
},
selectFocus(key) {
// console.log('%c 第681行','color:red;font-size:2em');
// console.log(key)
// console.log('%c 第684行','color:red;font-size:2em');
// console.log(this.selectAuto)
if (this.formView[key].shouldInput) {
if (this.selectAuto) {
setTimeout(() => {
let ref = this.$refs['selectSingle' + key][0]
if (ref) {
console.log('%c 第687行', 'color:red;font-size:2em')
console.log(this.formData[key])
ref.$el.querySelector('input').value = this.formData[key] ?? ''
ref.$el.querySelector('input').addEventListener('blur', () => {
this.$set(this.formData, key, ref.$el.querySelector('input').value ?? '')
})
}
}, 0)
}
this.selectAuto = !this.selectAuto
}
},
/**
*解析方法有几个形参,如果不需要额外的参数就不用重新方法
*@param {paraName}
*/
getFuncParameters(func) {
if (typeof func === 'function') {
var mathes = /[^(]+\(([^)]*)?\)/gm.exec(Function.prototype.toString.call(func))
if (mathes[1]) {
var args = mathes[1].replace(/[^,\w]*/g, '').split(',')
return args
}
}
},
/**
*重写element的组件的事件方法,使可以使用其他参数。如change:(val,{key,formView,formData})=>{}
*@param {paraName}
*/
rewriteEventMethods(type = 'install') {
// console.log('%c 第472行', 'color:red;font-size:2em')
// console.log(4545)
const methodsArr = ['blur', 'focus', 'change', 'input', 'clear', 'visible-change', 'remove-tag', 'select']
for (let [key, item] of Object.entries(this.formView)) {
methodsArr.forEach((method) => {
if (method in item && typeof item[method] === 'function' && item.type !== 'file') {
if (this.getFuncParameters(item[method])?.length > 1) {
if (item[method].toString() !== this.formItemValueChange(item[method], key).toString()) {
item[method] = type === 'install' ? this.formItemValueChange(item[method], key) : null
}
}
}
})
// if (!item.options && type === 'install') {
// this.$set(item, 'options', item.options || [])
// }
}
},
selectOptionsRow(key, row, item, allOptions) {
// console.log('%c 第844行', 'color:red;font-size:2em')
// console.log(key, row,item)
this.$set(this.selectOptions, key, row)
this.$emit('onItemSelect', this.selectOptions)
this.$nextTick(() => {
if (item.linkSetField) {
// 需要关联的设置的值
if (typeof item.linkSetField === 'string') {
this.$set(this.formData, item.linkSetField, row[item.linkSetFieldBindKey] || row.label)
}
if (typeof item.linkSetField === 'object') {
// console.log('%c 第1228行', 'color: red; font-size: 2em')
// console.log(item)
this.$set(this.formData, item.linkSetField.setField, row[item.linkSetField.bindField] || row.label)
}
if (Array.isArray(item.linkSetField)) {
item.linkSetField.forEach((oItem) => {
this.$set(this.formData, oItem.setField, row[oItem.bindField])
})
}
}
item.afterSelect &&
item.afterSelect({
value: this.formData[key],
key,
formView: this.formView,
formData: this.formData,
selectOption: row,
allOptions
})
})
},
formItemValueChange(func, key) {
// console.log('func', func)
// console.log('key', key)
// console.log('111')
return () =>
func(this.formData[key], {
key,
formView: this.formView,
formData: this.formData,
$index: this.tableRowIndex,
selectOptions: this.selectOptions
})
},
getAttr(e, name) {
return e.target.attributes[name].value
},
documentClick() {
this.updateKey()
},
updateKey(val) {
this.$emit('update:selectedKey', val)
},
updateSelectedKey(key) {
this.updateKey(this.selectedKey === key ? '' : key)
},
deleteSelectedKeyEvent(key) {
this.$emit('deleteSelectedKey', key)
},
setStyle(item, key) {
if (this.isDefaultLayout) {
let style = { 'min-width': this.formItemMinWidth + 'px' }
if (this.draggable) {
// style = { ...style, resize: 'both', overflow: 'auto', border: '1px dotted' }
}
if (item.itemlabelStyle) {
this.$nextTick(() => {
if (this.$refs.elform) {
let label = this.$refs.elform.$el.querySelector(`[item-key="${key}"]`).querySelector(`[for="${key}"]`)
Object.keys(item.itemlabelStyle).forEach((name) => {
label.style[name] = item.itemlabelStyle[name]
})
}
})
}
if (item.itemcontentStyle) {
this.$nextTick(() => {
if (this.$refs.elform) {
let label = this.$refs.elform.$el
.querySelector(`[item-key="${key}"]`)
.querySelector('.el-form-item__content')
Object.keys(item.itemcontentStyle).forEach((name) => {
label.style[name] = item.itemcontentStyle[name]
})
}
})
}
if (item.fillRow) {
return { 'grid-column-start': `span ${this.formColumns}`, ...style }
}
if (item.itemtype === 'SlotForm') {
style['margin-bottom'] = 0
style['grid-template-columns'] = '0 1fr'
}
if (item.order !== undefined) {
style['order'] = item.order
}
let hasSpan = item.span && item.span >= 0 && item.span <= this.formColumns
let hasRowspan = item.rowspan && item.rowspan > 1
if (hasSpan || hasRowspan) {
let spanstyle = {
'grid-column-start':
item.span <= 1 ? `span ${Math.max(item.span * this.formColumns, this.itemcolumns)}` : `span ${item.span}`
}
let rowstyle = {
'grid-row-start': `span ${item.rowspan}`
}
if (hasSpan && hasRowspan) {
return { ...spanstyle, ...rowstyle, ...style }
}
if (hasSpan && !hasRowspan) {
return { ...spanstyle, ...style }
}
return { ...rowstyle, ...style }
}
return { 'grid-column-start': `span ${this.itemcolumns}`, ...style }
}
},
// 设置下拉选项的值,如果直接设置在formView上会触发循环监听,所以代理一下这些值
setSelectOptions(code, params, item) {
// mustAssociated 必须要传参数才能查询的
if (item.mustAssociated) {
params = params && typeof params === 'function' ? params({ formData: this.formData }) : params
if (!params || (params && Object.values(params).filter((i) => !!i).length === 0)) {
this.$set(this.groupCodeOptions, code, '')
return
}
}
// console.log('query code =>', code, params, this.groupCodeOptions, this.groupCodeOptions[code])
if (this.groupCodeOptions[code]) {
return
}
return new Promise((resolve, reject) => {
this.$commonMethods
.getSelectData(code, params)
.then((res) => {
this.$set(this.groupCodeOptions, code, res || [])
resolve(res || [])
})
.catch((err) => {
this.$set(this.groupCodeOptions, code, [])
resolve([])
})
})
},
setCheckboxMultiple(i) {
// 代理多选的值,使可以直接返回指定类型的值,数组或者以逗号分割的字符串
// if (this.formView[i].defaultValue) {
// this.$set(this.formData, i, this.formView[i].defaultValue)
// }
const isStringType = this.formView[i]['valueType'] && this.formView[i]['valueType'].toLowerCase() === 'string'
this.$watch(
function () {
return this.formData[i]
},
function (newval) {
this.$set(this.checkboxMultiple, i, newval ? (isStringType ? newval.toString().split(',') : newval) : [])
},
{
immediate: true
}
)
this.$watch(
function () {
return this.checkboxMultiple[i]
},
function (newval) {
let val = isStringType ? this.checkboxMultiple[i].toString() : newval
this.$set(this.formData, i, val)
}
)
},
/**
* 点击全选操作
*@param {type} 1执行全选反选操作 2 返回是否已全选的Boolean值
*/
selmultiAll(key, type = 1, item, allOptions) {
// console.log('%c 第1385行', 'color: red; font-size: 2em')
// console.log(key, type, item, allOptions)
// todo 需要处理allow-create的场景
if (!this.formView[key].options && !this.formView[key].groupCode) return
let linkSetField = this.formView[key].linkSetField
let bindValueField = this.formView[key].bindValueField || 'value' || 'label'
const arr = (this.formView[key].options || this.groupCodeOptions[this.formView[key].groupCode] || []).map(
(i) => i[bindValueField]
)
const labelArr = (this.formView[key].options || this.groupCodeOptions[this.formView[key].groupCode] || []).map(
(i) => i.label
)
const isStringType = this.formView[key]['valueType'] && this.formView[key]['valueType'].toLowerCase() === 'string'
const selectedAll = isStringType
? this.formData[key] === arr.join(',')
: this.formData[key]?.length === arr.length
if (type === 1) {
this.$set(this.formData, key, selectedAll ? (isStringType ? '' : []) : isStringType ? arr.toString() : arr)
if (linkSetField) {
this.$set(
this.formData,
linkSetField,
selectedAll ? (isStringType ? '' : []) : isStringType ? labelArr.toString() : labelArr
)
}
this.$refs.SelectMultiple && this.$refs.SelectMultiple[0]?.emitChange(this.formData[key])
item.afterSelect &&
item.afterSelect({
value: this.formData[key],
key,
formView: this.formView,
formData: this.formData,
selectOption: {},
allOptions
})
} else {
return selectedAll
}
},
onResize() {
let resize = () => {
if (!this.$refs.elform || !this.$refs.elform.$el) return
const formWidth = window.getComputedStyle(this.$refs.elform.$el, null).getPropertyValue('width')
this.formWidth = parseInt(formWidth)
}
const timer = setTimeout(() => {
resize()
clearTimeout(timer)
}, 500)
},
dragEnd() {
this.updateKey()
let keysOrder = []
this.$nextTick(() => {
const elArr = this.$refs.elform.$el.querySelectorAll(`[item-key]`)
for (let el of elArr) {
keysOrder.push(el.getAttribute('item-key'))
}
this.$emit('keyOrderChange', keysOrder)
})
},
setDeepBindValue(key, deepBindSeparator) {
let arr = key.split(deepBindSeparator)
if (arr.length > 0) {
this.$watch(
() => {
return this.formData[key]
},
(val) => {
// console.log(val)
if (val === undefined || val === null) return
let obj = {}
for (let i = arr.length - 1; i >= 0; i--) {
let field = arr[i]
if (i === arr.length - 1) {
obj = { [field]: val }
} else if (i > 0) {
obj = { [field]: obj }
} else {
// this.formData[field] = obj
if (!this.formData[field] || (this.formData[field] && this.formData[field] !== obj)) {
this.$set(this.formData, field, obj)
}
}
}
// console.log(obj)
// console.log(this.formData)
},
{ immediate: true }
)
this.$watch(
() => {
// console.log(this.formData.hasOwnProperty(f))
let f = arr[0]
if (!this.formData.hasOwnProperty(f)) {
this.$set(this.formData, f, {})
}
// console.log(this.formData.hasOwnProperty(f))
return this.formData[f]
},
(val) => {
// console.log(val)
if (val === undefined || val === null) return
let newval = val
for (let i = 1; i < arr.length; i++) {
let field = arr[i]
if (newval) {
newval = newval[field]
}
}
// console.log(newval)
if (newval !== this.formData[key]) {
this.$set(this.formData, key, newval)
}
},
{ deep: true }
)
}
},
validate() {
return new Promise((provide, reject) => {
this.$refs['elform'].validate((valid) => {
console.log(valid)
provide(valid)
})
})
},
validateForm() {
return new Promise((resolve, reject) => {
this.$refs['elform'].validate(async (valid, error) => {
// console.log('validateForm =>', valid, error)
if (valid) {
let flag = true
if (this.$refs['SlotForm']) {
for await (let deepForm of this.$refs['SlotForm']) {
await deepForm.$refs['elform'].validate((valid2) => {
if (!valid2) {
flag = false
}
})
}
resolve(flag)
} else {
resolve(true)
}
} else {
resolve(false)
}
})
})
},
getSelectOptions(item) {
// 需要优化取值次数
// console.log('%c 第1223行', 'color:red;font-size:2em')
// console.log(item.groupCode, this.groupCodeOptions, this.groupCodeOptions[item.groupCode])
// console.log(item)
if (item.groupCode) {
if (!this.groupCodeOptions[item.groupCode]) {
this.setSelectOptions(item.groupCode, item.params, item).then((res) => {
// setTimeout(() => {
// this.getSelectOptions(item)
// }, 500)
})
} else {
return this.groupCodeOptions[item.groupCode]
}
} else {
return item.options
}
},
resetForm() {
this.$refs.elform.resetFields()
},
clearValidate() {
this.$refs.elform.clearValidate()
},
setFieldWatch(key, item) {
// 如果在里面执行了给formView 设置值,将导致死循环
this.$watch(
() => {
return this.formData[key]
},
(val) => {
item.watch({ value: val, formData: this.formData })
},
{ immediate: true, deep: true }
)
}
},
computed: {
// getLabelAlign() {
// return { 'label-position-top': this.labelPosition === 'top' }
// },
...mapGetters(['sysFormConfig']),
formLabelWidth() {
return this.labelPosition === 'top' ? '' : parseInt(this.labelWidth) + 'px'
},
hiddenLabelClass() {
return {
hiddenLabelClass: parseInt(this.labelWidth) === 0 || this.hiddenLabel
}
},
// rules() {
// return Object.keys(this.formView).reduce((prev, curr) => {
// let item = this.formView[curr]
// const requiredRules = {
// required: true,
// message: `${item.itemlabel || ''}不能为空`,
// trigger: 'blur'
// }
// if (item.rules && item.rules.length > 0) {
// prev[curr] = [...prev[curr], ...item.rules]
// } else {
// prev[curr] = []
// }
// if (item.required) {
// // if (item.rules && item.rules.length > 0) {
// prev[curr] = [...prev[curr], requiredRules]
// // } else {
// // // 需要根据类型判断提示内容
// // prev[curr] = [requiredRules]
// // }
// } else {
// // this.$refs.elform?.clearValidate(curr)//需要loader支持这种语法
// this.$refs.elform && this.$refs.elform.clearValidate(curr)
// }
// return prev
// }, {})
// },
isDefaultLayout() {
return this.layoutClass === 'default-layout'
},
styleObj() {
// grid-template-columns: repeat(auto-fill, 320px);
if (!this.isDefaultLayout) return
let style = {
'grid-template-columns': `repeat(${this.formColumns},1fr)`
}
const columnGap = this.$attrs['columnGap'] ?? this.columnGap
const rowGap = this.$attrs['rowGap'] ?? this.rowGap
if (columnGap) {
style['column-gap'] = columnGap + 'px'
}
if (rowGap) {
style['row-gap'] = rowGap + 'px'
}
return style
},
itemcolumns: {
get() {
if (!this.isDefaultLayout) return
if (this.autoLayout) {
let columnsNum = this.formColumns / this.columns
if (this.formWidth && this.formWidth < columnsNum * this.formItemMinWidth) {
return this.formColumns / Math.floor(this.formWidth / this.formItemMinWidth)
} else {
return this.columns
}
}
// console.log('%c 第1646行', 'color: red; font-size: 2em')
// console.log(this.isAutoComputedColumns && this.columns === 12)
// 如果自动分配columuns,那么表单元素小于等于4个,默认一行一列,否则一行两列
if (this.isAutoComputedColumns && this.columns === 12) {
return Object.keys(this.formView).filter((i) => !this.formView[i].hidden).length > 4 ? 6 : 12
}
return this.columns
},
set(val) {
// this.$emit('update:columns', val)
}
}
},
beforeUpdate() {
// label 文字超出后换行并修改行高
// let el = this.$refs.elform && this.$refs.elform.$el
// if (el) {
// for (let [key, item] of Object.entries(this.formView)) {
// if (item.itemlabel && item.itemlabel.length > 4) {
// let label = el.querySelector(`[item-key="${key}"]`)?.querySelector(`[for="${key}"]`)
// if (label && label.scrollWidth > label.clientWidth) {
// console.log(item.itemlabel)
// label.style['line-height'] = '16px'
// label.style['white-space'] = 'break-spaces'
// label.style['ovferflow'] = 'hidden'
// }
// }
// }
// }
},
updated() {
// label 文字超出后换行并修改行高
let el = this.$refs.elform && this.$refs.elform.$el
if (el) {
for (let [key, item] of Object.entries(this.formView)) {
if (item.itemlabel && item.itemlabel.length > 4) {
let label = el.querySelector(`[item-key="${key}"]`)?.querySelector(`[for="${key}"]`)
if (label && label.scrollWidth > label.clientWidth) {
// console.log(item.itemlabel)
label.style['line-height'] = '16px'
label.style['white-space'] = 'break-spaces'
label.style['overflow'] = 'hidden'
}
}
}
}
},
beforeDestroy() {
this.rewriteEventMethods('uninstall')
window.removeEventListener('resize', this.onResize)
document.removeEventListener('click', this.documentClick)
if (this.focusElement) {
this.focusElement.removeEventListener('blur', this.elementBlur)
}
this.focusElement = null
}
}
</script>
<style lang="scss" scoped>
.default-layout {
display: grid;
column-gap: 3px;
row-gap: 6px;
// grid-template-columns: repeat(12, 1fr);
> .el-form-item {
display: grid;
grid-template-columns: auto auto 1fr;
grid-auto-flow: column dense;
margin-bottom: 0px;
// min-width: 250px;
// grid-column: span 3;
::v-deep .el-form-item__label {
// line-height: 16px;
// display: grid;
// align-items: center;
// grid-auto-flow: column;
// justify-content: end;
white-space: nowrap;
overflow: hidden;
padding-right: 6px;
// white-space: pre-wrap;
// word-wrap: break-word;
// line-height: 1;
// display: flex;
// justify-content: flex-end;
// align-items: center;
// text-overflow: ellipsis;
}
::v-deep .el-input__inner {
padding: 0 6px;
}
::v-deep .el-icon-date {
display: none !important;
}
::v-deep .el-form-item__content {
height: max-content;
margin-left: 0px !important;
// .el-select,
// .el-date-editor {
// width: 100%;
// }
> div {
width: 100%;
}
.el-input--small .el-input__icon {
line-height: 30px;
}
.el-date-editor .el-range-separator {
padding: 0;
}
.el-date-editor .el-icon-date {
display: none !important;
}
.el-date-editor .el-icon-time {
display: none !important;
}
}
}
&.el-form--label-top {
column-gap: 6px;
::v-deep > .el-form-item {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto 1fr auto;
grid-auto-flow: row dense;
margin-bottom: 14px;
.el-form-item__label {
justify-content: flex-start;
text-align: left;
padding: 0;
}
}
}
&.el-form--label-left {
column-gap: 6px;
}
}
::v-deep.el-form-item.contentOverHidden {
.el-form-item__content {
overflow: hidden;
}
}
.tableForm {
::v-deep .el-form-item__label {
padding-right: 0 !important;
}
::v-deep .el-form-item {
align-items: center;
}
}
.hiddenLabelClass {
::v-deep > .el-form-item__label {
color: transparent;
height: 0 !important;
padding: 0;
// transform: translateX(-8px);
// position: absolute;
width: 0 !important;
}
}
.slotform {
}
.ghost {
// border: 1px solid #409eff;
box-shadow: 0px 0px 3px 3px orange !important;
border-radius: 2px;
}
.selectedFormItem {
box-shadow: 0px 0px 3px 3px #409eff;
border-radius: 2px;
}
.deleteSelectedKey {
position: absolute;
right: 0;
top: 0;
z-index: 9999;
}
.el-select-dropdown__item.optionBtnType {
display: inline-flex;
& ~ li {
display: inline-flex;
}
}
.el-input.is-disabled .el-input__inner {
color: #666;
}
::v-deep .el-input.is-disabled .el-input-group__append {
pointer-events: none;
}
::v-deep .el-input .el-input__suffix {
display: flex;
align-items: center;
}
// ::v-deep .my-el-upload {
// display: flex;
// width: 100px;
// align-items: center;
// flex-direction: row-reverse;
// .el-upload-list {
// line-height: auto;
// margin-top: 0px;
// max-width: 125px;
// }
// .el-upload-list__item {
// margin-top: 0px;
// }
// .el-upload-list__item-name {
// margin-right: 4px;
// }
// }
.itemIsDisabled {
cursor: not-allowed;
::v-deep * {
pointer-events: none;
color: #606266;
}
::v-deep .el-form-item__content input {
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
cursor: not-allowed;
}
}
//.el-form-item[readonly] {
// pointer-events: none;
//}
.formInLuckysheet {
// display: grid;
column-gap: 0px;
row-gap: 0px;
// grid-template-columns: repeat(12, 1fr);
> .el-form-item {
display: grid;
// grid-template-columns: auto auto 1fr;
grid-template-columns: 0 1fr;
grid-auto-flow: column dense;
margin-bottom: 0px;
::v-deep .el-form-item__label {
white-space: nowrap;
overflow: auto;
padding-right: 0px;
}
::v-deep .el-input__inner {
padding: 0 3px;
border: none;
height: 28px;
line-height: 28px;
width: 100%;
}
::v-deep .el-icon-date {
display: none !important;
}
::v-deep .el-icon-time {
display: none !important;
}
::v-deep .el-form-item__content {
line-height: 24px;
margin-left: 0px !important;
background: #fff;
overflow: hidden;
> div {
width: 100%;
}
.el-input__suffix {
margin-right: -6px;
}
.el-input--small .el-input__icon {
// line-height: 20px;
}
.el-date-editor .el-range-separator {
padding: 0;
}
}
}
// &.el-form--label-top {
// column-gap: 6px;
// ::v-deep > .el-form-item {
// display: grid;
// grid-template-columns: 1fr;
// grid-template-rows: auto auto 1fr auto;
// grid-auto-flow: row dense;
// margin-bottom: 14px;
// .el-form-item__label {
// text-align: left;
// padding: 0;
// }
// }
// }
// &.el-form--label-left {
// column-gap: 6px;
// }
}
.dialogSelect {
/deep/.el-input-group__append {
padding: 0 5px !important;
}
}
</style>
<style lang="scss">
.el-select-dropdown.el-popper.is-multiple {
max-width: 400px;
}
.el-autocomplete-suggestion {
min-width: 250px;
}
.clickable {
color: #017aff;
}
</style>