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
+1 -1
View File
@@ -5,4 +5,4 @@ mod response;
mod routes; mod routes;
mod wrapper; mod wrapper;
pub use routes::{MpscWriter, router}; pub use routes::{MAX_HISTORY_LOGS, MpscWriter, router};
+1 -1
View File
@@ -6,7 +6,7 @@ use axum::response::sse::{Event, KeepAlive, Sse};
use axum::routing::get; use axum::routing::get;
use axum::{Extension, Router}; use axum::{Extension, Router};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
pub use mpsc::MpscWriter; pub use mpsc::{MAX_HISTORY_LOGS, MpscWriter};
use tokio_stream::wrappers::BroadcastStream; use tokio_stream::wrappers::BroadcastStream;
pub(super) fn router() -> Router { pub(super) fn router() -> Router {
+1 -1
View File
@@ -5,7 +5,7 @@ use parking_lot::Mutex;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::fmt::MakeWriter;
const MAX_HISTORY_LOGS: usize = 20; pub const MAX_HISTORY_LOGS: usize = 30;
pub struct MpscWriter { pub struct MpscWriter {
pub sender: broadcast::Sender<String>, pub sender: broadcast::Sender<String>,
+1 -1
View File
@@ -23,7 +23,7 @@ mod me;
mod video_sources; mod video_sources;
mod videos; mod videos;
pub use logs::MpscWriter; pub use logs::{MAX_HISTORY_LOGS, MpscWriter};
pub fn router() -> Router { pub fn router() -> Router {
Router::new().route("/image-proxy", get(image_proxy)).nest( Router::new().route("/image-proxy", get(image_proxy)).nest(
+2 -2
View File
@@ -24,7 +24,7 @@ use task::{http_server, video_downloader};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker; use tokio_util::task::TaskTracker;
use crate::api::MpscWriter; use crate::api::{MAX_HISTORY_LOGS, MpscWriter};
use crate::config::{ARGS, VersionedConfig}; use crate::config::{ARGS, VersionedConfig};
use crate::database::setup_database; use crate::database::setup_database;
use crate::utils::init_logger; use crate::utils::init_logger;
@@ -79,7 +79,7 @@ fn spawn_task(
/// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置 /// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置
async fn init() -> (Arc<DatabaseConnection>, MpscWriter) { async fn init() -> (Arc<DatabaseConnection>, MpscWriter) {
let (tx, _rx) = tokio::sync::broadcast::channel(30); 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()); let log_writer = MpscWriter::new(tx, log_history.clone());
init_logger(&ARGS.log_level, Some(log_writer.clone())); init_logger(&ARGS.log_level, Some(log_writer.clone()));
+5 -2
View File
@@ -28,8 +28,8 @@ html {
--border: oklch(0.929 0.013 255.508); --border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788); --ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.37 0.04 257); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.13 0.04 265); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429); --chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.769 0.188 70.08);
@@ -113,6 +113,9 @@ html {
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --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 { @layer base {
+1 -1
View File
@@ -170,7 +170,7 @@
<SheetTitle class="text-lg">编辑状态</SheetTitle> <SheetTitle class="text-lg">编辑状态</SheetTitle>
<SheetDescription class="text-muted-foreground space-y-1 text-sm"> <SheetDescription class="text-muted-foreground space-y-1 text-sm">
<div>修改视频和分页的下载状态。可以将任务重置为未开始状态,或者标记为已完成。</div> <div>修改视频和分页的下载状态。可以将任务重置为未开始状态,或者标记为已完成。</div>
<div class="font-medium text-red-600"> <div class="font-medium text-rose-600">
⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件。 ⚠️ 已完成任务被重置为未开始,任务重新执行时会覆盖现存文件。
</div> </div>
</SheetDescription> </SheetDescription>
@@ -11,10 +11,11 @@
// 获取状态显示信息 // 获取状态显示信息
function getStatusInfo(value: number) { 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) if (value >= 1 && value <= 4)
return { label: `失败${value}次`, class: 'text-red-600', dotClass: 'bg-red-500' }; return { label: `失败${value}次`, class: 'text-rose-600', dotClass: 'bg-rose-600' };
return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-500' }; return { label: '未开始', class: 'text-yellow-600', dotClass: 'bg-yellow-600' };
} }
$: statusInfo = getStatusInfo(currentStatus); $: statusInfo = getStatusInfo(currentStatus);
@@ -71,8 +72,8 @@
onclick={() => onStatusChange(7)} onclick={() => onStatusChange(7)}
{disabled} {disabled}
class="h-7 min-w-[60px] cursor-pointer px-3 text-xs {currentStatus === 7 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' ? 'border-emerald-600 bg-emerald-600 font-medium text-white hover:bg-emerald-700'
: 'hover:border-green-400 hover:bg-green-50 hover:text-green-700'}" : 'hover:border-emerald-400 hover:bg-emerald-50 hover:text-emerald-700'}"
> >
已完成 已完成
</Button> </Button>
+5 -5
View File
@@ -33,11 +33,11 @@
function getSegmentColor(status: number): string { function getSegmentColor(status: number): string {
if (status === 7) { if (status === 7) {
return 'bg-emerald-500'; // 恢复更高对比度的绿色 return 'bg-emerald-500';
} else if (status === 0) { } else if (status === 0) {
return 'bg-slate-400'; // 恢复更清晰的灰色 - 未开始 return 'bg-yellow-500';
} else { } else {
return 'bg-rose-500'; // 恢复更清晰的红色 - 失败 return 'bg-rose-500';
} }
} }
@@ -50,7 +50,7 @@
const failed = downloadStatus.filter((status) => status !== 7 && status !== 0).length; const failed = downloadStatus.filter((status) => status !== 7 && status !== 0).length;
if (completed === total) { if (completed === total) {
return { text: '完成', color: 'outline' }; // 更简洁的文案 return { text: '完成', color: 'outline' };
} else if (failed > 0) { } else if (failed > 0) {
return { text: '失败', color: 'destructive' }; return { text: '失败', color: 'destructive' };
} else { } else {
@@ -136,7 +136,7 @@
<div <div
class="h-1.5 w-full cursor-help rounded-full transition-all {getSegmentColor( class="h-1.5 w-full cursor-help rounded-full transition-all {getSegmentColor(
status status
)} hover:opacity-80" )}"
></div> ></div>
</Tooltip.Trigger> </Tooltip.Trigger>
<Tooltip.Content> <Tooltip.Content>
+1 -1
View File
@@ -19,7 +19,7 @@
<BreadCrumb items={$breadcrumbStore} /> <BreadCrumb items={$breadcrumbStore} />
</div> </div>
</header> </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 /> <slot />
</div> </div>
</Sidebar.Inset> </Sidebar.Inset>
+5 -5
View File
@@ -87,29 +87,29 @@
const videoChartConfig = { const videoChartConfig = {
videos: { videos: {
label: '视频数量', label: '视频数量',
color: 'var(--chart-1)' color: 'var(--color-slate-700)'
} }
} satisfies Chart.ChartConfig; } satisfies Chart.ChartConfig;
const memoryChartConfig = { const memoryChartConfig = {
used: { used: {
label: '整体占用', label: '整体占用',
color: 'var(--chart-1)' color: 'var(--color-slate-700)'
}, },
process: { process: {
label: '程序占用', label: '程序占用',
color: 'var(--chart-2)' color: 'var(--color-slate-950)'
} }
} satisfies Chart.ChartConfig; } satisfies Chart.ChartConfig;
const cpuChartConfig = { const cpuChartConfig = {
used: { used: {
label: '整体占用', label: '整体占用',
color: 'var(--chart-1)' color: 'var(--color-slate-700)'
}, },
process: { process: {
label: '程序占用', label: '程序占用',
color: 'var(--chart-2)' color: 'var(--color-slate-950)'
} }
} satisfies Chart.ChartConfig; } satisfies Chart.ChartConfig;
+12 -8
View File
@@ -8,15 +8,18 @@
let logEventSource: EventSource | null = null; let logEventSource: EventSource | null = null;
let logs: Array<{ timestamp: string; level: string; message: string }> = []; let logs: Array<{ timestamp: string; level: string; message: string }> = [];
let shouldAutoScroll = true; let shouldAutoScroll = true;
let main: HTMLElement | null = null;
function checkScrollPosition() { function checkScrollPosition() {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement; if (main) {
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5; const { scrollTop, scrollHeight, clientHeight } = main;
shouldAutoScroll = scrollTop + clientHeight >= scrollHeight - 5;
}
} }
function scrollToBottom() { function scrollToBottom() {
if (shouldAutoScroll) { if (shouldAutoScroll && main) {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); main.scrollTop = main.scrollHeight;
} }
} }
@@ -45,23 +48,24 @@
onMount(() => { onMount(() => {
setBreadcrumb([{ label: '日志' }]); setBreadcrumb([{ label: '日志' }]);
window.addEventListener('scroll', checkScrollPosition); main = document.getElementById('main');
main?.addEventListener('scroll', checkScrollPosition);
startLogStream(); startLogStream();
return () => { return () => {
stopLogStream(); stopLogStream();
window.removeEventListener('scroll', checkScrollPosition); main?.removeEventListener('scroll', checkScrollPosition);
}; };
}); });
function getLevelColor(level: string) { function getLevelColor(level: string) {
switch (level) { switch (level) {
case 'ERROR': case 'ERROR':
return 'text-red-600'; return 'text-rose-600';
case 'WARN': case 'WARN':
return 'text-yellow-600'; return 'text-yellow-600';
case 'INFO': case 'INFO':
default: default:
return 'text-green-600'; return 'text-emerald-600';
} }
} }
</script> </script>
+2 -2
View File
@@ -112,8 +112,8 @@
{:else} {:else}
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="h-2 w-2 rounded-full bg-green-500"></div> <div class="h-2 w-2 rounded-full bg-emerald-500"></div>
<span class="text-sm text-green-600">已认证</span> <span class="text-sm text-emerald-600">已认证</span>
</div> </div>
<Button <Button
variant="outline" variant="outline"