Skip to content

@ahoo-wang/fetcher-viewer

@ahoo-wang/fetcher-viewer 包提供了一个 React + Ant Design 组件库,用于构建数据查看界面。它包含带类型化注册表的过滤面板组件、带丰富单元格渲染器的表格组件、支持保存/加载的视图管理、带操作项的顶部栏,以及一个完整的 FetcherViewer 组件,通过 Wow CQRS 查询将所有功能整合在一起。

安装

bash
pnpm add @ahoo-wang/fetcher-viewer

组件架构

mermaid
graph TB
    subgraph sg_1 ["FetcherViewer"]
        direction TB
        FV["FetcherViewer<br>top-level orchestrator"]
        V["Viewer<br>view + table + filters"]
        VT["ViewTable<br>Ant Design Table wrapper"]
        FP["FilterPanel<br>filter composition"]
        TB["TopBar<br>action bar"]
    end

    subgraph sg_2 ["Filter System"]
        direction LR
        TF["TypedFilter<br>type-based dispatch"]
        FR["filterRegistry<br>type -> component map"]
        FF["FilterField<br>id, text, number, select, bool, datetime"]
    end

    subgraph sg_3 ["Cell System"]
        direction LR
        TC["TypedCell<br>type-based dispatch"]
        CR["cellRegistry<br>type -> component map"]
        CELLS["TextCell, TagCell, ImageCell,<br>DateTimeCell, CurrencyCell, ..."]
    end

    subgraph sg_4 ["View Management"]
        direction LR
        VP["ViewPanel<br>view list sidebar"]
        SVM["SaveViewModal<br>create/edit views"]
        VMM["ViewManageModal<br>manage saved views"]
    end

    FV --> V
    V --> VT
    V --> FP
    V --> TB
    FP --> TF
    TF --> FR
    FR --> FF
    VT --> TC
    TC --> CR
    CR --> CELLS
    V --> VP
    VP --> SVM
    VP --> VMM

    style FV fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style V fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style VT fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style FP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style TB fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style TF fill:#161b22,stroke:#30363d,color:#e6edf3
    style FR fill:#161b22,stroke:#30363d,color:#e6edf3
    style FF fill:#161b22,stroke:#30363d,color:#e6edf3
    style TC fill:#161b22,stroke:#30363d,color:#e6edf3
    style CR fill:#161b22,stroke:#30363d,color:#e6edf3
    style CELLS fill:#161b22,stroke:#30363d,color:#e6edf3
    style VP fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style SVM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style VMM fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

FetcherViewer

顶层组件,负责协调视图定义加载、视图状态管理、通过 Wow 进行数据获取以及渲染完整的查看器 UI。

tsx
import { FetcherViewer } from '@ahoo-wang/fetcher-viewer';

function DataView() {
  return (
    <FetcherViewer
      viewerDefinitionId="order-view"
      pagination={{ pageSize: 20 }}
      enableRowSelection
      primaryAction={{
        label: 'Create Order',
        onClick: () => router.push('/orders/new'),
      }}
    />
  );
}

FetcherViewerProps

属性类型默认值描述
viewerDefinitionIdstring(必填)要加载的查看器定义 ID
ownerIdstring'(0)'数据范围的所有者 ID
tenantIdstring'(0)'数据范围的租户 ID
defaultViewIdstring--默认激活的视图
paginationfalse | PaginationProps(必填)分页配置,设为 false 可禁用分页
actionColumnViewTableActionColumn--行操作列配置
onClickPrimaryKey(id, record) => void--主键单元格的点击处理器
enableRowSelectionbooleanfalse启用复选框行选择
enhanceDataSource(data) => data--转换/增强获取的数据
onSwitchView(view) => void--切换视图时的回调
viewTableSettingViewTableSettingCapable--表格显示设置
primaryActionaction object--顶部栏中的主要操作按钮
secondaryActionsaction array--次要操作按钮
batchActionsaction array--选中行的批量操作

来源: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:48-71

命令式 Ref API

FetcherViewer 通过 ref 暴露命令式 API,用于父组件驱动控制——刷新数据、清除选择或访问当前查询/视图状态:

tsx
import { useRef } from 'react';
import { FetcherViewer, type FetcherViewerRef } from '@ahoo-wang/fetcher-viewer';

function DataView() {
  const viewerRef = useRef<FetcherViewerRef>(null);

  const handleRefresh = () => viewerRef.current?.refreshData();
  const handleClearSelection = () => viewerRef.current?.clearSelectedRowKeys();

  return (
    <>
      <button onClick={handleRefresh}>刷新</button>
      <button onClick={handleClearSelection}>清除选择</button>
      <FetcherViewer
        ref={viewerRef}
        viewerDefinitionId="order-view"
        pagination={{ pageSize: 20 }}
      />
    </>
  );
}
方法返回值描述
refreshData()void重新获取当前页数据
clearSelectedRowKeys()void清除所有选中的行键
getPageQuery()PagedQuery | undefined获取当前页查询(分页 + 条件)
getActiveView()ViewState | undefined获取当前活动的视图状态
getViewerDefinition()ViewDefinition | undefined获取已加载的查看器定义

来源: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:40-46

FetcherViewer 数据流

mermaid
sequenceDiagram
autonumber

    participant FV as FetcherViewer
    participant VD as useViewerDefinition
    participant VV as useViewerViews
    participant FD as useFetchData
    participant API as Wow Server
    participant V as Viewer

    FV->>VD: load definition
    VD->>API: GET /viewer-definitions/{id}
    API-->>VD: ViewDefinition

    FV->>VV: load views
    VV->>API: Query viewer views
    API-->>VV: ViewState[]

    FV->>FD: fetchData(viewDefinition, defaultView)
    FD->>API: POST /snapshot/paged (with conditions)
    API-->>FD: PagedList<RecordType>

    FV->>V: Render Viewer component
    V->>V: ViewTable + FilterPanel + TopBar + ViewPanel

来源: packages/viewer/src/fetcherviewer/FetcherViewer.tsx:82-357

刷新事件总线

FetcherViewer 与顶部栏的刷新项(RefreshDataBarItemAutoRefreshBarItem)通过 useRefreshDataEventBus(subscriberId) 创建的共享刷新事件总线通信。FetcherViewer 以其 viewerDefinitionId 作为订阅者 ID 进行订阅,当收到 subscriberId 匹配的 REFRESH 事件时重新加载当前页数据;命令式的 refreshData() 也向同一 ID 发布事件。

注册的处理器名称以订阅者 ID 为前缀(${subscriberId}:${handlerName})。卸载时,该 hook 只移除当前 hook 实例自己注册的处理器——绝不按整个订阅者 ID 前缀移除——因此共享同一 viewerDefinitionId 的多个组件(例如一个 FetcherViewer 及其刷新栏项)不会误删彼此的订阅。

来源: packages/viewer/src/hooks/useRefreshDataEventBus.ts:29-86packages/viewer/src/fetcherviewer/FetcherViewer.tsx:254-285

过滤系统

类型化过滤器注册表

过滤器通过类型使用注册表模式进行分发。TypedFilter 组件从注册表中查找对应的过滤器组件:

mermaid
graph LR
    subgraph sg_1 ["filterRegistry"]
        direction TB
        id["id -> IdFilter"]
        text["text -> TextFilter"]
        number["number -> NumberFilter"]
        select["select -> SelectFilter"]
        bool["bool -> BoolFilter"]
        datetime["datetime -> DateTimeFilter"]
    end

    TF["TypedFilter<br>type-based dispatch"] --> filterRegistry

    style TF fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style id fill:#161b22,stroke:#30363d,color:#e6edf3
    style text fill:#161b22,stroke:#30363d,color:#e6edf3
    style number fill:#161b22,stroke:#30363d,color:#e6edf3
    style select fill:#161b22,stroke:#30363d,color:#e6edf3
    style bool fill:#161b22,stroke:#30363d,color:#e6edf3
    style datetime fill:#161b22,stroke:#30363d,color:#e6edf3
过滤器类型组件描述
idIdFilter聚合 ID 输入
textTextFilter带运算符选择的文本输入
numberNumberFilter支持范围的数值输入
selectSelectFilter带选项的下拉选择
boolBoolFilter布尔值开关/复选框
datetimeDateTimeFilter支持范围的日期/时间选择器

来源: packages/viewer/src/filter/filterRegistry.ts:73-83

FilterPanel 组件

组件描述
FilterPanel显示一组活跃过滤器
EditableFilterPanel动态添加/移除过滤器
AvailableFilterSelect下拉选择要添加的过滤字段
AvailableFilterSelectModal过滤字段选择的弹窗版本
RemovableTypedFilter带移除按钮的单个过滤器

来源: packages/viewer/src/filter/panel/

过滤器 Props

每个过滤器组件接收标准化的 props:

typescript
interface FilterProps {
  field: FilterField;           // { name, label, type, format }
  label?: FilterLabelProps;     // Label display configuration
  operator?: FilterOperatorProps; // Operator selection (null to hide)
  value?: FilterValueProps;     // Value input configuration
  onChange?: (value?: FilterValue) => void; // Change callback
  conditionOptions?: ConditionOptions; // Condition building options
}

来源: packages/viewer/src/filter/types.ts:59-69

表格单元格系统

类型化单元格注册表

表格单元格通过类型使用与过滤器相同的注册表模式进行分发:

单元格类型组件描述
textTextCell支持省略号的纯文本显示
tagTagCell单个 Ant Design 标签
tagsTagsCell多个 Ant Design 标签
datetimeDateTimeCell格式化的日期/时间显示
calendar-timeCalendarTimeCell基于日历的时间显示
imageImageCell带缩略图的图片预览
image-groupImageGroupCell图片预览组
linkLinkCell可点击链接
currencyCurrencyCell格式化的货币显示
avatarAvatarCell用户头像显示
primary-keyPrimaryKeyCell可点击的主键单元格
actionActionCell单个操作按钮
actionsActionsCell多个操作按钮

来源: packages/viewer/src/table/cell/cellRegistry.ts:67-82

CellProps 接口

所有单元格组件接收标准化的 props:

typescript
interface CellProps<ValueType, RecordType, Attributes> {
  data: {
    value: ValueType;     // The cell value to display
    record: RecordType;   // The full row record
    index: number;        // Row index
  };
  attributes?: Attributes; // Component-specific attributes
}

来源: packages/viewer/src/table/cell/types.ts:100-106

ViewTable

ViewTable 组件封装了 Ant Design 的 Table,并与查看器定义集成,从视图字段配置自动生成列。支持:

  • 通过 TypedCell 自动分发单元格类型
  • 可排序列
  • 列可见性配置
  • 表格设置面板,用于字段排序和可见性管理
  • 行选择
  • 操作列

来源: packages/viewer/src/table/ViewTable.tsx

行键与选择稳定性

ViewTable 通过调用 mapToTableRecord(dataSource, primaryKeyFieldName) 为每行生成 key(用作 Ant Design 的 rowKey),其中 primaryKeyFieldName 是在字段定义中标记为 primaryKey 的字段名。键的优先级:

  1. 记录的主键字段值——在数据刷新、排序和翻页后保持稳定,行选择不会错位到其他记录。
  2. 记录自带的 key 属性(如存在则保留,不会被数组下标兜底覆盖)。
  3. 数组下标——仅作为最后兜底;下标键在数据变化后不稳定。

来源: packages/viewer/src/table/ViewTable.tsx:340-343packages/viewer/src/utils.ts:111-134

视图管理

mermaid
stateDiagram-v2
    [*] --> LoadingDef: FetcherViewer mounts
    LoadingDef --> LoadingViews: Definition loaded
    LoadingViews --> Active: Views loaded, default selected
    Active --> Active: Switch view
    Active --> Active: Create view
    Active --> Active: Edit view
    Active --> Active: Delete view
    Active --> Active: Update filters
    Active --> Active: Change page

    state Active {
        [*] --> Viewing
        Viewing --> FilterEditing: Open filter panel
        FilterEditing --> Viewing: Apply filters
        Viewing --> SaveModal: Save as new view
        SaveModal --> Viewing: View saved
    }

视图状态

每个视图(ViewState)持久化保存:

  • 活跃的过滤条件
  • 排序配置
  • 列可见性和排序
  • 视图名称和类型(PRIVATE/SHARED)
  • 默认视图标志

视图通过 Wow 命令操作进行管理,使用 ViewCommandClient

  • createView -- 创建新视图
  • editView -- 更新已有视图
  • defaultDeleteAggregate -- 删除视图

来源: packages/viewer/src/fetcherviewer/client/view/commandClient.ts

TopBar 组件

顶部栏在数据表格上方提供操作项:

组件描述
TopBar栏项容器
BarItem基础栏项组件
RefreshDataBarItem手动数据刷新按钮
AutoRefreshBarItem带间隔的自动刷新开关
FilterBarItem切换过滤面板可见性
FullscreenBarItem切换全屏模式
ColumnHeightBarItem调整表格行密度
DataMonitorBarItem数据监控指示器
ShareLinkBarItem复制可分享的视图链接

来源: packages/viewer/src/topbar/

独立组件

viewer 包还导出了可复用的 UI 组件:

组件描述
NumberRange用于范围过滤的双数值输入
RemoteSelect带远程数据获取的选择器
TagInput用于管理标签集合的输入框
Fullscreen全屏容器封装

来源: packages/viewer/src/components/

注册表模式

过滤器和单元格都使用共享的 TypedComponentRegistry<T, P> 模式:

typescript
import { TypedComponentRegistry } from '@ahoo-wang/fetcher-viewer';

// Create a custom registry
const myRegistry = TypedComponentRegistry.create<string, MyProps>([
  ['type1', MyComponent1],
  ['type2', MyComponent2],
]);

// Register additional types
myRegistry.register('type3', MyComponent3);

// Look up
const Component = myRegistry.get('type1');

来源: packages/viewer/src/registry/componentRegistry.ts

Storybook 集成

viewer 包包含全面的 Storybook stories,用于可视化开发和测试:

bash
pnpm storybook

Stories 与组件并列组织在 stories/ 目录中:

  • 过滤器组件: filter/stories/filter/panel/stories/
  • 单元格组件: table/cell/stories/
  • 表格组件: table/stories/table/setting/stories/
  • 顶部栏组件: topbar/stories/
  • 查看器组件: viewer/stories/view/stories/
  • FetcherViewer: fetcherviewer/stories/
  • 独立组件: components/stories/

主要导出

导出模块描述
FetcherViewerfetcherviewer/顶层查看器协调器
Viewerviewer/视图 + 表格 + 过滤器组合
Viewview/单个视图容器
ViewTabletable/带类型化单元格的 Ant Design 表格
TypedFilterfilter/按类型分发的过滤器组件
filterRegistryfilter/过滤器组件注册表
TypedCelltable/cell/按类型分发的单元格组件
cellRegistrytable/cell/单元格组件注册表
TopBartopbar/操作栏容器
FilterPanelfilter/panel/活跃过滤器显示
EditableFilterPanelfilter/panel/动态过滤器编辑器
ViewPanelviewer/panel/视图列表侧边栏
SaveViewModalviewer/panel/保存/编辑视图弹窗
TypedComponentRegistryregistry/通用类型化组件注册表
NumberRangecomponents/双数值范围输入
RemoteSelectcomponents/远程数据选择器
TagInputcomponents/标签管理输入框

交叉引用

  • Wow -- FetcherViewer 使用 Wow SnapshotQueryClient 进行数据获取,使用 CommandClient 进行视图管理
  • React -- 使用 React 包中的 useFetcheruseKeyStorageuseEventSubscription Hooks
  • Fetcher -- 用于所有 API 通信的核心 HTTP 客户端
  • Storage -- KeyStorage 用于持久化本地默认视图 ID
  • CoSec -- 通过 owner/tenant ID 支持多租户数据范围

基于 Apache License 2.0 发布。