perf:【IoT 物联网】场景联动场景描述输入组件优化使用 teleport 传送到 body,不受父容器 overflow 影响
parent
7f4d3d72f8
commit
7723358422
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue