feat: add logs ansi render

This commit is contained in:
engigu
2026-03-11 11:59:03 +08:00
parent db2bbd5d4d
commit b7522bc47c
7 changed files with 74 additions and 12 deletions
+7 -1
View File
@@ -133,6 +133,12 @@ async function uninstallPackage() {
}
}
import { ansiToHtml } from '@/utils/ansi'
const renderedLog = computed(() => {
return ansiToHtml(logContent.value)
})
function showLog(dep: Dependency) {
logPkgName.value = dep.name
logContent.value = dep.log || '暂无日志'
@@ -357,7 +363,7 @@ onMounted(async () => {
<DialogDescription class="sr-only">查看依赖包的详细安装输出日志</DialogDescription>
</DialogHeader>
<div class="max-h-[400px] overflow-y-auto">
<pre class="text-xs bg-muted p-3 rounded-lg whitespace-pre-wrap break-all font-mono">{{ logContent }}</pre>
<pre class="text-xs bg-muted p-3 rounded-lg whitespace-pre-wrap break-all font-mono" v-html="renderedLog"></pre>
</div>
<DialogFooter>
<Button variant="outline" @click="showLogDialog = false">关闭</Button>
+7 -1
View File
@@ -56,10 +56,16 @@ const isWsLoading = ref(false)
let logSocket: WebSocket | null = null
import { ansiToHtml } from '@/utils/ansi'
const decompressedOutput = computed(() => {
return wsContent.value || '无输出'
})
const renderedOutput = computed(() => {
return ansiToHtml(decompressedOutput.value)
})
async function loadLogs() {
try {
const params: { page: number; page_size: number; task_id?: string; task_name?: string; status?: string } = {
@@ -566,7 +572,7 @@ watch(() => route.query, (newQuery) => {
</div>
<div class="flex-1 overflow-auto bg-muted/5 min-h-[160px]">
<pre
class="p-4 text-xs font-mono whitespace-pre-wrap break-all log-pre leading-relaxed">{{ decompressedOutput }}</pre>
class="p-4 text-xs font-mono whitespace-pre-wrap break-all log-pre leading-relaxed" v-html="renderedOutput"></pre>
<div v-if="isWsLoading" class="p-4 text-sm text-muted-foreground italic">连接中...</div>
</div>
</div>
+5 -7
View File
@@ -17,14 +17,12 @@ const emit = defineEmits<{
const searchKeyword = ref('')
// 高亮搜索结果
const highlightedContent = computed(() => {
if (!searchKeyword.value.trim()) return props.content
import { ansiToHtml, highlightHtml } from '@/utils/ansi'
const keyword = searchKeyword.value.trim()
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const regex = new RegExp(`(${escaped})`, 'gi')
return props.content.replace(regex, '<mark class="bg-yellow-300 text-black">$1</mark>')
// 高亮搜索结果并处理 ANSI 颜色
const highlightedContent = computed(() => {
const html = ansiToHtml(props.content)
return highlightHtml(html, searchKeyword.value)
})
function close() {
+7 -2
View File
@@ -150,6 +150,12 @@ function formatDate(dateStr: string) {
}
}
import { ansiToHtml } from '@/utils/ansi'
const renderedContent = computed(() => {
return ansiToHtml(detailDialogProps.value.content)
})
function onDialogClose(open: boolean) {
if (!open) {
selectedLogId.value = null
@@ -291,8 +297,7 @@ function onDialogClose(open: boolean) {
</div>
<div class="p-6">
<div v-if="detailDialogProps.content"
class="text-sm text-foreground bg-muted/20 p-5 rounded-xl border border-border/50 whitespace-pre-wrap break-all leading-relaxed shadow-sm">
{{ detailDialogProps.content }}
class="text-sm text-foreground bg-muted/20 p-5 rounded-xl border border-border/50 whitespace-pre-wrap break-all leading-relaxed shadow-sm" v-html="renderedContent">
</div>
<div v-else class="text-sm text-muted-foreground italic py-2">无推送内容</div>
</div>