This commit is contained in:
X1627315083
2025-11-19 10:17:18 +08:00
parent 2c9c73b1d9
commit 4dcf4b1eb2

View File

@@ -22,7 +22,7 @@ const data = reactive({
// { id: '3', type: 'text', value: '333333' }, // { id: '3', type: 'text', value: '333333' },
// { id: '4', type: 'input', value: '', placeholder: '[请输入内容]' } // { id: '4', type: 'input', value: '', placeholder: '[请输入内容]' }
// ] as ContentItem[] // ] as ContentItem[]
content: props.content content: [ { id: '1', type: 'text', value: '' },],
}) })
const editableArea = ref<HTMLElement>() const editableArea = ref<HTMLElement>()
@@ -40,9 +40,9 @@ const compositionState = reactive({
// 检查并删除末尾的空文本框 // 检查并删除末尾的空文本框
const removeLastEmptyTextIfNeeded = () => { const removeLastEmptyTextIfNeeded = () => {
const lastItem = content.value[content.value.length - 1] const lastItem = data.content[data.content.length - 1]
if (lastItem && lastItem.type === 'text' && lastItem.value === '') { if (lastItem && lastItem.type === 'text' && lastItem.value === '') {
content.value.pop() data.content.pop()
return true return true
} }
return false return false
@@ -50,14 +50,14 @@ const removeLastEmptyTextIfNeeded = () => {
// 确保末尾有空文本框 // 确保末尾有空文本框
const ensureEmptyTextAtEnd = () => { const ensureEmptyTextAtEnd = () => {
const lastItem = content.value[content.value.length - 1] const lastItem = data.content[data.content.length - 1]
if (!lastItem || lastItem.type !== 'text' || lastItem.value !== '') { if (!lastItem || lastItem.type !== 'text' || lastItem.value !== '') {
const newItem: ContentItem = { const newItem: ContentItem = {
id: Date.now().toString(), id: Date.now().toString(),
type: 'text', type: 'text',
value: '' value: ''
} }
content.value.push(newItem) data.content.push(newItem)
return true return true
} }
return false return false
@@ -96,7 +96,7 @@ const getCurrentElementInfo = () => {
index = parseInt(parent?.getAttribute('data-index') || '-1') index = parseInt(parent?.getAttribute('data-index') || '-1')
type = 'input' type = 'input'
const item = content.value[index] const item = data.content[index]
if (element.classList.contains('has-placeholder')) { if (element.classList.contains('has-placeholder')) {
// placeholder状态下光标在任意位置都认为是"在元素内" // placeholder状态下光标在任意位置都认为是"在元素内"
isAtStart = range.startOffset === 0 isAtStart = range.startOffset === 0
@@ -172,10 +172,10 @@ const handleKeydown = (event: KeyboardEvent) => {
break break
case 'ArrowRight': case 'ArrowRight':
if (isAtEnd && index < content.value.length - 1) { if (isAtEnd && index < data.content.length - 1) {
event.preventDefault() event.preventDefault()
navigateToElement(index + 1, 'start') navigateToElement(index + 1, 'start')
} else if (isAtEnd && index === content.value.length - 1) { } else if (isAtEnd && index === data.content.length - 1) {
// 在最后一个元素末尾按右箭头,确保有一个空文本框 // 在最后一个元素末尾按右箭头,确保有一个空文本框
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
nextTick(() => { nextTick(() => {
@@ -189,26 +189,26 @@ const handleKeydown = (event: KeyboardEvent) => {
// 跨元素删除逻辑 // 跨元素删除逻辑
const handleCrossElementDelete = (currentIndex: number) => { const handleCrossElementDelete = (currentIndex: number) => {
const prevIndex = currentIndex - 1 const prevIndex = currentIndex - 1
const prevItem = content.value[prevIndex] const prevItem = data.content[prevIndex]
if (prevItem.type === 'input') { if (prevItem.type === 'input') {
if (prevItem.value.trim() === '') { if (prevItem.value.trim() === '') {
// 删除空输入框 // 删除空输入框
content.value.splice(prevIndex, 1) data.content.splice(prevIndex, 1)
nextTick(() => { nextTick(() => {
// 删除输入框后,先删除末尾的空文本框 // 删除输入框后,先删除末尾的空文本框
removeLastEmptyTextIfNeeded() removeLastEmptyTextIfNeeded()
// 然后聚焦到正确的位置 // 然后聚焦到正确的位置
if (prevIndex < content.value.length) { if (prevIndex < data.content.length) {
focusElement(prevIndex, 'end') focusElement(prevIndex, 'end')
} else if (content.value.length > 0) { } else if (data.content.length > 0) {
focusElement(content.value.length - 1, 'end') focusElement(data.content.length - 1, 'end')
} }
}) })
} else { } else {
// 删除输入框最后一个字符,但保留输入框 // 删除输入框最后一个字符,但保留输入框
const newValue = prevItem.value.slice(0, -1) const newValue = prevItem.value.slice(0, -1)
content.value[prevIndex].value = newValue data.content[prevIndex].value = newValue
updateInputDisplay(prevIndex) updateInputDisplay(prevIndex)
nextTick(() => focusElement(prevIndex, 'end')) nextTick(() => focusElement(prevIndex, 'end'))
} }
@@ -222,7 +222,7 @@ const handleCrossElementDelete = (currentIndex: number) => {
// 导航到元素 // 导航到元素
const navigateToElement = (targetIndex: number, position: 'start' | 'end') => { const navigateToElement = (targetIndex: number, position: 'start' | 'end') => {
const targetItem = content.value[targetIndex] const targetItem = data.content[targetIndex]
const element = const element =
targetItem.type === 'text' targetItem.type === 'text'
? (editableArea.value?.querySelector( ? (editableArea.value?.querySelector(
@@ -237,7 +237,7 @@ const navigateToElement = (targetIndex: number, position: 'start' | 'end') => {
// 焦点设置 // 焦点设置
const focusElement = (index: number, position: 'start' | 'end') => { const focusElement = (index: number, position: 'start' | 'end') => {
const item = content.value[index] const item = data.content[index]
const element = const element =
item.type === 'text' item.type === 'text'
? (editableArea.value?.querySelector( ? (editableArea.value?.querySelector(
@@ -252,7 +252,7 @@ const focusElement = (index: number, position: 'start' | 'end') => {
// 输入框显示管理 // 输入框显示管理
const updateInputDisplay = (index: number) => { const updateInputDisplay = (index: number) => {
const item = content.value[index] const item = data.content[index]
if (item.type !== 'input') return if (item.type !== 'input') return
const inputElement = editableArea.value?.querySelector( const inputElement = editableArea.value?.querySelector(
@@ -279,13 +279,13 @@ const handleInputChange = (index: number, event: Event) => {
} }
const target = event.target as HTMLSpanElement const target = event.target as HTMLSpanElement
const item = content.value[index] const item = data.content[index]
// 如果当前显示placeholder但内容已改变比如通过粘贴清除placeholder // 如果当前显示placeholder但内容已改变比如通过粘贴清除placeholder
if (target.classList.contains('has-placeholder') && target.textContent !== item.placeholder) { if (target.classList.contains('has-placeholder') && target.textContent !== item.placeholder) {
target.classList.remove('has-placeholder') target.classList.remove('has-placeholder')
const newValue = target.textContent || '' const newValue = target.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder // 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -298,7 +298,7 @@ const handleInputChange = (index: number, event: Event) => {
// 正常情况下的处理 // 正常情况下的处理
if (!target.classList.contains('has-placeholder')) { if (!target.classList.contains('has-placeholder')) {
const newValue = target.textContent || '' const newValue = target.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果内容变空显示placeholder // 如果内容变空显示placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -311,7 +311,7 @@ const handleInputChange = (index: number, event: Event) => {
// 添加专门的粘贴事件处理 // 添加专门的粘贴事件处理
const handleInputPaste = (event: ClipboardEvent, index: number) => { const handleInputPaste = (event: ClipboardEvent, index: number) => {
const target = event.target as HTMLSpanElement const target = event.target as HTMLSpanElement
const item = content.value[index] const item = data.content[index]
// 如果当前显示 placeholder先清除它 // 如果当前显示 placeholder先清除它
if (target.classList.contains('has-placeholder')) { if (target.classList.contains('has-placeholder')) {
@@ -325,7 +325,7 @@ const handleInputPaste = (event: ClipboardEvent, index: number) => {
// 更新值 // 更新值
const newValue = target.textContent || '' const newValue = target.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder // 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -362,7 +362,7 @@ const handleInputKeydown = (event: KeyboardEvent, index: number) => {
event.preventDefault() event.preventDefault()
target.textContent = event.key target.textContent = event.key
target.classList.remove('has-placeholder') target.classList.remove('has-placeholder')
content.value[index].value = event.key data.content[index].value = event.key
// 移动光标到末尾 // 移动光标到末尾
nextTick(() => { nextTick(() => {
@@ -380,14 +380,14 @@ const handleInputKeydown = (event: KeyboardEvent, index: number) => {
} }
const handlePasteInInput = (index: number, element: HTMLElement) => { const handlePasteInInput = (index: number, element: HTMLElement) => {
const item = content.value[index] const item = data.content[index]
// 如果当前显示 placeholder清除它 // 如果当前显示 placeholder清除它
if (element.classList.contains('has-placeholder')) { if (element.classList.contains('has-placeholder')) {
element.classList.remove('has-placeholder') element.classList.remove('has-placeholder')
// 获取粘贴后的实际内容 // 获取粘贴后的实际内容
const newValue = element.textContent || '' const newValue = element.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder // 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -397,7 +397,7 @@ const handlePasteInInput = (index: number, element: HTMLElement) => {
} else { } else {
// 正常情况,直接更新值 // 正常情况,直接更新值
const newValue = element.textContent || '' const newValue = element.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 检查是否需要显示 placeholder // 检查是否需要显示 placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -417,7 +417,7 @@ const handleCompositionStart = (index: number, event: CompositionEvent) => {
if (target.classList.contains('has-placeholder')) { if (target.classList.contains('has-placeholder')) {
target.classList.remove('has-placeholder') target.classList.remove('has-placeholder')
target.textContent = '' target.textContent = ''
content.value[index].value = '' data.content[index].value = ''
} }
} }
@@ -427,10 +427,10 @@ const handleCompositionEnd = (index: number, event: CompositionEvent) => {
const target = event.target as HTMLSpanElement const target = event.target as HTMLSpanElement
const newValue = target.textContent || '' const newValue = target.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果中文输入后内容为空显示placeholder // 如果中文输入后内容为空显示placeholder
const item = content.value[index] const item = data.content[index]
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder') target.classList.add('has-placeholder')
target.textContent = item.placeholder target.textContent = item.placeholder
@@ -454,7 +454,7 @@ const handleContainerClick = (event: MouseEvent) => {
// 确保末尾有空文本框并聚焦到它 // 确保末尾有空文本框并聚焦到它
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
nextTick(() => { nextTick(() => {
focusElement(content.value.length - 1, 'start') focusElement(data.content.length - 1, 'start')
}) })
} }
} }
@@ -462,7 +462,7 @@ const handleContainerClick = (event: MouseEvent) => {
// 初始化 // 初始化
const initPlaceholders = () => { const initPlaceholders = () => {
nextTick(() => { nextTick(() => {
content.value.forEach((_, index) => updateInputDisplay(index)) data.content.forEach((_, index) => updateInputDisplay(index))
// 确保初始状态下有一个空文本框 // 确保初始状态下有一个空文本框
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
}) })
@@ -470,7 +470,7 @@ const initPlaceholders = () => {
const getFullText = () => { const getFullText = () => {
if (assistModel.value) { if (assistModel.value) {
return content.value return data.content
.map(item => { .map(item => {
if (item.type === 'text') { if (item.type === 'text') {
return item.value return item.value
@@ -498,12 +498,15 @@ const textareaValue = ref('')
const assistModel = ref(false) const assistModel = ref(false)
const handleClickAssistBtn = () => { const handleClickAssistBtn = () => {
assistModel.value = !assistModel.value assistModel.value = !assistModel.value
if(assistModel.value){
data.content = JSON.parse(JSON.stringify(props.content))
initPlaceholders()
}
} }
const textareaRef = useTemplateRef<HTMLTextAreaElement>('textareaRef') const textareaRef = useTemplateRef<HTMLTextAreaElement>('textareaRef')
onMounted(() => { onMounted(() => {
initPlaceholders()
}) })
defineExpose({ defineExpose({
@@ -533,7 +536,7 @@ defineExpose({
@click="handleContainerClick" @click="handleContainerClick"
> >
<div class="promptinput-wrapper"> <div class="promptinput-wrapper">
<template v-for="(item, index) in content" :key="item.id"> <template v-for="(item, index) in data.content" :key="item.id">
<span <span
v-if="item.type === 'text'" v-if="item.type === 'text'"
class="text-field" class="text-field"