Numable 创作文档同一份内容也在终端里:`numable docs <主题>`English

xwidget —— 组件声明(.xwidget)

字段全表(每个字段必不必填、在哪儿编、写了会怎样)见 numable docs xwidget-fields —— 那张表由编辑器的契约表生成,不会和代码分叉。本章讲的是规则与写法。

读者:做工具的用户,和替他干活的 AI。两者读同一份。

它是什么

一个组件 = 一个 xWidget/<名>.xwidget 文件。它是外壳 + 一份纯渲染配方:外壳管这个组件在仪表盘和组件面板里叫什么、多大、点了去哪、能不能改参数;配方(canvas)管这个组件怎么取数、怎么画、多久刷一次。

同一份 .xwidget 同时供三个地方消费:App 内的仪表盘、组件面板的选择列表、以及桌面小组件。所以尺寸档位不是随便填的 —— 它决定这个组件能不能放到桌面上去。

一个包里的组件是多个而不是一个:22 是最常用的小方组件档,发布要求至少 3 个组件且必须有一个 22 和一个 42/44。

最小可用示例

一个行情组件的完整声明,可直接复制后改:

{
  "version": 2,
  "title": "个股",
  "sub": "价格 · 两个月形状",
  "i18n": { "en-US": { "title": "Stock", "sub": "Price and its 2-month shape" } },
  "layout": 22,
  "params": { "secid": "1.600519" },
  "events": {
    "onClick": "/detail?secid=${secid}",
    "onEdit": "/edit?ref=quote&secid=${secid}"
  },
  "canvas": {
    "source": "@[file://rc/quote.rcn]",
    "depends": [
      { "flow": "@[file://flow/quote.df]", "params": { "secid": "${secid}" } }
    ],
    "refresh": {
      "interval": ["09:30-16:10@60", "21:30-05:00@60", "3600"],
      "at": ["15:05", "16:05", "05:05"],
      "tz": "Asia/Shanghai"
    }
  }
}

怎么写

字段

字段 必填 类型 取值 / 一句注意
version 是 int 恒 2
title 是 string 组件名(仪表盘、组件面板、桌面小组件配置列表都显示)。裸值 = manifest.lang 那门
sub 是 string 副标题,一行放得下。组件面板里跟在标题后面
i18n 建议 object { "<locale>": { "title": …, "sub": … } },只认这两个键
layout 是 int 两位网格码,见下
params 否 object 组件实例参数的默认值,只有标量
events 否 object 只有 onClick / onEdit 两个键
canvas 是 object { source, depends?, refresh? },只有这三个键
jobs 否 array 长按这个组件能加哪几条提醒,每条 { id, params? }。id = 本包 xJob/<id>.xjob 的文件名;params 把组件实例参数映射成提醒参数,值只能写 ${组件参数} 或字面量(取数输出是结果不是身份,长按那一刻还不存在)。不写这个键,长按菜单里就没有「添加提醒」。详见 numable docs alerts(G46)

顶层只认上表这些键:scene preview previewData kind order open 这类键写了没有任何消费者,也不报错,不要写。canvas 里没有 params,也没有 events,也没有 onEdit —— canvas 只认 source / depends / refresh 三个键,写在里面的 onEdit 一个消费者都没有(交互事件只挂在外壳的 events 上)。克隆现成包时最容易连这块一起抄走,而且抄了不报错:长按组件就是没有「编辑参数」。

layout:网格码与尺寸

两位数,十位 = 宽占几格,个位 = 高占几格,两位各取 1–9,所以合法值是 11 到 99 之间的任意两位码,不是四个枚举。像素由两条式子推出:

w = 90 × 宽格 − 22        h = 98 × 高格 − 38

越界(某一位是 0)会静默回落成 22,不报错 —— 组件照样出来,只是尺寸不是你写的那个。

码 尺寸(pt) 常见用途
11 68 × 60 一个图标 + 一个数字,再多放不下
12 68 × 158 竖条,一列两三行小字
21 158 × 60 极简单行条
22 158 × 158 一个主数字 + 一条形状。每个包必须有一张
32 248 × 158 比 22 宽一格,主数字旁边能再挂一列
33 248 × 256 近正方,四五行的小列表
42 338 × 158 一排并列的几项 / 带趋势图的横条
44 338 × 354 列表、网格、多块信息
46 338 × 550 长列表。只有鸿蒙桌面放得下
62 518 × 158 超宽横条。桌面都放不下,只在仪表盘里成立

App 内的仪表盘任何码都能放。桌面小组件放不放得下各平台有差:

平台 桌面能放的档位
iPhone / iPad / Mac 只有 22 / 42 / 44
Android 4×4 以内的 16 个码(11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44)
鸿蒙 8 个码(11 21 22 32 33 42 44 46)
Windows 版 没有桌面小组件

要让一个组件在所有平台的桌面都可用,就写 22/42/44。别的档位不是不能用,只是那个组件在部分平台上只活在 App 内的仪表盘里 —— 这是设计选择,不是错误。各平台桌面小组件的其它差异见 numable docs capabilities。

三个组件的画法要点(同一份 RCN 换档必错位,所以档位是先定的,不是最后调的):

档 画法要点
42(338×158) 横向分栏:左侧主数字,右侧一条趋势形状。分栏位置写成 {parent.w} 的四则运算("x": "{parent.w}*0.42"),不要把锚点塞进 $[…] 方法(check G33),也不要在宽高字段里写方法(check G32)
44(338×354) 上下两段:顶部一行标题 + 时间锚,下面用 op:forEach 铺 4–6 行。行高写死、行数按可用高算,别让最后一行被裁一半
21(158×60) 只放得下一行:一个图标 + 一个数字 + 一个单位。不要放标题,标题已经在长按菜单和组件面板里显示过了

RCN 里用 {parent.w} / {parent.h} 定位,别把 158 / 338 写死 —— 换一档 layout 就整体错位。

params

"params": { "secid": "1.600519", "alias": "茅台", "mask": "0" }

events

只有两个键,值有两种类型,平台按解析后的类型分派:

值的类型 怎么写 行为
字符串 "/detail?secid=${secid}" · "numable://self" · "numable://self/page/item?id=${itemId}" 当作导航串,开对应页面
结构体 "@[file://flow/mark-today.af]" 或直接内联一个流对象 当作交互流跑

canvas

"canvas": {
  "source": "@[file://rc/quote.rcn]",
  "depends": [ { "flow": "@[file://flow/quote.df]", "params": { "secid": "${secid}" } } ],
  "refresh": { "interval": ["3600"] }
}

多条 depends:按数组顺序一条接一条串行跑,输出依次合并成一份数据交给 RCN,同名键后者覆盖前者。

"canvas": {
  "source": "@[file://rc/daily.rcn]",
  "depends": [
    { "flow": "@[file://flow/me.df]", "params": {} },
    { "flow": "@[file://flow/todo.df]", "params": { "login": "${login}" } }
  ],
  "refresh": { "interval": ["1800"] }
}

两条纪律:① 后一条读不到前一条的输出,入参各写各的;② 任意一条失败,整个组件就是失败态,不是「少一块」。所以别把可有可无的补充数据单开一条 depends —— 那条挂了会把主数据一起拖没。

静态组件:不取数的组件(说明组件、入口组件)连 depends 一起省掉。

{
  "version": 2,
  "title": "关于",
  "sub": "这个包能给你什么",
  "layout": 21,
  "params": {},
  "canvas": { "source": "@[file://rc/about.rcn]" }
}

⚠️ 省掉 refresh 不是「走默认周期」,是「只在首屏拉一次,之后不再轮询」。真要取数的组件漏写 refresh,现象是数据停在打开 App 那一刻不再动,而且不报错。

@[file://…] 的基准是 xWidget/(对 .xwidget 与 xWidget/rc/*.rcn 都是),所以写 @[file://rc/quote.rcn]、@[file://flow/quote.df],不写 xWidget/ 前缀。页面域(.xpage、page/rc/*.rcn)的基准是包根,两边不通用;基准写错的症状是解析成空 —— 取数根本不发,而且不报错。

depends 的两种形态(最常见的坑)

"depends": ["@[file://flow/quote.df]"]

裸字符串 = 传空入参。裸引用不会自动带上入参,于是流里的 ${secid} 取到空、请求打成 ?secid=,而流仍然报成功,组件安静地渲一片 --。

"depends": [ { "flow": "@[file://flow/quote.df]", "params": { "secid": "${secid}" } } ]

对象形态 = 只传显式写出来的键。要把外壳 params 交给取数流,就得一个键一个键写上去。不吃参数的组件也照样写成 { "flow": …, "params": {} },形状统一。

还有一条同源的纪律:外壳 params 不在 RCN 的渲染域里。RCN 只看得见取数流 resultFilter 透出的键。所以「不参与取数、但组件上要显示」的键(城市名、别名)也必须传进 .df、在流里落地、再透出来,RCN 才取得到 —— 否则那一格恒空或恒走兜底,同样不报错。

添加组件面板里的预览:用户在「添加组件」面板里看到的那张预览,App 会在实例参数里多给一个保留键 _preview,值为 "1",只给这一次渲染、不落盘;仪表盘、桌面小组件、分享都不给。组件要在 depends 的 params 里显式写 "_preview": "${_preview}" 才收得到,不写就和平常一样取数。典型用法是需要凭证的组件:用户还没接入时,流里看到 _preview 是 1 就给一组编造的示例数据,组件上标出「示例」,让用户看出加上之后长什么样;已经接入就照常取真数据。示例必须是编造的,不能用任何用户的真数据。

refresh

"refresh": { "interval": ["09:30-15:00@15", "3600"], "at": ["15:05"], "tz": "Asia/Shanghai" }
键 写法
interval 数组。HH:MM-HH:MM@秒 = 该时间窗内每 N 秒一次(@ 后面是秒,不是分钟);裸数字 = 秒,作为窗外兜底。跨零点的窗直接写 21:30-05:00@60
at 每日固定时点,["15:05"]
tz IANA 时区名。iPhone / iPad / Android 按它算时间窗与时点;鸿蒙与 Windows 版按设备本地时区算,时区跨度大的用户在这两个平台上会看到窗口偏移

到点判据是「到周期了 / 错过了某个时点 / 从来没拉过」三者之一。常用取值:榜单 ["300"]、天气 ["300"]、行情 ["09:30-15:00@10", "3600"](开盘时段每 10 秒、其余每小时)。

四条容易写错的细节:

  1. 多个时间窗按数组顺序先匹配者胜,不是取最短那个。["09:00-18:00@600", "09:30-15:00@60"] 里第二条永远轮不到 —— 上午九点半也落在第一个窗里。把窄窗写在前面。
  2. 窗外兜底取的是「第一个裸数字」,后面再写裸数字不生效。一个窗都没命中、又没有裸数字,结果是完全不轮询。
  3. at 的时点错过了会补一拍:App 没开着的时候到了 15:05,下次打开时判定仍然成立,会补取一次,不会跳过。
  4. 距上一次真取数不足 3 秒的一律跳过,写多小的 interval 都突破不了这个下限。
"refresh": { "interval": ["09:30-15:00@60", "21:00-23:00@300", "3600"], "at": ["15:05"] }

这段读作:开盘时段每 60 秒,晚间时段每 300 秒,其余每小时,外加收盘后 15:05 补一拍。

节奏由上游决定。 仪表盘按最早到期的那个组件醒来,不是固定节拍,所以写 10 秒就是 10 秒一跳。免费版用户的周期刷新会被放慢到每 5 分钟(窗口里的 @N 与裸秒都一样),Pro 用户按你写的节奏走;手动更新、首次加载、切语言、widget.refresh 不受这条影响。数据会变、接口扛得住,就写勤快些(行情盘中 10 秒,天气、榜单 5 分钟);上游一天才更新一次的,写快了只是反复取同一份数。要算的是「每次刷新打几个请求 × 每小时刷几次」有没有超过上游限流 —— 超了的表现是组件安静地停在旧数据,不报错。桌面小组件不跟这个数:各平台有自己的下限(iOS 15 分钟、Android 1 分钟、鸿蒙只显示 App 上次画好的图)。要「立刻更新一次」用交互流里的 widget.refresh。

规则(违反 = 返工)

规则 检查方式 违反时的现象 修法
depends 里凡是要传参的绑定都写成 {flow, params} 对象 check G12c 组件渲一片 --,而 run 全绿、流报成功 逐键写 "k": "${k}"
RCN 里用到的 ${x} 必须来自该组件某条 .df 的 resultFilter keys check G28 那一格渲空或恒走兜底,像「这组件本来就这样」 参数传进流 → 流里落地 → 透出 → RCN 取
onClick 的 numable://self/page/<route> 必须在 router.json 里存在;numable://self/widget/<id> 的 id 必须存在 check G12 App 里弹「页面不存在」/ 开出空的添加面板 跳首页写 numable://self
onEdit 走路由必须是裸 path 且在 router.json 里;走流必须是包内存在的 .af、禁 .. check G12d 点了没反应,或打开一个空白页且不报错 改成 /edit 这种裸 path
onEdit 的目标必须有写回能力 check G12e 打得开、按了不会有任何变化 html 页调 xbridge.updateParams;.xform 的 onSubmit 流里调 widget.updateParams
每包 ≥3 个组件,且至少有一个 22、一个 42 或 44 check --profile publish G6 发布被拒 补组件
i18n["en-US"].title/sub 齐全 check --profile publish G20 英文环境下组件标题、组件面板显示中文 补 B 表译文
params 键名不像密钥 check G18 密钥进明文回显面 走 manifest.credentials
几何用 {parent.w} / {parent.h},不写死 158/338 render 层(换一档 layout 出图就看得出) 换档后整体错位、内容出框 改相对锚点
刷新节奏不超过上游限流 人审 接口被限流,组件停在旧数据且不报错 按「每次请求数 × 每小时次数 ≤ 上游配额」调;要立刻刷用 widget.refresh
桌面要用的组件只用 22/42/44 人审 那个组件在部分平台的桌面组件列表里根本不出现 换档位
要取数的组件必须写 canvas.refresh 人审 数据停在打开 App 那一刻,之后再也不动,且不报错 补 "refresh": { "interval": ["1800"] }
onEdit 写在外壳 events 里,不写进 canvas 人审 长按组件没有「编辑参数」,而 check 全绿 挪到顶层 events
窄的时间窗写在数组前面 人审 窄窗被前面的宽窗吃掉,永远轮不到 调整数组顺序

出错怎么办

现象 最可能的原因 先做什么
组件上全是 --,但 numable run 全绿 depends 用了裸串形态,入参没传进去 改 {flow, params}
只有某一格空(比如城市名),别的都对 那个键没进 .df 的 resultFilter 传进去、落地、透出
numable render 出的图整块白 RCN 层的问题(缺 type、尺寸字段里塞了方法…) 见 numable docs rcn
点组件没反应 / 弹「页面不存在」 onClick 指向的路由不存在 对照 router.json
长按「编辑参数」进去改了没用 目标页没有写回调用 见 numable docs params
桌面小组件列表里找不到这个组件 layout 不在这个平台桌面支持的档位里 换 22/42/44
组件的尺寸不是我写的那个,变成了 22 layout 某一位是 0(比如 20、04),越界静默回落 两位都写 1–9
数据只在刚打开 App 时对,之后再也不更新 canvas.refresh 整个省掉了 = 只首屏拉一次 补 refresh.interval
加了第二条 depends 之后整个组件都空了 新加那条失败,而任一条失败即整组件失败 单独跑 numable run 看是哪条挂了
改了包内容,App 里还是旧的 manifest.version 没 +1 +1 重装

相关

numable docs df · numable docs params · numable docs rcn