如何将数据库取出的动态HTML包裹在Vue.js模板内容外部
问题场景
我需要将自定义HTML包裹在Vue模板内容外部,该需求无法通过<div v-html="HtmlWrapperTopHalf">插入内容实现,因为该指令会自动闭合未闭合的标签,而标签的闭合部分统一存放在动态HTML的下半部分,本质上我需要将Vue模板代码插入到动态模板HTML中。
我也无法提前预知动态HTML的内容并在Vue模板中构建对应结构:这些HTML来自用户通过富文本编辑器自主创建的内容,用户仅会在动态模板中间插入占位符{{MAIN_CONTENT}},此处即为Vue模板功能的渲染位置。
现有代码 Page.vue
<template> <div> //动态XHTML包裹层的上半部分放在此处 <p>Inner Content Here</p> <ComponentA /> <ComponentB /> <!-- etc. ---> <p>More Inner Content</p> //动态XHTML包裹层的下半部分放在此处 </div> </template> <script> axios.get("getHtmlContent").then((result) =>{ console.log("top", result.data.HtmlWrapperTopHalf) console.log("bottom", result.data.HtmlWrapperBottomHalf) }); </script>
接口返回示例
top: <div class="my-dynamic-class" id="my-unique-id"><h1>DYNAMIC TITLE</h1> bottom: <div id="my-dynamic-footer">custom footer text</div></div>
期望输出效果
<div> <div class="my-dynamic-class" id="my-unique-id"><h1>DYNAMIC TITLE</h1> <p>Inner Content Here<p> <div>Hello from ComponentA!!</div> <div>Hello from ComponentB!!</div> <!-- etc. ---> <p>More Inner Content<p> <div id="my-dynamic-footer">custom footer text</div></div> </div>
概念示意图

解决方案
Vue3 推荐方案:Teleport 注入
不需要引入全量编译版本的Vue,不会破坏动态HTML标签结构,性能和安全性都更可控:
<template> <div> <!-- 渲染完整的动态HTML结构 --> <div v-html="fullDynamicHtml"></div> <!-- 内部内容通过Teleport注入到预留插槽 --> <Teleport to="#main-content-slot" v-if="slotReady"> <p>Inner Content Here</p> <ComponentA /> <ComponentB /> <!-- 其余内部内容 --> <p>More Inner Content</p> </Teleport> </div> </template> <script setup> import { ref, nextTick } from 'vue' import axios from 'axios' import ComponentA from './ComponentA.vue' import ComponentB from './ComponentB.vue' const fullDynamicHtml = ref('') const slotReady = ref(false) axios.get("getHtmlContent").then(async (result) => { const { HtmlWrapperTopHalf, HtmlWrapperBottomHalf } = result.data // 拼接完整动态HTML,把占位符替换为唯一标识的插槽节点 fullDynamicHtml.value = `${HtmlWrapperTopHalf}<div id="main-content-slot"></div>${HtmlWrapperBottomHalf}` // 等待动态HTML渲染完成后再激活Teleport await nextTick() slotReady.value = true }) </script>
Vue2 兼容方案:手动DOM挂载
Vue2没有原生Teleport,可通过手动创建组件实例挂载到预留插槽实现:
<template> <div> <div v-html="fullDynamicHtml"></div> </div> </template> <script> import Vue from 'vue' import axios from 'axios' import ComponentA from './ComponentA.vue' import ComponentB from './ComponentB.vue' export default { components: { ComponentA, ComponentB }, data() { return { fullDynamicHtml: '' } }, async mounted() { const res = await axios.get("getHtmlContent") const { HtmlWrapperTopHalf, HtmlWrapperBottomHalf } = res.data this.fullDynamicHtml = `${HtmlWrapperTopHalf}<div id="main-content-slot"></div>${HtmlWrapperBottomHalf}` await this.$nextTick() // 创建内部内容组件实例 const InnerContent = Vue.extend({ template: ` <div> <p>Inner Content Here</p> <ComponentA /> <ComponentB /> <p>More Inner Content</p> </div> `, components: { ComponentA, ComponentB } }) const innerInstance = new InnerContent().$mount() // 插入到预留插槽位置 document.querySelector('#main-content-slot').appendChild(innerInstance.$el) } } </script>
安全提示:用户自主输入的富文本HTML存在XSS注入风险,建议在渲染前对动态HTML内容做过滤处理,剔除恶意脚本和危险标签。
内容的提问来源于stack exchange,提问作者RichC
相关产品推荐
相关产品推荐

