WebApp【公共组件库】@前端(For Git Submodule)
Tevin
2025-03-17 2bdad3dc48b5289e57315a30b8125b221d261d23
知识库,设置基于 cursor rules 的文档
7 files added
1 files deleted
1142 ■■■■■ changed files
_cursor.ai/readme.md 52 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/all-dev-specification.mdc 396 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/all-project-info.mdc 129 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/all-system-role.mdc 21 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/fit-base-fetcher.mdc 312 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/fit-base-pilot.mdc 225 ●●●●● patch | view | raw | blame | history
_cursor.ai/rules/rules01.technologyStack.md patch | view | raw | blame | history
_cursor.ai/rules/type-controller.mdc 7 ●●●●● patch | view | raw | blame | history
_cursor.ai/readme.md
New file
@@ -0,0 +1,52 @@
# 文件夹介绍
## rules 文件夹
rules 是指编辑器根据一定条件自动读取的对 AI 的要求
> 注意:需要进行初始化
> 请双击执行项目根目录 `link-rules.cmd` 命令文件,将公共资源目录中的 rules 链接到编辑器中
> 然后在编辑器设置中查看是否链接成功(CursorSetting > Rules > ProjectRules)
### 规则类型:全局使用 `Always`
所有的聊天(Agent、Ask、Edit)和 ctrl+k 编辑,都会参考此规则
- [AI系统扮演角色](/src/components/_cursor.ai/rules/all-system-role.mdc)
- [项目介绍](/src/components/_cursor.ai/rules/all-project-info.mdc)
- [开发规范](/src/components/_cursor.ai/rules/all-dev-specification.mdc)
### 规则类型:按路径匹配 `Auto-Attached`
当路径规则匹配上时,会参考此规则
- 请求集
- [数据控制器](/src/components/_cursor.ai/rules/type-controller.mdc)
- 界面
- 子组件
- 公共组件
### 规则类型:自主决定 `Agent-Requested`
根据 Description 的文字描述,由 AI 自主决定是否需要参考此规则
仅 Agent 模式生效,非 Agent 模式需要我们自己 @ 此规则
- [请求集基类](/src/components/_cursor.ai/rules/fit-base-fetcher.mdc)
- [数据控制器基类](/src/components/_cursor.ai/rules/fit-base-pilot.mdc)
- 表单验证规则
## prompts 文件夹
prompts 是我们主动提问,和我们在聊天窗口叫 ai 干活一样
区别是对于经常执行的有具体要求的操作,固定下来方便反复使用
## 文档文件夹
公共组件库组件对应的文档
| 文档目录      | 对应的组件目录          |
| ------------- | ----------------------- |
| `common.doc`  | src/components/common/  |
| `forms.doc`   | src/components/forms/   |
| `layout.doc`  | src/components/layout/  |
| `plugins.doc` | src/components/plugins/ |
_cursor.ai/rules/all-dev-specification.mdc
New file
@@ -0,0 +1,396 @@
---
description:
globs:
alwaysApply: true
---
# 开发规范
## 开发规范宗旨
**代码首先是给人读的,其次才是给机器运行**,所以让代码容易阅读是你应尽的责任
包括但不限于以下内容:
- 变量、函数命名需具有实义
- 避免使用难懂的语法或设计,用最简单的方式解决问题
- 避免单块代码过长,拆分为多个更简短的函数或方法
- 避免代码重复,通过封装函数、类或模块进行复用,封装遵守单一职责原则
- 写注释应解释 ‌"为什么"‌ 而不仅仅是 ‌"做什么"‌
### 命名需要具有实义
命名的原则是名称中有具体实体含义的词汇
比如没有实义的
```js
const onClick = () => null; // 不好的命名
```
因为 `click` 可以发生在任何元素上,从这个名称上无法分辨业务种类,所以我们认为这个函数名没有实义,需要改进一下:
```js
const onProductSelected = () => null; // 良好的命名
```
改进后,从函数名一看便知,这是商品被选中了以后的需要进行的操作,又因为具备了在同一个页面的唯一性,能帮我们快速获取信息,知道这是哪个业务的哪个环节,大幅提高阅读效率,这就是实义
## 常见命名要求
### JS 变量和属性的命名
使用小驼峰,注意不能以动词开头
```js
// 变量
const name = '';
const orderList = '';
// 属性
const user = {
    name: '',
    lastOrder: '',
};
```
#### 变量命名的特别约定
**变量名允许 is 开头**
变量名不能用动词开头,但是允许 is 作为特例
```js
// 允许 is 开头的变量名
let isOpened = false;
// 更好的命名
let selectorOpened = false;
```
**事件变量用 evt**
事件 event 的简写,必须是 evt(不能用单字母 e )
```html
<view @tap="evt => onOrderClick(evt)"></view>
<CMenu :onClick="evt => onMenuItemClick(evt)" />
```
**报错信息用 err**
报错 error 的简写,必须是 err(不能用单字母 e )
```js
Taro.request({
    fail: err => {},
});
```
**后端数据 res**
后端返回数据,优先采用 resource 的简写 res
```js
$fetch.getEquipments().then(res => {});
```
### JS 方法的命名
#### 在数据控制层
**提供给界面层使用的方法,用 on 开头**
```js
// 数据控制层定义
onOpenOrderDetail() {}
```
```html
// 界面层使用
<view class="order-item" @tap="evt => onOpenOrderDetail()">
```
**数据层自用的方法,用下划线开头**
```js
// 数据控制层定义
_checkOrderDetailOpened() {}
onOpenOrderDetail() {
    // 数据控制层内部自用
    this._checkOrderDetailOpened();
}
```
#### 在组件内
**接收父级方法的时候,用 on 开头**
```js
export default {
    props: {
        // 选择会员后的回调
        onUserSelected: Function,
    },
}
```
**组件自身模版绑定的时候,用 handle 开头**
```js
export default {
    methods: {
        // 组件层定义
        handleUserSelected() {}
    }
}
```
```html
// 组件层自身模板绑定
<view class="user-item" @tap="evt => handleUserSelected()">
```
**函数相互调用的时候,用下划线开头**
```js
export default {
    methods: {
        // 组件层定义
        _checkUser() {},
        handleUserSelected() {
            // 组件层内部自用
            this._checkUser()
        }
    }
}
```
**给父级ref的引用进行调用的方法,使用 $ 开头**
```js
export default {
    methods: {
        // 组件层定义
        $close() {}
    }
}
```
父级
```html
<CUserDetail ref="userDetail">
```
```js
onCloseAll() {
    // 父级 ref 调用
    this.$refs.userDetail.$close()
}
```
### 样式命名
样式名的命名,采用横杠连接
```css
.page-name {}
```
#### 界面样式
任何一个界面必定包含众多样式,所以:
* 最父级样式名固定,与界面名称一直
* 中间层,需要以父级为前缀,接板块说明
* 末端则可单名,且尽量不含实义
```css
/** 在界面 userList.vue 中,最父级样式名名称固定 **/
.user-list {
    /** 中间多层,以父级为前缀 **/
    .user-list-title {
        /** 末端可单名,尽量非实义 **/
        .left,
        .text,
        .icon,
        .sub,
        .close {}
    }
}
```
#### 组件样式
组件样式名必须已 `c-` 开头,最父级样式名必须与组件名挂钩
例如,组件 CMenu.vue 中,最父级样式名必须是:
```css
.c-menu {}
```
### 文件命名
常用文件命名约定
#### 界面层文件
界面层文件(.vue、.scss),采用小驼峰命名
```
index.vue
index.scss
```
#### 数据控制层文件
数据控制器文件(.js),以固定前缀 P 开头,再接大驼峰
```
PIndex.js
```
数据层代码定义的名称,需要和文件名一致
```js
export class PIndex extends Pilot {}
```
#### 组件文件
不论是公共组件还是页面子组件(.vue、.scss),都以固定前缀 C 开头,再接大驼峰
```
CMenu.vue
CMenu.scss
```
组件代码定义的名称,需要和文件名一致
```js
// CMenu.vue 的组件名,必须是 CMenu
export default {
    name: 'CMenu',
}
```
#### 请求文件
请求层文件(.js),以固定前缀 F 开头,再接大驼峰
```
FCommon.js
```
请求集合的代码定义名称,需要和文件名一致
```js
// FCommon.js 的类名称,必须是 FCommon
class FCommon extends Fetcher {}
// FCommon.js 代表的类的实例名称,则是 $fetchCommon
export const $fetchCommon = new FCommon();
```
## 常见书写要求
### 基础书写要求
书写规则都由编辑器自动格式化即可,例如
- 使用四个空格代替 tab
- 单行代码宽度最多100个字符
- js 代码使用单引号
更多规则请参考根目录 `.prettierrc` 文件
### JS 中的书写
#### 变量定义符
弃用 `var` ,改用 `const` 与 `let` ,且优先使用 `const` ,除非要改值
```js
const name = 'Tevin';
```
```js
let count = 0;
count++;
```
注意,const 定义的对象,其属性是可以改值的
```js
const search = {
    count: 1
};
search.count++; // 允许运行
search = {}; // 报错
```
所以:
* 对于普通数据,如果明确要变更变量值,才使用 `let`
* 对于引用数据,如果明确需要重新赋值一个引用对象,才使用 `let`
#### 变量判断
为了避免弱类型带来的bug追踪困难,变量判断需要使用恒等号,必须判断类型一致
```js
// 需要同时判断值和类型
if (order.state === 1) {}
```
#### 模版中的函数调用
- 模版中调用函数,为了便于阅读,必须显式的书写 evt 变量和箭头函数,即:  `evt => fncName()`,**这是规范要求,不是代码冗余**
- 把 Taro 自身组件视为普通元素,元素绑定事件使用 `@tap` 的方式
- 非元素皆是组件,组件使用 `:onClick` 的方式绑定
例如:
```html
<!-- 元素绑定事件使用 -->
<view @tap="() => onOpenOrderDetail()"></view>
<!-- 组件绑定事件使用 -->
<CMenu :onItemClick="evt => onMenuItemClick(evt)" />
```
#### 以 JSDoc 格式写方法注释
公共代码中的方法都需要以 JSDoc 的格式写注释
```js
export class Tools {
    /**
     * 显示消息提示框
     * @param {string} msg - 提示的消息内容
     * @param {number} [duration=2000] - 提示框显示时长(毫秒)
     * @param {boolean} [mask=false] - 是否显示透明蒙层,防止触摸穿透
     */
    static toast(msg, duration = 2000, mask = false) {
    }
}
```
### CSS 属性顺序
为了避免 css 属性写重复,请按如下顺序书写:
* **文档流属性**
    display, position, float, clear, visibility, table-layout 等
* **大小属性**
    width, height, margin, padding 等
* **文字排版属性**
    font, line-height, text-align, text-indent, vertical-align 等
* **装饰性属性**
    color, background, border, opacity, shadow, cursor 等
* **生成内容属性**
    content, list-style, quotes 等
* **二次渲染属性**
    zoom, transform 等
* **动画属性**
    transition, animation 等
_cursor.ai/rules/all-project-info.mdc
New file
@@ -0,0 +1,129 @@
---
description:
globs:
alwaysApply: true
---
# 项目介绍
## 工程介绍
这是一套Web前端开发的工程文档,用于指导移动端(H5网页、混合App、小程序等)的开发
## 技术栈
- 语法框架:Vue(v2.5.0)
- 工程框架:Taro(v3.2.13)
- 显示框架:Taro-UI-Vue(v1.0.0-beta.10)
- 样式:Sass
说明:使用时,优先使用公共组件库的组件,其次是 Taro-UI-Vue 的组件,最后才是 Taro 本身的基础元素组件
## 工程目录结构
工程主要目录及其用途
- root/(根目录)
    - public/(静态资源目录)
    - src/(开发源码目录)
        - components/(公共资源目录)
            - bases/(公共基类目录)
            - common/(公共工具目录)
            - forms/(公共表单组件目录)
            - layout/(公共排版组件目录)
            - plugins/(公共复杂组件目录)
        - fetchers/(请求层目录)
            - FName.js(请求集)
        - pages/(界面层目录)
            - pageGroup/(界面层分组目录)
                - pageName/(界面层单页目录)
                    - cmpt/(界面子组件目录)
                        - CName.vue(子组件)
                        - cName.scss(子组件样式)
                    - page.vue(界面)
                    - page.scss(界面样式)
        - pilots/(数据控制层目录)
            - _overall/(全局数据控制目录)
            - pilotGroup/(数据控制层分组目录)
                - mixin/(混合件目录)
                    - MName.js(混合件)
                - PName.js(数据控制器)
### 目录解释
- **公共资源目录**:移动端所有项目公用,使用 git 子模块跨项目同步
- **界面层**:包含界面和子组件,界面子组件是对整个逻辑流程的单个节点的封装(TODO:界面拆分原理)
- **数据控制层**:包含数据控制器和混合件,混合件是数据控制器代码片段共享的一种设计(非vue混入)
- **请求层**:请求层中的单个文件是请求集,一个请求集存放某个类型业务的所有请求的集合
### 短路径映射
工程资源引用时,通常使用更短的引用路径:
* `@components` 代表 `root/src/components`
* `@fetchers` 代表 `root/src/fetchers`
* `@pages` 代表 `root/src/pages`
* `@pilots` 代表 `root/src/pilots`
例如:'@components/layout/h5Page' 实际引用的是 'root/src/components/layout/h5Page'
## 页面构成
### 页面定义
当我们说页面的时候,不是普通的 vue 组件,而是由**界面层**和**数据控制层**两部分共同组成的代码实体,逻辑结构如下
- 页面:
    - 界面层(root/src/pages/pageGroup/pageName/page.vue)
        - 界面子组件(root/src/pages/pageGroup/pageName/cmpt/CName.vue)
    - 数据控制层(root/src/pilots/pilotsGroup/PName.js)
        - 请求层(root/src/fetchers/FName.js)
(当我们说组件、子组件、公共组件的时候,才是指普通 Vue 组件)
### 界面层和数据控制层的拆分
一个业务页面的界面层和数据控制层,实际上是对一个 Vue 组件的拆分,最终运行的时候,还是会合二为一,还原成原本的 Vue 实例
因此,数据控制层其实是对这个 Vue 实例部分功能的一种转写,类似于语法糖
**数据控制层写法**
例如:
```js
// 数据控制器
export class PIndex extends Pilot {
    $data() {
        return {
            a: 1,
        };
    }
    $computed = {
        a2() {},
    };
    $mounted() {}
    onOpenSelector() {}
}
```
会转换成
```js
// 标准 Vue
const Component = Vue.extend({
    data() {
        return {
            a: 1,
        };
    },
    computed: {
        a2() {},
    },
    methods: {
        onOpenSelector() {},
    },
    mounted() {},
});
```
_cursor.ai/rules/all-system-role.mdc
New file
@@ -0,0 +1,21 @@
---
description:
globs:
alwaysApply: true
---
# 角色
## 你是一名拥有10年经验的资深Web前端开发工程师
- 对代码质量有严格的要求,代码语义清晰、简洁高效
- 拥有丰富的开发实践,擅长编写易于使用和维护的组件,能够设计干净健壮的架构
- 懂得遵守最小改动原则,尽量不破坏现有功能
- 擅长编用 markdown 写技术文档,层次分明、齐全易懂
## 你是一个重视需求分析的开发者
- 善于从用户角度理解业务需求
- 会主动发现需求中存在缺陷,并与用户讨论完善
- 倾向于选择最简单的解决方案,避免过度设计
## 你是一个熟悉液化气行业的专业人士
- 了解液化气充装、配送、销售等业务流程
- 了解会员、订单、气瓶、电子秤等资产管理相关内容
_cursor.ai/rules/fit-base-fetcher.mdc
New file
@@ -0,0 +1,312 @@
---
description: 数据控制器基类,所有数据控制器都必须继承此类
globs:
alwaysApply: false
---
# 请求层基类 Fetcher.js
## 功能说明
类 Fetcher 是一个用于处理 HTTP 请求的基类,包含了URL拼接、响应数据适配、报错提示等多种方法来简化和统一请求的处理
请求层的每个请求集,都必须继承此类
## 引用方法
```js
import {
    Fetcher
} from '@components/bases/Fetcher';
```
## 构造函数
构造函数接受一个配置对象作为参数,包含如下属性:
- **urlPrefix** 数组,包含两项,第一项为本地 Mock 的 URL 前缀,第二项为服务器接口的前缀
```js
class FCommon extends Fetcher {
    constructor() {
        // 构造函数使用示例
        super({
            urlPrefix: ['/api/order/', '/mini/order/'],
        });
    }
}
```
## 主要方法
### `spellURL(devSuffix, serSuffix)`
**功能**
根据开发环境和生产环境拼接 URL 地址
**参数**
- `devSuffix` (String):开发环境下的 URL 后缀
- `serSuffix` (String, 可选):生产环境下的 URL 后缀。如果未提供,则使用 `devSuffix`
**返回值**
- (String):处理后的完整 URL 地址
**示例**
```javascript
class FCommon extends Fetcher {
    getUserInfo(id) {
        const url = this.spellURL('getUserInfo', 'user/user_info');
    }
}
```
**注意事项**
- 如果启用了 Mock 模式(比如本地开发时),URL 会指向本地的 JSON 文件
- 自动处理路径中的 `../` 和多余的 `/`,确保路径正确
---
### `get(url, data, options)`
**功能**
发起 GET 请求
**参数**
- `url` (String):请求的 URL 地址
- `data` (Object):请求参数
- `options` (Object,可选):请求配置
**返回值**
- (Promise):返回请求结果的 Promise
---
### `post(url, data, options)`
**功能**
发起 POST 请求,query 方法的简写
**参数**
- `url` (String):请求的 URL 地址
- `data` (Object):请求参数
- `options` (Object,可选):请求配置
**返回值**
- (Promise):返回请求结果的 Promise
**示例**
```js
class FCommon extends Fetcher {
    getUserInfo() {
        const url = this.spellURL('getUserInfo', 'user/user_info');
        const send = {};
        return this.post(url, send);
    }
}
```
---
### `query(type, url, data, options)`
**功能**
发起基础 AJAX 请求
**参数**
- `type` (String):请求类型(如 `'get'`、`'post'`)
- `url` (String):请求的 URL 地址
- `data` (Object,可选):请求参数
- `options` (Object,可选):请求配置(参照下文)
**返回值**
- (Promise):返回请求结果的 Promise
**注意事项**
- 小程序环境下会自动处理 Cookie,无需手动设置
---
### `stringToCamel(str)`
**功能**
将下划线命名的字符串转换为小驼峰命名
**参数**
- `str` (String):需要转换的字符串
**返回值**
- (String):转换后的小驼峰字符串
---
### `stringToUnderline(str)`
**功能**
将小驼峰命名的字符串转换为下划线命名,如果没有大写字母,则返回原字符串
**参数**
- `str` (String):需要转换的字符串
**返回值**
- (String):转换后的下划线字符串
---
### `transKeyName(type, json)`
**功能**
将 JSON 对象的键名在驼峰与下划线命名模式之间转换
**参数**
- `type` (String):转换的目标类型,有两个值:
  - `'camel'`:下划线转小驼峰
  - `'underline'`:小驼峰转下划线
- `json` (Object):需要转换的 JSON 对象
**返回值**
- (Object):转换后的 JSON 对象
**示例**
```js
class FCommon extends Fetcher {
    saveUserInfo() {
        const json = { 'user_name': 'John' };
        const result = this.transKeyName('camel', json); // 返回 { userName: 'John' }
    }
}
```
**注意事项**
- 支持嵌套对象的递归转换
## 请求 query 的 options 配置
### `hostType` 主机类型
在混合 App 中,指定本次请求的主机类型(即域名),有两个值:
- `base`:基础主机(预登陆用)
- `main`:默认主机,默认值,正常业务主机
  - 优先由 java 层在 webview 的 URL 上指定
  - 未指定时,按文件 project.config.json 的 host.buildHost 项值指定
**示例**
```js
class FCommon extends Fetcher {
    loginPrepare() {
        const url = this.spellURL('loginPrepare', 'User/preLogin');
        const send = {};
        return this.post(url, send, { hostType: 'base' });
    }
}
```
### `silence` 静音请求
设为 true 时,如果请求失败,不显示提示
**示例**
```js
class FCommon extends Fetcher {
    getUserInfo() {
        const url = this.spellURL('getUserInfo', 'User/info');
        const send = {};
        return this.post(url, send, { silence: true });
    }
}
```
## 响应处理
### 处理流程
1. query 方法调用 Taro.request 发送请求
2. 收到响应后,先检测响应数据格式,发现其他类型的响应格式时,会自动转换为:**前端统一响应数据格式**
3. 根据响应格式
    - 如果成功,将响应内容,回传给数据控制层
    - 如果失败,直接失败提示,回传空内容给数据控制层
4. 将响应内容传给数据控制层之前,会进行如下处理
    - 将数据中的键名,转换为的驼峰
    - 将数据中的内容,如果是常规数字的字符串,转为数值
### 前端统一响应数据格式
格式如下:
```js
{
    state: { // 状态
        code, // 状态码
        msg // 状态消息
    },
    data: { // 实际数据,必须是 Object
    }
}
```
例如:
```json
{
    "state": {
        "code": 2000,
        "msg": "OK"
    },
    "data": {
        "id": 1,
        "name": "Tevin"
    }
}
```
注意,为了保证接口数据的扩展性,data 只能接 Object 类型,禁止接其他类型
### 前端统一响应状态码
- `2000`:通用请求成功
- `2001`:请求成功,但是没有数据,弹窗提示 msg(仅特殊情况使用)
- `5000`:通用请求失败,弹窗提示 msg
- `9001`:登录已过期,返回登录页
- `9002`:已登录但没有操作权限,弹窗提示 msg
## 请求调用
请求层的每一个请求集,都会实例化自身作为全局单例,使用时,只需引用已经实例化好的全局单例,直接发请求即可
```js
import { Fetcher } from '@components/bases/Fetcher';
class FCommon extends Fetcher {}
// 全局单例
export const $fetchCommon = new FCommon();
```
```js
import {
    $fetchCommon
} from '@fetchers/FCommon';
export class PPageName extends Pilot {
    // 在数据控制层中,发请求示例
    onLoadUserInfo() {
        Taro.showLoading();
        $fetchCommon.getUserInfo(this.userId)
            .then(res => {
                Taro.hideLoading();
                if (!res) {
                    return;
                }
                this.userInfo = res || {};
            });
    }
}
```
_cursor.ai/rules/fit-base-pilot.mdc
New file
@@ -0,0 +1,225 @@
---
description:
globs:
alwaysApply: false
---
# 数据控制层基类 Pilot.js
## 功能说明
Pilot 是数据控制层的基类
- 将 Vue 对象的数据绑定、方法绑定、生命周期等功能拆分出来,进行独立申明(拆分后界面层管显示,数据控制层管数据流转)
- 增加跨页通讯、键名转换等相关的能力
- 每个数据控制器都需要继承此类
- 子类实例化后,再将申明的内容合并回去
## 引用方法
```js
import {
    Pilot
} from '@components/bases/Pilot';
```
## 主要方法
### `createOptions(dataAdd)`
**功能**
创建页面合并对象,将实例的属性和方法映射到页面合并对象中
**参数**
- `dataAdd` (Object):需要添加到页面中的额外数据
**返回值**
- (Object):页面合并对象
**注意事项**
- 如果 `dataAdd` 中包含 `assets` 属性,会自动调用 transAssets 转换其中的图片地址值
- `$` 开头的属性会被直接映射到合并对象中,同时去掉 `$` 前缀
- 非 `$` 开头的属性会被映射到合并对象的 `methods` 属性中
---
### `transAssets(assets)`
**功能**
转换静态图片引用的路径,根据运行环境(H5 或小程序)生成图片实际发布的路径
**参数**
- `assets` (Object):包含图片路径的对象
**返回值**
- (Object):转换后的图片路径对象
#### 静态图片地址转换说明
在目前项目体系设计下,由于不同项目(公众号、小程序、混合app)图片加载机制的不同,设置了一个统一的图片地址转换入口
经过静态方法 transAssets 转换,可以获得图片实际发布的地址
**示例1**
```javascript
const assets = { logo: 'assets/images/logo.png' };
const result = Pilot.transAssets(assets); // 输出转换后的图片实际发布地址
```
**示例2**
```HTML
<template>
    <image :src="assets.homeNav1" mode="aspectFit" />
</template>
```
```js
export default {
    name: 'index',
    ...new PIndex().createOptions({
        // 首页菜单图片,会全部经过 transAssets 进行路径转换,转换为图片实际发布的地址
        assets: {
            homeNav1: 'assets/img/home-nav-01.png',
            homeNav2: 'assets/img/home-nav-02.png',
            homeNav3: 'assets/img/home-nav-03.png',
        },
    }),
};
```
**注意事项**
移动端的图片不要使用 css 背景,而是使用 image 图片元素,再填充由 transAssets 生成的实际发布地址
## 页面合并流程
`createOptions` 是类 Pilot 的核心方法,此方法将独立申明的信息转换为合并对象,以方便原 Vue 对象进行合并
### 第一步:生成合并对象
* 扫描当前类,以 `$` 开头的属性或方法,去掉 `$` 符后,直接作为 Vue 的属性或方法准备合并
* 扫描当前类,非以 `$` 开头的方法,作为 Vue 对象申明中 methods 的子级方法准备合并
* 生成合并对象,用以进行合并
```js
// 数据控制器中,用 $ 开头的视为 Vue 属性或方法
export class PIndex extends Pilot {
    $data() {
        return {
            a: 1,
        };
    }
    $computed = {
        a2() {},
    };
    $mounted() {}
}
// 合并后的实际效果
const Component = Vue.extend({
    data() {
        return {
            a: 1,
        };
    },
    computed: {
        a2() {},
    },
    mounted() {},
});
```
```js
// 数据控制器中,非 $ 开头的,转到 methods 中
export class PIndex extends Pilot {
    onOpenSelector() {}
}
// 合并后的实际效果
const Component = Vue.extend({
    methods: {
        onOpenSelector() {},
    },
});
```
注意:不能在数据控制器中定义 $name、$components、$methods 这三项,因为实际合并时会覆盖
### 第二步:实际合并
数据控制层和界面层实际的合并,其实是写在界面层上
由界面层实例化数据控制层,并使用其基类的 createOptions 方法生成选项,再合并进原 Vue 对象
```js
// 引入界面层对应数据控制器
import {
    PPageName
} from '@pilots/pilotGroup/PPageName';
export default {
    name: 'PageName',
    components: {},
    // 示例化数据控制器,由实例生成 Vue 选项,再展开合并到界面 Vue 对象上
    ...new PPageName().createOptions(),
};
```
## 页面能力扩展
### 跨页面通讯
Taro 的跨页面通讯能力,仅小程序可用,非小城不能跨页通讯非常不方便
因此,使用 createOptions 创建合并对象时,给页面的 Vue 实例,增加了跨页面通讯的方法,兼容所有平台
**跨页发送:$poster**
此方法挂载到 vue 实例的 this 上
```js
this.$poster(direction, action, data);
```
参数:
- `direction` (String,必填):发送去向,有两值:
  - `'nextPage'`:发生给下一页(如果正在创建,会等待创建成功后再传递)
  - `'prevPage'`:发送给上一页
- `action` (String,必填):动作名称
- `data` (any,可选):携带数据
例如:
```js
export class PList extends Pilot {
    onSaveSetting() {
        // 设置保存后,通知上页
        this.$poster('prevPage', 'settingChanged');
    }
}
```
**跨页接收:$onMessage**
此方法的用法,类似生命周期的钩子
```js
$onMessage(action, data) {}
```
例如:
```js
export class PIndex extends Pilot {
    $onMessage(action, data) {
        // 接收消息,设置已变更
        if (action === 'settingChanged') {
            // do something
        }
    }
}
```
### 跨端通讯
当我们进混合 APP 开发时,我们需要与 java 层进行通讯,使用 createOptions 创建合并对象时,增加了跨通讯的能力
(TODO:更多跨端通讯明细待续)
_cursor.ai/rules/rules01.technologyStack.md
_cursor.ai/rules/type-controller.mdc
New file
@@ -0,0 +1,7 @@
---
description:
globs: src/pilots/**/P*.js
alwaysApply: false
---
# 数据控制器