背景
在项目开发中,实现用户友好的输入交互是提升用户体验的关键之一。例如,在客服对话框中,其中有包含多个快捷选项用于快速问答,每个快捷选项都是一个可点击的按钮,并需要绑定点击事件来执行相应操作。然而,直接在 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 事件监听器),并通过事件对象判断点击的目标元素,从而调用相应的处理方法,确保点击事件能够正常触发。