@@ -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`),虚拟器只计算该渲染哪些行。
|
||||
- 分组数据拍平成「提示 / 标题 / 一行卡片 / 空提示」四种行;可见行处于正常文档流,上下各一个占位 `div` 用 `height` 撑出未渲染部分。
|
||||
@@ -185,7 +185,7 @@ div.flex.h-dvh.flex-col ← (app)/layout.tsx
|
||||
| 页面组件 | `library-list`、`file-card`、`login-stage` 等 | 只做接线:传选择器、参数与回调 |
|
||||
|
||||
- 容器级交互 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`),不知道外部如何呈现。
|
||||
|
||||
### 交互清单
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ import type { DownloadStatus } from "@/types/torbox";
|
||||
import { buildStreamUrl, toPotPlayerUrl, type MediaKind } from "@/utils/media";
|
||||
|
||||
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 }) => (
|
||||
<span className="font-mono uppercase">{ext || "file"}</span>
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
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 { LogoutButton } from "./logout-button";
|
||||
import { MobileMenu } from "./mobile-menu";
|
||||
+1
-1
@@ -23,7 +23,7 @@ import {
|
||||
useLibraryFilterStore
|
||||
} from "@/stores/library-filter-store";
|
||||
|
||||
import { LibraryFilters } from "./library-filters";
|
||||
import { LibraryFilters } from "../library/library-filters";
|
||||
import { LogoutButton } from "./logout-button";
|
||||
|
||||
/** 只在菜单打开时挂载,来源计数不在后台重复计算 */
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/stores/library-filter-store";
|
||||
import { formatCount } from "@/utils/format";
|
||||
|
||||
import { SourceDot } from "./source-dot";
|
||||
import { SourceDot } from "../item/source-dot";
|
||||
|
||||
// 数字滚动过程中的格式化:计数取整
|
||||
const formatInteger = (value: number) => formatCount(Math.round(value));
|
||||
+6
-6
@@ -37,11 +37,11 @@ import {
|
||||
import type { SourceType } from "@/types/torbox";
|
||||
import type { MediaKind } from "@/utils/media";
|
||||
|
||||
import { DeleteItemDialog } from "./delete-item-dialog";
|
||||
import { FileCard } from "./file-card";
|
||||
import { FileGrid, FileGridItem } from "./file-grid";
|
||||
import { ItemHeader } from "./item-header";
|
||||
import { LibraryImagePreview } from "./library-image-preview";
|
||||
import { DeleteItemDialog } from "../item/delete-item-dialog";
|
||||
import { FileCard } from "../file/file-card";
|
||||
import { FileGrid, FileGridItem } from "../file/file-grid";
|
||||
import { ItemHeader } from "../item/item-header";
|
||||
import { LibraryImagePreview } from "../preview/library-image-preview";
|
||||
import {
|
||||
CARD_FOCUS_SELECTOR,
|
||||
CARD_PROPS,
|
||||
@@ -59,7 +59,7 @@ import {
|
||||
type LibraryRow,
|
||||
type RowPiece
|
||||
} from "./library-rows";
|
||||
import { LibraryVideoDialog } from "./library-video-dialog";
|
||||
import { LibraryVideoDialog } from "../preview/library-video-dialog";
|
||||
import { SourceNotice } from "./source-notice";
|
||||
|
||||
/** 列表顶部留白;首个分组标题自带 8px 上留白,合计 20px */
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// 资源列表加载骨架屏
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
import { FileGrid } from "./file-grid";
|
||||
import { FileGrid } from "../file/file-grid";
|
||||
|
||||
export const LibrarySkeleton = () => (
|
||||
<div
|
||||
+4
-1
@@ -10,7 +10,10 @@ import type { LibraryItemView } from "@/hooks/use-library";
|
||||
import { useOrigin } from "@/hooks/use-origin";
|
||||
import { buildStreamUrl } from "@/utils/media";
|
||||
|
||||
import { findLibraryItem, type LibraryFileTarget } from "./library-rows";
|
||||
import {
|
||||
findLibraryItem,
|
||||
type LibraryFileTarget
|
||||
} from "../library/library-rows";
|
||||
|
||||
/** 灯箱连同样式较大,第一次预览图片时才加载 */
|
||||
const ImageLightbox = dynamic(
|
||||
+4
-1
@@ -32,7 +32,10 @@ import { useVideoDialogStore } from "@/stores/video-dialog-store";
|
||||
import { formatBytes } from "@/utils/format";
|
||||
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";
|
||||
|
||||
/** 16:9 铺满主列,尺寸由主列宽度决定。手机宽度下贴边、去掉圆角 */
|
||||
@@ -3,7 +3,7 @@ import { isMockMode } from "@/server/env";
|
||||
import { isSearchAvailable } from "@/server/search";
|
||||
import { requireSessionToken } from "@/server/session";
|
||||
|
||||
import { AppHeader } from "./components/app-header";
|
||||
import { AppHeader } from "./components/header/app-header";
|
||||
|
||||
/** 登录后页面骨架:纵向 flex,header 不参与滚动,滚动发生在各页面内部的内容区 */
|
||||
const AppLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Metadata } from "next";
|
||||
|
||||
import { requireSessionToken } from "@/server/session";
|
||||
|
||||
import { LibraryView } from "./components/library-view";
|
||||
import { LibraryView } from "./components/library/library-view";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "资源库"
|
||||
|
||||
Reference in New Issue
Block a user