
Wxa Skills Generate
- 41 installs
- 169 repo stars
- Updated July 28, 2026
- wechat-miniprogram/ai-mode-skills
Helps with ai & agent building tasks during AI-assisted development.
About
wxa-skills-generate is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- wxa-skills-generate
- AI & Agent Building
- AI-coding skill
Wxa Skills Generate by the numbers
- 41 all-time installs (skills.sh)
- +3 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #8,104 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wechat-miniprogram/ai-mode-skills --skill wxa-skills-generateAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41 |
|---|---|
| repo stars | ★ 169 |
| Last updated | July 28, 2026 |
| Repository | wechat-miniprogram/ai-mode-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
wxa-skill-generate
从小程序源码生成符合 wx.modelContext 规范的技能分包(skills/):分析源码 → 识别业务 → 提取接口与 JSAPI → 设计原子接口 → 生成代码 → 集成配置 → 交棒校验。
职责边界
- ✅ 本 skill 做:源码分析、原子接口设计、代码生成、
app.json/project.config.json集成 - ❌ 本 skill 不做:静态校验、真机执行、渲染验证(这些全部由
wxa-skills-validate负责) - 📦 交付:
skills/{skill-name}/(含mcp.json、SKILL.md、index.js、原子接口实现文件、工具模块、组件目录)+ 配置文件更新
依赖
- 可读的源码目录(仅给 appid / URL / 截图 → 触发阻断)
- 本 skill 主体不执行代码,只生成代码;但运行时探测(probe)阶段需要:
scripts/probe.mjs+scripts/probe-lib.mjs(与本 skill 同目录)miniprogram-automator:阶段 3.6 环境检查时由模型安装到 skill 的scripts/目录(cd <skill-path>/scripts && npm install miniprogram-automator),禁止安装到小程序源项目- 微信开发者工具 CLI 已安装且「服务端口」已开启(支持
WX_CLI_PATH环境变量 / 自动检测) - probe 为强制触发:阶段 3 标记
requiresRuntimeProbe: true或命中 T1~T6 时必须执行 probe,禁止跳过(详见阶段 3.6 与references/RUNTIME_PROBE.md),通过evaluate覆写wx.request同时捕获请求参数与响应数据
术语约定
- 原子接口:对外暴露给小程序 AI 的可调用能力。约定路径
skills/{skill}/apis/{name}.js(validator 也兼容tools/services//tools/) - 原子组件:用于渲染原子接口返回数据的 UI。强约束路径
skills/{skill}/components/{name}/(与mcp.json._meta.ui.componentPath严格相等) - 压缩代码:单行超 500 字符、变量名单字符的产物(含混淆)
参考资料索引
| 文件 | 用途 | 加载时机 |
|---|---|---|
references/ANALYSIS_PATTERNS.md | 业务流程识别、接口调用、JSAPI 匹配的正则模式 | 阶段 1/2/3 扫描源码时 |
references/JSAPI_WHITELIST.md | wx API 白名单完整清单(接口侧 / 组件侧 / 不可迁移),SKILL.md C 节只列高频项 | 阶段 1 鉴权扫描 / 阶段 3 JSAPI 提取 / 阶段 5 代码生成时(C 节高频清单未覆盖目标 API 时必查) |
references/CODE_TEMPLATES.md | index.js / 工具模块 / 接口实现 / mcp.json / SKILL.md / 配置的代码模板 | 阶段 5 代码生成 |
references/COMPONENT_TEMPLATES.md | 原子组件模板(列表/详情/状态) | 阶段 5 组件生成 |
references/ATOMIC_COMPONENT_DESIGN.md | 原子组件设计规范(尺寸档位 / 主题 / 边距 / 字体 / 布局 / 操作区) | 阶段 5 组件生成(强制前置,优先级最高) |
references/ATOMIC_COMPONENT_CSS.md | 原子组件 WXSS 实现规范(容器约束、单位换算、省略规范、禁用清单) | 阶段 5 样式编写 |
references/STYLE_MIGRATION.md | 源样式提取 + 字段映射的完整工作流 | 阶段 5 组件生成前(强制前置) |
references/HALF_SCREEN.md | 半屏页面(viewCtx.openDetailPage)API、上行消息、禁用接口/组件清单 | 按需——仅当业务确有"详情 / 补充信息"语义时(默认不生成) |
references/RUNTIME_PROBE.md | 运行时探测(automator probe)触发条件、SOP、失败兜底、结果接入。命中 T1~T6 时必须执行,不可跳过 | 阶段 3.6——命中触发条件时强制执行 |
---
硬性约束
A. 独立分包禁止项(必须改写)
| 禁止项 | 正确做法 |
|---|---|
getApp() | 分包内自行管理状态(模块变量 / wx.storage) |
require('../../xxx') 引用主包/兄弟分包 / import ... from '@/' | 把依赖完整拷贝到当前分包:单 skill 私有放 {skill}/utils/,多 skill 复用放 skills/_shared/ |
依赖主包 wx.cloud.init() | utils/util.js 中 ensureCloudInit() 自行初始化 |
依赖主包 app.js 初始化 storage | utils/util.js 中 ensureStorageInit() 自行初始化 |
从 getApp().globalData 读配置 | baseUrl / env 硬编码在分包 utils/util.js |
| 依赖主包登录态 | 每次执行接口前 ensureLogin() 主动走一遍登录流程 |
| 使用主包注册的全局组件 | 在分包 JSON 中重新声明 usingComponents |
B. 直接终止生成的阻断规则
出现以下任一情况,立即终止生成并告知用户:
| 阻断情况 | 检测时机 | 告知文案 |
|---|---|---|
依赖小程序插件(plugin:// / requirePlugin / app.json 的 plugins) | 阶段 1/3 | "该功能依赖小程序插件,当前暂不支持自动生成,需手动接入" |
| 用户声明的能力在源码中找不到任何对应接口或页面 | 阶段 3 | "未能在源码中定位到 <能力名>,无法生成,请确认能力名称或补充源码" |
| 未提供可读的源码目录(只给 appid / URL / 截图) | 阶段 1 前 | "请提供小程序完整源码目录,当前无法基于非源码资产生成" |
| 所有候选实现都依赖非白名单 JSAPI 且无替代方案 | 阶段 3 | "该能力依赖非白名单 JSAPI(如 <api>),无法自动生成" |
app.json 缺 "lazyCodeLoading": "requiredComponents" 配置 | 阶段 1 | "项目 app.json 顶层缺少 \"lazyCodeLoading\": \"requiredComponents\",否则独立分包内的原子接口被小程序 AI 路由调用时无法正确加载执行。请在 app.json 顶层添加该字段后重新触发生成" |
| 静态分析 + 运行时探测 + 离线兜底三者全部失败 | 阶段 3 | "接口 <api> 无法通过静态分析、运行时探测、离线抓包任何一种方式获取真实接口信息,无法生成" |
C. wx API 白名单(每次生成必须对照)
阶段 1 鉴权扫描、阶段 3 JSAPI 提取、阶段 5 代码生成时必须对照白名单。源码用到清单之外的 JSAPI → 按"不可迁移 JSAPI"处理。
>
完整清单(接口侧 / 组件侧 / 不可迁移)见 `references/JSAPI_WHITELIST.md`。下文 C.1 / C.2 / C.4 仅列高频条目,覆盖业务时必查 reference 完整列表,不要凭印象。
C.1 接口侧白名单(高频,完整清单见 references/JSAPI_WHITELIST.md §1)
"接口侧"指通过wx.modelContext.registerAPI()注册的处理函数及其依赖的纯 JS 模块——常规放在<skill>/apis/(也可放tools/services//tools/,validator 会按这三个候选目录解析),引用的工具模块目录名(如utils//services//helpers// 自定义名)不限。作用域以"是否在原子接口处理函数链路上"判定,不以目录名判定。
| 分类 | 高频接口 |
|---|---|
| 小程序 AI | wx.modelContext.registerAPI、wx.modelContext.createSkill(返回 { use, registerAPI })、wx.modelContext.expireAllCards、wx.modelContext.getSessionId(获取会话 ID) |
| 登录 | wx.login、wx.checkSession |
| 网络 | wx.request、网络状态 getNetworkType / on*NetworkStatusChange |
| 云开发 | wx.cloud.init / callFunction / database |
| 位置 | wx.getLocation / getFuzzyLocation / chooseLocation / openLocation |
| 系统 | wx.getDeviceInfo、wx.getAppBaseInfo、wx.getWindowInfo |
| 数据缓存 | wx.{get,set,remove,clear,batchGet,batchSet}Storage(含 Sync)、wx.getStorageInfo |
| 上传下载 | wx.uploadFile、wx.downloadFile |
| 微信支付 | wx.requestPayment、wx.openBusinessView(多种 businessType) |
| 订阅消息 | wx.requestSubscribeMessage |
| 授权设置 | wx.authorize、wx.openSetting、wx.getSetting |
| 设备 | wx.makePhoneCall、wx.scanCode |
| 媒体 | wx.chooseMedia、wx.chooseMessageFile、wx.saveImageToPhotosAlbum、wx.getImageInfo |
| 分享/手机号 | wx.shareAppMessage、wx.getPhoneNumber、wx.getRealtimePhoneNumber |
| 账号 | wx.getAccountInfoSync(接口与组件均可调) |
其他场景(人脸核身、发票、地址、微信运动、城市服务、WiFi、蓝牙/BLE、WebSocket、TCP/UDP、mDNS、传感器、加密、文件 wx.openDocument 等)涉及时一律查 `references/JSAPI_WHITELIST.md §1` 完整表,再决定能否使用。C.2 组件侧白名单(高频,完整清单见 references/JSAPI_WHITELIST.md §2)
"组件侧"指原子组件Component({})内的代码及其引用的纯 JS 模块。组件目录路径强约束为<skill>/components/<name>/index.{js,json,wxml,wxss}(与mcp.json中接口的_meta.ui.componentPath严格相等),但组件index.js引用的工具模块目录名不限。
| 分类 | 高频接口 |
|---|---|
| 小程序 AI | getContext(this)(支持 reapplyApiCall 等)、getViewContext(this)(支持 preloadDetailPage 及 on 事件等)、expireAllCards / expirePreviousCards |
| 网络请求 | wx.request(不支持,若调需声明 scope.dynamic) |
| 系统 | wx.getDeviceInfo、wx.getAppBaseInfo、wx.getWindowInfo |
| 数据缓存 | wx.getStorage / setStorage 全套(含 Sync) |
| 媒体/交互 | wx.previewMedia、wx.showToast、wx.hideToast |
| 文件/上传下载 | wx.openDocument、wx.downloadFile |
| 账号 | wx.getAccountInfoSync |
| 其它支持 | 位置 openLocation、设备 makePhoneCall、设置 openSetting、分享 shareAppMessage、振动、隐私授权 |
| 地图 | this.createSelectorQuery().select('#mapId').context() 获取 MapContext 后调 MapContext.*(`openMapApp` 除外),完整方法清单见 references/JSAPI_WHITELIST.md §2 |
组件侧禁用:wx.cloud.* / 位置 / 登录 / 支付 / 其它任何业务接口(除上表已列出的能力)。组件只能收数据(接口返回的 structuredContent / _meta)、做预览、读系统信息、读写本地缓存、读账号信息、操作 MapContext、发声明过能力的网络请求。组件与接口处于不同 JS 上下文,全局变量不共享。在 methods / tap handler / 异步回调里主动调 sendFollowUpMessage / getDimensions 时必须现取 wx.modelContext.getContext(this) / getViewContext(this),不要通过 this._modelCtx 等缓存引用调(详见 references/COMPONENT_TEMPLATES.md)。
C.3 组件配置(关联页面 + 网络能力)
每个带 _meta.ui.componentPath 的接口,对应组件必须在 mcp.json 顶层 components[] 中声明一条记录,`path` 必须与该接口的 `_meta.ui.componentPath` 字符串完全相等(含末尾 /index,严格相等比对);`relatedPage` 为必填(关联小程序页面 path,用于卡片右上角"进入小程序"入口),必须以 `/` 开头(绝对路径),且去掉前导 / 后必须是项目 app.json.pages[] 中真实存在的页面,业务上无对应页面时兜底用 `/<app.json.pages[0]>`(首页,同样带前导 `/`)。网络能力(permissions.scope.dynamic)按需声明。
{
"components": [
{
"path": "components/order-list/index",
"relatedPage": "/pages/order/list"
},
{
"path": "components/weather-card/index",
"relatedPage": "/pages/weather/index",
"permissions": { "scope.dynamic": { "desc": "声明使用场景" } }
}
]
}运行时若需要给关联页面附加 query 参数,在组件 created 里现取 viewCtx.setRelatedPage({ query }),示例代码见 references/CODE_TEMPLATES.md 第四节。该约束被静态规则强制校验。
C.3.1 组件过期态声明(按需,非强制)
默认不生成。仅当源业务上存在"卡片到某时刻作废、不应再被点"语义(成交、关店、活动结束、超时)时,在 components[] 记录上加 expirable: true + 业务化 expiredText(默认文案"服务已过期")。纯展示卡片不要写。代码示例与"过期触发"模板见 references/COMPONENT_TEMPLATES.md "卡片过期"节。
触发 API(按粒度二选一,不要同时调):
wx.modelContext.expireAllCards():原子接口/组件均可调,过期所有expirable: true卡片含自身wx.modelContext.getViewContext(this).expirePreviousCards():仅原子组件可调用(依赖组件实例this),只过期此前已渲染的卡片不含自身
精细过滤(两个 API 都支持):可传 { componentPaths: [...] } 只过期匹配 componentPath 的卡片;加 match: 'latest' 只过期最近一张。componentPath 用绝对路径(含分包前缀,如 packageA/weather-skill/components/weather-card/index),多条 path 取并集。
声明与调用必须配对。
C.3.2 半屏页面(按需,非强制,默认不生成)
半屏页面是原子组件内容的延伸——仅当源业务确有"详情 / 用户补充信息"语义时挂上。
- 入口:仅在原子组件
methods内,wx.modelContext.getViewContext(this).openDetailPage({ url }),承载页面用项目内已有的小程序页面(原子接口里没有 `this`,不可调) - 半屏内"下一步":原生页面
wx.modelContext.getContext().sendFollowUpMessage(...)(不传 `this`);web-view h5 走WeixinJSBridge.invoke('invokeMiniProgramAPI', { name: 'sendFollowUpMessage', arg })。上行后半屏自动关闭回小程序 AI 对话 - 场景值:1433 / 1434;左上角关闭按钮位置用
wx.getDetailPageCloseButtonBoundingClientRect适配 - 禁用清单:跳出类(
navigateToMiniProgram/ 公众号 / 视频号 / 表情 / 客服)、页面路由(navigateTo/redirectTo/switchTab/reLaunch/wx.router.*)、聊天工具(shareXxxToGroup)、地图MapContext.openMapApp、广告(createInterstitialAd等 +<ad><ad-custom>组件)、导航组件(<navigator>/<functional-page-navigator>)—— 完整清单与示例代码见references/HALF_SCREEN.md
C.4 不可迁移 JSAPI(接口与组件均禁用,高频示例;完整清单见 references/JSAPI_WHITELIST.md §3)
| 不可用 API | 替代策略 |
|---|---|
wx.showToast / showModal / showLoading / showActionSheet 等 UI 反馈 | 结果通过 content / structuredContent 回馈,小程序 AI 无 loading/modal 概念 |
wx.navigateTo / redirectTo / switchTab / reLaunch / navigateBack | 删除,小程序 AI 不在页面栈内导航 |
wx.chooseImage / wx.chooseVideo / wx.previewImage(老接口) | 改用 wx.chooseMedia(接口侧)/ wx.previewMedia(组件侧) |
wx.setClipboardData / getClipboardData | 跳过 |
wx.getUserInfo / getUserProfile | 改用登录 + 后端资料接口 |
wx.createSelectorQuery / createCanvasContext | 接口侧不适用;组件侧仅允许通过 this.createSelectorQuery().select('#mapId').context() 获取 MapContext(详见 C.2) |
wx.pageScrollTo / wx.createAnimation | 容器不支持滚动;动画用 CSS transition/animation(限 opacity/transform) |
其它老接口、Taro 特有不可迁移项(Hook、Pinia/Vuex、Vue setup 等)见 references/JSAPI_WHITELIST.md §3。C.5 button 的 open-type 改写
组件内 button 禁用 open-type(share / getPhoneNumber / getRealtimePhoneNumber)→ 去掉 open-type,改 bindtap,在 tap handler 内调对应白名单 JSAPI(wx.shareAppMessage / wx.getPhoneNumber / wx.getRealtimePhoneNumber)。
C.6 判定规则
1. 能力仅能通过不可迁移 JSAPI 实现(如"扫码核验"且源码无网络 API 替代)→ 触发阻断规则 B 2. 能力核心逻辑可用网络请求实现 → 生成纯网络请求版本,丢掉不可迁移的 JSAPI 调用 3. 老接口有白名单内新接口替代(chooseImage → chooseMedia、previewImage → previewMedia)→ 自动替换
D. 原子组件约束
- 仅支持
tap事件 - 支持的内置组件:
view(含hover-class)/text(不含user-select)/image(仅网络地址)/map/button(不含 `open-type`)/canvas/scroll-view(仅横向滚动 `scroll-x`,禁纵向 `scroll-y`) - 不支持的内置组件:
swiper/swiper-item/input/textarea/picker/picker-view/checkbox/radio/form/label/slider/switch/editor/rich-text/icon/progress/navigator/web-view/movable-area/movable-view/root-portal/match-media等 button用open-type→ 按 C.5 改写为bindtap+ 白名单 JSAPI- 渲染容器:宽度随屏幕,宽高比 4:1(最小高) ~ 1:1(最大高),超出裁剪、不支持纵向滚动(横向超长内容用
<scroll-view scroll-x="true">包裹) - 不支持打开小程序接口;不可声明为虚拟组件;组件与接口处于不同 JS 上下文,全局变量不共享
- 每个可交互元素必须绑 `bindtap`,tap handler 上行
content数组(① 单text或 ②text+api/call组合,推荐 ②)。详见阶段 5.0.1 +references/COMPONENT_TEMPLATES.md"上行消息"节
---
执行清单(复制后勾选)
阶段 0 — 业务需求澄清(强制前置)
- [ ] 判定用户场景是否明确(两项判定)
- [ ] 不明确 → 最小扫描 + 引导澄清 + 等待确认
- [ ] 产出"目标业务场景 + 期望原子能力"清单
阶段 1 — 项目扫描
- [ ] 提取 app.json / app.js / project.config.json 关键字段
- [ ] 产出云开发标记 + 云环境 ID + appid + 插件使用情况
- [ ] 产出鉴权迁移清单(token key / header 方式 / 登录流程)
- [ ] 产出 storage 初始化清单
阶段 2 — 业务功能识别(用户已明确时跳过)
- [ ] 产出结构化功能清单 JSON
- [ ] 用户二次确认
阶段 3 — 接口与 JSAPI 提取 + 可行性校验
- [ ] 每个能力对应的接口/JSAPI 清单
- [ ] 完整依赖链路
- [ ] 鉴权依赖确认
- [ ] 可行性三级评定(高/中/无置信)
- [ ] 逐条检查 T1~T6 触发条件,**命中任一即标记 `requiresRuntimeProbe: true`**
- [ ] ⚠️ **`requiresRuntimeProbe: true` 时必须执行 probe,禁止标记后跳过**:环境检查(自动安装 automator)→ 通知用户 → 生成 plan.json → 执行 `scripts/probe.mjs` → 结果写入 `<源项目>/.ai-mode-skills/probe/` → 合并写入 `merged-result.json` → 接入阶段 4
- [ ] 未命中 T1~T6 但属于建议探测场景(压缩源码 / outputSchema 不确定)→ 执行 probe
阶段 4 — 原子接口设计
- [ ] 原子接口清单(含 name / description / inputSchema / outputSchema / _meta.ui.componentPath)
- [ ] API 依赖图
- [ ] storage key 清单
阶段 5 — 代码生成
- [ ] 每个原子组件符合 `ATOMIC_COMPONENT_DESIGN.md`(尺寸档位 / 背景 / 边距 / 字号 + 透明度 / 布局 / 操作区)
- [ ] 每个原子组件走完 STYLE_MIGRATION.md 的 7 步
- [ ] 每个组件内的可交互元素都绑了 bindtap,tap handler 优先上行 `{ content: [{ type: 'text', text }, { type: 'api/call', data: { name, arguments } }] }` 组合,text 是用户视角的简短中文、`name` 在 mcp.json 中存在、`arguments` 与 inputSchema 对齐;无法映射到原子接口时可退回单 `text` 形态
- [ ] skills/{skill-name}/ 目录完整(mcp.json / SKILL.md / index.js / apis/* / utils/* / components/*)
- [ ] SKILL.md 已按 `references/CODE_TEMPLATES.md` 第五节的 5 节结构与禁止项写完(路由说明,非接口手册)
阶段 6 — 配置集成
- [ ] app.json 加 agent.skills + subPackages
- [ ] project.config.json 的 packOptions.include 加 skills
收尾 — 交棒给 wxa-skills-validate
- [ ] 明确告知用户:"请使用 wxa-skills-validate 做校验"
- [ ] 提示 skills 路径与 project-path---
跨阶段跳转规则
| 场景 | 流向 |
|---|---|
| 正常主干 | 0 → 1 → (2) → 3 → 4 → 5 → 6 → 交棒 wxa-skills-validate |
| 用户已明确能力 | 跳过 2,0 → 1 → 3 |
| 阶段 3 命中 probe 触发条件 | 3.5 → 3.6(probe,强制执行) → 4。⚠️ 禁止 3.5 → 4 跳过 3.6 |
| probe 失败,离线兜底成功 | 3.6 → 4(使用离线兜底数据) |
| probe + 离线兜底均失败 | 3.6 → 阻断规则 B |
| validator 反馈 T1~T6 / A/B/C/D 类错误 | 回本 skill 阶段 5 改代码 |
| validator 反馈 T7/T8(接口划分 / 依赖链路) | 回本 skill 阶段 4 重设计 |
| 任一阶段触发阻断规则 B | 立即终止,输出阻断原因 |
核心原则:
1. 业务场景不明确时,必须先澄清后生成,严禁跳过阶段 0 2. 每个阶段必须完整产出"产出物清单"中的全部项才能跳转 3. 本 skill 只生成代码,所有校验由 wxa-skills-validate 负责
增量与重入
工作区已存在 skills/ 产物时:
| 用户意图 | 入口阶段 | 说明 |
|---|---|---|
| 新增一个原子能力 | 阶段 0(轻量)→ 阶段 3 | 先澄清新能力,扫描接口并入增量清单 |
| 修改已有原子接口的行为 | 阶段 4 | 更新接口清单 → 5 → 6 → 交棒 |
| 修改组件样式/模板 | 阶段 5 | 仅改 components/{x}/,重新走 5 → 6 → 交棒 |
| validator T1~T6 / A/B/C/D 反馈 | 阶段 5 | 按报告定位文件,改完交棒 |
| validator T7/T8 反馈 | 阶段 4 | 重设计后 5 → 6 → 交棒 |
| 仅做验证 | 不进入本 skill,直接给 wxa-skills-validate | — |
重入时已生成且未触及的文件保持不变,只更新受影响的文件。
---
阶段 0 — 业务需求澄清(强制前置)
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 用户发起生成请求(任何请求都必须从本阶段开始) |
| 产出物 | 判定结果 + 必要时的澄清清单 |
| 下一步 | "明确"或澄清确认完毕 → 阶段 1 |
判定规则(必须同时满足 2 项才算"明确"):
| # | 判定项 | 示例 |
|---|---|---|
| ① | 指明具体业务名词 | "商品检索""订单管理""地址管理""签到";非"核心功能""主要能力" |
| ② | 可推断至少 2-3 个原子能力的粒度 | "检索商品 + 展示列表 + 查看详情";非"业务相关" |
任一不满足 → 进入下方澄清流程。
不明确时的引导流程
1. 最小扫描:只读 app.json 的 tabBar.list、pages(一级路径)、subPackages.root。禁止读 JS/WXML/WXSS,禁止做依赖分析。 2. 归纳候选:基于路径关键词(见 references/ANALYSIS_PATTERNS.md 页面功能识别表)归纳 3~6 个候选场景。 3. 向用户提问(一次问完,别反复打断):
- 希望把哪些业务场景做成小程序 AI 的 SKILL?
- 每个场景希望暴露给小程序 AI 的原子能力大致是什么?
- 是否涉及登录态、支付、位置、云开发等敏感能力?
4. 等用户回复后才能进入阶段 1。严禁在用户确认前扫描源码或生成代码。
澄清输出清单模板:
目标业务场景:
- 场景 A:<名称> → 期望原子能力:<能力 1>、<能力 2>
- 场景 B:<名称> → 期望原子能力:<能力 3>
技术约束:
- 是否涉及支付/登录/位置:是/否
- 是否使用云开发:待阶段 1 扫描确认---
阶段 1 — 项目扫描
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 阶段 0 产出明确的业务场景与原子能力清单 |
| 产出物 | ① 配置字段(appid / pages / subPackages / tabBar / agent / packOptions);② 云开发标记 + 云环境 ID;③ 插件使用情况;④ 鉴权迁移清单;⑤ storage 初始化清单 |
| 下一步 | 用户已明确所有原子能力 → 阶段 3;否则 → 阶段 2 |
| 阻断条件 | 未提供源码目录 / 目标能力依赖插件 → 阻断规则 B |
1.1 配置扫描
读 app.json / app.js / project.config.json,提取 pages / subPackages / tabBar / 已有 agent / appid / packOptions;扫云开发(wx.cloud 调用 / cloudfunctions/ 目录)与云环境 ID(wx.cloud.init({ env }))。`lazyCodeLoading` 必检:缺 "lazyCodeLoading": "requiredComponents" → 阻断规则 B(不要"代为补全")。云开发项目同时扫 cloudfunctionRoot/<fn>/index.js 的入参/返回结构。
1.2 鉴权逻辑扫描(必做)
扫 app.js + 主包 request 封装(utils/request.js / http.js / api.js) + 登录文件,提取四项:①token/session 存取 key(关键词 getStorageSync + token/session/openid/cookie)②请求 header 鉴权方式(Authorization / Bearer / 自定义)③登录入口(wx.login / wx.checkSession)④换 token 接口(wx.login 后的 wx.request / 云函数)。
迁移策略(形成鉴权迁移清单,阶段 5 写入分包工具模块):分包自包含完整登录能力,每次执行接口前 ensureLogin() 主动登录;token 存模块级变量(不写 storage——分包与主包隔离,登录态不可靠);无鉴权接口跳过。
1.3 主包 storage 初始化扫描(必做)
扫 app.js 与主包 .js 中的 wx.{set,get,clear}Storage*,提取 key / defaultValue / initCondition / sourceFile。迁移:① setStorageSync 初始化值 → 分包 ensureStorageInit() 重建;② getApp().globalData 运行时缓存 → 模块级变量或按需写 storage;③ onLaunch 异步获取后写 storage → 分包首次调用时自行重发请求并缓存。形成 storage 初始化清单(与阶段 4 内部"接口间数据传递的 storage key 清单"不是同一张表)。
1.4 压缩代码处理
识别:单行 >500 字符 / 单双字符变量名 / 缺注释空行。处理顺序:① 优先问用户要未压缩源码;② 否则尝试 prettier 格式化后再提取;③ 格式化后关键字段仍全是 a.b.c.d → 阻断规则 B。禁止盲目猜变量名——猜出来的代码会在 validator 大量失败。
1.5 插件检测
扫 app.json 的 plugins 字段、页面/组件 JSON 的 usingComponents 中的 plugin:// 引用。目标能力依赖插件 → 阻断规则 B。
---
阶段 2 — 业务功能识别(用户已明确时跳过)
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 阶段 1 完成 且 用户仅给源码未明确原子能力 |
| 产出物 | 结构化功能清单(JSON)且已获得用户二次确认 |
| 下一步 | 用户确认 → 阶段 3 |
| 阻断条件 | 用户始终无法确认 → 停留本阶段 |
动作:
1. 针对阶段 0 选定的候选场景对应页面,按 references/ANALYSIS_PATTERNS.md 的模式分析页面用途、交互事件、数据流向 2. 从用户视角识别功能点(每个功能 = 一个原子接口) 3. 分析数据依赖(A 的返回值被 B 使用)
产出物 JSON(字段统一 camelCase):
[
{
"functionName": "检索商品",
"pages": ["pages/items/list", "pages/search/index"],
"sourceApis": ["GET /api/items/search"],
"suggestedAtomicInterfaces": ["searchItems"],
"needsComponent": true
}
]必须将清单发给用户二次确认才能进入阶段 3。
---
阶段 3 — 接口与 JSAPI 提取
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 已有用户确认的目标原子能力清单 |
| 产出物 | 每个能力的接口/JSAPI 清单 + 完整依赖链路 + 可行性校验结果 |
| 下一步 | 所有能力均找到对应实现 → 阶段 4 |
| 阻断条件 | 任一能力找不到对应实现 / 依赖链路含插件 → 阻断规则 B |
详细匹配模式见 references/ANALYSIS_PATTERNS.md。
3.1 提取范围:仅扫用户已确认能力对应的页面/模块,搜索网络调用(wx.request / wx.cloud.{callFunction,database,callContainer})+ 白名单内 JSAPI(高频列表见"硬性约束 C",完整清单见 references/JSAPI_WHITELIST.md)。
3.2 依赖追踪:对相关页面/模块的所有 require / import 递归追踪,识别完整依赖链路。阶段 5 把依赖完整内联拷贝到分包工具模块(utils/util.js 或 utils/request.js 等),不要放到 `apis/` 下——apis/ 仅放 mcp.json 注册的接口。
3.3 鉴权依赖确认(必做):结合阶段 1 的鉴权迁移清单,对每个目标接口确认 ① 是否需要登录态 ② token 来源(storage 直读 / 需先登录) ③ 登录方式(wx.login + 换 token / 其他);标注后阶段 5 据此实现 ensureLogin()。
3.4 插件依赖阻断:依赖链路含 requirePlugin / require('../plugin/') / plugin:// → 阻断规则 B。
3.5 可行性三级校验(必做):
| 级别 | 识别特征 | 处理 |
|---|---|---|
| ✅ 高置信 | 唯一接口/云函数,参数返回路径清晰 | 直接采用,进阶段 4 |
| ⚠️ 中置信 | 多个候选 / 参数模糊 / 依赖非白名单 JSAPI 需替代 | 列候选 + 差异 + 询问用户后再进阶段 4(不可直接终止) |
| ❌ 无置信 | 遍历全部涉及页面仍找不到任何实现 | 阻断规则 B |
中置信询问模板:
以下原子能力在源码中存在多个候选实现,请确认选择:
能力:<能力名>
候选 1:<接口路径/云函数名> — 参数 <x>、返回 <y>(来自 pages/xxx.js 第 N 行)
候选 2:<接口路径/云函数名> — 参数 <x>、返回 <y>(来自 pages/yyy.js 第 M 行)
请回复序号(如"1")或说明选择理由。3.6 运行时探测(probe):
命中 T1~T6 时强制执行,仅在环境不可用/用户拒绝时允许降级。完整 SOP 见 references/RUNTIME_PROBE.md。触发条件(命中任一即执行):
| # | 条件 | 原因 |
|---|---|---|
| T1 | URL 动态拼接 / 压缩不可读 | 无法确定真实 URL |
| T2 | 请求含签名/加密字段 | 无法离线复现 |
| T3 | 响应结构不可推断(T3a 透传无字段访问 / T3b 模板隐式消费) | 无法推断 outputSchema |
| T4 | 必须登录才返回业务数据 | 无法确认正常态字段 |
| T5 | 中置信且用户也不确定 | 静态匹配不足 |
| T6 | 参数传递链 >3 跳 + globalData | 静态追溯不可靠 |
建议探测(非强制):压缩源码、字段类型不确定、T3b 可从 wxml 推断但需验证嵌套结构。
执行流程:
1. 静态分析产出中间结果 → 写入 <源项目>/.ai-mode-skills/static-analysis.json,标记各维度 confidence: "high"/"partial"/"low" 2. 判断是否需 probe:存在非 "high" 维度或命中 T1~T6 → 需要 3. 环境检查:确认 miniprogram-automator 已安装(装在 skill 的 scripts/ 目录)、CLI 可用 4. 通知用户(非阻断)→ 生成 <源项目>/.ai-mode-skills/probe/plan.json → 执行 scripts/probe.mjs 5. 合并结果(见 references/RUNTIME_PROBE.md §5)→ 写入 <源项目>/.ai-mode-skills/merged-result.json 6. 降级:probe 失败 → 离线兜底(HAR/抓包);全失败 → 阻断规则 B
代码注释溯源(apis/<name>.js 顶部):
// [ai-mode:static] URL /api/items/search 来自 utils/request.js:42
// [ai-mode:probe] 2026-06-02 验证完整 URL https://shop.example.com/api/items/search
// [ai-mode:probe] 实际响应字段:list[].{id, name, price, img}, total(number)---
阶段 4 — 原子接口设计
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | <源项目>/.ai-mode-skills/merged-result.json 已生成(阶段 3 完成静态分析 + probe 合并后的最终结果)。⚠️ 如果 `static-analysis.json` 中存在 `requiresProbe: true` 的接口,则必须先完成 3.6 probe 执行(成功或降级兜底)后才能进入本阶段,否则禁止进入 |
| 产出物 | ① 原子接口清单;② API 依赖图;③ storage key 清单 |
| 下一步 | 三份产出物齐全 → 阶段 5 |
4.1 技能划分:同业务域(商品/订单/地址)原子接口聚合到同一 skill;共享 storage 上下文的接口必须在同一 skill 内;每 skill 推荐 3-8 个原子接口(更多则按子业务拆分)。
4.2 接口字段:每条接口含 name(驼峰、全局唯一)/ description(含内部串联操作,帮助小程序 AI 决策)/ inputSchema(仅小程序 AI 需从用户获取的参数;无参用 {"type":"object","properties":{}})/ outputSchema(对应 structuredContent)/ _meta.ui.componentPath(可选,格式 components/xxx/index,纯操作型/中间态可省;声明则组件目录必须 4 文件齐全)。
多模态入参:当接口需要用户上传图片(如 P 图、图像识别)时,对应inputSchema.properties.<field>加"format": "image",类型为string(运行时填本地图片路径)。小程序 AI 输入框会据此识别为多模态字段、引导用户上传图片。
4.3 按需关联组件:返回值类型 → 组件模板对照(详见 references/COMPONENT_TEMPLATES.md):列表/卡片项 → 通用列表;详情/单对象 → 详情卡片;购物车/带数量总价 → 购物车;下单成功/支付结果/操作确认 → 状态结果;单值/中间数据 → 可不配组件,需收束反馈时用状态结果(简化版)。
4.4 产出物示例:
[{
"skill": "business",
"name": "searchItems",
"title": "检索商品",
"description": "根据关键词检索商品,返回商品列表",
"inputSchema": { "type": "object", "properties": {} },
"outputSchema": { "type": "object", "properties": { "items": { "type": "array" } } },
"_meta": { "ui": { "componentPath": "components/item-list/index" } }
}]API 依赖图(仅在通过 storage 传上下文时必备):
searchProducts ──(storage: skills_shopping_lastSearchResult)──▶ addToCart
└─(storage: skills_shopping_lastSearchResult)──▶ getProductDetailstorage key 命名统一 skills_{skillName}_{dataName},列表含 key / 写入方 / 读取方 / 数据结构。
---
阶段 5 — 代码生成
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 阶段 4 三份产出物齐全 |
| 产出物 | 完整的 skills/{skill-name}/(mcp.json / SKILL.md / index.js / apis/* / utils/* / components/*) |
| 下一步 | 代码生成完成 → 阶段 6 |
| 阻断条件 | 产出物缺失 → 停留本阶段补齐 |
代码模板见 references/CODE_TEMPLATES.md、组件模板见 references/COMPONENT_TEMPLATES.md、设计规范见 `references/ATOMIC_COMPONENT_DESIGN.md`(最高优先级)、CSS 实现规范见 references/ATOMIC_COMPONENT_CSS.md。
5.0 三个强制前置(写任何组件 WXML/WXSS 前必须按序走完)
| 编号 | 主题 | 关键要点 | 详见 |
|---|---|---|---|
| 5.0.0 设计规范(最高优先级) | 尺寸/主题/边距/字体/布局/操作区 | ① 5 档宽高比 + 圆角 4px;② 主题色按 §2.1 流程从主包 app.json/app.wxss 抽(浅 + 暗都抽,wxss 顶部注释"色源=…"链路;主包 6 步都查不到才走 §2.3 兜底);③ 边距 屏幕 16 / 卡片 12 / 元素 8·16;④ 字号 17/15/12 三档 + 同一基色 0.9/0.45/0.3 透明度分层;⑤ 主轴上下/左右布局;横向超长可用 <scroll-view scroll-x>,禁纵向滚动、禁 >2 列网格;⑥ ≤3 控件、主动作 ≤1、动宾文案、主按钮居右 | references/ATOMIC_COMPONENT_DESIGN.md |
| 5.0 源样式提取 + 字段映射 | 7 步工作流 | 与设计规范冲突时以设计规范为准,仅迁移源项目品牌色与字段映射结果。自检:wxss 主色是 #07c160 / #ff4d4f 且源页面未用,或 wxml 出现 item.imageUrl 但源 API 字段是 cover/pic/thumb — 视为"照抄模板",必须回炉重做 | references/STYLE_MIGRATION.md |
| 5.0.1 组件交互行为 | 组件是小程序 AI 的"回合出口",不是"页面入口" | 每个组件都要同时考虑"展示什么"+"用户下一步做什么"——按 mcp.json.apis[].description + API 依赖图列出下一步,映射到 mcp.json.apis[].name 已存在的接口;不存在则去掉按钮,不要上行不存在的 name。每个可交互元素绑 bindtap + hover-class,关键实体用 data-* 携带 | references/COMPONENT_TEMPLATES.md "上行消息"节 |
tap handler 优先形态 2(text + api/call 组合):
// 使用点必须现取 ctx;不要用 this._modelCtx 之类的缓存引用
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: '<用户视角的简短中文,例如:选择拿铁>' },
{ type: 'api/call', data: { name: '<mcp.json 已声明的 api name>', arguments: { /* 对齐该接口 inputSchema */ } } },
],
})只有当点击动作无法映射到原子接口时才退回形态 1(单 text)。每次上行 api/call 前打一行 [ai-mode] {componentName} send api/call name=... args=... console.info。禁止:组件内直调业务接口、单独发 api/call 不带前导 text、arguments 用占位值、name 不在 mcp.json 中、只展示不响应的"死"按钮、用 this._modelCtx.sendFollowUpMessage(...) 缓存引用调方法。
5.1 目录结构
{项目根目录}/
├── app.json # 含 agent.skills 注册
└── skills/ # 独立分包(多 skill 共用)
├── _shared/ # 可选:≥2 个 skill 共用的工具函数才放这里
└── {skill-name}/
├── mcp.json # 原子接口 Schema 定义
├── SKILL.md # skill 路由说明
├── index.js # 接口注册入口
├── apis/ # 原子接口实现(推荐目录;validator 兼容 tools/services/、tools/)
├── utils/ # 工具模块(目录名不强制,常见 utils/services/helpers)
└── components/{component-name}/ # index.js/json/wxml/wxss(路径强约束,与 mcp.json _meta.ui.componentPath 严格相等)目录分层:跨 skill 禁止require('../../{otherSkill}/...');多 skill 复用走skills/_shared/(不在mcp.json注册、不调registerAPI)。
5.2 mcp.json + 技能自身 SKILL.md + 返回值 + 日志
- `mcp.json`:顶层
{ "apis": [...] },每项必含name/description/inputSchema/outputSchema/_meta.ui.componentPath;可含components数组(声明组件网络能力,详见 C.3)。完整字段示例见references/CODE_TEMPLATES.md第四节 - 技能自身 `SKILL.md`(文件名严格全大写)定位"路由说明",只允许 5 节按序:能力域定位 → 触发场景(用户原话 few-shot)→ 不适用范围 → 前置条件 → 使用顺序。通篇禁止:驼峰 apiName /
inputSchema/outputSchema/ 参数表 / 返回值表 /componentPath/ storage key / 接口依赖图 / 安装 CLI 运维。完整模板见references/CODE_TEMPLATES.md第五节 - 返回值格式:
{ isError?, content: [{type:'text', text}], structuredContent?, _meta? }——content给 LLM 文本,structuredContent对应outputSchema,_meta对 LLM 不可见可传 UI 组件 - 日志规范:原子接口必打 入口 / 入参 / 请求前后 / 出口 / catch;原子组件必打
created/attached/ 收到 Result /setData/NotificationType.Overflow(必监听,用于校验裁剪)。统一前缀[ai-mode]。日志不打够等于没日志——真机失败看不到关键节点 → 回阶段 5 补齐重跑
---
阶段 6 — 配置集成
契约:
| 项 | 内容 |
|---|---|
| 入口条件 | 阶段 5 生成完整 skills/{skill-name}/ |
| 产出物 | app.json 含 agent.skills + subPackages;project.config.json 的 packOptions.include 含 skills |
| 下一步 | 两份配置均已更新 → 交棒 wxa-skills-validate |
| 阻断条件 | 未更新配置直接交棒 → 必定失败,停留本阶段 |
配置格式见 references/CODE_TEMPLATES.md 第六节。关键要点:
agent.skills[].path指向skills/{skill-name}目录subPackages中skills整体作为independent: true的独立分包;多 skill 共用同一个分包——新增 skill 只在agent.skills[]里追加,不要为每个 skill 加一条subPackages条目project.config.json的packOptions.include需含{ "type": "folder", "value": "skills" }
---
收尾 — 交棒给 wxa-skills-validate(强制)
阶段 6 完成后,必须在回复中明确告知用户:
代码生成与配置集成已完成。下一步请使用 `wxa-skills-validate` skill 对产物进行校验与真机验证:
- skills 路径:<abs-path>/skills
- project-path:<abs-path>(含 project.config.json 的 appid 为 <appid>)
wxa-skills-validate 会依次执行:静态校验 → cli agent tool execute → cli agent render → 交付文档。交棒步骤不可省略。仅输出代码不算完成,必须在对话中显式提示用户切换到校验 skill。
分析模式参考
第一、二、三阶段使用。包含页面功能识别、接口调用搜索、JSAPI 匹配、依赖追踪的正则模式与搜索关键词。
目录
- 一、业务流程分析模式
- 页面功能识别
- 用户交互事件提取
- 页面间导航追踪
- 二、网络接口搜索模式
- wx.request(HTTP 请求)
- wx.cloud.callFunction(云函数)
- wx.cloud.database(云数据库)
- wx.cloud 云存储
- wx.cloud.callContainer(云托管)
- 三、JSAPI 使用搜索模式
- 定位相关
- 支付相关
- 登录与授权相关
- 手机号 / 分享 / 订阅消息
- 图片视频
- 人脸核身
- 系统信息 / 加密
- 云开发相关
- 不可迁移的 JSAPI(仅用于分析记录)
- JSAPI 调用上下文分析
- 四、变量引用追踪
- 五、信息提取
- 六、云环境 ID 提取
- 七、原子接口关联分析
- 八、小程序插件依赖检测
---
一、业务流程分析模式
页面功能识别
通过页面路径和 WXML 内容推断页面用途:
| 路径关键词 | 常见业务 |
|---|---|
goods/list、product/list、shop/index | 商品列表/首页 |
goods/detail、product/detail | 商品详情 |
cart、shopping-cart | 购物车 |
order/create、order/confirm | 订单确认 |
order/list、order/index | 订单列表 |
order/detail | 订单详情 |
pay、checkout | 支付 |
user、mine、profile | 个人中心 |
address、addr | 地址管理 |
search | 搜索 |
login、auth | 登录/授权 |
store、shop/nearby | 门店 |
book、reserve、appointment | 预约 |
用户交互事件提取
bind(tap|submit|change|input|confirm)\s*=\s*['"](\w+)['"]
catch(tap)\s*=\s*['"](\w+)['"]追踪事件处理函数中的网络请求和 JSAPI 调用,建立"用户操作 → 接口调用"映射。
页面间导航追踪
wx\.(navigateTo|redirectTo|switchTab|reLaunch)\s*\(\s*\{[^}]*url\s*:\s*['"]([^'"]+)['"]提取页面间的 query 参数传递,识别业务流程的页面跳转链路。
---
二、网络接口搜索模式
wx.request(HTTP 请求)
wx\.request\s*\(\s*\{
wx\s*\[\s*['"]request['"]\s*\]\s*\(追踪封装函数(常见文件名:request.js、http.js、api.js、service.js):
return\s+new\s+Promise\s*\(.*wx\.request
async\s+function\s+\w*(request|fetch|http|api)\w*wx.cloud.callFunction(云函数)
wx\.cloud\.callFunction\s*\(\s*\{
await\s+wx\.cloud\.callFunction\s*\(
wx\s*\[\s*['"]cloud['"]\s*\]\s*\.?\s*\[?\s*['"]?callFunction['"]?\s*\]?\s*\(封装调用:
function\s+\w*(cloud|callCloud)\w*\s*\(wx.cloud.database(云数据库)
wx\.cloud\.database\s*\(\s*\)
\.collection\s*\(\s*['"](\w+)['"]\s*\)操作链追踪:.where() → .orderBy() → .limit() → .get()|.add()|.update()|.remove()|.count()
wx.cloud 云存储
wx\.cloud\.(uploadFile|downloadFile|getTempFileURL|deleteFile)\s*\(wx.cloud.callContainer(云托管)
wx\.cloud\.callContainer\s*\(\s*\{提取 path、X-WX-SERVICE 头。
---
三、JSAPI 使用搜索模式
⚠️ 以下是技能分包白名单内支持的 JSAPI 搜索模式。完整白名单见 `references/JSAPI_WHITELIST.md`(SKILL.md的"硬性约束 C"节只列高频项)。源项目中可能用到白名单之外的接口(如wx.scanCode、wx.chooseAddress、wx.navigateTo等),这些不可迁移,提取时应标记并按"硬性约束 C.4 / C.6"判定规则处理。
定位相关
wx\.getLocation\s*\(
wx\.getFuzzyLocation\s*\(
wx\.openLocation\s*\(
wx\.chooseLocation\s*\(分析 getLocation 的结果如何被使用(作为请求参数、展示等);openLocation / chooseLocation 通常是独立的地图页面交互。
支付相关
wx\.requestPayment\s*\(
wx\.requestVirtualPayment\s*\(
wx\.openBusinessView\s*\(追踪支付参数来源(通常来自一个预下单接口的返回值)。常见模式:
// 模式1:直接从接口返回中解构
const payParams = await request('/api/order/prepay', { orderId })
wx.requestPayment({ ...payParams })
// 模式2:从接口返回中提取特定字段
const res = await request('/api/pay/create', { orderId })
wx.requestPayment({
timeStamp: res.timeStamp,
nonceStr: res.nonceStr,
package: res.package,
signType: res.signType,
paySign: res.paySign
})
// 模式3:微信支付分(openBusinessView)
wx.openBusinessView({
businessType: 'wxpayScoreUse', // 或 'wxpayScoreEnable'
extraData: { /* 签名参数 */ }
})仅businessType=wxpayScoreUse/wxpayScoreEnable在白名单内;其它businessType不可迁移。
登录与授权相关
wx\.login\s*\(
wx\.checkSession\s*\(
wx\.authorize\s*\(分析登录凭证(code)如何使用(通常发送到后端换取 token);authorize 用于主动申请 scope 授权(如 scope.userLocation)。
手机号 / 分享 / 订阅消息
wx\.getPhoneNumber\s*\(
wx\.getRealtimePhoneNumber\s*\(
wx\.shareAppMessage\s*\(
wx\.requestSubscribeMessage\s*\(源项目常见用法:
- 手机号:通常通过
<button open-type="getPhoneNumber">+bindgetphonenumber处理;迁移时改为bindtap+wx.getPhoneNumber() - 分享:源项目常用
onShareAppMessage页面生命周期;技能分包内应改为在原子接口主动调用wx.shareAppMessage - 订阅消息:通常与支付或下单流程绑定,在关键节点请求用户授权
图片视频
wx\.chooseMedia\s*\(
wx\.chooseMessageFile\s*\(
wx\.previewMedia\s*\(wx.previewMedia仅原子组件可用,原子接口不支持;chooseMedia/chooseMessageFile仅原子接口可用。
老接口替换:
wx.chooseImage/wx.chooseVideo→ 统一改为wx.chooseMediawx.previewImage→ 组件侧改为wx.previewMedia
人脸核身
wx\.startFacialRecognitionVerify\s*\(
wx\.startFacialRecognitionVerifyAndUploadVideo\s*\(系统信息 / 加密
wx\.getDeviceInfo\s*\(
wx\.getAppBaseInfo\s*\(
wx\.getUserCryptoManager\s*\(wx.getDeviceInfo / wx.getAppBaseInfo 接口与组件均可用;其余仅接口可用。
云开发相关
wx\.cloud\.init\s*\(
wx\.cloud\.callFunction\s*\(
wx\.cloud\.database\s*\(云开发仅在原子接口中可用,组件侧不可用。
不可迁移的 JSAPI(仅用于分析记录)
以下 JSAPI 不在技能分包白名单中,生成代码时不应使用:
wx\.(scanCode|chooseAddress|chooseImage|chooseVideo)\s*\(
wx\.(setClipboardData|getClipboardData|saveImageToPhotosAlbum|previewImage)\s*\(
wx\.(getUserProfile|getUserInfo)\s*\(
wx\.(navigateTo|redirectTo|switchTab|reLaunch|navigateBack)\s*\(
wx\.(showToast|showModal|showLoading|showActionSheet|hideToast|hideLoading)\s*\(
wx\.(pageScrollTo|createAnimation|createSelectorQuery|createCanvasContext)\s*\(遇到源项目中使用了这些接口的业务功能时:
- 若核心逻辑仍可通过网络请求实现 → 生成纯网络请求版本,丢掉不可迁移的 JSAPI 调用
- 若强依赖不可用 JSAPI(如扫码核心功能且无 API 替代)→ 跳过该功能,不生成原子接口
- 老接口若有白名单内的新接口替代(如
chooseImage→chooseMedia、previewImage→previewMedia)→ 自动替换,不标记为"不可迁移"
JSAPI 调用上下文分析
对每个 JSAPI 调用,需要分析:
1. 调用时机:是在函数开头(先获取信息再请求)还是在请求之后(如支付) 2. 结果使用:返回值作为后续请求的参数?展示给用户?存入 storage? 3. 错误处理:是否有 fail 回调?如何处理用户拒绝/取消? 4. 关联接口:与哪个网络请求配合使用?
---
四、变量引用追踪
wx 和 wx.cloud 可能被赋值给变量:
var e = wx; e.request({...}) // 追踪 e → wx
const cloud = wx.cloud; cloud.callFunction({...}) // 追踪 cloud → wx.cloud搜索策略:先找所有 .request(、.callFunction(、.database()、.callContainer( 调用,再向上追踪调用对象是否指向 wx/wx.cloud。
---
五、信息提取
URL/路径提取
['"`](https?:\/\/[^\s'"`]+)['"`] # 完整 URL
['"`](\/api\/[^\s'"`]+)['"`] # API 路径
(baseUrl|BASE_URL)\s*[:=]\s*['"`]([^'"`]+) # baseURL 定义云函数名提取
callFunction\s*\(\s*\{\s*name\s*:\s*['"](\w+)['"]集合名提取
\.collection\s*\(\s*['"](\w+)['"]\s*\)云托管路径提取
callContainer\s*\(\s*\{[^}]*path\s*:\s*['"]([^'"]+)['"]
X-WX-SERVICE['"]\s*:\s*['"]([^'"]+)['"]---
六、云环境 ID 提取
必须提取:独立分包需自行 wx.cloud.init()。wx\.cloud\.init\s*\(\s*\{[^}]*env\s*:\s*['"]([^'"]+)['"] # 字符串 env
wx\.cloud\.init\s*\(\s*\{[^}]*env\s*:\s*wx\.cloud\.DYNAMIC_CURRENT_ENV # 动态 env4 种场景: 1. 字符串 env 'cloud-xxx' → 直接写入 ensureCloudInit() 2. wx.cloud.DYNAMIC_CURRENT_ENV → 分包中也用此值 3. 条件 env(if/三元) → 分析条件,提取对应值 4. 未找到 init → 告警 + TODO 注释
---
七、原子接口 关联分析
多调用串联模式识别
在同一个事件处理函数中,依次调用多个接口或 JSAPI 的模式:
// 模式:async 函数中有多个 await 调用
async\s+\w+\s*\([^)]*\)\s*\{[^}]*await[^}]*awaitStorage 数据流追踪
识别页面间通过 storage 传递数据的模式:
wx\.setStorageSync\s*\(\s*['"]([^'"]+)['"]
wx\.getStorageSync\s*\(\s*['"]([^'"]+)['"]追踪同一个 key 在哪些页面写入、哪些页面读取,建立数据流图。
页面参数传递追踪
// 页面跳转中的参数
url:\s*['"][^'"]*\?([^'"]+)['"]
// 页面 onLoad 中的参数接收
onLoad\s*\(\s*(options|params|query)\s*\)---
八、小程序插件依赖检测
⛔ 如果目标原子接口依赖的业务逻辑中使用了小程序插件,直接退出生成流程,告知用户小程序插件暂未支持。
app.json 插件声明
"plugins"\s*:\s*\{提取 plugins 中声明的所有插件 ID 和版本。
页面/组件 JSON 中的插件引用
"plugin://在 usingComponents 中出现 plugin:// 前缀即表示使用了插件组件。
JS 中的插件模块引用
requirePlugin\s*\(
require\s*\(\s*['"]plugin://requirePlugin 或 require('plugin://...') 是加载插件 JS 模块的方式。
检测流程
1. 扫描 app.json 的 plugins 字段,获取插件声明列表 2. 扫描目标页面/组件的 JSON 文件,检查 usingComponents 中是否有 plugin:// 引用 3. 扫描目标页面/组件的 JS 文件,检查是否调用 requirePlugin 4. 任一匹配命中 → 退出生成流程,告知用户:「该功能依赖小程序插件,当前暂不支持自动生成,需手动接入技能分包。」
原子组件 CSS 样式规范
第五阶段使用(组件样式撰写时)。原子组件的 CSS 支持范围有限,编写样式时需注意以下约束。
>
本文件只管"CSS 能力/单位/溢出/选择器白名单"等实现层约束。卡片的尺寸档位 / 圆角 / 字号档位 / 边距 / 主题 / 操作区结构等设计层规范以ATOMIC_COMPONENT_DESIGN.md为准;本文件示例若与该规范冲突,以ATOMIC_COMPONENT_DESIGN.md为准。
目录
---
〇、渲染容器与单位约束(最高优先级)
渲染容器尺寸(宿主强制,组件不可突破)
| 属性 | 值 | 说明 |
|---|---|---|
| 最大宽度 | 100vw | 根容器宽度上限 |
| 最小高度 | 25vw | 即使内容少,宿主也会保留至少此高度 |
| 最大高度 | 100vw | 超出即被裁剪,容器不支持纵向滚动(横向超长内容可用 <scroll-view scroll-x="true"> 包裹,仅横向滚动) |
⚠️ 以上尺寸由宿主自动施加在外层容器上,组件根节点不要再写 `max-height` / `min-height` / `height`。一旦组件自行设置高度,宿主的 NotificationType.Overflow 回调将无法触发,溢出检测会全部失效。
⚠️ 超出 `100vw` 高度的内容会被直接裁掉且不可滚动查看。因此生成组件时必须: 1. 估算内容撑开后的总高度(含 padding / margin / 所有 item 的总高)。 2. 若估算结果可能超过 100vw,主动减少单项高度、精简字段、或用文本省略/多行截断,而非依赖滚动。 3. 常见超限场景与对策:
- 单个 item 内容过丰富 → 精简字段、把长文本改为单行省略
- 多行文本 → 用
-webkit-line-clamp限制 1~2 行 - 数据条数较多 → 在
index.js计算visibleItems+omittedCount,WXML 渲染"还有 N 条未展示",由宿主的100vw边界自然兜底裁剪
长度单位:推荐使用 vw
| 项 | 规定 |
|---|---|
| ✅ 推荐 | `vw`(便于按宿主 100vw 容器精确控制尺寸,溢出判断更直观) |
| ✅ 允许 | rpx、px、em、rem、% —— 只要最终渲染高度不超出 100vw 即可 |
| ❌ 禁用 | vh、vmin、vmax、pt 等不在小程序 WXSS 规范内的单位 |
vw 换算参考(375 px 设备宽):
| 需求 | 参考值 |
|---|---|
| 基础行间距 / 小 padding | 2vw ≈ 7.5px |
| 注释字号(设计规范档位) | font-size: 3.2vw ≈ 12px |
| 正文字号(设计规范档位) | font-size: 4vw ≈ 15px |
| 标题字号(设计规范档位) | font-size: 4.53vw ≈ 17px |
| 卡片/按钮圆角(设计规范固定值) | 1.07vw ≈ 4px |
| 卡片内边距(设计规范固定值) | 3.2vw ≈ 12px |
| 列表项图片(对齐 150rpx) | 20vw |
| 列表项图片(对齐 160rpx) | 21.3vw |
| 按钮高度(设计规范固定值) | 10.67vw ≈ 40px |
换算关系:1vw ≈ 3.75px ≈ 7.5rpx(即Nrpx ÷ 7.5 = Nvw)。混用单位时,务必以"最终渲染是否超出100vw高度"为判断标准。
em / rem 注意事项:
rem相对于根节点字体尺寸,但目前无法指定根节点字体尺寸,取值不稳定,尽量避免使用em相对于当前节点字体尺寸;若em值从父节点继承,不同 lib 版本行为不同(lib v5.x 相对当前节点,lib v6.x 相对父节点)——避免继承式 em
环境变量 env() 与安全区
表达长度时可用 env() 读取宿主环境变量(iOS 安全区等):
.my-class {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}| 环境变量 | 类型 | 说明 |
|---|---|---|
safe-area-inset-left | 长度 | 安全区左侧距离 |
safe-area-inset-top | 长度 | 安全区顶部距离 |
safe-area-inset-right | 长度 | 安全区右侧距离 |
safe-area-inset-bottom | 长度 | 安全区底部距离 |
原子组件容器已由宿主约束最大高度 100vw,通常无需再处理安全区;仅当组件显式对齐系统 UI 层(如状态栏、Home Indicator)时才需要。---
一、选择器支持
出于性能考虑,仅支持以下选择器:
- 类选择器:
.my-class {} - ID 选择器:
#my-id {} - 标签名选择器:
view {} - 后代选择器(空格分隔)
✅ 推荐使用类选择器,具有独特的性能优化。
/* ✅ 支持 */
.my-class {}
#my-id {}
view {}
view#my-id.my-class .another-class {}
.my-class, #my-id { display: block; }
/* ❌ 不支持 */
.parent > .child {} /* 子选择器 */
.item + .item {} /* 相邻兄弟选择器 */
.item ~ .item {} /* 通用兄弟选择器 */
[type="text"] {} /* 属性选择器 */
:hover {} /* 伪类选择器 */
::before, ::after {} /* 伪元素选择器 */---
二、媒体查询
支持 CSS Media Queries,可按容器尺寸 / 主题做自适应:
@media (max-width: 360px) {
.my-class { display: block; }
}
@media (prefers-color-scheme: dark) {
.card { background-color: #1c1c1e; color: #f5f5f7; }
}支持的判断条件:
| 条件 | 值类型 | 说明 |
|---|---|---|
orientation | string | landscape 宽大于高;portrait 宽不大于高 |
width / min-width / max-width | number | 宽度精确值 / 下限 / 上限 |
height / min-height / max-height | number | 高度精确值 / 下限 / 上限 |
prefers-color-scheme | string | 当前环境主题,通常是 light 或 dark |
支持 not / and 关键字;基本媒体类型仅 all / screen 两种,通常无需写明。
---
三、CSS 属性支持范围
✅ 支持的属性
| 分类 | 属性 |
|---|---|
| 定位 | display(none/inline/inline-block/block/flex)、position(relative/absolute)、box-sizing、overflow-x、overflow-y |
| 颜色 | color、opacity、visibility |
| Flex | flex-direction、flex-wrap、align-items、align-self、align-content、justify-content、flex-grow、flex-shrink、flex-basis、aspect-ratio |
| 背景 | background-color、background-image、background-size、background-repeat、background-origin、background-clip、background-position |
| 尺寸 | width、height、min-width、min-height、max-width、max-height、left、right、top、bottom |
| 边距 | padding-*、margin-* |
| 边线 | border-*-width、border-*-style(none/solid)、border-*-color |
| 圆角 | border-*-radius |
| 阴影 | box-shadow、text-shadow |
| 文本 | font-size、line-height、text-align、font-weight、word-break、white-space、text-overflow、text-indent、vertical-align、letter-spacing、word-spacing、font-family、font-style |
| 变换 | transform、transform-origin、filter(仅blur) |
| 动画 | transition-*(仅opacity/transform)、animation-* |
❌ 不支持的属性(常见)
| 属性 | 说明 |
|---|---|
position: fixed/sticky | 仅支持 relative 和 absolute |
display: grid/table/inline-flex | 仅支持 none/inline/inline-block/block/flex |
z-index | 不支持 |
float、clear | 不支持浮动布局 |
cursor | 不支持 |
text-decoration | 不支持 |
border-style 其他值 | 仅支持 none 和 solid |
filter 其他函数 | 仅支持 blur() |
CSS 变量 (--*) | 不支持 |
---
四、动画限制
transition 和 animation 仅支持以下属性:
opacity(透明度)transform(2D/3D 变换)
/* ✅ 支持 */
.fade { transition: opacity 0.3s ease; }
.slide { transition: transform 0.3s ease; }
/* ❌ 不支持 */
.wrong { transition: background-color 0.3s ease; }---
五、推荐写法
容器与溢出处理(必须遵循)
组件根节点不要写 `max-height` / `min-height` / `height`——外层尺寸由宿主自动施加,手写高度会让 NotificationType.Overflow 回调失效。根节点只需要 overflow: hidden + 内部布局;溢出由宿主边界自然裁剪,并通过宿主回调上报。任何可能超出的文本或区域必须通过 CSS 省略样式显式提示"内容已省略":
/* ✅ 组件根容器:不写 max-height / min-height / height,宿主自动施加 */
.card-container {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 3.2vw; /* 12px,与 ATOMIC_COMPONENT_DESIGN.md 的"卡片内边距"对齐 */
overflow: hidden; /* 兜底:超出部分被裁剪 */
}
/* ✅ 内容区域自适应 */
.card-body {
flex: 1;
overflow: hidden;
}
/* ✅ 单行文本溢出省略(推荐所有潜在长文本都加) */
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* ✅ 多行文本截断(2 行示例,常用于标题/描述) */
.text-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}关键要点:
- 根节点禁写 `max-height` / `min-height` / `height`;尺寸由宿主自动施加,组件自行设置会破坏
NotificationType.Overflow回调 - 根节点保留
overflow: hidden做兜底 - 凡可能超长的单行文本都加
text-overflow: ellipsis三件套(overflow:hidden; white-space:nowrap; text-overflow:ellipsis) - 多行文本使用
-webkit-line-clamp截断到合适行数(通常 1~2 行) - 生成代码前先估算实际内容总高,若超过
100vw通过减小 item 尺寸或精简字段控制,而非依赖滚动
通用样式示例
背景配色、字号/透明度档位、主按钮样式见 ATOMIC_COMPONENT_DESIGN.md。以下仅示范规范落到 CSS 的最小形态(具体底色与文字色应改为源项目 token):.card {
display: flex;
flex-direction: column;
padding: 3.2vw; /* 12px 内边距 */
background-color: #f5f5f5; /* 实色·浅色 */
border-radius: 1.07vw; /* 4px 圆角 */
overflow: hidden;
box-sizing: border-box;
}
.card-title { font-size: 4.53vw; font-weight: 600; color: rgba(0,0,0,0.9); } /* 17px 主文 */
.card-desc { font-size: 4vw; color: rgba(0,0,0,0.45); } /* 15px 次要 */
.card-hint { font-size: 3.2vw; color: rgba(0,0,0,0.3); } /* 12px 辅助 */原子组件设计规范
第五阶段强制前置。本文件是原子组件视觉/结构的权威规范;与STYLE_MIGRATION.md/ATOMIC_COMPONENT_CSS.md/COMPONENT_TEMPLATES.md存在冲突时,以本文件为准。设计目标:保证所有 skill 产出的卡片在宿主小程序 AI 界面内视觉统一、可预测、能适配深浅主题。
---
一、卡片尺寸(比例档位)
卡片宽度随屏幕变化,高度由宿主按宽高比自动施加。只允许以下 5 档:
| 宽高比 | 适用场景 |
|---|---|
| 1:1(正方形) | 列表数据(商品/订单/搜索结果等)、商品主视觉、单图详情、重点单对象;信息密度最高 |
| 4:3(横向矩形) | 图文混排、单对象详情(字段较少);中等密度 |
| 16:9(宽屏) | 视频封面、宣传图、Banner |
| 3:1(横幅) | 轻量入口、标题展示 |
| 4:1(窄条) | 工具条、单行信息、工具名展示 |
硬规则:
- 必须从 5 档中选一,不得自选 4.27:1 / 5:2 等非档位比例。在
index.wxss顶部注释写明所选档位(如ratio=4:3)与依据。 - 组件根节点禁写
max-height/min-height/height(由宿主施加,自写会让NotificationType.Overflow回调失效)。内容撑不满或会溢出 → 换档,不要手写 height 硬撑。 - 圆角固定 4px(≈
1.07vw),所有档位/所有卡片/所有按钮一致;不得圆形、胶囊或 >8px 的大圆角。
---
二、卡片背景
原则:背景色必须从源项目主包里提取,让原子组件与原小程序视觉连贯;浅色 + 暗黑两套主题必须同时实现。中性灰白只在源项目完全没有可用色板时作为最后退路,并必须在 wxss 顶部注释里声明降级原因。
2.1 主题色提取流程(强制,每个 skill 必做一次)
按以下顺序读源项目主包,提取到一个可用色就停止;提取过程必须落到组件 wxss 顶部注释里("色源 = …"),便于校验回溯:
| 顺序 | 提取位置 | 取值字段 |
|---|---|---|
| 1 | app.json window | navigationBarBackgroundColor / backgroundColor / backgroundColorTop 等 |
| 2 | app.json tabBar | selectedColor / color / backgroundColor(主色 / 主底) |
| 3 | app.json darkmode 或 themeLocation 指向的主题文件 | 浅色 + 暗黑两套主题色 |
| 4 | app.wxss 全局类(page、.container、.card、.bg-* 等)的 background-color / color / border-color | 通用底色 / 描边色 / 文字色 |
| 5 | 主包高频页面 wxss(首页 / tabBar 页 / 详情页)的卡片容器类 .card / .cell / .list-item 等 | 卡片底色、描边色、阴影色 |
| 6 | 主包品牌 / 主题工具类(.btn-primary、.theme-color 等) | 主按钮色、强调色 |
硬规则:
- 必须至少取到一组浅色底(背景)+ 主文字色,且来源是上表前 6 步之一;只要前 6 步任意一步抽到可用值,就禁止直接套用 §2.3 的兜底色板。
- 暗黑色板按相同顺序找
darkmode/theme-dark.wxss/@media (prefers-color-scheme: dark)块;找不到时对已抽到的浅色主题色做降明度处理得到暗黑版本,仍然不要直接套兜底深色#1C1C1E。
"浅色主题色"定义:指 §2.1 步骤 1\~6 中提取到的、用于浅色模式的所有色值,包含三类:
- 浅色底(卡片/页面背景色):通常是高明度低饱和色,如
#FFF8F0、#F5F5F5、#FFFFFF - 品牌主色(按钮/强调/选中态):可能是中明度中饱和色,如
#6F4E37、#07C160 - 浅色描边色:通常是低饱和浅灰,如
#E5E5E5、#EEEEEE
降明度算法(必须按此执行,不得凭感觉取值):
1. 将浅色 HEX 转为 HSL(H=色相 0\~360, S=饱和度 0\~100%, L=明度 0\~100%) 2. 保持 H 不变;S 根据目标明度做微调(L 低于 30 时 S 可降至原值的 60\~80%,防止暗黑下过饱和刺眼) 3. L 按下表规则设定:
| 浅色色值类型 | 浅色典型 L 范围 | 暗黑目标 L | 说明 | 示例 |
|---|---|---|---|---|
| 浅色底(背景) | 90\~100% | 10\~15% | 保持色相,压到深色区间 | #FFF8F0 (H=30,S=100%,L=97%) → #2A221A (H=30,S=30%,L=13%) |
| 品牌主色(按钮/强调) | 30\~70% | 保持原 L 或略降 | 品牌色在暗黑下仍需可辨识,不硬压 | #6F4E37 (H=25,S=35%,L=33%) → #6F4E37(不变)或 #5A3D2C(L 降至 28%) |
| 浅色描边 | 80\~95% | 25\~35% | 需比暗黑底更亮,肉眼可辨 | #E5E5E5 (H=0,S=0%,L=90%) → #3A3A3C (H=240,S=2%,L=23%) |
| 文字色(浅底用黑字) | 0\~10% | 90\~100%(白系) | 暗黑下文字翻转为白 | rgba(0,0,0,0.9) → rgba(255,255,255,0.9) |
4. 将调整后的 HSL 转回 HEX 写入 @media (prefers-color-scheme: dark) 块
暗黑底与暗黑描边的对比度要求:暗黑描边必须比暗黑底明度至少高 10 个百分点(ΔL ≥ 10),确保肉眼可辨。若自动计算后不满足,将描边 L 上调至 底色L + 10。
- 在每个组件
index.wxss顶部注释中必须写明色源链路,例如:
/* 色源:app.wxss .container { background:#FFF8F0 } + tabBar.selectedColor #6F4E37
* 暗黑:源项目无 darkmode → 底色 #FFF8F0 (H=30,S=100%,L=97%) 降明度 L→13% → #2A221A
* 品牌主色 #6F4E37 暗黑下保持不变
*/- 写不出色源(即未做提取)= 视为偷懒,必须回本节重做。
2.2 主题变量结构
每个 skill 用一组变量描述主题,浅色 + 暗黑两套:
| 变量 | 浅色取值 | 暗黑取值 |
|---|---|---|
底色 | 来源:2.1 步骤 1\~5 | 来源优先级:① 2.1 同序找到的暗黑值(darkmode/theme-dark) ② 浅色底按 §2.1 降明度算法(L→10\~15%)推导 |
描边(可无) | 来源:2.1 步骤 4\~5 的卡片描边;无则不写 | 优先取源暗黑值;无则按 §2.1 降明度算法(L→25\~35%),且必须比暗黑底 ΔL ≥ 10 |
主文字色 | 来源:2.1 步骤 4 的全局 color / darkmode.txt;无则黑 #000 配透明度 | 优先取源暗黑字色;无则翻转为白系 rgba(255,255,255,0.9) |
结构性硬规则(与配色无关):
- 必须实现暗黑:通过
@media (prefers-color-scheme: dark)切换;不得复制出第二份 WXML;切换时只动底色 / 描边 / 文字色,不动布局、尺寸、字号、padding。 - 同一 skill 内所有卡片共用同一组主题变量,不要每张卡片换风格。
- 卡片底色与上一层小程序 AI 容器必须有视觉区分(不能与宿主背景同色融成一片)。
- 主文与底色对比度 ≥ 4.5:1,注释 ≥ 3:1;不达标时换底色或文字色,不靠加粗硬撑。
- 描边宽度固定
1px,且与底色保持足够对比度(肉眼可辨)。
2.3 末位降级色板(仅当 2.1 全部 6 步都查不到任何可用值时才允许使用)
| 风格 | 浅色底 | 浅色描边 | 浅色主文字 | 暗黑底 | 暗黑描边 | 暗黑主文字 |
|---|---|---|---|---|---|---|
| 实色(无描边) | #F5F5F5 | — | rgba(0,0,0,0.9) | #1C1C1E | — | rgba(255,255,255,0.9) |
| 线框(白底 + 描边) | #FFFFFF | 1px solid #E5E5E5 | rgba(0,0,0,0.9) | #1C1C1E 附近深底 | 1px solid #3A3A3C | rgba(255,255,255,0.9) |
使用本表的卡片,wxss 顶部注释必须写一行:色源:源项目主包 .wxss 不可读 / 无任何主题色 token,使用 ATOMIC_COMPONENT_DESIGN §2.3 兜底,否则视为偷懒,必须回 2.1 重做。---
三、边距
| 项 | 值 | 说明 |
|---|---|---|
| 卡片到屏幕左右 | 16px | 宿主通常已处理;组件外层不得再加 margin 破坏 |
| 卡片内边距 | 12px(≈ 3.2vw) | 顶/底/左/右均 ≥12px,允许顶部略大于左右(注意,容器顶部的小程序名称自带间距,因此原子组件的内容一般不需要额外加顶部间距) |
| 元素之间间距 | 8px 或 16px 二选一 | 高密度(列表 item)用 8px;段落之间用 16px |
硬规则:不得内容贴卡片描边(< 12px);不得 item 间距 > 16px 造成空洞 → 放不下就换档;图片/图标/按钮也遵守 12px 内边距,满出血图片仅限 1:1 / 16:9 主视觉位。
---
四、字体
核心原则:字号分级 + 透明度分层级;同一卡片内的层级关系必须用同一种基色叠不同透明度表达,不要用"主文红 / 次要灰 / 辅助蓝"这类混色乱搭。
字号档位
| 层级 | 字号 | 用途 |
|---|---|---|
| 标题 | 17px(4.53vw) | 主标题 |
| 正文 | 15px(4vw) | 描述、字段值、按钮文案 |
| 注释 | 12px(3.2vw) | 时间、辅助说明、标签 |
- 字号范围 12~36(超出即违规),同一卡片内字号 ≤ 3 档。
- 超单行宽度优先省略(
text-overflow: ellipsis/-webkit-line-clamp),禁止靠缩放字号适配。
文字色与层级(透明度)
文字颜色按"基色 + 透明度档位"组合:
- 基色:根据卡片底色取的高对比色——浅底取黑
#000、深底/品牌深色底取白#fff、源项目若有指定的卡片文字色(如咖啡棕底配米黄字)则用源色。基色优先级 = 源项目色 > 默认黑/白。 - 透明度档位:
主文 0.9/次要 0.45/辅助 0.3,三档共用同一基色(即rgba(基色, 0.9 / 0.45 / 0.3))。 - 暗黑模式下基色翻转为白系(或源项目暗黑配色里指定的字色),透明度档位保持一致。
禁止:
- 不要用色相区分层级——主文红、次要灰、辅助蓝这类混搭违规。要分主次,靠同一基色的 0.9 / 0.45 / 0.3 透明度。
- 品牌色字 / 彩色字仅允许用于主按钮文字或关键强调字段(价格、状态标签、徽章),且使用必须与源项目视觉一致;不用于普通正文 / 描述 / 标题。
font-weight仅用400/500/600;不写font-family(跟随系统),不用衬线 / 斜体 / 装饰字体。
---
五、布局
仅允许两种布局:
- 上下布局(
flex-direction: column):图文罗列,如商品列表。 - 左右布局(
flex-direction: row):强字段对比,如"快车 ¥59 / 专车 ¥88"并排。
禁止:
- 横向滚动堆叠仅限
<scroll-view scroll-x="true">(容器不支持纵向滚动,且scroll-y不可用)。如需横滚展示一行多卡,用<scroll-view scroll-x>包裹横向 flex 列表;如内容多到需要纵向滚动,按 §7.2 决策处理(半屏展示完整数据),不可以用纵向滚动绕开,也不要用上行api/call重调接口来替代半屏。 - >2 列网格——容器窄、会把字号挤到不可读;源页面 ≥2 列网格一律降到 2 列或上下流。
---
六、操作区(按钮)
数量 ≤ 3,主动作 ≤ 1,必须区分主次:
| 数量 | 布局 |
|---|---|
| 1 个 | 单主按钮,占底部主操作位(整行或居右) |
| 2 个 | 双按钮并排:等权 → 等宽;不等权 → 主按钮居右,副按钮居左 |
| 3 个 | 1 主按钮 + 最多 2 个辅助图标;主按钮在右/底,辅助操作图标化收窄 |
| ≥ 3 个主动作 | 不允许——必须收纳:仅保留 1 主按钮,其余降级图标;还多就删 |
文案硬规则:
- 动宾结构,明确告知结果:合法"立即预订 / 去下单 / 选择拿铁 / 查看详情 / 加入购物车"。
- 禁止模糊文案:"点击这里 / 更多 / 确定 / OK / 去 / 看看 / 下一步"(除非确属分步表单第 N 步)。
- 长度 ≤ 8 字,超出改写不折行。
视觉硬规则:
- 主按钮用源项目品牌主色(或默认微信蓝
#576B95)作底 / 文字;线框主题下用描边版本。副按钮用"线框 + 主文透明度",不得比主按钮更醒目。 - 按钮高度
10.67vw(≈ 40px),圆角4px,不做胶囊。 - 按钮
bindtap+hover-class+ 上行text + api/call,协议见COMPONENT_TEMPLATES.md。
---
七、高度预估与溢出处理
任何组件生成前都应评估内容是否会超出容器。溢出时优先换档,换档仍溢出则按内容形态自动决策(纵向→半屏,横向→scroll-x)。
7.1 高度预估
公式(375px 视口基准,宿主 title 已排除在 maxHeight 之外):
1. maxHeight = 375 × 高度数 ÷ 宽度数
2. footerHeight = 按钮行数 × 48 (单行按钮 48px;双行按钮 96px)
3. availableHeight = maxHeight − 24(内边距) − 25(标题) − footerHeight − 16(安全余量)
4. estimatedContentHeight = 列表项数 × 单项高度
5. 判定:见 7.2 决策流程按钮行数判定:
- 单行(48px):仅有半屏入口按钮,或仅有业务按钮
- 双行(96px):同时存在半屏入口("查看全部")+ 其他功能按钮(如"加入购物车"/"立即预订")
单项高度参考(按项内行数估算,每行含行高+间距约 21px,项间距 8~16px):
| 项内结构 | 标准高度 | 标准高度(vw) | 紧凑高度(可压缩) | 紧凑高度(vw) | 典型场景 |
|---|---|---|---|---|---|
| 1 行文字 | ~30px | ~8vw | ~24px | ~6.4vw | 标签、单行选项 |
| 2 行文字 | ~50px | ~13.33vw | ~40px | ~10.67vw | 名称+描述 |
| 3 行文字(名称+描述+辅助) | ~65px | ~17.33vw | ~52px | ~13.87vw | 带地址/价格的列表项 |
| 缩略图+2行文字(左右布局) | ~80px | ~21.33vw | ~64px | ~17.07vw | 商品卡片 |
高度单位必须使用 `vw`(换算关系:1vw ≈ 3.75px,详见ATOMIC_COMPONENT_CSS.md"长度单位"章节)。列表项高度必须严格拟合上表中的参考值(标准或紧凑),禁止自行凭感觉设定任意高度——偏离这些档位会导致内容溢出或留白过大。选型依据:先根据项内结构匹配上表行,再根据可用空间决定用标准还是紧凑档位。紧凑高度通过缩小间距(项间距 8px→4px)、文本省略为单行、减小图片尺寸实现。当按钮双行导致空间紧张时,可用紧凑高度多放 1 项。
速查表(1:1 档,按钮行数 × 单项高度组合):
| 按钮行数 | availableHeight | ~17.33vw 标准 | ~13.87vw 紧凑 | ~21.33vw 标准 | ~17.07vw 紧凑 |
|---|---|---|---|---|---|
| 单行 | ~69.87vw | 4 项 | 5 项 | 3 项 | 4 项 |
| 双行 | ~57.07vw | 3 项 | 4 项 | 2 项 | 3 项 |
若 estimatedContentHeight > availableHeight 则溢出。
7.2 溢出决策
estimatedContentHeight ≤ availableHeight → 无溢出,正常生成
│
└─ 溢出 → 步骤 A:能否换到更大档位?
│
├─ 当前非 1:1 且换到更大档位后 availableHeight ≥ estimatedContentHeight
│ → 换档(如 4:3→1:1),正常生成
│
└─ 已经是 1:1 或换档仍溢出 → 步骤 B:按内容形态自动决策处理方式
│
├─ 内容天然横向(分类标签/推荐卡片/时段选择)→ 横向滚动
│
└─ 内容天然纵向(列表/详情/图文/表格/步骤)→ 半屏自动决策,不询问用户(仅边界场景才问)。同一卡片可混合使用。
7.3 半屏实现要点
1. 卡片内做摘要/截断(列表 slice + omittedCount;详情仅展示关键字段) 2. WXML 末尾加"查看全部/详情"按钮,tap 调 viewCtx.openDetailPage({ url }) 3. 半屏页面复用项目内已有页面,展示完整数据 + 可上行 api/call
完整 API 见 references/HALF_SCREEN.md。
7.4 横向滚动实现要点
1. <scroll-view scroll-x="true"> 包裹横向 flex 列表,禁 scroll-y 2. 单项宽度固定,末尾可加渐变遮罩暗示可滑
代码生成模板
第五阶段使用。包含utils/util.js/ 原子接口 /index.js/mcp.json/SKILL.md的代码模板,以及app.json/project.config.json配置片段。
⚠️ 独立运行原则:所有代码运行在独立分包skills/中,与主包 JS 环境完全隔离。不得出现getApp()、跨包require/import。工具函数、配置、初始化逻辑必须自包含在技能目录内。
>
⚠️ 目录分层原则:工具函数统一放在utils/目录下,与apis/同级;apis/目录只存放在 `mcp.json` 中注册的原子接口,不要混入工具函数,以保持接口与工具层的清晰边界。
>
⚠️ 日志必写原则:原子接口和原子组件在关键节点(入口/入参/请求前后/出口/catch;组件的 created/setData/attached)打[ai-mode]前缀的console.info日志,这是真机验证失败时唯一的排查依据。
目录
- 一、utils/util.js 工具函数
- 1.1 必选:返回值工厂
- 1.2 按需:云开发初始化
- 1.3 按需:主包 storage 初始化迁移
- 1.4 按需:HTTP 请求
- 1.4.1 按需:登录鉴权
- 1.5 按需:JSAPI 封装
- 1.6 按需:接口间数据传递
- 二、原子接口模板
- 三、index.js 注册模板
- 四、mcp.json 模板
- 五、SKILL.md 模板
- 六、app.json + project.config.json 配置
---
一、utils/util.js 工具函数
utils/util.js 是分包的工具层,位于 utils/ 目录下,与 apis/ 同级。按实际需要按需组合以下能力块,只保留真正用到的部分。不要把所有块都写进来。
若工具函数较多,可以在utils/下再拆分文件(如utils/request.js、utils/login.js),但禁止把这些工具函数放到 `apis/` 下——apis/只能放mcp.json注册的原子接口。
1.1 必选:返回值工厂(每个 skill 都需要)
// utils/util.js — 始终包含
function errorResult(msg) {
return { isError: true, content: [{ type: 'text', text: msg }] }
}
function successResult(msg, structuredContent) {
const result = { isError: false, content: [{ type: 'text', text: msg }] }
if (structuredContent !== undefined) result.structuredContent = structuredContent
return result
}
module.exports = { errorResult, successResult /* 按需追加其它导出 */ }1.2 按需:云开发初始化(使用 wx.cloud.* 时才需要)
let _cloudInited = false
function ensureCloudInit() {
if (_cloudInited) return
wx.cloud.init({ env: '{从 app.js 提取的实际 env ID}', traceUser: true })
_cloudInited = true
}1.3 按需:主包 storage 初始化迁移(仅当主包 app.js 有写 storage 默认值时才需要)
若主包 app.js 中存在 wx.setStorageSync('key', defaultValue) 初始化语句,需在分包内迁移,否则跳过此块:
let _storageInited = false
function ensureStorageInit() {
if (_storageInited) return
// 从主包 app.js 扫描到的 setStorageSync 语句,按原样迁移到此处
_storageInited = true
}1.4 按需:HTTP 请求(使用 wx.request 时才需要)
鉴权方式完全以主包 request 封装为准——有些项目用 header token、有些用 cookie、有些用签名、有些无鉴权——生成时读主包 utils/request.js 确认后再写,不要套固定模式。
token 由 ensureLogin() 获取后保存在模块级变量中,request 函数从该变量读取附加到 header,不从 storage 读取。
const BASE_URL = '{从主包提取的 baseUrl}'
let _token = '' // 模块级变量,由 ensureLogin() 写入
function request(options) {
return new Promise((resolve, reject) => {
wx.request({
...options,
url: options.url.startsWith('http') ? options.url : BASE_URL + options.url,
header: Object.assign(
{ '{鉴权 header 字段名}': _token }, // 按主包实际方式替换
options.header
),
success(res) {
res.statusCode >= 200 && res.statusCode < 300
? resolve(res.data)
: reject(new Error(`HTTP ${res.statusCode}`))
},
fail: reject
})
})
}1.4.1 按需:登录鉴权(接口需要登录态时才需要)
何时需要:若主包中该业务接口的wx.request携带了 token / session / cookie 等鉴权信息,则分包必须实现ensureLogin(),并在每个需要鉴权的原子接口入口处await ensureLogin()。
实现规则(完全以主包登录逻辑为准,不要套固定写法):
- 分包不依赖 storage 中的登录态,每次冷启动都重新走一遍登录流程
- 登录流程与主包完全相同(
wx.login换 token),从主包源码中提取接口路径、参数、返回字段后在分包内还原 - 登录成功后将 token 保存到模块级变量(如
let _token = ''),供同一进程内的后续请求复用;不写 storage - 需防并发重复登录(多个接口同时调用
ensureLogin时只发起一次登录请求) - 关键节点打
[ai-mode]前缀日志
使用方式:在需要鉴权的原子接口文件入口处 await ensureLogin() 后再发起业务请求。1.5 按需:JSAPI 封装(仅白名单内)
按实际接口自行封装,参考主包实现。常见示例:wx.getLocation / wx.getFuzzyLocation / wx.openLocation / wx.chooseLocation / wx.requestPayment / wx.requestVirtualPayment / wx.openBusinessView(仅 wxpayScoreUse/wxpayScoreEnable)/ wx.login / wx.checkSession / wx.authorize / wx.shareAppMessage / wx.getPhoneNumber / wx.getRealtimePhoneNumber / wx.chooseMedia / wx.chooseMessageFile / wx.startFacialRecognitionVerify / wx.requestSubscribeMessage / wx.cloud.database。完整白名单见 `references/JSAPI_WHITELIST.md`(SKILL.md 的"硬性约束 C"节只列高频项)。不要模板化复制——形式由主包实现决定。
1.6 按需:接口间数据传递
接口间数据传递有两种方式,由业务逻辑决定用哪种,不要默认引入 storage 传递:
- 直接通过 `inputSchema` 参数传递:下游接口在
mcp.json的inputSchema中声明所需字段,由小程序 AI 从上游structuredContent里提取后传入,分包内无需写任何 storage 代码——这是优先选项。 - 通过 `wx.setStorageSync` 传递:仅当下游接口无法在
inputSchema中描述依赖(例如需要静默传递大量中间态)时使用。key 命名格式skills_{skillName}_{dataName}。
// 仅在确认需要 storage 传递时才加入 utils/util.js
function setStepContext(key, value) { wx.setStorageSync(key, value) }
function getStepContext(key, defaultValue) { return wx.getStorageSync(key) || defaultValue }
function removeStepContext(key) { wx.removeStorageSync(key) }---
二、原子接口模板
每个原子接口文件遵循以下骨架,内部逻辑完全由主包对应业务决定,不要套固定范式。
// apis/{apiName}.js
const { errorResult, successResult } = require('../utils/util')
// 按需追加其它 utils 导入,如 require('../utils/request')
async function {apiName}(params = {}) {
console.info('[ai-mode] {apiName} 入口, params=', JSON.stringify(params))
try {
// 1. 参数校验(仅校验真正必须的字段)
// 2. 执行业务逻辑(网络请求 / JSAPI / storage 读写,完全对照主包)
// 3. 整理返回值,返回 successResult
return successResult('描述结果的一句话', { /* structuredContent */ })
} catch (err) {
console.error('[ai-mode] {apiName} 出错:', err.message)
return errorResult(`操作失败: ${err.message}`)
}
}
module.exports = {apiName}生成原则:
- 业务逻辑完全以主包为准,不要根据模板臆造字段名、接口路径或 storage key。
- 只校验真正影响业务的必填参数,不要过度防御。
- 有需要传递数据给下游的,先考虑 outputSchema + 小程序 AI 传参;确实需要 storage 传递时再用。---
三、index.js 注册模板
3.1 基础模式(无中间件)
// skills/{skillName}/index.js
// 按 mcp.json 中 apis[].name 一一注册,三者必须完全一致:
// require 导入名 = registerAPI 第一参数 = mcp.json name
const {apiName1} = require('./apis/{apiName1}')
const {apiName2} = require('./apis/{apiName2}')
wx.modelContext.registerAPI('{apiName1}', {apiName1})
wx.modelContext.registerAPI('{apiName2}', {apiName2})3.2 中间件模式(推荐用于需要统一登录态 / 上报 / 错误监听的场景)
Koa 式洋葱模型的中间件,每个原子接口都会执行一遍,可用于统一登录态、统一上报和错误监听等场景。
// skills/{skillName}/index.js
const {apiName1} = require('./apis/{apiName1}')
const {apiName2} = require('./apis/{apiName2}')
const skill = wx.modelContext.createSkill('skills/{skillName}')
// 注册中间件(按需组合,执行顺序 = 注册顺序)
skill.use(async (ctx, next) => { // ← 中间件 1:鉴权
console.info('[ai-mode] middleware: ensureLogin')
await next()
})
skill.use(async (ctx, next) => { // ← 中间件 2:上报
try {
await next()
} finally {
console.info('[ai-mode] middleware: report', { name: ctx.name })
}
})
// 注册原子接口(等同于 wx.modelContext.registerAPI)
skill.registerAPI('{apiName1}', {apiName1})
skill.registerAPI('{apiName2}', {apiName2})中间件 context 属性:
ctx.name— 原子接口名称ctx.skillPath— 中间件执行时的 skillPathctx.arguments— 传递给原子接口的参数的副本,修改后不影响传递给原子接口的真实参数值
使用原则:当多个原子接口有相同的前置逻辑(如ensureLogin()、统一错误捕获)时,优先用中间件模式替代在每个接口入口处重复调用。中间件与ensureLogin()二选一,不要混用。
---
四、mcp.json 模板
{
"apis": [
{
"name": "{apiName}",
"description": "{完整描述接口行为,含内部操作与前置依赖}",
"_meta": { "ui": { "componentPath": "components/{componentName}/index" } },
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"{param}": { "type": "string", "description": "{参数含义}" }
},
"required": ["{param}"],
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {},
"additionalProperties": false
}
}
]
}多模态入参(接收用户上传图片):当接口需要图片时,对应字段类型为 string 并加 "format": "image",运行时填本地图片路径;小程序 AI 输入框据此识别为多模态字段并引导用户上传。
{
"name": "editPhoto",
"description": "帮用户 P 图",
"inputSchema": {
"type": "object",
"properties": {
"imagePath": { "type": "string", "format": "image", "description": "本地图片路径" },
"query": { "type": "string", "description": "用户的 P 图需求" }
},
"required": ["imagePath", "query"]
}
}components[]段(relatedPage/ 网络能力 /expirable+expiredText)见SKILL.mdC.3 / C.3.1;components[].path必须与对应接口的_meta.ui.componentPath字符串完全相等。expirable/expiredText默认不写,仅在源业务确实有"卡片作废"语义时才声明,并在代码里相应调用wx.modelContext.expireAllCards()(接口或组件均可,含自身)或wx.modelContext.getViewContext(this).expirePreviousCards()(仅原子组件可调用,不含自身)。
运行时给 `relatedPage` 附加 query(在组件 created 里现取 viewCtx,按 Result 数据动态拼):
const viewCtx = wx.modelContext.getViewContext(this)
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = data.result && data.result.structuredContent
if (sc && sc.orderId) viewCtx.setRelatedPage({ query: `orderId=${sc.orderId}` })
})---
五、SKILL.md 模板
定位:SKILL.md 是技能的路由说明,目标 = 让调度方在最少 token 内做对三件事:① 判断"用户当前需求该不该路由到我";② 判断"我能/不能做什么";③ 在多 skill 共存时不抢别人的活。
写入内容清单(只能写下表中的 5 类,按顺序排列;超出此清单一律不写):
| # | 章节 | 写什么 | 不写什么 | 何时省略 |
|---|---|---|---|---|
| 1 | 能力域定位 | 一句话锚点,位于 # 标题 下首行 | 多段落叙事、emoji、口号 | 不可省略 |
| 2 | 触发场景 | 3~6 条用户原话 few-shot,每条是一句真实用户口吻的自然语言(口语、片段、含俚语都行),覆盖不同表达方式 | 关键词清单;技术术语;照抄 mcp.json.apis[].description | 不可省略 |
| 3 | 不适用范围 | 反例短句("xx 诉求 → 不在本技能范围 / 由 yy 技能处理") | 自我否定式废话 | 项目内无易混淆兄弟 skill 时整节省略 |
| 4 | 前置条件 | 影响是否可路由的硬约束(已登录 / 授权 / 区域 / 账号资质) | 实现细节、token 来源、storage 初始化等技术内容 | 无前置条件时整节省略,不要写"无"占行 |
| 5 | 使用顺序 | 能力之间业务依赖的自然语言短句("加入购物车前需先检索到具体商品") | 流程图、依赖图、表格、apiName、storage key | 各能力相互独立时整节省略 |
硬性约束(通篇生效,命中即重写):
- 不出现驼峰 apiName,所有章节均为业务中文
- 不出现
inputSchema/outputSchema/ 参数表 / 返回值表 /_meta.ui.componentPath/ 组件路径 / JSON Schema 片段(接口契约只在mcp.json单一来源维护) - 不出现 storage key 清单 / 接口依赖图(图状或表格化的形式;这些是阶段 4 的内部产出物)
- 不写安装 / CLI / 部署 / 如何使用本技能 等运维文档
# {技能业务名,中文,例:商品检索与下单}
{一句话能力域定位。例:基于商品库进行关键词检索、查看详情、加入购物车并完成下单的能力集合。}
## 触发场景
用户原话举例(路由命中本技能):
- "帮我搜下有没有那种轻便的{品类}"
- "我想买{品牌}的,有什么推荐"
- "把刚才那个加到购物车"
- "结一下账吧"
- ...(3~6 条;用真实用户口吻,覆盖不同表达方式;不要写关键词清单或技术术语)
## 不适用范围
- {反例 1,例:售后退款相关诉求 → 不在本技能范围}
- {反例 2,例:会员积分查询 → 由会员技能处理}
- ...(项目内无易混淆兄弟 skill 时整节省略)
## 前置条件
- {影响是否可路由的硬约束,例:需用户已登录 / 需定位授权 / 仅 xx 城市可用}
- ...(无前置条件时整节省略;不要写"无"占行)
## 使用顺序
- {自然语言短句描述能力之间的业务依赖,例:加入购物车前需先检索到具体商品;查看订单详情需先有订单号}
- ...(各能力相互独立时整节省略)---
六、app.json + project.config.json 配置
{
"lazyCodeLoading": "requiredComponents",
"agent": {
"skills": [{ "name": "...", "description": "...", "path": "skills/..." }]
},
"subPackages": [{
"root": "skills",
"independent": true,
"pages": []
}]
}⚠️ `lazyCodeLoading` 由开发者在使用 skill 前自行添加,generate 不要写入此字段(只用于展示完整目标形态)。阶段 1 扫描时若app.json顶层缺该字段,按阻断规则 B 终止流程并提示用户去补;不要"代为补全"或"忽略继续"。本次 generate 的写入范围只有agent和subPackages两块。
多 skill 共用一个独立分包:subPackages里root: "skills"指外层目录,多个 skill(skills/foo/、skills/bar/)整体作为同一个独立分包。新增 skill 时只在agent.skills[]数组里追加一项{ name, description, path: "skills/<新>" },不要为每个 skill 再加一条subPackages条目。
project.config.json 确保 packOptions.include 含 { "type": "folder", "value": "skills" }。
原子组件模板参考
组件用于原子接口的结果展示。每个原子接口返回的structuredContent会通过NotificationType.Result传递给组件。
>
⚠️ 本文件是骨架规范,不是照抄目标。配色、字号、圆角、间距必须从源页面.wxss提取;字段名必须按源 API 实际返回结构做归一化映射,不要假设源数据会返回imageUrl/title等通用名。
⚠️ 重要限制
原子组件仅支持 `tap` 点击事件,不支持其它交互事件(如 touch)。
支持的内置组件:view(支持 hover-class)、text(不支持 user-select)、image(仅网络地址)、map、button(不含 `open-type`)、canvas、scroll-view(仅横滚 `scroll-x`,禁 `scroll-y`)。不支持 swiper / input / textarea / picker / checkbox / radio / form / slider / switch / editor / rich-text / navigator / web-view / movable-* / root-portal 等其他内置组件。
`button` 特殊约束:不可使用 open-type。源代码里的 <button open-type="share"> / open-type="getPhoneNumber" 等必须改为 <button bindtap="..."> + 在 tap handler 中调用 wx.shareAppMessage / wx.getPhoneNumber 等对应 JSAPI。
不支持网络请求和云开发接口(组件侧不支持直接调用 wx.request,若需使用网络能力需声明 permissions.scope.dynamic)。
渲染容器约束:宽度随屏幕宽度变化,最小高度为宽高比 4:1,最大高度为宽高比 1:1,不超出范围时随内容自动撑高,不支持纵向滚动,超出裁剪(横向超长内容可用 <scroll-view scroll-x="true"> 包裹,仅横向滚动)。外层容器尺寸由宿主自动设置,组件根节点不要再写 `max-height` / `min-height` / `height`——一旦组件自己设了高度,宿主的 NotificationType.Overflow 回调将无法触发,溢出无法被检测。卡片比例必须从 `1:1 / 4:3 / 16:9 / 3:1 / 4:1` 五档中选一(见 ATOMIC_COMPONENT_DESIGN.md 第一章);实现层细节见 ATOMIC_COMPONENT_CSS.md。
组件不可声明为虚拟组件。
---
上行消息(发送给小程序 AI)
组件本身没有页面导航能力。用户对组件内元素的任何点击/操作,都必须通过 `modelCtx.sendFollowUpMessage` 把"结构化 toolCall 事件"上行给小程序 AI,由小程序 AI 拿到 name + arguments 直接路由到对应原子接口。组件不直接调用原子接口。
上行协议(硬性,不可改写)
// 形态 1:单 text(无对应接口时)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [{ type: 'text', text: '换一批推荐' }],
})
// 形态 2:text + api/call 组合(推荐,有对应接口时)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: '选择拿铁' },
{ type: 'api/call', data: { name: 'selectGoods', arguments: { goodsId: 123 } } },
],
})核心规则:content 是数组;api/call 必须前面有 text(给小程序 AI 用户上下文),不能单独发;text 可以单独发;name 必须在 mcp.json.apis[] 中存在,arguments 与该接口 inputSchema 对齐。
硬性规则(生成组件时必须遵守)
1. 每个可交互元素都绑 `bindtap` + `hover-class`。列表 item / 按钮 / "查看全部"都不能是纯展示。 2. tap handler 优先用形态 2,无对应接口时才用形态 1。arguments 值从 e.currentTarget.dataset / this.data 取,不用占位值。 3. `text` 是用户视角的简短中文(≤ 12 字,如"选择拿铁"),从 dataset 可读字段拼出,不是 args 序列化。 4. 每次上行 `api/call` 前打一行 console.info:[ai-mode] {componentName} send api/call name=<name> args=<JSON>。 5. 组件不直接调原子接口 / 业务 API;不上行不存在的 name。 6. 主动调用 `sendFollowUpMessage` / `getDimensions` 必须现取 ctx——在 methods / tap handler / 异步回调里,当场 wx.modelContext.getContext(this).sendFollowUpMessage(...)(或 getViewContext(this) 对应方法)。禁止通过 this._modelCtx / this._viewCtx 之类的实例缓存引用去调方法(小程序 AI 宿主可能复用组件实例承接多轮结果,旧 ctx 会被标记为过期)。created 里用临时局部变量绑 on(...) 之后是否把 modelCtx / viewCtx 再存一份到 this 不影响——因为 on(...) 的回调已由 SDK 内部持有。同理,按需调用的卡片过期 API(wx.modelContext.expireAllCards() / getViewContext(this).expirePreviousCards(),详见 SKILL.md C.3.1)也要在使用点现取 ctx,不要缓存。
api/call 模板(按交互类型选 name + arguments)
按"当前组件在 API 依赖图中的下一跳"选择 name,arguments 按该接口 inputSchema 填:
| 交互类型 | 选哪个 name(示例) | arguments 要带什么 |
|---|---|---|
| 列表项 → 查看详情 | searchItemDetail / searchOrderDetail 这类详情接口 | 被点对象的 id(如 { itemId: 1528954 } / { orderId: 12345 }) |
| 列表项 → 继续下一步业务 | 依赖图下游接口(如选商家后 → searchSchedule) | 已选对象的 id + 当前组件上下文必需入参(如 { storeId, itemId, date, time }) |
| "换一换" / "换一批" | 同当前展示能力的检索接口(如 searchItems) | 带上一轮 query + { refresh: true }(仅当接口 inputSchema 有该字段)。注意:"查看更多/全部"不走上行 api/call,走半屏 viewCtx.openDetailPage()(见 §7.3) |
| 详情页主 CTA(购票 / 加购 / 预约 / 支付) | 触发业务动作的下一跳接口(如 bookingItem / createOrder) | 当前详情对象的关键标识(seqNo / orderId / sessionId / quantity / gradeId 等) |
| 多选/枚举型选择(座位、时段、规格) | 与"选择结果"挂钩的下一跳接口(如选完座 → order) | 所选实体集合(如 { seqNo, seats: [{ row, column }, ...] }) |
| 状态结果页引导继续 | 紧邻的状态收束接口(如支付完成后看订单 → listMyOrders,若存在) | 过渡所需的最小入参,允许空对象 {} |
`name` 必须在 `mcp.json.apis[]` 内。如果当前 skill 还没有对应下一跳接口(例如"支付成功后看订单"但没有listMyOrders),就去掉那个按钮,而不是上行一个不存在的name。
>
arguments字段名与该接口inputSchema.properties必须一致;值类型必须符合 schema(number 就是 number,不要传"123"字符串)。
代码样例(WXML + JS)
<!-- components/display-items/index.wxml 节选 -->
<view wx:for="{{items}}" wx:key="id"
class="item-card"
hover-class="item-card-hover"
bindtap="onTapItem"
data-id="{{item.id}}"
data-name="{{item.name}}">
<image src="{{item.image}}" class="item-image" mode="aspectFill" />
<view class="item-name">{{item.name}}</view>
<text class="item-price">¥{{item.price}}</text>
</view>
<view class="more-btn" hover-class="more-btn-hover" bindtap="onTapMore">换一批</view>// components/display-items/index.js 的 methods 节选
methods: {
// 小工具:统一打 [ai-mode] 日志 + 上行 [text, api/call] 组合(使用点现取 ctx,不用 this._modelCtx 等缓存引用)
_sendUserAction(text, name, args) {
console.info(`[ai-mode] display-items send api/call name=${name} args=${JSON.stringify(args)}`)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text },
{ type: 'api/call', data: { name, arguments: args } },
],
})
},
onTapItem(e) {
const { id, name } = e.currentTarget.dataset
// 详情页依赖图下一跳:searchItemDetail(itemId);text 用商品名,给用户视角的可读上下文
this._sendUserAction(`查看 ${name}`, 'searchItemDetail', { itemId: Number(id) })
},
onTapMore() {
// 同能力重检索 —— name 仍是 searchItems;arguments 按该接口 inputSchema 填
this._sendUserAction('换一批', 'searchItems', { query: this.data.lastQuery || '热门推荐' })
},
}卡片过期(按需,非强制)
默认不生成。仅当业务上确实有"该卡片应作废、不应再被点"语义时使用。详见SKILL.mdC.3.1。前提是mcp.json.components[]对应记录已声明expirable: true+ 业务化expiredText,否则调用为空操作。
// 形态 A:写操作型动作完成后,让所有可过期卡片(含自身)一并失效——接口或组件均可
await wx.modelContext.expireAllCards()
// 形态 B:仅过期"此前已渲染的同类卡片",自身不过期——仅原子组件可调用
await wx.modelContext.getViewContext(this).expirePreviousCards()
// 形态 C:精细过滤(A/B 两个 API 都支持)——只过期匹配特定 componentPath 的卡
await wx.modelContext.expireAllCards({
componentPaths: ['packageA/weather-skill/components/weather-card/index'], // 绝对路径,含分包前缀;多条取并集
})
// 形态 D:在 C 的基础上加 match: 'latest',只过期最近一张匹配卡
await wx.modelContext.getViewContext(this).expirePreviousCards({
componentPaths: ['packageA/weather-skill/components/weather-card/index'],
match: 'latest',
})A/B 二选一,不要同时调;调用前后建议各打一行 [ai-mode] {componentName} expire... done|fail 日志。
半屏页面跳转(溢出时必须生成,其他按需)
高度预估溢出且换档仍无法容纳 → 必须挂半屏。入口仅在组件methods内。完整 API 见references/HALF_SCREEN.md。
Component({
methods: {
showDetail(e) {
const id = e.currentTarget.dataset.id
const viewCtx = wx.modelContext.getViewContext(this)
viewCtx.openDetailPage({ url: `/pages/detail/index?id=${id}` }) // 项目内已有的页面路径
},
},
})溢出处理模板
方式一:半屏展示(纵向内容默认)
WXML:
<!-- 列表项:仅渲染 visibleItems(top-N) -->
<view wx:for="{{visibleItems}}" wx:key="id"
class="item-card" hover-class="item-card-hover"
bindtap="onTapItem"
data-id="{{item.id}}" data-name="{{item.name}}">
<image src="{{item.image}}" class="item-image" mode="aspectFill" />
<view class="item-name">{{item.name}}</view>
<text class="item-price">¥{{item.price}}</text>
</view>
<!-- 溢出提示 + 查看全部按钮 -->
<view wx:if="{{omittedCount > 0}}"
class="view-all-btn" hover-class="view-all-btn-hover"
bindtap="onTapViewAll">
查看全部(共 {{totalCount}} 条)
</view>JS:
Component({
data: {
visibleItems: [],
omittedCount: 0,
totalCount: 0,
allItems: [], // 保留全量数据供半屏使用
},
lifetimes: {
created() {
console.info('[ai-mode] {componentName} created')
const { NotificationType } = wx.modelContext
const viewCtx = wx.modelContext.getViewContext(this)
const { minHeight, maxHeight, width } = viewCtx.getDimensions()
console.info(`[ai-mode] {componentName} dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
// 记录容器尺寸,用于计算 maxVisibleItems
this._maxCardHeight = maxHeight
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = data.result && data.result.structuredContent
console.info('[ai-mode] {componentName} 收到 Result:', sc)
if (!sc || !sc.items) return
const allItems = (sc.items || []).map(item => ({
// 字段归一化(按 STYLE_MIGRATION.md 步骤3 映射表)
id: item.id || item._id,
name: item.name || item.title || '',
image: item.cover || item.pic || item.thumb || item.image || '',
price: item.price || item.salePrice || 0,
}))
// 核心:按容器高度计算可容纳条数
const ITEM_HEIGHT_PX = 60 // 与 wxss 中 item 高度对应,按实际设计调整
const HEADER_PX = 25 // 标题区
const FOOTER_PX = 48 // 操作区
const PADDING_PX = 24 // 容器内边距 12*2
const available = this._maxCardHeight - HEADER_PX - FOOTER_PX - PADDING_PX
const maxVisible = Math.max(1, Math.floor(Math.max(0, available) / ITEM_HEIGHT_PX))
const visibleItems = allItems.slice(0, maxVisible)
const omittedCount = allItems.length - visibleItems.length
this.setData({
visibleItems,
omittedCount,
totalCount: allItems.length,
allItems, // 供半屏使用
})
console.info(`[ai-mode] {componentName} setData total=${allItems.length} visible=${visibleItems.length} omitted=${omittedCount}`)
})
// 必做:溢出监听
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(`[ai-mode] {componentName} overflow overflowed=${overflowed} data=${JSON.stringify(data)}`)
})
console.info('[ai-mode] {componentName} overflow monitor=on')
}
},
methods: {
_sendUserAction(text, name, args) {
console.info(`[ai-mode] {componentName} send api/call name=${name} args=${JSON.stringify(args)}`)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text },
{ type: 'api/call', data: { name, arguments: args } },
],
})
},
onTapItem(e) {
const { id, name } = e.currentTarget.dataset
this._sendUserAction(`查看 ${name}`, 'searchItemDetail', { itemId: Number(id) })
},
// 溢出时:查看全部 → 打开半屏
onTapViewAll() {
const viewCtx = wx.modelContext.getViewContext(this)
// 复用项目内已有页面,把数据通过 storage 或 query 传递
// storage key 格式:skills_{skillName}_{dataName}
wx.setStorageSync('skills_{skillName}_allItems', this.data.allItems)
viewCtx.openDetailPage({
url: '/pages/{existingListPage}?fromAI=1'
})
console.info('[ai-mode] {componentName} openDetailPage url=/pages/{existingListPage}')
},
}
})方式二:横向滚动(横向排列型内容)
WXML:
<scroll-view scroll-x="true" class="scroll-container">
<view class="scroll-content">
<view wx:for="{{items}}" wx:key="id"
class="scroll-item" hover-class="scroll-item-hover"
bindtap="onTapItem"
data-id="{{item.id}}" data-name="{{item.name}}">
<image src="{{item.image}}" class="item-image" mode="aspectFill" />
<view class="item-name">{{item.name}}</view>
</view>
</view>
</scroll-view>WXSS:
.scroll-container {
width: 100%;
white-space: nowrap;
}
.scroll-content {
display: flex;
flex-direction: row;
gap: 2.13vw; /* 8px */
}
.scroll-item {
flex-shrink: 0;
width: 26.67vw; /* 100px,按实际设计调整 */
/* 其他样式按设计规范 */
}质量自检
生成完组件后对照以下清单勾选;任一不满足须补齐:
- [ ] 符合 `ATOMIC_COMPONENT_DESIGN.md`:尺寸档位(5 档之一,
index.wxss顶部注释)、圆角 4px、整 skill 一套主题变量(浅色 + 暗黑两套,颜色按 §2.1 流程从主包 `app.json`/`app.wxss` 等位置提取,且 wxss 顶部注释写出"色源 = …"链路,通过@media (prefers-color-scheme: dark)切换)、12px 内边距、17/15/12 字号 + 同一基色 0.9/0.45/0.3 透明度分层、操作区 ≤3 且主动作 ≤1、按钮文案动宾结构 - [ ] 高度预估与溢出:已按 §7 预估;溢出时优先换档,换档仍溢出已自动生成半屏或横向滚动
- [ ] 每个可交互元素都有
bindtap+ 配对的hover-class - [ ] 优先使用形态 2(
text+api/call组合);只有无法映射到原子接口时才用形态 1(单text) - [ ] 若
content里有api/call,前面必须有至少一项type: 'text';禁止单独发api/call - [ ]
text是用户视角的简短中文(≤ 12 字)、从 dataset 可读字段拼出,不是 args 的 JSON 序列化 - [ ] 所有
data.name都在当前 skill 的mcp.json.apis[].name中存在 - [ ] 所有
data.arguments字段名与目标接口inputSchema.properties完全对齐,必填项齐全、无多余字段,值类型匹配 - [ ]
arguments里的值取自e.currentTarget.dataset或this.data,不是占位值 - [ ] 每次上行
api/call前都打了一行[ai-mode] {componentName} send api/call name=... args=...console.info - [ ] 没有组件层面的直接业务 API 调用(组件不
wx.request业务接口;网络调用必须在原子接口里完成) - [ ] 点击路径覆盖了
mcp.json.apis[].description中描述的全部下一跳(避免"按钮可见但没绑 api/call"或"按钮映射到不存在的 name") - [ ]
created中已绑定NotificationType.Overflow监听,且在绑定后同步打出基线日志[ai-mode] {componentName} overflow monitor=on;事件触发时再打[ai-mode] {componentName} overflow overflowed=<true|false> data=<JSON>(校验侧据此判断是否有裁剪,不依赖截图)
---
组件 JS 骨架(所有组件通用)
每个组件的 index.js 必须通过 wx.modelContext.getContext(this) 监听 NotificationType.Result,在收到结果后做字段归一化并 setData。关键节点必须打 [ai-mode] 前缀日志。
必做:卡片溢出日志(wxa-skills-validate 的 render 核对会直接读取这条日志判断卡片是否被裁剪)
- 通过
wx.modelContext.getViewContext(this)拿到视图上下文,监听NotificationType.Overflow - 绑定监听成功后,立即同步打一行基线日志
[ai-mode] {componentName} overflow monitor=on,用于告知校验侧"监听已到位" - 每次 Overflow 事件触发,再打一行
[ai-mode] {componentName} overflow overflowed=<true|false> data=<JSON>(JSON 序列化完整data,至少含contentHeight/overflowHeight/maxHeight) - 该日志是校验侧判"是否有内容被裁剪"的主要依据;截图仅作为辅助
Component({
data: { /* 按实际渲染字段定义 */ },
lifetimes: {
created() {
console.info('[ai-mode] {componentName} created')
const { NotificationType } = wx.modelContext
// 仅在 created 里用临时局部变量绑定监听;on(...) 注册后回调由 SDK 持有,无需挂到 this
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = data.result && data.result.structuredContent
console.info('[ai-mode] {componentName} 收到 Result:', sc)
if (!sc) return
// 在此按源 API 实际返回字段做归一化,然后 setData
this.setData({ /* 归一化后的字段 */ })
})
// 获取视图上下文:容器尺寸 + 溢出监听
const viewCtx = wx.modelContext.getViewContext(this)
const { minHeight, maxHeight, width } = viewCtx.getDimensions()
console.info(`[ai-mode] {componentName} dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
// ⚠️ 必做:监听卡片是否溢出裁剪
// data 字段:contentHeight=内容总高、overflowHeight=溢出高度、maxHeight=容器硬阈值
// overflowHeight > 0 即表示内容被裁剪
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(
`[ai-mode] {componentName} overflow overflowed=${overflowed} ` +
`data=${JSON.stringify(data)}`
)
})
// 基线日志:监听已绑定;即使 SDK 未派发 Overflow 事件,也能据此判定为"未裁剪"
console.info('[ai-mode] {componentName} overflow monitor=on')
// 注:是否把 modelCtx / viewCtx 挂到 this 不重要,on(...) 注册后回调已由 SDK 持有;
// 真正的规矩在"使用点":任何在 methods / tap handler / 异步回调里主动调用
// sendFollowUpMessage / getDimensions 时,必须重新 wx.modelContext.getContext(this)
// (或 getViewContext(this))现取;禁止使用 this._modelCtx 之类的缓存引用去调方法。
}
}
})校验侧判定规则:
-consoleMessages.snapshotCard中必须能搜到[ai-mode] <component> overflow monitor=on基线日志;否则视为未接入监听,render 核对不通过。
- 如果还能搜到[ai-mode] <component> overflow overflowed=true ...(或data.overflowHeight > 0)→ 判定为裁剪,不通过。
- 如果只有monitor=on基线日志、没有任何overflowed=true(或根本没有overflowed=...行)→ 判定为未裁剪,通过。
---
组件类型选用
| 返回值类型 | 推荐组件形态 |
|---|---|
| 列表、搜索结果、推荐商品等可迭代数据 | 列表型:wx:for 遍历 items,每项展示图文+操作按钮 |
| 详情、用户卡片、单商品信息等单对象 | 详情型:大图+字段行+操作按钮 |
| 购物车、收藏夹等带数量/总价的列表 | 购物车型:列表+底部合计+结算按钮 |
| 下单成功、支付结果、操作确认等状态 | 状态型:图标+标题+摘要+引导按钮 |
组件index.json固定为{ "component": true, "usingComponents": {} }。
>
组件 index.wxss 首行必须含"样式参考"注释块,说明对齐的源页面路径与迁移的视觉 token;若源不可读,也须写明兜底原因。半屏页面(溢出时必须生成,其他按需)
高度预估判定溢出且换档仍无法容纳时必须挂半屏(ATOMIC_COMPONENT_DESIGN.md§7.2)。卡片内展示摘要/前 N 项 + "查看全部/详情"按钮,点击viewCtx.openDetailPage()打开半屏。其他场景仅当业务确有"详情/补充信息"语义时挂上。
---
1. 调用入口
半屏页面只能在原子组件内打开(原子接口里没有 this,拿不到 viewCtx,不可调):
// 在原子组件 methods / tap handler 里
Component({
methods: {
showDetail() {
const viewCtx = wx.modelContext.getViewContext(this)
viewCtx.openDetailPage({
url: '/packageA/pages/weather-detail?foo=bar' // 项目内已有的小程序页面路径
})
},
},
})<!-- 组件 wxml -->
<view bind:tap="showDetail">查看未来 15 天的天气</view>承载页面来源:可以复用项目内已有的小程序页面(不用新写一个页面专给半屏用)。半屏打开页面的场景值为 1433 或 1434,页面里可以根据场景值切换样式。
关闭按钮位置适配:通过 wx.getDetailPageCloseButtonBoundingClientRect 拿到左上角关闭按钮的位置,避免业务内容被遮挡。
---
2. 半屏页面里上行消息
半屏页面里的"下一步"操作应当上行一段文本消息到小程序 AI,同时半屏会自动关闭回到小程序 AI 对话界面。
2.1 原生页面(小程序 Page)
Page({
onTap() {
const ctx = wx.modelContext.getContext() // 注意:半屏页面里不传 this
ctx.sendFollowUpMessage({
content: [
{ type: 'text', text: '选择拿铁' }, // 必传
{ type: 'api/call', data: { name: 'selectGoods', arguments: {} } }, // 可选,不传则由模型推理
],
})
},
})2.2 web-view 加载的 H5 页面
// 前置 wx.config()
wx.ready(function () {
WeixinJSBridge.invoke('invokeMiniProgramAPI', {
name: 'sendFollowUpMessage',
arg: {
content: [
{ type: 'text', text: '选择拿铁' },
{ type: 'api/call', data: { name: 'selectGoods', arguments: {} } },
],
},
}, function (res) {})
})上行协议(content形态、name必须在mcp.json.apis[].name已声明、arguments与inputSchema对齐)与原子组件 tap handler 完全一致——见SKILL.md §5.0.1+COMPONENT_TEMPLATES.md的"上行消息"节。
---
3. 半屏页面的接口/组件禁用清单
半屏页面执行环境与小程序页面一致,但禁用以下任何"会让用户跳出半屏"的能力。命中即视为不可迁移,按 SKILL.md C.6 处理(删除调用 / 用网络请求替代 / 触发阻断)。
3.1 跳出类(跳公众号/视频号/其他小程序/表情/问一问/地图 App)
wx.restartMiniProgram
wx.openOfficialAccountProfile
wx.openOfficialAccountChat
wx.openOfficialAccountArticle
wx.openInquiriesTopic
wx.openEmbeddedMiniProgram
wx.onEmbeddedMiniProgramHeightChange
wx.offEmbeddedMiniProgramHeightChange
wx.navigateToMiniProgram
wx.navigateBackMiniProgram
wx.exitMiniProgram3.2 页面路由
wx.switchTab
wx.rewriteRoute
wx.reLaunch
wx.redirectTo
wx.navigateTo
wx.navigateBack
wx.router
router.addRouteBuilder
router.getRouteContext
router.removeRouteBuilder3.3 聊天工具
wx.shareVideoToGroup
wx.shareImageToGroup
wx.shareFileToGroup
wx.shareEmojiToGroup
wx.shareAppMessageToGroup
wx.selectGroupMembers
wx.openChatTool
wx.notifyGroupMembers
wx.getChatToolInfo
wx.enterChatToolMode3.4 地图
MapContext.openMapApp3.5 视频号
wx.reserveChannelsLive
wx.openChannelsUserProfile
wx.openChannelsLiveNoticeInfo
wx.openChannelsLive
wx.openChannelsEvent
wx.openChannelsActivity
wx.getChannelsShareKey
wx.getChannelsLiveNoticeInfo
wx.getChannelsLiveInfo3.6 微信客服 / 微信表情
wx.openCustomerServiceChat
wx.openStickerSetView
wx.openStickerIPView
wx.openSingleStickerView3.7 广告(接口与组件)
接口:
wx.getShowSplashAdStatus
wx.createRewardedVideoAd
wx.createInterstitialAd组件:
ad
ad-custom3.8 导航组件
functional-page-navigator
navigator---
4. 与原子组件/原子接口的关系
| 维度 | 原子接口 | 原子组件 | 半屏页面 |
|---|---|---|---|
| 运行时上下文 | service worker 端 | 渲染层(含 this) | 普通小程序页面 |
调起 openDetailPage | ❌ 不可(无 this / viewCtx) | ✅ 可(getViewContext(this)) | — |
调 sendFollowUpMessage | ❌ 不可 | ✅ getContext(this) | ✅ getContext() 不传 this |
| 默认是否生成 | ✅ 必生成 | 按需(有 UI 时生成) | 默认不生成,仅业务强需要时按本指南挂上 |
---
5. 自检
- [ ] 溢出组件已生成半屏,卡片内有摘要 + "查看全部/详情"入口
- [ ] 仅在溢出或源业务确有"详情/补充信息"语义时才挂半屏
- [ ]
openDetailPage调用点在原子组件methods内,不在原子接口里 - [ ] 半屏页面 wxml/wxss 不依赖原子组件渲染容器约束(半屏页面尺寸是普通页面尺寸,不再受卡片宽高比限制)
- [ ] 半屏页面里没有任何 §3 的禁用接口/组件调用
- [ ] 半屏页面里"下一步"按钮的 tap handler 走
wx.modelContext.getContext()(不传 `this`)上行消息,半屏会自动关闭 - [ ] 若用 web-view 承载 h5,h5 里走
WeixinJSBridge.invoke('invokeMiniProgramAPI', { name: 'sendFollowUpMessage', ... }),并配wx.config()/wx.ready() - [ ] 左上角关闭按钮位置用
wx.getDetailPageCloseButtonBoundingClientRect适配,避免遮挡业务内容
#!/usr/bin/env node
// scripts/probe.mjs
// 用法:node probe.mjs --project <path> --plan <plan.json> [--output <path>] [--auto-port 9420] [--cli-path <path>] [--mode launch|connect] [--ws-endpoint <url>]
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { parseArgs, detectDefaultCliPath, runProbePlan, summarize, DEFAULT_AUTO_PORT } from "./probe-lib.mjs";
async function main() {
const opts = parseArgs(process.argv.slice(2));
if (opts.help) {
console.log(`用法: node probe.mjs --project <path> --plan <plan.json> [--output] [--auto-port ${DEFAULT_AUTO_PORT}] [--cli-path] [--mode launch|connect] [--ws-endpoint]`);
process.exit(0);
}
if (!opts.project || !opts.plan) {
console.error("错误:必须提供 --project 和 --plan");
process.exit(2);
}
let plan;
try {
plan = JSON.parse(await readFile(resolve(opts.plan), "utf8"));
} catch (err) {
console.error(`错误:读取 plan 失败:${err.message}`);
process.exit(2);
}
if (!Array.isArray(plan) || !plan.length) {
console.error("错误:plan 必须是非空数组");
process.exit(2);
}
const cliPath = opts["cli-path"] || detectDefaultCliPath();
if (!cliPath && opts.mode !== "connect") {
console.error("错误:未找到微信开发者工具 CLI,请通过 --cli-path 指定或设置 WX_CLI_PATH 环境变量");
process.exit(2);
}
if (cliPath) console.log(`[probe] CLI: ${cliPath}`);
let payload;
try {
payload = await runProbePlan({
projectPath: resolve(opts.project),
plan,
autoPort: Number(opts["auto-port"]) || DEFAULT_AUTO_PORT,
cliPath,
launchTimeoutMs: Number(opts["launch-timeout"]) || undefined,
interactionTimeoutMs: Number(opts["interaction-timeout"]) || undefined,
outputPath: opts.output ? resolve(opts.output) : null,
mode: opts.mode === "connect" ? "connect" : "launch",
wsEndpoint: opts["ws-endpoint"],
});
} catch (err) {
console.error(`[probe] 执行失败:${err.message}`);
process.exit(2);
}
const sum = summarize(payload);
if (opts.output) {
console.log(`[probe] 结果已写入 ${opts.output}`);
} else {
console.log(JSON.stringify(payload, null, 2));
}
console.log(`[probe] 汇总:成功 ${sum.ok}/${sum.total},失败 ${sum.failed}`);
if (sum.failures.length) {
for (const f of sum.failures) {
console.error(` - ${f.api_name}: ${f.status} (${f.error || "未知"})`);
}
process.exit(1);
}
}
main().catch((err) => {
console.error(`[probe] 未处理异常:${err?.stack || err}`);
process.exit(2);
});