# book-index-ui 中文文档

规范地址：https://npmcn.com/package/book-index-ui/

版本：0\.64\.0
npm 元数据更新时间：2026-11-03T04:49:13.871Z
中文译文更新时间：2026-11-03T06:02:03.896Z
软件包许可：Apache-2\.0

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/book-index-ui
原文对照：https://npmcn.com/package/book-index-ui/?lang=original
项目主页：https://github.com/open-guji/book-index-manager/tree/main/ui
源码仓库：https://github.com/open-guji/book-index-manager

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：2.3 MB，17 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：react \>=18\.0\.0，opencc-js \>=1\.0\.0，react-dom \>=18\.0\.0，remark-gfm \>=4\.0\.0，@xyflow/react \>=12\.0\.0，@dagrejs/dagre \>=1\.0\.0，react-markdown \>=9\.0\.0
- 首次发布：2026-03-19T22:52:49.172Z
- 最新版本发布：2026-11-03T04:49:13.578Z
- 已发布版本数：167（近 12 个月 167 个）

## 中文简介

用于浏览和编辑古书索引元数据的React组件

## 安装

```sh
npm install book-index-ui
```

也可以使用：`pnpm add book-index-ui`、`yarn add book-index-ui`、`bun add book-index-ui`

## README

# book-index-ui

`book-index-manager` 的 React 组件库 + 存储客户端，发布为 npm 包 `book-index-ui`。 配合 [book-index-manager](https://github.com/open-guji/book-index-manager) 的 Python CLI 一起使用。

```bash
npm install book-index-ui
```

## 包结构

两个独立入口：

```ts
import { ... } from 'book-index-ui'          // React 组件 + 类型 + 数据层
import { ... } from 'book-index-ui/storage'  // 仅数据层（无 React，体积小，适合 Node/Worker）
import 'book-index-ui/styles'                // CSS（用到组件时引入）
```

## 主要 export

### React 组件

| 组件 | 用途 |
| --- | --- |
| `IndexBrowser` | 完整索引浏览器：搜索框 + 分类 tab + 推荐 + 最近浏览 |
| `IndexView` | 单条目详情视图（基于 ID 拉数据 + 渲染） |
| `IndexDetail` | **旧版**详情纯渲染（2026-09 重构前，仍导出以兼容） |
| `BookDetailLayout` | 详情页外壳：Tab（基本信息/整理本/全文/反馈）+ 路由到下面四个 Page；kaiyuanguji-web 用它 |
| `WorkPage` / `BookPage` / `CollectionPage` / `EntityPage` | **现行详情页**（2026-09 版式重构）：作品 / 版本 / 丛编 / 人物 |
| `primitives`（`PageFrame`、`SectionHead`、`DataTable`、`Chip` …） | 四个 Page 共用的版式原语与 `DETAIL_CSS` |
| `deriveEra` / `deriveDating` / `buildVersionTable` … | `core/detail-model.ts` 的数据派生层（刊刻年代、角色归一、版本表） |
| `WorkCatalog` | 作品目录 |
| `IndexEditor` | 详情编辑器（写入需 storage 实现 saveItem） |
| `HomePage` | 首页：推荐丛编 + 经典作品（kaiyuanguji-web 用） |
| `TextReader` | **统一阅读器**（overview#307）：整理本与全文合一，版本下拉含全部版本，取数走 `core/text-api`（只认新结构），见下「统一阅读器」 |
| `ReaderShell` / `ReaderToc` / `ImagePanel` / `ReaderMdText` | 阅读器（整理本与全文共用），见下「阅读器」 |
| `CollectionCatalog` | 丛编目录（按册/卷分组） |
| `EmendatedBySection` | "校勘自" 引用列表 |
| `VersionLineageView` / `VersionLineageGraph` | 版本传承图（dagre + xyflow） |
| `FeedbackButton` / `FeedbackList` / `FeedbackForm` | 反馈组件 |
| `LocaleProvider` / `LocaleToggle` | 繁简切换 |
| `useI18n` / `getT` | **统一 i18n 入口**：界面文字 `t(key)`、数据文字 `convert`，见下「界面文字与繁简」 |
| `useT` / `useConvert` | 旧 hook（整本字典 / 繁简转换），新代码用 `useI18n` |
| `findTraditionalChars` | 简体模式残留繁体字检测（vitest 与网站 e2e 共用） |

### 统一阅读器（`TextReader`，overview#307）

```tsx
<TextReader
  id={workOrBookId} transport={storage}
  versionKey={key}  chapter={chapterKey}          // 受控（可不传，自己管）
  onLocationChange={(loc, cause) => router.push(urlFor(loc))}  // loc: { key, chapter, isDefault }；cause: 'auto' | 'chapter' | 'version'
  onNavigate={id => router.push(entryUrl(id))}
/>
```

-   数据模型（规格 overview `项目进展/古籍索引网站/设计/阅读文本.md`）：`items/<id>/manifest.json` 列全部版本（`versions[0]` 是 `default`），`<key>/index.json` 是章目录（`chapters[{ n, file:'001', title, has_json }]`），章正文 `<key>/001.txt`，整理本的章另有 `001.json`。`IndexStorage` 新增可选 `getTextManifest / getTextIndex / getChapter`，`BundleStorage` 已实现。
-   **只认新结构**（用户 09-30 定）：条目没有 `manifest.json`（含旧的 `collated_edition/`、`full_text/…` 目录）就显示「暂无文本」，不从旧目录合成。旧的 `CollatedEdition`／`BookFullText` 暂留给还没迁移的页面，迁移在正式站验完后清掉。
-   界面：工具条一个「版本」下拉按 manifest 顺序列全部版本（含整理本），只有一份时只在书名后标来源名；正文末尾显示所选版本的出处与授权；章有 json 用结构化渲染（条目、作品链接、跨章搜索），否则按 md；切版本尽量停在同一章号，对不上回第一章。
-   组件不改 URL：宿主在 `onLocationChange` 里改地址（新 URL：`/read/<id>[/<key>][/<章>]`，主版本不写 key）。`versionKey／chapter` 传了就受控；传了无效值时组件回落并以 `auto` 通知。
-   工具条另有上一章／下一章（窄屏隐去，翻卷交给正文底部的翻页卡片）、书名链接回条目页；窄屏（右栏藏起来）时正文末尾有「报告错字」入口。

### 阅读器（`components/Reader/`，2026-09 N5a）

整理本与全文共用一个外壳 `ReaderShell`：

-   布局：**书影在正文左边**，目录在最左。宽屏（≥1100px）目录是可收起的侧栏（默认展开）；更窄时是抽屉（默认收起，选卷后自动合上，Esc 关闭，打开时 Tab 圈在抽屉内）；窄屏（≤719px）书影区不显示，只留正文。
-   工具条（吸顶）只用文字与图标：目录、繁｜简、A− A+、「阅读设置」键（overview#463）。其余偏好收进**右侧「阅读设置」侧栏**（≥860px 右侧可折叠栏；窄屏是底栏「设置」打开的底部抽屉），分三组：文字（字体、字号、繁简）、标注（专名线，对读时加标点）、版面（自然段、标出作品链接、书影、对读留白，竖排预留 `allowVertical`）。宿主可用 `settingsAnnotate` / `settingsLayout` 往侧栏塞自己的控件。字体选项只有宋体（默认，沿用 `--bim-font-reading`）／楷体／系统默认（无衬线），全是系统已装字体，不加载网络字体；楷体栈 `Kaiti SC → Kaiti TC → STKaiti → KaiTi → AR PL UKai CN → Noto Serif CJK SC → serif`。
-   正文：`--bim-font-reading`（宋体，默认接 `--bim-font-serif`，宿主覆盖后者即可）18px、行高 2.05（手机端同样），`line-break: strict` 避头尾；界面黑体。
-   无障碍：第一个可聚焦元素是「跳到正文」；目录用游走 tabindex，几百卷也只有当前卷进 Tab 序列（↑↓ Home End 移动）。
-   偏好（字号、字体、自然段、专名线…）存 `localStorage['bim-reader-prefs']`，首帧用默认值，SSR 安全。
-   宿主若有吸顶导航，设 `--bim-reader-top`（如 `60px`）。

书影接口：`TextReader` 的 `resolveImages(chapterKey)` 返回当卷的 `ReaderPageImage[]`（可异步）——每页一张图，URL 由宿主给，可带 `width/height` 与逐字框 `boxes`（暂定格式 `bim-charbox-v0`：原图像素坐标 `x/y/w/h`，可选 `char`/`textOffset`/`confidence`）。其他格式的框设 `boxFormat` 并用 `renderImageOverlay` 自己画，这是与 CV 交付格式对齐前的扩展点。没有影像时书影区默认收起（`imagePanel="auto"`），点工具条「书影」可看占位。

版本下拉框（overview#235，同一 owner 有多份全文时用）：`ReaderShell` 的四个 props——

| prop | 说明 |
| --- | --- |
| `versions` | `ReaderVersion[]`（`key / label / sourceName / license / sourceUrl / primary`）。两份以上时工具条出「版本」下拉框，只有一份不出；有值时正文末尾显示所选版本的出处与授权 |
| `currentVersionKey` | 当前版本 key；不给（`undefined`）时组件自管，给了但无效则选 `primary`，再没有就第一份 |
| `onVersionChange` | 下拉框切换回调 `(key) => void`；组件不改 URL，由宿主换 `toc` 与正文（换上新目录后自动选中第一卷） |
| `versionSource` | 是否显示正文末尾的「出处 · 授权」，默认 `true`；宿主自己画出处时设 `false` |

配套两个导出（`book-index-ui` 根入口与 `components/Reader`）：`readerVersionsFromFullText(entries)` 把 book-text 的 `WorkFullTextEntry[]` 换成 `ReaderVersion[]`（顺序不变，清单已按「哪份最好」排好）；`pickReaderVersion(versions, key?)` 给出当前选中的一份（key 有效用它，否则 primary，再否则第一份），宿主用它决定该载入哪份 toc / 正文。

**`resolveImages` 要用 `useCallback`（或模块级函数）包一层**：组件按 `[resolveImages, 当前卷]` 重新取书影，每次渲染都传一个新函数会反复请求。

### 正文实体标注（`EntityText`，overview#389）

给一段正文和实体区间：人名、地名、官职等画直线专名线，书名画波浪线（书名号样式）；已收录的实体是 `/item/<id>` 链接，悬停或聚焦弹出摘要卡（取法同详情页 `transport.getItem`），Esc 收起。

```tsx
import { EntityText, adaptEntityJson } from 'book-index-ui';

const spans = adaptEntityJson(entityJson);   // entity.json → EntitySpan[]；格式定稿后只改这一层
<EntityText text={paragraph} entities={spans} offsets="plain" plainBase={0}
            transport={storage} onNavigate={(id, e) => { e.preventDefault(); router.push(`/item/${id}`); }} />
```

entity.json 的 `span` 是**纯字**偏移（不计标点空白），配 `offsets="plain"`；正文只是整卷一段时用 `plainBase` 给出本段起点。`book-index://Work/<id>` 等 URI 由 `bookIndexUriToId` 转站内 id。一页铺很多段时设 `injectStyles={false}`，在页面上放一次 `<style>{ENTITY_TEXT_CSS}</style>`。

### 数据层（`book-index-ui/storage`）

| 类 | 用途 |
| --- | --- |
| `BookIndexManager` | Facade：统一 `getItem/saveItem/deleteItem/generateId/...` |
| `BookIndexStorage` | 本地文件系统实现（Node / Electron） |
| `GithubStorage` | GitHub 只读 + jsdelivr CDN fallback（浏览器/CI 用） |
| `BundleStorage` | 同域预打包数据读取（kaiyuanguji-web 生产模式） |
| `LocalStorage` | `BookIndexStorage` 的薄包装（兼容历史 API） |
| `IndexStorage` | 接口（自实现自定义后端时实现它） |

数据层还导出 `encodeId / decodeId / smartDecode / extractType / shardOf / scoreEntry / rankByRelevance / cleanName` 等纯函数工具。

### 界面文字与繁简（overview#337）

两类文字，两条路：

| 类别 | 例 | 怎么写 |
| --- | --- | --- |
| 界面固定文字 | 标题、按钮、aria-label、单位、「展開其餘 {n} 冊」 | `const { t } = useI18n(); t('detail.moreN', { n })` |
| 数据文字 | 书名、作者、提要、分类名、版本名、sections.json 里的分区名 | `const { convert } = useI18n(); convert(data.title)` |

-   字典按区域分文件：`src/i18n/messages/<区域>.ts`，`defineMessages(繁体栏, 简体栏, 英文栏?)`，繁简两栏键必须一一对应（tsc 检查）；早期的整块字典 `locales/zh-Hant.ts`／`zh-Hans.ts` 仍可用。
-   键是带类型的点路径（`MessageKey`），写错 tsc 报错；当前语言缺键回落到繁体。
-   没包 `LocaleProvider` 时按繁体。网站务必在最外层包 `LocaleProvider`（或网站的 `BimLocaleProvider`），否则组件一律显示繁体。
-   组件外（Next.js `generateMetadata` 等服务端）：`getT(locale)('…')`，数据文字用同一个转换函数（`LocaleProvider` 的 `converter` 或内置 opencc）。
-   加英文：`Locale` 加 `'en'`，各字典填英文栏（可部分，缺的回落繁体）；数据文字的英文另议。
-   检测：`tests/unit/zh-hans-leak.test.tsx` 在简体模式下渲染各页，用 `findTraditionalChars`（常用繁体字表 + 专名白名单）断言正文无繁体残留；`tests/unit/i18n-dictionaries.test.ts` 检查字典繁简同形、简体栏无繁体。

### 类型

`IndexEntry` / `IndexDetailData` / `WorkDetailData` / `BookDetailData` / `CollectionDetailData` / `EntityDetailData` / `IndexType` / `IndexStatus` / `LineageGraph` / `CollatedEditionIndex` / `ResourceCatalog` 等。

## 最小用法

### 1\. 用 GithubStorage 直接渲染索引（浏览器）

```tsx
import { IndexBrowser, GithubStorage, LocaleProvider } from 'book-index-ui';
import 'book-index-ui/styles';

const transport = new GithubStorage({
    org: 'open-guji',
    repos: { draft: 'book-index-draft', official: 'book-index' },
});

export default function App() {
    return (
        <LocaleProvider>
            <IndexBrowser
                transport={transport}
                onEntryClick={entry => console.log('clicked', entry.id)}
            />
        </LocaleProvider>
    );
}
```

### 2\. 仅用数据层（Node 环境，例如 build script）

```ts
import { BookIndexManager } from 'book-index-ui/storage';

const mgr = new BookIndexManager('/workspace');
const item = await mgr.getItem('1evgowbkc2qyo');
console.log(item?.title);
```

### 3\. 自定义 storage 后端（VS Code 扩展、Electron 等）

```ts
import type { IndexStorage, IndexEntry, PageResult, LoadOptions } from 'book-index-ui';

class MyStorage implements IndexStorage {
    async loadEntries(type, options): Promise<PageResult<IndexEntry>> { ... }
    async getItem(id): Promise<Record<string, unknown> | null> { ... }
    // ... 实现 IndexStorage 全部必选方法
}
```

参考 `kaiyuanguji-web/nextjs/src/lib/local-api-storage.ts`（浏览器 → Next.js API 路由）和 `guji-platform/src/storage/VscodeStorage.ts`（VS Code 文件系统）。

### 4\. 宿主接入要点（手机 / 无障碍，2026-09-28）

-   **搜索结果是真链接**：`IndexBrowser` 的结果卡片是 `<a href>`，href 由 `BidUrlProvider` 的 `buildUrl` 生成（默认 `/book-index?id=…`），站点路由不同时务必包一层 Provider。普通左键点击仍调 `onEntryClick` 走客户端路由；Ctrl / Cmd / Shift / 中键交给浏览器（新标签、复制链接）。不传 `onEntryClick` 则按 href 整页跳转。
-   **反馈浮钮** `FeedbackButton`：
    -   层级取 `--bim-feedback-fab-z`（默认 40），低于抽屉与弹层；也可传 `zIndex` prop。
    -   挂载且未隐藏时在 `<html>` 上写 `--bim-feedback-fab-offset`（浮钮占掉的底部高度）。正文容器写 `padding-bottom: var(--bim-feedback-fab-offset, 0px)`，右下角内容就不会被盖住。
    -   `hidden` prop：移动端菜单 / 抽屉打开时传 `true`，浮钮与留白一起撤掉。

## 数据约定

`book-index-ui` 读写的数据格式与 Python 端 `book-index-manager` CLI 完全一致。详见 [根 README](https://npmcn.com/package/README.md) 的"存储结构"和"Manager API 对照表"。

**关键约束**：文件名清洗（`cleanName`）的 CJK 范围 TS/Python 必须一致——见 [tests/unit/cleanName.test.ts](https://npmcn.com/package/book-index-ui/tests/unit/cleanName.test.ts) 的跨语言 fixture。

## 开发

```bash
npm install
npm run dev          # vite dev server（开发组件）
npm run test         # vitest 单元测试
npm run test:e2e     # Playwright E2E
npm run build:lib    # 输出 dist/ 给 npm 发布
```

## 许可

Apache 2.0

## schema-v2 派生字段（overview#458）

详情页（`BookDetailLayout` 及其下各页）直接读 build 产物的 `_` 字段，没有中间的归一／兼容层：

| 记录 | 页面读的字段 |
| --- | --- |
| Work | `_books`（版本卡）、`_edition_count`、`_classifications`（优先 zongmu）、`_related`（含 `direction`）、`_collections`；著录文字仍读源档的 `indexed_by` |
| Book | `_collections`（所属丛编与册次）；上级作品的版本取自 Work 的 `_books` |
| Collection | `_members`（前 20 项）＋`_member_count`；上级丛编读源档的 `contained_in` |
| Entity | `_works` |

卡片里 `{id, h:1}` 的枢纽引用没有 title，名称取自 `_hubs.json`：`IndexStorage.getHubs?()` 提供（`BundleStorage` 默认读 `${basePath}/_hubs.json`，可用 `hubsPath` 改相对路径）；不提供时枢纽引用无名，页面回退显示 ID。`BookDetailLayout` 会给传入的 `transport` 套一层 `withHubTitles`（只补枢纽卡的 `title`，不改字段形状）。夹具取自 book-index `schema-v2` 的 `build/contract-sample/`（`tests/unit/fixtures/contract-sample/`）。

`tests/unit/DerivedGolden.test.tsx` 用 `tests/unit/fixtures/derived-golden/`（真实 build 产物里挑的 47 个样本）逐字节比对整页渲染，改页面后输出有意变化时才重新生成（见该测试文件头部）。

## 常见问题

### book-index-ui 是什么？

用于浏览和编辑古书索引元数据的React组件

### 如何安装 book-index-ui？

在项目目录运行 npm install book-index-ui，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（\>=18\.0\.0）、opencc-js（\>=1\.0\.0）、react-dom（\>=18\.0\.0）、remark-gfm（\>=4\.0\.0）、react-markdown（\>=9\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### book-index-ui 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### book-index-ui 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### book-index-ui 可以商用吗？

book-index-ui 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### book-index-ui 还在维护吗？

最新版本 0\.64\.0 发布于 2026/11/03（今天）；至今共发布 167 个版本，其中过去 12 个月发布了 167 个；首次发布于 2026/03/20。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

