vxe-table-demonic
Version:
一个基于 vue 的 PC 端表单/表格组件,支持增删改查、虚拟列表、虚拟树、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、弹窗、自定义模板、渲染器、JSON 配置式...
268 lines (238 loc) • 9.5 kB
text/typescript
import { defineComponent, h, ref, Ref, computed, nextTick, watch, PropType, reactive, inject } from 'vue'
import XEUtils from 'xe-utils'
import GlobalConfig from '../../v-x-e-table/src/conf'
import { getFuncText } from '../../tools/utils'
import { useSize } from '../../hooks/size'
import { VxeTextareaPropTypes, TextareaReactData, TextareaMethods, VxeTextareaConstructor, VxeTextareaEmits, TextareaPrivateRef, VxeFormConstructor, VxeFormPrivateMethods, VxeFormDefines } from '../../../types/all'
let autoTxtElem: HTMLDivElement
export default defineComponent({
name: 'VxeTextarea',
props: {
modelValue: [String, Number] as PropType<VxeTextareaPropTypes.ModelValue>,
className: String as PropType<VxeTextareaPropTypes.ClassName>,
immediate: { type: Boolean as PropType<VxeTextareaPropTypes.Immediate>, default: true },
name: String as PropType<VxeTextareaPropTypes.Name>,
readonly: Boolean as PropType<VxeTextareaPropTypes.Readonly>,
disabled: Boolean as PropType<VxeTextareaPropTypes.Disabled>,
placeholder: {
type: String as PropType<VxeTextareaPropTypes.Placeholder>
},
maxlength: [String, Number] as PropType<VxeTextareaPropTypes.Maxlength>,
rows: { type: [String, Number] as PropType<VxeTextareaPropTypes.Rows>, default: 2 },
cols: { type: [String, Number] as PropType<VxeTextareaPropTypes.Cols>, default: null },
showWordCount: Boolean as PropType<VxeTextareaPropTypes.ShowWordCount>,
countMethod: Function as PropType<VxeTextareaPropTypes.CountMethod>,
autosize: [Boolean, Object] as PropType<VxeTextareaPropTypes.Autosize>,
form: String as PropType<VxeTextareaPropTypes.Form>,
resize: { type: String as PropType<VxeTextareaPropTypes.Resize>, default: () => GlobalConfig.textarea.resize },
size: { type: String as PropType<VxeTextareaPropTypes.Size>, default: () => GlobalConfig.textarea.size || GlobalConfig.size }
},
emits: [
'update:modelValue',
'input',
'keydown',
'keyup',
'click',
'change',
'focus',
'blur'
] as VxeTextareaEmits,
setup (props, context) {
const { emit } = context
const $xeform = inject<VxeFormConstructor & VxeFormPrivateMethods | null>('$xeform', null)
const $xeformiteminfo = inject<VxeFormDefines.ProvideItemInfo | null>('$xeformiteminfo', null)
const xID = XEUtils.uniqueId()
const computeSize = useSize(props)
const reactData = reactive<TextareaReactData>({
inputValue: props.modelValue
})
const refElem = ref() as Ref<HTMLDivElement>
const refTextarea = ref() as Ref<HTMLTextAreaElement>
const refMaps: TextareaPrivateRef = {
refElem,
refTextarea
}
const $xetextarea = {
xID,
props,
context,
reactData,
getRefMaps: () => refMaps
} as unknown as VxeTextareaConstructor
let textareaMethods = {} as TextareaMethods
const computeInputCount = computed(() => {
return XEUtils.getSize(reactData.inputValue)
})
const computeIsCountError = computed(() => {
const inputCount = computeInputCount.value
return props.maxlength && inputCount > XEUtils.toNumber(props.maxlength)
})
const computeSizeOpts = computed(() => {
return Object.assign({ minRows: 1, maxRows: 10 }, GlobalConfig.textarea.autosize, props.autosize)
})
const updateAutoTxt = () => {
const { size, autosize } = props
const { inputValue } = reactData
if (autosize) {
if (!autoTxtElem) {
autoTxtElem = document.createElement('div')
}
if (!autoTxtElem.parentNode) {
document.body.appendChild(autoTxtElem)
}
const textElem = refTextarea.value
const textStyle = getComputedStyle(textElem)
autoTxtElem.className = ['vxe-textarea--autosize', size ? `size--${size}` : ''].join(' ')
autoTxtElem.style.width = `${textElem.clientWidth}px`
autoTxtElem.style.padding = textStyle.padding
autoTxtElem.innerText = ('' + (inputValue || ' ')).replace(/\n$/, '\n ')
}
}
const handleResize = () => {
if (props.autosize) {
nextTick(() => {
const sizeOpts = computeSizeOpts.value
const { minRows, maxRows } = sizeOpts
const textElem = refTextarea.value
const sizeHeight = autoTxtElem.clientHeight
const textStyle = getComputedStyle(textElem)
const lineHeight = XEUtils.toNumber(textStyle.lineHeight)
const paddingTop = XEUtils.toNumber(textStyle.paddingTop)
const paddingBottom = XEUtils.toNumber(textStyle.paddingBottom)
const borderTopWidth = XEUtils.toNumber(textStyle.borderTopWidth)
const borderBottomWidth = XEUtils.toNumber(textStyle.borderBottomWidth)
const intervalHeight = paddingTop + paddingBottom + borderTopWidth + borderBottomWidth
const rowNum = (sizeHeight - intervalHeight) / lineHeight
const textRows = rowNum && /[0-9]/.test('' + rowNum) ? rowNum : Math.floor(rowNum) + 1
let vaildRows = textRows
if (textRows < minRows) {
vaildRows = minRows
} else if (textRows > maxRows) {
vaildRows = maxRows
}
textElem.style.height = `${(vaildRows * lineHeight) + intervalHeight}px`
})
}
}
const triggerEvent = (evnt: Event & { type: 'focus' | 'blur' | 'change' }) => {
const value = reactData.inputValue
$xetextarea.dispatchEvent(evnt.type, { value }, evnt)
}
const emitUpdate = (value: string, evnt: Event) => {
reactData.inputValue = value
emit('update:modelValue', value)
if (XEUtils.toValueString(props.modelValue) !== value) {
textareaMethods.dispatchEvent('change', { value }, evnt)
// 自动更新校验状态
if ($xeform && $xeformiteminfo) {
$xeform.triggerItemEvent(evnt, $xeformiteminfo.itemConfig.field, value)
}
}
}
const inputEvent = (evnt: InputEvent) => {
const { immediate } = props
const textElem = evnt.target as HTMLTextAreaElement
const value = textElem.value
reactData.inputValue = value
if (immediate) {
emitUpdate(value, evnt)
}
$xetextarea.dispatchEvent('input', { value }, evnt)
handleResize()
}
const changeEvent = (evnt: Event & { type: 'change' }) => {
const { immediate } = props
if (immediate) {
triggerEvent(evnt)
} else {
emitUpdate(reactData.inputValue, evnt)
}
}
const blurEvent = (evnt: Event & { type: 'blur' }) => {
const { immediate } = props
const { inputValue } = reactData
if (!immediate) {
emitUpdate(inputValue, evnt)
}
$xetextarea.dispatchEvent('blur', { value: inputValue }, evnt)
}
textareaMethods = {
dispatchEvent (type, params, evnt) {
emit(type, Object.assign({ $textarea: $xetextarea, $event: evnt }, params))
},
focus () {
const textElem = refTextarea.value
textElem.focus()
return nextTick()
},
blur () {
const textElem = refTextarea.value
textElem.blur()
return nextTick()
}
}
Object.assign($xetextarea, textareaMethods)
watch(() => props.modelValue, (val) => {
reactData.inputValue = val
updateAutoTxt()
})
nextTick(() => {
const { autosize } = props
if (autosize) {
updateAutoTxt()
handleResize()
}
})
const renderVN = () => {
const { className, resize, placeholder, disabled, maxlength, autosize, showWordCount, countMethod, rows, cols } = props
const { inputValue } = reactData
const vSize = computeSize.value
const isCountError = computeIsCountError.value
const inputCount = computeInputCount.value
return h('div', {
ref: refElem,
class: ['vxe-textarea', className, {
[`size--${vSize}`]: vSize,
'is--autosize': autosize,
'is--count': showWordCount,
'is--disabled': disabled,
'def--rows': !XEUtils.eqNull(rows),
'def--cols': !XEUtils.eqNull(cols)
}]
}, [
h('textarea', {
ref: refTextarea,
class: 'vxe-textarea--inner',
value: inputValue,
name: props.name,
placeholder: placeholder ? getFuncText(placeholder) : null,
maxlength,
readonly: props.readonly,
disabled,
rows,
cols,
style: resize ? {
resize
} : null,
onInput: inputEvent,
onChange: changeEvent,
onKeydown: triggerEvent,
onKeyup: triggerEvent,
onClick: triggerEvent,
onFocus: triggerEvent,
onBlur: blurEvent
}),
showWordCount ? h('span', {
class: ['vxe-textarea--count', {
'is--error': isCountError
}]
}, countMethod ? `${countMethod({ value: inputValue })}` : `${inputCount}${maxlength ? `/${maxlength}` : ''}`) : null
])
}
$xetextarea.renderVN = renderVN
return $xetextarea
},
render () {
return this.renderVN()
}
})