From 2bdad3dc48b5289e57315a30b8125b221d261d23 Mon Sep 17 00:00:00 2001 From: Tevin <tingquanren@163.com> Date: Mon, 17 Mar 2025 17:21:46 +0800 Subject: [PATCH] 知识库,设置基于 cursor rules 的文档 --- _cursor.ai/rules/all-dev-specification.mdc | 396 +++++++++++++++++++ _cursor.ai/rules/all-system-role.mdc | 21 + /dev/null | 0 _cursor.ai/rules/all-project-info.mdc | 129 ++++++ _cursor.ai/rules/fit-base-pilot.mdc | 225 +++++++++++ _cursor.ai/rules/fit-base-fetcher.mdc | 312 +++++++++++++++ _cursor.ai/rules/type-controller.mdc | 7 _cursor.ai/readme.md | 52 ++ 8 files changed, 1,142 insertions(+), 0 deletions(-) diff --git a/_cursor.ai/readme.md b/_cursor.ai/readme.md new file mode 100644 index 0000000..7e9c875 --- /dev/null +++ b/_cursor.ai/readme.md @@ -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/ | \ No newline at end of file diff --git a/_cursor.ai/rules/all-dev-specification.mdc b/_cursor.ai/rules/all-dev-specification.mdc new file mode 100644 index 0000000..d9bd17b --- /dev/null +++ b/_cursor.ai/rules/all-dev-specification.mdc @@ -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 等 diff --git a/_cursor.ai/rules/all-project-info.mdc b/_cursor.ai/rules/all-project-info.mdc new file mode 100644 index 0000000..40e0023 --- /dev/null +++ b/_cursor.ai/rules/all-project-info.mdc @@ -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() {}, +}); +``` \ No newline at end of file diff --git a/_cursor.ai/rules/all-system-role.mdc b/_cursor.ai/rules/all-system-role.mdc new file mode 100644 index 0000000..d0410d7 --- /dev/null +++ b/_cursor.ai/rules/all-system-role.mdc @@ -0,0 +1,21 @@ +--- +description: +globs: +alwaysApply: true +--- +# 角色 + +## 你是一名拥有10年经验的资深Web前端开发工程师 +- 对代码质量有严格的要求,代码语义清晰、简洁高效 +- 拥有丰富的开发实践,擅长编写易于使用和维护的组件,能够设计干净健壮的架构 +- 懂得遵守最小改动原则,尽量不破坏现有功能 +- 擅长编用 markdown 写技术文档,层次分明、齐全易懂 + +## 你是一个重视需求分析的开发者 +- 善于从用户角度理解业务需求 +- 会主动发现需求中存在缺陷,并与用户讨论完善 +- 倾向于选择最简单的解决方案,避免过度设计 + +## 你是一个熟悉液化气行业的专业人士 +- 了解液化气充装、配送、销售等业务流程 +- 了解会员、订单、气瓶、电子秤等资产管理相关内容 \ No newline at end of file diff --git a/_cursor.ai/rules/fit-base-fetcher.mdc b/_cursor.ai/rules/fit-base-fetcher.mdc new file mode 100644 index 0000000..766f648 --- /dev/null +++ b/_cursor.ai/rules/fit-base-fetcher.mdc @@ -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 || {}; + }); + } + +} +``` + + diff --git a/_cursor.ai/rules/fit-base-pilot.mdc b/_cursor.ai/rules/fit-base-pilot.mdc new file mode 100644 index 0000000..0f753aa --- /dev/null +++ b/_cursor.ai/rules/fit-base-pilot.mdc @@ -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:更多跨端通讯明细待续) + diff --git a/_cursor.ai/rules/rules01.technologyStack.md b/_cursor.ai/rules/rules01.technologyStack.md deleted file mode 100644 index e69de29..0000000 --- a/_cursor.ai/rules/rules01.technologyStack.md +++ /dev/null diff --git a/_cursor.ai/rules/type-controller.mdc b/_cursor.ai/rules/type-controller.mdc new file mode 100644 index 0000000..a1f356c --- /dev/null +++ b/_cursor.ai/rules/type-controller.mdc @@ -0,0 +1,7 @@ +--- +description: +globs: src/pilots/**/P*.js +alwaysApply: false +--- + +# 数据控制器 -- Gitblit v1.9.1