feat: 按功能对代码分组
torbox-admin / deploy (push) Successful in 2m45s

This commit is contained in:
2026-10-08 21:27:08 -07:00
parent ee772dbe25
commit 97b8a6e199
35 changed files with 23 additions and 17 deletions
+2 -2
View File
@@ -81,7 +81,7 @@ div.flex.h-dvh.flex-col ← (app)/layout.tsx
### 长列表(虚拟滚动) ### 长列表(虚拟滚动)
资源库使用 TanStack Virtual(`src/app/(app)/components/library-list.tsx`),同样不使用定位: 资源库使用 TanStack Virtual(`src/app/(app)/components/library/library-list.tsx`),同样不使用定位:
- 滚动容器由页面持有(上面那个唯一的 `overflow-y-auto`),虚拟器只计算该渲染哪些行。 - 滚动容器由页面持有(上面那个唯一的 `overflow-y-auto`),虚拟器只计算该渲染哪些行。
- 分组数据拍平成「提示 / 标题 / 一行卡片 / 空提示」四种行;可见行处于正常文档流,上下各一个占位 `div` 用 `height` 撑出未渲染部分。 - 分组数据拍平成「提示 / 标题 / 一行卡片 / 空提示」四种行;可见行处于正常文档流,上下各一个占位 `div` 用 `height` 撑出未渲染部分。
@@ -185,7 +185,7 @@ div.flex.h-dvh.flex-col ← (app)/layout.tsx
| 页面组件 | `library-list`、`file-card`、`login-stage` 等 | 只做接线:传选择器、参数与回调 | | 页面组件 | `library-list`、`file-card`、`login-stage` 等 | 只做接线:传选择器、参数与回调 |
- 容器级交互 hook 统一签名 `useXxx(containerRef, options)`,在容器上做事件委托,彼此独立;删掉一行调用即可拆掉一个交互。 - 容器级交互 hook 统一签名 `useXxx(containerRef, options)`,在容器上做事件委托,彼此独立;删掉一行调用即可拆掉一个交互。
- 目标靠 data 属性识别(`src/app/(app)/components/library-dom.ts`):`data-file-card` 网格单元、`data-card-focus` 卡片主操作、虚拟行 `data-index`。 - 目标靠 data 属性识别(`src/app/(app)/components/library/library-dom.ts`):`data-file-card` 网格单元、`data-card-focus` 卡片主操作、虚拟行 `data-index`。
- 表单等业务组件只通过回调对外通知(如 `LoginForm` 的 `onTyping / onReject / onAccept`),不知道外部如何呈现。 - 表单等业务组件只通过回调对外通知(如 `LoginForm` 的 `onTyping / onReject / onAccept`),不知道外部如何呈现。
### 交互清单 ### 交互清单
@@ -38,7 +38,7 @@ import type { DownloadStatus } from "@/types/torbox";
import { buildStreamUrl, toPotPlayerUrl, type MediaKind } from "@/utils/media"; import { buildStreamUrl, toPotPlayerUrl, type MediaKind } from "@/utils/media";
import { ImageCover } from "./image-cover"; import { ImageCover } from "./image-cover";
import { CARD_FOCUS_PROPS } from "./library-dom"; import { CARD_FOCUS_PROPS } from "../library/library-dom";
const ExtLabel = ({ ext }: { ext: string }) => ( const ExtLabel = ({ ext }: { ext: string }) => (
<span className="font-mono uppercase">{ext || "file"}</span> <span className="font-mono uppercase">{ext || "file"}</span>
@@ -2,7 +2,7 @@
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { AddResourceButton } from "./add-resource-button"; import { AddResourceButton } from "../add-resource/add-resource-button";
import { HeaderBrand } from "./header-brand"; import { HeaderBrand } from "./header-brand";
import { LogoutButton } from "./logout-button"; import { LogoutButton } from "./logout-button";
import { MobileMenu } from "./mobile-menu"; import { MobileMenu } from "./mobile-menu";
@@ -23,7 +23,7 @@ import {
useLibraryFilterStore useLibraryFilterStore
} from "@/stores/library-filter-store"; } from "@/stores/library-filter-store";
import { LibraryFilters } from "./library-filters"; import { LibraryFilters } from "../library/library-filters";
import { LogoutButton } from "./logout-button"; import { LogoutButton } from "./logout-button";
/** 只在菜单打开时挂载,来源计数不在后台重复计算 */ /** 只在菜单打开时挂载,来源计数不在后台重复计算 */
@@ -14,7 +14,7 @@ import {
} from "@/stores/library-filter-store"; } from "@/stores/library-filter-store";
import { formatCount } from "@/utils/format"; import { formatCount } from "@/utils/format";
import { SourceDot } from "./source-dot"; import { SourceDot } from "../item/source-dot";
// 数字滚动过程中的格式化:计数取整 // 数字滚动过程中的格式化:计数取整
const formatInteger = (value: number) => formatCount(Math.round(value)); const formatInteger = (value: number) => formatCount(Math.round(value));
@@ -37,11 +37,11 @@ import {
import type { SourceType } from "@/types/torbox"; import type { SourceType } from "@/types/torbox";
import type { MediaKind } from "@/utils/media"; import type { MediaKind } from "@/utils/media";
import { DeleteItemDialog } from "./delete-item-dialog"; import { DeleteItemDialog } from "../item/delete-item-dialog";
import { FileCard } from "./file-card"; import { FileCard } from "../file/file-card";
import { FileGrid, FileGridItem } from "./file-grid"; import { FileGrid, FileGridItem } from "../file/file-grid";
import { ItemHeader } from "./item-header"; import { ItemHeader } from "../item/item-header";
import { LibraryImagePreview } from "./library-image-preview"; import { LibraryImagePreview } from "../preview/library-image-preview";
import { import {
CARD_FOCUS_SELECTOR, CARD_FOCUS_SELECTOR,
CARD_PROPS, CARD_PROPS,
@@ -59,7 +59,7 @@ import {
type LibraryRow, type LibraryRow,
type RowPiece type RowPiece
} from "./library-rows"; } from "./library-rows";
import { LibraryVideoDialog } from "./library-video-dialog"; import { LibraryVideoDialog } from "../preview/library-video-dialog";
import { SourceNotice } from "./source-notice"; import { SourceNotice } from "./source-notice";
/** 列表顶部留白;首个分组标题自带 8px 上留白,合计 20px */ /** 列表顶部留白;首个分组标题自带 8px 上留白,合计 20px */
@@ -1,7 +1,7 @@
// 资源列表加载骨架屏 // 资源列表加载骨架屏
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { FileGrid } from "./file-grid"; import { FileGrid } from "../file/file-grid";
export const LibrarySkeleton = () => ( export const LibrarySkeleton = () => (
<div <div
@@ -10,7 +10,10 @@ import type { LibraryItemView } from "@/hooks/use-library";
import { useOrigin } from "@/hooks/use-origin"; import { useOrigin } from "@/hooks/use-origin";
import { buildStreamUrl } from "@/utils/media"; import { buildStreamUrl } from "@/utils/media";
import { findLibraryItem, type LibraryFileTarget } from "./library-rows"; import {
findLibraryItem,
type LibraryFileTarget
} from "../library/library-rows";
/** 灯箱连同样式较大,第一次预览图片时才加载 */ /** 灯箱连同样式较大,第一次预览图片时才加载 */
const ImageLightbox = dynamic( const ImageLightbox = dynamic(
@@ -32,7 +32,10 @@ import { useVideoDialogStore } from "@/stores/video-dialog-store";
import { formatBytes } from "@/utils/format"; import { formatBytes } from "@/utils/format";
import { buildHlsUrl, buildStreamUrl, toPotPlayerUrl } from "@/utils/media"; import { buildHlsUrl, buildStreamUrl, toPotPlayerUrl } from "@/utils/media";
import { findLibraryItem, type LibraryFileTarget } from "./library-rows"; import {
findLibraryItem,
type LibraryFileTarget
} from "../library/library-rows";
import { VideoPlaylist } from "./video-playlist"; import { VideoPlaylist } from "./video-playlist";
/** 16:9 铺满主列,尺寸由主列宽度决定。手机宽度下贴边、去掉圆角 */ /** 16:9 铺满主列,尺寸由主列宽度决定。手机宽度下贴边、去掉圆角 */
+1 -1
View File
@@ -3,7 +3,7 @@ import { isMockMode } from "@/server/env";
import { isSearchAvailable } from "@/server/search"; import { isSearchAvailable } from "@/server/search";
import { requireSessionToken } from "@/server/session"; import { requireSessionToken } from "@/server/session";
import { AppHeader } from "./components/app-header"; import { AppHeader } from "./components/header/app-header";
/** 登录后页面骨架:纵向 flex,header 不参与滚动,滚动发生在各页面内部的内容区 */ /** 登录后页面骨架:纵向 flex,header 不参与滚动,滚动发生在各页面内部的内容区 */
const AppLayout = async ({ children }: { children: React.ReactNode }) => { const AppLayout = async ({ children }: { children: React.ReactNode }) => {
+1 -1
View File
@@ -3,7 +3,7 @@ import type { Metadata } from "next";
import { requireSessionToken } from "@/server/session"; import { requireSessionToken } from "@/server/session";
import { LibraryView } from "./components/library-view"; import { LibraryView } from "./components/library/library-view";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "资源库" title: "资源库"