当前位置: 首页 > article >正文

使用 v-html 指令渲染的标签, 标签内绑定的 click 事件不生效

背景

在项目开发中,实现用户友好的输入交互是提升用户体验的关键之一。例如,在客服对话框中,其中有包含多个快捷选项用于快速问答,每个快捷选项都是一个可点击的按钮,并需要绑定点击事件来执行相应操作。然而,直接在 v-html 渲染的标签内绑定的 click 事件不生效。这是因为 v-html 只是将 HTML 字符串插入到 DOM 中,并不会编译其中的 Vue 指令。

v-html 工作原理

  1. 基本用途

    • v-html 指令用于将一个字符串作为 HTML 插入到 DOM 中。

    • 这意味着任何包含在字符串中的 HTML 都会被原样插入,而不会被 Vue 编译。

  2. 工作原理

    • 当 Vue.js 执行 v-html 指令时,它会将字符串解析为 DOM 节点,并将其插入到指定的位置。

    • 这个过程是通过浏览器的 DOM API 完成的,具体来说是通过 innerHTML 属性。

    • 由于插入的是原生的 DOM 节点,而不是经过 Vue 编译的虚拟 DOM,因此其中的 Vue 指令不会被识别和执行。

代码示例

<template>
  <div>
    <el-dialog width="68%" :visible.sync="isShow" :before-close="close" class="kefu" :show-close="false"
      :close-on-click-modal="false">
      <div class="kefu-con">
        <i class="el-icon-close close" @click="close" />
        <div class="header">
          <img :src="kefuImg" alt="">
          <div class="title">小奶龙智能问答助手</div>
        </div>
        <div class="container" ref="container">
          <div class="content" ref="content">
            <div v-for="item in messageForm">
              <div class="reply-container" v-if="item.type === 'reply'">
                <div class="reply-content">
                  <div class="img-con">
                    <img :src="kefuImg" alt="">
                  </div>
                  <div class="reply">
                    <div v-if="item.isXml" v-html="item.content"></div>
                    <p v-else>
                      {{ item.content }}
                    </p>
                  </div>
                </div>
              </div>
              <div class="qs-container" v-if="item.type === 'qs'">
                <div class="qs-content">
                  <div class="qs">
                    <p>
                      {{ item.content }}
                    </p>
                  </div>
                </div>
              </div>
            </div>
          </div>

        </div>
        <div class="footer">
          <img :src="kefuImg" alt="">
          <div class="question-con">
            <el-input class="ipt" v-model="question" placeholder="请输入想咨询的问题"></el-input>
            <el-button class="btn" type="warning" round @click="send">发送</el-button>
          </div>

        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getAnswer } from '@/api/checkin.js';
export default {
  data() {
    return {
      isShow: false,
      kefuImg: require("@/assets/images/headshot.png"),
      question: '',
      messageForm: [{
        content: "<p>您好, 我是您的小奶龙,你的智能助手。 你可以问我编码相关的问题,也可以一起更高效、更高质量地完成编码工作。比如 “<span class='quick' style='color: #5094d5;cursor: pointer;' @click='quick'>动态路由实现</span>”, “<span class='quick' style='color: #5094d5;cursor: pointer;' @click='quick'>移动端适配</span>” 等等一些问题。</p>",
        type: "reply", // reply 回答  qs 问题
        isXml: true,
      },
      {
        content: '动态路由实现',
        type: "qs", // reply 回答  qs 问题
        isXml: false,
      }
      ]
    }
  },
  methods: {
    quick() {
      console.log('quick方法触发了::: ');
    },
    show() {
      this.isShow = true;
    },
    close() {
      this.isShow = false;
    },
    send() {
      // console.log('this.$refs.content.scrollHeight::: ', this.$refs.content.scrollHeight);

      let NewQuestion = this.question.trim();
      if (NewQuestion === '') {
        return;
      }
      // console.log('this.question::: ', NewQuestion);
      this.messageForm.push({
        content: NewQuestion,
        type: 'qs',
        isXml: false,
      });
     
      setTimeout(()=>{
        // 模拟异步请求
        this.question = '';
      })
    }
  },
}
</script>

页面展示如下,点击快捷问答选项 “动态路由实现” 没有触发 quick 事件
在这里插入图片描述

解决方法

1. 在父容器上监听点击事件,并通过事件对象判断点击的目标元素。

<template>
  <div class="reply" @click="handleProxyClick">
    <div v-html="htmlContent"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      htmlContent: '<button class="my-button">点击我</button>'
    };
  },
  methods: {
    handleProxyClick(event) {
      console.log('event::: ', event);
      // 获取触发事件的目标元素  event 事件对象
      const target = event.target;
    },
  }
}
</script>

事件对象里 eventtarget 就是鼠标点击的元素
在这里插入图片描述

2. 如果渲染多个标签,可以通过声明 id 属性或者 class 类名 来判断。

<template>
  <div class="reply" @click="handleProxyClick">
    <div v-html="htmlContent"></div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      htmlContent: '<button id="222" class="my-button">点击我</button>'
    };
  },
  methods: {
    handleProxyClick(event) {
      // 获取触发事件的目标元素  event 事件对象
      const target = event.target;

      console.log("target.classList::: ", target.classList);
      console.log("target.id::: ", target.id);

    },
  }
}
</script>

如图所示
在这里插入图片描述

代码实现

<template>
  <div>
    <el-dialog width="68%" :visible.sync="isShow" :before-close="close" class="kefu" :show-close="false"
      :close-on-click-modal="false">
      <div class="kefu-con">
        <i class="el-icon-close close" @click="close" />
        <div class="header">
          <img :src="kefuImg" alt="">
          <div class="title">小奶龙智能问答助手</div>
        </div>
        <div class="container" ref="container">
          <div class="content" ref="content">
            <div v-for="item in messageForm">
              <div class="reply-container" v-if="item.type === 'reply'">
                <div class="reply-content">
                  <div class="img-con">
                    <img :src="kefuImg" alt="">
                  </div>
                  <div class="reply" @click="handleProxyClick">
                    <div v-if="item.isXml" v-html="item.content"></div>
                    <p v-else>
                      {{ item.content }}
                    </p>
                  </div>
                </div>
              </div>
              <div class="qs-container" v-if="item.type === 'qs'">
                <div class="qs-content">
                  <div class="qs">
                    <p>
                      {{ item.content }}
                    </p>
                  </div>
                </div>
              </div>
            </div>
          </div>

        </div>
        <div class="footer">
          <img :src="kefuImg" alt="">
          <div class="question-con">
            <el-input class="ipt" v-model="question" placeholder="请输入想咨询的问题"></el-input>
            <el-button class="btn" type="warning" round @click="send">发送</el-button>
          </div>

        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getAnswer } from '@/api/checkin.js';
export default {
  data() {
    return {
      isShow: false,
      kefuImg: require("@/assets/images/headshot.png"),
      question: '',
      messageForm: [{
        content: "<p>您好, 我是您的小奶龙,你的智能助手。 你可以问我编码相关的问题,也可以一起更高效、更高质量地完成编码工作。比如 “<span class='quick' style='color: #5094d5;cursor: pointer;' @click='quick'>动态路由实现</span>”, “<span class='quick' style='color: #5094d5;cursor: pointer;' @click='quick'>移动端适配</span>” 等等一些问题。</p>",
        type: "reply", // reply 回答  qs 问题
        isXml: true,
      },
      {
        content: '动态路由实现',
        type: "qs", // reply 回答  qs 问题
        isXml: false,
      }
      ]
    }
  },
  methods: {
    handleProxyClick(event) {
      // 获取触发事件的目标元素  event 事件对象
      const target = event.target;
      // 判断目标元素是否包含指定类名
      if (target.classList.contains('quick')) {
        // 传递目标元素的文本内容
        this.quick(target.outerText);
      }
    },
    quick(text) {
      console.log('quick方法触发了::: ');
      this.question = text;
      // 发送
      this.send();
    },
    show() {
      this.isShow = true;
    },
    close() {
      this.isShow = false;
    },
    send() {
      let NewQuestion = this.question.trim();
      if (NewQuestion === '') {
        return;
      }
      this.messageForm.push({
        content: NewQuestion,
        type: 'qs',
        isXml: false,
      });
      setTimeout(() => {
        // 模拟异步请求
        this.question = '';
      })
    }
  },
}
</script>

实现效果
在这里插入图片描述

总结

在项目开发中,某些对话框中的快捷选项使用 v-html 渲染,导致标签内绑定的 click 事件不生效。为了解决这一问题,可以通过在父容器上使用事件代理(如 @click 事件监听器),并通过事件对象判断点击的目标元素,从而调用相应的处理方法,确保点击事件能够正常触发。


http://www.kler.cn/a/369734.html

相关文章:

  • 【编译原理实验二】——自动机实验:NFA转DFA并最小化
  • 【学术会议征稿-第二届生成式人工智能与信息安全学术会议(GAIIS 2025)】人工智能与信息安全的魅力
  • 【Unity3D】实现2D角色/怪物死亡消散粒子效果
  • 快速分析LabVIEW主要特征进行判断
  • 【JavaEE】_MVC架构与三层架构
  • LangChain的开发流程
  • Linux开放端口问题(同一局域网)
  • Django入门教程——动态表格分页展示数据
  • 指令(一):Android OS实用指令
  • 【入驻电商平台指南】ISV入驻京东平台申请流程
  • Redis内部数据结构Dict结构详解
  • 关于写“查看IT设备详细信息”接口的理解
  • PostgresSql 常用运维命令
  • 【大数据学习 | Zookeeper】Zookeeper的选举机制
  • 《使用Gin框架构建分布式应用》阅读笔记:p212-p233
  • 解锁AI潜力:揭秘高效人工智能数据准备的奥秘
  • 使用Claude新功能分析数据文件
  • 如何对网络设备进行监控:以监控易平台为例
  • 1FreeRTOS学习(队列、二值信号量、计数型信号量之间的相同点和不同点)
  • 前端实现卡片,展开/收起效果(vue/React/html)
  • 从零开始基于ROS-Noetic使用gazebo操控真实机械臂(附完整运行代码)
  • 论文笔记:TELLER 可解释的、可概括的、可控的假新闻检测的可信框架
  • <项目代码>YOLOv8 煤矸石识别<目标检测>
  • 在 macOS 上添加 hosts 文件解析的步骤
  • docker 安装部署 nginx
  • SPA和SSR