|
|
@@ -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 {
|