背景
在项目开发中,实现用户友好的输入交互是提升用户体验的关键之一。例如,在客服对话框中,其中有包含多个快捷选项用于快速问答,每个快捷选项都是一个可点击的按钮,并需要绑定点击事件来执行相应操作。然而,直接在 v-html
渲染的标签内绑定的 click
事件不生效。这是因为 v-html
只是将 HTML 字符串插入到 DOM 中,并不会编译其中的 Vue 指令。
v-html 工作原理
-
基本用途
-
v-html 指令用于将一个字符串作为 HTML 插入到 DOM 中。
-
这意味着任何包含在字符串中的 HTML 都会被原样插入,而不会被 Vue 编译。
-
-
工作原理
-
当 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>
事件对象里 event
的 target
就是鼠标点击的元素
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 事件监听器),并通过事件对象判断点击的目标元素,从而调用相应的处理方法,确保点击事件能够正常触发。