| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- <template>
- <div ref="textRef" class="input textarea" :class="{ suffix: $slots.icon || maxlength, disabled, right }">
- <div
- ref="inputRef"
- contenteditable="true"
- class="ui-text input-div"
- :placeholder="props.placeholder"
- :readonly="readonly"
- v-bind="other"
- @input="inputHandler"
- @click="emit('click')"
- @focus="focusHandler"
- @blur="blurHandler"
- @paste="pasteHandler"
- @compositionstart="compositionstartHandler"
- @compositionend="compositionendHandler"
- />
- <span class="replace" />
- <span v-if="$slots.icon || props.maxlength" class="retouch">
- <slot name="icon" />
- <span v-if="props.maxlength" class="len">
- <span>{{ length }}</span> / {{ maxlength }}
- </span>
- </span>
- </div>
- </template>
- <script setup lang="ts">
- import { defineEmits, defineExpose, defineProps, nextTick, ref, watchEffect } from 'vue'
- import { richTextProps } from './richtext'
- const props = defineProps(richTextProps)
- const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click', ''])
- const textRef = ref(null)
- const inputRef = ref(null)
- const length = ref(0)
- const updateContent = html => {
- inputRef.value.innerHTML = html
- length.value = inputRef.value.textContent.length
- }
- watchEffect(() => {
- if (inputRef.value && props.modelValue !== inputRef.value.innerHTML) {
- updateContent(props.modelValue)
- }
- })
- let inComposition = false
- const compositionstartHandler = () => {
- inComposition = true
- }
- const compositionendHandler = (ev: any) => {
- inComposition = false
- inputHandler(ev)
- }
- const inputHandler = (ev: any) => {
- if (inComposition) return
- if (!props.maxlength || ev.target.textContent.length <= Number(props.maxlength)) {
- length.value = inputRef.value.textContent.length
- emit('update:modelValue', ev.target.innerHTML)
- } else {
- nextTick(() => {
- if (ev.target.innerHTML !== props.modelValue.toString()) {
- updateContent(props.modelValue.toString())
- inputFocus()
- }
- })
- }
- }
- //获取当前光标位置
- const getCursortPosition = function (element = inputRef.value) {
- let caretOffset = 0
- const doc = element.ownerDocument || element.document
- const win = doc.defaultView || doc.parentWindow
- let sel
- if (typeof win.getSelection != 'undefined') {
- //谷歌、火狐
- sel = win.getSelection()
- if (sel.rangeCount > 0) {
- //选中的区域
- const range = win.getSelection().getRangeAt(0)
- const preCaretRange = range.cloneRange() //克隆一个选中区域
- preCaretRange.selectNodeContents(element) //设置选中区域的节点内容为当前节点
- preCaretRange.setEnd(range.endContainer, range.endOffset) //重置选中区域的结束位置
- caretOffset = preCaretRange.toString().length
- }
- } else if ((sel = doc.selection) && sel.type != 'Control') {
- //IE
- const textRange = sel.createRange()
- const preCaretTextRange = doc.body.createTextRange()
- preCaretTextRange.moveToElementText(element)
- preCaretTextRange.setEndPoint('EndToEnd', textRange)
- caretOffset = preCaretTextRange.text.length
- }
- return caretOffset
- }
- let interval: number
- const focusHandler = () => {
- clearInterval(interval)
- interval = window.setInterval(() => {
- console.log(getCursortPosition())
- emit('updatePos', getCursortPosition())
- }, 100)
- emit('focus')
- }
- const blurHandler = () => {
- clearInterval(interval)
- emit('blur')
- }
- const inputFocus = () => {
- inputRef.value.focus()
- const range = window.getSelection()
- range.selectAllChildren(inputRef.value)
- range.collapseToEnd()
- }
- const getPasteText = text => {
- if (!props.maxlength) {
- return text
- }
- const $el = document.createElement('div')
- $el.innerHTML = text
- if ($el.textContent.length > props.maxlength - length.value) {
- return $el.textContent.slice(0, Math.max(0, props.maxlength - length.value))
- } else {
- return text
- }
- }
- const pasteHandler = event => {
- event.preventDefault()
- let text
- const clp = (event.originalEvent || event).clipboardData
- // 兼容针对于opera ie等浏览器
- if (clp === undefined || clp === null) {
- text = window.clipboardData.getData('text') || ''
- if (text !== '') {
- if (window.getSelection) {
- // 针对于ie11 10 9 safari
- const newNode = document.createElement('span')
- newNode.innerHTML = getPasteText(text)
- window.getSelection().getRangeAt(0).insertNode(newNode)
- } else {
- document.selection.createRange().pasteHTML(text)
- }
- }
- } else {
- // 兼容chorme或hotfire
- text = clp.getData('text/plain') || ''
- if (text !== '') {
- document.execCommand('insertText', false, getPasteText(text))
- }
- }
- }
- defineExpose({
- root: textRef,
- input: inputRef,
- getCursortPosition,
- })
- </script>
|