perf:【IoT 物联网】场景联动场景描述输入组件优化使用 teleport 传送到 body,不受父容器 overflow 影响

pull/793/head
puhui999 2025-07-17 22:14:35 +08:00
parent 7f4d3d72f8
commit 7723358422
1 changed files with 108 additions and 35 deletions

View File

@ -2,6 +2,7 @@
<template> <template>
<div class="description-input"> <div class="description-input">
<el-input <el-input
ref="inputRef"
v-model="localValue" v-model="localValue"
type="textarea" type="textarea"
placeholder="请输入场景描述(可选)" placeholder="请输入场景描述(可选)"
@ -11,38 +12,45 @@
resize="none" resize="none"
@input="handleInput" @input="handleInput"
/> />
<!-- 描述模板 --> <!-- 描述模板 -->
<div v-if="showTemplates" class="templates"> <teleport to="body">
<div class="templates-header"> <div
<span class="templates-title">描述模板</span> v-if="showTemplates"
<el-button ref="templateDropdownRef"
type="text" class="templates"
size="small" :style="dropdownStyle"
@click="showTemplates = false" >
> <div class="templates-header">
<Icon icon="ep:close" /> <span class="templates-title">描述模板</span>
</el-button> <el-button
</div> type="text"
<div class="templates-list"> size="small"
<div @click="showTemplates = false"
v-for="template in descriptionTemplates" >
:key="template.title" <Icon icon="ep:close" />
class="template-item" </el-button>
@click="applyTemplate(template)" </div>
> <div class="templates-list">
<div class="template-title">{{ template.title }}</div> <div
<div class="template-content">{{ template.content }}</div> v-for="template in descriptionTemplates"
:key="template.title"
class="template-item"
@click="applyTemplate(template)"
>
<div class="template-title">{{ template.title }}</div>
<div class="template-content">{{ template.content }}</div>
</div>
</div> </div>
</div> </div>
</div> </teleport>
<!-- 模板按钮 --> <!-- 模板按钮 -->
<div v-if="!localValue && !showTemplates" class="template-trigger"> <div v-if="!localValue && !showTemplates" class="template-trigger">
<el-button <el-button
type="text" type="text"
size="small" size="small"
@click="showTemplates = true" @click="toggleTemplates"
> >
<Icon icon="ep:document" class="mr-1" /> <Icon icon="ep:document" class="mr-1" />
使用模板 使用模板
@ -73,6 +81,9 @@ const localValue = useVModel(props, 'modelValue', emit, {
}) })
const showTemplates = ref(false) const showTemplates = ref(false)
const templateDropdownRef = ref()
const inputRef = ref()
const dropdownStyle = ref({})
// //
const descriptionTemplates = [ const descriptionTemplates = [
@ -98,6 +109,38 @@ const descriptionTemplates = [
} }
] ]
//
const calculateDropdownPosition = () => {
if (!inputRef.value) return
const inputElement = inputRef.value.$el || inputRef.value
const rect = inputElement.getBoundingClientRect()
const viewportHeight = window.innerHeight
const dropdownHeight = 300 //
let top = rect.bottom + 4
let left = rect.left
//
if (top + dropdownHeight > viewportHeight) {
top = rect.top - dropdownHeight - 4
}
//
const maxLeft = window.innerWidth - 400 //
if (left > maxLeft) {
left = maxLeft
}
if (left < 10) {
left = 10
}
dropdownStyle.value = {
top: `${top}px`,
left: `${left}px`
}
}
const handleInput = (value: string) => { const handleInput = (value: string) => {
if (value.length > 0) { if (value.length > 0) {
showTemplates.value = false showTemplates.value = false
@ -108,6 +151,36 @@ const applyTemplate = (template: any) => {
localValue.value = template.content localValue.value = template.content
showTemplates.value = false showTemplates.value = false
} }
const toggleTemplates = () => {
showTemplates.value = !showTemplates.value
if (showTemplates.value) {
nextTick(() => {
calculateDropdownPosition()
})
}
}
//
const handleClickOutside = (event: Event) => {
if (templateDropdownRef.value && !templateDropdownRef.value.contains(event.target as Node) &&
inputRef.value && !inputRef.value.$el.contains(event.target as Node)) {
showTemplates.value = false
}
}
//
onMounted(() => {
window.addEventListener('resize', calculateDropdownPosition)
window.addEventListener('scroll', calculateDropdownPosition)
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
window.removeEventListener('resize', calculateDropdownPosition)
window.removeEventListener('scroll', calculateDropdownPosition)
document.removeEventListener('click', handleClickOutside)
})
</script> </script>
<style scoped> <style scoped>
@ -117,16 +190,16 @@ const applyTemplate = (template: any) => {
} }
.templates { .templates {
position: absolute; position: fixed;
top: 100%; z-index: 9999;
left: 0;
right: 0;
z-index: 1000;
background: white; background: white;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
border-radius: 4px; border-radius: 6px;
box-shadow: var(--el-box-shadow-light); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
margin-top: 4px; min-width: 300px;
max-width: 400px;
max-height: 400px;
overflow: hidden;
} }
.templates-header { .templates-header {