Vue 2 contentEditable 与 v-model

新手上路,请多包涵

我正在尝试制作一个类似于 Medium 的文本编辑器。我正在使用内容可编辑的段落标签并将每个项目存储在一个数组中,并使用 v-for 渲染每个项目。但是,我在使用 v-model 将文本与数组绑定时遇到了问题。似乎与 v-model 和 contenteditable 属性有冲突。这是我的代码:

 <div id="editbar">
     <button class="toolbar" v-on:click.prevent="stylize('bold')">Bold</button>
</div>
<div v-for="(value, index) in content">
     <p v-bind:id="'content-'+index" v-bind:ref="'content-'+index" v-model="content[index].value" v-on:keyup="emit_content($event)" v-on:keyup.delete="remove_content(index)" contenteditable></p>
</div>

在我的脚本中:

 export default {
   data() {
      return {
         content: [{ value: ''}]
      }
   },
   methods: {
      stylize(style) {
         document.execCommand(style, false, null);
      },
      remove_content(index) {
         if(this.content.length > 1 && this.content[index].value.length == 0) {
            this.content.splice(index, 1);
         }
      }
   }
}

我没有在网上找到任何答案。

原文由 Soubriquet 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 909
2 个回答

我昨天想通了!确定了这个解决方案。我基本上只是手动跟踪我的 content 数组中的 innerHTML,方法是更新任何可能的事件并通过手动分配具有动态引用的相应元素重新渲染,例如 content-0content-1 ,… 工作精美:

 <template>
   <div id="editbar">
       <button class="toolbar" v-on:click.prevent="stylize('bold')">Bold</button>
   </div>
   <div>
      <div v-for="(value, index) in content">
          <p v-bind:id="'content-'+index" class="content" v-bind:ref="'content-'+index" v-on:keydown.enter="prevent_nl($event)" v-on:keyup.enter="add_content(index)" v-on:keyup.delete="remove_content(index)" contenteditable></p>
      </div>
   </div>
</template>
<script>
export default {
   data() {
      return {
         content: [{
            html: ''
         }]
      }
   },
   methods: {
      add_content(index) {
        //append to array
      },
      remove_content(index) {
        //first, check some edge conditions and remove from array

        //then, update innerHTML of each element by ref
        for(var i = 0; i < this.content.length; i++) {
           this.$refs['content-'+i][0].innerHTML = this.content[i].html;
        }
      },
      stylize(style){
         document.execCommand(style, false, null);
         for(var i = 0; i < this.content.length; i++) {
            this.content[i].html = this.$refs['content-'+i][0].innerHTML;
         }
      }
   }
}
</script>

原文由 Soubriquet 发布,翻译遵循 CC BY-SA 4.0 许可协议

我尝试了一个示例,并且 eslint-plugin-vue 报告说 v-model p 。请参阅 valid-v-model 规则。

在撰写本文时,Vue 似乎并不直接支持您想要的。我将介绍两种通用解决方案:

直接在可编辑元素上使用输入事件

<template>
  <p
    contenteditable
    @input="onInput"
  >
    {{ content }}
  </p>
</template>

<script>
export default {
  data() {
    return { content: 'hello world' };
  },
  methods: {
    onInput(e) {
      console.log(e.target.innerText);
    },
  },
};
</script>


创建可重用的可编辑组件

可编辑的.vue

 <template>
  <p
    ref="editable"
    contenteditable
    v-on="listeners"
  />
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  computed: {
    listeners() {
      return { ...this.$listeners, input: this.onInput };
    },
  },
  mounted() {
    this.$refs.editable.innerText = this.value;
  },
  methods: {
    onInput(e) {
      this.$emit('input', e.target.innerText);
    },
  },
};
</script>

索引.vue

 <template>
  <Editable v-model="content" />
</template>

<script>
import Editable from '~/components/Editable';

export default {
  components: { Editable },
  data() {
    return { content: 'hello world' };
  },
};
</script>


针对您的特定问题的定制解决方案

经过多次迭代,我发现对于您的用例, 使用单独的组件更容易获得有效的解决方案。似乎 contenteditable 元素非常棘手——尤其是在列表中呈现时。我发现我必须在删除后手动更新每个 p innerText 的 --- 才能使其正常工作。我还发现使用 ids 有效,但使用 refs 没有。

可能有一种方法可以在模型和内容之间实现完整的双向绑定,但我认为这需要在每次更改后操作光标位置。

 <template>
  <div>
    <p
      v-for="(value, index) in content"
      :id="`content-${index}`"
      :key="index"
      contenteditable
      @input="event => onInput(event, index)"
      @keyup.delete="onRemove(index)"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      content: [
        { value: 'paragraph 1' },
        { value: 'paragraph 2' },
        { value: 'paragraph 3' },
      ],
    };
  },
  mounted() {
    this.updateAllContent();
  },
  methods: {
    onInput(event, index) {
      const value = event.target.innerText;
      this.content[index].value = value;
    },
    onRemove(index) {
      if (this.content.length > 1 && this.content[index].value.length === 0) {
        this.$delete(this.content, index);
        this.updateAllContent();
      }
    },
    updateAllContent() {
      this.content.forEach((c, index) => {
        const el = document.getElementById(`content-${index}`);
        el.innerText = c.value;
      });
    },
  },
};
</script>

原文由 David Weldon 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏