vue 异步操作生成DOM替代v-html

由于v-html属于同步指令,无法绑定异步函数,直接绑定异步函数无法渲染异步生成的字符串节点

应用场景:需要从接口获取到文本内容展示到窗口,遍历循环多条的文本内容有匹配的文案需要根据规则转换成table排版并转换成图片展示到页面

解决方案与思路:

       转换图片需要用到canvas ,由于是不去确定的内容条数,无法再页面直接写死canvas,只写死一个canvas 在页面上所有出现需要转换的页面都用同一个canvas 也不合适,会出现冲突,所以考虑的是动态创建canvas 转换完成后再从DOM树移除,因为生成canvas 节点属于异步操作,需要canvas onload后才可操作api所以这里就涉及到需要异步操作的地方;

        这里我偷懒,使用第三方插件html2canvas

        下载依赖:npm -i html2canvas -S

以上思路可以生成我们想要的字符串节点,但是无法通过v-html渲染,因为操作canvas 的时候是异步的,直接v-html绑定异步函数只会展示异步函数本身并非返回值

        解决方案:既然无法通过v-html渲染,那就直接通过event去挂载,在遍历的消息列表上绑定事件并把event 传递给处理函数,处理函数处理完成返回字符串节点后转换成真实dom后再appendchild 到消息节点上,绑定事件理论只需要自动触发一次,所以可以写一个自动触发trigger指令实现,只触发一次可以使用 v-once

        具体实现:

html:

<template>
    <span
        v-if="con.type === 'txt'"
        style="white-space: pre-wrap"
    >
        <span 
            v-if="con.data.indexOf('历史参考:') !== -1" 
            v-trigger 
            v-once 
            @click.stop="(event) => { createImg(con.data,event) }">
        </span>
        <span v-else v-html="con.data"></span>
    </span>
</template>

<style lang="scss">
/ table样式定制
.tablewrap{
    position: fixed;
    left: -9999px;
    top: -9999px;
    .res-table{
        ...
    }
</style>

dom操作 domHandle.js



import { formatDates } from '@/utils/tools'
import html2canvas from "html2canvas"
// 转换真实dom
function toHtml (str) {
    let parser = new DOMParser()
    let doc = parser.parseFromString(str, 'text/html')
    return doc.body.firstChild
}
/**
 * html 转 canvas 图片
 */
function canvasToSvg (domstr) {
    return new Promise((resolve, reject) => {
        var dom = toHtml(domstr)
        // 创建的dom 必须渲染到dom树 否则无法渲染style样式
        document.querySelector('body').appendChild(dom)
        var ops = {
            width: dom.offsetWidth,
            height: dom.offsetHeight,
            useCORS: true,
            allowTaint: false
        }
        html2canvas(dom, ops).then((canvas) => {
            let imgData = canvas.toDataURL('image/png')
            // 生成玩图片路径 移除dom
            document.querySelector('body').removeChild(dom)
            resolve(imgData)
        })
    })
}
// 过滤历史参考消息添加节点 ev当前节点
export function filterText (text, ev) {
    const resId = Date.now()
    // 创建dom
    if (text.indexOf('历史参考:\n') !== -1) {
        // 新版表格展示板
        let tableText = text.split('历史参考:\n')[1].split('\n--------------------')[0]
        let tableList = tableText.split('\n')
        tableList = tableList.map(str => {
            return str.split(';')
        })
        let dom = `<div id="${resId}" class="tablewrap"><table class="res-table">`
        tableList.forEach((item, index) => {
            dom += `
                        <tr class="${index == 0 ? 'th' : ''}">
                            ${(() => {
                    let otd = ``
                    item.forEach((tdD, ind) => {
                        let isTime = (ind == 0 && !isNaN(tdD * 1))
                        let isIssue = ind == 1
                        let isTm = ind == 3
                        let clList = isIssue ? 'black' : isTm ? 'red' : 'blue'
                        otd += `<td class="${clList}">${(() => {
                            if (/(\d\,)+/.test(tdD)) {
                                let coded = ``
                                // 特殊处理
                                tdD.split(',').forEach(tm => {
                                    coded += `<span class="code">${tm}</span>`
                                })
                                return coded
                            } else {
                                let classList = isTime ? 'red' : { '大': 'red', '双': 'red' }[tdD]
                                return `<span class="${classList}">${
                                    // 第一列时间处理
                                    isTime ? formatDates(new Date(tdD * 1), 'hh:mm') : tdD
                                    }</span>`
                            }
                        })()
                            }</td>`
                    })
                    return otd
                })()
                }
                        </tr>
                        `
        })
        dom += `</table></div>`
        // html 转 canvas 生成图片
        let src_ = await canvasToSvg(dom)
        text = `<span>${text.replace(tableText, `<img style="width: 100%;" src="${src_}" />`)}</span>`
        // 节点字符串生成完成 把节点插入回消息容器
        ev.target.appendChild(toHtml(text))

        // 如果是同步操作直接使用以下操作
        // text = text.replace(tableText, dom)
        // return text
    }
}

script:

import { filterText } from './domHandle'
import { ImagePreview } from 'vant';
export default {
    ...
    directives:{
        // 自定义一个默认触发指令 用于生成开奖结果图片
        trigger:{
            inserted(el,binging){
                el.click()
            }
        }
    },
    methods: {
        // 历史参考结果处理成图片 / 点击图片预览
        createImg(text,event) {
            if (event.target.childNodes.length) {
                // 图片已经生成过 跳出点击事件
                return
            }
            if (event.target.getAttribute('src')) {
                // 执行图片预览
                ImagePreview({
                    images: [event.target.getAttribute('src')],
                    closeable: true
                })
                return
            }
            filterText(text, event)
       },
    },
    ...
}

示例图:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值