Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[Bug] 详情页所有 Action 按钮点击无响应 — ActionProvider / 参数弹窗 / API handler / Toast / Confirm 全链路缺失 #994

Closed

Description

@hotlong

问题描述

在记录详情页面(如 Opportunity #107),头部所有 Action 按钮(Change Stage、Mark as Won 等)点击后完全无反应 — 无弹窗、无确认、无 Toast、无 API 调用。

screenshot

根因分析

Action 执行链路有 5 个环节断裂,导致按钮虽然渲染出来,但点击后执行静默失败:

# 断裂环节 涉及文件 影响
1 缺少 ActionProvider apps/console/src/components/RecordDetailView.tsx useAction() 回退到空的本地 ActionRunner,没有注册任何 handler
2 api action 的 target 是逻辑名称,不是 URL examples/crm/src/actions/opportunity.actions.ts ActionRunner.executeAPI() 对 'opportunity_change_stage' 执行 fetch(),直接报错但被静默 catch
3 无 onParamCollection handler packages/core/src/actions/ActionRunner.ts L289-295 "Change Stage" 需要用户选择 new_stage 参数,但参数收集步骤被跳过
4 无 onConfirm handler packages/core/src/actions/ActionRunner.ts L194 "Mark as Won" 的 confirmText 回退到 window.confirm,可能被浏览器抑制
5 无 onToast handler packages/core/src/actions/ActionRunner.ts L188 成功/失败消息(successMessage/errorMessage)被静默丢弃,用户零反馈

调用链路详解

RecordDetailView 
  → 从 objectDef.actions 筛选 record_header actions
  → 构建 action:bar schema → 传入 DetailView
    → DetailView 通过 SchemaRenderer 渲染 action:bar
      → action:bar 遍历 actions → 解析 action:button 组件
        → action:button 调用 useAction().execute()
          → ❌ 无 ActionProvider → 创建空 ActionRunner
            → ❌ 无自定义 handler → 走内置 executeAPI()
              → ❌ fetch('opportunity_change_stage') 失败
                → ❌ 无 toastHandler → 错误被静默吞掉

涉及的 Action 定义

// examples/crm/src/actions/opportunity.actions.ts
export const OpportunityActions = [
  {
    name: 'opportunity_change_stage',
    label: 'Change Stage',
    type: 'api',
    target: 'opportunity_change_stage',  // ← 逻辑名称,不是 HTTP URL
    params: [{ name: 'new_stage', label: 'New Stage', type: 'select', required: true, options: [...] }],
    refreshAfter: true,
    successMessage: 'Stage updated successfully',
  },
  {
    name: 'opportunity_mark_won',
    label: 'Mark as Won',
    type: 'api',
    target: 'opportunity_mark_won',      // ← 逻辑名称,不是 HTTP URL
    confirmText: 'Mark this opportunity as Closed Won?',
    refreshAfter: true,
    successMessage: 'Opportunity marked as won!',
  },
  // ... opportunity_mark_lost 等
];

现有代码上下文

App.tsx 已有 useActionRunner 的使用模式(仅用于 CRUD dialog),可作为参考:

// apps/console/src/App.tsx L105-137
const { execute: executeAction, runner } = useActionRunner();
runner.registerHandler('crud_success', async (action) => { ... });
runner.registerHandler('dialog_cancel', async () => { ... });

但 RecordDetailView 没有类似的 handler 注册逻辑,也没有被 ActionProvider 包裹。


开发任务清单

任务 1:在 RecordDetailView 中引入 ActionProvider

文件: apps/console/src/components/RecordDetailView.tsx

  • 用 <ActionProvider> 包裹 <DetailView> 及其子组件
  • 传入 context:包含当前 record 数据、objectName、user 信息
  • context 需要在 DetailView 数据加载完成后动态更新(updateContext)
// 伪代码
<ActionProvider
  context={{ record: currentRecordData, objectName, user }}
  onConfirm={confirmHandler}
  onToast={toastHandler}
  onNavigate={navigateHandler}
  onParamCollection={paramCollectionHandler}
  handlers={{ api: apiActionHandler }}
>
  <DetailView schema={detailSchema} dataSource={dataSource} ... />
</ActionProvider>

任务 2:实现自定义 API Action Handler

新增文件: apps/console/src/hooks/useRecordActions.ts(或扩展现有 useObjectActions.ts)

  • 注册 api 类型的 handler,拦截所有 type: 'api' 的 action
  • 根据 action.target(逻辑名称)映射到 dataSource 操作:
    • opportunity_change_stage → dataSource.update(objectName, recordId, { stage: params.new_stage })
    • opportunity_mark_won → dataSource.update(objectName, recordId, { stage: 'closed_won' })
    • opportunity_mark_lost → dataSource.update(objectName, recordId, { stage: 'closed_lost', loss_reason: params.loss_reason })
  • 通用化:对于未知 target,尝试 dataSource.update(objectName, recordId, params) 作为默认行为
  • 返回 { success: true, reload: true } 以触发页面刷新

任务 3:实现参数收集弹窗(onParamCollection)

新增文件: apps/console/src/components/ActionParamDialog.tsx

  • 根据 ActionParamDef[] 动态渲染 Shadcn 表单字段:
    • type: 'select' → <Select> 组件,options 来自 param.options
    • type: 'text' → <Input> 组件
    • type: 'textarea' → <Textarea> 组件
  • 支持 required 验证
  • 确认返回 Record<string, any>,取消返回 null
  • 弹窗使用 Shadcn Dialog 或 AlertDialog

任务 4:实现确认弹窗(onConfirm)

新增文件: apps/console/src/components/ActionConfirmDialog.tsx(或复用已有 AlertDialog)

  • 替换默认的 window.confirm
  • 渲染 confirmText 内容
  • 支持结构化 confirm 对象(title、message、confirmText、cancelText)
  • 返回 Promise<boolean>

任务 5:接入 Toast Handler

文件: apps/console/src/components/RecordDetailView.tsx

  • 传入 onToast handler,映射到 Sonner:
    onToast={(msg, opts) => {
      if (opts?.type === 'error') toast.error(msg);
      else toast.success(msg);
    }}
  • 确保 successMessage 和 errorMessage 正确展示

任务 6:接入 Navigate Handler

  • 传入 onNavigate,映射到 React Router 的 useNavigate()
  • 支持 SPA 内导航和外部链接

任务 7:处理执行后结果(refreshAfter / reload / redirect)

文件: apps/console/src/components/RecordDetailView.tsx

  • action 执行完毕后检查 result.reload / action.refreshAfter
  • 如为 true,触发 DetailView 重新加载数据(通过 refreshKey 或 dataSource 重新查询)
  • 如有 result.redirect,通过 navigate 跳转

任务 8:处理 visible 条件表达式的上下文

文件: packages/components/src/renderers/action/action-button.tsx L52

  • useCondition() 当前使用空 context {},导致 visible: "stage !== 'closed_won'" 永远返回 true
  • 需要确保 record 数据作为 context 传入 useCondition,使 visible 表达式能正确评估当前记录状态
  • 可通过 ActionProvider context 或 SchemaRenderer data prop 传递

任务 9:测试与文档

  • 单元测试: 为 useRecordActions hook 编写测试(handler 注册、API 映射、参数合并)
  • 集成测试: 在 RecordDetailView.test.tsx 中测试完整 action 流程:
    • 渲染 → 点击 "Change Stage" → 参数弹窗出现 → 选择 stage → 确认 → API 调用 → toast 成功 → 页面刷新
    • 渲染 → 点击 "Mark as Won" → 确认弹窗出现 → 确认 → API 调用 → toast 成功
    • 渲染 → 点击 "Mark as Won" → 确认弹窗出现 → 取消 → 无 API 调用
  • 更新 ROADMAP.md: 记录 action 系统集成完成状态

验收标准

  1. ✅ 点击 Change Stage → 弹出 Stage 选择弹窗 → 选择后确认 → 更新成功 → Toast 提示 "Stage updated successfully" → 页面数据刷新
  2. ✅ 点击 Mark as Won → 弹出确认弹窗 "Mark this opportunity as Closed Won?" → 确认后 → 更新成功 → Toast 提示 → 页面刷新
  3. ✅ 当 stage 已经是 closed_won 或 closed_lost 时,"Change Stage" 和 "Mark as Won" 按钮不显示(visible 条件生效)
  4. ✅ API 调用失败时显示错误 Toast
  5. ✅ 所有测试通过
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions