| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- import XEUtils from 'xe-utils'
- import GlobalConfig from '../../v-x-e-table/src/conf'
- import vSize from '../../mixins/size'
- import VXETable from '../../v-x-e-table'
- import { UtilTools, DomTools, isEnableConf } from '../../tools'
- import { createItem } from './util'
- class Rule {
- constructor (rule) {
- Object.assign(this, {
- $options: rule,
- required: rule.required,
- min: rule.min,
- max: rule.min,
- type: rule.type,
- pattern: rule.pattern,
- validator: rule.validator,
- trigger: rule.trigger,
- maxWidth: rule.maxWidth
- })
- }
- get message () {
- return UtilTools.getFuncText(this.$options.message)
- }
- }
- function getResetValue (value, resetValue) {
- if (XEUtils.isArray(value)) {
- resetValue = []
- }
- return resetValue
- }
- function callSlot (_vm, slotFunc, params, h) {
- if (slotFunc) {
- const { $scopedSlots } = _vm
- if (XEUtils.isString(slotFunc)) {
- slotFunc = $scopedSlots[slotFunc] || null
- }
- if (XEUtils.isFunction(slotFunc)) {
- return slotFunc.call(_vm, params, h)
- }
- }
- return []
- }
- function renderPrefixIcon (h, titlePrefix) {
- return h('span', {
- class: 'vxe-form--item-title-prefix'
- }, [
- h('i', {
- class: titlePrefix.icon || GlobalConfig.icon.FORM_PREFIX
- })
- ])
- }
- function renderSuffixIcon (h, titleSuffix) {
- return h('span', {
- class: 'vxe-form--item-title-suffix'
- }, [
- h('i', {
- class: titleSuffix.icon || GlobalConfig.icon.FORM_SUFFIX
- })
- ])
- }
- function renderTitle (h, _vm, item) {
- const { data } = _vm
- const { slots, field, itemRender, titlePrefix, titleSuffix } = item
- const compConf = isEnableConf(itemRender) ? VXETable.renderer.get(itemRender.name) : null
- const params = { data, property: field, item, $form: _vm }
- const tss = []
- if (titlePrefix) {
- tss.push(
- titlePrefix.message
- ? h('vxe-tooltip', {
- props: {
- content: UtilTools.getFuncText(titlePrefix.message),
- enterable: titlePrefix.enterable,
- theme: titlePrefix.theme
- }
- }, [
- renderPrefixIcon(h, titlePrefix)
- ])
- : renderPrefixIcon(h, titlePrefix)
- )
- }
- tss.push(
- h('span', {
- class: 'vxe-form--item-title-label'
- }, compConf && compConf.renderItemTitle ? compConf.renderItemTitle(itemRender, params) : (slots && slots.title ? callSlot(_vm, slots.title, params, h) : UtilTools.getFuncText(item.title)))
- )
- if (titleSuffix) {
- tss.push(
- titleSuffix.message
- ? h('vxe-tooltip', {
- props: {
- content: UtilTools.getFuncText(titleSuffix.message),
- enterable: titleSuffix.enterable,
- theme: titleSuffix.theme
- }
- }, [
- renderSuffixIcon(h, titleSuffix)
- ])
- : renderSuffixIcon(h, titleSuffix)
- )
- }
- return tss
- }
- function renderItems (h, _vm, itemList) {
- const { _e, rules, data, collapseAll, validOpts, titleOverflow: allTitleOverflow } = _vm
- return itemList.map((item, index) => {
- const { slots, title, folding, visible, visibleMethod, field, collapseNode, itemRender, showError, errRule, className, titleOverflow, children } = item
- const compConf = isEnableConf(itemRender) ? VXETable.renderer.get(itemRender.name) : null
- const span = item.span || _vm.span
- const align = item.align || _vm.align
- const titleAlign = item.titleAlign || _vm.titleAlign
- const titleWidth = item.titleWidth || _vm.titleWidth
- let itemVisibleMethod = visibleMethod
- const itemOverflow = (XEUtils.isUndefined(titleOverflow) || XEUtils.isNull(titleOverflow)) ? allTitleOverflow : titleOverflow
- const showEllipsis = itemOverflow === 'ellipsis'
- const showTitle = itemOverflow === 'title'
- const showTooltip = itemOverflow === true || itemOverflow === 'tooltip'
- const hasEllipsis = showTitle || showTooltip || showEllipsis
- const params = { data, property: field, item, $form: _vm }
- let isRequired
- if (visible === false) {
- return _e()
- }
- // 如果为项集合
- const isGather = children && children.length > 0
- if (isGather) {
- const childVNs = renderItems(h, _vm, item.children)
- return childVNs.length ? h('div', {
- class: ['vxe-form--gather vxe-row', item.id, span ? `vxe-col--${span} is--span` : '', className ? (XEUtils.isFunction(className) ? className(params) : className) : '']
- }, childVNs) : _e()
- }
- if (!itemVisibleMethod && compConf && compConf.itemVisibleMethod) {
- itemVisibleMethod = compConf.itemVisibleMethod
- }
- if (rules) {
- const itemRules = rules[field]
- if (itemRules) {
- isRequired = itemRules.some(rule => rule.required)
- }
- }
- let contentVNs = []
- if (slots && slots.default) {
- contentVNs = callSlot(_vm, slots.default, params, h)
- } else if (compConf && compConf.renderItemContent) {
- contentVNs = compConf.renderItemContent.call(_vm, h, itemRender, params)
- } else if (compConf && compConf.renderItem) {
- contentVNs = compConf.renderItem.call(_vm, h, itemRender, params)
- } else if (field) {
- contentVNs = [`${XEUtils.get(data, field)}`]
- }
- const ons = showTooltip ? {
- mouseenter (evnt) {
- _vm.triggerHeaderHelpEvent(evnt, params)
- },
- mouseleave: _vm.handleTargetLeaveEvent
- } : {}
- return h('div', {
- class: ['vxe-form--item', item.id, span ? `vxe-col--${span} is--span` : null, className ? (XEUtils.isFunction(className) ? className(params) : className) : '', {
- 'is--title': title,
- 'is--required': isRequired,
- 'is--hidden': folding && collapseAll,
- 'is--active': !itemVisibleMethod || itemVisibleMethod(params),
- 'is--error': showError
- }],
- key: index
- }, [
- h('div', {
- class: 'vxe-form--item-inner'
- }, [
- title || (slots && slots.title) ? h('div', {
- class: ['vxe-form--item-title', titleAlign ? `align--${titleAlign}` : null, {
- 'is--ellipsis': hasEllipsis
- }],
- style: titleWidth ? {
- width: isNaN(titleWidth) ? titleWidth : `${titleWidth}px`
- } : null,
- attrs: {
- title: showTitle ? UtilTools.getFuncText(title) : null
- },
- on: ons
- }, renderTitle(h, _vm, item)) : null,
- h('div', {
- class: ['vxe-form--item-content', align ? `align--${align}` : null]
- }, contentVNs.concat(
- [
- collapseNode ? h('div', {
- class: 'vxe-form--item-trigger-node',
- on: {
- click: _vm.toggleCollapseEvent
- }
- }, [
- h('span', {
- class: 'vxe-form--item-trigger-text'
- }, collapseAll ? GlobalConfig.i18n('vxe.form.unfolding') : GlobalConfig.i18n('vxe.form.folding')),
- h('i', {
- class: ['vxe-form--item-trigger-icon', collapseAll ? GlobalConfig.icon.FORM_FOLDING : GlobalConfig.icon.FORM_UNFOLDING]
- })
- ]) : null,
- errRule && validOpts.showMessage ? h('div', {
- class: 'vxe-form--item-valid',
- style: errRule.maxWidth ? {
- width: `${errRule.maxWidth}px`
- } : null
- }, errRule.message) : null
- ])
- )
- ])
- ])
- })
- }
- export default {
- name: 'VxeForm',
- mixins: [vSize],
- props: {
- loading: Boolean,
- data: Object,
- size: { type: String, default: () => GlobalConfig.form.size || GlobalConfig.size },
- span: [String, Number],
- align: { type: String, default: () => GlobalConfig.form.align },
- titleAlign: { type: String, default: () => GlobalConfig.form.titleAlign },
- titleWidth: [String, Number],
- titleColon: { type: Boolean, default: () => GlobalConfig.form.titleColon },
- titleAsterisk: { type: Boolean, default: () => GlobalConfig.form.titleAsterisk },
- titleOverflow: { type: [Boolean, String], default: null },
- items: Array,
- rules: Object,
- preventSubmit: { type: Boolean, default: () => GlobalConfig.form.preventSubmit },
- validConfig: Object
- },
- data () {
- return {
- collapseAll: true,
- staticItems: [],
- formItems: [],
- tooltipTimeout: null,
- tooltipActive: false,
- tooltipStore: {
- item: null,
- visible: false
- }
- }
- },
- provide () {
- return {
- $xeform: this
- }
- },
- computed: {
- validOpts () {
- return Object.assign({}, GlobalConfig.form.validConfig, this.validConfig)
- },
- tooltipOpts () {
- const opts = Object.assign({ leaveDelay: 300 }, GlobalConfig.form.tooltipConfig, this.tooltipConfig)
- if (opts.enterable) {
- opts.leaveMethod = this.handleTooltipLeaveMethod
- }
- return opts
- }
- },
- created () {
- this.$nextTick(() => {
- const { items } = this
- if (items) {
- this.loadItem(items)
- }
- })
- },
- watch: {
- staticItems (value) {
- this.formItems = value
- },
- items (value) {
- this.loadItem(value)
- }
- },
- render (h) {
- const { _e, loading, vSize, tooltipOpts, formItems } = this
- const hasUseTooltip = VXETable._tooltip
- return h('form', {
- class: ['vxe-form', {
- [`size--${vSize}`]: vSize,
- 'is--colon': this.titleColon,
- 'is--asterisk': this.titleAsterisk,
- 'is--loading': loading
- }],
- on: {
- submit: this.submitEvent,
- reset: this.resetEvent
- }
- }, [
- h('div', {
- class: 'vxe-form--wrapper vxe-row'
- }, renderItems(h, this, formItems)),
- h('div', {
- class: 'vxe-form-slots',
- ref: 'hideItem'
- }, this.$slots.default),
- h('div', {
- class: ['vxe-loading', {
- 'is--visible': loading
- }]
- }, [
- h('div', {
- class: 'vxe-loading--spinner'
- })
- ]),
- /**
- * 工具提示
- */
- hasUseTooltip ? h('vxe-tooltip', {
- ref: 'tooltip',
- ...tooltipOpts
- }) : _e()
- ])
- },
- methods: {
- loadItem (list) {
- if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
- const { $scopedSlots } = this
- list.forEach(item => {
- if (item.slots) {
- XEUtils.each(item.slots, (func) => {
- if (!XEUtils.isFunction(func)) {
- if (!$scopedSlots[func]) {
- UtilTools.error('vxe.error.notSlot', [func])
- }
- }
- })
- }
- })
- }
- this.staticItems = list.map(item => createItem(this, item))
- return this.$nextTick()
- },
- getItems () {
- const itemList = []
- XEUtils.eachTree(this.formItems, item => {
- itemList.push(item)
- }, { children: 'children' })
- return itemList
- },
- toggleCollapse () {
- this.collapseAll = !this.collapseAll
- return this.$nextTick()
- },
- toggleCollapseEvent (evnt) {
- this.toggleCollapse()
- this.$emit('toggle-collapse', { collapse: !this.collapseAll, data: this.data, $form: this, $event: evnt }, evnt)
- },
- submitEvent (evnt) {
- evnt.preventDefault()
- if (!this.preventSubmit) {
- this.beginValidate().then(() => {
- this.$emit('submit', { data: this.data, $form: this, $event: evnt })
- }).catch(errMap => {
- this.$emit('submit-invalid', { data: this.data, errMap, $form: this, $event: evnt })
- })
- }
- },
- reset () {
- const { data } = this
- if (data) {
- const itemList = this.getItems()
- itemList.forEach(item => {
- const { field, resetValue, itemRender } = item
- if (isEnableConf(itemRender)) {
- const compConf = VXETable.renderer.get(itemRender.name)
- if (compConf && compConf.itemResetMethod) {
- compConf.itemResetMethod({ data, property: field, item, $form: this })
- } else if (field) {
- XEUtils.set(data, field, resetValue === null ? getResetValue(XEUtils.get(data, field), undefined) : resetValue)
- }
- }
- })
- }
- return this.clearValidate()
- },
- resetEvent (evnt) {
- evnt.preventDefault()
- this.reset()
- this.$emit('reset', { data: this.data, $form: this, $event: evnt })
- },
- handleTooltipLeaveMethod () {
- const { tooltipOpts } = this
- setTimeout(() => {
- if (!this.tooltipActive) {
- this.closeTooltip()
- }
- }, tooltipOpts.leaveDelay)
- return false
- },
- closeTooltip () {
- const { tooltipStore } = this
- const $tooltip = this.$refs.tooltip
- if (tooltipStore.visible) {
- Object.assign(tooltipStore, {
- item: null,
- visible: false
- })
- if ($tooltip) {
- $tooltip.close()
- }
- }
- return this.$nextTick()
- },
- triggerHeaderHelpEvent (evnt, params) {
- const { item } = params
- const { tooltipStore } = this
- const $tooltip = this.$refs.tooltip
- const overflowElem = evnt.currentTarget
- const content = (overflowElem.textContent || '').trim()
- const isCellOverflow = overflowElem.scrollWidth > overflowElem.clientWidth
- clearTimeout(this.tooltipTimeout)
- this.tooltipActive = true
- this.closeTooltip()
- if (content && isCellOverflow) {
- Object.assign(tooltipStore, {
- item,
- visible: true
- })
- if ($tooltip) {
- $tooltip.open(overflowElem, content)
- }
- }
- },
- handleTargetLeaveEvent () {
- const { tooltipOpts } = this
- this.tooltipActive = false
- if (tooltipOpts.enterable) {
- this.tooltipTimeout = setTimeout(() => {
- const $tooltip = this.$refs.tooltip
- if ($tooltip && !$tooltip.isHover) {
- this.closeTooltip()
- }
- }, tooltipOpts.leaveDelay)
- } else {
- this.closeTooltip()
- }
- },
- clearValidate (field) {
- const itemList = this.getItems()
- if (field) {
- const item = itemList.find(item => item.field === field)
- if (item) {
- item.showError = false
- }
- } else {
- itemList.forEach(item => {
- item.showError = false
- })
- }
- return this.$nextTick()
- },
- validate (callback) {
- return this.beginValidate('', callback)
- },
- beginValidate (type, callback) {
- const { data, rules: formRules, validOpts } = this
- const validRest = {}
- const validFields = []
- const itemValids = []
- const itemList = this.getItems()
- this.clearValidate()
- clearTimeout(this.showErrTime)
- if (data && formRules) {
- itemList.forEach(item => {
- const { field } = item
- if (field) {
- itemValids.push(
- this.validItemRules(type || 'all', field).then(() => {
- item.errRule = null
- }).catch(({ rule, rules }) => {
- const rest = { rule, rules, data, property: field, $form: this }
- if (!validRest[field]) {
- validRest[field] = []
- }
- validRest[field].push(rest)
- validFields.push(field)
- item.errRule = rule
- return Promise.reject(rest)
- })
- )
- }
- })
- return Promise.all(itemValids).then(() => {
- if (callback) {
- callback()
- }
- }).catch(() => {
- this.showErrTime = setTimeout(() => {
- itemList.forEach(item => {
- if (item.errRule) {
- item.showError = true
- }
- })
- }, 20)
- if (callback) {
- callback(validRest)
- }
- if (validOpts.autoPos) {
- this.$nextTick(() => {
- this.handleFocus(validFields)
- })
- }
- return Promise.reject(validRest)
- })
- }
- if (callback) {
- callback()
- }
- return Promise.resolve()
- },
- /**
- * 校验数据
- * 按表格行、列顺序依次校验(同步或异步)
- * 校验规则根据索引顺序依次校验,如果是异步则会等待校验完成才会继续校验下一列
- * 如果校验失败则,触发回调或者 Promise<(ErrMap 校验不通过列的信息)>
- * 如果是传回调方式这返回一个 (ErrMap 校验不通过列的信息)
- *
- * rule 配置:
- * required=Boolean 是否必填
- * min=Number 最小长度
- * max=Number 最大长度
- * validator=Function({ itemValue, rule, rules, data, property }) 自定义校验,接收一个 Promise
- * trigger=change 触发方式
- */
- validItemRules (type, property, val) {
- const { data, rules: formRules } = this
- const errorRules = []
- const syncVailds = []
- if (property && formRules) {
- const rules = XEUtils.get(formRules, property)
- if (rules) {
- const itemValue = XEUtils.isUndefined(val) ? XEUtils.get(data, property) : val
- rules.forEach(rule => {
- if (type === 'all' || !rule.trigger || type === rule.trigger) {
- if (XEUtils.isFunction(rule.validator)) {
- const customValid = rule.validator({
- itemValue,
- rule,
- rules,
- data,
- property,
- $form: this
- })
- if (customValid) {
- if (XEUtils.isError(customValid)) {
- errorRules.push(new Rule({ type: 'custom', trigger: rule.trigger, message: customValid.message, rule: new Rule(rule) }))
- } else if (customValid.catch) {
- // 如果为异步校验(注:异步校验是并发无序的)
- syncVailds.push(
- customValid.catch(e => {
- errorRules.push(new Rule({ type: 'custom', trigger: rule.trigger, message: e ? e.message : rule.message, rule: new Rule(rule) }))
- })
- )
- }
- }
- } else {
- const isNumber = rule.type === 'number'
- const numVal = isNumber ? XEUtils.toNumber(itemValue) : XEUtils.getSize(itemValue)
- if (itemValue === null || itemValue === undefined || itemValue === '') {
- if (rule.required) {
- errorRules.push(new Rule(rule))
- }
- } else if (
- (isNumber && isNaN(itemValue)) ||
- (!isNaN(rule.min) && numVal < parseFloat(rule.min)) ||
- (!isNaN(rule.max) && numVal > parseFloat(rule.max)) ||
- (rule.pattern && !(rule.pattern.test ? rule.pattern : new RegExp(rule.pattern)).test(itemValue))
- ) {
- errorRules.push(new Rule(rule))
- }
- }
- }
- })
- }
- }
- return Promise.all(syncVailds).then(() => {
- if (errorRules.length) {
- const rest = { rules: errorRules, rule: errorRules[0] }
- return Promise.reject(rest)
- }
- })
- },
- handleFocus (fields) {
- const { $el } = this
- const itemList = this.getItems()
- fields.some(property => {
- const item = itemList.find(item => item.field === property)
- if (item && isEnableConf(item.itemRender)) {
- const { itemRender } = item
- const compConf = VXETable.renderer.get(itemRender.name)
- let inputElem
- // 如果指定了聚焦 class
- if (itemRender.autofocus) {
- inputElem = $el.querySelector(`.${item.id} ${itemRender.autofocus}`)
- }
- // 渲染器的聚焦处理
- if (!inputElem && compConf && compConf.autofocus) {
- inputElem = $el.querySelector(`.${item.id} ${compConf.autofocus}`)
- }
- if (inputElem) {
- inputElem.focus()
- // 保持一致行为,光标移到末端
- if (DomTools.browse.msie) {
- const textRange = inputElem.createTextRange()
- textRange.collapse(false)
- textRange.select()
- }
- return true
- }
- }
- })
- },
- /**
- * 更新项状态
- * 如果组件值 v-model 发生 change 时,调用改函数用于更新某一项编辑状态
- * 如果单元格配置了校验规则,则会进行校验
- */
- updateStatus (scope, itemValue) {
- const { property } = scope
- if (property) {
- this.validItemRules('change', property, itemValue)
- .then(() => {
- this.clearValidate(property)
- })
- .catch(({ rule }) => {
- const itemList = this.getItems()
- const item = itemList.find(item => item.field === property)
- if (item) {
- item.showError = true
- item.errRule = rule
- }
- })
- }
- }
- }
- }
|