lint-codes —— numable check 错误码对照(自动生成)
check有两档:personal(默认,个人自用)只查会让包坏掉的规则;publish(--profile publish)加上上架要求。「档位」列写「仅 publish」的,个人包不用管。级别 E = 必须修,W = 建议修。每码对应的写法说明看「章」列指向的numable docs <id>。
总览
| 码 | 管什么 | 档位 | 章 |
|---|---|---|---|
| G0 | 包结构与 JSON 语法 | personal 也查 | numable docs layout |
| G1 | 不写不会被加载的文件,不带测试夹具 | personal 也查 | numable docs layout |
| G2 | manifest 必填字段与身份 | personal 也查 | numable docs layout |
| G3 | 网络白名单不多不少 | personal 也查 | numable docs layout |
| G4 | 取数流的跨端安全写法 | personal 也查 | numable docs df |
| G6 | 组件档位齐全 | 仅 publish | numable docs xwidget |
| G7 | 颜色与节点结构 | personal 也查 | numable docs rcn |
| G8 | 多语言词表结构与引用 | 仅 publish | numable docs i18n |
| G11 | 包图标 | 仅 publish | numable docs layout |
| G12 | 路由、流引用与参数编辑入口 | personal 也查 | numable docs page |
| G13 | 包体积上限 | personal 也查 | numable docs layout |
| G14 | 封包格式(由封包 seal 检查) | personal 也查 | numable docs layout |
| G15 | H5 输入框字号 | personal 也查 | numable docs page |
| G16 | 外部链接与 scheme | personal 也查 | numable docs layout |
| G17 | XPage 顶部让位 | personal 也查 | numable docs page |
| G18 | 凭证声明 | personal 也查 | numable docs layout |
| G19 | 商店门面文案 | 仅 publish | numable docs layout |
| G20 | 组件的多语言标题 | 仅 publish | numable docs xwidget |
| G21 | 包名长度与元数据词表 | 仅 publish | numable docs layout |
| G22 | 点击要有即时反馈 | personal 也查 | numable docs rcn |
| G23 | 首屏缓存 | 仅 publish | numable docs df |
| G24 | 添加组件入口只留一个 | 仅 publish | numable docs page |
| G25 | 凭证绑定要有直达入口 | 仅 publish | numable docs page |
| G26 | 取数失败必须让流失败 | personal 也查 | numable docs df |
| G27 | 添加组件按钮统一版式 | 仅 publish | numable docs page |
| G28 | 表达式笔误与渲染取值域 | personal 也查 | numable docs rcn |
| G29 | 点击不写成 click 串 | personal 也查 | numable docs rcn |
| G30 | 字形可渲染与条件键写法 | personal 也查 | numable docs df |
| G31 | 方法名必须在 VParser 白名单里 | personal 也查 | numable docs df |
| G32 | 尺寸字段里不许有 $[方法] | personal 也查 | numable docs rcn |
| G33 | 布局锚点不许进方法参数 | personal 也查 | numable docs rcn |
| G34 | resultFilter 不许透出保留键 lang / theme | personal 也查 | numable docs df |
| G35 | XPage 节点层的 ${@i18n.k} 必须在页表里查得到 | personal 也查 | numable docs page |
| G36 | 事件值不许以 ${ 开头 | personal 也查 | numable docs xwidget |
| G37 | 写盘的 .af 落盘后要刷新 | personal 也查 | numable docs af |
| G38 | richText 的 span 边界不许靠空格分隔 | personal 也查 | numable docs rcn |
| G39 | 秘密不许写进 data.* 沙盒 | personal 也查 | numable docs credentials |
| G40 | 凭证声明必须真被用 | personal 也查 | numable docs credentials |
| G41 | 循环体首节点读跨轮累积量 | personal 也查 | numable docs df |
| G42 | 桥方法必须真存在 | personal 也查 | numable docs bridge |
| G43 | banner.xbanner 必须是 v2 canvas 形态 | personal 也查 | numable docs layout |
| G44 | .df 末尾应有 resultFilter 限定输出 | personal 也查 | numable docs df |
| G45 | .xjob 的组合必须是三型之一 | personal 也查 | numable docs alerts |
| G46 | 组件的 jobs 声明必须指得到、传得进 | personal 也查 | numable docs xwidget |
| G47 | .xjob 的 i18n 只有四个槽,译文里的变量不能写丢 | personal 也查 | numable docs i18n |
| G48 | 文件引用必须指向包里存在的文件 | personal 也查 | numable docs layout |
| G49 | 桥 call 型方法的返回值是信封 | personal 也查 | numable docs bridge |
| G50 | 数字输入框别用 type="number",且要把全角归一 | personal 也查 | numable docs page |
| G51 | 用了表格取数,minEngine 要到 3 | personal 也查 | numable docs df |
| G52 | request / sleep 的参数要写成字符串 | personal 也查 | numable docs df |
| G53 | .xwidget 的默认参数不写中文 | personal 也查 | numable docs xwidget |
| G54 | 判语言只判中文,不判英文 | personal 也查 | numable docs i18n |
| G55 | H5 吸顶元素要贴在折叠栏下面 | personal 也查 | numable docs page |
G0 包结构与 JSON 语法
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 包根必须有 manifest.json,且能被解析 | E | 缺 manifest.json / manifest.json 解析失败;这个包装不上 | 用 numable init 生成骨架,或补回 manifest.json | |
| 所有 .json .rcn .df .af .xwidget .xbanner .xpage .xform .xmenu 必须是合法 JSON | E | 整个组件不渲染,日志干净、什么都不报 | 修掉语法错(常见:多余逗号、注释、单引号) |
G1 不写不会被加载的文件,不带测试夹具
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 不得出现 page.json、xWidget/template/ 与 **/actionFlow/ 目录、.flow.json 后缀 | E | 这些文件与目录不会被加载,写了像「没生效」 | RCN 放 rc/,流放 flow/,后缀用 .af(交互流)/.df(取数流) | |
| G1b | 包内不得含测试夹具(*.params.json、fixtures/ 目录) | E | 夹具被打进签名包,体积与内容都不该发出去 | 夹具移到 .numable/params/,它不会进包 |
| G1c | .xpage 节点不写 loading / error 字段 | W | 写了也不生效:App 不读这两个字段,loading / error 态恒由 App 内置渲染 | 删掉这两个字段 |
G2 manifest 必填字段与身份
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| id / version / title / category / domain / minEngine 必填 | E | manifest 缺字段 | 补齐这六个字段 | |
| 废弃字段 manifest.scheme 不得出现 | E | 写了也不生效 | 删掉该字段;深链直接写 numable:// |
|
| id 应为 26 位 ULID(system:true 的系统包免) | W | manifest.id 不是 ULID | 用 numable init 生成的 id |
G3 网络白名单不多不少
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 请求用到的 host 必须已写进 manifest.network | E | 设备上请求被网络守卫静默拦掉,组件恒空 | 把实际请求的域名补进 network | |
| manifest.network 里声明的 host 必须真有人用 | E(personal 档为 W) | 过度授权,安装面板会列出用不到的域名吓到用户 | 删掉没用上的域名 |
G4 取数流的跨端安全写法
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| G4b | concurrent 之后紧邻的节点不得引用并发分支的 id | E | 并发结果晚一拍才可见,读到的是空值 | 在并发与消费之间插一个 op:set 屏障节点 |
| G4c | parseDate 的 pattern 去掉时间 token 后不得剩字母 | E | Android/iOS 上整条解析失败返回 null,时间显示错乱 | 先用 subString 截出纯时间串再 parseDate |
| .df 里只能用取数白名单内的能力(封包 seal 检查) | E | 封包时被拒 | 界面类能力(ui.* / nav.* / xpage.*)搬到 .af |
G6 组件档位齐全
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 每包至少 3 张 .xwidget,必须有 22 档,且必须有 42 或 44 档 | E | 只有 N 个组件 / 没有 22 档 / 没有 42、44 档 | 补齐小号方形与大号档;个人自用不检查这条 |
G7 颜色与节点结构
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .rcn 里所有带 hex 的 *color 字段必须写成「浅|深」双分支(纯透明、纯黑遮罩除外) | E | 单色硬编码,换到另一种主题下看不清或整块消失 | 写成 "#FFFFFF|#15171A" 形式 | |
| G7b | img.scaleType 只能是 fitXY / fitStart / fitEnd / fitCenter / centerCrop | E | 拼错会被静默兜底成 centerCrop,图被裁 | 改成白名单里的写法 |
| G7c | RCN 的每个 cell(含 cells / react / children 里的)必须有非空 type | E | 整个组件渲染失败,编辑器里只显示一句「wasm 未就绪」 | 每个 cell 补 type;op 简写要写全;注释挪进 _note 字段 |
G8 多语言词表结构与引用
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| manifest.lang 必须是 BCP-47 语言标记 | E · personal 也查 | 语言判定失败 | 写成 zh-CN / en-US 这类形式 | |
| 内容词表必须是扁平对象,不得多套一层 values | E · personal 也查 | 取词恒空,屏上出现字面量 | 去掉外层包装 | |
| 被引用的词条 key 必须在必需语言里存在 | E | 该语言下文案显示成原始 key | 补齐译文;确实要留空的写进 i18nEmptyOk 声明 | |
| router.json 没有顶层词表;routes[].title 不得引 ${@i18n.},要用各语言旁挂字段 | E · personal 也查 | 页面标题渲成表达式原文 | 把标题写进 routes[].i18n 的各 locale 字段 | |
| routes[].title 里不得写表达式(${key} / $[方法::…]) | E · personal 也查 | 标题不求值,标题栏会原样露出这串表达式 | 写成纯文本;要多语言用这条路由的旁挂 i18n 表 | |
| 词表键须为 BCP-47、各语言 key 数量应一致、无过期与未使用的 key | W | 某语言缺词、留下无用词条 | 按提示补齐或删除;个人自用不检查这一组 | |
| G8b | 用户可见的键里出现中文硬编码 | W | 英文环境下显示中文 | 提取成 ${@i18n.key} |
G11 包图标
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 包内应有 logo.png | W | 商店与安装面板回落成首字母图标 | 放一张 512×512 的 logo.png | |
| G11b | logo 必须正方形、可解码;建议 512;不得自己烤圆角 | E | 透明圆角或不透明底会被宿主按边长 0.2237 再裁一次,边缘发白发黑 | 图案满幅铺到方形边缘,圆角交给宿主 |
G12 路由、流引用与参数编辑入口
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| numable://self/page/x 的目标必须在 router.json;numable://self/widget/ |
E | App 里弹「页面不存在」,或开出一个空的添加面板 | 补路由;跳首页写 numable://self | |
| G12b | H5 页调用的流必须存在,且后缀与调用方式匹配 | E | 调用返回 -6 flow not found | 取数用 .df + runDataFlow,副作用用 .af + runFlow |
| G12c | depends 的裸字符串绑定不得吞掉入参 | E | 入参变空,流仍报成功,组件静默渲成 -- | 写成 {"flow":"x","params":{"k":"${k}"}} |
| G12d | onEdit 的 af 形态只认包内 .af、禁 ..、文件必须存在;走路由的必须是裸路径且在 router.json | E | 点了没反应,或打开一个空白页且不报错 | 路径基准是 xWidget/,按实际文件名写 |
| G12e | onEdit 的 af 必须包含 widget.updateParams;走页面的目标页必须真有写回能力 | E | 编辑页打得开、按了不会有任何变化 | af 里补 widget.updateParams;xform 补 onSubmit,xpage 补 events |
G13 包体积上限
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 源态总字节不超过 3MB | E | 源态超过 3MB 上限 | 压缩或删减图片等资产;大图交给网络地址 | |
| 单个文件不超过 256KB | W | 流越大,每次取数越慢 | 拆成几条流,或把重复展开的表达式改成查表;大图交给网络地址 |
G14 封包格式(由封包 seal 检查)
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 打包产物的目录结构、清单与签名格式必须符合规范(由封包 seal 检查,不在 numable check 里) | E | 封包阶段被拒,或客户端验签失败装不上 | 不要手工改动打包产物;用发布工具链重新封包 |
G15 H5 输入框字号
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| H5 页的输入控件必须显式写 font-size,且不小于 16px | E | iOS 上聚焦输入框会把整页放大 | 给 input / textarea / select 写 font-size: 16px 起 |
G16 外部链接与 scheme
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| manifest.schemes 是废弃字段,出现即报错 | E | 写了不生效 | 删掉整个字段;跳外部应用由运行时统一弹首次确认 | |
| 不得使用 javascript / file / data / intent / about / blob 等危险 scheme | E | 会被拦截 | 改用 https 或 numable:// | |
| 第三方 scheme 的 URL(nav.open 的 url 与 events 绑定串)里,每一个拼进去的 ${} 都应包一层 urlEncode | W | 参数带特殊字符时跳转失败 | 写成 $[urlEncode::(${v})] | |
| nav.open 的 fallback 只能是 http(s) 地址 | E | 未装目标应用时无法回落 | fallback 填一个网页地址 |
G17 XPage 顶部让位
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .xpage 根节点的 paddingTop 必须引用 ${@contentInset.top} | E | 页面顶部被悬浮胶囊压住,而且不报错 | 根节点写 "paddingTop": "${@contentInset.top}";确有理由豁免时写 _notePad 说明 | |
| 根节点 layout 不得是 pager | E | 顶部让位无处安放 | 外面包一层 list 作根 |
G18 凭证声明
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| credentials 每项须是对象、必须有 id、id 不重复 | E | 凭证注入不生效 | 给每条凭证一个唯一 id | |
| type 只能是 bearer / token / header / query / jwt-assertion / oauth2 | E | 不在机制白名单 | 换成白名单内的机制 | |
| jwt-assertion 与 oauth2 必须带 preset,且 preset 须在内置表里 | E | 这两类机制只能用内置配置 | 需要新增站点走意见反馈提交 | |
| type=header 须写 headerName;type=query 须写 paramName;用 query 注入会提示改用请求头 | E | 凭证注入不到请求里,或出现在地址栏 | 能走请求头就走请求头 | |
| 必须显式写 hosts,且 hosts 是 manifest.network 的子集 | E | 凭证只随声明的域名发送,漏声明就发不出去 | hosts 写实际用凭证的域名,并确保它们都在 network 里 | |
| label 必须是各语言旁挂的非空字符串且中英双份;help 必须是 https 地址 | E | 绑定面板上没有说明文字 | 补 zh-CN 与 en-US 两份 label | |
| .xwidget.params 的键不得看起来像凭证(token / secret / password / api_key / 密钥 / 令牌) | E | 上架包禁止把密钥放进用户可编辑参数 | 改走 manifest.credentials | |
| G18b | request.credential 必须是字面量声明 id,不能写 ${};且该 id 必须已声明 | E | 运行时会静默按匿名请求发出去 | 直接写声明里的 id 字符串 |
| G18c | credential.state 的 id 必须是字面量、且已在 manifest.credentials 声明;测试夹具豁免须写 _note 理由 | E | 查的是一个不存在的凭证,永远拿到 unbound,界面一直叫用户去绑定 | 写成已声明的 id 字面量 |
G19 商店门面文案
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 必须有 manifest.subtitle | E | 商店里的包没有一句话介绍 | 写一句不超过 22 字的介绍 | |
| subtitle 建议不超过 22 字 | W | 商店里被截断 | 缩短 | |
| 必须有 i18n["en-US"] 的 title 与 subtitle(基准语言已是英文的免) | E | 英文环境下商店里显示中文 | 补英文标题与介绍 | |
| 自由文本的 category 需要英文覆盖;英文 subtitle 超过 42 字符会提示缩短 | W | 英文环境显示不全 | category 改用平台枚举值;缩短英文介绍 |
G20 组件的多语言标题
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .xwidget.i18n 必须是对象 | E · personal 也查 | 组件标题取不到译文 | 写成 {"en-US": {"title": "…", "sub": "…"}} | |
| 应有英文的 title 与 sub 覆盖;键须为 BCP-47;只认 title 与 sub 两个字段 | W | 英文环境下选择组件的列表里,组件标题会显示中文 | 补英文两项,删掉其它字段 |
G21 包名长度与元数据词表
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 每种语言下包名显示宽度不超过 24(全角算 2、半角算 1) | E | 包名太宽,宫格里两行也放不下 | 缩短 title | |
| 包名建议不超过 16 | W | 窄屏下容易被截断 | 再缩短一点 | |
| manifest.i18n 的键须为 BCP-47(W),每个 locale 的值须是对象(E,personal 也查) | E | 元数据译文整块失效 | 按 {"en-US": {…}} 的形状写 | |
| 元数据词表只认 title / subtitle / category / description;category 建议用平台枚举值 | W | 多余字段不会被读取 | 删掉其它字段 |
G22 点击要有即时反馈
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 组件 cell 事件绑定的 .af,首个 action 应是 ui.haptic,并且排在第一位 | W | 组件没有点击动画,点下去到画面变化之间约 250ms 全无反馈,用户会再点一次 | 把 {"action":"ui.haptic"} 放到 actions[0];确实不该震的场景可以不加 |
G23 首屏缓存
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| G23-C1 | 页面消费的 .df 里有请求,就应该配 data.get 读缓存(系统包免) | W | 首屏每次都白等一轮网络 | 做成「先渲缓存、后台校新」三态;确实不该缓存的写 _lintCache:"exempt" 并附 _note |
| 缓存豁免必须写明理由 | E | 无法判断是遗漏还是有意为之 | 在 _note 里写一句为什么 | |
| G23-C2 | 组件使用的 .df,缓存写回必须受 ${_cache} 开关控制,且默认关闭 | E · personal 也查 | 组件刷新链路整条空转,数据永远不更新 | 写回节点包在 _cache 判断里,页面传 1、组件不传 |
| G23-C3 | 根 depends 带缓存的 .xpage 必须有 events.onRefresh(先 data.remove 再 reloadPage) | E · personal 也查 | 下拉刷新等于没刷,还是那份缓存 | 补 onRefresh,顺序是先删缓存再重载页面 |
| G23-C4 | 写进缓存的值里不得含凭证 | E · personal 也查 | 密钥落进本地缓存 | 缓存前把凭证字段剔掉 |
| G23-C5 | 缓存键不得含下标;建议加 c. 前缀 | W | 顺序一变,就把 A 的缓存渲给了 B | 键里用对象自身的稳定标识,不用位置 |
G24 添加组件入口只留一个
这组检查挂在商店门面那一段里,个人自用档不跑。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 单个文件里的添加组件的调用(pickWidgets( / "widget.pick")不应超过 2 处 | W | 像是手抄了一份组件目录 | 收成一个按钮,由系统面板去列出组件 | |
| 不要用桥上不存在的 addWidget(它已被删除) | W · personal 也查 | 写了会抛 TypeError,而这类调用几乎总被页面自己的 try/catch 吞掉 | 改用 pickWidgets(items) | |
| 添加组件的页面不要出现「已添加」字样 | W | 接口确认不了用户到底加没加,文案会撒谎 | 改成「已提交」或干脆不提示 |
G25 凭证绑定要有直达入口
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 有 required:true 的凭证时,page/ 里必须有 numable://app/mine?section=credentials 的直达入口 | E | 用户被要求「先加组件再自己去找入口」 | 在引导页写编号步骤,并放一个直达按钮 | |
| page/ 里不得出现「添加下面任意一个组件,按组件上提示连接」这类绕路引导 | E | 用户先往仪表盘加一个注定空着的组件,再去组件上找入口,比直达多两步 | 改成直达 numable://app/mine?section=credentials 的按钮 |
G26 取数失败必须让流失败
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| xWidget 下的 .df 只要有 request 或 concurrent,就必须有 action:"error" 出口 | E | 取数失败时流仍然报成功,空数据会覆盖掉上一份好数据 | 主干字段判空后抛 error;「集合为空」属于正常成功,不要当失败 |
G27 添加组件按钮统一版式
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 有添加组件的调用就必须有 .pickb 按钮,并带上统一的 xb:addbtn 基准 CSS 块 | W | 版式与基准不符,各包按钮长得不一样 | 用配套脚本生成基准样式后原样贴入 | |
| p-addbtn*.rcn 的版式必须与基准一致;XPage 里的按钮节点高度必须是 56pt | W | 按钮高矮不一 | 按基准重新生成 |
G28 表达式笔误与渲染取值域
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 不得出现重复单位后缀,如 14.0ptpt | E | 整个组件渲不出来,而且不报错 | 一个数值只带一个 pt | |
| $[...] 里不得再嵌一层 $[ | E | 整个组件渲不出来 | 内层写裸方法名,如 if::(...) | |
| .rcn 里的 ${x} 必须来自这个组件 depends 的 .df 输出(resultFilter 的键);外壳参数不在渲染取值域里 | E | 那一处渲成空,或恒走兜底值 | 参数经 depends.params 传进 .df,在流里落地后由 resultFilter 透出,再在组件的画面里取 |
G29 点击不写成 click 串
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .rcn 的 click 字符串里不得出现 @[file:// | E | 点击行为不可控 | 改用 events.* 事件绑定 |
G30 字形可渲染与条件键写法
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 流里 op:if 的条件键必须是 props.val | E | 条件恒假、分支体静默不执行,而流仍然报成功 | 把条件写进 props.val | |
| RCN 能渲到的文本里禁用 emoji(码点 ≥ U+1F000) | E | 字形管线渲不出 emoji,那一处留下静默空位 | 改用包内图片资产,或基本平面内的符号如 ★ ✓ ✕ › |
G31 方法名必须在 VParser 白名单里
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .rcn/.df/.af/.xpage/.xform/.xbanner 里的 name::(…) 必须是 VParser 的 43 个方法之一 | E | 未知方法静默求空:不报错、不进日志,那一格渲成空白或恒走兜底分支(踩过 abs:: 与 push::) | 查 numable docs methods 的方法表;绝对值写 if::(ge::(v,0),v,calc::(0-v)),没有 abs/avg/filter/groupBy/indexOf/push |
G32 尺寸字段里不许有 $[方法]
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .rcn 的 x/y/w/h/fontSize/lineWidth/maxWidth/maxHeight 值里不得出现 $[ | E | 方法在这个位置不保证求值,求空就成了裸 "pt" → StaticCanvas 布局失败 = 整个组件渲不出且不报错 | 先 op:set 落地成变量(值上套 findNotEmpty 兜底),再写 "y":"${_v}pt" |
G33 布局锚点不许进方法参数
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| $[…] 的参数串里不得出现 {parent.x} / { |
E | 布局锚点与方法求值是两个域,方法里取不到它 → 该节点静默不画,不报错 | 改写成纯布局表达式(布局表达式里可以内插 ${}):"w": "({parent.w}-134pt)*${pct}/100" |
G34 resultFilter 不许透出保留键 lang / theme
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .df 的 resultFilter.keys 里不得有 lang 或 theme | E | 渲染层按 {...data, theme, lang} 注入、后写的赢:RCN 里拿到的是宿主的语言码/明暗档而不是业务值,流仍 success、组件照常渲染 | 换个键名(langCode / themeName 之类) |
G35 XPage 节点层的 ${@i18n.k} 必须在页表里查得到
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .xpage 的节点 params / props.text / menu[].label 里引用的 @i18n key,必须在该 .xpage 顶层 i18n 表里存在 | E | 节点层的 @i18n 只查本页页表,查不到就求值成空串 —— 不报错、屏上一片空白,像是作者忘了写文案 | 把文案补进 .xpage 顶层 i18n 表(每个门一份),或挪进对应 .rcn 的 rc.i18n(canvas.source 内部不受本闸管) |
G36 事件值不许以 ${ 开头
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .xwidget/.rcn/.xpage 的 events.* / click / onClick 字符串不得以 ${ 开头 | E | 派发层先按首字符分类再插值(/=路由、numable://=深链、@[file://=flow),以插值开头的串哪一类都不像 → 被当成 flow 名去找,点了没反应且不报错 | 把能确定的前缀写在插值外面:"/zone?id=${id}" / "https://${host}/x" |
G37 写盘的 .af 落盘后要刷新
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 含 data.set / data.merge / data.remove 的 .af,若某个组件的取数流用 data.get 读了被写的键,就应有 widget.refresh 或 widget.updateParams(带 xpage.reloadPage 的页级刷新链豁免) | W | 落盘之后仪表盘那个组件 reload 的第一步是 cacheGet,命中的是旧数据渲的那张位图 —— 用户填完设置返回,组件纹丝不动,要等下一个 interval 才自愈 | 在所有写盘 action 之后补一个 widget.refresh(scope 按影响面选 self / widget / bundle);刻意不刷的写 _lintRefresh:"exempt" 并附 _note 理由 | |
| 写盘后不刷新的豁免必须写明理由 | E | 无法判断是遗漏还是有意为之 | 在 _note 里写一句为什么(与 _lintCache 同规) |
G38 richText 的 span 边界不许靠空格分隔
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| richText 的 spans 之间不能靠空格分隔 —— 无论写在前一个 span 的尾部、后一个 span 的头部,还是藏在词表值里 | E | 两段贴在一起,例如「1842」和「Clicks」渲成「1842Clicks」;普通 txt 里的同一个空格却好好的,所以看着像随机 | 改用一个独立的间距 span:文本写 ${@i18n.nbsp}、词表值写 U+00A0(不换行空格);那个 span 的字号就是间距宽度,数字与单位的字号对比一点不用动 | |
| 间距 span 的文本求值后不能是普通空白 | E | 整段是普通空白的 span 在渲染时会被整个丢掉,等于没写 | 让它落到 U+00A0(不换行空格)上 |
G39 秘密不许写进 data.* 沙盒
判据一句话:泄露即造成账号风险的值 = 秘密,只住凭证中心;其余(用户名、仓库名、地区、开关)= 配置,住参数或 data.*。本条只看字面键名(${ 之前那截)与字面值,算出来的键静态看不出是什么。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| .df / .af 里的 data.set / data.merge,键名形如凭证(token / secret / password / api_key / access_key / private_key / credential / authorization / bearer / pat)一律禁止 | E | 沙盒是磁盘上的明文,工作台的调试数据面板里直接看得见,这个包的任何流都读得到因而能把它发出去;而且它会随本地备份文件一起被拷走 —— 用户备份一次,那把钥匙就明文躺在备份里 | 改走正门:manifest 里声明一条 credentials,请求上写 "credential": "<声明 id>",值由宿主在网络边界注入,流里结构性读不到它 | |
| 键名看着正常,但写进去的值形如凭证(值里含 token / authorization / secret / password / api_key / bearer / credential)同样禁止 | E | 现象与上一条完全一样 —— 换个不起眼的键名藏起来,并不会让这把钥匙变安全 | 同样改走 credentials 声明。本条没有豁免开关:确属误报就改字段名避开这些词,而不是把一条泄密路径注释掉 |
G40 凭证声明必须真被用
包只能查自己声明过的凭证,所以「声明了却从不真用」就是探测用户身份的唯一形态:声明一批 declId(github / cloudflare / stripe…),只问「绑没绑」,再把答案回传自己的服务器。拦住这条,画像这条路就没了。判据分两档,看的是被谁引用而不是有没有引用。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| 声明了一条 credentials,有 credential.state 查它,却没有任何请求写 "credential": "<声明 id>" | E | 这正是探测用户身份的形态 —— 装了这个包的人,他绑没绑 GitHub、绑没绑 Cloudflare,包一问便知,而它一次都不打算真用这些凭证 | 真要用它就在请求上写 credential:"<声明 id>";只是想留一个披露或绑定的靶子,就把那处 credential.state 去掉 | |
| 声明了一条 credentials,但既没有请求用它、也没有 credential.state 查它 | W | 安装面板会白白多披露一条「此包请求使用你的 X 凭证」,而它一次都用不上 —— 用户为一个不存在的用途多担一份心 | 用不上就把这条声明删掉。刻意留作披露或绑定靶子(测试包)的可以忽略本条 —— 它只是提醒,不拦发布 |
G41 循环体首节点读跨轮累积量
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| forEach / for 的循环体第一个节点,不得引用本循环体内会被 set 的键 | W | 每轮第一个节点读到的是「进入循环那一刻」的快照,不是上一轮写完的值 —— 守卫旗标全程失效,「只挑第一个」变成挑最后一个;流仍报成功、check 0 error、画面正常,只有数字是错的 | 在循环体首行插一个屏障节点,如 {"op":"set","props":{"key":"_lb","value":"1"}} |
G42 桥方法必须真存在
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| html 页只调「方法全表」里的桥方法,且没有对该名字的存在性判断 | E | 页面抛 TypeError,而这类调用几乎总被自己的 try/catch 吞掉 —— 不崩、不报错、日志干净,只是那个功能安静地不存在 | 对着 numable docs bridge 的方法全表核名字 |
|
带 if (xbridge.x) / typeof 守卫地引用一个全表里没有的名字 |
W | 不会崩,但那条回落分支从此永远生效 —— 看着像在走桥,其实一直走的是兜底路径 | 改成全表里的名字;桥没有对应能力就把那条假分支删掉,别留一个永远走不到的桥调用 |
G43 banner.xbanner 必须是 v2 canvas 形态
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
banner.xbanner 须为 { version:2, canvas:{source,depends[,refresh]} },不得再有顶层 rcn / flow |
E | App 只读 canvas,顶层 rcn / flow 写法的横幅被当作没有、换成默认模板,不报错、日志干净 |
把 rcn.rc 挪进 canvas.source,取数改成 canvas.depends 引用 .df,写法见 numable docs layout |
|
canvas.source 是 @[file://…rcn] 引用或内联 {cells,…} 对象;canvas.depends 是数组 |
E | 横幅整块不显示,或取数流一次都不跑 | 引用写包根路径(xWidget/rc/…),depends 与 .xwidget 同一种写法 |
G44 .df 末尾应有 resultFilter 限定输出
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
.df 的 actions 里没有任何 resultFilter |
W | 流的输出 = 整个数据域:入参(含宿主注入的 @i18n/@app)与循环里的临时量一并吐给渲染层、随缓存落盘。实证过的一种失效:泄回的空 @i18n 曾把 RCN 自带词表整个挡住,组件上所有 ${@i18n.*} 空白、零报错 |
末尾加一条 resultFilter,keys 只列渲染真正要读的键 |
G45 .xjob 的组合必须是三型之一
.xjob 没有 type 字段,型是组合判出来的:有 alert 没有 task.depends = 静态提醒;有 alert 也有 depends = 动态提醒;没有 alert = 后台任务。所以这一条查的就是「三种组合之外的写法」。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| G45.shape | id 只能是 [a-z0-9-](缺省 = 文件名),title 必填且不能是空串;顶层只认 version / id / title / sub / i18n / params / form / events / task / alert(_note 前缀放行),旧写法里的 type、顶层 level / cooldown 都算未知键 |
E | 实例按「包 + 规则 id + 参数」认身份,id 换一种拼法就成了另一条规则(旧的那条还在响);没有 title 时同意面板、提醒管理页、长按组件的菜单都没有名字可显示 |
id 用小写字母、数字和连字符;title 写一句用户看得懂的规则名 |
| G45.location / G45.id-filename | .xjob 必须直接放在 xJob/ 下(不能进子目录);写了 id 就必须与文件名一致(缺省 = 文件名) |
E | 客户端按 xJob/<id>.xjob 一层枚举:放进子目录的那份文件在包里、规则却永远不存在,装上之后一条提醒也加不出来;id 与文件名不一致时,组件的 jobs[].id 与 alert.add 按文件名找得到它,实例却按 id 建 —— 改个名字,用户已经加过的那条提醒再也匹配不上 |
把文件放回 xJob/ 一层;id 要么不写,要么与文件名逐字相同 |
| G45.task | 每个 .xjob 都要有 task,task.refresh 必填且不能是空对象;depends 写了就必须是数组,每个槽与 then 都要有 flow;task 只认 depends / then / refresh |
E | 没有节律的 Job 永远不会被评估 —— 装上了、管理页里有它,就是一次也不响;没有 flow 的槽什么也不取,合并出来的数据里自然没有它那份 |
补 task.refresh:静态写 at(每日时点),动态写 interval(窗口或裸秒) |
| G45.depends-slot | task.depends 的每一槽都是 { flow, params? } 对象 —— 裸字符串不是简写 |
E | 平台取这一槽时读的是 flow 字段,给个裸串就读不到:那一槽静默不取数,合并出来的数据里没有它那一份,而流照样报成功、日志干净 |
写成 { "flow": "@[file://xWidget/flow/x.df]" },要传参再加 params |
| G45.kind | task 与 alert 不能都没有 |
E | 这个文件什么也不做 | 要通知就写 alert.message,只想在后台写状态就写 task.then |
| G45.depends-only | 没有 task.depends 时不许写 task.then / alert.activeCondition / task.refresh.cooldown |
E | 静态提醒布防时根本不取数,这三样没有数据可判、可写、可命中 | 真要按数据判就加 task.depends(可以直接引组件已有的 .df),否则把这几个键删掉 |
| G45.alert-only | 没有 alert(= 后台任务)时不许写 params / form / events |
E | 后台任务是包级单实例:没有同意面板字段、没有用户参数、没有点击 | 要用户填参数就说明它该是提醒,补 alert.message |
| G45.task-then | 没有 alert(= 后台任务)时 task.then 必填 |
E | 不写状态的任务跑完什么也没留下 —— 每次都白取一轮数 | 在 task.then 的 .df 里写 data.*;真不需要写状态就说明它该是提醒,补 alert.message |
| G45.alert-keys | alert 只认 message / activeCondition / level,level 只能是 quiet / normal / urgent |
E | 多写的键不会被读到;level 写了个认不出的值会被当成默认档,作者以为自己调过了 |
文案写进 alert.message.{title,body},强度写 alert.level(三档之一) |
| G45.alert-message | alert.message.title 必填且不能是空串 |
E | 提醒没有标题,响了也是一条空通知 | 写 alert.message.title(body 可选),译文放 i18n[语言].message |
| G45.form-keys | form 的键必须是 params 里已有的键;params 的值恒字符串 |
E | 同意面板的字段区只渲 params 的键,多出来的那个字段不会出现;非字符串的值会让实例哈希不稳,同一条规则被当成两个实例 |
先在 params 里给默认值(数字也写成串,如 "08:00" / "3"),再在 form 里描述它的输入形态 |
| G45.hit | 有 task.depends 时,必须能判出真假:写 task.then 或 alert.activeCondition,或让 depends 的 .df 透出 hit |
E | 省略 activeCondition 等于写 $[eq::(${hit},1)];读不到 hit 的判定恒为未知,这条提醒永远不会响,而日志干净 |
在 .df 的 resultFilter 里透出 hit,或显式写一条 alert.activeCondition |
| G45.message-scope | 没有 depends 的提醒,alert.message 只能引 params 与 @app |
E | 布防那一刻只有这两样,别的键渲出来是空 —— 通知已经发出去了,事后无从复现 | 文案里只用用户填过的 params;要引数据就把它做成动态提醒(加 task.depends) |
| G45.days / G45.at-param | refresh.days 与 refresh.at / refresh.interval 里的 ${参数} 都只属于没有 depends 的静态提醒;.xwidget 的 canvas.refresh 也不认 days(cooldown 只是无效) |
E | 动态 Job 的评估节律归宿主,按星期几过滤、拿参数当时刻都只对系统定时有意义,写了不生效 | 要按星期几或用户填的时刻触发,就做成静态提醒(不写 depends) |
| G45.once | 日期式 at(YYYY-MM-DD HH:MM,只响一次)只能写在没有 depends 的静态提醒里;同一条 at 里不能与每日式 HH:MM 混写,不能再配 interval / days;不含 ${} 的字面日期必须合法(一个半角空格、24 小时制、全部补零、日期真实存在)。模板首尾去空白后中间含空白即算日期式,纯 ${when} 看不出、不查 |
E | 这几种写法运行时都不报错:日期式照排、每日式照每天响、days 被忽略 —— 作者得到的一定不是想要的;写死一个 2 月 30 日则那一项永远排不出来,提醒永远不响 |
只响一次的提醒单独一条规则:"at": ["${date} ${time}"],form 里 date 用 datePicker(format: "YYYY-MM-DD")、time 用 timePicker;要每天响的另写一条 |
| G45.recipe | task.then 与判定流程 {flow, params} 二选一,也可写运行时判定配方 {recipe, …}:只有 cross(越线:value + line,dir 缺省 below)与 changed(数值变化:keys 1~4 个)两种;字段整串 ${键} 或字面值;oncePerDay / fireOnFirst 为布尔;配方只用于有 alert 的提醒 |
E | 配方与 flow 同写时 flow 不会跑;写错配方名、缺字段或多了未知键,宿主判不出触发,这条提醒永远不响;后台任务写配方什么也不会写进 data.* | 越线写 {"recipe":"cross","value":"${px}","line":"${price}","dir":"${dir}"};数值变化写 {"recipe":"changed","keys":["${ver}"]};「超出阈值」不用配方,直接写 alert.activeCondition + refresh.cooldown;后台任务仍用写入流程 |
| G45.all-urgent | 一个包里不该所有提醒都标 alert.level: "urgent"(只在 publish 档查) |
W · 仅 publish | 分档是留给「真的不能等」的那一条的;一包全 urgent 等于没有分档,用户多半把这个包的通知整体关掉 —— 那时真急的那条也一起哑了 | 只给最该打断人的那条留 urgent,其余写 normal,纯信息的写 quiet |
| G45.min-engine | 包里有 xJob/,或 .af 里用了 alert.add / alert.skip、H5 里调了 xbridge.alertAdd 时,manifest.minEngine 必须 ≥ 2;用了日期式 at(一次性提醒)、.af 里的 alert.remove 或 H5 里的 xbridge.alertRemove 时必须 ≥ 3;task.then 写了运行时判定配方(recipe)时必须 ≥ 4(比的是能力引入时的轴,不随当前轴上涨) |
E | 老版本客户端遇到 xJob/ 只是不响,但 .af 里的 alert.* 对它是未知 action —— 整条流报错,用户点一下什么都不发生;日期式 at 在引擎轴 3 之前的应用里解析失败、那一项静默丢掉 —— 装得上、就是不响;引擎轴 4 之前的应用不认识判定配方,每轮判为未知 —— 同样装得上、永远不响 |
把 manifest.minEngine 写成能力要求的轴(2、3 或 4)或更高,老客户端就不会装到这个包 |
G46 组件的 jobs 声明必须指得到、传得进
.xwidget 的 jobs 是「长按组件 → 添加提醒」那个入口:列出本包里可以基于这个组件创建的 .xjob,并把组件实例的参数映射进去预填。 alert.add / xbridge.alertAdd 按 id 引用 .xjob 也归这一组。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| G46.id / G46.shape | jobs 是数组,每条是带 id 的对象,且 xJob/<id>.xjob 真实存在 |
E | 长按组件的「添加提醒」会列出一条打不开的规则 | id 写本包 xJob/ 下那个文件的名字(不带后缀) |
| G46.params-key | 映射的键必须是那个 .xjob 的 params 键 |
E | 映射进去的值没有接收者,同意面板上不会出现,用户还得自己再填一遍 | 对着那个 .xjob 的 params 表核键名 |
| G46.params-ref | 映射的值只能引组件自己的 ${参数} 或写字面量 |
E | 取数输出是结果不是身份,长按那一刻它还不存在 —— 映射过去是空值 | 映射组件 params 里的身份键(如 ${secid}),需要固定值就直接写字面量 |
| G46.add-id | alert.add / xbridge.alertAdd 里写成字面量的 id 必须是本包 xJob/ 下的提醒(有 alert 块) |
E | 点下去同意面板不开,这一步直接失败(rule_not_found)—— 不会回 cancel,但不点到那一下就看不见 |
id 写本包 xJob/ 下那个提醒文件的名字(不带后缀);后台任务不能由用户添加 |
| G46.task-ref | 引用的那条 .xjob 必须是提醒(有 alert 块),不能是后台任务 |
W | 后台任务是包级单实例、没有同意面板、没有用户参数 —— 长按组件的「添加提醒」按「有没有 alert」过滤,这一条根本不会出现在菜单里,而声明看上去是好的 |
改引一条真提醒;这条规则本就该能被用户按参数创建的话,给它补上 alert.message |
G47 .xjob 的 i18n 只有四个槽,译文里的变量不能写丢
.xjob 的 i18n 是一层旁挂表:i18n[语言] = { title, sub, message, form },裸字段是基准语言。message 的译文里直接写 ${},变量摆在哪由每种语言自己决定。
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| G47.slots | i18n[语言] 只认 title / sub / message / form 四个槽 |
W · 仅 publish | 四槽之外的键不会被读到,等于白写 | 字段区的标签写进 form.<键>.title,其余文案归 title / sub / message |
| G47.message | 译文 message 的 ${} 引用集必须与基准语言逐字段相同 |
E | 翻译时把变量写丢或写错,那一处渲出来是空 —— 而通知已经发出去了,事后无从复现 | 对照基准语言核一遍变量名;只翻一半(只覆盖 title)是允许的,覆盖了就必须带全那一行的变量 |
| G20(扩到 .xjob) | title / sub 有基准语言值时应有 i18n["en-US"] 译文 |
W · 仅 publish | en 环境下提醒管理页与同意面板这一处显示基准语言 | 补 i18n["en-US"].title(与组件标题同规) |
G48 文件引用必须指向包里存在的文件
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
@[file://…] 引用(.xwidget 的 canvas.source / canvas.depends / 事件绑定,.rcn / 页面里的 flow 绑定等)指向的文件必须存在 |
E | 组件整个加载失败,或点了没反应;numable run 报「引用了…但这个文件不存在」 |
改了 .df / .rcn / .af 的文件名,就把所有引用它的地方一起改掉(克隆 starter 后改名最容易漏) |
G49 桥 call 型方法的返回值是信封
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
H5 页里 call 型桥方法(runDataFlow / getData / confirm / appInfo / credentialState …)的返回值不能直接当数据用:结果在 .data,先判 code === 0 |
W | 字段全是 undefined;if (await xbridge.confirm(…)) 恒为真,用户点了「取消」照样往下执行 —— 都不报错 |
写一个 call() 拆信封(code !== 0 抛错,否则返回 data),所有 call 型都经它,见 numable docs bridge |
|
xbridge.confirm / xbridge.alert 的参数是 (标题, 正文, 选项) 三个位置参数,不能传一个对象 |
W | 对象被当成标题:对话框标题显示 [object Object] 或空白,正文为空,按钮文案不生效 | 写成 xbridge.confirm("标题", "正文", { okText, cancelText, destructive }) |
G50 数字输入框别用 type="number",且要把全角归一
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
H5 页里不用 type="number" 的输入框(标记、JS 拼出来的、.type = "number" 都算) |
W | 中文键盘打出全角「。」(如 112。4)时 .value 被净化成空串,页面以为没填 —— 填的数静默丢失,不报错 |
改成 type="text" + inputmode="decimal"(整数用 numeric),键盘不变 |
|
有 inputmode="decimal" / "numeric" 的输入框时,页面要把全角数字和 。., 归一成半角 |
W | parseFloat("12。5") 静默得 12,"12.5" 得 NaN —— 错的数看着照样像个数 |
写一个 normNum()(全角数字减 0xFEE0,。.,、 换成 .),在 document 级 input(跳过 isComposing)与 compositionend 上归一,读取处也过一遍 |
G51 用了表格取数,minEngine 要到 3
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
包里任何 .df / .af / .rcn / 页面用了 request 的 formatType: "tsv" / "csv"(字面量,大小写不敏感),或调了 mapField / groupSum / convertSum,manifest.minEngine 的主版本必须 ≥ 3 |
E | 老版本 App 不认 tsv / csv,把表格原文当普通文本返回(iOS 上是空);三个方法在老版本上不存在、静默求空 —— 都不报错,装得上、跑得动,销量恒 0 或空白 | manifest 写 "minEngine": "3.0.0",老版本 App 装包时会提示先升级 |
G52 request / sleep 的参数要写成字符串
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
request 的 timeout、queryParams / header 里的每个值,以及 sleep 的 timestamp,不能是 JSON 数字 / 布尔 / 对象,也不能整串就是一个求出来是数字的方法调用($[calc::(…)]、$[floor::(…)] 这类);${key} 引用静态看不出类型,不查 |
E | 鸿蒙上这一步「参数解析错误」直接不执行(queryParams / header 在 iOS 上也一样),组件恒失败;而网页引擎、CLI 与渲染台都照跑,本地全绿 |
写成字符串:"timeout": "8000"、"timestamp": "1500";算出来的数外面套 parseNumber::(…,0)(出来是字符串),例如 "$[parseNumber::(calc::(5050-${el}),0)]" |
G53 .xwidget 的默认参数不写中文
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
.xwidget 的 params 里任何字符串值含中日韩文字(递归数组与对象;_note 不算) |
W | params 是组件的默认参数,非中文界面的用户拿到的就是这份默认值:标题直接显示中文,城市、节日这类默认值落在中国的那一个上 |
默认参数不写自然语言:写 "" 或 "auto",在 .df 里按国内默认判据(${@app.region} 是 cn,或为空且 ${@app.language} 以 zh 开头)兜底;标题类在 RCN 里用 ${@i18n.*} |
G54 判语言只判中文,不判英文
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
表达式里出现 startsWith::(${@app.language},en)(或 en-…;@app.locale / @device.language 同);_note 不算 |
W | 判的是「是不是英文」,于是日语、德语等系统掉进中文分支 —— 非中文用户看到中文 | 语言判据只有一条:zh 开头 → 中文,其余(含空)一律英文。写 $[startsWith::(${@app.language},zh)] |
G55 H5 吸顶元素要贴在折叠栏下面
| 子项 | 检查什么 | 级别 | 现象 | 修法 |
|---|---|---|---|---|
| page/html 下同一个 CSS 声明块或 style 属性里写了 position: sticky,同时 top 是 0 或 var(--xb-content-top) | W | 页面滚过头部后,容器顶部淡入一条小标题栏;top: 0 的工具条钻到栏底下被盖住,var(--xb-content-top) 的与栏之间留一道缝 | 改成 top: var(--xb-bar-bottom),本地预览在 :root 里写一份 --xb-bar-bottom: 0px 兜底 |