fix: 修复日志页面自动滚动问题 (#382)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2025-07-09 23:34:50 +08:00
committed by GitHub
parent ce60838244
commit 74a45526f0
13 changed files with 43 additions and 35 deletions

View File

@@ -5,4 +5,4 @@ mod response;
mod routes;
mod wrapper;
pub use routes::{MpscWriter, router};
pub use routes::{MAX_HISTORY_LOGS, MpscWriter, router};

View File

@@ -6,7 +6,7 @@ use axum::response::sse::{Event, KeepAlive, Sse};
use axum::routing::get;
use axum::{Extension, Router};
use futures::{Stream, StreamExt};
pub use mpsc::MpscWriter;
pub use mpsc::{MAX_HISTORY_LOGS, MpscWriter};
use tokio_stream::wrappers::BroadcastStream;
pub(super) fn router() -> Router {

View File

@@ -5,7 +5,7 @@ use parking_lot::Mutex;
use tokio::sync::broadcast;
use tracing_subscriber::fmt::MakeWriter;
const MAX_HISTORY_LOGS: usize = 20;
pub const MAX_HISTORY_LOGS: usize = 30;
pub struct MpscWriter {
pub sender: broadcast::Sender<String>,

View File

@@ -23,7 +23,7 @@ mod me;
mod video_sources;
mod videos;
pub use logs::MpscWriter;
pub use logs::{MAX_HISTORY_LOGS, MpscWriter};
pub fn router() -> Router {
Router::new().route("/image-proxy", get(image_proxy)).nest(

View File

@@ -24,7 +24,7 @@ use task::{http_server, video_downloader};
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use crate::api::MpscWriter;
use crate::api::{MAX_HISTORY_LOGS, MpscWriter};
use crate::config::{ARGS, VersionedConfig};
use crate::database::setup_database;
use crate::utils::init_logger;
@@ -79,7 +79,7 @@ fn spawn_task(
/// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置
async fn init() -> (Arc<DatabaseConnection>, MpscWriter) {
let (tx, _rx) = tokio::sync::broadcast::channel(30);
let log_history = Arc::new(Mutex::new(VecDeque::with_capacity(20)));
let log_history = Arc::new(Mutex::new(VecDeque::with_capacity(MAX_HISTORY_LOGS + 1)));
let log_writer = MpscWriter::new(tx, log_history.clone());
init_logger(&ARGS.log_level, Some(log_writer.clone()));

View File

@@ -28,8 +28,8 @@ html {
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.37 0.04 257);
--chart-2: oklch(0.13 0.04 265);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
@@ -113,6 +113,9 @@ html {
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace, ui-sans-serif, system-ui, sans-serif;
}
@layer base {

View File

@@ -170,7 +170,7 @@
<SheetTitle class="text-lg">编辑状态</SheetTitle>
<SheetDescription class="text-muted-foreground space-y-1 text-sm">
<div>修改视频和分页的下载状态。可以将任务重置为未开始状态,或者标记为已完成。</div>
<div class="font-medium text-red-600">
<div class="font-medium text-rose-600">
⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件。
</div>
</SheetDescription>

View File

@@ -11,10 +11,11 @@
// 获取状态显示信息
function getStatusInfo(value: number) {
if (value === 7) return { label: '已完成', class: 'text-green-600', dotClass: 'bg-green-500' };
if (value === 7)
return { label: '已完成', class: 'text-emerald-600', dotClass: 'bg-emerald-600' };
if (value >= 1 && value <= 4)
return { label: `失败${value}次`, class: 'text-red-600', dotClass: 'bg-red-500' };
return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-500' };
return { label: `失败${value}次`, class: 'text-rose-600', dotClass: 'bg-rose-600' };
return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-600' };
}
$: statusInfo = getStatusInfo(currentStatus);
@@ -71,8 +72,8 @@
onclick={() => onStatusChange(7)}
{disabled}
class="h-7 min-w-[60px] cursor-pointer px-3 text-xs {currentStatus === 7
? 'border-green-600 bg-green-600 font-medium text-white hover:bg-green-700'
: 'hover:border-green-400 hover:bg-green-50 hover:text-green-700'}"
? 'border-emerald-600 bg-emerald-600 font-medium text-white hover:bg-emerald-700'
: 'hover:border-emerald-400 hover:bg-emerald-50 hover:text-emerald-700'}"
>
已完成
</Button>

View File

@@ -33,11 +33,11 @@
function getSegmentColor(status: number): string {
if (status === 7) {
return 'bg-emerald-500'; // 恢复更高对比度的绿色
return 'bg-emerald-500';
} else if (status === 0) {
return 'bg-slate-400'; // 恢复更清晰的灰色 - 未开始
return 'bg-yellow-500';
} else {
return 'bg-rose-500'; // 恢复更清晰的红色 - 失败
return 'bg-rose-500';
}
}
@@ -50,7 +50,7 @@
const failed = downloadStatus.filter((status) => status !== 7 && status !== 0).length;
if (completed === total) {
return { text: '完成', color: 'outline' }; // 更简洁的文案
return { text: '完成', color: 'outline' };
} else if (failed > 0) {
return { text: '失败', color: 'destructive' };
} else {
@@ -136,7 +136,7 @@
<div
class="h-1.5 w-full cursor-help rounded-full transition-all {getSegmentColor(
status
)} hover:opacity-80"
)}"
></div>
</Tooltip.Trigger>
<Tooltip.Content>

View File

@@ -19,7 +19,7 @@
<BreadCrumb items={$breadcrumbStore} />
</div>
</header>
<div class="w-full overflow-y-auto px-6 py-2" style="scrollbar-width: thin;">
<div class="w-full overflow-y-auto px-6 py-2" style="scrollbar-width: thin;" id="main">
<slot />
</div>
</Sidebar.Inset>

View File

@@ -87,29 +87,29 @@
const videoChartConfig = {
videos: {
label: '视频数量',
color: 'var(--chart-1)'
color: 'var(--color-slate-700)'
}
} satisfies Chart.ChartConfig;
const memoryChartConfig = {
used: {
label: '整体占用',
color: 'var(--chart-1)'
color: 'var(--color-slate-700)'
},
process: {
label: '程序占用',
color: 'var(--chart-2)'
color: 'var(--color-slate-950)'
}
} satisfies Chart.ChartConfig;
const cpuChartConfig = {
used: {
label: '整体占用',
color: 'var(--chart-1)'
color: 'var(--color-slate-700)'
},
process: {
label: '程序占用',
color: 'var(--chart-2)'
color: 'var(--color-slate-950)'
}
} satisfies Chart.ChartConfig;

View File

@@ -8,15 +8,18 @@
let logEventSource: EventSource | null = null;
let logs: Array<{ timestamp: string; level: string; message: string }> = [];
let shouldAutoScroll = true;
let main: HTMLElement | null = null;
function checkScrollPosition() {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5;
if (main) {
const { scrollTop, scrollHeight, clientHeight } = main;
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5;
}
}
function scrollToBottom() {
if (shouldAutoScroll) {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' });
if (shouldAutoScroll && main) {
main.scrollTop = main.scrollHeight;
}
}
@@ -45,23 +48,24 @@
onMount(() => {
setBreadcrumb([{ label: '日志' }]);
window.addEventListener('scroll', checkScrollPosition);
main = document.getElementById('main');
main?.addEventListener('scroll', checkScrollPosition);
startLogStream();
return () => {
stopLogStream();
window.removeEventListener('scroll', checkScrollPosition);
main?.removeEventListener('scroll', checkScrollPosition);
};
});
function getLevelColor(level: string) {
switch (level) {
case 'ERROR':
return 'text-red-600';
return 'text-rose-600';
case 'WARN':
return 'text-yellow-600';
case 'INFO':
default:
return 'text-green-600';
return 'text-emerald-600';
}
}
</script>

View File

@@ -112,8 +112,8 @@
{:else}
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<div class="h-2 w-2 rounded-full bg-green-500"></div>
<span class="text-sm text-green-600">已认证</span>
<div class="h-2 w-2 rounded-full bg-emerald-500"></div>
<span class="text-sm text-emerald-600">已认证</span>
</div>
<Button
variant="outline"