Vue3+TypeScript+Vuetify构建v-table报错及优化实现咨询
问题解决:Vue3+Vuetify构建日志表格报错及Vue风格实现
报错原因
你遇到的TypeError: parentCont.value.appendChild is not a function是因为Vuetify的<v-table>是组件而非原生DOM元素,通过ref获取到的是组件实例,不是原生table DOM节点,所以没有appendChild这类原生DOM方法。之前用document.getElementById能成功,是因为直接拿到了原生DOM元素。
方案1:修复原代码的DOM操作方式
如果要保留手动DOM操作的逻辑,需要获取<v-table>对应的原生DOM元素,通过组件实例的$el属性访问:
<template> <div> <v-table width="800" ref="LogTable"> </v-table> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' const logRefData = ref("03/22 08:51:06 INFO creating mailslot for RSVP" + "\n" + "2024-05-07 08:51:06 INFO: mailbox allocated for rsvp" + "\n" + "2024-05-07 08:51:06 INFO: creating mailslot for RSVP via UDP" + "\n" + "2024-05-07 08:51:06 INFO: mailbox allocated for rsvp - udp" + "\n" + "2024-05-07 08:51:06 TRACE: entity for rsvp allocated and initialized" + "\n" + "2024-05-07 08:51:06 INFO: creating socket for querying route" + "\n" + "2024-05-07 08:51:06 INFO: no mailbox necessary for forward" + "\n" + "2024-05-07 08:51:06 INFO: creating mailslot for route engine" + "\n" + "2024-05-07 08:51:06 TRACE: ready to accept informational socket connection." + "\n" + "2024-05-07 08:52: 50 EVENT: api request SENDER") const logRefDataArray = ref(logRefData.value.split(/\r?\n/).filter(item => item)); const LogTable = ref(); const renderLogTable = () => { // 获取v-table对应的原生DOM元素 const tableDom = LogTable.value?.$el; if (!tableDom) return; // 清空表格内容,找到tbody(v-table的内容默认在tbody里) const tbody = tableDom.querySelector('tbody') || tableDom; tbody.innerHTML = ''; if (logRefDataArray.value.length) { for (const [i, log] of logRefDataArray.value.entries()) { const tr = document.createElement("tr"); const td = document.createElement("td"); const logStamp = log.substring(0, log.indexOf(': ') + 1).split(' '); let logStampData = ''; if (logStamp.length) { const date = logStamp.splice(0, 1); const time = logStamp.splice(0, 1); const logType = logStamp.join(' '); logStampData = `<span>${i + 1}.</span><i>${date} ${time}</i> <b>${logType}</b> `; } const logMessage = log.substring(log.indexOf(': ') + 1); const logToPrint = `${logStampData}${logMessage}`; td.innerHTML = logToPrint; tr.appendChild(td); tbody.appendChild(tr); } } else { tbody.innerHTML = `<tr><td class="no_entry_found">No logs found</td></tr>`; } } onMounted(() => { renderLogTable(); }); </script>
方案2:符合Vue风格的实现(推荐)
Vue的核心是数据驱动视图,避免手动操作DOM,直接通过模板渲染结合v-for、v-html实现:
完整代码
<template> <div> <v-table width="800"> <tbody> <!-- 渲染日志行 --> <tr v-for="(logItem, index) in structuredLogs" :key="index"> <td v-html="getLogHtml(logItem, index + 1)"></td> </tr> <!-- 无日志提示 --> <tr v-if="structuredLogs.length === 0"> <td class="no_entry_found">No logs found</td> </tr> </tbody> </v-table> </div> </template> <script setup lang="ts"> import { ref, computed } from 'vue' const rawLogData = ref("03/22 08:51:06 INFO creating mailslot for RSVP" + "\n" + "2024-05-07 08:51:06 INFO: mailbox allocated for rsvp" + "\n" + "2024-05-07 08:51:06 INFO: creating mailslot for RSVP via UDP" + "\n" + "2024-05-07 08:51:06 INFO: mailbox allocated for rsvp - udp" + "\n" + "2024-05-07 08:51:06 TRACE: entity for rsvp allocated and initialized" + "\n" + "2024-05-07 08:51:06 INFO: creating socket for querying route" + "\n" + "2024-05-07 08:51:06 INFO: no mailbox necessary for forward" + "\n" + "2024-05-07 08:51:06 INFO: creating mailslot for route engine" + "\n" + "2024-05-07 08:51:06 TRACE: ready to accept informational socket connection." + "\n" + "2024-05-07 08:52: 50 EVENT: api request SENDER") // 计算属性:将原始日志解析为结构化数组 const structuredLogs = computed(() => { return rawLogData.value.split(/\r?\n/) .filter(item => item.trim()) .map(log => { const splitIndex = log.indexOf(': ') + 1; if (splitIndex <= 1) { return { stamp: '', message: log }; } const stampPart = log.substring(0, splitIndex); const message = log.substring(splitIndex).trim(); const [date, time, ...typeParts] = stampPart.split(' ').filter(part => part); return { date, time, type: typeParts.join(' ').replace(':', ''), // 去掉类型后的冒号 message }; }); }); // 生成带格式的HTML字符串,供v-html使用 const getLogHtml = (logItem: { date?: string, time?: string, type?: string, message: string }, index: number) => { if (!logItem.date) { return logItem.message; } return ` <span>${index}.</span> <i>${logItem.date} ${logItem.time}</i> <b>${logItem.type}</b> ${logItem.message} `.replace(/\s+/g, ' '); // 去除多余空格 }; </script> <style scoped> .no_entry_found { text-align: center; color: #666; } i { color: #666; margin: 0 8px; } b { margin: 0 8px; color: #2c3e50; } span { margin-right: 8px; color: #999; } </style>
关于v-html的使用注意事项
v-html会直接将字符串渲染为HTML,只在可信内容上使用,避免渲染用户输入的内容,防止XSS攻击。- 在Vue3的Script Setup中,直接在模板里使用
v-html="变量"即可,变量可以是计算属性或方法返回的HTML字符串。 - 如果需要样式生效,注意样式的作用域(如使用
<style scoped>时,可能需要用:deep()穿透,或者把样式放在全局)。
内容的提问来源于stack exchange,提问作者Divya Bharathi
相关产品推荐
相关产品推荐

