create-vela-workflow 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +136 -0
  2. package/bin/cli.js +188 -0
  3. package/docs/ai-workflow-tutorial.md +462 -0
  4. package/docs/official-site-tutorial.md +391 -0
  5. package/package.json +34 -0
  6. package/templates/.github/HARNESS-ENGINEERING-GUIDE.md +407 -0
  7. package/templates/.github/agents/vela-knowledge.agent.md +45 -0
  8. package/templates/.github/agents/vela-s1-prd.agent.md +69 -0
  9. package/templates/.github/agents/vela-s2-tech.agent.md +66 -0
  10. package/templates/.github/agents/vela-s3-coding.agent.md +301 -0
  11. package/templates/.github/agents/vela-workflow.agent.md +110 -0
  12. package/templates/.github/copilot-instructions.md +64 -0
  13. package/templates/.github/prompts/vela-apis.prompt.md +98 -0
  14. package/templates/.github/prompts/vela-best-practices.prompt.md +93 -0
  15. package/templates/.github/prompts/vela-components.prompt.md +118 -0
  16. package/templates/.github/prompts/vela-dev-guide.prompt.md +622 -0
  17. package/templates/.github/rules/project-init.md +45 -0
  18. package/templates/.github/rules/vela-coding-convention.md +324 -0
  19. package/templates/.github/rules/vela-css.md +217 -0
  20. package/templates/.github/rules/vela-design-driven.md +306 -0
  21. package/templates/.github/rules/vela-figma-mcp.md +198 -0
  22. package/templates/.github/rules/vela-format.md +119 -0
  23. package/templates/.github/rules/vela-layout.md +67 -0
  24. package/templates/.github/rules/vela-platform.md +46 -0
  25. package/templates/.github/rules/vela-quality.md +109 -0
  26. package/templates/.kiro/hooks/figma-design-check.kiro.hook +14 -0
  27. package/templates/.kiro/hooks/post-coding-validation.kiro.hook +13 -0
  28. package/templates/.kiro/hooks/validate-ux-files.kiro.hook +16 -0
  29. package/templates/.kiro/settings/mcp.json +7 -0
  30. package/templates/.kiro/skills/vela-js-app/SKILL.md +1072 -0
  31. package/templates/.kiro/steering/workflow-conventions.md +110 -0
  32. package/templates/.workflow/resource-paths.json +62 -0
  33. package/templates/.workflow/scripts/.gitkeep +0 -0
  34. package/templates/.workflow/scripts/checkpoint_manager.js +284 -0
  35. package/templates/.workflow/scripts/context_loader.js +841 -0
  36. package/templates/.workflow/scripts/figma_export.js +346 -0
  37. package/templates/.workflow/scripts/session_manager.js +438 -0
  38. package/templates/.workflow/stages/.gitkeep +0 -0
  39. package/templates/.workflow/stages/commands.md +171 -0
  40. package/templates/.workflow/stages/s1_prd.md +286 -0
  41. package/templates/.workflow/stages/s2_tech_design.md +302 -0
  42. package/templates/.workflow/stages/s3_coding.md +699 -0
  43. package/templates/.workflow/stages/s4_simulator.md +259 -0
  44. package/templates/.workflow/workflow-config.json +46 -0
  45. package/templates/.workflow/workflow_starter.md +912 -0
@@ -0,0 +1,1072 @@
1
+ ---
2
+ name: vela-js-app-development
3
+ description: Xiaomi Vela JS 应用开发技能。用于创建、开发和调试运行在小米 IoT 穿戴设备(手表)上的 Vela JS 应用。包含项目结构、manifest 配置、UX 文件编写(template/style/script)、组件使用、接口调用等完整知识。当用户需要开发 Vela JS 应用、创建页面、使用组件或调用系统接口时使用此技能。
4
+ ---
5
+
6
+ # Xiaomi Vela JS 应用开发指南
7
+
8
+ 官方文档站点:https://iot.mi.com/vela/quickapp/
9
+
10
+ ## 框架概述
11
+
12
+ Xiaomi Vela JS 是小米基于 Vela OS 的轻量级 JS 应用框架,面向智能穿戴设备(手表)。采用前端 MVVM 开发范式,使用 `.ux` 文件编写页面,`manifest.json` 配置应用,Flexbox 布局,支持数据绑定和组件化开发。
13
+
14
+ ## 创建项目
15
+
16
+ ### 方式一:使用脚手架(推荐)
17
+
18
+ 检查是否安装了 aiot-toolkit,如果已安装则使用命令创建:
19
+ ```bash
20
+ npm create aiot ux -- --name my-app --template vela-demo
21
+ cd my-app
22
+ npm install
23
+ ```
24
+
25
+ ### 方式二:手动创建
26
+
27
+ 如果未安装 aiot-toolkit,参考以下目录结构手动创建项目。应用图标 `logo.png` 从 skill 的 assets 目录拷贝到 `src/common/logo.png`(assets 路径:`.kiro/skills/vela-js-app/assets/logo.png`)。
28
+
29
+ ## 项目结构
30
+
31
+ ```
32
+ ├── README.md # 项目说明
33
+ ├── .gitignore # Git 忽略配置
34
+ ├── package.json # npm 配置,定义构建脚本和依赖
35
+ └── src/
36
+ ├── manifest.json # 应用配置(包名、路由、接口声明等)
37
+ ├── app.ux # 应用级公共脚本和生命周期
38
+ ├── config-watch.json # 手表设备配置(内容为 {})
39
+ ├── pages/ # 页面目录
40
+ │ ├── index/
41
+ │ │ └── index.ux
42
+ │ └── detail/
43
+ │ └── detail.ux
44
+ ├── i18n/ # 国际化资源
45
+ │ ├── defaults.json
46
+ │ ├── zh-CN.json
47
+ │ └── en.json
48
+ └── common/ # 公共资源
49
+ └── logo.png # 应用图标
50
+ ```
51
+
52
+ 注意:页面放在 `src/pages/` 下,`manifest.json` 中 `router.pages` 的 key 需带 `pages/` 前缀(如 `"pages/index"` 对应 `src/pages/index/`)。
53
+
54
+ 构建后会自动生成 `build/`(中间产物)、`dist/`(rpk 包)目录;`sign/`(签名证书)由 release 命令引导生成。
55
+
56
+ ### package.json 示例
57
+
58
+ ```json
59
+ {
60
+ "name": "my-app",
61
+ "version": "1.0.0",
62
+ "scripts": {
63
+ "start": "aiot start --watch",
64
+ "build": "aiot build",
65
+ "release": "aiot release"
66
+ },
67
+ "devDependencies": {
68
+ "aiot-toolkit": "^2.0.5"
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### .gitignore
74
+
75
+ ```
76
+ /node_modules
77
+ /dist
78
+ /build
79
+ ```
80
+
81
+ ## manifest.json 配置
82
+
83
+ ```json
84
+ {
85
+ "package": "com.company.demo",
86
+ "name": "应用名称",
87
+ "icon": "/common/logo.png",
88
+ "versionName": "1.0",
89
+ "versionCode": 1,
90
+ "minPlatformVersion": 1200,
91
+ "deviceTypeList": ["watch"],
92
+ "features": [
93
+ { "name": "system.router" },
94
+ { "name": "system.configuration" }
95
+ ],
96
+ "config": {
97
+ "logLevel": "log",
98
+ "designWidth": 480
99
+ },
100
+ "router": {
101
+ "entry": "pages/index",
102
+ "pages": {
103
+ "pages/index": {
104
+ "component": "index"
105
+ },
106
+ "pages/detail": {
107
+ "component": "detail"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ 关键字段:
115
+ - `package`:应用包名,格式 com.company.module
116
+ - `name`:应用名称,6个汉字以内
117
+ - `versionCode`:整数版本号,每次发布+1
118
+ - `features`:接口声明数组,使用接口前必须声明
119
+ - `config.designWidth`:设计基准宽度,默认480px
120
+ - `router.entry`:首页路径,如 `"pages/index"`
121
+ - `router.pages`:页面路由配置,key 为页面目录相对 src 的路径(如 `"pages/index"` 对应 `src/pages/index/`),component 为 ux 文件名
122
+ - `router.pages[].launchMode`:页面启动模式,支持 "standard"(默认)和 "singleTask"
123
+
124
+ ## UX 文件格式
125
+
126
+ 每个页面/组件由 `.ux` 文件编写,包含 template、style、script 三部分:
127
+
128
+ ```html
129
+ <template>
130
+ <!-- 只能有一个根节点 -->
131
+ <div class="page">
132
+ <text class="title">{{title}}</text>
133
+ <input type="button" value="点击" onclick="handleClick" />
134
+ </div>
135
+ </template>
136
+
137
+ <style>
138
+ .page {
139
+ flex-direction: column;
140
+ justify-content: center;
141
+ align-items: center;
142
+ }
143
+ .title {
144
+ font-size: 30px;
145
+ color: #333333;
146
+ }
147
+ </style>
148
+
149
+ <script>
150
+ import router from '@system.router'
151
+
152
+ export default {
153
+ private: {
154
+ title: '示例页面'
155
+ },
156
+ onInit() {
157
+ console.log('页面初始化')
158
+ },
159
+ handleClick() {
160
+ router.push({ uri: '/pages/detail' })
161
+ }
162
+ }
163
+ </script>
164
+ ```
165
+
166
+ 也可拆分为独立文件(detail.ux + detail.css + detail.js),此时 ux 文件不包含 template 标签。
167
+
168
+ ## Template 模板语法
169
+
170
+ ### 数据绑定
171
+ ```html
172
+ <text>{{message}}</text>
173
+ ```
174
+
175
+ ### 条件渲染
176
+ ```html
177
+ <!-- if/elif/else 必须是相邻兄弟节点 -->
178
+ <text if="{{show}}">显示</text>
179
+ <text elif="{{other}}">其他</text>
180
+ <text else>默认</text>
181
+
182
+ <!-- show 指令:不从DOM移除,仅隐藏 -->
183
+ <text show="{{visible}}">内容</text>
184
+ ```
185
+
186
+ ### 列表渲染
187
+ ```html
188
+ <!-- 基本用法 -->
189
+ <div for="{{list}}" tid="id">
190
+ <text>{{$idx}}: {{$item.name}}</text>
191
+ </div>
192
+
193
+ <!-- 自定义变量名 -->
194
+ <div for="(index, item) in list" tid="id">
195
+ <text>{{index}}: {{item.name}}</text>
196
+ </div>
197
+ ```
198
+
199
+ - `tid` 属性指定数组元素唯一ID,用于优化渲染
200
+ - for 只能循环数组,不能循环对象
201
+ - `<block>` 标签可用于逻辑控制,不产生额外DOM节点
202
+
203
+ ### 事件绑定
204
+ ```html
205
+ <text onclick="handleClick">点击</text>
206
+ <text @click="handleClick">简写</text>
207
+ <text onclick="handleClick($idx, $item)">传参</text>
208
+ ```
209
+ 回调函数末尾自动添加 `evt` 参数。
210
+
211
+ ## Style 样式系统
212
+
213
+ ### 布局
214
+ - 采用 CSS Flexbox 布局,默认 `flex-direction: row`
215
+ - 盒模型为 `border-box`
216
+ - div 为 Flex 容器,text/span 为文本容器
217
+
218
+ ### 长度单位
219
+ - `px`:相对于 designWidth 的适配单位(类似 rem),自动按屏幕宽度缩放
220
+ - `%`:百分比
221
+ - `dp`:设备独立像素(API Level 3+)
222
+
223
+ ### 选择器(支持)
224
+ - `.class` / `#id` / `tag` / 并列选择 `.a, .b`
225
+ - 优先级:inline > #id > .class > tag
226
+ - 暂不支持后代选择器
227
+
228
+ ### 样式预编译
229
+ 支持 less 和 scss:
230
+ ```html
231
+ <style lang="less">
232
+ @import './style.less';
233
+ </style>
234
+ ```
235
+
236
+ ### 通用样式属性
237
+ width, height, min-width, min-height, max-width, max-height, padding, margin, border, border-radius, background-color, background-image, background-size, background-position, color, opacity, display(flex|none), visibility, position(relative|absolute), flex, flex-grow, flex-shrink, flex-direction, align-items, justify-content, box-shadow, left/top/right/bottom
238
+
239
+ ## Script 脚本
240
+
241
+ ### 页面数据对象
242
+ ```javascript
243
+ export default {
244
+ // 页面级数据(三选一,不能与 data 同时使用)
245
+ public: {}, // 允许被外部传入数据覆盖
246
+ protected: {}, // 允许被应用内部页面传参覆盖
247
+ private: {}, // 不允许被覆盖
248
+
249
+ // 组件级数据
250
+ data: {},
251
+
252
+ // 计算属性
253
+ computed: {
254
+ fullName() {
255
+ return this.firstName + ' ' + this.lastName
256
+ }
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### 页面生命周期
262
+ - `onInit()`:数据准备好,可使用页面数据
263
+ - `onReady()`:模板编译完成,可获取DOM节点
264
+ - `onShow()`:页面显示
265
+ - `onHide()`:页面隐藏
266
+ - `onDestroy()`:页面销毁,应释放资源
267
+ - `onBackPress()`:返回按键,return true 阻止返回
268
+ - `onRefresh(query)`:singleTask模式页面重新打开
269
+ - `onConfigurationChanged(event)`:系统配置变化(如语言)
270
+
271
+ ### APP 生命周期(app.ux)
272
+ - `onCreate()` / `onShow()` / `onHide()` / `onDestroy()` / `onError(e)`
273
+
274
+ ### 全局对象和方法
275
+ ```javascript
276
+ // 访问 app.ux 中定义的数据和方法
277
+ this.$app.$def.data1
278
+ this.$app.$def.method1()
279
+
280
+ // 退出应用
281
+ this.$app.exit()
282
+
283
+ // 页面信息
284
+ this.$page.name / this.$page.path
285
+
286
+ // 页面有效性
287
+ this.$valid
288
+
289
+ // DOM操作
290
+ this.$element('id')
291
+
292
+ // 数据监听
293
+ this.$watch('propName', 'handlerName')
294
+
295
+ // 能力查询
296
+ this.$canIUse('@system.router.push')
297
+
298
+ // 下次DOM更新后回调
299
+ this.$nextTick(() => { /* ... */ })
300
+
301
+ // 事件通信
302
+ this.$on('eventName', handler)
303
+ this.$off('eventName', handler)
304
+ this.$emit('eventName', { data: 1 }) // 触发当前组件事件
305
+ this.$dispatch('eventName', { data: 1 }) // 向上传递
306
+ this.$broadcast('eventName', { data: 1 }) // 向下传递
307
+ ```
308
+
309
+ ## 自定义组件
310
+
311
+ ### 定义组件(comp.ux)
312
+ ```html
313
+ <template>
314
+ <div>
315
+ <text>{{say}}</text>
316
+ </div>
317
+ </template>
318
+ <script>
319
+ export default {
320
+ props: ['say'], // 或 Object 形式设置默认值和类型校验
321
+ data: { localVal: '' }, // 组件只能用 data
322
+ onInit() {},
323
+ onReady() {},
324
+ onDestroy() {}
325
+ }
326
+ </script>
327
+ ```
328
+
329
+ ### 引入组件
330
+ ```html
331
+ <import name="my-comp" src="./comp"></import>
332
+ <template>
333
+ <div>
334
+ <my-comp say="{{message}}" prop-object="{{obj}}"></my-comp>
335
+ </div>
336
+ </template>
337
+ ```
338
+
339
+ ### Props
340
+ - 驼峰命名在模板中转为短横线:`propObject` → `prop-object`
341
+ - 数据单向流动:父→子
342
+ - 支持默认值和类型校验(String/Number/Boolean/Function/Object/Array)
343
+
344
+ ### 父子通信
345
+ - 父→子:通过 props 传递数据
346
+ - 子→父:`this.$emit('eventName', data)` + 父组件 `onemit-evt="handler"`
347
+ - 向上传递:`this.$dispatch()` + 父组件 `this.$on()`
348
+ - 向下传递:`this.$broadcast()` + 子组件 `this.$on()`
349
+
350
+ ## 页面切换
351
+
352
+ ```javascript
353
+ import router from '@system.router'
354
+
355
+ // 跳转(保留当前页面)
356
+ router.push({ uri: '/pages/detail', params: { id: '1' } })
357
+
358
+ // 替换(销毁当前页面)
359
+ router.replace({ uri: '/pages/detail', params: { id: '1' } })
360
+
361
+ // 返回
362
+ router.back()
363
+ router.back({ path: '/pages/index' })
364
+
365
+ // 清空页面栈
366
+ router.clear()
367
+
368
+ // 获取页面信息
369
+ router.getLength()
370
+ router.getState() // { index, name, path }
371
+ router.getPages() // [{ name, path }, ...]
372
+ ```
373
+
374
+ 接收参数:在目标页面的 `protected`(应用内)或 `public`(应用外)中声明同名属性。
375
+
376
+ ## 组件详解
377
+
378
+ 官方组件文档:https://iot.mi.com/vela/quickapp/zh/components/
379
+
380
+ ### 容器组件
381
+
382
+ **div** — 基础 Flex 容器
383
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/container/div.html)
384
+
385
+ **list + list-item** — 高性能列表
386
+ [list 文档](https://iot.mi.com/vela/quickapp/zh/components/container/list.html) | [list-item 文档](https://iot.mi.com/vela/quickapp/zh/components/container/list-item.html)
387
+ ```html
388
+ <list class="list" onscrollbottom="loadMore">
389
+ <list-item type="item" for="{{list}}" tid="id">
390
+ <text>{{$item.name}}</text>
391
+ </list-item>
392
+ </list>
393
+ ```
394
+ - `list-item` 必须设置 `type` 属性,相同结构的 item 使用相同 type
395
+ - list 事件:`scroll`({scrollX, scrollY, scrollState})、`scrollbottom`、`scrolltop`
396
+ - list 方法:`scrollTo({index, behavior})` / `scrollBy({top, behavior})`
397
+
398
+ **scroll** — 滚动容器
399
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/container/scroll.html)
400
+ ```html
401
+ <scroll scroll-y="true" style="height: 300px;" onscroll="onScroll">
402
+ <!-- 内容 -->
403
+ </scroll>
404
+ ```
405
+ - 属性:`scroll-x` / `scroll-y`(boolean)、`scroll-top` / `scroll-left`(设置滚动位置)
406
+ - 样式:`scroll-snap-type` / `scroll-snap-align`(滚动吸附)
407
+
408
+ **swiper** — 滑块视图
409
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/container/swiper.html)
410
+ ```html
411
+ <swiper index="0" autoplay="true" interval="3000" loop="true" onchange="onChange">
412
+ <div><text>页面1</text></div>
413
+ <div><text>页面2</text></div>
414
+ </swiper>
415
+ ```
416
+ - 属性:`index` / `autoplay` / `interval` / `loop` / `vertical` / `indicator` / `previousmargin` / `nextmargin`
417
+ - 样式:`indicator-color` / `indicator-selected-color` / `indicator-size`
418
+ - 事件:`change`({index})
419
+ - 方法:`swipeTo({index})`
420
+
421
+ **stack** — 层叠容器
422
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/container/stack.html)
423
+
424
+ ### 基础组件
425
+
426
+ **text** — 文本
427
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/text.html)
428
+ - 样式:`lines`(行数)、`text-overflow`(clip/ellipsis,需配合 lines)、`color`、`font-size`、`font-weight`、`text-align`、`line-height`、`text-indent`
429
+ - 子组件仅支持 `<span>`
430
+
431
+ **image** — 图片
432
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/image.html)
433
+ ```html
434
+ <image src="/common/logo.png" alt="blank" />
435
+ ```
436
+ - 属性:`src`(本地/网络 uri)、`alt`(占位图,设为 "blank" 无占位图)
437
+ - 样式:`object-fit`(contain/cover/none/scale-down,默认 cover)
438
+ - 事件:`complete`({width, height})、`error`
439
+
440
+ **其他基础组件**:
441
+ - `span`:行内文本,仅作 text 子组件 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/span.html)
442
+ - `a`:链接 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/a.html)
443
+ - `progress`:进度条(type: horizontal/circular, percent) [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/progress.html)
444
+ - `marquee`:跑马灯 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/marquee.html)
445
+ - `chart`:图表 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/chart.html)
446
+ - `qrcode`:二维码 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/qrcode.html)
447
+ - `barcode`:条形码 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/barcode.html)
448
+ - `image-animator`:帧动画 [文档](https://iot.mi.com/vela/quickapp/zh/components/basic/image-animator.html)
449
+
450
+ ### 表单组件
451
+
452
+ **input** — 输入/按钮/选择
453
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/form/input.html)
454
+ ```html
455
+ <input type="button" value="点击" onclick="onClick" />
456
+ <input type="checkbox" checked="{{checked}}" onchange="onCheck" />
457
+ <input type="radio" name="group" value="1" onchange="onRadio" />
458
+ ```
459
+ - type:`button` / `checkbox` / `radio`
460
+ - 属性:`checked`(checkbox/radio)、`name`、`value`
461
+ - 事件:`change`({name, value, checked}),button 无 change 事件
462
+
463
+ **picker** — 选择器
464
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/form/picker.html)
465
+ - type:`text` / `date` / `time` / `multi-text`
466
+
467
+ **slider** — 滑块 [文档](https://iot.mi.com/vela/quickapp/zh/components/form/slider.html)
468
+
469
+ **switch** — 开关 [文档](https://iot.mi.com/vela/quickapp/zh/components/form/switch.html)
470
+
471
+ ### 通用属性
472
+ 所有组件支持:`id`, `style`, `class`, `for`, `if`, `show`, `data-*`
473
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/general/properties.html)
474
+
475
+ ### 通用事件
476
+ 所有组件支持:`touchstart`, `touchmove`, `touchend`, `click`, `longpress`, `swipe`
477
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/general/events.html)
478
+
479
+ ### 通用样式
480
+ [文档](https://iot.mi.com/vela/quickapp/zh/components/general/style.html) | [颜色](https://iot.mi.com/vela/quickapp/zh/components/general/color.html) | [动画](https://iot.mi.com/vela/quickapp/zh/components/general/animation-style.html) | [背景图](https://iot.mi.com/vela/quickapp/zh/components/general/background-img-styles.html)
481
+
482
+ ## 接口详解
483
+
484
+ 官方接口文档:https://iot.mi.com/vela/quickapp/zh/features/
485
+
486
+ ### 高频接口完整用法
487
+
488
+ **fetch — 网络请求**
489
+ [文档](https://iot.mi.com/vela/quickapp/zh/features/network/fetch.html)
490
+ 声明:`{ "name": "system.fetch" }`
491
+ ```javascript
492
+ import fetch from '@system.fetch'
493
+
494
+ // GET 请求
495
+ fetch.fetch({
496
+ url: 'https://api.example.com/data',
497
+ responseType: 'json',
498
+ success(res) {
499
+ console.log(res.data) // Object(json时)
500
+ console.log(res.code) // HTTP 状态码
501
+ console.log(res.headers)
502
+ },
503
+ fail(data, code) {
504
+ console.log(code)
505
+ }
506
+ })
507
+
508
+ // POST 请求
509
+ fetch.fetch({
510
+ url: 'https://api.example.com/submit',
511
+ method: 'POST',
512
+ header: { 'Content-Type': 'application/json' },
513
+ data: JSON.stringify({ key: 'value' }),
514
+ responseType: 'json',
515
+ success(res) { console.log(res.data) }
516
+ })
517
+ ```
518
+ - 参数:`url`、`method`(GET/POST/PUT/DELETE...)、`header`、`data`(String/Object/ArrayBuffer)、`responseType`(text/json/file/arraybuffer)
519
+ - 返回:`{code, data, headers}`
520
+
521
+ **audio — 音频播放**
522
+ [文档](https://iot.mi.com/vela/quickapp/zh/features/other/audio.html)
523
+ 声明:`{ "name": "system.audio" }`
524
+ ```javascript
525
+ import audio from '@system.audio'
526
+
527
+ audio.src = '/common/music.mp3'
528
+ audio.autoplay = false
529
+ audio.loop = false
530
+ audio.volume = 0.8
531
+
532
+ audio.play()
533
+ audio.pause()
534
+ audio.stop()
535
+
536
+ audio.onended = function() { console.log('播放结束') }
537
+ audio.onerror = function() { console.log('播放出错') }
538
+
539
+ audio.getPlayState({
540
+ success(data) {
541
+ // data: { state, src, currentTime, duration, percent, autoplay, loop, volume, muted }
542
+ }
543
+ })
544
+ ```
545
+ - 可读写属性:`src`、`currentTime`、`autoplay`、`loop`、`volume`、`muted`
546
+ - 只读属性:`duration`
547
+ - 事件:`onplay`、`onpause`、`onstop`、`onended`、`onerror`、`onloadeddata`、`ondurationchange`
548
+
549
+ **prompt — 弹窗提示**
550
+ [文档](https://iot.mi.com/vela/quickapp/zh/features/other/prompt.html)
551
+ 声明:`{ "name": "system.prompt" }`
552
+ ```javascript
553
+ import prompt from '@system.prompt'
554
+
555
+ prompt.showToast({ message: '操作成功', duration: 2000 })
556
+ ```
557
+
558
+ **storage — 键值存储**
559
+ [文档](https://iot.mi.com/vela/quickapp/zh/features/data/storage.html)
560
+ 声明:`{ "name": "system.storage" }`
561
+ ```javascript
562
+ import storage from '@system.storage'
563
+
564
+ storage.set({ key: 'token', value: 'xxx' })
565
+ storage.get({ key: 'token', success(data) { console.log(data) } })
566
+ storage.delete({ key: 'token' })
567
+ storage.clear()
568
+ ```
569
+
570
+ **router — 页面路由**(无需声明)
571
+ [文档](https://iot.mi.com/vela/quickapp/zh/features/basic/router.html)
572
+ 用法见上方"页面切换"章节。
573
+
574
+ ### 其他接口速查
575
+
576
+ **基本功能**:
577
+ - `@system.app`:应用管理 [文档](https://iot.mi.com/vela/quickapp/zh/features/basic/app.html)
578
+ - `@system.configuration`:应用配置 [文档](https://iot.mi.com/vela/quickapp/zh/features/basic/configuration.html)
579
+ - `@system.device`:设备信息 [文档](https://iot.mi.com/vela/quickapp/zh/features/basic/device.html)
580
+
581
+ **网络**:
582
+ - `@system.request`:下载管理(download/onDownloadComplete) [文档](https://iot.mi.com/vela/quickapp/zh/features/network/request.html)
583
+ - `@system.interconnect`:设备互联(instance/send/diagnosis) [文档](https://iot.mi.com/vela/quickapp/zh/features/network/interconnect.html)
584
+ - `@system.uploadtask`:上传任务 [文档](https://iot.mi.com/vela/quickapp/zh/features/network/uploadtask.html)
585
+
586
+ **系统能力**:
587
+ - `@system.network`:网络状态(subscribe/unsubscribe/getType) [文档](https://iot.mi.com/vela/quickapp/zh/features/system/network.html)
588
+ - `@system.vibrator`:振动 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/vibrator.html)
589
+ - `@system.brightness`:屏幕亮度(getValue/setValue/getMode/setMode) [文档](https://iot.mi.com/vela/quickapp/zh/features/system/brightness.html)
590
+ - `@system.record`:录音 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/record.html)
591
+ - `@system.geolocation`:地理位置(需权限 hapjs.permission.LOCATION) [文档](https://iot.mi.com/vela/quickapp/zh/features/system/geolocation.html)
592
+ - `@system.sensor`:传感器(加速度、陀螺仪、心率等) [文档](https://iot.mi.com/vela/quickapp/zh/features/system/sensor.html)
593
+ - `@system.event`:系统事件 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/event.html)
594
+ - `@system.battery`:电池信息 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/battery.html)
595
+ - `@system.volume`:音量控制 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/volume.html)
596
+ - `@system.zip`:压缩解压 [文档](https://iot.mi.com/vela/quickapp/zh/features/system/zip.html)
597
+ - `@system.alarm`:闹钟(setAlarm/getAlarm/cancelAlarm) [文档](https://iot.mi.com/vela/quickapp/zh/features/system/alarm.html)
598
+
599
+ **安全**:
600
+ - `@system.crypto`:加密(rsa/aes/digest/hmac 等) [文档](https://iot.mi.com/vela/quickapp/zh/features/security/crypto.html)
601
+ - `@system.secureelement`:安全元件 [文档](https://iot.mi.com/vela/quickapp/zh/features/security/secureelement.html)
602
+
603
+ **其他**:
604
+ - `@system.file`:文件操作(readText/writeText/list/get/delete/move/copy/mkdir/rmdir) [文档](https://iot.mi.com/vela/quickapp/zh/features/data/file.html)
605
+ - `@system.protobuf`:Protobuf 编解码 [文档](https://iot.mi.com/vela/quickapp/zh/features/other/protobuf.html)
606
+ - `@system.serviceclient`:服务客户端 [文档](https://iot.mi.com/vela/quickapp/zh/features/other/serviceclient.html)
607
+
608
+ ### 通用错误码
609
+ - 200:系统通用错误
610
+ - 201:用户拒绝
611
+ - 202:参数错误
612
+ - 203:功能不支持
613
+ - 204:请求超时
614
+ - 300:I/O 错误
615
+
616
+ ## 文件存储分区
617
+
618
+ | 分区 | URI | 读写 | 说明 |
619
+ |------|-----|------|------|
620
+ | 应用资源 | /path | 只读 | 应用内置资源 |
621
+ | Cache | internal://cache/path | 读写 | 缓存文件,可能被系统清理 |
622
+ | Files | internal://files/path | 读写 | 永久小文件 |
623
+ | Mass | internal://mass/path | 读写 | 大文件,不保证可用 |
624
+ | Temp | internal://tmp/path | 只读 | 临时文件,应用重启后失效 |
625
+
626
+ ## 多屏适配
627
+
628
+ - 使用 `config.designWidth` 设置设计基准宽度
629
+ - px 单位会根据实际屏幕宽度自动缩放
630
+ - 转换公式:`设计稿1px / 设计稿宽度 = 框架样式1px / designWidth`
631
+ - 支持 media query 进行条件样式
632
+
633
+ ## 国际化(i18n)
634
+
635
+ 在 `src/i18n/` 目录下定义 JSON 资源文件:
636
+ - 文件命名优先级:`zh-CN.json` > `zh.json` > `defaults.json`
637
+ - 支持基础文本、命名插值 `{msg}`、列表插值 `{0}`、单复数 `car | cars`
638
+
639
+ 页面中使用:
640
+ ```html
641
+ <text>{{ $t('message.hello') }}</text>
642
+ <text>{{ $t('message.greeting', { name: '小明' }) }}</text>
643
+ <text>{{ $tc('message.car', 2) }}</text>
644
+ ```
645
+
646
+ ```javascript
647
+ // script 中
648
+ this.$t('message.hello')
649
+ this.$tc('message.apple', 2, { count: 6 })
650
+ ```
651
+
652
+ 语言变化监听:
653
+ ```javascript
654
+ onConfigurationChanged(event) {
655
+ if (event.type === 'locale') {
656
+ console.log('语言已切换')
657
+ }
658
+ }
659
+ ```
660
+
661
+ ## 后台运行
662
+
663
+ 在 manifest.json 的 `config.background.features` 中声明需要后台运行的接口:
664
+ ```json
665
+ {
666
+ "config": {
667
+ "background": {
668
+ "features": ["system.audio", "system.request", "system.geolocation"]
669
+ }
670
+ }
671
+ }
672
+ ```
673
+ 建议将后台运行逻辑放在 `app.ux` 中,避免页面销毁影响。
674
+
675
+ ## 动态组件
676
+
677
+ 使用 `<component>` 元素的 `is` 属性动态切换组件:
678
+ ```html
679
+ <import src="./part1.ux" name="part1"></import>
680
+ <import src="./part2.ux" name="part2"></import>
681
+ <template>
682
+ <div>
683
+ <component is="{{'part' + status}}"></component>
684
+ </div>
685
+ </template>
686
+ ```
687
+
688
+ ## 动画样式
689
+
690
+ ### transform
691
+ ```css
692
+ div {
693
+ transform: translate(10px, 20px) rotate(45deg) scale(1.5);
694
+ transform-origin: 50% 50%;
695
+ }
696
+ ```
697
+ 支持:translate/translateX/translateY、scale/scaleX/scaleY、rotate
698
+
699
+ ### animation + @keyframes
700
+ ```css
701
+ .box {
702
+ animation-name: fadeIn;
703
+ animation-duration: 1s;
704
+ animation-timing-function: ease;
705
+ animation-iteration-count: infinite;
706
+ animation-delay: 0s;
707
+ }
708
+ @keyframes fadeIn {
709
+ from { opacity: 0; }
710
+ to { opacity: 1; }
711
+ }
712
+ ```
713
+
714
+ ### transition
715
+ ```css
716
+ .box {
717
+ transition-property: width, background-color;
718
+ transition-duration: 0.3s;
719
+ transition-timing-function: ease-in-out;
720
+ transition-delay: 0s;
721
+ }
722
+ ```
723
+
724
+ ## Media Query 媒体查询
725
+
726
+ 根据设备特征条件应用样式,单位为 dp(设备独立像素):
727
+ ```css
728
+ @media (shape: circle) {
729
+ .box { border-radius: 50%; }
730
+ }
731
+ @media (device-type: watch) and (min-width: 200) {
732
+ .box { width: 100%; }
733
+ }
734
+ @media (shape: circle) or (shape: pill-shaped) {
735
+ .box { padding: 10px; }
736
+ }
737
+ ```
738
+
739
+ 支持的媒体特性:
740
+ - `shape`:circle / rect / pill-shaped
741
+ - `device-type`:watch / band / smartspeaker
742
+ - `width` / `min-width` / `max-width`(dp单位,不带单位书写)
743
+ - `height` / `min-height` / `max-height`
744
+ - `aspect-ratio` / `min-aspect-ratio` / `max-aspect-ratio`
745
+
746
+ 逻辑操作符:`and` / `not` / `only` / `or` / `,`(逗号等同or)/ `<=` / `>=`
747
+
748
+ 也支持 `@import` 方式:
749
+ ```css
750
+ @import './circle.css' (shape: circle);
751
+ ```
752
+
753
+ 常见设备 dp 参考:
754
+ - Xiaomi Watch S1 Pro/S5:480×480px, DPR=2, 宽=240dp
755
+ - Xiaomi Watch S3/S4/H1:466×466px, DPR=2, 宽=233dp
756
+ - REDMI Watch 5:432×514px, DPR=2, 宽=216dp
757
+ - 小米手环9:192×490px, DPR=2, 宽=96dp
758
+
759
+ ## 条件编译
760
+
761
+ 基于设备特性在编译时选择性包含代码,支持 ux/js/css 文件:
762
+ ```html
763
+ <!-- template 中 -->
764
+ <!-- if true: process.env.SHAPE === 'CIRCLE' -->
765
+ <div class="circle-layout"></div>
766
+ <!-- endif -->
767
+
768
+ <!-- script 中 -->
769
+ // if true: process.env.SHAPE === 'RECT'
770
+ console.log('矩形屏幕')
771
+ // endif
772
+ ```
773
+
774
+ 需安装 `conditional-compilation-webpack-plugin` 和 `cross-env`,在 package.json 中配置编译脚本:
775
+ ```json
776
+ {
777
+ "scripts": {
778
+ "build:circle": "cross-env DEVICE_TYPE=WATCH SHAPE=CIRCLE aiot build",
779
+ "build:rect": "cross-env DEVICE_TYPE=WATCH SHAPE=RECT aiot build"
780
+ }
781
+ }
782
+ ```
783
+
784
+ ## hap 链接
785
+
786
+ router 支持 `hap://` 协议打开其他 JS 应用:
787
+ ```
788
+ hap://app/<package>/[path][?key=value]
789
+ ```
790
+ 示例:`hap://app/com.example.demo/Detail?id=1`
791
+
792
+ ## 颜色配置
793
+
794
+ 支持的颜色格式:
795
+ - `#rgb` / `#rrggbb` / `#rgba` / `#rrggbbaa`
796
+ - `rgb(255, 0, 0)` / `rgba(255, 0, 0, 0.5)`
797
+ - `transparent`
798
+ - CSS 颜色名:`red`, `blue`, `black`, `white` 等
799
+
800
+ ## 通用组件方法
801
+
802
+ 通过 `this.$element('id')` 获取 DOM 节点后可调用:
803
+
804
+ ### getBoundingClientRect(OBJECT)
805
+ 获取元素位置和尺寸(需在 onShow 之后调用):
806
+ ```javascript
807
+ this.$element('box').getBoundingClientRect({
808
+ success(data) {
809
+ // data: { left, right, top, bottom, width, height }
810
+ }
811
+ })
812
+ ```
813
+
814
+ ### focus(OBJECT)
815
+ ```javascript
816
+ this.$element('input1').focus({ focus: true })
817
+ ```
818
+
819
+ ## 完整页面示例
820
+
821
+ ```html
822
+ <template>
823
+ <div class="page">
824
+ <text class="title">{{title}}</text>
825
+ <div class="list-wrap">
826
+ <div for="{{items}}" tid="id" class="item" onclick="onItemClick($item)">
827
+ <image src="{{$item.icon}}" class="icon" />
828
+ <text class="name">{{$item.name}}</text>
829
+ </div>
830
+ </div>
831
+ <div if="{{items.length === 0}}" class="empty">
832
+ <text>暂无数据</text>
833
+ </div>
834
+ </div>
835
+ </template>
836
+
837
+ <style>
838
+ .page {
839
+ flex-direction: column;
840
+ padding: 20px;
841
+ }
842
+ .title {
843
+ font-size: 36px;
844
+ font-weight: bold;
845
+ margin-bottom: 20px;
846
+ }
847
+ .list-wrap {
848
+ flex-direction: column;
849
+ }
850
+ .item {
851
+ flex-direction: row;
852
+ align-items: center;
853
+ padding: 15px 0;
854
+ border-bottom: 1px solid #eeeeee;
855
+ }
856
+ .icon {
857
+ width: 60px;
858
+ height: 60px;
859
+ margin-right: 15px;
860
+ }
861
+ .name {
862
+ font-size: 28px;
863
+ color: #333333;
864
+ }
865
+ .empty {
866
+ justify-content: center;
867
+ align-items: center;
868
+ margin-top: 100px;
869
+ }
870
+ </style>
871
+
872
+ <script>
873
+ import router from '@system.router'
874
+ import storage from '@system.storage'
875
+
876
+ export default {
877
+ private: {
878
+ title: '我的应用',
879
+ items: []
880
+ },
881
+ onInit() {
882
+ this.loadData()
883
+ },
884
+ loadData() {
885
+ storage.get({
886
+ key: 'items',
887
+ success: (data) => {
888
+ if (data) {
889
+ this.items = JSON.parse(data)
890
+ }
891
+ }
892
+ })
893
+ },
894
+ onItemClick(item) {
895
+ router.push({
896
+ uri: '/pages/detail',
897
+ params: { id: item.id }
898
+ })
899
+ }
900
+ }
901
+ </script>
902
+ ```
903
+
904
+ ## 开发注意事项与最佳实践
905
+
906
+ ### 内存优化(重要)
907
+
908
+ 手表设备内存有限,必须严格控制内存占用:
909
+
910
+ 1. **与 UI 无关的数据不要放在 ViewModel 中**,避免不必要的数据代理开销:
911
+ ```javascript
912
+ const someObj = { a: 1 } // ✅ 放在 export default 外部
913
+ export default {
914
+ private: {
915
+ someObj: { a: 1 } // ❌ 不需要绑定到UI的数据不要放这里
916
+ }
917
+ }
918
+ ```
919
+
920
+ 2. **数据更新时原地修改,避免重新赋值整个对象/数组**:
921
+ ```javascript
922
+ // ❌ this.list = [{ name: 'bb' }]
923
+ // ✅ this.list[0].name = 'bb'
924
+ ```
925
+
926
+ 3. **使用 `static` 属性标记不会变化的节点**,减少动态节点内存:
927
+ ```html
928
+ <text static>{{title}}</text>
929
+ <image static src="/common/logo.png"/>
930
+ <!-- 属性级静态标记 -->
931
+ <text if.static="{{show}}" class.static="{{cls}}">内容</text>
932
+ <!-- block 级静态标记:内部所有节点只渲染一次 -->
933
+ <block static>
934
+ <text>{{fixedTitle}}</text>
935
+ </block>
936
+ ```
937
+
938
+ 4. **页面销毁时清除定时器**:
939
+ ```javascript
940
+ onDestroy() {
941
+ if (this.timer) clearTimeout(this.timer)
942
+ }
943
+ ```
944
+
945
+ 5. **读取的文件/存储数据用完后及时置 null 释放**
946
+
947
+ 6. **调用 `global.runGC()` 手动触发垃圾回收**(不要频繁调用)
948
+
949
+ 7. **不要将页面属性和方法缓存到全局**,否则页面销毁后无法释放内存
950
+
951
+ ### 启动性能优化
952
+
953
+ 1. **避免 setTimeout 延迟跳转**,用 async/await 或回调直接跳转:
954
+ ```javascript
955
+ // ✅ 推荐
956
+ async onInit() {
957
+ const data = await this.getData()
958
+ if (!data) router.push({ uri: '/pages/home' })
959
+ }
960
+ ```
961
+
962
+ 2. **logo 页避免 HTTP 请求**,防止弱网阻塞页面跳转
963
+
964
+ 3. **首页数据做本地缓存**,进入时先读缓存展示,同时异步请求更新
965
+
966
+ 4. **UI 先行**,不要等数据全部加载完才渲染页面
967
+
968
+ 5. **减少 console 打印**,特别是长日志和 JSON 对象,release 包用 TerserPlugin 过滤 `console.debug`
969
+
970
+ ### 渲染性能优化
971
+
972
+ 1. **list 超过 10 条使用分页渲染**,避免一次性渲染大量数据
973
+ 2. **长文案分块渲染**,监听 scroll 事件触底加载下一段
974
+ 3. **Swiper 多图使用懒加载**,只保留 3 个子组件动态更新数据
975
+ 4. **减少 border-radius 与背景图同时使用**
976
+ 5. **图片尺寸与组件尺寸保持一致**,避免运行时缩放
977
+ 6. **减少标签嵌套层级和动态样式修改**
978
+
979
+ ### 圆形屏幕安全区域
980
+
981
+ 手表屏幕多为圆形,四角为不可见区域,页面布局必须留出安全边距,避免内容被圆弧裁切:
982
+
983
+ - **上下安全边距**:约屏幕高度的 10%(如 480px 屏幕留 48-50px)
984
+ - **左右安全边距**:约屏幕宽度的 7-8%(如 480px 屏幕留 34-38px)
985
+ - 内容超出一屏时,使用 `<scroll scroll-y="true">` 包裹,确保可滚动
986
+
987
+ 推荐写法:
988
+ ```html
989
+ <scroll class="page" scroll-y="true">
990
+ <div class="container">
991
+ <!-- 页面内容 -->
992
+ </div>
993
+ </scroll>
994
+ ```
995
+ ```css
996
+ .page {
997
+ width: 100%;
998
+ height: 100%;
999
+ }
1000
+ .container {
1001
+ flex-direction: column;
1002
+ padding: 50px 36px;
1003
+ }
1004
+ ```
1005
+
1006
+ 对于方形屏幕(如 REDMI Watch 5),安全边距可适当减小(上下 20px,左右 16px)。可通过 media query 区分:
1007
+ ```css
1008
+ @media (shape: circle) {
1009
+ .container { padding: 50px 36px; }
1010
+ }
1011
+ @media (shape: rect) {
1012
+ .container { padding: 20px 16px; }
1013
+ }
1014
+ ```
1015
+
1016
+ ### 图片使用规范
1017
+
1018
+ - 尽量使用本地图片,避免在线大图
1019
+ - 在线图片不超过 200KB,尺寸不超过屏幕尺寸
1020
+ - 首次加载大图时增加 loading,下载后缓存到 `internal://files/`
1021
+ - 优先使用 PNG8 格式降低体积
1022
+ - 使用 tinypng.com 等工具压缩图片
1023
+
1024
+ ### 代码规范
1025
+
1026
+ 1. `app.ux` 中的代码必须写在 `<script>` 标签内
1027
+ 2. `template` 只能有一个根节点
1028
+ 3. 角度相关 CSS 属性必须带单位:`rotate: 360deg`
1029
+ 4. `list-item` 中谨慎使用 `if/else/show`,保证所有 list-item 结构一致
1030
+ 5. `image` 的 `src` 不要用变量拼接路径(如 `src="/common/{{type}}"`),直接用完整变量 `src="{{imgPath}}"`
1031
+ 6. `input` 等无子元素的标签必须自闭合(如 `<input />`),遵循 XML 规范
1032
+
1033
+ ### 异常处理
1034
+
1035
+ 1. 网络异常时给用户提示
1036
+ 2. 数据异常(空数据/接口错误)要有兜底处理
1037
+ 3. 添加 try-catch 捕获 JS 异常
1038
+ 4. 按钮防重复点击,点击后加 loading 防止多次请求
1039
+ 5. `onShow` 中的 fetch 请求注意:息屏亮屏会重新触发 onShow
1040
+
1041
+ ### 包体积优化
1042
+
1043
+ 1. 去除未使用的三方依赖,选用轻量替代
1044
+ 2. 公共方法挂载到 `global` 上,避免多页面重复 import
1045
+ 3. 去除未使用的 CSS 和 JS 代码
1046
+ 4. 尽可能减少页面数量
1047
+
1048
+ ### 通信(interconnect)注意
1049
+
1050
+ - 使用 `diagnosis()` 方法判断连接状态,不要轮询 `getApkStatus()`
1051
+ - 多条数据直接循环发送,不要加 setTimeout 延迟
1052
+ - 手表 rpk 和手机 app 必须使用相同证书签名
1053
+
1054
+ ### 异步接口 Promise 封装
1055
+
1056
+ 推荐将异步回调接口统一封装为 Promise,方便 async/await 使用:
1057
+ ```javascript
1058
+ function promisify(fn) {
1059
+ return (opts = {}) => new Promise((resolve, reject) => {
1060
+ fn({
1061
+ ...opts,
1062
+ success: data => resolve(data),
1063
+ fail: (data, code) => reject({ data, code })
1064
+ })
1065
+ })
1066
+ }
1067
+
1068
+ // 使用
1069
+ import storage from '@system.storage'
1070
+ const getItem = promisify(storage.get)
1071
+ const data = await getItem({ key: 'myKey' })
1072
+ ```