From 00633a55105c96ddbe2477348f885bbd8e8e0c50 Mon Sep 17 00:00:00 2001 From: meijiali <你的邮箱@xxx.com> Date: Tue, 7 Jul 2026 18:43:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8C=89=20Pencil=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E9=87=8D=E5=81=9A=E9=A1=B5=E9=9D=A2=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/app.css | 663 ++++++++++++++++++++++- app/templates/hotspots/report.html | 22 +- app/templates/index.html | 159 +++--- app/templates/partials/report_panel.html | 170 +++--- app/templates/partials/task_cards.html | 44 ++ app/templates/tasks/detail.html | 194 ++++--- tests/integration/test_routes.py | 60 +- 7 files changed, 1051 insertions(+), 261 deletions(-) create mode 100644 app/templates/partials/task_cards.html diff --git a/app/static/app.css b/app/static/app.css index b9ecc85..8d71e0a 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -587,7 +587,633 @@ a { border-color: #fecaca; } +.control-hero, +.task-detail-hero, +.report-reading-hero { + align-items: stretch; + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + display: grid; + gap: 28px; + grid-template-columns: minmax(0, 0.92fr) minmax(360px, 1.08fr); + margin-bottom: 22px; + padding: 28px; +} + +.task-detail-hero, +.report-reading-hero { + grid-template-columns: minmax(0, 1fr) 360px; +} + +.control-hero-copy, +.task-detail-copy, +.report-title-block { + display: flex; + flex-direction: column; + justify-content: center; + min-width: 0; +} + +.control-hero h1, +.task-detail-hero h1, +.report-reading-hero h1 { + color: var(--foreground); + font-size: 34px; + font-weight: 800; + letter-spacing: 0; + line-height: 1.15; + margin: 0 0 12px; +} + +.control-hero p:not(.section-kicker), +.task-detail-hero p:not(.section-kicker), +.report-reading-hero p:not(.section-kicker) { + color: #475569; + font-size: 15px; + line-height: 1.55; + margin: 0; +} + +.control-subtitle { + color: var(--muted-foreground); + font-size: 13px; + font-weight: 700; + margin: -4px 0 10px; +} + +.quick-launch-panel, +.task-history-panel, +.sidepanel-card, +.task-state-card, +.sentiment-summary-card, +.insight-card, +.content-performance-card, +.sentiment-sidepanel, +.keyword-sidepanel, +.report-data-note { + background: var(--card); + border: 1px solid var(--border); + border-radius: 10px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); +} + +.quick-launch-panel { + background: var(--background); + padding: 22px; +} + +.panel-title-row { + align-items: center; + display: flex; + gap: 16px; + justify-content: space-between; + margin-bottom: 16px; +} + +.panel-title-row h2, +.sidepanel-card h2, +.task-state-card strong, +.sentiment-summary-card strong, +.insight-card h2, +.representative-comments h2, +.content-performance-card h2, +.sentiment-sidepanel h2, +.keyword-sidepanel h2, +.report-data-note h2 { + color: var(--foreground); + font-size: 19px; + font-weight: 800; + letter-spacing: 0; + line-height: 1.25; + margin: 0; +} + +.soft-badge { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 999px; + color: var(--foreground); + display: inline-flex; + font-size: 12px; + font-weight: 700; + line-height: 1; + padding: 7px 10px; + white-space: nowrap; +} + +.soft-badge-success { + background: #f0fdfa; + border-color: #ccfbf1; + color: #0f766e; +} + +.default-scale-grid, +.overview-grid { + display: grid; + gap: 12px; +} + +.default-scale-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-bottom: 16px; +} + +.scale-tile, +.overview-card { + background: var(--card); + border: 1px solid var(--border); + border-radius: 9px; + padding: 14px; +} + +.scale-tile strong, +.overview-card strong { + color: var(--foreground); + display: block; + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 24px; + font-weight: 700; + line-height: 1.2; +} + +.scale-tile span, +.overview-card span, +.overview-card small { + color: var(--muted-foreground); + display: block; + font-size: 12px; + margin-top: 4px; +} + +.quick-form-grid { + align-items: end; + display: grid; + gap: 12px; + grid-template-columns: minmax(150px, 1.2fr) repeat(3, minmax(100px, 0.8fr)) minmax(150px, 0.9fr); +} + +.quick-start-btn { + min-height: 50px; +} + +.scale-hint { + color: var(--muted-foreground); + font-size: 13px; + line-height: 1.45; + margin: 12px 0 0; +} + +.overview-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin-bottom: 22px; +} + +.dashboard-workspace, +.detail-workspace, +.report-reading-layout { + display: grid; + gap: 20px; + grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr); +} + +.task-history-panel { + padding: 20px; +} + +.task-card-list, +.hotspot-card-list, +.report-main-column, +.report-side-column { + display: flex; + flex-direction: column; + gap: 14px; +} + +.task-card, +.hotspot-result-card, +.quote-card { + align-items: center; + background: var(--card); + border: 1px solid var(--border); + border-radius: 10px; + display: flex; + gap: 18px; + padding: 18px; +} + +.task-card-number { + display: flex; + flex: 0 0 90px; + flex-direction: column; + gap: 4px; +} + +.task-card-number strong, +.hotspot-rank { + color: var(--foreground); + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 24px; + font-weight: 700; +} + +.task-card-number span, +.task-card-title-row p, +.task-card-meta, +.task-card-progress, +.hotspot-chip-row, +.stat-line span, +.sidepanel-card p, +.report-data-note p { + color: var(--muted-foreground); + font-size: 13px; +} + +.task-card-body { + flex: 1 1 auto; + min-width: 0; +} + +.task-card-title-row, +.progress-label-row, +.stat-line { + align-items: center; + display: flex; + gap: 12px; + justify-content: space-between; +} + +.task-card-title-row h3 { + color: var(--foreground); + font-size: 16px; + font-weight: 800; + margin: 0 0 3px; +} + +.task-card-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 10px; +} + +.task-card-error { + color: #991b1b; + font-size: 13px; + margin: 10px 0 0; +} + +.task-card-action { + flex: 0 0 auto; +} + +.task-status-sidepanel, +.detail-sidepanel { + display: flex; + flex-direction: column; + gap: 16px; +} + +.sidepanel-card, +.task-state-card, +.sentiment-summary-card, +.insight-card, +.content-performance-card, +.sentiment-sidepanel, +.keyword-sidepanel, +.report-data-note { + padding: 20px; +} + +.sidepanel-card-blue, +.report-data-note { + background: #eff6ff; + border-color: #dbeafe; +} + +.sidepanel-card-danger { + background: #fff1f2; + border-color: #ffe4e6; +} + +.sidepanel-icon { + align-items: center; + background: #eff6ff; + border-radius: 8px; + color: #2563eb; + display: inline-flex; + font-size: 18px; + font-weight: 800; + height: 34px; + justify-content: center; + margin-bottom: 12px; + width: 34px; +} + +.stage-list { + display: grid; + gap: 10px; + margin-top: 14px; +} + +.stage-list span { + align-items: center; + color: #475569; + display: flex; + font-size: 13px; + gap: 10px; +} + +.stage-dot { + background: var(--border); + border-radius: 999px; + display: inline-block; + height: 10px; + width: 10px; +} + +.stage-dot-success { + background: #0f766e; +} + +.stage-dot-active { + background: #2563eb; +} + +.task-state-card, +.sentiment-summary-card { + background: #f0fdfa; + border-color: #ccfbf1; +} + +.task-state-card span, +.sentiment-summary-card span { + color: #0f766e; + display: block; + font-size: 13px; + font-weight: 700; + margin-bottom: 8px; +} + +.task-state-card strong, +.sentiment-summary-card strong { + display: block; + font-size: 34px; + margin-bottom: 10px; +} + +.execution-timeline { + display: grid; + gap: 12px; + grid-template-columns: repeat(6, minmax(0, 1fr)); + margin-bottom: 22px; +} + +.execution-step { + background: var(--card); + border: 1px solid var(--border); + border-radius: 10px; + min-width: 0; + padding: 16px; +} + +.execution-step-icon { + align-items: center; + background: var(--secondary); + border-radius: 999px; + color: var(--muted-foreground); + display: inline-flex; + font-weight: 800; + height: 28px; + justify-content: center; + margin-bottom: 10px; + width: 28px; +} + +.execution-step-done .execution-step-icon { + background: #0f766e; + color: #ffffff; +} + +.execution-step-active .execution-step-icon { + background: #2563eb; + color: #ffffff; +} + +.execution-step strong, +.execution-step small { + display: block; +} + +.execution-step strong { + color: var(--foreground); + font-size: 15px; + font-weight: 800; + margin-bottom: 6px; +} + +.execution-step small { + color: var(--muted-foreground); + line-height: 1.35; +} + +.hotspot-result-card { + min-height: 116px; +} + +.hotspot-rank { + align-items: center; + background: #eff6ff; + border-radius: 10px; + color: #2563eb; + display: flex; + flex: 0 0 56px; + height: 56px; + justify-content: center; +} + +.hotspot-result-body { + flex: 1 1 auto; + min-width: 0; +} + +.hotspot-result-body h3 { + color: var(--foreground); + font-size: 17px; + font-weight: 800; + line-height: 1.35; + margin: 0 0 10px; +} + +.hotspot-chip-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.hotspot-chip-row span, +.keyword-cloud span, +.ghost-actions span { + background: var(--secondary); + border: 1px solid var(--border); + border-radius: 8px; + color: #475569; + display: inline-flex; + font-size: 12px; + padding: 6px 10px; +} + +.report-reading-layout { + align-items: start; +} + +.insight-list { + display: grid; + gap: 12px; + margin-top: 16px; +} + +.insight-list p { + color: #475569; + display: flex; + font-size: 14px; + gap: 10px; + line-height: 1.55; + margin: 0; +} + +.insight-list p span { + background: #2563eb; + border-radius: 999px; + flex: 0 0 8px; + height: 8px; + margin-top: 7px; +} + +.representative-comments { + display: grid; + gap: 12px; +} + +.representative-comments .panel-title-row { + margin-bottom: 0; +} + +.quote-card { + align-items: flex-start; +} + +.quote-card p { + color: var(--foreground); + flex: 1 1 auto; + font-size: 14px; + line-height: 1.45; + margin: 0; +} + +.quote-card small { + color: var(--muted-foreground); + white-space: nowrap; +} + +.content-performance-card { + display: grid; + gap: 14px; +} + +.performance-row { + align-items: center; + display: flex; + gap: 12px; + justify-content: space-between; +} + +.performance-row strong, +.performance-row span { + display: block; +} + +.performance-row strong { + color: var(--foreground); + font-size: 15px; +} + +.performance-row span { + color: var(--muted-foreground); + font-size: 12px; + margin-top: 4px; +} + +.performance-row em { + color: #2563eb; + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + font-style: normal; + font-weight: 700; + white-space: nowrap; +} + +.sentiment-sidepanel, +.keyword-sidepanel, +.report-data-note { + display: grid; + gap: 14px; +} + +.sentiment-row { + display: grid; + gap: 6px; +} + +.sentiment-positive { + background-color: #0f766e !important; +} + +.sentiment-neutral { + background-color: #2563eb !important; +} + +.sentiment-negative { + background-color: #e11d48 !important; +} + +.keyword-cloud, +.ghost-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.keyword-cloud span, +.ghost-actions span { + background: #eff6ff; + border-color: #dbeafe; + color: #2563eb; + font-weight: 700; +} + @media (max-width: 992px) { + .control-hero, + .task-detail-hero, + .report-reading-hero, + .dashboard-workspace, + .detail-workspace, + .report-reading-layout { + grid-template-columns: 1fr; + } + + .quick-form-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .quick-start-btn { + grid-column: 1 / -1; + } + + .overview-grid, + .execution-timeline { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero-shell { align-items: flex-start; flex-direction: column; @@ -642,11 +1268,46 @@ a { .section-heading, .page-heading, - .content-item-row { + .content-item-row, + .panel-title-row, + .task-card, + .hotspot-result-card, + .quote-card, + .performance-row { align-items: flex-start; flex-direction: column; } + .control-hero, + .task-detail-hero, + .report-reading-hero { + padding: 22px; + } + + .control-hero h1, + .task-detail-hero h1, + .report-reading-hero h1 { + font-size: 28px; + } + + .default-scale-grid, + .overview-grid, + .execution-timeline, + .quick-form-grid { + grid-template-columns: 1fr; + } + + .task-card-number, + .task-card-action, + .hotspot-rank { + flex: 0 0 auto; + } + + .task-card-action, + .hotspot-result-card .btn { + width: 100%; + } + .card-body { padding: 16px; } diff --git a/app/templates/hotspots/report.html b/app/templates/hotspots/report.html index b216ee4..73735db 100644 --- a/app/templates/hotspots/report.html +++ b/app/templates/hotspots/report.html @@ -8,14 +8,22 @@ {% endblock %} {% block content %} -
-
-

热点报告

-

{{ hotspot.title }} 汇总报告

-
- 热点 #{{ hotspot.rank }} -
{% if report %} + {% set metrics = report.metrics_json | from_json_object %} + {% set sentiment = metrics.get("sentiment", {}) %} + {% set positive_pct = sentiment.get("positive", {}).get("pct", 0) %} +
+
+

HOTSPOT REPORT

+

{{ hotspot.title }}

+

{{ report.summary or "报告总结生成失败,请查看下方结构化统计。" }}

+
+ +
{% include "partials/report_panel.html" %} {% else %}
diff --git a/app/templates/index.html b/app/templates/index.html index dd6e02c..bdd2f39 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,102 +1,123 @@ {% extends "base.html" %} {% set has_running_tasks = tasks | selectattr("status", "equalto", "running") | list | length > 0 %} {% block title %}热榜评论雷达 - 热榜评论分析工具{% endblock %} +{% block breadcrumbs %}{% endblock %} {% block content %} -
-
-
-

Hot Comment Radar

-

抓取热点评论,快速看清讨论情绪

-

面向内部演示和联调的轻量工具:选择平台、控制抓取规模、查看任务状态,并进入报告和评论明细。

-
-
-
- 5 - 默认热点 -
-
- 25 - 默认内容 -
-
- 1,250 - 评论上限 -
-
+
+
+

COMMENT INTELLIGENCE

+

内容平台评论分析控制台

+

用默认配置快速启动一次评论分析

+

选择平台后直接开始抓取,系统会按 5 个热点、25 条内容、最多 1,250 条评论完成采集、AI 分析和报告生成。

-
- -
-
-
-
-

采集配置

-

创建抓取任务

+
+
+
+

采集配置

+

默认抓取方案

+
+ 推荐
- 默认规模 5 × 5 × 50 -
-
-
-
+
+
+ 5 + 热点 +
+
+ 25 + 内容 +
+
+ 1250 + 评论上限 +
+
+
+
-
+
-
+
-
+
-
- -
-
-
- 预计最多抓取:1250 条评论(实际数量可能受平台返回数量、去重、失败、限流影响) +
+

实际数量可能受平台返回数量、去重、失败、限流影响。

+
+
+ +
+
+ 最近任务 + {{ tasks | length }} + {% if has_running_tasks %}1 个任务运行中{% else %}暂无运行中任务{% endif %} +
+
+ 默认规模 + 1,250 + 5 热点 / 25 内容 / 评论上限 +
+
+ 平台范围 + 2 + 小红书与抖音 +
+
+ 任务策略 + 1 + 同一时间只运行一个任务
-
-
-
-

任务历史

-

最近任务

+
+
+
+
+

任务历史

+

最近任务

+
+ {% if has_running_tasks %}自动更新中{% else %}卡片化展示,减少表格压迫感 · 运行中任务自动刷新{% endif %}
- {% if has_running_tasks %}自动更新中{% else %}运行中任务自动刷新{% endif %} -
-
- - - - - - - - - - - - - - {% include "partials/task_rows.html" %} - -
任务 ID平台创建时间配置规模进度状态操作
-
-
+
+ {% include "partials/task_cards.html" %} +
+ + +
+ {% if has_running_tasks %} {% endif %} diff --git a/app/templates/partials/report_panel.html b/app/templates/partials/report_panel.html index 978c78e..afbcdf2 100644 --- a/app/templates/partials/report_panel.html +++ b/app/templates/partials/report_panel.html @@ -4,88 +4,114 @@ {% set top_labels = metrics.get("top_labels", []) %} {% set sample_count = metrics.get("sample_count", 0) %} {% set item_count = metrics.get("item_count") %} +{% set positive = sentiment.get("positive", {"count": 0, "pct": 0}) %} +{% set neutral = sentiment.get("neutral", {"count": 0, "pct": 0}) %} +{% set negative = sentiment.get("negative", {"count": 0, "pct": 0}) %} -
-
- {% if task.analysis_status == "insufficient" %} -
当前有效评论样本不足,AI 总结暂不可用。建议增加评论抓取数量后重新分析。
- {% endif %} +{% if task.analysis_status == "insufficient" %} +
当前有效评论样本不足,AI 总结暂不可用。建议增加评论抓取数量后重新分析。
+{% endif %} -
-
-
-
评论样本
-
{{ sample_count }}
-
-
- {% if item_count is not none %} -
-
-
关联内容
-
{{ item_count }}
-
-
- {% endif %} -
-
-
AI 成功率
-
{{ rate_percent(task.analysis_success_rate) }}%
-
-
-
- -
-
AI 总结
-

{{ report.summary or "总结生成失败,请查看上方统计数据。" }}

-
- -
-
-

情绪分布

- {% for key, bar_class in [("positive", "bg-success"), ("neutral", "bg-secondary"), ("negative", "bg-danger"), ("unknown", "bg-warning")] %} - {% set item = sentiment.get(key, {"count": 0, "pct": 0}) %} -
- {{ sentiment_label(key) }} - {{ item.get("count", 0) }} 条({{ item.get("pct", 0) }}%) -
-
-
-
- {% endfor %} -
-
-

Top 标签

+
+
+
+

核心结论

+
+

{{ report.summary or "总结生成失败,请优先查看情绪分布、Top 标签和典型评论。" }}

+

评论样本 {{ sample_count }} 条{% if item_count is not none %},关联内容 {{ item_count }} 条{% endif %},AI 成功率 {{ rate_percent(task.analysis_success_rate) }}%。

{% if top_labels %} -
- {% for label in top_labels %} - {{ label.get("name") }} ({{ label.get("count", 0) }}) - {% endfor %} -
+

当前高频讨论方向集中在{% for label in top_labels[:3] %}{{ "、" if not loop.first }}{{ label.get("name") }}{% endfor %}。

{% else %} -

暂无标签数据

+

暂无高频标签数据,建议结合评论明细人工复核。

{% endif %}
-
-
-
+ -
-
典型评论
-
-
+
+
+
+

COMMENTS

+

代表评论 · 典型评论

+
+ 按情绪和点赞综合筛选 +
{% for key in ["positive", "neutral", "negative"] %} -
-

{{ sentiment_label(key) }}

+
+ {{ sentiment_label(key) }} {% for comment in typical.get(key, []) %} -
-

{{ comment.get("content") }}

- 点赞 {{ comment.get("like_count", 0) }} -
+

{{ comment.get("content") }}

+ 点赞 {{ comment.get("like_count", 0) }} {% else %} -

暂无{{ sentiment_label(key) }}代表评论

+

暂无{{ sentiment_label(key) }}代表评论

{% endfor %} +
+ {% endfor %} +
+ +
+

内容表现

+
+
+ 评论样本 + 用于本次报告统计的有效评论 +
+ {{ sample_count }} 条 +
+ {% if item_count is not none %} +
+
+ 关联内容 + 参与热点级聚合的内容条目 +
+ {{ item_count }} 条 +
+ {% endif %} +
+
+ 高频标签 + 用于定位主要讨论方向 +
+ {{ top_labels | length }} 个 +
+
+
+ + + diff --git a/app/templates/partials/task_cards.html b/app/templates/partials/task_cards.html new file mode 100644 index 0000000..4f95a67 --- /dev/null +++ b/app/templates/partials/task_cards.html @@ -0,0 +1,44 @@ +{% for task in tasks %} + {% set status_label, status_class = status_badge_config(task.status) %} + {% set percent = progress_percent(task.processed_items_count, task.total_items_count) %} + {% set ai_percent = rate_percent(task.analysis_success_rate) %} +
+
+ {{ task.display_id or loop.index }} + {{ task.platform | platform_label }} +
+
+
+
+

{{ status_label }}

+

{{ task.scale_label }} · {{ task.created_at_label }}

+
+ {{ status_label }} +
+
+
+ 已处理 {{ task.processed_items_count }} / 共 {{ task.total_items_count }} 条内容 + {{ percent }}% +
+
+
+
+
+
+ 成功 {{ task.successful_items_count }} / 失败 {{ task.failed_items_count }} + 评论 {{ task.comments_count or 0 }} + 报告 {{ task.reports_count or 0 }} + AI 成功率 {{ ai_percent }}% + {% if task.analysis_status == "insufficient" %} + AI 样本不足 + {% endif %} +
+ {% if task.error_summary %} +

{{ task.error_summary }}

+ {% endif %} +
+ 查看 +
+{% else %} +
还没有任何任务,请在上方创建第一个任务
+{% endfor %} diff --git a/app/templates/tasks/detail.html b/app/templates/tasks/detail.html index 5abb6eb..69402ee 100644 --- a/app/templates/tasks/detail.html +++ b/app/templates/tasks/detail.html @@ -7,117 +7,105 @@ {% endblock %} {% block content %} -
-
-

任务详情

-

任务 {{ task.display_id }}

+{% set label, cls = status_badge_config(task.status) %} +{% set percent = progress_percent(task.processed_items_count, task.total_items_count) %} +{% set ai_percent = rate_percent(task.analysis_success_rate) %} +{% set target_comments = task.hotspot_limit * task.item_limit_per_hotspot * task.comment_limit_per_item %} + +
+
+

TASK DETAIL

+

{{ task.platform | platform_label }}热点评论采集任务 {{ task.display_id }}

+

创建时间 {{ task.created_at_label }},当前阶段 {{ task.current_stage_label or "等待启动" }}。任务执行过程会隔离单条内容失败,并保留失败原因用于复查。

- {% set label, cls = status_badge_config(task.status) %} - {{ label }} -
-
-
- {% set percent = progress_percent(task.processed_items_count, task.total_items_count) %} - {% set ai_percent = rate_percent(task.analysis_success_rate) %} - {% set target_comments = task.hotspot_limit * task.item_limit_per_hotspot * task.comment_limit_per_item %} -
-
- {{ task.platform | platform_label }} - {{ task.current_stage_label or "等待启动" }} -
-

创建时间 {{ task.created_at_label }} · 阶段 {{ task.current_stage_label or "等待启动" }}

-
-
-
- 已处理 {{ task.processed_items_count }} / 共 {{ task.total_items_count }} 条内容 - {{ percent }}% -
-
-
-
-
- 成功 {{ task.successful_items_count }} / 失败 {{ task.failed_items_count }} - AI 成功率 {{ ai_percent }}% -
-
-
-
-
-
目标上限
- {{ task.hotspot_limit }} 热点 × {{ task.item_limit_per_hotspot }} 内容 × {{ task.comment_limit_per_item }} 评论 -
最多 {{ target_comments }} 条评论
-
-
-
-
-
实际结果
- 实际评论 {{ task.comments_count or 0 }} -
已获取热点 {{ task.hotspots_count or 0 }} / 目标 {{ task.hotspot_limit }} · 报告 {{ task.reports_count or 0 }}
-
-
-
-
-
最近进度
- {{ task.last_progress_ago_label or "暂无记录" }} -
运行时长 {{ task.running_duration_label or "刚刚" }} · 评论 {{ task.comments_count or 0 }}
-
-
-
- {% if task.status == "running" %} -
- 小规模通常较快,默认规模可能需要数分钟,取决于 TikHub 与 AI 响应速度。 -
- {% endif %} - {% if task.is_progress_stale %} -
- 超过 {{ task.stale_threshold_minutes }} 分钟没有进度更新,可能仍在等待外部接口或 AI 响应;如果长时间不恢复,请刷新状态或查看后端日志。 -
- {% endif %} - {% if task.status == "success" and (task.comments_count or 0) < target_comments %} -
少于理论上限通常是内容本身评论不足或平台返回不足,不直接代表任务失败。若失败内容数大于 0,请结合失败原因判断。
- {% endif %} - {% if task.analysis_status == "insufficient" %} -
AI 成功率 {{ ai_percent }}%,当前样本可能不足,报告结论请结合评论明细判断。
- {% endif %} - {% if task.error_summary %}
{{ task.error_summary }}
{% endif %} -
+
+ +
+ {% for stage, desc, state in [ + ("创建任务", "参数校验完成", "done"), + ("热点抓取", (task.hotspots_count or 0) ~ " 个热点入库", "done" if (task.hotspots_count or 0) > 0 or task.status == "success" else "pending"), + ("内容采集", task.processed_items_count ~ " / " ~ task.total_items_count ~ " 条内容", "done" if task.processed_items_count >= task.total_items_count and task.total_items_count > 0 else "active"), + ("评论分页", (task.comments_count or 0) ~ " 条评论", "done" if (task.comments_count or 0) > 0 else "pending"), + ("AI 分析", "成功率 " ~ ai_percent ~ "%", "done" if ai_percent >= 80 else "active"), + ("报告生成", (task.reports_count or 0) ~ " 份报告", "done" if (task.reports_count or 0) > 0 else "pending") + ] %} +
+ {% if state == "done" %}✓{% elif state == "active" %}•{% else %}○{% endif %} + {{ stage }} + {{ desc }} +
+ {% endfor %} +
+ {% if task.status == "running" and not hotspots %}

正在抓取热点数据,请稍候...

{% endif %} -
-{% for hotspot in hotspots %} -
-

- -

-
-
-
- 内容条目 {{ hotspot.content_items | length }} - 查看汇总报告 -
-
    - {% for item in hotspot.content_items %} -
  • -
    - - {{ item.title or item.summary or item.source_item_id }} - 状态 {{ item.status }} · ID {{ item.source_item_id }} - - 查看详情 -
    -
  • - {% endfor %} -
+ +
+
+
+
+

执行阶段

+

热点结果

+ 按热度与评论完成度查看
-
-{% endfor %} + {% for hotspot in hotspots %} +
+
{{ "%02d"|format(hotspot.rank or loop.index) }}
+
+

热点 {{ hotspot.rank or loop.index }}:{{ hotspot.title }}

+
+ {{ hotspot.content_items | length }} 内容 + {{ hotspot.comments | length }} 评论 + {{ task.platform | platform_label }} +
+
+ 查看报告 +
+ {% else %} +
暂无热点结果
+ {% endfor %} + + +
+ +{% if task.is_progress_stale %} +
+ 超过 {{ task.stale_threshold_minutes }} 分钟没有进度更新,可能仍在等待外部接口或 AI 响应;如果长时间不恢复,请刷新状态或查看后端日志。 +
+{% endif %} +{% if task.status == "success" and (task.comments_count or 0) < target_comments %} +
少于理论上限通常是内容本身评论不足或平台返回不足,不直接代表任务失败。若失败内容数大于 0,请结合失败原因判断。
+{% endif %} +{% if task.analysis_status == "insufficient" %} +
AI 成功率 {{ ai_percent }}%,当前样本可能不足,报告结论请结合评论明细判断。
+{% endif %} + {% if task.status == "running" %} {% endif %} diff --git a/tests/integration/test_routes.py b/tests/integration/test_routes.py index b5c1d7a..83d2b54 100644 --- a/tests/integration/test_routes.py +++ b/tests/integration/test_routes.py @@ -25,22 +25,24 @@ def test_index_page_renders_task_form_empty_state_and_default_scale(): assert "还没有任何任务" in response.text -def test_index_page_uses_pencil_data_grid_layout_for_ui_polish(): +def test_index_page_uses_pencil_control_console_layout_for_ui_polish(): with make_test_client() as (client, _engine): response = client.get("/") assert response.status_code == 200 soup = BeautifulSoup(response.text, "html.parser") visible_text = soup.get_text(" ") - hero = soup.select_one(".hero-band.hero-band-inverse") + hero = soup.select_one(".control-hero") assert hero is not None - assert "抓取热点评论,快速看清讨论情绪" in visible_text - assert soup.select_one(".hero-metrics.hero-data-grid") is not None - assert soup.select_one(".task-workspace") is not None - assert soup.select_one(".tool-card.hard-panel") is not None - assert soup.select_one(".section-heading.panel-heading") is not None - assert soup.select_one("table.app-table.data-grid-table") is not None - assert soup.select_one(".scale-hint-panel") is not None + assert "用默认配置快速启动一次评论分析" in visible_text + assert "内容平台评论分析控制台" in visible_text + assert soup.select_one(".quick-launch-panel") is not None + assert soup.select_one(".default-scale-grid") is not None + assert soup.select_one(".overview-grid") is not None + assert soup.select_one(".task-card-list") is not None + assert soup.select_one(".task-status-sidepanel") is not None + assert soup.select_one("table.app-table.data-grid-table") is None + assert "卡片化展示,减少表格压迫感" in visible_text assert "运行中任务自动刷新" in visible_text @@ -722,6 +724,46 @@ def test_task_detail_page_explains_target_and_actual_counts(): assert "少于理论上限通常是内容本身评论不足或平台返回不足" in response.text +def test_task_detail_page_uses_pencil_execution_view_layout(): + with make_test_client() as (client, engine): + seed_result_data(engine) + + response = client.get("/tasks/task-result") + + assert response.status_code == 200 + soup = BeautifulSoup(response.text, "html.parser") + visible_text = soup.get_text(" ") + assert soup.select_one(".task-detail-hero") is not None + assert soup.select_one(".execution-timeline") is not None + assert soup.select_one(".hotspot-card-list") is not None + assert soup.select_one(".detail-sidepanel") is not None + assert "TASK DETAIL" in visible_text + assert "执行阶段" in visible_text + assert "热点结果" in visible_text + assert "错误会被隔离" in visible_text + assert "查看报告" in visible_text + + +def test_hotspot_report_page_uses_pencil_analysis_reading_layout(): + with make_test_client() as (client, engine): + seed_result_data(engine) + + response = client.get("/hotspots/hot-result/report") + + assert response.status_code == 200 + soup = BeautifulSoup(response.text, "html.parser") + visible_text = soup.get_text(" ") + assert soup.select_one(".report-reading-hero") is not None + assert soup.select_one(".report-reading-layout") is not None + assert soup.select_one(".insight-card") is not None + assert soup.select_one(".sentiment-sidepanel") is not None + assert "HOTSPOT REPORT" in visible_text + assert "核心结论" in visible_text + assert "代表评论" in visible_text + assert "内容表现" in visible_text + assert "同一份报告数据" in visible_text + + def test_task_api_includes_runtime_and_stale_progress_fields(monkeypatch): now = datetime(2026, 7, 3, 10, 30, tzinfo=UTC) monkeypatch.setattr("app.services.task_service.utc_now", lambda: now)