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>
<div class="description-input">
<el-input
ref="inputRef"
v-model="localValue"
type="textarea"
placeholder="请输入场景描述(可选)"
@ -11,38 +12,45 @@
resize="none"
@input="handleInput"
/>
<!-- 描述模板 -->
<div v-if="showTemplates" class="templates">
<div class="templates-header">
<span class="templates-title">描述模板</span>
<el-button
type="text"
size="small"
@click="showTemplates = false"
>
<Icon icon="ep:close" />
</el-button>
</div>
<div class="templates-list">
<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>
<teleport to="body">
<div
v-if="showTemplates"
ref="templateDropdownRef"
class="templates"
:style="dropdownStyle"
>
<div class="templates-header">
<span class="templates-title">描述模板</span>
<el-button
type="text"
size="small"
@click="showTemplates = false"
>
<Icon icon="ep:close" />
</el-button>
</div>
<div class="templates-list">
<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>
</teleport>
<!-- 模板按钮 -->
<div v-if="!localValue && !showTemplates" class="template-trigger">
<el-button
type="text"
size="small"
@click="showTemplates = true"
<el-button
type="text"
size="small"
@click="toggleTemplates"
>
<Icon icon="ep:document" class="mr-1" />
使用模板
@ -73,6 +81,9 @@ const localValue = useVModel(props, 'modelValue', emit, {
})
const showTemplates = ref(false)
const templateDropdownRef = ref()
const inputRef = ref()
const dropdownStyle = ref({})
//
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) => {
if (value.length > 0) {
showTemplates.value = false
@ -108,6 +151,36 @@ const applyTemplate = (template: any) => {
localValue.value = template.content
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>
<style scoped>
@ -117,16 +190,16 @@ const applyTemplate = (template: any) => {
}
.templates {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1000;
position: fixed;
z-index: 9999;
background: white;
border: 1px solid var(--el-border-color-light);
border-radius: 4px;
box-shadow: var(--el-box-shadow-light);
margin-top: 4px;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 300px;
max-width: 400px;
max-height: 400px;
overflow: hidden;
}
.templates-header {