DirectSurface UIDirectSurface UI
开始使用
文档/业务指南

常用业务场景组合

import {  RenderAppContextScope,  RenderButton,  RenderDockPanel,  RenderFieldPresenter,  RenderFormPanel,  RenderNumberInput,  RenderStackPanel,  RenderText,  RenderTextBox,  createAppContextKey,} from 'ds-ui'

本页把布局、组件、应用上下文、命令和性能规则放到典型业务页面里说明。组件手册回答“某个控件怎么用”,本页回答“复杂界面应该怎么组合”。

组合原则

复杂页面先拆职责,再选组件:

应用壳
  菜单、导航、工作区、全局上下文

页面壳
  页面标题、查询区、操作区、主内容、详情区

业务块
  表单、表格、树、图表、编辑器、弹窗

基础控件
  TextBox、Dropdown、Button、TreeView、GridView 等

推荐规则:

  • 页面根负责分区,不负责绘制业务细节。
  • 业务块内部自己管理滚动,避免外层和内层滚动互相抢事件。
  • 页面级状态放页面或页面上下文,应用级上下文只放登录态、租户、全局服务。
  • 按钮、菜单、快捷键尽量走命令系统,避免每个按钮重复写权限判断。
  • 大数据组件必须明确虚拟化、增量更新或可视区绘制策略。

场景一:复杂工作台

典型工作台包含菜单、左侧模块导航、中央多标签页和右侧诊断或消息区:

RenderDockPanel appShell
  top: MenuBar + 全局 Toolbar
  left: NavigationMenu
  right: Notification / Runtime Diagnostics / Inspector
  fill: TabbedWorkspace 或 DockWorkspace

每个 tab 内部再独立组合页面:

Tab Page
  RenderAppContextScope
    RenderCommandScope
      RenderStackPanel vertical
        PageHeader
        Query Form
        CommandToolbar
        GridView / Editor / MarkdownViewer flex=1

实现时不要让应用壳直接持有页面草稿、当前选中行或当前文书。应用壳只负责打开页面、关闭页面和提供全局上下文。页面关闭时,页面 scope 和页面命令一起释放。

一个最小的布局骨架:

const appShell = new RenderDockPanel() const topBar = new RenderStackPanel({  orientation: 'horizontal',  spacing: 8,  crossAxisAlignment: 'center',})topBar.addChild(new RenderText('业务工作台'), 1)topBar.addChild(new RenderButton({ label: '刷新', onClick: reload })) const navigation = new RenderStackPanel({  orientation: 'vertical',  spacing: 8,  crossAxisAlignment: 'stretch',})navigation.addChild(new RenderText('模块导航')) const pageHost = new RenderStackPanel({  orientation: 'vertical',  spacing: 8,  crossAxisAlignment: 'stretch',})pageHost.addChild(new RenderText('当前页面'))pageHost.addChild(new RenderText('主内容区'), 1) topBar.height = 48navigation.width = 240appShell.addChild(topBar, { dock: 'top' })appShell.addChild(navigation, { dock: 'left' })appShell.setFill(pageHost)

场景二:查询 + 表格 + 详情

这是后台系统、CIS 字典维护、医嘱查询、模板列表最常见的页面模式。

RenderStackPanel vertical
  PageHeader
  RenderFormPanel 查询条件
  RenderCommandToolbar
  RenderDockPanel body
    fill: GridView
    right: Detail Panel / ObjectInspector / Form

关键点:

  • 查询条件用 表单开发 的规则组织,不要手写坐标。
  • 表格使用 GridView,由表格自己管理滚动、选择、排序和复制。
  • 详情区可以是只读详情、编辑表单或对象查看器。详情区宽度固定或可拖拽,不要让 GridView 被详情内容撑开。
  • 查询按钮、重置按钮、导出按钮走 命令与权限,便于按钮权限和快捷键统一管理。

数据流建议:

queryDraft
  -> 点击查询
  -> service.search(queryDraft)
  -> rows
  -> GridView
  -> selectedRow
  -> Detail Panel

不要把 rows 放到应用级上下文。它通常属于当前页面,页面关闭后应释放。

场景三:表单编辑页

表单页不要从一开始就做成“一个巨大表单”。复杂业务表单应分组:

RenderStackPanel vertical
  PageHeader
  RenderScrollViewer
    RenderStackPanel vertical
      Section 基本信息
        RenderFormPanel
      Section 联系方式
        RenderFormPanel
      Section 扩展信息
        RenderFormPanel / GridView / TextArea
  Footer CommandToolbar

字段层级:

RenderFormPanel
  RenderFieldPresenter
    RenderTextBox / RenderNumberInput / RenderComboBox / RenderDatePicker

表单草稿和字段绑定示例:

interface OrderDraft {  name: string  quantity: number} const orderDraft: OrderDraft = {  name: '',  quantity: 1,} const orderForm = new RenderFormPanel({  columns: 2,  labelWidth: 96,  adaptive: true,  columnGap: 16,  rowGap: 8,}) orderForm.addField(new RenderFieldPresenter({  label: '名称',  required: true,  child: new RenderTextBox({    value: orderDraft.name,    placeholder: '请输入名称',    onChange: value => {      orderDraft.name = value    },  }),})) orderForm.addField(new RenderFieldPresenter({  label: '数量',  unit: '件',  child: new RenderNumberInput({    value: orderDraft.quantity,    min: 1,    onChange: value => {      orderDraft.quantity = value    },  }),}))

校验建议:

  • 即时校验更新字段的 statusmessage
  • 提交校验先收集所有错误,再滚动到第一个错误。
  • 异步校验必须和页面生命周期绑定,页面关闭后不能继续更新 UI。

不要把后端原始对象直接双向绑定到控件。建议复制成页面 draft,保存成功后再合并回业务状态。

场景四:主从维护页

主从页通常是左侧树或列表,右侧详情编辑:

RenderDockPanel
  left: TreeView / ListView
  fill: StackPanel
    Header
    Detail Form
    Child Grid

常见数据关系:

selectedMaster
  -> detailDraft
  -> childRows
  -> 保存时一次提交主表和子表

交互建议:

  • 左侧树或列表只负责选择,不直接修改右侧表单。
  • 选择变化时,如果右侧有未保存草稿,先走关闭保护或切换确认。
  • 子表使用 GridView;不要在 GridView 单元格内嵌完整表单。
  • 当前主对象可以放页面上下文,供右侧表单、右键菜单和弹窗读取。

场景五:复杂上下文组合

复杂业务系统里,上下文按生命周期分层,而不是把所有东西放全局。

Application appContext
  currentUser
  tenant
  permission service
  dictionary service

Workspace / Tab / Page context
  currentPatient
  currentVisit
  currentDocument
  page draft
  page service

Modal / Popover context
  open reason
  selected row
  temporary draft

页面级注入示例:

const currentRecordKey = createAppContextKey<{ id: string; title: string }>('record.current') const recordContent = new RenderStackPanel({  orientation: 'vertical',  spacing: 8,})recordContent.addChild(new RenderText('当前文书')) const recordPage = new RenderAppContextScope({  values: [    [currentRecordKey, { id: 'r001', title: '入院记录' }],  ],  child: recordContent,})

上下文放置规则:

对象 推荐范围 原因
登录用户、租户 应用级 整个应用共享,生命周期等同应用。
权限服务、字典服务 应用级 多页面复用,通常是无页面状态服务。
当前患者、当前文书 页面或 tab 级 不同 tab 可以打开不同患者或文书。
查询条件、选中行、编辑草稿 页面级 页面关闭后应释放,不能串到其他页面。
弹窗参数、临时草稿 浮层级 弹窗关闭后释放。
大数组、大文档、大图片缓存 页面或文档级 避免全局长期持有导致内存不下降。

场景六:弹窗编辑

弹窗适合短流程编辑,不适合承载完整复杂页面。

推荐结构:

打开源页面
  当前上下文和 selectedRow
  -> showModal / showPopover
      local draft
      form
      confirm / cancel
  -> onConfirm 返回结果
  -> 页面更新 rows 或 detailDraft

设计规则:

  • 弹窗打开时复制一份 draft,取消时直接丢弃。
  • 确定时返回结果,由页面决定如何合并。
  • 弹窗需要读取当前患者、当前文书时,要继承打开源页面上下文。
  • 弹窗内异步请求、定时器和订阅必须在关闭时释放。

详见 弹窗编辑Popup 与 Overlay

场景七:带权限的操作区

业务按钮不要直接散落在页面里。推荐从命令定义生成工具栏、菜单和快捷键。

CommandManager
  record.save
  record.submit
  record.delete

RenderCommandToolbar
  根据 command state 绘制按钮

MenuBar / ContextMenu / Keyboard
  复用同一批 command

权限和启用状态分工:

判断 说明
visible 当前用户或当前页面是否应该看到这个命令。
enabled 当前状态下按钮是否可点击。
canExecute 执行前最终判断,避免状态变化导致误执行。
execute 只写真正的业务动作,不重复写 UI 状态判断。

当表单 draft、选中行、页面状态变化时,调用命令管理器刷新状态。这样按钮、菜单和快捷键会一致变化。

场景八:大数据页面

大数据页面必须先确定“谁拥有滚动”:

页面类型 滚动所有者
大表格 GridView 自己管理滚动。
大树 TreeView 自己管理滚动和虚拟行。
大文档 文档编辑器或 MarkdownViewer 自己管理滚动。
普通表单 外层 ScrollViewer 管理滚动。
工作台 每个 tab 页面自己管理内部滚动。

常见错误:

  • 外层 ScrollViewer 包住 GridView,又让 GridView 自己滚动。
  • 把 10 万行数据转成 10 万个 RenderObject。
  • hover 时重新构造整棵 render tree。
  • 页面关闭后全局缓存仍持有 rows、图片或文档对象。

性能文档从 性能总览 开始阅读。

场景选择表

需求 推荐文档
新建一个业务页面 创建第一个页面
做查询条件 + 表格 查询表格页
做主从维护 主从页
做复杂表单 表单开发
页面、弹窗、命令共享当前对象 应用上下文
按钮权限和状态统一管理 命令与权限
页面关闭和资源释放 页面上下文与生命周期
大数据卡顿 性能总览