仰望星辰工作室

fix(ui): 话题过多时首页话题栏收起为单行,不再堆满页面顶部

F fqh 提交于 2026-09-13 23:15 · 8b6a61d ·父提交 f67ea54
fix(ui): 话题过多时首页话题栏收起为单行,不再堆满页面顶部

问题
- 首页话题筛选条把所有话题按 flex-wrap 全部铺开,话题一多就形成一面话题墙,
  把瀑布流内容整体挤出首屏

改动
- 话题栏按需收起为单行横向滚动(隐藏滚动条 + 两侧渐隐提示可滑动)
- 新增「展开 / 收起」按钮,展开后限制最大高度并在内部滚动
- 当前选中的话题自动滚动到可视区中部,避免筛选后看不到选中项
- PC 端竖向滚轮直接转为话题条横向滚动
- 话题不多(未换行)时不启用收起,保持原有自动换行,小站观感不变
- 后台话题管理列表限制最大高度并内部滚动,话题过多时不再撑长页面

实现
- 模板新增 topic-bar / chip-toggle 结构
- app.js 新增 initTopicBar():按渲染后是否换行判断是否需要收起
- app.css 新增 .topic-bar / .topic-chips.collapsed|expanded / .chip-toggle / .topic-manage
4 个文件变更 +96 -5 fangqihang1717@163.com
•web/static/app.css +25 -1
•web/static/app.js +60 -0
•web/templates/admin.html +1 -1
•web/templates/front.html +10 -3
变更内容
diff --git a/web/static/app.css b/web/static/app.css
index 5702d8e..1652474 100644
--- a/web/static/app.css
+++ b/web/static/app.css
@@ -111,7 +111,31 @@ button { font-family: inherit; }
 
 /* ---------------- 话题 ---------------- */
 .page-head { margin-bottom: 12px; }
-.topic-chips { display: flex; flex-wrap: wrap; gap: 8px; }
+.topic-bar { display: flex; align-items: center; gap: 10px; }
+.topic-chips { display: flex; flex-wrap: wrap; gap: 8px; flex: 1; min-width: 0; }
+/* 话题较多时收起为单行横向滚动,避免话题墙占满页面顶部(由 app.js 按需添加) */
+.topic-chips.collapsed {
+  flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
+  scrollbar-width: none; -ms-overflow-style: none;
+  -webkit-overflow-scrolling: touch;
+  /* 左右渐隐,暗示可以横向滑动 */
+  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
+  mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
+}
+.topic-chips.collapsed::-webkit-scrollbar { display: none; }
+.topic-chips.collapsed .chip { flex: 0 0 auto; }
+/* 展开后限制高度,超出部分内部滚动 */
+.topic-chips.expanded { max-height: 34vh; overflow-y: auto; padding-right: 4px; }
+.chip-toggle {
+  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px;
+  padding: 4px 10px; border-radius: 999px; font-size: 12px; cursor: pointer;
+  background: var(--clv-card); border: 1px solid var(--clv-border); color: var(--clv-muted);
+}
+.chip-toggle:hover { color: var(--clv-text); border-color: var(--clv-accent); }
+.chip-toggle svg { transition: transform .2s; }
+.chip-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
+/* 后台话题管理:话题过多时限制高度,内部滚动 */
+.topic-manage { max-height: 220px; overflow-y: auto; padding-right: 4px; }
 .chip {
   display: inline-flex; align-items: center; gap: 4px;
   padding: 4px 12px; border-radius: 999px; font-size: 13px;
diff --git a/web/static/app.js b/web/static/app.js
index 99253e0..867d5a3 100644
--- a/web/static/app.js
+++ b/web/static/app.js
@@ -900,6 +900,65 @@
     }
   }
 
+  /* ---------------- 首页话题栏:话题过多时收起为单行 ---------------- */
+  function initTopicBar() {
+    var chips = document.getElementById('topic-chips');
+    var toggle = document.getElementById('topic-toggle');
+    if (!chips || !toggle) return;
+    var first = chips.firstElementChild;
+    if (!first) return;
+
+    // 先按默认样式渲染,若换行超过一行说明话题较多
+    var wrapped = false;
+    for (var i = 1; i < chips.children.length; i++) {
+      if (chips.children[i].offsetTop !== first.offsetTop) { wrapped = true; break; }
+    }
+    if (!wrapped) return; // 话题不多,保持原有的自动换行
+
+    chips.classList.add('collapsed');
+    toggle.hidden = false;
+    var label = toggle.querySelector('span');
+
+    // 选中的话题可能被滚出可视区,居中显示它
+    function revealActive() {
+      var act = chips.querySelector('.chip.active');
+      if (!act) return;
+      chips.scrollLeft = act.offsetLeft - (chips.clientWidth - act.offsetWidth) / 2;
+    }
+    revealActive();
+
+    var rt = null;
+    window.addEventListener('resize', function () {
+      if (!chips.classList.contains('collapsed')) return;
+      clearTimeout(rt);
+      rt = setTimeout(revealActive, 150);
+    });
+
+    // PC 端:竖向滚轮转横向滚动
+    chips.addEventListener('wheel', function (e) {
+      if (!chips.classList.contains('collapsed')) return;
+      if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return;
+      e.preventDefault();
+      chips.scrollLeft += e.deltaY;
+    }, { passive: false });
+
+    toggle.addEventListener('click', function () {
+      if (chips.classList.contains('expanded')) {
+        chips.classList.remove('expanded');
+        chips.classList.add('collapsed');
+        toggle.setAttribute('aria-expanded', 'false');
+        if (label) label.textContent = '展开';
+        chips.scrollTop = 0;
+        revealActive();
+      } else {
+        chips.classList.remove('collapsed');
+        chips.classList.add('expanded');
+        toggle.setAttribute('aria-expanded', 'true');
+        if (label) label.textContent = '收起';
+      }
+    });
+  }
+
   /* ---------------- 旧版数据迁移(后台) ---------------- */
   function initMigrate() {
     var form = document.getElementById('migrate-form');
@@ -1017,5 +1076,6 @@
     initPatrol();
     initAdmin();
     initMigrate();
+    initTopicBar();
   });
 })();
diff --git a/web/templates/admin.html b/web/templates/admin.html
index 23ab7f6..0a6430a 100644
--- a/web/templates/admin.html
+++ b/web/templates/admin.html
@@ -419,7 +419,7 @@
 <div class="grid2-cards">
   <div class="card">
     <h3>话题管理</h3>
-    <div class="topic-chips">
+    <div class="topic-chips topic-manage">
       {{range .topics}}
       <span class="chip"># {{.name}}
         <form method="post" action="/admin/notices" class="inline-form" data-confirm="确定删除该话题吗?">
diff --git a/web/templates/front.html b/web/templates/front.html
index 8f81901..f7dfc61 100644
--- a/web/templates/front.html
+++ b/web/templates/front.html
@@ -1,9 +1,16 @@
 {{/* ============ 首页:瀑布流 + 无限滚动 ============ */}}
 {{define "pg_index"}}
 <div class="page-head">
-  {{if .topics}}<div class="topic-chips">
-    <a class="chip {{if not .topic}}active{{end}}" href="/">全部</a>
-    {{range .topics}}<a class="chip {{if eq $.topic .name}}active{{end}}" href="/?topic={{.name}}"># {{.name}}</a>{{end}}
+  {{if .topics}}<div class="topic-bar">
+    <div class="topic-chips" id="topic-chips">
+      <a class="chip {{if not .topic}}active{{end}}" href="/">全部</a>
+      {{range .topics}}<a class="chip {{if eq $.topic .name}}active{{end}}" href="/?topic={{.name}}"># {{.name}}</a>{{end}}
+    </div>
+    {{/* 话题较多时由脚本收起为单行,此处按钮用于展开全部 */}}
+    <button type="button" class="chip-toggle" id="topic-toggle" hidden aria-expanded="false" aria-controls="topic-chips">
+      <span>展开</span>
+      <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
+    </button>
   </div>{{end}}
 </div>
 <div class="masonry" id="masonry" data-topic="{{.topic}}" aria-live="polite"></div>