Skip to content

工作区卡片 ​

工作区卡片把小程序的概览、进度或常用操作放到 PC 的游戏工作区。用户可以先看卡片,再按需打开小程序页面。

客户端支持

SDK 提供卡片构建、页面刷新和本地调试入口。工作区卡片需要支持该能力的 Windows/macOS 客户端及相应平台权限;浏览器调试页不会渲染卡片,请在真实 PC 客户端验收。

Controller 运行在 PC 提供的受限执行环境,独立于小程序页面。项目只需从 @heybox/hb-sdk/card-controller 导入构建器,无需安装额外的视图依赖。

阅读导航 ​

第一次接入按顺序阅读;已经接入但没有显示卡片,可以直接跳到排障。

工作区卡片如何运行 ​

部分你要做什么运行在哪里
项目声明在 package.json#heybox 指定绑定游戏、权限和 Controller 文件开发与构建阶段
Controller返回卡片内容,处理刷新和卡片按钮PC 的受限执行环境,独立于小程序页面
小程序页面读取卡片打开上下文,必要时请求刷新卡片普通小程序页面,使用根 SDK

声明卡片、批准权限、发布版本是三个独立步骤。 构建成功或小程序页面能打开,都不代表卡片已经可以展示。

快速接入 ​

以下示例适用于已经启用 miniappManifest() 的工坊小程序项目。先完成快速开始,再合并配置,保留项目原有的其他 heybox 字段。

1. 声明入口与绑定游戏 ​

json
{
  "heybox": {
    "platforms": ["windows", "macos"],
    "bindAppIds": ["730"],
    "permissions": {
      "workspaceCards": { "enabled": true }
    },
    "workspaceCards": "./src/workspace-cards.ts"
  }
}

730 是示例游戏编号,请换成项目实际绑定的游戏。workspaceCards 指向项目内的一个 TypeScript 文件,不是卡片数组或构建后的 Manifest 对象。

2. 创建 Controller 文件 ​

创建 src/workspace-cards.ts:

ts
import {
  defineCard,
  defineWorkspaceCardsController,
  region,
  text,
  viewDocument,
} from '@heybox/hb-sdk/card-controller'

export default defineWorkspaceCardsController({
  cards: {
    summary: defineCard({
      title: '游戏助手',
      sizes: ['1x1'],
      defaultSize: '1x1',
      documents: {
        '1x1': viewDocument([region('main', 'ready', [text('欢迎使用游戏助手')])]),
      },
    }),
  },
})

这张卡的 ID 是 summary,内容固定,所以可以省略 refresh。Controller 只从 @heybox/hb-sdk/card-controller 导入;普通页面才从 @heybox/hb-sdk 导入 SDK。

3. 构建并检查产物 ​

bash
hb-sdk build

构建不需要登录或绑定远端小程序。成功后检查 dist/manifest.json 中生成的 workspaceCards,以及 dist/_hb/workspace-cards/controller.mjs。摘要和文件回执由构建器生成,请勿手填或修改构建后的文件。

接下来按调试与发布在真实 PC 验收,再提交版本。dist/manifest.json 是发布合同,不是供业务页面从 CDN 读取的文件。

编写卡片与 Controller ​

卡片定义与卡片内容 ​

defineCard() 声明卡片标题和尺寸;documents 描述每个尺寸的内容。卡片内容使用 viewDocument() 和布局构建器,不使用 HTML、Vue 组件或 DOM。

字段写法与含义
cards 的对象键卡片 ID,例如 summary;必须为小写 kebab-case,最多 64 个字符
title用户看到的标题,使用固定字符串
sizes支持的尺寸,只能是 1x1、2x1、2x2;必须包含 1x1
defaultSize默认尺寸,必须在 sizes 中
documents固定内容,可省略;省略时必须提供 refresh 产生内容
loadingDocuments / errorDocuments可选的等待/错误内容;提供时需覆盖全部尺寸,不能包含动作或动态图片

声明两个尺寸时,要写两份完整文档;PC 不会把小尺寸文档自动拉伸成大尺寸文档。所有卡片都使用同一 Controller,不需要声明 static/dynamic mode。

刷新与按钮怎样协作 ​

入口用途返回
refresh(ctx)首次加载、周期刷新或页面请求刷新时计算内容{ cards: { 卡片ID: { documents } } }
defineAction({ run })用户点击卡片按钮时执行操作同样返回卡片内容
defineOpenMiniProgramAction()打开当前小程序页面不提供 run,由 PC 处理打开行为

每张返回的卡片必须包含其全部声明尺寸。可以只返回本次要更新的卡片,但不能返回未声明的卡片或空状态。整批结果先校验;有一张不合规,这一批内容不会部分提交。

示例:计数器卡片 ​

在 package.json#heybox.permissions 中同时声明 workspaceCards 和 storage 的 enabled: true。然后把 Controller 文件替换为:

ts
import {
  button,
  defineAction,
  defineCard,
  defineWorkspaceCardsController,
  region,
  text,
  viewDocument,
  type WorkspaceCardControllerResult,
} from '@heybox/hb-sdk/card-controller'

function render(count: number): WorkspaceCardControllerResult {
  return {
    cards: {
      counter: {
        documents: {
          '1x1': viewDocument([
            region('main', 'ready', [
              text(`计数:${count}`),
              button('加一', { action: increment(null) }),
            ]),
          ]),
        },
      },
    },
  }
}

const increment = defineAction<null>({
  async run(ctx) {
    if (!ctx.storage) throw new Error('当前环境未开放存储')
    const { data } = await ctx.storage.getStorage({ key: 'counter' })
    const next = (typeof data === 'number' ? data : 0) + 1
    await ctx.storage.setStorage({ key: 'counter', data: next })
    return render(next)
  },
})

export default defineWorkspaceCardsController({
  cards: {
    counter: defineCard({
      title: '共享计数器',
      sizes: ['1x1'],
      defaultSize: '1x1',
    }),
  },
  actions: { increment },
  async refresh(ctx) {
    const stored = await ctx.storage?.getStorage({ key: 'counter' })
    return render(typeof stored?.data === 'number' ? stored.data : 0)
  },
})

actions 的对象键生成动作 ID。increment(null) 把该动作绑定到按钮,不会在生成文档时执行 run。需要输入参数时,用 actionInput 声明输入形状,再把对应数据传给 handle;不要自行拼接 commandId。

注意:以上计数更新是先读后写,不保证并发加一。已完成的存储写入不会因为后续内容校验失败而回滚。

选择布局构建器 ​

内容常用构建器
文本与状态text、badge、icon
横向/纵向/网格布局hstack、vstack、zstack、grid
分组与分隔region、group、divider、spacer
列表与数据展示list、meter、radialProgress、barChart
切换与输入tabs、carousel、expandable、button、textField、toggle

构建器与 PC 的 ViewDocument 合同共用,但卡片只允许受限子集。公开入口里有某个 builder,不代表工作区卡片允许使用它:当前禁止 renderer 和 frame;图片能力尚未接入,不要直接填远程图片 URL 或依赖 ctx.images。

精确参数与类型见 Card Controller API。Tabs、展开等本地交互由视图处理;需要执行业务操作时才绑定受管动作。

Controller 能读取哪些能力 ​

上下文说明
ctx.game.appId当前游戏工作区的编号
ctx.environment当前平台和语言
ctx.trigger触发原因与本次执行 ID
ctx.operationId本次操作标识;有副作用的后端业务应据此或自身业务键做幂等
ctx.storage可选的设备存储,受 storage 声明约束
ctx.network可选的受控网络,受权限与 networkOrigins 共同约束

Controller 与同一小程序页面共享设备存储,按小程序隔离,不按登录账号隔离。只存设备偏好等非账号数据;不要存凭据、账号资料或账号派生内容。它不提供事务或 CAS。

使用网络必须同时满足:声明并获准 network、在 Controller 根对象中声明精确 HTTPS networkOrigins、当前 PC 提供 ctx.network。origin 不带路径,例如 https://api.example.com,不是 https://api.example.com/data。refresh 只允许 GET,动作中的网络合同允许 GET/POST;实际开放仍以 PC 权限策略为准。当前不支持 Controller 访问小黑盒官方域业务接口。

这些写法会被构建拒绝 ​

  • Controller 只能从 @heybox/hb-sdk/card-controller 具名导入,不导入根 SDK、本地业务模块、Node、Electron 或其他依赖。
  • cards、actions、标题、尺寸、默认尺寸、networkOrigins 和输入 schema 必须静态可读;不使用 spread、计算属性或运行时配置生成元数据。
  • 辅助逻辑放在同文件的 function 中。顶层普通 const 只允许原始字面量;动作 handle 可使用 const action = defineAction(...)。不要用顶层 const documents = (...) => ...。
  • 不使用 fetch、DOM、timer、动态 import、eval、Function 或 WASM。异步能力只通过 ctx 提供的端口使用。
  • 不在返回内容中提供刷新周期、cron 或 nextRefresh;刷新调度由 PC 负责。
限制当前上限
卡片每个小程序 3 张
动作16 个
网络 origin8 个
Controller 单文件1 MiB
每张返回的卡片状态64 KiB

卡片与小程序页面协作 ​

普通前台页面使用根 SDK @heybox/hb-sdk,与 Controller 的作者入口分开。

从卡片打开小程序 ​

Controller 使用 defineOpenMiniProgramAction() 生成打开动作,将 handle 放入 actions,再绑定到按钮。它只能打开当前小程序,不执行自定义 URL 或任意客户端命令。

例如,下面的 Controller 会展示“打开工具”按钮,并向页面传入 { section: 'overview' }:

ts
import {
  actionInput,
  button,
  defineCard,
  defineOpenMiniProgramAction,
  defineWorkspaceCardsController,
  region,
  viewDocument,
} from '@heybox/hb-sdk/card-controller'

const openPage = defineOpenMiniProgramAction<{ section: string }>({
  input: actionInput.object({ section: actionInput.string({ maxLength: 32 }) }),
})

export default defineWorkspaceCardsController({
  cards: {
    summary: defineCard({
      title: '游戏助手',
      sizes: ['1x1'],
      defaultSize: '1x1',
      documents: {
        '1x1': viewDocument([
          region('main', 'ready', [
            button('打开工具', { action: openPage({ section: 'overview' }) }),
          ]),
        ]),
      },
    }),
  },
  actions: { 'open-page': openPage },
})

页面通过 workspaceCards.getLaunchContext() 读取最近一次卡片启动信息:

字段含义
source固定为 workspace-card
launchId本次启动的唯一标识,用于去重
gameAppId触发打开的游戏编号
cardId / actionId触发打开的卡片与动作
params动作携带的可选 JSON 参数
launchedAt启动时间,毫秒时间戳

普通打开页面时上下文为空。页面未关闭时,下一次卡片点击仍可能带来新的启动上下文,所以需要订阅变化,不能只在首次加载读取一次。

ts
import { workspaceCards } from '@heybox/hb-sdk'

let lastLaunchId: string | undefined
const off = workspaceCards.onLaunchContextChange(context => {
  if (!context || context.launchId === lastLaunchId) return
  lastLaunchId = context.launchId
  // 在这里切换到该游戏对应的业务内容。
  console.log(context.gameAppId, context.cardId, context.params)
})

// 页面或组件销毁时调用 off();普通页面可在 pagehide 时清理。
window.addEventListener('pagehide', off, { once: true })

订阅会立即回放当前值,不需要先额外调用 getLaunchContext()。账号变化、普通入口启动或会话结束后,上下文会清空。不要把这份来源信息当成用户身份或业务授权证明。

页面修改数据后请求刷新 ​

ts
import { workspaceCards } from '@heybox/hb-sdk'

// 在业务数据保存成功后调用;cardIds 使用 Controller 声明的 ID。
const { scheduled } = await workspaceCards.requestRefresh({
  gameAppId: '730',
  cardIds: ['summary'],
})
console.log(`已调度 ${scheduled} 个活动实例`)

省略参数时请求刷新当前小程序的活动实例。它不会创建游戏工作区、添加卡片或打开已关闭的实例。

scheduled 是进入调度的实例数,不是卡片张数,也不代表刷新完成。0 表示没有匹配的活动实例。需要查看最终效果时,应回到 PC 游戏工作区验收;不通过循环调用强迫立即刷新。

刷新合并、周期和失败退避由 PC 管理。卡片按钮的点击处理与视图内的 Tabs/展开交互不需要等待周期刷新。

当前 PC 默认每 30 分钟刷新一次内容,页面请求在 1 秒最小间隔内合并。已发布卡片的版本和权限每 30 秒复查,执行资格最多保留 60 秒;到期、无法续验、下架或撤权后立即停止 Controller 执行,清除旧文档和动作,并显示无内容、无动作的不可用占位。资格复查与内容刷新各自调度,固定内容卡可以省略 refresh。

Kit 1.19 起,稳定卡位由 Host 管理。同一逻辑卡片使用同一模板时复用原卡位;模板变化后,旧卡位仍由 Host 或用户管理,Extension 不会自动物理删除卡位。卡位保留不延长执行资格,也不会保留旧内容或旧动作。

兼容与错误处理 ​

不支持该能力的 Host 可能使 requestRefresh() 返回 CAPABILITY_UNAVAILABLE。按错误处理捕获 HbMiniProgramSDKError,让普通页面继续可用。

详细签名见 workspaceCards API。

调试与发布 ​

构建检查、本地 PC 调试与已发布版本展示是不同阶段,要分别验收。

本地调试 ​

bash
hb-sdk dev
  1. 确认项目声明了 Windows/macOS 中至少一个平台及目标游戏。
  2. 启动前完成 CLI 登录,并创建或绑定一个可管理的小程序;hb-sdk dev 校验通过后才会启动调试页。
  3. 从调试页的桌面端入口,在支持工作区卡片的 PC 测试客户端打开当前项目。
  4. 打开绑定游戏的工作区,检查卡片内容、每个尺寸和按钮行为。
  5. 修改 Controller 后检查新内容是否生效;退出本地调试后再验收已发布版本。

浏览器调试页只提供登录、绑定、启动与状态控制,不会在浏览器里展示或执行卡片。未登录或未绑定时,hb-sdk dev 会在启动前退出并给出修复指引;服务启动后的卡片构建失败只影响卡片连接,普通页面调试仍可继续。

当前本地卡片调试从项目声明与本地构建产物建立连接,不需要调用独立的后端“注册卡片调试会话”接口。CLI 与 PC 应使用相互匹配的开发版本;旧启动入口只能打开页面时,不能据此判断卡片已经接入。

本地调试权限不会修改线上平台批准状态,也不会临时批准 workspaceCards。卡片权限与网络、存储权限分别判断;正式验证时,以真实 PC 的能力和有效权限结果为准。

发布后展示 ​

按CLI 与发布流程完成登录、绑定、构建、上传与提审:

bash
hb-sdk login
hb-sdk remote info
hb-sdk build
hb-sdk remote deploy --release-note "增加游戏工作区卡片"

remote deploy 会运行项目的 scripts.build,前面的 hb-sdk build 用于先做离线自检。审核通过后按实际流程使用自动发布,或执行:

bash
hb-sdk remote release <version>

展示卡片还需同时满足以下条件,不能只检查“审核通过”:

条件如何核对
已发布版本包含卡片看实际发布版本的 Manifest,而不是本地源码或待审版本
已批准工作区卡片权限平台有效权限中的 workspaceCards 为 enabled;项目声明不能代替批准
游戏匹配当前 PC 游戏属于发布版本的 bindAppIds
平台匹配发布版本支持当前 Windows/macOS 平台
构建产物匹配使用本次构建上传的 Controller,不混用旧文件或手改生成的摘要
PC 可以承载卡片客户端支持该能力,工作区仍有可用容量

同一小程序在同一游戏工作区中,本地调试内容优先于已发布内容;结束本地调试后才回到已发布版本。定位线上显示问题前,先确认是否仍有本地调试连接。

常见问题与排障 ​

现象优先检查下一步
hb-sdk build 失败Controller 文件路径、静态元数据、尺寸覆盖、受限 API按构建错误修正后重新构建,不跳过校验
浏览器能开页面,但没有卡片预览浏览器调试页不渲染卡片到真实 PC 的绑定游戏工作区验收
PC 能开页面,但没有本地卡片登录/绑定、桌面平台声明、卡片构建状态、CLI 与 PC 版本检查调试页状态,再通过桌面入口打开
已发布小程序出现在游戏列表,但没有卡片已发布版本的卡片配置和有效 workspaceCards 权限平台权限启用后重新获取;仍缺失时交给服务端排查发布快照
游戏列表里也没有小程序游戏绑定、平台、版本发布状态与工坊资格核对当前发布版本及实际游戏编号
某张卡片或某个尺寸更新失败返回卡片 ID、每个尺寸的文档、状态字节大小一批内容校验失败不会部分提交;先用最小文档复现
按钮没有执行动作在 actions 中注册、handle 参数与 schema、PC 开放状态不自行拼接命令,核对实际错误
网络/存储端口不存在声明、平台批准及 PC 能力显式处理 ctx.network / ctx.storage 缺席
页面请求刷新返回 scheduled: 0当前小程序是否有匹配的活动卡片实例打开对应游戏工作区,核对游戏与卡片 ID
修改代码但仍看到旧内容发布版本是否变化,是否混用了本地与发布内容分别检查本地重建状态和已发布版本

向维护者反馈时提供:小程序 ID、已发布版本号、实际游戏编号、PC 平台与版本、调试或发布场景、时间和错误码。不要附带登录凭据或完整请求 Cookie。

服务端可能在列表中保留小程序入口,同时省略无法通过权限或快照校验的卡片配置;接口返回成功不等于该小程序的卡片获准展示。

相关文档 ​

完成检查 ​

  • 项目声明了 Controller 文件、卡片权限、桌面平台和绑定游戏。
  • 每张卡都支持 1x1,所有声明尺寸都有完整文档,按钮使用已声明动作。
  • 构建通过,并在真实 PC 分别验收本地调试与已发布版本。
  • 启动上下文按 launchId 去重,订阅在页面或组件销毁时清理。
  • 权限禁用、可选端口缺席、无活动实例、网络离线与不支持能力的情况均有处理。