You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:02:44