html5

短信模板插入

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 3 示例</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
      .container {
        margin: 50px auto;
        padding: 20px;
        display: flex;
      }
      .left {
        width: 50%;
      }
      .form-group {
        margin-bottom: 20px;
      }
      .message-group {
        font-size: 14px;
        padding: 0 5px 5px;
        border: 1px solid rgba(0, 0, 0, 0.15);
        flex: 1;
      }
      .params {
        display: flex;
        flex: 1;
        align-items: center;
        justify-content: flex-start;
        cursor: pointer;
        flex-wrap: wrap;
      }
      .param {
        word-break: keep-all;
        margin-right: 25px;
        color: #1890ff;
        border: 1px dashed #1890ff;
        border-radius: 4px;
        padding: 2px 5px;
        span {
          padding-right: 5px;
        }
      }
      .line {
        width: 100%;
        height: 1px;
        background-color: #ddd;
        margin: 10px 0;
      }
      .form-template {
        width: 100%;
        min-height: 100px;
        box-sizing: border-box;
        font-size: 14px;
        padding: 10px;
        line-height: 1.5;
        word-break: break-word;
        resize: vertical;
        overflow: auto;
      }
      smstag {
        color: #1890ff;
        border-radius: 4px;
        padding: 1px 1px 1px 8px;
        border: 1px solid #1890ff;
        white-space: nowrap;
        margin: 0 3px;
        font-weight: 400;
        cursor: default;
        font-size: 14px;
      }
      .deleteBtn {
        color: #1890ff;
        cursor: pointer;
        margin-left: 5px;
        margin-right: 0px;
        font-size: 14px;
        height: 27px;
        display: inline-block;
        vertical-align: middle;
      }
      label {
        display: block;
        margin-bottom: 5px;
      }
      select,
      input {
        width: 100%;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="container">
        <div class="left">
          <div class="form-group">
            <label>业务场景:</label>
            <select v-model="selectedTemplate" @change="handleChange">
              <option value="" disabled hidden>请选择业务场景</option>
              <option v-for="scene in scenes" :key="scene.value" :value="scene">
                {{ scene.label }}
              </option>
            </select>
          </div>

          <div class="form-group message-group">
            <label>短信模板:</label>
            <div class="params">
              业务参数:
              <div
                class="param"
                v-for="param in selectedTemplate.value"
                :key="param"
                @click="insertStr(param)"
              >
                {{ param }}
              </div>
            </div>
            <div class="line"></div>
            <div
              id="messageId"
              class="form-template"
              contenteditable="true"
              type="text"
              ref="messageRef"
              @click="handleClick"
            ></div>
          </div>
        </div>
        <div class="right">
          <div>结果:</div>
          <div>{{ messageResult }}</div>
        </div>
      </div>
    </div>

    <script>
      const { createApp, ref, watch, onMounted, onUnmounted } = Vue

      createApp({
        setup() {
          const scenes = ref([
            {
              label: '用户注册短信验证码',
              value: ['短信验证码'],
              content:
                '尊敬的用户您好,您正在注册账号,验证码为 {1},请勿泄露给他人,有效期5分钟',
              params: ['短信验证码']
            },
            {
              label: '修改绑定手机号',
              value: ['短信验证码', '时间'],
              content:
                '尊敬的用户您好,您正在变更手机号,验证码为 {1} ,请勿泄露给他人,有效期 {2} 分钟。',
              params: ['短信验证码', '时间']
            }
          ])
          const selectedTemplate = ref('')
          const messageRef = ref(null)
          const messageResult = ref('')
          // 光标位置
          const savedRange = ref(null)

          // select 选择事件
          function handleChange(value) {
            messageRef.value.innerHTML = replaceTemplateParams()
            getMessageContent(messageRef.value.innerHTML)
          }

          // 替换模板参数
          function replaceTemplateParams() {
            const regx = /\{(.*?)\}/g
            let tempContent = selectedTemplate.value.content
            return tempContent.replace(regx, match => {
              let tempValue = ''
              let index = parseInt(match.replace(/\{|\}/g, ''))
              let tempParam = ''
              tempParam = selectedTemplate.value.params[index - 1]

              let node = document.createElement('smstag')
              node.contentEditable = 'false'
              // 添加删除按钮
              let deleteBtn = document.createElement('span')
              deleteBtn.innerText = 'x'
              // 删除按钮添加类名
              deleteBtn.className = 'deleteBtn'
              node.innerText = tempParam
              node.appendChild(deleteBtn)
              let space = document.createTextNode('\u00A0')
              node.appendChild(space)
              return node.outerHTML
            })
          }

          // message 内容
          function getMessageContent(content) {
            let result = ''
            const regex = /<smstag.*?>(.*?)<\/smstag>/g
            result = content.replace(regex, (match, result) => {
              let matchStr = result.replace(/<\/?span.*?>|x|&nbsp;/g, '')
              return `${matchStr}`
            })
            messageResult.value = result
          }

          function handleClick(e) {
            if (e.target.nodeName === 'SPAN') {
              e.target.parentNode.remove()
              getMessageContent(messageRef.value.innerHTML)
            }
          }

          // 生成插入内容
          function insertStr(str) {
            let node = document.createElement('smstag')
            node.innerText = str
            node.contentEditable = 'false'
            // 添加删除按钮
            let deleteBtn = document.createElement('span')
            deleteBtn.innerText = 'x'
            // 删除按钮添加类名
            deleteBtn.className = 'deleteBtn'
            node.appendChild(deleteBtn)
            let space = document.createTextNode('\u00A0')
            node.appendChild(space)
            insertNode(node)
          }

          // 插入到光标位置
          function insertNode(node) {
            // 删除选中内容
            savedRange.value && savedRange.value.deleteContents()
            // 插入标签
            savedRange.value && savedRange.value.insertNode(node)
            // 插入成功,光标定位到标签后面
            savedRange.value && savedRange.value.setStartAfter(node.lastChild)
            getMessageContent(messageRef.value.innerHTML)
            // 光标置空
            savedRange.value = null
          }

          function selectionChangeFn() {
            let sel = window.getSelection()
            let range = sel.rangeCount > 0 ? sel.getRangeAt(0) : null
            if (
              range &&
              range.commonAncestorContainer.ownerDocument.activeElement.id ===
                'messageId'
            ) {
              savedRange.value = range
            }
          }

          onMounted(() => {
            document.addEventListener('selectionchange', selectionChangeFn)
          })
          onUnmounted(() => {
            document.removeEventListener('selectionchange', selectionChangeFn)
          })
          return {
            scenes,
            selectedTemplate,
            messageRef,
            messageResult,
            handleChange,
            insertStr,
            handleClick
          }
        }
      }).mount('#app')
    </script>
  </body>
</html>
上次更新: