diff --git a/app/services/task_service.py b/app/services/task_service.py index b7c0a1c..42809ad 100644 --- a/app/services/task_service.py +++ b/app/services/task_service.py @@ -3,6 +3,7 @@ import json from concurrent.futures import ThreadPoolExecutor from collections.abc import Callable from datetime import UTC, datetime +from zoneinfo import ZoneInfo from sqlalchemy import func, select from sqlalchemy.orm import Session, sessionmaker @@ -22,6 +23,7 @@ RUNNING_TASK_MESSAGE = "当前有正在运行的任务,请稍后再试" RESTART_ERROR_MESSAGE = "系统重启,任务被中断" STALE_PROGRESS_THRESHOLD_SECONDS = 10 * 60 STALE_PROGRESS_ERROR_TYPE = "stale_progress_timeout" +DISPLAY_TIMEZONE = ZoneInfo("Asia/Shanghai") task_executor = ThreadPoolExecutor(max_workers=1) @@ -183,10 +185,7 @@ def hydrate_task_progress(session: Session, task: Task) -> Task: task.display_number = display_number task.display_id = str(display_number) task.created_at_label = format_datetime_minute(task.created_at) - task.scale_label = ( - f"{PLATFORM_LABELS.get(task.platform, task.platform)} · " - f"{task.hotspot_limit}热点 × {task.item_limit_per_hotspot}内容 × {task.comment_limit_per_item}评论" - ) + task.scale_label = f"{task.hotspot_limit}热点 × {task.item_limit_per_hotspot}内容 × {task.comment_limit_per_item}评论" task.error_summary = build_task_error_summary(task) task.hotspots_count = session.scalar(select(func.count(Hotspot.id)).where(Hotspot.task_id == task.id)) or 0 task.comments_count = session.scalar(select(func.count(Comment.id)).where(Comment.task_id == task.id)) or 0 @@ -230,7 +229,7 @@ def calculate_task_display_number(session: Session, task: Task) -> int: def format_datetime_minute(value: datetime | None) -> str: value = ensure_utc_datetime(value) - return value.strftime("%Y-%m-%d %H:%M") if value else "" + return value.astimezone(DISPLAY_TIMEZONE).strftime("%Y-%m-%d %H:%M") if value else "" def build_task_error_summary(task: Task) -> str | None: diff --git a/app/static/app.css b/app/static/app.css index 618774b..cee9210 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -1,16 +1,63 @@ body { - background: #f5f7fb; - color: #172033; + background: + linear-gradient(180deg, #f4f7fb 0%, #eef3f8 42%, #f8fafc 100%); + color: #182235; + min-height: 100vh; } +.app-navbar { + background: rgba(255, 255, 255, 0.94) !important; + border-bottom: 1px solid #dfe7f1; + box-shadow: 0 10px 30px rgba(19, 34, 56, 0.04); +} + +.navbar-brand { + color: #102033; +} + +.nav-link { + color: #526173; +} + +.tool-card, .card { + background: rgba(255, 255, 255, 0.96); border-radius: 8px; - border: 1px solid #dfe5ef; - box-shadow: 0 10px 28px rgba(31, 42, 68, 0.06); + border: 1px solid #dbe4ef; + box-shadow: 0 16px 42px rgba(31, 42, 68, 0.07); + overflow: hidden; } -.navbar { - background: #fff !important; +.card-header, +.section-heading { + background: linear-gradient(180deg, #ffffff, #f8fafc); + border-bottom: 1px solid #e4ebf3; + padding: 18px 20px; +} + +.section-heading { + align-items: center; + display: flex; + justify-content: space-between; + gap: 16px; +} + +.section-heading h2, +.page-heading h1 { + font-size: 22px; + font-weight: 700; + letter-spacing: 0; + margin: 0; +} + +.section-kicker, +.eyebrow { + color: #0e766e; + font-size: 12px; + font-weight: 700; + letter-spacing: 0; + margin: 0 0 4px; + text-transform: uppercase; } .hero-band { @@ -20,7 +67,9 @@ body { url("https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1600&q=80"); background-position: center; background-size: cover; + border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 8px; + box-shadow: 0 24px 60px rgba(15, 36, 62, 0.18); color: #fff; display: flex; justify-content: space-between; @@ -52,20 +101,67 @@ body { gap: 12px; } -.eyebrow { - color: #71d4c7; - font-size: 13px; - font-weight: 700; - letter-spacing: 0; - text-transform: uppercase; +.page-heading { + align-items: center; + display: flex; + justify-content: space-between; + gap: 16px; } .metric-box { - background: #f8fafc; + background: linear-gradient(180deg, #f9fbfd, #f3f7fb); border: 1px solid #e3e9f2; border-radius: 8px; height: 100%; - padding: 14px; + padding: 16px; +} + +.metric-box strong { + color: #132033; + display: block; + font-size: 18px; + margin: 4px 0; +} + +.app-table thead th { + background: #f8fafc; + color: #536174; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + white-space: nowrap; +} + +.app-table tbody tr { + border-color: #e8eef5; +} + +.app-table tbody tr:hover { + background: #f7fbff; +} + +.task-number { + align-items: center; + background: #e7f0ff; + border: 1px solid #cfe0ff; + border-radius: 999px; + color: #0b5ed7; + display: inline-flex; + font-weight: 700; + height: 32px; + justify-content: center; + min-width: 32px; + padding: 0 10px; +} + +.task-overview { + border-top: 3px solid #0d6efd; +} + +.hotspot-item { + border-color: #e3eaf2; + margin-bottom: 10px; + overflow: hidden; } .status-panel { @@ -98,4 +194,10 @@ body { .hero-copy h1 { font-size: 36px; } + + .section-heading, + .page-heading { + align-items: flex-start; + flex-direction: column; + } } diff --git a/app/templates/base.html b/app/templates/base.html index cbb8425..04267b7 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,9 +10,9 @@ -