Files
dygou_go/frontend/src/components/log/LogPanel.vue
T
2026-06-26 10:56:25 +08:00

258 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useLogs } from '@/composables/useLogs'
import { useAccounts } from '@/composables/useAccounts'
const { logs, results, clearLogs } = useLogs()
const { accounts, selectedIndices, statusMap } = useAccounts()
const activeTab = ref<'log' | 'result' | 'stats'>('log')
const logBodyRef = ref<HTMLElement | null>(null)
// 自动滚动到底部
watch(logs, async () => {
await nextTick()
if (logBodyRef.value) {
logBodyRef.value.scrollTop = logBodyRef.value.scrollHeight
}
}, { deep: true })
// 统计数据
const stats = computed(() => {
const selectedNames = [...selectedIndices.value]
.map(i => accounts.value[i]?.name)
.filter(Boolean) as string[]
let success = 0
let fail = 0
for (const name of selectedNames) {
const st = statusMap.value[name]
if (st === true || (st && typeof st === 'object' && (st as any).ok)) {
success++
} else if (st === false || (st && typeof st === 'object' && (st as any).message)) {
fail++
}
}
return {
success,
fail,
pending: Math.max(0, selectedNames.length - success - fail),
selected: selectedNames.length,
total: accounts.value.length
}
})
// 结果列表
const resultEntries = computed(() => Object.entries(results.value))
</script>
<template>
<div class="panel log-wrap">
<div class="log-header">
<div class="log-tabs">
<button
class="log-tab"
:class="{ active: activeTab === 'log' }"
@click="activeTab = 'log'"
>
实时日志
</button>
<button
class="log-tab"
:class="{ active: activeTab === 'result' }"
@click="activeTab = 'result'"
>
抢购结果
</button>
<button
class="log-tab"
:class="{ active: activeTab === 'stats' }"
@click="activeTab = 'stats'"
>
统计
</button>
</div>
<button class="btn btn-compact" @click="clearLogs">清空</button>
</div>
<!-- 实时日志 -->
<div
v-show="activeTab === 'log'"
ref="logBodyRef"
class="log-panel"
>
<div v-if="logs.length === 0" class="log-i">[系统] 就绪,等待操作...</div>
<div
v-for="(log, index) in logs"
:key="index"
:class="`log-${log.type}`"
>
<template v-if="log.source === 'server'">
[后端]
<span v-if="log.category" class="log-cat">{{ log.category }}</span>
{{ log.message }}
</template>
<template v-else>
[{{ log.time }}] {{ log.message }}
</template>
</div>
</div>
<!-- 抢购结果 -->
<div
v-show="activeTab === 'result'"
class="log-panel"
>
<div v-if="resultEntries.length === 0" class="result-empty">暂无抢购结果</div>
<template v-else>
<div class="log-i">=== 最新结果 ===</div>
<div
v-for="[key, value] in resultEntries"
:key="key"
class="log-result"
:class="value.includes('成功') ? 'log-s' : 'log-e'"
>
<b>{{ key }}</b>: {{ value }}
</div>
</template>
</div>
<!-- 统计 -->
<div
v-show="activeTab === 'stats'"
class="log-panel"
>
<div class="log-result log-s">成功账号: {{ stats.success }}</div>
<div class="log-result log-e">异常账号: {{ stats.fail }}</div>
<div class="log-result log-i">待处理账号: {{ stats.pending }}</div>
<div class="log-result">已选账号: {{ stats.selected }} / 总账号: {{ stats.total }}</div>
</div>
</div>
</template>
<style scoped>
.log-wrap {
flex: 1;
min-height: 0;
}
.log-header {
padding: 8px 12px;
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
background: var(--surface);
}
.log-tabs {
display: flex;
gap: 2px;
align-items: center;
}
.log-tab {
height: 26px;
padding: 0 10px;
border-radius: var(--radius-sm);
border: 0;
background: transparent;
color: var(--muted);
cursor: pointer;
font-size: 11.5px;
font-weight: 500;
transition: .12s;
}
.log-tab:hover {
color: var(--text-strong);
background: var(--surface-2);
}
.log-tab.active {
background: var(--accent-soft);
color: var(--accent);
}
.log-panel {
flex: 1;
min-height: 0;
overflow: auto;
padding: 10px 12px;
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
font-size: 11.5px;
line-height: 1.7;
background: var(--field);
}
.log-s {
color: var(--success);
}
.log-e {
color: var(--danger);
}
.log-i {
color: var(--accent);
}
.log-w {
color: var(--warning);
}
.log-d {
color: var(--muted-2);
}
.log-result {
background: var(--surface);
padding: 6px 9px;
margin: 4px 0;
border-radius: var(--radius-sm);
word-break: break-all;
border: 1px solid var(--line);
}
.log-cat {
display: inline-block;
min-width: 32px;
text-align: center;
margin-right: 6px;
padding: 0 5px;
border: 1px solid var(--line);
border-radius: 3px;
color: var(--muted);
font-size: 10px;
line-height: 1.5;
background: var(--surface);
}
.result-empty {
color: var(--muted);
padding: 14px 4px;
font-style: italic;
}
.panel {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}
@media (max-width: 1180px) {
.log-wrap {
height: 360px;
}
}
</style>