You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue组件中如何在contenteditable div光标位置插入span元素

解决contenteditable元素中光标位置插入动态变量标签的问题

你的核心问题是打开下拉菜单时,contenteditable元素失去焦点,导致原光标位置的Selection/Ranges丢失,所以点击变量时无法获取到之前的光标位置。下面是修复方案:

关键修改点

  1. 保存光标位置:在打开下拉菜单前,记录当前contenteditable元素的光标范围
  2. 插入标签时使用保存的范围,而不是实时获取Selection(此时焦点在下拉菜单上,Selection已经改变)

修改后的完整代码

<template>
  <div class="relative inline-flex items-center">
    <button type="button" @click="toggleDropdown" class="inline-flex items-center gap-x-1.5 rounded-full bg-green-100 hover:bg-green-50 px-2 py-1 text-xs font-medium text-green-700">
      <BoltIcon class="h-3 w-3 fill-green-700" viewBox="0 0 12 12" aria-hidden="true"></BoltIcon>
      Add Dynamic Variable
    </button>
    <div v-if="dropdownVisible" class="">
      <div class="py-1 absolute right-0 z-10 mt-4 origin-top-right rounded-md bg-green-100 shadow-lg ring-1 ring-white/20 focus:outline-none">
        <div v-for="variable in variables" :key="variable" class="pl-3 pr-4 py-2 text-xs flex flex-row font-medium text-green-700 cursor-pointer hover:bg-green-50" @click="selectVariable(variable)">
          <BoltIcon class="h-3 w-3 mr-1 fill-green-700" viewBox="0 0 12 12" aria-hidden="true"></BoltIcon>
          {{ variable }}
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { BoltIcon } from '@heroicons/vue/24/solid';

const props = defineProps({
  variables: {
    type: Array,
    required: true,
  },
  targetElementId: {
    type: String,
    required: true,
  },
});

const dropdownVisible = ref(false);
const inputField = ref(null);
const savedRange = ref(null); // 用于保存光标位置的Range对象

const toggleDropdown = () => {
  if (inputField.value) {
    // 打开下拉前保存当前光标位置
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      savedRange.value = selection.getRangeAt(0);
    }
    // 如果是关闭下拉,恢复焦点到输入框
    if (!dropdownVisible.value) {
      inputField.value.focus();
      // 恢复光标位置(可选,提升体验)
      if (savedRange.value) {
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(savedRange.value);
      }
    }
  }
  dropdownVisible.value = !dropdownVisible.value;
};

const selectVariable = (variable) => {
  const inputEl = inputField.value;

  if (!inputEl) {
    console.error(`Input field with id ${props.targetElementId} not found.`);
    return;
  }

  const variableLabel = document.createElement('span');
  variableLabel.className =
    'inline-flex items-center gap-x-0.5 rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20';
  variableLabel.innerHTML = 
    `${variable}
    <button type="button" class="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-green-600/20">
      <span class="sr-only">Remove</span>
      <svg viewBox="0 0 14 14" class="h-3.5 w-3.5 stroke-green-700/50 group-hover:stroke-green-700/75">
        <path d="M4 4l6 6m0-6l-6 6" />
      </svg>
      <span class="absolute -inset-1"></span>
    </button>`;

  variableLabel.setAttribute('contenteditable', 'false');
  variableLabel.querySelector('button').addEventListener('click', () => {
    variableLabel.remove();
  });

  // 使用保存的光标位置插入标签
  if (savedRange.value) {
    const range = savedRange.value;
    range.deleteContents();
    range.insertNode(variableLabel);
    // 插入后将光标移到标签后面
    range.collapse(false);
    const selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(range);
    savedRange.value = null; // 清空保存的范围
  } else {
    // 没有保存的光标位置时,默认插入到末尾
    inputEl.appendChild(variableLabel);
  }

  dropdownVisible.value = false;
  inputEl.focus(); // 插入后恢复焦点
};

onMounted(() => {
  inputField.value = document.getElementById(props.targetElementId);
});
</script>

<style scoped>
</style>

核心逻辑说明

  • 添加savedRange变量存储打开下拉菜单前的光标范围,因为点击下拉项时,contenteditable元素已经失去焦点,实时获取的Selection不再指向原光标位置
  • 在toggleDropdown中,打开下拉前保存当前Range;关闭下拉时可选恢复光标位置,提升用户体验
  • 在selectVariable中,优先使用保存的savedRange插入标签,插入后将光标移到标签后方,保持输入连贯性
  • 插入完成后恢复contenteditable元素的焦点,确保后续输入正常

内容的提问来源于stack exchange,提问作者M P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 02:24:54