【无标题】vue中实现可编辑的div,且可插入动态参数

<template>
  <div id="app">
    <el-dialog v-dialogDrag title="参数设置" :visible.sync="dialogVisible" @close="closeDialog" width="460px">
      <el-form ref="form" label-width="80px">
        <el-form-item label="参数名称:">
           <el-input v-model="dynamicParam" placeholder="请输入参数" style="width:250px" @input="InputData"></el-input>
        </el-form-item>
      </el-form>
      <div style="margin-left:80px;margin-top:8px;width:308px;font-size:12px;color:#b9b9b9;line-height:21px">
        key :字母、数字、下划线组成;长度1~20个半角字符;开头必须为英文字母;不能为纯数字
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="updateDynamicParam">确 定</el-button>
      </span>
    </el-dialog>
    <div class="editable-container">
      <div
        ref="editor"
        contenteditable="true"
        @input="handleInput"
        @keydown="handleKeydown"
        @focus="handleFocus"
        @click="handleClick"
        class="editable-div"
      ></div>
      <div class="char-counter">{{ charCount }}/{{ maxLength }}</div>
    </div>
    <el-button @click="openDialog">插入参数</el-button>
    <el-button @click="logContent">输出内容</el-button>
  </div>
</template>

<script>

export default {
  name: 'copyInput',
  data() {
    return {
      dialogVisible: false,
      dynamicParam: '',
      maxLength: 960,
      charCount: 0,
      savedRange: null,
      editingButton: null,
    };
  },
  methods: {
    openDialog() {
      const sel = window.getSelection();
      if (sel.rangeCount > 0) {
        this.savedRange = sel.getRangeAt(0);
      }
      this.dialogVisible = true;
    },
    closeDialog() {
      this.dialogVisible = false;
      this.dynamicParam = '';
      this.editingButton = null;
    },
    InputData(val) {
      if (val && val.length > 20) {
        val = val.slice(0, 20);
      }
      this.dynamicParam = val.replace(/[^a-z0-9_]/g, '');
    },
    insertDynamicParam() {
      const regT = /^[a-z][a-z0-9_]*$/g;
      if (!regT.test(this.dynamicParam)) {
        this.$message.error('参数名称需以字母开头,且不能为纯数字!');
        return
      }
      const paramValue = this.dynamicParam.trim();
      if (paramValue) {
        const buttonHtml = `<input type="button" class="j-btn" unselectable="on" readonly="" value="\${${paramValue}}">`;
        this.insertHtmlAtCursor(buttonHtml);
        this.dialogVisible = false;
        this.dynamicParam = '';
      }
    },
    updateDynamicParam() {
      if (this.editingButton) {
        this.editingButton.value = `\${${this.dynamicParam}}`;
        this.handleInput();
        this.closeDialog();
      } else {
        this.insertDynamicParam();
      }
    },
    insertHtmlAtCursor(html) {
      const editor = this.$refs.editor;
      editor.focus();
      if (this.savedRange) {
        const sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(this.savedRange);

        var range = sel.getRangeAt(0);
        range.deleteContents();
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = html;
        const frag = document.createDocumentFragment();
        let node;
        let lastNode;
        while ((node = tempDiv.firstChild)) {
          lastNode = frag.appendChild(node);
        }
        range.insertNode(frag);
        if (lastNode) {
          range.setStartAfter(lastNode);
          range.collapse(true);
          sel.removeAllRanges();
          sel.addRange(range);
        }
        this.savedRange = null;
      }
      this.handleInput();
    },
    handleInput() {
      const editor = this.$refs.editor;
      const textContent = this.getTextContent(editor);
      this.charCount = textContent.length;

      if (this.charCount > this.maxLength) {
        editor.innerHTML = editor.innerHTML.slice(0, this.maxLength);
        this.restoreHighlight(editor);
        this.charCount = this.maxLength;
        editor.focus();
        document.execCommand('selectAll', false, null);
        document.getSelection().collapseToEnd();
      }
    },
    handleKeydown(event) {
      if (this.charCount >= this.maxLength && event.key !== 'Backspace') {
        event.preventDefault();
      }
    },
    restoreHighlight(editor) {
      const buttons = editor.querySelectorAll('.j-btn');
      buttons.forEach(button => {
        button.setAttribute('unselectable', 'on');
        button.setAttribute('readonly', '');
        button.classList.add('j-btn');
      });
    },
    getTextContent(element) {
      let text = '';
      element.childNodes.forEach(node => {
        if (node.nodeType === Node.TEXT_NODE) {
          text += node.textContent;
        } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'INPUT' && node.classList.contains('j-btn')) {
          text += node.value;
        } else if (node.nodeType === Node.ELEMENT_NODE) {
          text += this.getTextContent(node);
        }
      });
      return text;
    },
    logContent() {
      const editor = this.$refs.editor;
      const textContent = this.getTextContent(editor);
      console.log(textContent);
    },
    handleFocus(event) {
      const editor = this.$refs.editor;
      const sel = window.getSelection();
      if (sel.rangeCount === 0) {
        const range = document.createRange();
        range.setStart(editor, 0);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    },
    handleClick(event) {
      if (event.target.classList.contains('j-btn')) {
        this.editingButton = event.target;
        this.dynamicParam = this.editingButton.value.replace(/^\$\{|\}$/g, '');
        this.dialogVisible = true;
      }
    },
  },
};
</script>

<style>
.editable-container {
  position: relative;
  width: 500px;
}

.editable-div {
  -webkit-user-modify: read-write-plaintext-only;
  -moz-user-modify: read-write-plaintext-only;
  width: 100%;
  height: 200px;
  overflow-y: auto;
  border: 1px solid #ccc;
  border-radius: 2px;
  padding: 5px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
  box-sizing: border-box;
}

.char-counter {
  position: absolute;
  bottom: 5px;
  right: 10px;
  font-size: 12px;
  color: #999;
}

.j-btn {
  color: #1a8de5;
  border: none;
  background-color: transparent;
  cursor: pointer;
}
</style>

细节说明:

  • openDialog 方法在弹框打开时保存当前的选区。
  • insertHtmlAtCursor 方法在插入HTML时恢复保存的选区,然后在选区位置插入HTML。
  • 插入完成后,清空保存的选区(this.savedRange = null),确保每次插入动态参数时都使用最新的光标位置。
  • charCount 数据属性:用于跟踪当前的字符数。
  • handleInput 方法:在每次输入时更新 charCount
  • editable-container:用于包裹编辑框和字符计数器。
  • char-counter:用于在编辑框右下角显示字符数。
  • getTextContent 方法:递归遍历编辑框的子节点,提取文本节点内容和input标签的value值,以计算实际的字符数。
  • handleFocus 方法:在focus事件处理程序中,确保在IE浏览器中可以正确聚焦。使用window.getSelection()document.createRange()来手动设置选择范围。

  • @focus 事件监听器:在<div>元素上添加@focus事件监听器,以便在焦点事件发生时调用handleFocus方法。

  • handleClick 方法:当用户点击动态参数按钮时,打开弹框并允许编辑按钮的value值。
  • editingButton 数据属性:保存当前正在编辑的动态参数按钮。
  • updateDynamicParam 方法:如果正在编辑的按钮存在,更新其value属性值;否则插入新的动态参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值