diff --git a/docs/ui-review/collections-ux.md b/docs/ui-review/collections-ux.md new file mode 100644 index 0000000..48a167b --- /dev/null +++ b/docs/ui-review/collections-ux.md @@ -0,0 +1,20 @@ +# 书架与书库样式优化 + +日期:2026-09-21。 + +- 标题、辅助文字、设置入口统一;书架的“书库 / 书源”和书库的“文件管理 / 上传书籍”使用同样的图标文字入口。子目录的面包屑单列显示。 +- 搜索框统一圆角与高度;空书架隐藏排序栏,刷新移至标题栏。 +- 空状态统一为图标、标题、说明和主操作卡片。空书架可去搜书或打开书库;空书库可上传第一本书;存在未识别文件时引导查看文件管理;只读目录不显示上传操作。空库不再显示单独的“0 本”状态栏。 +- 保留搜索、排序、目录导航和有书时的封面网格。 + +验证:Web 构建及 38 项书架、书库、权限相关测试通过。生产 bundle 在 Chromium 中检查 320/390/1280px 空状态、有书、子目录、无搜索结果、只读和深色主题;验证书源、书库、文件管理、上传文件选择器、排序、搜索清除操作,以及页面和导航无横向溢出。 + +浏览器脚本使用可控 API fixture;没有上传实际文件,也未进行 Android/iOS 真机测试。 + +```powershell +npm --prefix web run build +$env:CHROME_PATH='C:\Program Files\Google\Chrome\Application\chrome.exe' +node web/tools/ui-review/collections.mjs +``` + +截图输出到本目录 `collections-*.png`,重点预览:`collections-shelf-empty-390.png`、`collections-library-empty-390.png`、`collections-shelf-books.png`、`collections-library-books.png`。 diff --git a/web/src/styles/reader.css b/web/src/styles/reader.css index 835d51d..4225702 100644 --- a/web/src/styles/reader.css +++ b/web/src/styles/reader.css @@ -3853,6 +3853,38 @@ section[aria-label="切换书源"] button { margin:.35rem .4rem .35rem 0; paddin .source-modal-content { overflow-y: auto; overscroll-behavior: contain; padding: 0 1rem 1rem; } .source-modal-actions { display: flex; flex-wrap: wrap; gap: .5rem; padding: .75rem 1rem; border-top: 1px solid var(--reader-line); flex: none; } .source-modal-actions button { min-height: 44px; } + +/* Collection pages share a quiet heading, labelled destinations and useful empty states. */ +.shelf-head { align-items: center; padding-top: calc(1.25rem + var(--safe-top)); padding-bottom: .85rem; } +.shelf-head-actions { margin-top: 0; } +.shelf-head .icon-button, .collection-heading .icon-button { width: 44px; height: 44px; border-radius: .8rem; } +.shelf-subtitle { margin-top: .45rem; line-height: 1.5; } +.collection-header { padding: calc(1.25rem + var(--safe-top)) calc(.9rem + var(--safe-right)) .75rem calc(.9rem + var(--safe-left)); flex: none; } +.collection-heading { display: flex; align-items: center; gap: .5rem; } +.collection-heading-text { flex: 1; min-width: 0; } +.collection-heading h1 { margin: 0; font-size: 1.5rem; font-weight: 650; letter-spacing: -.02em; line-height: 1.15; } +.collection-heading p { margin: .45rem 0 0; font-size: .78rem; line-height: 1.5; } +.collection-links { display: flex; gap: .65rem; margin-top: .85rem; } +.shelf-links { max-width: calc(var(--shelf-measure) - 1.8rem); margin: 0 auto .9rem; padding-inline: calc(.9rem + var(--safe-left)) calc(.9rem + var(--safe-right)); } +.collection-link.button { display: flex; align-items: center; gap: .6rem; flex: 1; min-width: 0; min-height: 48px; padding: .7rem .85rem; font-size: .875rem; border-radius: .8rem; background: color-mix(in srgb, var(--reader-accent) 4%, var(--reader-paper)); border-color: color-mix(in srgb, var(--reader-line) 65%, transparent); text-align: start; } +.collection-link > span { flex: 1; } +.collection-link[hidden] { display: none; } +.collection-link > .icon:first-child { color: var(--reader-accent); } +.collection-link > .icon:last-child { color: var(--reader-muted); font-size: .8rem; } +.shelf-screen .shelf-search input, .library-search input { border-radius: .85rem; min-height: 48px; } +.library-browse-screen .library-search { margin-top: .35rem; } +.library-path { margin-top: .75rem; padding: .25rem 0 0; } +.library-path .manager-crumb { min-height: 44px; } +.collection-empty.empty-state { width: calc(100% - 2rem); max-width: 30rem; margin: clamp(1.5rem, 7vh, 4rem) auto 2rem; padding: 1.75rem 1.25rem; gap: .7rem; border: 1px solid var(--reader-line); border-radius: 1.25rem; background: color-mix(in srgb, var(--reader-surface) 45%, var(--reader-paper)); } +.collection-empty .empty-glyph { box-sizing: content-box; width: 30px; height: 30px; padding: 1rem; border-radius: 1.1rem; background: color-mix(in srgb, var(--reader-accent) 9%, transparent); color: var(--reader-accent); opacity: 1; margin-bottom: .5rem; } +.collection-empty p { font-size: 1.1rem; line-height: 1.5; } +.collection-empty p.muted { max-width: 23rem; font-size: .875rem; line-height: 1.8; text-wrap: pretty; } +.collection-empty .empty-actions { margin-top: .6rem; gap: .65rem; } +.collection-empty .button { min-height: 44px; font-size: .875rem; } +.library-browse-body > .manager-status { background: transparent; border: 0; margin: .75rem auto; } +@media (min-width: 72rem) { + .collection-header, .library-browse-body { width: 100%; max-width: 68rem; margin-inline: auto; } +} @media (max-width: 600px) { .source-entry-actions { flex-direction: column; gap: 0; } .source-entry-top { align-items: center; } diff --git a/web/src/ui/library-screen.tsx b/web/src/ui/library-screen.tsx index 212e3a6..cfc258e 100644 --- a/web/src/ui/library-screen.tsx +++ b/web/src/ui/library-screen.tsx @@ -479,14 +479,18 @@ export class LibraryBrowseScreen { const listing = state.listing; return ( <> -
浏览本地书籍,发现下一本好书
{hasQuery ? '没有匹配的书' : '这个文件夹里没有可阅读的书'}
+{hasQuery ? '没有匹配的书' : hasFiles ? '这个文件夹里没有可阅读的书' : '书库还没有书'}
{hasQuery ? `「${state.query}」在这个文件夹里没有匹配` - : `${state.listing?.files ?? 0} 个文件里没有被扫描成书的,文件页能看到每一行的原因`} + : hasFiles ? '这里有 ' + state.listing!.files + ' 个文件,可在文件管理中查看识别结果。' + : writable ? '上传 EPUB、TXT 等书籍,开始建立你的书库。' : '管理员添加书籍后,就能在这里浏览并加入书架。'}
没有匹配的书
换个关键词,或者检查一下作者名的写法
@@ -936,19 +919,20 @@ export class ShelfScreen {书库还是空的
+书架还没有书
- 把书籍放进挂载的目录,扫一次,它们就会出现在这里 + 从书库挑选喜欢的书,或通过书源搜索,加入书架后就能开始阅读。