This commit is contained in:
2026-07-01 15:22:29 +08:00
parent fb84477023
commit f49b3ebebb
6 changed files with 22 additions and 380 deletions
-45
View File
@@ -1,45 +0,0 @@
# Xiaomi Vela JS Application Guidelines
## Project Overview
This is a Xiaomi Vela JS quickapp (快应用) targeting smartwatch/wearable devices. Use the `vela-quickapp` skill for Vela-specific tasks.
## Code Style
- All directory names use **lowercase** (`common/`, not `Common/`)
- Resource paths in manifest.json and CSS use absolute paths (`/common/icon.png`)
- Code imports use relative paths (`../common/utils`)
- Style units are `px` (logical pixels, auto-scaled by framework)
- Use Flexbox layout exclusively (`flex-direction`, `justify-content`, `align-items`)
## Architecture
```
src/
├── manifest.json # App config (package, router, config fields are REQUIRED)
├── app.ux # App entry point, import shared utilities here
├── pages/ # Each page = one directory with one .ux file
└── common/ # Shared resources: styles, utils, images
```
- Each page is a single `.ux` file with `<template>`, `<style>`, `<script>` sections
- `<template>` must have exactly one root node
- Data model uses `private: {}` in script (not `data`)
- Use `@system.*` imports for system APIs; declare in manifest `features` first
## Build and Test
```bash
npm install # Install dependencies (includes aiot-toolkit)
node build.js # Build debug .rpk → dist/
node build.js --release # Build release .rpk (requires sign/ with certs)
```
## Conventions
- **manifest.json `config` field is mandatory** — omitting it causes build failure
- **Router paths must match directory structure** — `"entry": "pages/index"` maps to `src/pages/index/index.ux`
- **`component` value must match .ux filename** without extension
- **`designWidth`**: 480 for watch, adjust for other device types
- Use `{{}}` for data binding, `for="{{list}}"` for iteration, `if="{{cond}}"` for conditionals
- Event binding: `onclick="handler"` or `@click="handler"` (shorthand)
@@ -1,92 +0,0 @@
---
description: "Use when creating or editing .ux files for Xiaomi Vela JS applications. Covers template structure, data binding, event handling, list rendering, conditional rendering, style conventions, and script lifecycle."
applyTo: "**/*.ux"
---
# UX File Guidelines
## File Structure
Every `.ux` file has exactly three sections in this order:
```html
<template>
<!-- single root node only -->
</template>
<style>
/* Flexbox-only layout */
</style>
<script>
// MVVM data model
</script>
```
## Template Rules
- **Single root node** — `<template>` must contain exactly one root `<div>`
- **Never use `<block>` as root** — only as a child for logical grouping
- **Data binding**: `{{variable}}` — no expressions with side effects
- **Events**: `onclick="handler"` or `@click="handler"` shorthand
- **Lists**: `for="{{list}}"` with `tid="uniqueField"` for performance
- **Conditionals**: `if`/`elif`/`else` must be adjacent sibling nodes
### List Rendering
```html
<!-- Default names: $item, $idx -->
<div for="{{items}}" tid="id">
<text>{{$idx}}: {{$item.name}}</text>
</div>
<!-- Custom names -->
<div for="{{(index, item) in items}}">
<text>{{index}}: {{item.name}}</text>
</div>
```
### Conditional Rendering
```html
<text if="{{status === 'ok'}}">Success</text>
<text elif="{{status === 'error'}}">Failed</text>
<text else>Loading</text>
```
- `if` removes from VDOM when false; `show` just hides (keeps in VDOM)
## Style Rules
- Use **Flexbox** exclusively — no float, no grid
- Units are **`px`** only (logical pixels, auto-scaled)
- Prefer `class` over inline `style` for reusable patterns
- Resource paths in `url()` use **absolute paths**: `url(/common/bg.png)`
## Script Rules
- Use `private: {}` for page data (not `data`)
- `private` properties are **not overridable** by parent components
- Import system APIs: `import router from '@system.router'`
- Declare used APIs in manifest.json `features` array first
### Lifecycle Hooks
```javascript
export default {
private: { /* reactive data */ },
onInit() {}, // data initialized
onReady() {}, // DOM rendered
onShow() {}, // page visible
onHide() {}, // page hidden
onDestroy() {} // page destroyed
}
```
## Common Mistakes
- Multiple root nodes in `<template>` → wrap in single `<div>`
- Using relative paths in manifest.json `icon` → use `/common/icon.png`
- Missing `config` field in manifest.json → build fails
- Router path doesn't match directory → page not found
- `for` without `tid` → poor list update performance
-226
View File
@@ -1,226 +0,0 @@
---
name: vela-quickapp
description: 'Create, build, and debug Xiaomi Vela JS applications (快应用). Use when: creating new Vela quickapp projects; building .rpk packages; writing UX template files; configuring manifest.json; developing for Xiaomi smartwatch/wearable devices; working with aiot-toolkit CLI; debugging Vela app compilation errors. DO NOT use for: general web development; React/Vue/Angular projects; native C/C++ Vela OS development.'
argument-hint: '[action: create|build|debug|explain] [topic]'
---
# Xiaomi Vela JS Application Development
## Overview
Xiaomi Vela JS 应用是一种基于 Xiaomi Vela OS 的轻量级应用,面向智能穿戴设备(手表等)。采用前端 MVVM 开发范式,使用 JavaScript + 类 HTML 模板 + CSS 样式。
## Project Structure
```
project-root/
├── src/
│ ├── manifest.json # 必需:应用配置
│ ├── app.ux # 必需:应用入口
│ ├── pages/
│ │ ├── index/
│ │ │ └── index.ux # 主页面
│ │ └── detail/
│ │ └── detail.ux # 其他页面
│ ├── common/ # 公共资源(注意小写)
│ │ ├── style.css
│ │ ├── utils.js
│ │ └── icon.png
│ └── i18n/ # 可选:多语言
│ ├── defaults.json
│ └── zh-CN.json
├── sign/ # 签名文件(release 模式需要)
│ ├── private.pem
│ └── certificate.pem
├── package.json
└── build.js # 构建脚本
```
## manifest.json (Required Fields)
```json
{
"package": "com.example.appname",
"name": "应用名称",
"icon": "/common/icon.png",
"versionName": "1.0",
"versionCode": 1,
"minAPILevel": 1,
"features": [],
"config": {
"logLevel": "log",
"designWidth": 480
},
"router": {
"entry": "pages/index",
"pages": {
"pages/index": { "component": "index" }
}
},
"display": {
"backgroundColor": "#ffffff"
}
}
```
### Critical Rules
- `config` 字段是**必需的**,缺少会导致构建失败
- `router.entry``router.pages` 的 key 必须与 `src/` 下的目录路径一致
- `component` 值必须与 `.ux` 文件名一致(不含扩展名)
- `icon` 路径使用绝对路径(以 `/` 开头),指向 `src/` 下的资源
- `designWidth` 根据目标设备设置(手表常用 480)
## UX File Syntax
每个页面由一个 `.ux` 文件定义,包含三个部分:
```html
<template>
<!-- 只能有一个根节点 -->
<div class="page">
<text>{{message}}</text>
</div>
</template>
<style>
.page {
flex-direction: column;
align-items: center;
}
</style>
<script>
export default {
private: {
message: 'Hello Vela'
}
}
</script>
```
### Template 语法
| 特性 | 语法 | 示例 |
|------|------|------|
| 数据绑定 | `{{variable}}` | `<text>{{title}}</text>` |
| 事件绑定 | `onclick="fn"``@click="fn"` | `<div @click="handleTap">` |
| 列表渲染 | `for="{{list}}"` | `<div for="{{items}}" tid="id">` |
| 条件渲染 | `if/elif/else` | `<text if="{{show}}">` |
| 显示隐藏 | `show="{{visible}}"` | `<div show="{{visible}}">` |
### 列表渲染详细
```html
<div for="{{list}}" tid="uniqueId">
<text>{{$idx}}: {{$item.name}}</text>
</div>
<!-- 自定义变量名 -->
<div for="{{(index, item) in list}}">
<text>{{index}}: {{item.name}}</text>
</div>
```
`tid` 属性指定数组元素唯一标识,优化渲染性能。必须保证该属性值在每个元素中唯一。
### Style 语法
- 类似 CSS,使用 Flexbox 布局
- 单位为 `px`(逻辑像素,框架自动适配不同屏幕)
- 支持 `class``style` 属性
- `style` 可以是 string 或 object
### Script 语法
```javascript
import router from '@system.router'
export default {
// 页面私有数据(不可被覆盖)
private: {
title: '页面标题',
list: []
},
// 生命周期
onInit() { /* 页面初始化 */ },
onReady() { /* 页面渲染完成 */ },
onShow() { /* 页面显示 */ },
onHide() { /* 页面隐藏 */ },
onDestroy() { /* 页面销毁 */ },
// 自定义方法
handleClick() {
router.push({ uri: '/pages/detail' })
}
}
```
## Build Process
### 安装依赖
```bash
npm install # 安装 aiot-toolkit
```
### 构建命令
```bash
# 开发模式(生成 .debug.rpk
npx aiot build
# 生产模式(生成 .release.rpk,需要 sign/ 下的签名文件)
npx aiot release
```
### 构建产物
- `dist/` — 最终 rpk 文件
- `build/` — webpack 编译中间产物
- `.temp_<project>/` — 临时构建目录
### 生成签名文件(release 模式)
```bash
openssl req -newkey rsa:2048 -nodes \
-keyout sign/private.pem \
-x509 -days 3650 \
-out sign/certificate.pem \
-subj "/CN=appname/O=Example/C=CN"
```
## Common Pitfalls & Solutions
| 错误 | 原因 | 解决 |
|------|------|------|
| `must have required property 'config'` | manifest.json 缺少 config 字段 | 添加 `"config": {"logLevel": "log", "designWidth": 480}` |
| `path does not exist` | router 路径与实际目录不匹配 | 确保 router.entry/pages key 与 src/ 下路径一致 |
| `Compilation failed` | component 名与 ux 文件名不匹配 | component 值必须是 ux 文件名(不含 .ux) |
| 资源文件找不到 | 大小写不匹配 | Windows 不区分大小写,但 Vela 内部可能区分,统一用小写 |
| 导入的 css 中图片路径失效 | 被导入文件的相对路径在编译后失效 | 使用绝对路径如 `/common/img.png` |
## File Path Rules
| 类型 | 路径方式 | 示例 |
|------|----------|------|
| 导入代码文件 | 相对路径 | `import util from '../common/utils'` |
| 引用资源文件 | 相对路径 | `src="./logo.png"` |
| manifest 中的资源 | 绝对路径 | `"icon": "/common/icon.png"` |
| CSS 中引用资源 | url() + 绝对路径 | `background: url(/common/bg.png)` |
## API & Components
- **UI 组件**: `<div>`, `<text>`, `<image>`, `<input>`, `<list>`, `<list-item>`, `<switch>`, `<slider>`
- **系统接口**: 通过 `features` 在 manifest.json 中声明后使用
- `system.router` — 页面路由
- `system.fetch` — 网络请求
- `system.storage` — 本地存储
- `system.device` — 设备信息
## References
- [官方文档](https://iot.mi.com/vela/quickapp/zh/guide/)
- [UI 组件](https://iot.mi.com/vela/quickapp/zh/components/)
- [JS 接口](https://iot.mi.com/vela/quickapp/zh/features/)
- [AIoT-IDE 下载](https://iot.mi.com/vela/quickapp/zh/guide/start/use-ide.html)
+5 -2
View File
@@ -14,8 +14,11 @@ src/
├── common/ ├── common/
│ ├── style.css # 公共样式 │ ├── style.css # 公共样式
│ └── utils.js # 公共工具函数 │ └── utils.js # 公共工具函数
└── Common/ └── common/
── icon.png # 应用图标 ── icon.png # 应用图标
├── photo1.png # 示例照片1
├── photo2.png # 示例照片2
└── photo3.png # 示例照片3
``` ```
## 开发说明 ## 开发说明
+3 -1
View File
@@ -4,7 +4,9 @@
"icon": "/common/icon.png", "icon": "/common/icon.png",
"versionName": "1.0", "versionName": "1.0",
"versionCode": 1, "versionCode": 1,
"minAPILevel": 1, "minPlatformVersion": 1100,
"simulationVersion": "default",
"deviceTypeList": ["watch"],
"features": [], "features": [],
"config": { "config": {
"logLevel": "log", "logLevel": "log",
+14 -14
View File
@@ -1,12 +1,12 @@
<template> <template>
<div class="page"> <div class="page">
<text class="title">照片查看器</text> <text class="title">照片查看器</text>
<div class="photo-list"> <list class="photo-list">
<div class="photo-item" for="{{photos}}"> <list-item class="photo-item" for="{{photos}}">
<image class="photo" src="{{$item.url}}"></image> <image class="photo" src="{{$item.url}}"></image>
<text class="photo-name">{{$item.name}}</text> <text class="photo-name">{{$item.name}}</text>
</div> </list-item>
</div> </list>
</div> </div>
</template> </template>
@@ -14,13 +14,13 @@
.page { .page {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px; padding: 20dp;
} }
.title { .title {
font-size: 40px; font-size: 40dp;
font-weight: bold; font-weight: bold;
margin-bottom: 30px; margin-bottom: 30dp;
color: #333333; color: #333333;
} }
@@ -32,20 +32,20 @@
.photo-item { .photo-item {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20dp;
padding: 15px; padding: 15dp;
background-color: #f5f5f5; background-color: #f5f5f5;
border-radius: 10px; border-radius: 10dp;
} }
.photo { .photo {
width: 100px; width: 100dp;
height: 100px; height: 100dp;
margin-right: 20px; margin-right: 20dp;
} }
.photo-name { .photo-name {
font-size: 30px; font-size: 30dp;
color: #666666; color: #666666;
} }
</style> </style>