From 64f1fbf906010eb2f38b5bc9e677ddc554e40e85 Mon Sep 17 00:00:00 2001 From: stash Date: Wed, 28 Oct 2026 12:44:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20=E5=89=8D=E7=AB=AF=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20@stash/ui=20=E7=BB=84=E4=BB=B6=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增本地依赖 @stash/ui(file:../../stash-ui),引入其 style.css 与设计令牌 - 页面骨架改用 Layout(contained + header/sider),搜索框改为吸顶 - 卡片/菜单/按钮/标签/头像/分页/空态/提示全部换成组件库组件 - 删除本地 iconfont 与 1400 行手写组件样式,App.css 只保留页面级样式 - 数据层、路由、过滤逻辑未改动;33 个 Rust 测试通过 --- stash-web/package-lock.json | 24 + stash-web/package.json | 1 + stash-web/src/App.css | 1636 ++--------------- stash-web/src/App.tsx | 204 +- stash-web/src/assets/iconfont.woff2 | Bin 52136 -> 0 bytes stash-web/src/components/common/BackToTop.tsx | 29 +- .../src/components/common/CountBadge.tsx | 8 +- stash-web/src/components/common/Icon.tsx | 40 +- stash-web/src/components/content/AppCard.tsx | 44 +- stash-web/src/components/content/AppGrid.tsx | 10 +- .../src/components/content/ArticleBody.tsx | 10 +- .../src/components/content/ArticleCard.tsx | 15 +- .../components/content/ArticleDetailView.tsx | 56 +- .../src/components/content/ArticleGrid.tsx | 7 +- stash-web/src/components/content/CesCard.tsx | 45 +- .../src/components/content/CesCardGrid.tsx | 7 +- .../src/components/content/DetailView.tsx | 35 +- .../src/components/content/GroupBlock.tsx | 9 +- stash-web/src/components/content/HomeView.tsx | 3 +- .../src/components/content/ItemDetailView.tsx | 100 +- .../src/components/content/PosterCard.tsx | 15 +- .../src/components/content/PosterGrid.tsx | 9 +- .../src/components/content/RelatedList.tsx | 47 +- .../src/components/content/SectionCard.tsx | 38 +- .../src/components/header/HeaderInfoItem.tsx | 15 +- .../src/components/header/SiteHeader.tsx | 65 +- stash-web/src/components/search/SearchBar.tsx | 29 +- .../src/components/sidebar/CategoryNav.tsx | 116 +- .../src/components/sidebar/ProfileCard.tsx | 22 +- .../src/components/sidebar/ServicePanel.tsx | 86 +- stash-web/src/components/sidebar/Sidebar.tsx | 4 +- stash-web/src/iconfont.css | 74 - stash-web/src/index.css | 80 +- stash-web/src/main.tsx | 2 +- stash-web/vite.config.ts | 4 + 35 files changed, 735 insertions(+), 2154 deletions(-) delete mode 100644 stash-web/src/assets/iconfont.woff2 delete mode 100644 stash-web/src/iconfont.css diff --git a/stash-web/package-lock.json b/stash-web/package-lock.json index 7a7d8d7..a526936 100644 --- a/stash-web/package-lock.json +++ b/stash-web/package-lock.json @@ -8,6 +8,7 @@ "name": "stash-web", "version": "0.0.0", "dependencies": { + "@stash/ui": "file:../../stash-ui", "react": "^19.3.1", "react-dom": "^19.3.1" }, @@ -21,6 +22,25 @@ "vite": "^8.3.4" } }, + "../../stash-ui": { + "name": "@stash/ui", + "version": "0.1.0", + "devDependencies": { + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.2", + "oxlint": "^1.88.0", + "playwright-core": "^1.64.1", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "typescript": "~6.0.3", + "vite": "^8.3.4" + }, + "peerDependencies": { + "react": ">=19", + "react-dom": ">=19" + } + }, "node_modules/@oxc-project/types": { "version": "0.154.0", "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.154.0.tgz", @@ -616,6 +636,10 @@ "dev": true, "license": "MIT" }, + "node_modules/@stash/ui": { + "resolved": "../../stash-ui", + "link": true + }, "node_modules/@types/node": { "version": "24.19.2", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.19.2.tgz", diff --git a/stash-web/package.json b/stash-web/package.json index 37c7667..df086d3 100644 --- a/stash-web/package.json +++ b/stash-web/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@stash/ui": "file:../../stash-ui", "react": "^19.3.1", "react-dom": "^19.3.1" }, diff --git a/stash-web/src/App.css b/stash-web/src/App.css index ad0c908..7734ff0 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1,1652 +1,408 @@ -/* ============================================================ - 页面背景:复刻 Alright 的 .site-background - ============================================================ */ - -.site-background { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 65vh; - background-image: url('./assets/site-bg.jpg'); - background-size: cover; - background-position: top center; - background-repeat: no-repeat; - pointer-events: none; - z-index: 0; -} - -/* 底部 80% 高度做一层「底色 → 透明」的渐变,让图片自然融进页面背景 */ -.site-background::after { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 80%; - background-image: linear-gradient(0deg, var(--bg-color) 0%, rgba(0, 0, 0, 0) 100%); -} - -/* ============================================================ - 全局容器 - ============================================================ */ +/* 页面级样式:只保留「组件库没有」的部分(背景、外壳、封面比例、正文版式)。 + 颜色 / 间距 / 字号全部取自 @stash/ui 的令牌,组件内部样式不要再在这里覆盖。 */ .app { - height: 100%; - display: flex; - flex-direction: column; + background-color: var(--su-bg-page); } -/* 版心:顶栏与内容区共用,保证左右边界一?*/ -.layout-container { - width: 100%; - max-width: var(--layout-max-width); - margin: 0 auto; - padding: 0 20px; -} - -.card-box { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-main); - box-shadow: var(--shadow-card); -} - -/* 仅屏幕阅读器可见 */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* ============================================================ - 顶部栏:品牌 + 运行信息 + 操作图标 - ============================================================ */ - -.site-header { +/* 顶部背景图:固定 65vh,底部渐隐到页面底色 */ +.site-background { position: fixed; - top: 0; - left: 0; - width: 100%; - height: var(--top-nav-height); - background-color: var(--card-bg); - border-bottom: 1px solid var(--border-color); - z-index: 200; + inset: 0 0 auto; + z-index: -1; + height: 65vh; + background: url('./assets/site-bg.jpg') center top / cover no-repeat; + opacity: 0.16; + mask-image: linear-gradient(to bottom, #000 35%, transparent 100%); } .site-header-inner { - height: 100%; display: flex; align-items: center; - justify-content: space-between; - gap: 16px; + gap: var(--su-space-6); + height: 64px; } .site-brand { display: flex; align-items: center; - gap: 10px; - flex-shrink: 0; + gap: var(--su-space-2); } .site-brand-mark { + display: grid; + place-items: center; width: 32px; height: 32px; - border-radius: 50%; - background-color: var(--text-main); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - font-weight: 700; + border-radius: var(--su-radius-sm); + background-color: var(--su-color-primary); + color: var(--su-white); + font-size: var(--su-font-size-md); + font-weight: var(--su-font-weight-strong); } .site-brand-name { - font-size: 18px; - font-weight: 800; + font-size: var(--su-font-size-lg); + line-height: var(--su-line-height-lg); letter-spacing: 0.04em; - color: var(--text-main); } -/* 中间运行信息 */ - .header-info { - display: flex; - align-items: center; - gap: 20px; - min-width: 0; - font-size: 14px; - color: var(--text-sub); + flex: 1; + justify-content: center; } -.header-info-item { - display: flex; - align-items: center; - gap: 6px; - white-space: nowrap; -} - -.header-info-tag { - padding: 2px 8px; - border-radius: 4px; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 12px; -} - -.header-info-tag.is-green { - background-color: rgba(74, 189, 134, 0.12); - color: var(--green); -} - -.header-info-text { - overflow: hidden; - text-overflow: ellipsis; -} - -/* 右侧图标 */ - .header-actions { + margin-left: auto; +} + +.content { display: flex; - align-items: center; - gap: 16px; - flex-shrink: 0; - color: var(--text-sub); + flex-direction: column; + gap: var(--su-space-4); + padding-top: var(--su-space-2); } -.header-icon-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 6px; - border-radius: 50%; - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.header-icon-button:hover { - background-color: var(--border-strong); - color: var(--text-main); -} - -.header-icon-button .iconfont { - font-size: 19px; -} - -/* ============================================================ - 搜索框:固定悬浮在顶栏下方居? ============================================================ */ - -/* 固定悬浮、相对整页居中,与设计稿一?*/ +/* 搜索框吸在页头下方,滚动时内容从它下面穿过 */ .search-row { - position: fixed; - top: 76px; - left: 50%; - transform: translateX(-50%); - width: min(480px, calc(100vw - 48px)); - height: 44px; - z-index: 100; -} - -.search-bar { - display: flex; - align-items: center; - gap: 10px; - height: 100%; - padding: 0 12px; -} - -.search-icon { - flex-shrink: 0; - font-size: 16px; - color: var(--text-sub); -} - -.search-input { - flex: 1; - min-width: 0; - height: 36px; - border: none; - outline: none; - background: transparent; - font-size: 14px; - color: var(--text-main); -} - -.search-input::placeholder { - color: var(--text-muted); -} - -.search-clear { - flex-shrink: 0; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - color: var(--text-muted); - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.search-clear:hover { - background-color: var(--border-strong); - color: var(--text-main); -} - -.search-clear .iconfont { - font-size: 14px; -} - -/* ============================================================ - 双列布局:左侧栏 + 右侧内容 - ============================================================ */ - -.layout { - position: relative; - z-index: 1; - width: 100%; - max-width: var(--layout-max-width); - margin: var(--top-nav-height) auto 0; - height: calc(100vh - var(--top-nav-height)); - padding: 36px 20px 0; - display: grid; - grid-template-columns: var(--sidebar-width) minmax(0, 1fr); - gap: 20px; - align-items: stretch; + position: sticky; + top: 64px; + z-index: var(--su-z-header); + padding: var(--su-space-2) 0; + background-color: color-mix(in srgb, var(--su-bg-page) 88%, transparent); + backdrop-filter: blur(8px); } .sidebar { + position: sticky; + top: 84px; display: flex; flex-direction: column; - gap: 16px; - min-height: 0; - /* 与右侧首个分区卡片对?*/ - margin-top: var(--content-top-offset); + gap: var(--su-space-3); + padding: var(--su-space-4) var(--su-space-4) var(--su-space-4) 0; } -/* ---------- 用户资料?---------- */ +.profile-card, +.service-panel, +.nav-card { + flex-shrink: 0; +} .profile-card { display: flex; flex-direction: column; align-items: center; - gap: 6px; - padding: 18px 14px 14px; + gap: var(--su-space-2); text-align: center; - flex-shrink: 0; -} - -.profile-avatar { - position: relative; - width: 64px; - height: 64px; - margin-bottom: 4px; - border-radius: 50%; - display: grid; - place-items: center; - background: linear-gradient(135deg, #dfe4ff, #c9d0ff); - color: var(--primary-color); -} - -.profile-avatar .iconfont { - font-size: 34px; -} - -.profile-badge { - position: absolute; - right: -2px; - bottom: -2px; - width: 20px; - height: 20px; - border-radius: 50%; - border: 2px solid var(--card-bg); - background-color: var(--orange); - color: #fff; - display: grid; - place-items: center; - font-size: 10px; - font-weight: 700; -} - -.profile-name { - font-size: 14px; - font-weight: 600; - color: var(--text-main); - line-height: 1.2; -} - -.profile-role { - padding: 1px 8px; - border: 1px solid var(--border-color); - border-radius: 999px; - font-size: 12px; - color: var(--text-sub); -} - -.profile-bio { - margin: 2px 0 0; - font-size: 12px; - line-height: 1.55; - color: var(--text-sub); -} - -.profile-location { - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); -} - -/* ---------- 本地服务面板 ---------- */ - -.service-panel { - padding: 12px 14px; - flex-shrink: 0; -} - -.service-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.service-title { - font-size: 14px; - font-weight: 600; - color: var(--text-main); -} - -.service-pill { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 2px 8px; - border-radius: 999px; - background-color: var(--border-strong); - font-size: 12px; - color: var(--text-sub); -} - -.service-pill.is-on { - background-color: rgba(34, 197, 94, 0.12); - color: var(--green); } .service-stats { - display: grid; - gap: 6px; - margin-top: 10px; -} - -.service-stats > div { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - font-size: 12px; -} - -.service-stats dt { - flex-shrink: 0; - color: var(--text-sub); -} - -.service-stats dd { - margin: 0; - overflow: hidden; - font-family: var(--font-mono); - font-size: 12px; - font-weight: 600; - color: var(--text-main); - text-overflow: ellipsis; - white-space: nowrap; -} - -.service-actions { - display: flex; - gap: 8px; - margin-top: 12px; -} - -.service-action { - flex: 1; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 5px; - padding: 6px 8px; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--card-bg); - color: var(--text-sub); - font-size: 12px; - white-space: nowrap; - cursor: pointer; - transition: - border-color 0.2s, - color 0.2s, - background-color 0.2s; -} - -.service-action:not(:disabled):hover { - border-color: var(--primary-color); - background-color: var(--primary-soft); - color: var(--primary-color); -} - -.service-action:disabled { - opacity: 0.6; - cursor: default; -} - -.service-action .iconfont { - font-size: 14px; -} - -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--border-strong); - flex-shrink: 0; - transition: - background-color 0.2s, - box-shadow 0.2s; -} - -.status-dot.is-on { - background-color: var(--green); - box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16); -} - -/* ---------- 分类导航 ---------- */ - -.nav-card { - padding: 8px; - flex-shrink: 0; -} - -.nav-group { - list-style: none; -} - -.nav-item { - margin-bottom: 4px; -} - -.nav-item:last-child { - margin-bottom: 0; -} - -.nav-link { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 9px 12px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - color: var(--text-sub); - cursor: pointer; - text-align: left; - transition: - background-color 0.2s, - color 0.2s; -} - -.nav-link:hover, -.nav-link.active { - background-color: var(--primary-soft); - color: var(--primary-color); -} - -/* 折叠箭头:复刻主题的 6×6 边框 V 形,靠右对齐 */ -.nav-link.has-children::after { - content: ''; - flex-shrink: 0; - width: 6px; - height: 6px; - border-left: 1px solid currentColor; - border-bottom: 1px solid currentColor; - opacity: 0.4; - transform: rotate(315deg); - transition: - transform 0.3s, - opacity 0.3s; -} - -.nav-link.has-children:hover::after { - opacity: 0.7; -} - -.nav-item.open > .nav-link.has-children::after { - opacity: 0.7; - transform: rotate(135deg); -} - -/* 子菜单展开:用 grid-template-rows 0fr → 1fr 做高度动画, - 高度精确贴合内容,全程 overflow: hidden,不会出现滚动条 */ -.submenu-wrap { - display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 0.28s ease; -} - -.submenu-wrap.is-open { - grid-template-rows: 1fr; -} - -.submenu { - list-style: none; - min-height: 0; - padding-left: 12px; - overflow: hidden; -} - -.submenu .nav-link { - padding: 7px 12px; - font-size: 12px; -} - -.submenu .nav-link > span:first-child { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ============================================================ - 右侧内容? ============================================================ */ - -.content { - position: relative; display: flex; flex-direction: column; - min-height: 0; - /* 让出悬浮的搜索框;侧栏与遮罩层都按同一个变量对?*/ - padding-top: var(--content-top-offset); + gap: var(--su-space-2); + margin: var(--su-space-3) 0; } -/* 滚动后才淡入的顶部渐隐层? 卡片滚到容器顶边时被硬切会很难看,用一层背景色渐变把它柔化?*/ -.content-veil { - position: absolute; - top: var(--content-top-offset); - right: 0; - left: 0; - height: 32px; - pointer-events: none; - opacity: 0; - background: linear-gradient(to bottom, var(--bg-color) 0%, transparent 100%); - transition: opacity 0.2s ease; - z-index: 5; +.service-host { + max-width: 140px; } -.content-veil.is-visible { - opacity: 1; -} - -.content-scroll { - flex: 1; - min-height: 0; - overflow-y: auto; - padding-right: 4px; - padding-bottom: 40px; - scroll-behavior: smooth; - -ms-overflow-style: none; - scrollbar-width: none; -} - -.content-scroll::-webkit-scrollbar { - display: none; - width: 0; -} - -/* ---------- 首页:分区卡?---------- */ - .home-view { display: flex; flex-direction: column; - gap: 20px; -} - -.section-card { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: 12px; - padding: 20px; - box-shadow: var(--shadow-card); + gap: var(--su-space-4); } .section-header { display: flex; align-items: center; justify-content: space-between; - margin-bottom: 16px; -} - -.section-title-wrapper { - display: flex; - align-items: center; + margin-bottom: var(--su-space-4); } .section-bar { width: 4px; height: 16px; - margin-right: 8px; - border-radius: 2px; - background-color: var(--primary-color); + border-radius: var(--su-radius-pill); } -/* 标题容器设为 relative,角标才能贴在标题右上角 */ -.section-title-box { - position: relative; - display: inline-flex; - align-items: center; -} - -.section-title { - font-size: 16px; - font-weight: 600; - line-height: 1.2; - color: var(--text-main); -} - -.count-badge { - position: absolute; - top: -8px; - right: -18px; - display: flex; - align-items: center; - justify-content: center; - height: 16px; - min-width: 16px; - padding: 0 4px; - border-radius: 8px; - background-color: var(--danger); - box-shadow: 0 1px 3px rgba(255, 77, 79, 0.3); - color: #fff; - font-size: 12px; - font-weight: 700; - line-height: 1; -} - -.section-more { - display: inline-flex; - align-items: center; - padding: 4px 6px; - border-radius: 4px; - color: var(--text-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - transition: - background-color 0.2s, - color 0.2s; -} - -.section-more:hover { - background-color: var(--border-color); - color: var(--text-sub); -} - -.section-empty { - padding: 8px 0; - font-size: 14px; - color: var(--text-sub); -} - -/* ---------- 应用网格 ---------- */ - -.app-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 12px; -} - -.app-grid--detail { - gap: 16px; -} +/* ---------- 应用条目 ---------- */ .app-card { display: flex; align-items: center; - gap: 15px; - padding: 6px 0; - /* 和主题的作者条目一样:没有底色、描边、阴影,就是一行内容 */ - background: none; - border: 0; - color: inherit; + gap: var(--su-space-3); + padding: var(--su-space-2); + border-radius: var(--su-radius-sm); + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } -.app-card:hover .app-card-name { - color: var(--primary-color); -} - -/* 48px 圆形头像 + 右下角认证角标,对应主题 .flex-avatar / .avatar-badge.badge_5 */ -.app-card-avatar { - position: relative; - width: 48px; - height: 48px; - flex-shrink: 0; - border-radius: 100%; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 18px; - font-weight: 700; -} - -/* 有封面图时直接铺满圆形头像 */ -.app-card-avatar img { - width: 100%; - height: 100%; - border-radius: inherit; - object-fit: cover; - display: block; -} - -.app-card-verify { - position: absolute; - right: 0; - bottom: 0; - width: 10px; - height: 10px; - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNiIgaGVpZ2h0PSIzNiIgZmlsbD0ibm9uZSI+PGNpcmNsZSBjeD0iMTgiIGN5PSIxOCIgcj0iMTgiIGZpbGw9InVybCgjYSkiLz48cGF0aCBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOC4yNSAxMS44NDRhMi4yMzIgMi4yMzIgMCAwIDEgMCAzLjE1NkwxNy41ODggMjUuNjYyYTIuMzggMi4zOCAwIDAgMS0zLjM2NiAwbC01LjY0NC01LjY0NGEyLjIzMiAyLjIzMiAwIDAgMSAzLjE1Ni0zLjE1NWw0LjE3IDQuMTcgOS4xOS05LjE4OWEyLjIzMiAyLjIzMiAwIDAgMSAzLjE1NiAwWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMzEuNzE0IiB4Mj0iLTMuNzMxIiB5MT0iLjg1NyIgeTI9IjExLjgzOSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNGRkI3NEEiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRDkwMEYiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4="); - background-position: center; - background-repeat: no-repeat; - background-size: contain; +.app-card:hover { + background-color: var(--su-bg-subtle); } .app-card-info { display: flex; flex-direction: column; - justify-content: center; - min-width: 0; - flex: 1 1 auto; - overflow: hidden; -} - -.app-card-title { - display: flex; - align-items: center; - gap: 6px; + gap: 2px; min-width: 0; } -.app-card-name { - font-size: 16px; - font-weight: 500; - line-height: 24px; - color: var(--text-main); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* ---------- 封面卡(海报 / 图文) ---------- */ -/* 名字后的 HOT 徽标:主题里是 24×16 的图形,不是文字 */ -.app-card-badge { - position: relative; - top: 2px; - display: inline-block; - flex-shrink: 0; - width: 24px; - height: 16px; - background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MCIgaGVpZ2h0PSIyMSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0idXJsKCNhKSIgZD0iTTAgNGE0IDQgMCAwIDEgNC00aDQyYTQgNCAwIDAgMSA0IDR2MTNhNCA0IDAgMCAxLTQgNEg0YTQgNCAwIDAgMS00LTRWNFoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNi4yODYgOS4zNDVoOC4wM1Y0LjE3NmgyLjI4NnYxMi42NDhoLTIuMjg3di01LjE2OUg2LjI4NnY1LjE2OUg0VjQuMTc2aDIuMjg2djUuMTY5Wk0zMi41MTkgMTEuNzA4YzAgLjc3LS4xMzIgMS40NzktLjM5NiAyLjEyNmE1LjAzOSA1LjAzOSAwIDAgMS0xLjEgMS42NzUgNS4wMiA1LjAyIDAgMCAxLTEuNjcgMS4xMDNBNS41MjYgNS41MjYgMCAwIDEgMjcuMjYgMTdoLTMuMDk1Yy0uNzU3IDAtMS40NTctLjEzLTIuMTAyLS4zODhhNS4wMiA1LjAyIDAgMCAxLTEuNjcxLTEuMTAyIDUuMTUyIDUuMTUyIDAgMCAxLTEuMTA4LTEuNjc2IDUuNTc2IDUuNTc2IDAgMCAxLS4zOTYtMi4xMjZWOS4yOTJjMC0uNzY1LjEzMi0xLjQ3LjM5Ni0yLjExN2E1LjAzIDUuMDMgMCAwIDEgMS4xMDgtMS42NzYgNS4wMjIgNS4wMjIgMCAwIDEgMS42Ny0xLjEwMkE1LjQ4NCA1LjQ4NCAwIDAgMSAyNC4xNjYgNGgzLjA5NWMuNzU2IDAgMS40NTQuMTMyIDIuMDkzLjM5N2E1LjAyMiA1LjAyMiAwIDAgMSAxLjY3IDEuMTAyIDQuOTIyIDQuOTIyIDAgMCAxIDEuMSAxLjY3NmMuMjY0LjY0Ny4zOTYgMS4zNTIuMzk2IDIuMTE3djIuNDE2Wm0tMi4yODctMi40MTZjMC0uNDUzLS4wNzMtLjg2Mi0uMjItMS4yMjZhMi41ODkgMi41ODkgMCAwIDAtLjYwNi0uOTQ0IDIuNTggMi41OCAwIDAgMC0uOTQxLS42MDggMy4xODYgMy4xODYgMCAwIDAtMS4yMDUtLjIyaC0zLjA5NWMtLjQ0NiAwLS44NTMuMDczLTEuMjIzLjIyYTIuNjgxIDIuNjgxIDAgMCAwLS45NC42MDhjLS4yNjUuMjU5LS40Ny41NzMtLjYxNi45NDQtLjE0MS4zNjQtLjIxMS43NzMtLjIxMSAxLjIyNnYyLjQxNmMwIC40NTMuMDcuODY1LjIxIDEuMjM1LjE0Ny4zNjUuMzUyLjY4LjYxNi45NDQuMjY0LjI1OC41NzguNDYxLjk0MS42MDhhMy40IDMuNCAwIDAgMCAxLjIyMy4yMTJoMy4wNzhjLjQ0NSAwIC44NS0uMDcgMS4yMTMtLjIxMi4zNy0uMTQ3LjY4Ni0uMzUuOTUtLjYwOC4yNjQtLjI2NS40NjYtLjU4LjYwNy0uOTQ0LjE0Ni0uMzcuMjItLjc4Mi4yMi0xLjIzNVY5LjI5MlpNNDYgNi40N2gtNS4wMzl2MTAuMzU0aC0yLjI4NlY2LjQ2OWgtNS4wNDhWNC4xNzZINDZWNi40N1oiLz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSI0NC4wNDgiIHgyPSI5LjEwNyIgeTE9Ii41IiB5Mj0iMjYuMjc2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI0ZGN0I1MSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0ZGMEUwRSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjwvc3ZnPg=="); - background-position: center; - background-repeat: no-repeat; - background-size: contain; -} - -.app-card-desc { - font-size: 12px; - line-height: 16px; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* ---------- 海报网格 ---------- */ - -.poster-grid { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 12px; -} - -.poster-grid--detail { - gap: 14px; -} - -.poster-card { +.cover-card { position: relative; display: block; width: 100%; - margin: 0; - padding: 0; - border: none; + overflow: hidden; + border-radius: var(--su-radius-sm); + background-color: var(--su-bg-light); + text-align: left; +} + +.cover { + display: block; + width: 100%; + object-fit: cover; + transition: transform var(--su-duration-base) var(--su-ease-in-out); +} + +.cover--poster { aspect-ratio: 2 / 3; - border-radius: 8px; - overflow: hidden; - background-color: var(--border-strong); - text-align: left; - font: inherit; - cursor: pointer; - transition: transform 0.2s ease; } -.poster-card:hover { - transform: translateY(-2px); -} - -.poster-cover { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.poster-caption { - position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 20px 8px 8px; - background: linear-gradient( - to top, - rgba(0, 0, 0, 0.85) 0%, - rgba(0, 0, 0, 0.4) 60%, - transparent 100% - ); - color: #fff; -} - -.poster-title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - font-size: 12px; - font-weight: 500; - line-height: 1.3; -} - -/* ---------- 首页图文网格 ---------- */ - -.article-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 12px; -} - -.article-card { - position: relative; - display: block; - width: 100%; - margin: 0; - padding: 0; - border: none; +.cover--article { aspect-ratio: 4 / 3; - border-radius: 6px; - overflow: hidden; - background-color: var(--border-strong); - text-align: left; - font: inherit; - cursor: pointer; - transition: transform 0.2s ease; } -.article-card:hover { - transform: translateY(-2px); +.cover-card:hover .cover { + transform: scale(1.04); } -.article-cover { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.article-caption { +.cover-caption { position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 16px 6px 6px; - background: linear-gradient( - to top, - rgba(0, 0, 0, 0.8) 0%, - rgba(0, 0, 0, 0.3) 70%, - transparent 100% - ); - color: #fff; -} - -.article-title { + inset: auto 0 0; display: -webkit-box; + overflow: hidden; + padding: 24px var(--su-space-2) var(--su-space-2); + background: linear-gradient(transparent, rgb(0 0 0 / 62%)); + color: var(--su-white); + font-size: var(--su-font-size-sm); + line-height: var(--su-line-height-sm); -webkit-box-orient: vertical; -webkit-line-clamp: 2; - overflow: hidden; - font-size: 12px; - line-height: 1.3; -} - -/* ---------- 明细?---------- */ - -.detail-view { - display: flex; - flex-direction: column; - gap: 20px; -} - -.detail-nav { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 20px; -} - -.detail-back { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--primary-color); - font-size: 14px; - font-weight: 500; - cursor: pointer; -} - -.detail-back:hover { - text-decoration: underline; -} - -.detail-breadcrumb { - font-size: 12px; - color: var(--text-muted); -} - -.detail-hero { - display: flex; - flex-direction: column; - align-items: center; - padding: 12px 0 4px; - text-align: center; -} - -.detail-hero-icon { - width: 44px; - height: 44px; - margin-bottom: 8px; - border-radius: 50%; - background-color: var(--primary-soft); - color: var(--primary-color); - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; -} - -.detail-hero-title { - font-size: 24px; - font-weight: 700; - color: var(--text-main); -} - -.detail-hero-count { - margin-top: 4px; - font-size: 14px; - color: var(--text-muted); -} - -/* 分组块:小标?+ 该组网格 */ -.group-block { - margin-bottom: 12px; -} - -.group-title { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 12px; - font-size: 16px; - font-weight: 600; - color: var(--text-main); -} - -.group-title::before { - content: ''; - width: 3px; - height: 14px; - border-radius: 2px; - background-color: var(--primary-color); -} - -/* 明细页图文白?*/ -.ces-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 16px; } .ces-card { - display: flex; - flex-direction: column; - width: 100%; - padding: 0; overflow: hidden; - border: 1px solid var(--border-color); - border-radius: 10px; - background-color: var(--card-bg); - box-shadow: var(--shadow-card); - text-align: left; - font: inherit; - cursor: pointer; - transition: - transform 0.2s, - box-shadow 0.2s; -} - -.ces-card:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-hover); -} - -.ces-cover { - width: 100%; - aspect-ratio: 16 / 10; - object-fit: cover; - display: block; - background-color: var(--border-color); } .ces-body { display: flex; flex-direction: column; - gap: 4px; - padding: 12px; + padding: var(--su-space-3); + text-align: left; } -.ces-title { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - font-size: 14px; - font-weight: 500; - line-height: 1.4; - color: var(--text-main); -} - -.ces-desc { - display: block; - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); -} - -/* ============================================================ - 文章详情页(点击图文卡片进入? ============================================================ */ - -.article-head { - display: flex; - flex-direction: column; - align-items: center; - gap: 12px; - padding: 4px 0; - text-align: center; -} - -.article-heading { - max-width: 760px; - font-size: 24px; - font-weight: 700; - line-height: 1.4; - color: var(--text-main); -} - -.article-meta { - display: flex; - align-items: center; - font-size: 12px; - color: var(--text-muted); -} - -.article-meta-item { - display: inline-flex; - align-items: center; -} - -.article-meta-dot { - width: 3px; - height: 3px; - margin: 0 8px; - border-radius: 50%; - background-color: var(--border-strong); -} - -.article-body { - padding: 32px 40px 40px; -} - -.article-body-text { - margin-bottom: 16px; - font-size: 14px; - line-height: 1.9; - color: var(--text-sub); -} - -.article-body-heading { - margin: 28px 0 16px; - font-size: 16px; - font-weight: 700; - color: var(--text-main); -} - -.article-body-image { - display: block; +.ces-body--button { width: 100%; - margin: 20px 0; - border-radius: 4px; - background-color: var(--border-color); + cursor: pointer; } -/* ============================================================ - 条目详情页(点击应用卡片进入? ============================================================ */ +.ces-body--button:hover { + background-color: var(--su-bg-subtle); +} + +/* ---------- 详情 ---------- */ -/* 明细 / 条目详情共用同一套打开动画,和点击 ••?的表现保持一?*/ .detail-view { - animation: view-in 0.22s ease both; -} - -@keyframes view-in { - from { - opacity: 0; - transform: translateY(6px); - } - to { - opacity: 1; - transform: none; - } -} - -.item-card { display: flex; flex-direction: column; - align-items: center; - padding: 24px 28px; + gap: var(--su-space-4); } -.item-title { +.detail-hero { display: flex; align-items: center; - justify-content: center; - gap: 6px; + gap: var(--su-space-3); +} + +.detail-hero-icon { font-size: 24px; - font-weight: 600; - color: var(--text-main); + line-height: 1; } .item-edit { - font-size: 14px; - font-weight: 400; - color: var(--text-muted); - cursor: default; + margin-left: var(--su-space-2); + vertical-align: middle; } -/* 主图:应用是方形头像,电影是竖版海报 */ .item-cover { - display: flex; - align-items: center; - justify-content: center; - margin: 18px 0 10px; + display: grid; + place-items: center; + width: 120px; + margin: var(--su-space-4) 0; overflow: hidden; - background-color: var(--primary-soft); - color: var(--primary-color); + border-radius: var(--su-radius-md); + background-color: var(--su-bg-light); } .item-cover--square { - width: 96px; - height: 96px; - border-radius: 12px; + aspect-ratio: 1; } .item-cover--portrait { - width: 179px; + width: 160px; aspect-ratio: 2 / 3; - border-radius: 8px; } .item-cover img { width: 100%; height: 100%; object-fit: cover; - display: block; } .item-cover-letter { - font-size: 38px; - font-weight: 700; -} - -.item-tags { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px; -} - -.item-tag { - padding: 2px 10px; - border-radius: 999px; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 12px; + font-size: 32px; + color: var(--su-text-sub); } .item-quote { - display: flex; - gap: 6px; - width: 100%; - margin-top: 18px; - text-align: left; -} - -.item-quote-mark { - flex-shrink: 0; - font-size: 32px; - font-weight: 700; - line-height: 1; - color: rgba(255, 62, 62, 0.35); -} - -.item-quote-text { - font-size: 14px; - line-height: 1.7; - color: var(--text-sub); -} - -.item-desc { - width: 100%; - margin-top: 10px; - font-size: 14px; - line-height: 1.7; - color: var(--text-sub); - text-align: left; + padding: var(--su-space-3) var(--su-space-4); + border-left: 2px solid var(--su-color-primary); + border-radius: 0 var(--su-radius-sm) var(--su-radius-sm) 0; + background-color: var(--su-bg-subtle); } .item-actions { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - margin-top: 18px; -} - -.item-stat { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - min-width: 56px; - height: 36px; - padding: 0 12px; - border-radius: 8px; - background-color: var(--bg-color); - color: var(--text-sub); - font-size: 12px; -} - -.item-stat .iconfont { - font-size: 16px; -} - -.item-stat em { - font-style: normal; - font-weight: 600; - color: var(--text-main); -} - -.item-stat--icon { - cursor: pointer; -} - -.item-stat--icon:hover { - background-color: var(--border-color); -} - -.item-jump { - flex: 1; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - height: 36px; - border-radius: 8px; - background-color: var(--primary-color); - color: #fff; - font-size: 14px; - transition: background-color 0.2s; -} - -.item-jump:hover { - background-color: #2c47d8; -} - -.item-jump:disabled { - opacity: 0.55; - cursor: default; -} - -.item-jump .iconfont { - font-size: 14px; + margin-top: var(--su-space-4); } .item-banner { width: 100%; - margin-top: 18px; - aspect-ratio: 3 / 1; - border-radius: 10px; - object-fit: cover; - background-color: var(--border-color); + margin-top: var(--su-space-4); + border-radius: var(--su-radius-md); } -/* 详情页「推荐阅读?*/ +/* ---------- 文章 ---------- */ -.related-card { - padding: 20px; -} - -.related-title { +.article-head { display: flex; - align-items: center; - gap: 8px; - margin-bottom: 16px; - font-size: 16px; - font-weight: 600; - color: var(--text-main); + flex-direction: column; + gap: var(--su-space-2); } -.related-title-icon { - font-size: 16px; +.article-body { + display: flex; + flex-direction: column; + gap: var(--su-space-3); } -.related-grid { - list-style: none; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 14px 24px; +.article-meta-dot { + color: var(--su-text-muted); } +.article-lead { + margin-bottom: var(--su-space-2); +} + +.article-body-image { + width: 100%; + margin: var(--su-space-2) 0; + border-radius: var(--su-radius-md); +} + +/* ---------- 推荐阅读 ---------- */ + .related-item { display: flex; align-items: center; - gap: 10px; - width: 100%; - padding: 4px; - border-radius: 8px; + gap: var(--su-space-2); + padding: var(--su-space-2); + border-radius: var(--su-radius-sm); text-align: left; - cursor: pointer; - transition: background-color 0.2s; + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } .related-item:hover { - background-color: var(--bg-color); -} - -.related-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - background-color: var(--primary-soft); - color: var(--primary-color); - font-size: 14px; - font-weight: 700; - flex-shrink: 0; -} - -.related-avatar img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; + background-color: var(--su-bg-subtle); } .related-info { - display: flex; - flex-direction: column; min-width: 0; } -.related-name { - font-size: 14px; - font-weight: 600; - color: var(--text-main); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* ---------- 通用 ---------- */ -.related-desc { - margin-top: 2px; - font-size: 12px; - color: var(--text-muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -@media (max-width: 900px) { - .related-grid { - grid-template-columns: minmax(0, 1fr); - } -} - -/* ============================================================ - 提示?/ 回到顶部 - ============================================================ */ - -.notice { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 20px; - padding: 24px; - border: 1px dashed var(--border-color); - border-radius: 12px; - background-color: rgba(255, 255, 255, 0.7); - color: var(--text-sub); - font-size: 14px; -} - -.notice strong { - font-size: 14px; - font-weight: 600; - color: var(--text-main); -} - -.notice-error { - border-style: solid; - border-color: rgba(255, 62, 62, 0.25); - background-color: #fff5f5; -} - -.notice-error strong { - color: var(--danger); -} - -.notice-action { - align-self: flex-start; - margin-top: 8px; - padding: 6px 14px; - border: 1px solid var(--border-color); - border-radius: 8px; - background-color: var(--card-bg); - font-size: 14px; - cursor: pointer; -} - -.notice-action:hover { - border-color: var(--primary-color); - color: var(--primary-color); -} - -/* 返回顶部:复刻 Alright 的 .totop-button(42px 圆形浅灰底), - 默认隐藏,内容区滚动后才淡入 */ -.back-to-top { - position: fixed; - top: 50%; - right: 30px; - width: 42px; - height: 42px; - display: flex; +.link-button { + display: inline-flex; align-items: center; - justify-content: center; - border-radius: 50%; - border: 1px solid var(--border-color); - background-color: var(--border-color); - color: var(--text-sub); - cursor: pointer; - z-index: 99; - opacity: 0; - visibility: hidden; - transform: translateY(-50%) translateY(15px); - transition: - opacity 0.3s ease, - visibility 0.3s ease, - transform 0.3s ease, - background-color 0.2s, - color 0.2s; + gap: var(--su-space-1); + height: 32px; + padding: 0 var(--su-space-3); + border-radius: var(--su-radius-sm); + font-size: var(--su-font-size-sm); + color: var(--su-text-sub); + transition: background-color var(--su-duration-fast) var(--su-ease-in-out); } -.back-to-top.is-visible { - opacity: 1; - visibility: visible; - transform: translateY(-50%); +.link-button:hover { + background-color: var(--su-bg-subtle); + color: var(--su-text-main); } -.back-to-top:hover { - background-color: var(--border-strong); - border-color: var(--border-strong); - color: var(--text-main); +.sr-only { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; } -.back-to-top .iconfont { - font-size: 20px; -} - -/* 刷新按钮转圈 */ -.service-action[data-spinning='true'] .iconfont { - animation: spin 0.9s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* ============================================================ - 响应? ============================================================ */ +/* ---------- 响应式 ---------- */ @media (max-width: 1100px) { - :root { - --sidebar-width: 200px; - } - - .app-grid, - .article-grid, - .ces-grid { + .sidebar { + position: static; + display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--su-space-3); + padding: var(--su-space-4); } - .poster-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -@media (max-width: 900px) { - .app { - height: auto; - min-height: 100vh; + .profile-card { + grid-row: span 2; } - .layout-container { - padding: 0 16px; - } - - .site-header { - position: sticky; + .service-panel { + display: none; } .header-info { display: none; } +} - .search-row { - position: static; - left: auto; - top: auto; - transform: none; - width: auto; - height: 44px; - margin: 16px 16px 0; - } - - .layout { - margin-top: 0; - height: auto; - padding: 16px; - grid-template-columns: minmax(0, 1fr); - } - +@media (max-width: 820px) { .sidebar { display: none; } - .content { - padding-top: 0; + .search-row { + top: 60px; } - .content-veil { - display: none; + .site-header-inner { + height: 60px; } +} - .content-scroll { - overflow: visible; - -webkit-mask-image: none; - mask-image: none; - } - - /* 窄屏沿用主题的右下侧定位,隐藏/显示的位移单独处理 */ - .back-to-top { - top: auto; - right: 15px; - bottom: 30%; - transform: translateY(15px); - } - - .back-to-top.is-visible { - transform: translateY(0); +@media (prefers-reduced-motion: reduce) { + .cover, + .app-card, + .related-item, + .link-button { + transition: none; } } diff --git a/stash-web/src/App.tsx b/stash-web/src/App.tsx index 7b557c5..b9edf9b 100644 --- a/stash-web/src/App.tsx +++ b/stash-web/src/App.tsx @@ -1,3 +1,4 @@ +import { Empty, Layout, Notice } from '@stash/ui' import { useEffect, useMemo, useRef, useState } from 'react' import { BackToTop } from './components/common/BackToTop' import { ArticleDetailView } from './components/content/ArticleDetailView' @@ -39,7 +40,6 @@ export default function App() { const { view, go, back } = useViewRoute() const [scrolled, setScrolled] = useState(false) const searchRef = useRef(null) - const scrollRef = useRef(null) const categories = useMemo( () => withLocalCategory(projects, catalog.categories), @@ -103,20 +103,18 @@ export default function App() { return [] }, [itemView, itemCategory]) - // 切换视图时把内容区滚回顶部 + // 切换视图时回到顶部(页面整体滚动) useEffect(() => { - scrollRef.current?.scrollTo({ top: 0 }) + window.scrollTo({ top: 0 }) }, [view]) - // 桌面端滚动作在内容容器里,窄屏滚的是窗口: - // 两处都要驱动「已滚动」状态(顶部渐隐层 + 返回顶部按钮共用) useEffect(() => { const sync = () => { - const inner = scrollRef.current?.scrollTop ?? 0 - const next = Math.max(inner, window.scrollY) > 4 + const next = window.scrollY > 4 setScrolled((current) => (current === next ? current : next)) } + sync() window.addEventListener('scroll', sync, { passive: true }) return () => window.removeEventListener('scroll', sync) }, []) @@ -125,7 +123,6 @@ export default function App() { * 「打开这个分区」应该落到哪个视图。 * * 应用类分区配了首页展示块时,直接落到那个分组;其余情况打开整个分区。 - * 这样卡片上的「···」和分区标题都通向卡片里实际展示的那块内容。 */ const detailViewFor = (categoryId: string): View => { const category = categories.find((item) => item.id === categoryId) @@ -203,21 +200,13 @@ export default function App() { function renderItemView() { if (itemView === undefined) return null if (itemCategory === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '分类不存在'} -

- ) + return } if (itemCategory.kind === 'articles') { const article = findArticleItem(itemCategory, itemView.itemId) if (article === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '条目不存在'} -

- ) + return } return ( @@ -230,11 +219,7 @@ export default function App() { } if (detailModel === undefined) { - return ( -

- {dataPending ? '正在读取内容库…' : '条目不存在'} -

- ) + return } return ( @@ -249,111 +234,98 @@ export default function App() { } return ( -
+ <> {/* 主题的顶部背景图:固定 65vh,底部渐隐到页面底色 */} + ) } diff --git a/stash-web/src/assets/iconfont.woff2 b/stash-web/src/assets/iconfont.woff2 deleted file mode 100644 index 4ca9e6fd1d7e2027c002ddf7510f5924d203e668..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52136 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 F007T$p)LRb diff --git a/stash-web/src/components/common/BackToTop.tsx b/stash-web/src/components/common/BackToTop.tsx index 49d7f8f..aef9b79 100644 --- a/stash-web/src/components/common/BackToTop.tsx +++ b/stash-web/src/components/common/BackToTop.tsx @@ -1,33 +1,14 @@ -import { ArrowUpIcon } from './Icon' +import { ScrollTop } from '@stash/ui' /** - * 回到顶部:复刻 Alright 主题的 .scroll-toolbar .totop-button。 - * - * - 42px 圆形、浅灰底,固定在右侧(bottom: 50%) - * - 默认隐藏,只有内容区滚动后才淡入,回到顶部后再淡出 + * 回到顶部:直接用组件库的 ScrollTop。 + * 页面整体滚动(没有内部滚动容器),所以不传 targetId。 */ type BackToTopProps = { - /** 内容区是否已滚动(由 App 传入) */ + /** 页面是否已滚动(由 App 传入) */ visible: boolean } export function BackToTop({ visible }: BackToTopProps) { - const handleClick = () => { - // 桌面端滚动的是内容容器,窄屏滚动的是窗口,两处都滚一遍 - document.getElementById('content-scroll')?.scrollTo({ top: 0, behavior: 'smooth' }) - window.scrollTo({ top: 0, behavior: 'smooth' }) - } - - return ( - - ) + return } diff --git a/stash-web/src/components/common/CountBadge.tsx b/stash-web/src/components/common/CountBadge.tsx index 8248eaf..4c8212e 100644 --- a/stash-web/src/components/common/CountBadge.tsx +++ b/stash-web/src/components/common/CountBadge.tsx @@ -1,11 +1,13 @@ +import { Badge } from '@stash/ui' + /** - * 数量角标:贴在分区标题右上角的小红标。 - * 纯展示组件,只接收数字,宽度由内容撑开(两位数不会变形)。 + * 数量角标:贴在分区标题旁边的小红标。 + * 组件库的 Badge 已经处理了两位数和 99+ 的宽度,这里只做语义封装。 */ type CountBadgeProps = { count: number } export function CountBadge({ count }: CountBadgeProps) { - return {count} + return } diff --git a/stash-web/src/components/common/Icon.tsx b/stash-web/src/components/common/Icon.tsx index 38a32c9..6151b46 100644 --- a/stash-web/src/components/common/Icon.tsx +++ b/stash-web/src/components/common/Icon.tsx @@ -1,63 +1,57 @@ -/** - * 通用图标组件。 - * - * 全部使用 Alright 主题的 iconfont 线性图标(见 src/iconfont.css), - * 尺寸与颜色由使用处的 CSS 控制(font-size / color), - * 因此同一套图标可以复用在按钮、统计条、导航等不同位置。 - */ +import { Icon } from '@stash/ui' +/** + * 语义图标:stash 内部一直按用途引用(SearchIcon / RefreshIcon…), + * 这里统一映射到 @stash/ui 的 iconfont 字形,避免各页面记字形名。 + */ type IconProps = { - /** 附加类名,便于局部覆盖字号或颜色 */ className?: string } -const base = (name: string, className?: string) => - `iconfont icon-${name}${className !== undefined ? ` ${className}` : ''}` - export function SearchIcon({ className }: IconProps) { - return