Skip to content

架构:把审批 UI 重建在标准 SDUI 渲染器上(ListView + RecordDetailView + ApiDataSource + 多态记录预览组件) #2763

Description

@os-zhuang

Problem

The Approval Center is hand-rolled: a bespoke inbox page (/apps/setup/system/approvals, wired from HomePage.onOpenApprovals) plus a bespoke decision drawer. It re-implements list/detail machinery (search, filter, sort, pagination, keyboard nav, bulk-select, status badges, responsive, the record layout) that the platform's own metadata-driven renderers already provide — and does several of them worse. Every polish defect in #2762 (vertical status badge, flat action hierarchy, whitespace, the i18n split where bespoke buttons are localized but DeclaredActionsBar labels aren't) is a symptom of diverging from the standard renderers.

For a metadata-driven platform, the approval surface should be metadata, not a special-case page.

Relationship: #2762 is the polish/stopgap (fix the defects in place). This issue is the root cure (rebuild both surfaces on the standard renderers so those defects can't recur). Discovered while dogfooding v16.0 approvals; the surface is demonstrable via objectstack-ai/objectstack#3364 (part of the objectstack-ai/objectstack#3358 sweep).

Target architecture

Both standard renderers are already page/view-metadata + data-source driven and already accept an ApiDataSource:

  • plugin-list/src/ListView.tsx — list, api-backed.
  • app-shell/src/views/RecordDetailView.tsx — detail; takes a dataSource prop, renders an effectivePage, uses dataSource.findOne(...), and has an embedded mode (renders inside a drawer).
  • core/src/adapters/ApiDataSource.ts + resolveDataSource.ts — the provider:'api' primitive.
  • views/DeclaredActionsBar.tsx — metadata-driven decision actions (already used).

So the entire approval UI can collapse to: a sys_approval_request list view + a detail page, backed by an ApiDataSource pointed at the approvals endpoints, plus one reusable polymorphic record-preview component (+ a progress widget). The bespoke inbox page and drawer get deleted.

Decision drawer → standard mechanisms

Drawer element Standard SDUI mechanism Status
Request's own fields detail-page sections / field groups exists
Timeline (审批动态) related list on sys_approval_action exists
Decision composer (comment + attachments) declared action's ActionParamDialog (ADR-0059 file/richtext params; framework#3332 attachments param) exists
Action bar (approve/reject/reassign/…) DeclaredActionsBar already used
Progress / viewer (0-of-N, can_act) computed fields via ApiDataSourcegetRequest primitive exists, not wired
Target-record card (invoice/expense fields) polymorphic reference-preview component missing

Inbox list → standard mechanisms

Same story: a standard ListView fed an ApiDataSource/approvals/requests (listRequests), so 待我审批 / 我发起的 / 全部 become server-side scopes (not client filters), and it inherits badges/columns/i18n/light-mode/responsive/pagination/a11y/keyboard for free. The target-record columns use the same polymorphic component.

Scope — the two reusable primitives (shared by list + detail)

  • A1 · ApiDataSource for approvals — wire ListView/RecordDetailView to read from the approvals endpoints (/approvals/requests, /approvals/requests/:id) so viewer + decision_progress come down as fields. Needs a small framework-side contract: the approvals api data source shape (list + get returning the computed fields) — track in objectstack-ai/framework.
  • A2 · Polymorphic record-preview component — render a compact card for a (object_name, record_id) pair, resolving the target object + its display fields. Reusable well beyond approvals (audit rows, activity, comments all reference polymorphic targets).
  • A3 · Progress/quorum widget — a segmented indicator (unanimous / M-of-N / per-group 会签 ticks) bound to decision_progress. (Half a primitive; could be a component or a chart.)

Then compose as metadata

  • B1 · sys_approval_request detail page — sections (request fields), the polymorphic target-record card (A2), the progress widget (A3), a sys_approval_action related-list (timeline), and DeclaredActionsBar; data via A1.
  • B2 · sys_approval_request list view — columns incl. the polymorphic target-record + a key field (amount/urgency); scopes via A1; row + drawer actions via DeclaredActionsBar.
  • B3 · Delete the bespoke 审批中心 page + decision drawer once B1/B2 reach parity; keep ?request= deep-link semantics.

Honest caveat

Release notes deliberately keep approve/reject in a richer inline composer (excluded from the bar) so a reviewer can stage attachments while reading the timeline. Either prove ActionParamDialog can host that (comment + multi-file + inline context), or keep one thin custom approve/reject action — a component-level exception, not a hand-rolled page.

Why it's worth it

Approvals stop being a special case: new decision capabilities already ship as backend metadata (DeclaredActionsBar), and after this the whole surface is metadata + 2 reusable components. A2 (polymorphic preview) and A1 (api-backed standard views) pay off across the console.

Refs: #2762 (polish), objectstack-ai/objectstack#3364, objectstack-ai/objectstack#3358.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsduiServer-Driven UI runtime

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions