From 3b03f87a02458f719e2eb4bf112a13441b427d14 Mon Sep 17 00:00:00 2001 From: ‘chensiAb’ <‘chenchenco03@163.com’> Date: Tue, 25 Mar 2025 13:54:34 +0800 Subject: [PATCH] Merge branch 'master' of ssh://dev.zhiheiot.com:29418/mob-components --- forms/textarea/CTextArea.vue | 3 _cursor.ai/文档说明.md | 47 _cursor.ai/plugins.doc/echarts.doc/CECharts.doc.md | 138 ++ _cursor.ai/prompts/创建一个请求.prompts.md | 79 + forms/checkbox/CCheckBox.vue | 8 _cursor.ai/rules/type-surface.mdc | 43 _cursor.ai/rules/type-component.mdc | 57 _cursor.ai/layout.doc/description.doc/CDescription.doc.md | 149 ++ _cursor.ai/rules/type-pilot.mdc | 47 _cursor.ai/plugins.doc/infiniteScroll.doc/CInfiniteScroll.doc.md | 91 + forms/datePicker/CDatePicker.vue | 3 _cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md | 15 _cursor.ai/layout.doc/card.doc/CCard.doc.md | 134 ++ forms/form/CForm.vue | 5 forms/select/CSelect.vue | 3 _cursor.ai/rules/all-dev-specification.mdc | 304 ++-- _cursor.ai/rules/all-system-role.mdc | 21 _cursor.ai/link-rules.cmd | 29 _cursor.ai/rules/fit-base-pilot.mdc | 28 _cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md | 78 _cursor.ai/layout.doc/h5Page.doc/CH5Page.doc.md | 175 ++ _cursor.ai/prompts/更新组件目录.prompts.md | 35 _cursor.ai/prompts/更新公共组件文档.prompts.md | 61 + _cursor.ai/工作共识.md | 138 ++ _cursor.ai/layout.doc/navCustomBar.doc/CNavCustomBar.doc.md | 80 + _cursor.ai/layout.doc/drawer.doc/CDrawer.doc.md | 199 +++ _cursor.ai/layout.doc/numerical.doc/CNumerical.doc.md | 117 + _cursor.ai/plugins.doc/qrcode.doc/CQRCode.doc.md | 76 + forms/imagePicker/CImagePicker.vue | 3 _cursor.ai/forms.doc/input.doc/CInput.doc.md | 8 _cursor.ai/forms.doc/form.doc/CForm.doc.md | 18 _cursor.ai/plugins.doc/filter.doc/CFilter.doc.md | 132 ++ _cursor.ai/layout.doc/homeNav.doc/CHomeNav.doc.md | 127 ++ _cursor.ai/rules/fit-base-fetcher.mdc | 66 _cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md | 4 forms/chinaArea/CChinaArea.vue | 3 forms/numberValve/CNumberValve.vue | 5 _cursor.ai/forms.doc/select.doc/CSelect.doc.md | 4 _cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md | 57 forms/numberStep/CNumberStep.vue | 2 forms/userSignature/CSignatureLayer.vue | 182 +- _cursor.ai/layout.doc/alert.doc/CAlert.doc.md | 129 ++ _cursor.ai/prompts/创建一个页面.prompts.md | 70 + forms/userSignature/cSignatureLayer.scss | 6 _cursor.ai/组件目录.md | 176 ++ forms/input/CInput.vue | 2 _cursor.ai/rules/type-fetchers.mdc | 38 /dev/null | 0 _cursor.ai/layout.doc/waiting.doc/CWaiting.doc.md | 107 + _cursor.ai/rules/all-project-info.mdc | 128 ++ _cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md | 15 forms/switch/CSwitch.vue | 3 _cursor.ai/common.doc/Tools.doc.md | 0 _cursor.ai/layout.doc/anchor.doc/CAnchor.doc.md | 150 ++ 54 files changed, 3,248 insertions(+), 350 deletions(-) diff --git "a/_cursor.ai/101-\345\267\245\347\250\213\344\273\213\347\273\215.md" "b/_cursor.ai/101-\345\267\245\347\250\213\344\273\213\347\273\215.md" deleted file mode 100644 index f1987f3..0000000 --- "a/_cursor.ai/101-\345\267\245\347\250\213\344\273\213\347\273\215.md" +++ /dev/null @@ -1,299 +0,0 @@ -# 工程介绍 - -- [工程介绍](#工程介绍) - - [工程介绍](#工程介绍-1) - - [技术栈](#技术栈) - - [工程目录结构](#工程目录结构) - - [短路径映射](#短路径映射) - - [页面构成](#页面构成) - - [界面层和数据控制层的拆分](#界面层和数据控制层的拆分) - - [新页面模板](#新页面模板) - - [界面层空白模板](#界面层空白模板) - - [H5 空白界面模板](#h5-空白界面模板) - - [小程序空白界面模板](#小程序空白界面模板) - - [空白样式文件模板](#空白样式文件模板) - - [数据控制层空白模板](#数据控制层空白模板) - - [数据控制器空白模板](#数据控制器空白模板) - - [请求集空白模板](#请求集空白模板) - -## 工程介绍 - -这是一套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(数据控制器) - -### 短路径映射 - -工程资源引用时,通常使用更短的引用路径: - -* `@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 实例部分功能的一种转写,类似于语法糖 - -**数据控制层写法** - -例如: - -```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() {}, -}); -``` - -更多细节,请参照《数据控制层基类Pilot》 - -## 新页面模板 - -新建页面时,页面初始内容,可参考如下空白模板 - -### 界面层空白模板 - -#### H5 空白界面模板 - -H5 界面需要 CPage、CContent、CNavBar 这三个基础页面组件 - -```html -/** -* pageName - 页面名称 -* @author 作者 -*/ - -<template> - <CPage> - <CNavBar title="页面名称" /> - <CContent class="page-name"> - <!-- 页面内容 --> - </CContent> - </CPage> -</template> - -<script> - import Taro from '@tarojs/taro'; - import {} from 'taro-ui-vue'; - import { - PPageName - } from '@pilots/pilotGroup/PPageName'; - import { - CPage, - CContent, - CNavBar - } from '@components/layout/h5Page'; - import './pageName.scss'; - - export default { - name: 'PageName', - components: {}, - ...new PPageName().createOptions(), - }; -</script> -``` - -#### 小程序空白界面模板 - -```html -/** -* pageName - 页面名称 -* @author 作者 -*/ - -<template> - <view class="page-name"> - <!-- 页面内容 --> - </view> -</template> - -<script> - import Taro from '@tarojs/taro'; - import {} from 'taro-ui-vue'; - import { - PPageName - } from '@pilots/pilotGroup/PPageName'; - import './pageName.scss'; - - export default { - name: 'PageName', - components: {}, - ...new PPageName().createOptions(), - }; -</script> -``` - -#### 空白样式文件模板 - -```css -/** - * pageName - 页面名称 - * @author 作者 - */ - -@import "../../../components/common/sassMixin"; - -.page-name {} -``` - -### 数据控制层空白模板 - -#### 数据控制器空白模板 - -```js -/** - * PPageName - 页面名称 - * @author 作者 - */ - -import Taro from '@tarojs/taro'; -import { - Pilot -} from '@components/bases/Pilot'; -import { - $fetchCommon -} from '@fetchers/FCommon'; - -export class PPageName extends Pilot { - - $data() { - return {}; - } - - $mounted() { - this.onLoadDataResource(); - } - - // 加载用户详情 - onLoadDataResource() { - Taro.showLoading(); - $fetchCommon.getPageDetail() - .then(res => { - Taro.hideLoading(); - if (!res) { - return; - } - // do something - }); - } - -} -``` - -说明:请求异常由请求层的基类自动处理,数据控制层跳过错误的逻辑,只处理请求成功的后续业务 - -#### 请求集空白模板 - -```js -/** - * FCommon - 公用请求集 - * @author 作者 - */ - -import { - Fetcher -} from '@components/bases/Fetcher'; - -class FCommon extends Fetcher { - - constructor() { - super({ - // url前缀(本地路径, 服务器路径) - urlPrefix: ['/api/common/', '/serverPath/'], - }); - } - - // 读取页面详情 - getPageDetail() { - const url = this.spellURL('getPageDetail', 'page/Detail'); - const send = {}; - return this.post(url, send); - } - -} - -export const $fetchCommon = new FCommon(); -``` diff --git "a/_cursor.ai/301-\345\205\254\345\205\261\350\241\250\345\215\225\347\273\204\344\273\266.md" "b/_cursor.ai/301-\345\205\254\345\205\261\350\241\250\345\215\225\347\273\204\344\273\266.md" deleted file mode 100644 index b040b7e..0000000 --- "a/_cursor.ai/301-\345\205\254\345\205\261\350\241\250\345\215\225\347\273\204\344\273\266.md" +++ /dev/null @@ -1,46 +0,0 @@ -# 公共表单组件 - -- [公共表单组件](#公共表单组件) - - [组件列表](#组件列表) - - [表单容器组件](#表单容器组件) - - [基础输入组件](#基础输入组件) - - [选择组件](#选择组件) - - [开关组件](#开关组件) - - [数值组件](#数值组件) - - [表单项验证规则](#表单项验证规则) - - [完整表单示例](#完整表单示例) - -移动端表单组件库 - -## 组件列表 - -### 表单容器组件 -- [CForm 表单组件](./forms/CForm.md) -- [CFormItem 表单项组件](./forms/CFormItem.md) -- [CFormSubmit 表单提交按钮组件](./forms/CFormSubmit.md) -- [CFormAgreement 表单协议组件](./forms/CFormAgreement.md) - -### 基础输入组件 -- [CInput 文本输入框组件](./forms/CInput.md) -- [CInputPhoneCode 手机验证码输入框组件](./forms/CInputPhoneCode.md) -- [CInputScanCode 扫码输入框组件](./forms/CInputScanCode.md) -- [CInputExpressCode 快递单号输入框组件](./forms/CInputExpressCode.md) -- [CTextArea 多行文本输入组件](./forms/CTextArea.md) - -### 选择组件 -- [CSelect 下拉选择组件](./forms/CSelect.md) -- [CJumpSelect 跳转选择组件](./forms/CJumpSelect.md) -- [CCheckBox 复选框组件](./forms/CCheckBox.md) -- [CDatePicker 日期选择组件](./forms/CDatePicker.md) - -### 开关组件 -- [CSwitch 开关组件](./forms/CSwitch.md) -- [CSwitchRadio 开关式单选组件](./forms/CSwitchRadio.md) - -### 数值组件 -- [CNumberStep 数字步进器组件](./forms/CNumberStep.md) -- [CNumberValve 数值滑块组件](./forms/CNumberValve.md) - -## 表单项验证规则 - -## 完整表单示例 \ No newline at end of file diff --git "a/_cursor.ai/210-\345\205\254\345\205\261\345\267\245\345\205\267\347\256\261Tools.md" b/_cursor.ai/common.doc/Tools.doc.md similarity index 100% rename from "_cursor.ai/210-\345\205\254\345\205\261\345\267\245\345\205\267\347\256\261Tools.md" rename to _cursor.ai/common.doc/Tools.doc.md diff --git a/_cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md b/_cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md index 1998ff2..dc8dac3 100644 --- a/_cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md +++ b/_cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md @@ -14,8 +14,12 @@ - `itemRes` (Object,必填):表单数据资源对象,表单组件内部机制专用 - `options` (Array,可选):选项列表,每个选项应包含 label 和 value 属性,默认为空数组 -- `boxType` (String,可选):勾选类型,可选值有 checkbox(多选)、radio(单选),默认为 checkbox -- `displayType` (String,可选):显示模式,可选值有 plane(直接显示)、dialog(弹窗选择),默认为 plane +- `boxType` (String,可选):勾选类型,默认为 checkbox + - `checkbox`:多选模式 + - `radio`:单选模式 +- `displayType` (String,可选):显示模式,默认为 plane + - `plane`:直接显示模式 + - `dialog`:弹窗选择模式 - `placeholder` (String,可选):输入框占位提示文本 ## 使用示例 @@ -121,12 +125,13 @@ ## 注意事项 1. 组件支持两种选择类型: - - `checkbox`:多选模式,可以选择多个选项,表单值为数组 - - `radio`:单选模式,只能选择一个选项,表单值为单个值 + - `checkbox`:多选模式,可以选择多个选项,表单值为数组 + - `radio`:单选模式,只能选择一个选项,表单值为单个值 2. 组件支持两种显示模式: - `plane`:直接显示模式,选项直接显示在表单项下方 - `dialog`:弹窗选择模式,点击表单项后弹出选择弹窗 3. 当选项较多时,建议使用弹窗选择模式(`displayType="dialog"`) 4. 在多选模式下,表单值为选中项的 value 值组成的数组 5. 在单选模式下,表单值为选中项的 value 值 -6. 组件会自动根据表单值显示已选项的文本,多个选项时用逗号分隔 \ No newline at end of file +6. 组件会自动根据表单值显示已选项的文本,多个选项时用逗号分隔 +7. 使用单选模式时,确保传入的表单值如果为单个值而非数组,否则可能导致显示异常 \ No newline at end of file diff --git a/_cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md b/_cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md index 335fe08..7d0cb73 100644 --- a/_cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md +++ b/_cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md @@ -2,7 +2,7 @@ ## 功能说明 -CChinaArea 是一个中国地区选择器组件,用于在表单中选择省市区地址。组件内置了完整的中国行政区划数据,支持省、市、区三级联动选择,并且可以设置默认值和只读模式。 +CChinaArea 是一个中国地区选择器组件,用于在表单中选择省市区地址。组件内置了完整的中国行政区划数据,支持省、市、区(街道)多级联动选择,并且可以通过地理定位自动获取当前位置。 ## 引用方式 @@ -13,11 +13,11 @@ ## 组件参数 - `itemRes` (Object,必填):表单数据资源对象,表单组件内部机制专用 -- `level` (Number,可选):选择层级,可选值为 1(省)、2(省市)、3(省市区),默认为 3 +- `autoGeo` (Boolean,可选):是否自动通过地理定位获取省市区,默认为 false +- `level` (Number,可选):联动级别,默认为 3 + - `3`:省市区三级联动 + - `4`:省市区街道四级联动 - `placeholder` (String,可选):选择器占位提示文本 -- `readOnly` (Boolean,可选):只读模式,默认为 false -- `separator` (String,可选):地址文本分隔符,默认为空格 -- `defaultCode` (String,可选):默认选中的区域编码 ## 使用示例 @@ -47,12 +47,7 @@ data() { return { form: { - address: { - province: '', - city: '', - district: '', - code: '' - } + address: [] } }; } @@ -60,30 +55,34 @@ </script> ``` -### 自定义配置 +### 四级联动 ```html -<CFormItem name="region" label="配送区域"> +<CFormItem name="region" label="详细区域"> <CChinaArea - :level="2" - separator="/" - defaultCode="330100" - placeholder="请选择配送区域" + :level="4" + placeholder="请选择所在区域" + /> +</CFormItem> +``` + +### 自动获取地理位置 + +```html +<CFormItem name="location" label="当前位置"> + <CChinaArea + :autoGeo="true" + placeholder="正在获取位置..." /> </CFormItem> ``` ## 注意事项 -1. 组件返回的数据格式为对象,包含以下字段: - - `province`:省份名称 - - `city`:城市名称 - - `district`:区县名称(仅在 level=3 时有值) - - `code`:选中区域的行政区划代码 -2. 可以通过 `defaultCode` 设置默认选中的区域 -3. `level` 参数决定了选择的层级: - - 1:仅选择省份 - - 2:选择省份和城市 - - 3:选择省份、城市和区县 -4. 组件会根据当前选择自动联动更新下级选项 -5. 在只读模式下,地址信息将以文本形式显示,使用 `separator` 分隔 \ No newline at end of file +1. 组件返回的数据格式为数组,包含选中的地区名称: + - 三级联动:`['浙江省', '杭州市', '西湖区']` + - 四级联动:`['浙江省', '杭州市', '西湖区', '文三路街道']` +2. 当启用地理定位功能(`autoGeo=true`)时,组件会尝试获取当前位置的省市区信息 +3. 组件会根据当前选择自动联动更新下级选项 +4. 地区数据源来自内置的中国行政区划数据 +5. 位置信息在表单中显示时,地址各级之间会以 ` / ` 分隔 \ No newline at end of file diff --git a/_cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md b/_cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md index 2e6991d..1fcedb4 100644 --- a/_cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md +++ b/_cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md @@ -13,11 +13,17 @@ ## 组件参数 - `itemRes` (Object,必填):表单数据资源对象,表单组件内部机制专用 -- `mode` (String,可选):日期时间选择器模式,可选值有 date、dateTime、dateRange,默认为 date +- `mode` (String,可选):日期时间选择器模式,默认为 date + - `date`:选择单个日期,使用系统日期选择器 + - `dateTime`:选择日期和时间,使用自定义的日期时间选择器 + - `dateRange`:选择日期范围,使用自定义的日期范围选择器 - `placeholder` (String,可选):输入框占位提示文本 - `limitStart` (String,可选):可选日期的开始日期,格式为 YYYY-MM-DD - `limitEnd` (String,可选):可选日期的结束日期,格式为 YYYY-MM-DD -- `fields` (String,可选):日期选择粒度,可选值有 year、month、day,默认为 day +- `fields` (String,可选):日期选择粒度,默认为 day + - `year`:只选择年 + - `month`:选择到月 + - `day`:选择到日 - `readOnly` (Boolean,可选):只读模式,默认为 false - `allowClear` (Boolean,可选):是否允许清除已选值,默认为 false @@ -102,10 +108,7 @@ ## 注意事项 -1. 不同的 `mode` 值对应不同的日期选择交互方式: - - `date`:选择单个日期,使用系统日期选择器 - - `dateTime`:选择日期和时间,使用自定义的日期时间选择器 - - `dateRange`:选择日期范围,使用自定义的日期范围选择器 +1. 不同的 `mode` 值对应不同的日期选择交互方式 2. 当设置 `allowClear` 为 true 时,已选择日期后会显示清除图标,点击可清除已选值 3. `limitStart` 和 `limitEnd` 参数仅在 `mode` 为 date 时生效 4. 组件默认的可选日期范围是当前年份的前后 30 年 diff --git a/_cursor.ai/forms.doc/form.doc/CForm.doc.md b/_cursor.ai/forms.doc/form.doc/CForm.doc.md index 3280a71..5869514 100644 --- a/_cursor.ai/forms.doc/form.doc/CForm.doc.md +++ b/_cursor.ai/forms.doc/form.doc/CForm.doc.md @@ -13,16 +13,18 @@ ## 组件参数 - `formData` (Object,必填):表单数据对象,用于存储表单各项的值 -- `autoScrollToError` (String,可选):是否自动滚动到错误位置,可选值有 on、off,默认为 off +- `autoScrollToError` (String,可选):是否自动滚动到错误位置,默认为 off + - `on`:启用自动滚动 + - `off`:禁用自动滚动 - `onChange` (Function,可选):表单项变化的回调函数,参数为变化的表单项数据 - `onFinish` (Function,可选):表单完成的回调函数,仅在提交且通过表单验证后调用,参数为整个表单数据 ## 实例方法 - `$submit`:手动触发表单提交,会执行表单验证 -- `$preVerify`:提前验证指定的表单项,参数为表单项名称数组和回调函数 -- `$setErrors`:直接设置表单错误,参数为错误对象,格式为 `{字段名: 错误信息}` -- `$setScrollTop`:设置滚动位置,参数为滚动的 top 值 +- `$preVerify(keys, callback)`:提前验证指定的表单项,参数为表单项名称数组和回调函数 +- `$setErrors(errors)`:直接设置表单错误,参数为错误对象,格式为 `{字段名: 错误信息}` +- `$setScrollTop(top)`:设置滚动位置,参数为滚动的 top 值 ## 使用示例 @@ -31,7 +33,7 @@ ```html <template> <CForm - :form="form" + :formData="form" :onFinish="handleFinish" > <CFormItem name="username" label="用户名" required> @@ -78,7 +80,7 @@ ```html <CForm - :form="form" + :formData="form" autoScrollToError="on" :onFinish="handleFinish" > @@ -98,11 +100,11 @@ ```html <CForm ref="form" - :form="form" + :formData="form" :onFinish="handleFinish" > <!-- 表单项 --> - <button @tap="submitForm">提交</button> + <button @tap="evt => submitForm()">提交</button> </CForm> ``` diff --git a/_cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md b/_cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md index 4af8934..5d60ec9 100644 --- a/_cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md +++ b/_cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md @@ -2,7 +2,7 @@ ## 功能说明 -CImagePicker 是一个图片选择器组件,用于在表单中上传和管理图片。组件支持单张和多张图片上传,支持预览、删除等功能,并且可以限制上传图片的数量和大小。 +CImagePicker 是一个图片选择器组件,用于在表单中上传和管理图片。组件支持单张和多张图片上传,支持预览、删除等功能,并且可以限制上传图片的数量和来源。 ## 引用方式 @@ -13,12 +13,16 @@ ## 组件参数 - `itemRes` (Object,必填):表单数据资源对象,表单组件内部机制专用 -- `maxCount` (Number,可选):最大上传图片数量,默认为 9 -- `maxSize` (Number,可选):单张图片最大大小,单位为 MB,默认为 5 -- `compress` (Boolean,可选):是否压缩图片,默认为 true -- `quality` (Number,可选):图片压缩质量,取值范围 0-1,默认为 0.8 -- `placeholder` (String,可选):选择器占位提示文本 -- `readOnly` (Boolean,可选):只读模式,默认为 false +- `count` (Number,可选):最大图片张数,默认为 1 +- `sourceType` (Array,可选):图片来源,默认为 ['album', 'camera'] + - `album`:从相册选择 + - `camera`:使用相机 +- `params` (Object,可选):上传图片的附加参数,默认为空对象 +- `needThumb` (Boolean,可选):是否开启缩略图,默认为 false + +## 实例方法 + +- `$uploadImage(callback)`:上传图片到服务器,参数为回调函数,回调函数接收两个参数:状态和结果 ## 使用示例 @@ -29,7 +33,7 @@ <CForm :form="form"> <CFormItem name="photos" label="图片上传"> <CImagePicker - :maxCount="3" + :count="3" placeholder="请上传图片" /> </CFormItem> @@ -57,24 +61,58 @@ </script> ``` -### 自定义配置 +### 限制图片来源 ```html -<CFormItem name="certificate" label="证书照片"> +<CFormItem name="idCard" label="身份证照片"> <CImagePicker - :maxCount="1" - :maxSize="2" - :compress="true" - :quality="0.6" - placeholder="请上传证书照片" + :count="1" + :sourceType="['camera']" + placeholder="请拍摄身份证照片" /> </CFormItem> ``` +### 使用上传方法 + +```html +<template> + <CForm :form="form"> + <CFormItem name="certificate" label="证书照片"> + <CImagePicker + ref="imagePicker" + :count="1" + :params="{type: 'certificate'}" + :needThumb="true" + placeholder="请上传证书照片" + /> + </CFormItem> + <button @tap="evt => handleUpload()">上传图片</button> + </CForm> +</template> + +<script> +export default { + // ... + methods: { + handleUpload() { + this.$refs.imagePicker.$uploadImage((state, result) => { + if (state === 'success') { + console.log('上传成功', result); + } else { + console.error('上传失败', result); + } + }); + } + } +}; +</script> +``` + ## 注意事项 -1. 组件会自动处理图片上传,支持压缩和预览功能 -2. 上传的图片会被转换为 base64 格式存储在表单数据中 -3. 当设置 `maxCount` 为 1 时,组件会以单图模式运行 -4. 建议根据实际需求设置合适的 `maxSize` 和压缩参数 -5. 在只读模式下,只能查看已上传的图片,无法进行上传和删除操作 \ No newline at end of file +1. 组件会自动过滤不支持的图片格式,只允许上传 gif、png、jpg、jpeg 格式的图片 +2. 图片大小超过 1MB 的会自动进行压缩处理 +3. 通过 `$uploadImage` 方法可以将图片上传到服务器,上传成功后会自动更新表单值 +4. 如果设置了 `needThumb` 为 true,上传时会同时生成缩略图 +5. 组件内部集成了图片预览功能,点击已上传的图片可以放大查看 \ No newline at end of file diff --git a/_cursor.ai/forms.doc/input.doc/CInput.doc.md b/_cursor.ai/forms.doc/input.doc/CInput.doc.md index a6c1dae..e762d3c 100644 --- a/_cursor.ai/forms.doc/input.doc/CInput.doc.md +++ b/_cursor.ai/forms.doc/input.doc/CInput.doc.md @@ -13,7 +13,13 @@ ## 组件参数 - `itemRes` (Object,必填):表单数据资源对象,表单组件内部机制专用 -- `type` (String,可选):输入框类型,可选值有 text、number、password、phone、idcard、digit +- `type` (String,可选):输入框类型 + - `text`:文本输入 + - `number`:数字输入 + - `password`:密码输入 + - `phone`:手机号输入 + - `idcard`:身份证号输入 + - `digit`:带小数点的数字输入 - `placeholder` (String,可选):输入框占位提示文本 - `unit` (String,可选):输入框单位,设置后会在输入框右侧显示单位文本 - `readOnly` (Boolean,可选):只读模式,默认为 false diff --git a/_cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md b/_cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md index 3ef361c..fa9e54f 100644 --- a/_cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md +++ b/_cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md @@ -16,7 +16,9 @@ - `placeholder` (String,可选):输入框占位提示文本 - `range` (Array,可选):取值范围,格式为 [最小值, 最大值],默认为 [0, 100] - `step` (Number,可选):步长,即每次点击增减按钮改变的数值,默认为 1 -- `correct` (String,可选):奇偶修正模式,可选值有 odd(奇数)、even(偶数),默认为空 +- `correct` (String,可选):奇偶修正模式,默认为空 + - `odd`:只允许选择奇数 + - `even`:只允许选择偶数 - `unit` (String,可选):数值单位,显示在数字输入框右侧 ## 使用示例 diff --git a/_cursor.ai/forms.doc/select.doc/CSelect.doc.md b/_cursor.ai/forms.doc/select.doc/CSelect.doc.md index 42ab6cf..8a44b3e 100644 --- a/_cursor.ai/forms.doc/select.doc/CSelect.doc.md +++ b/_cursor.ai/forms.doc/select.doc/CSelect.doc.md @@ -16,7 +16,9 @@ - `options` (Array,必填):选择菜单选项数组,每个选项应包含 name 和 value/id 属性 - `placeholder` (String,可选):输入框占位提示文本 - `readOnly` (Boolean,可选):只读模式,默认为 false -- `selectByPage` (String,可选):开启选择菜单跳转选择页面模式,值为 'on' +- `selectByPage` (String,可选):选择菜单模式 + - 不设置:下拉选择模式 + - `on`:跳转页面选择模式 - `onOpenSelectorPage` (Function,可选):页面跳转模式下,发起选择的回调函数 ## 使用示例 diff --git a/_cursor.ai/layout.doc/alert.doc/CAlert.doc.md b/_cursor.ai/layout.doc/alert.doc/CAlert.doc.md new file mode 100644 index 0000000..0e83f04 --- /dev/null +++ b/_cursor.ai/layout.doc/alert.doc/CAlert.doc.md @@ -0,0 +1,129 @@ +# CAlert 弹窗 + +## 功能说明 + +基于 Taro UI 的模态框(AtModal)封装的alert弹窗组件,支持Alert(提示)和Confirm(确认)两种模式。 + +## 引用方式 + +```js +import CAlert from '@components/layout/alert'; +``` + +## 组件参数 + +- `onConfirm` (Function,可选):确认模式下,点击确认按钮的回调函数 + +## 实例方法 + +- `$alert(option, callback)`:显示提示弹窗 + - `option` (String|Object):弹窗配置,可以是字符串或对象 + - 当为字符串时,直接作为弹窗内容 + - 当为对象时,支持以下属性: + - `title` (String):弹窗标题 + - `content` (String):弹窗主要内容 + - `contents` (Array):弹窗内容数组,会逐行显示 + - `callback` (Function):点击"知道了"按钮后的回调函数 + +- `$confirm(option, callback)`:显示确认弹窗 + - `option` (String|Object):弹窗配置,参数同`$alert` + - `callback` (Function):点击"取消"或"确定"按钮后的回调函数 + +## 使用示例 + +### 基础提示框用法 + +```html +<template> + <view class="page"> + <button @tap="evt => showAlert()">显示提示</button> + <CAlert ref="alert" /> + </view> +</template> + +<script> +import CAlert from '@components/layout/alert'; + +export default { + components: { + CAlert + }, + methods: { + showAlert() { + this.$refs.alert.$alert('这是一条提示信息'); + } + } +} +</script> +``` + +### 带标题的提示框 + +```html +<template> + <view class="page"> + <button @tap="evt => showAlertWithTitle()">显示带标题的提示</button> + <CAlert ref="alert" /> + </view> +</template> + +<script> +import CAlert from '@components/layout/alert'; + +export default { + components: { + CAlert + }, + methods: { + showAlertWithTitle() { + this.$refs.alert.$alert({ + title: '提示', + content: '这是一条提示信息' + }); + } + } +} +</script> +``` + +### 确认框用法 + +```html +<template> + <view class="page"> + <button @tap="evt => showConfirm()">显示确认框</button> + <CAlert + ref="alert" + :onConfirm="handleConfirm" + /> + </view> +</template> + +<script> +import CAlert from '@components/layout/alert'; + +export default { + components: { + CAlert + }, + methods: { + showConfirm() { + this.$refs.alert.$confirm('是否确认此操作?'); + }, + handleConfirm() { + console.log('用户点击了确认'); + // 执行确认操作 + } + } +} +</script> +``` + +## 注意事项 + +1. 弹窗内容支持普通文本,不支持HTML结构 +2. 使用确认框时,需要通过props传入`onConfirm`回调函数来响应用户的确认操作 +3. 组件内部存在一个拼写错误,确认模式的变量名为`'comfirm'`而非`'confirm'`,但使用时仍应使用`$confirm`方法 +4. 组件基于Taro UI的模态框,在样式上与Taro UI保持一致 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/anchor.doc/CAnchor.doc.md b/_cursor.ai/layout.doc/anchor.doc/CAnchor.doc.md new file mode 100644 index 0000000..8c287b4 --- /dev/null +++ b/_cursor.ai/layout.doc/anchor.doc/CAnchor.doc.md @@ -0,0 +1,150 @@ +# CAnchor 应用内超连接 + +## 功能说明 + +应用内超连接组件,用于在应用内部页面之间进行跳转,封装了 Taro 的页面跳转方法,支持多种跳转模式。组件可包裹任何内容作为点击触发区域。 + +## 引用方式 + +```js +import CAnchor from '@components/layout/anchor'; +``` + +## 组件参数 + +- `href` (String,必选):跳转目标路径,可以是页面路径,也可以是特殊值 'back' +- `rel` (String,可选):跳转方式,默认值为 'navigate' + - `navigate`:保留当前页面,跳转到应用内的某个页面(对应 Taro.navigateTo) + - `redirect`:关闭当前页面,跳转到应用内的某个页面(对应 Taro.redirectTo) + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CAnchor href="/pages/order/detail?id=12345"> + <view class="link-text">查看订单详情</view> + </CAnchor> + </view> +</template> + +<script> +import CAnchor from '@components/layout/anchor'; + +export default { + components: { + CAnchor + } +} +</script> +``` + +### 包裹图片或按钮 + +```html +<template> + <view class="page"> + <!-- 包裹图片 --> + <CAnchor href="/pages/product/detail?id=6789"> + <image + src="/static/images/product.jpg" + mode="aspectFill" + class="product-image" + /> + </CAnchor> + + <!-- 包裹按钮 --> + <CAnchor href="/pages/cart/index"> + <AtButton type="primary"> + 去购物车结算 + </AtButton> + </CAnchor> + </view> +</template> + +<script> +import CAnchor from '@components/layout/anchor'; +import { AtButton } from 'taro-ui-vue'; + +export default { + components: { + CAnchor, + AtButton + } +} +</script> +``` + +### 返回上一页 + +```html +<template> + <view class="page"> + <CAnchor href="back"> + <view class="back-link"> + <AtIcon value="chevron-left" size="16" /> + <text>返回上一页</text> + </view> + </CAnchor> + </view> +</template> + +<script> +import CAnchor from '@components/layout/anchor'; +import { AtIcon } from 'taro-ui-vue'; + +export default { + components: { + CAnchor, + AtIcon + } +} +</script> + +<style> +.back-link { + display: flex; + align-items: center; +} +</style> +``` + +### 重定向模式 + +```html +<template> + <view class="page"> + <CAnchor + href="/pages/login/index" + rel="redirect" + > + <view class="redirect-link"> + 请先登录后再操作 + </view> + </CAnchor> + </view> +</template> + +<script> +import CAnchor from '@components/layout/anchor'; + +export default { + components: { + CAnchor + } +} +</script> +``` + +## 注意事项 + +1. 组件通过默认插槽提供内容,可以包裹任何元素作为点击区域 +2. href 参数必须提供有效的页面路径,否则点击不会触发任何操作 +3. 当 href 设置为 'back' 时,等同于调用 Taro.navigateBack() 方法 +4. 页面路径应该以 '/' 开头,例如 '/pages/index/index' +5. 可以在路径中添加查询参数,例如 '/pages/detail?id=123' +6. 重定向模式会关闭当前页面,无法通过返回按钮回到当前页面 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/card.doc/CCard.doc.md b/_cursor.ai/layout.doc/card.doc/CCard.doc.md new file mode 100644 index 0000000..bc58fbe --- /dev/null +++ b/_cursor.ai/layout.doc/card.doc/CCard.doc.md @@ -0,0 +1,134 @@ +# CCard 列表卡片 + +## 功能说明 + +一个用于显示信息的卡片组件,结构包括主体、标题、内容、操作区四个部分。适用于需要将信息以卡片形式展示的场景,如列表项、信息展示等。 + +## 引用方式 + +```js +import { CCard, CCardTitle, CCardContent, CCardAction } from '@components/layout/card'; +``` + +## 组件结构 + +卡片组件由以下四个组件组成: + +1. `CCard` - 卡片主体,作为其他组件的容器 +2. `CCardTitle` - 卡片标题,显示标题内容和可选的额外操作 +3. `CCardContent` - 卡片内容区域 +4. `CCardAction` - 卡片底部操作区域,支持横向滚动 + +## 组件参数 + +### CCard + +无特定参数,通过插槽嵌套其他卡片子组件使用 + +### CCardTitle + +- `title` (String,可选):标题文本,默认值为 '列表卡片标题' +- `onTitleClick` (Function,可选):标题点击事件回调函数 + +### CCardContent + +无特定参数,用于包裹卡片主要内容 + +### CCardAction + +无特定参数,用于包裹卡片底部的操作按钮 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CCard> + <CCardTitle title="用户信息" /> + <CCardContent> + <view class="user-info"> + <view class="info-item">姓名:张三</view> + <view class="info-item">电话:13800138000</view> + <view class="info-item">地址:广州市天河区</view> + </view> + </CCardContent> + </CCard> + </view> +</template> + +<script> +import { CCard, CCardTitle, CCardContent } from '@components/layout/card'; + +export default { + components: { + CCard, + CCardTitle, + CCardContent + } +} +</script> +``` + +### 完整卡片示例(带标题、内容和操作) + +```html +<template> + <view class="page"> + <CCard> + <CCardTitle title="订单信息"> + <AtTag size="small" type="primary">新订单</AtTag> + </CCardTitle> + + <CCardContent> + <view class="order-info"> + <view class="order-item">订单号:2023042501</view> + <view class="order-item">客户:李四</view> + <view class="order-item">金额:¥125.00</view> + <view class="order-item">状态:待处理</view> + </view> + </CCardContent> + + <CCardAction> + <AtButton size="small" type="secondary" @tap="evt => handleCancel()">取消</AtButton> + <AtButton size="small" type="primary" @tap="evt => handleConfirm()">确认</AtButton> + </CCardAction> + </CCard> + </view> +</template> + +<script> +import { CCard, CCardTitle, CCardContent, CCardAction } from '@components/layout/card'; +import { AtButton, AtTag } from 'taro-ui-vue'; + +export default { + components: { + CCard, + CCardTitle, + CCardContent, + CCardAction, + AtButton, + AtTag + }, + methods: { + handleCancel() { + console.log('取消订单'); + }, + handleConfirm() { + console.log('确认订单'); + } + } +} +</script> +``` + +## 注意事项 + +1. 卡片组件需要按照 `CCard > CCardTitle/CCardContent/CCardAction` 的层级结构使用 +2. `CCardTitle` 设置了底部边框,`CCardAction` 设置了顶部边框,都使用了 `m-border-strong-bottom/top` 样式类 +3. `CCardTitle` 组件支持通过默认插槽在标题右侧添加额外内容(如标签、按钮等) +4. `CCardAction` 组件使用 `scroll-view` 实现,当操作按钮过多时支持横向滚动 +5. 卡片内容可以根据实际需要自由组织,`CCardContent` 不对内容做特定限制 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/description.doc/CDescription.doc.md b/_cursor.ai/layout.doc/description.doc/CDescription.doc.md new file mode 100644 index 0000000..8a62006 --- /dev/null +++ b/_cursor.ai/layout.doc/description.doc/CDescription.doc.md @@ -0,0 +1,149 @@ +# CDescription 描述列表项 + +## 功能说明 + +描述列表项组件,用于展示标签和内容的键值对形式信息,常用于详情页面的信息展示,支持不同的标签对齐方式和样式选项。 + +## 引用方式 + +```js +import CDescription from '@components/layout/description'; +``` + +## 组件参数 + +- `label` (String,必选):左侧标签文本 +- `labelAlign` (String,可选):标签对齐方式,默认值为 'left' + - `left`:定宽左对齐 + - `right`:定宽右对齐 + - `none`:无固定宽度 +- `enlarged` (Boolean,可选):是否加大显示区域,默认值为 false +- `hasBorder` (Boolean,可选):是否显示底部边框,默认值为 false + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CDescription label="名称"> + <text>张三</text> + </CDescription> + <CDescription label="电话"> + <text>13800138000</text> + </CDescription> + <CDescription label="地址"> + <text>广州市天河区某某路某某号</text> + </CDescription> + </view> +</template> + +<script> +import CDescription from '@components/layout/description'; + +export default { + components: { + CDescription + } +} +</script> +``` + +### 不同对齐方式和样式 + +```html +<template> + <view class="page"> + <!-- 标签右对齐 --> + <CDescription + label="订单编号" + labelAlign="right" + > + <text>OR202304250001</text> + </CDescription> + + <!-- 带底部边框 --> + <CDescription + label="金额" + hasBorder + > + <text class="m-text-primary">¥198.00</text> + </CDescription> + + <!-- 加大显示区域 --> + <CDescription + label="备注" + enlarged + > + <text>此订单为加急订单,请优先处理,并且联系物流安排配送</text> + </CDescription> + + <!-- 无宽度限制的标签 --> + <CDescription + label="客户提供的自定义信息要求" + labelAlign="none" + > + <text>产品需要绿色包装</text> + </CDescription> + </view> +</template> + +<script> +import CDescription from '@components/layout/description'; + +export default { + components: { + CDescription + } +} +</script> +``` + +### 在卡片内使用 + +```html +<template> + <view class="page"> + <CCard> + <CCardTitle title="订单信息" /> + <CCardContent> + <CDescription label="订单编号"> + <text>OR202304250001</text> + </CDescription> + <CDescription label="下单时间"> + <text>2023-04-25 14:30:25</text> + </CDescription> + <CDescription label="订单状态"> + <text class="m-text-warning">待处理</text> + </CDescription> + </CCardContent> + </CCard> + </view> +</template> + +<script> +import CDescription from '@components/layout/description'; +import { CCard, CCardTitle, CCardContent } from '@components/layout/card'; + +export default { + components: { + CDescription, + CCard, + CCardTitle, + CCardContent + } +} +</script> +``` + +## 注意事项 + +1. 组件主要用于键值对形式的信息展示,内容部分通过默认插槽提供 +2. 标签宽度在labelAlign为left和right时是固定的,建议标签文字不要过长 +3. 当标签文字较长时,可以使用labelAlign="none"取消固定宽度限制 +4. 内容部分可以插入任何元素,不仅限于文本 +5. 组件适合连续使用来展示多个信息项,形成一个完整的描述列表 +6. 当设置hasBorder为true时,会在项目底部显示一条分隔线 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/drawer.doc/CDrawer.doc.md b/_cursor.ai/layout.doc/drawer.doc/CDrawer.doc.md new file mode 100644 index 0000000..1d8df88 --- /dev/null +++ b/_cursor.ai/layout.doc/drawer.doc/CDrawer.doc.md @@ -0,0 +1,199 @@ +# CDrawer 抽屉 + +## 功能说明 + +抽屉组件,可从页面不同方向滑出,通常用于展示临时的操作面板或内容区域,支持从顶部、左侧、右侧滑入。点击遮罩层可关闭抽屉。 + +## 引用方式 + +```js +import CDrawer from '@components/layout/drawer'; +``` + +## 组件参数 + +- `show` (Boolean,必选):控制抽屉显示或隐藏,默认值为 false +- `direction` (String,可选):抽屉滑出方向,默认值为 'right' + - `top`:从顶部滑出 + - `left`:从左侧滑出 + - `right`:从右侧滑出 +- `onClose` (Function,必选):抽屉关闭时的回调函数,通常用于设置 show 为 false + +## 使用示例 + +### 基础用法 - 从右侧滑出 + +```html +<template> + <view class="page"> + <button @tap="evt => openDrawer()">打开抽屉</button> + + <CDrawer + :show="drawerVisible" + :onClose="closeDrawer" + > + <view class="drawer-content"> + <view class="drawer-title">筛选条件</view> + <view class="drawer-item">选项1</view> + <view class="drawer-item">选项2</view> + <view class="drawer-item">选项3</view> + <button @tap="evt => applyFilter()">确定</button> + </view> + </CDrawer> + </view> +</template> + +<script> +import CDrawer from '@components/layout/drawer'; + +export default { + components: { + CDrawer + }, + data() { + return { + drawerVisible: false + } + }, + methods: { + openDrawer() { + this.drawerVisible = true; + }, + closeDrawer() { + this.drawerVisible = false; + }, + applyFilter() { + // 应用筛选逻辑 + this.closeDrawer(); + } + } +} +</script> + +<style> +.drawer-content { + padding: 20px; +} +.drawer-title { + font-size: 18px; + font-weight: bold; + margin-bottom: 20px; +} +.drawer-item { + padding: 10px 0; +} +</style> +``` + +### 从左侧滑出的抽屉 + +```html +<template> + <view class="page"> + <button @tap="evt => openDrawer()">打开导航菜单</button> + + <CDrawer + :show="drawerVisible" + direction="left" + :onClose="closeDrawer" + > + <view class="menu-content"> + <view class="menu-item" @tap="evt => navigateTo('/pages/home/index')">首页</view> + <view class="menu-item" @tap="evt => navigateTo('/pages/order/list')">订单管理</view> + <view class="menu-item" @tap="evt => navigateTo('/pages/user/index')">用户中心</view> + <view class="menu-item" @tap="evt => navigateTo('/pages/setting/index')">系统设置</view> + </view> + </CDrawer> + </view> +</template> + +<script> +import CDrawer from '@components/layout/drawer'; +import Taro from '@tarojs/taro'; + +export default { + components: { + CDrawer + }, + data() { + return { + drawerVisible: false + } + }, + methods: { + openDrawer() { + this.drawerVisible = true; + }, + closeDrawer() { + this.drawerVisible = false; + }, + navigateTo(url) { + this.closeDrawer(); + Taro.navigateTo({ url }); + } + } +} +</script> +``` + +### 从顶部滑出的抽屉 + +```html +<template> + <view class="page"> + <button @tap="evt => openDrawer()">显示通知</button> + + <CDrawer + :show="drawerVisible" + direction="top" + :onClose="closeDrawer" + > + <view class="notification"> + <view class="notification-title">系统通知</view> + <view class="notification-content"> + 您有3条未读消息,请及时查看处理。 + </view> + <button @tap="evt => viewMessages()">查看详情</button> + </view> + </CDrawer> + </view> +</template> + +<script> +import CDrawer from '@components/layout/drawer'; + +export default { + components: { + CDrawer + }, + data() { + return { + drawerVisible: false + } + }, + methods: { + openDrawer() { + this.drawerVisible = true; + }, + closeDrawer() { + this.drawerVisible = false; + }, + viewMessages() { + this.closeDrawer(); + // 跳转到消息页面 + } + } +} +</script> +``` + +## 注意事项 + +1. 使用抽屉组件时,必须提供 onClose 回调函数,否则点击遮罩层无法关闭抽屉 +2. 抽屉内容通过默认插槽提供,可以根据需要自定义内容和样式 +3. 抽屉组件默认占满整个屏幕,内容区域的样式需要自行定义 +4. 抽屉打开时会添加遮罩层,防止用户与页面其他部分交互 +5. 抽屉内容区宽度固定,左右抽屉为屏幕宽度的80%,顶部抽屉为屏幕高度的40% +6. 当前版本的组件不支持从底部滑出的抽屉,如有需要可考虑扩展组件 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/h5Page.doc/CH5Page.doc.md b/_cursor.ai/layout.doc/h5Page.doc/CH5Page.doc.md new file mode 100644 index 0000000..00e2eb4 --- /dev/null +++ b/_cursor.ai/layout.doc/h5Page.doc/CH5Page.doc.md @@ -0,0 +1,175 @@ +# CH5Page H5页面布局 + +## 功能说明 + +H5页面布局组件集,用于构建标准的H5页面结构,包含页面容器、导航栏和内容区域三个部分,适用于需要遵循统一布局规范的H5页面。 + +## 引用方式 + +```js +import { CPage, CNavBar, CContent } from '@components/layout/h5Page'; +``` + +## 组件结构 + +H5页面布局组件由以下三个组件组成: + +1. `CPage` - 页面容器,作为整个页面的最外层容器 +2. `CNavBar` - 导航栏,显示页面标题和导航按钮 +3. `CContent` - 内容区域,显示页面主要内容,可选择是否支持滚动 + +## 组件参数 + +### CPage + +无特定参数,通过插槽嵌套其他组件使用 + +### CNavBar + +- `title` (String,必选):导航栏标题 +- `iconType` (String,可选):左侧图标类型,默认值为 'chevron-left' +- `onClickIcon` (Function,可选):左侧图标点击事件回调函数,返回true时将执行默认的返回上一页行为 +- `dropNav` (Array,可选):右侧下拉菜单配置,数组中的每项为对象,包含以下属性: + - `title` (String):菜单项标题 + - `url` (String):点击菜单项跳转的页面路径 + +### CContent + +- `scroll` (String,可选):是否开启滚动功能,默认值为 'off' + - `off`:不开启滚动 + - `on`:开启滚动 + +## 实例方法 + +### CContent + +- `$scrollTop(top)`:滚动到指定位置 + - `top` (Number):要滚动到的位置,单位为像素,默认为0 + +## 使用示例 + +### 基础用法 + +```html +<template> + <CPage> + <CNavBar title="页面标题" /> + <CContent> + <view class="page-content"> + <!-- 页面内容 --> + <view class="content-item">内容项1</view> + <view class="content-item">内容项2</view> + <view class="content-item">内容项3</view> + </view> + </CContent> + </CPage> +</template> + +<script> +import { CPage, CNavBar, CContent } from '@components/layout/h5Page'; + +export default { + components: { + CPage, + CNavBar, + CContent + } +} +</script> +``` + +### 带滚动的内容和自定义返回事件 + +```html +<template> + <CPage> + <CNavBar + title="详情页面" + :onClickIcon="handleBackClick" + /> + <CContent + scroll="on" + ref="content" + > + <view class="page-content"> + <!-- 长内容需要滚动 --> + <view class="content-section" v-for="i in 20" :key="i"> + 内容区域 {{i}} + </view> + </view> + <view class="scroll-top-btn" @tap="evt => scrollToTop()">回到顶部</view> + </CContent> + </CPage> +</template> + +<script> +import { CPage, CNavBar, CContent } from '@components/layout/h5Page'; + +export default { + components: { + CPage, + CNavBar, + CContent + }, + methods: { + handleBackClick() { + // 在返回前执行一些操作 + console.log('用户点击了返回按钮'); + // 返回true继续执行默认的返回行为 + return true; + }, + scrollToTop() { + this.$refs.content.$scrollTop(0); + } + } +} +</script> +``` + +### 带下拉菜单的导航栏 + +```html +<template> + <CPage> + <CNavBar + title="订单列表" + :dropNav="navMenuItems" + /> + <CContent> + <!-- 页面内容 --> + </CContent> + </CPage> +</template> + +<script> +import { CPage, CNavBar, CContent } from '@components/layout/h5Page'; + +export default { + components: { + CPage, + CNavBar, + CContent + }, + data() { + return { + navMenuItems: [ + { title: '新增订单', url: '/pages/order/create' }, + { title: '订单筛选', url: '/pages/order/filter' }, + { title: '导出订单', url: '/pages/order/export' } + ] + } + } +} +</script> +``` + +## 注意事项 + +1. H5页面组件需要按照 `CPage > CNavBar + CContent` 的层级结构使用 +2. 当使用下拉菜单时,点击页面其他区域会自动关闭菜单 +3. CNavBar 的下拉菜单项需要设置 url 才能正常跳转 +4. CContent 设置 scroll="on" 后会自动处理内容区域的滚动,无需额外设置样式 +5. 当点击CNavBar的返回图标时,如果没有设置 onClickIcon 回调,将默认执行 Taro.navigateBack() +6. 组件会监听页面点击和触摸事件来处理下拉菜单的关闭逻辑 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/homeNav.doc/CHomeNav.doc.md b/_cursor.ai/layout.doc/homeNav.doc/CHomeNav.doc.md new file mode 100644 index 0000000..f8b1e18 --- /dev/null +++ b/_cursor.ai/layout.doc/homeNav.doc/CHomeNav.doc.md @@ -0,0 +1,127 @@ +# CHomeNav 首页导航 + +## 功能说明 + +用于首页显示导航菜单项的组件,支持两列或三列排版,可以显示分组标题。搭配CHomeItem子组件使用,用于展示一组功能入口。 + +## 引用方式 + +```js +import { CHomeNav, CHomeItem } from '@components/layout/homeNav'; +``` + +## 组件结构 + +首页导航组件由以下两个组件组成: + +1. `CHomeNav` - 导航容器,作为导航项的容器,控制排版 +2. `CHomeItem` - 导航项,代表一个功能入口 + +## 组件参数 + +### CHomeNav + +- `layout` (String,可选):排版方式,默认值为 'two' + - `two`:两列排版 + - `three`:三列排版 +- `title` (String,可选):分组标题,不设置则不显示 + +### CHomeItem + +- `label` (String,必选):导航项名称 +- `icon` (String,必选):导航项图标地址 +- `href` (String,必选):点击后跳转的链接地址 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CHomeNav title="常用功能"> + <CHomeItem + label="订单管理" + icon="/static/icons/order.png" + href="/pages/order/list" + /> + <CHomeItem + label="客户管理" + icon="/static/icons/customer.png" + href="/pages/customer/list" + /> + <CHomeItem + label="配送管理" + icon="/static/icons/delivery.png" + href="/pages/delivery/list" + /> + <CHomeItem + label="数据统计" + icon="/static/icons/statistics.png" + href="/pages/statistics/index" + /> + </CHomeNav> + </view> +</template> + +<script> +import { CHomeNav, CHomeItem } from '@components/layout/homeNav'; + +export default { + components: { + CHomeNav, + CHomeItem + } +} +</script> +``` + +### 三列排版 + +```html +<template> + <view class="page"> + <CHomeNav + title="快捷功能" + layout="three" + > + <CHomeItem + label="新增订单" + icon="/static/icons/add-order.png" + href="/pages/order/create" + /> + <CHomeItem + label="新增客户" + icon="/static/icons/add-customer.png" + href="/pages/customer/create" + /> + <CHomeItem + label="扫码收款" + icon="/static/icons/qr-payment.png" + href="/pages/payment/scan" + /> + </CHomeNav> + </view> +</template> + +<script> +import { CHomeNav, CHomeItem } from '@components/layout/homeNav'; + +export default { + components: { + CHomeNav, + CHomeItem + } +} +</script> +``` + +## 注意事项 + +1. CHomeNav 组件内部需要包含 CHomeItem 子组件,不支持其他内容 +2. CHomeItem 组件使用了 CAnchor 组件处理链接跳转,能够根据链接类型自动选择合适的跳转方式 +3. 根据设计,当使用两列布局时图标稍大,三列布局时图标稍小 +4. 传给 CHomeItem 的 icon 应该是一个完整的图片路径 +5. 不管使用哪种布局,都会自动调整导航项的间距,保证美观 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/navCustomBar.doc/CNavCustomBar.doc.md b/_cursor.ai/layout.doc/navCustomBar.doc/CNavCustomBar.doc.md new file mode 100644 index 0000000..1c46be4 --- /dev/null +++ b/_cursor.ai/layout.doc/navCustomBar.doc/CNavCustomBar.doc.md @@ -0,0 +1,80 @@ +# CNavCustomBar 自定义导航条 + +## 功能说明 + +自定义导航条组件,用于在页面顶部显示导航信息,可以自动适配不同设备的状态栏高度,支持显示返回按钮和标题。 + +## 引用方式 + +```js +import CNavCustomBar from '@components/layout/navCustomBar'; +``` + +## 组件参数 + +- `isNeedBackIcon` (Boolean,可选):是否需要显示返回按钮,默认值为 false +- `title` (String,可选):导航栏标题文本,默认值为空字符串 + +## 实例方法 + +- `$getStatusBarHeight()`:获取状态栏高度 +- `$getNavBarHeight()`:获取导航栏的总高度(包括状态栏) + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CNavCustomBar + title="页面标题" + :isNeedBackIcon="true" + /> + <view class="content" :style="{paddingTop: navBarHeight + 'px'}"> + <!-- 页面内容 --> + </view> + </view> +</template> + +<script> +import CNavCustomBar from '@components/layout/navCustomBar'; + +export default { + components: { + CNavCustomBar + }, + data() { + return { + navBarHeight: 0 + } + }, + mounted() { + this.navBarHeight = this.$refs.navBar.$getNavBarHeight(); + } +} +</script> +``` + +### 自定义图标 + +```html +<template> + <view class="page"> + <CNavCustomBar title="首页"> + <template #icon> + <image class="menu-icon" src="../assets/menu.png" /> + </template> + </CNavCustomBar> + </view> +</template> +``` + +## 注意事项 + +1. 组件使用固定定位显示在页面顶部,使用时需要给内容区域设置上内边距,值为导航栏高度,可通过 `$getNavBarHeight()` 方法获取 +2. 组件会自动适配不同设备的状态栏高度 +3. 当 `isNeedBackIcon` 为 true 时,点击返回按钮默认执行 Taro.navigateBack({ delta: 1 }) +4. 可以通过具名插槽 "icon" 自定义左侧图标,但仅在 isNeedBackIcon 为 false 时生效 + +<!-- 作者:chensi --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/numerical.doc/CNumerical.doc.md b/_cursor.ai/layout.doc/numerical.doc/CNumerical.doc.md new file mode 100644 index 0000000..02abe60 --- /dev/null +++ b/_cursor.ai/layout.doc/numerical.doc/CNumerical.doc.md @@ -0,0 +1,117 @@ +# CNumerical 数值显示 + +## 功能说明 + +用于以美观的方式展示一组数值及其标题的组件,支持小数点分隔显示,可以自适应不同数量的数值项,并支持点击事件。 + +## 引用方式 + +```js +import CNumerical from '@components/layout/numerical'; +``` + +## 组件参数 + +- `values` (Array,必选):数值集合,数组中的每项为对象,包含以下属性: + - `title` (String):数值项的标题 + - `value` (Number|String):要显示的数值 + - `textType` (String,可选):文本颜色类型,对应 CSS 类 'm-text-{textType}' + - `onClick` (Function,可选):点击数值项时的回调函数,接收当前项对象作为参数 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CNumerical :values="numericalValues" /> + </view> +</template> + +<script> +import CNumerical from '@components/layout/numerical'; + +export default { + components: { + CNumerical + }, + data() { + return { + numericalValues: [ + { + title: '今日销量', + value: 135 + }, + { + title: '月销量', + value: 3254 + }, + { + title: '总销量', + value: 58962 + } + ] + } + } +} +</script> +``` + +### 带颜色样式和点击事件 + +```html +<template> + <view class="page"> + <CNumerical :values="numericalValues" /> + </view> +</template> + +<script> +import CNumerical from '@components/layout/numerical'; + +export default { + components: { + CNumerical + }, + data() { + return { + numericalValues: [ + { + title: '收入(元)', + value: 9856.75, + textType: 'primary' + }, + { + title: '支出(元)', + value: 4328.50, + textType: 'warning' + }, + { + title: '详情', + value: 0, + onClick: (item) => { + console.log('用户点击了详情'); + // 跳转到详情页或显示详情弹窗 + } + } + ] + } + } +} +</script> +``` + +## 注意事项 + +1. 组件会根据传入的数值项数量自动调整布局: + - 当数量能被3整除时,每行显示3个项目 + - 当数量除以3余1时,最后4个项目每行显示2个 + - 当数量除以3余2时,最后2个项目独占一行 + - 当只有1个项目时,占满整行 +2. 数值会自动分离整数和小数部分,整数部分字体较大,小数部分字体较小 +3. 当整数部分超过4位数时,小数部分的字体会进一步缩小 +4. 具有onClick属性的项目会显示一个向右的箭头,并增加点击样式 +5. textType属性可以控制数值的颜色,通过添加类名 'm-text-{textType}' 实现 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/layout.doc/waiting.doc/CWaiting.doc.md b/_cursor.ai/layout.doc/waiting.doc/CWaiting.doc.md new file mode 100644 index 0000000..afc4d50 --- /dev/null +++ b/_cursor.ai/layout.doc/waiting.doc/CWaiting.doc.md @@ -0,0 +1,107 @@ +# CWaiting 等待中 + +## 功能说明 + +基于 Taro UI 的模态框和活动指示器封装的等待提示组件,用于显示加载中或处理中的状态,支持显示标题和副标题。 + +## 引用方式 + +```js +import CWaiting from '@components/layout/waiting'; +``` + +## 组件参数 + +- `isOpened` (Boolean,必选):是否显示等待框 +- `title` (String,可选):等待框标题 +- `sub` (String,可选):等待框副标题 +- `subType` (String,可选):副标题的颜色类型,默认值为 'warning' + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <button @tap="evt => showWaiting()">显示等待框</button> + <CWaiting + :isOpened="waitingOpened" + title="正在加载中..." + /> + </view> +</template> + +<script> +import CWaiting from '@components/layout/waiting'; + +export default { + components: { + CWaiting + }, + data() { + return { + waitingOpened: false + } + }, + methods: { + showWaiting() { + this.waitingOpened = true; + // 模拟3秒后完成加载 + setTimeout(() => { + this.waitingOpened = false; + }, 3000); + } + } +} +</script> +``` + +### 带副标题的等待框 + +```html +<template> + <view class="page"> + <button @tap="evt => showWaitingWithSub()">显示带副标题的等待框</button> + <CWaiting + :isOpened="waitingOpened" + title="正在处理中..." + sub="请勿关闭应用" + /> + </view> +</template> + +<script> +import CWaiting from '@components/layout/waiting'; + +export default { + components: { + CWaiting + }, + data() { + return { + waitingOpened: false + } + }, + methods: { + showWaitingWithSub() { + this.waitingOpened = true; + // 模拟处理过程 + setTimeout(() => { + this.waitingOpened = false; + }, 3000); + } + } +} +</script> +``` + +## 注意事项 + +1. 等待框无法通过点击遮罩层关闭,需要手动控制 `isOpened` 属性来关闭等待框 +2. 副标题默认使用警告色(橙色),通过 CSS 类 'm-text-warning' 实现 +3. 组件基于 Taro UI 的 AtModal 和 AtActivityIndicator,在样式上保持与 Taro UI 一致 +4. 建议在异步操作完成后,记得将 `isOpened` 设置为 false 关闭等待框 +5. 组件不包含超时处理,如需超时处理,需要在使用组件的页面自行实现 + +<!-- 作者:Tevin --> \ No newline at end of file diff --git a/_cursor.ai/link-rules.cmd b/_cursor.ai/link-rules.cmd new file mode 100644 index 0000000..8240806 --- /dev/null +++ b/_cursor.ai/link-rules.cmd @@ -0,0 +1,29 @@ +@echo off +set BASE_DIR=%~dp0..\..\..\ +cd %BASE_DIR% || ( + echo Failed to change directory. Please verify if the path is correct. + goto :end +) + +if not exist "src\components\_cursor.ai\rules" ( + echo Source directory does not exist: src\components\_cursor.ai\rules + goto :end +) + +if exist ".cursor\rules" ( + echo Target directory already exists: .cursor\rules + choice /c YN /m "Delete existing directory and recreate the link? (Y/N)" + if errorlevel 2 goto :end + rmdir ".cursor\rules" +) + +mklink /j ".cursor\rules" "src\components\_cursor.ai\rules" +if %errorlevel% equ 0 ( + echo Link created successfully! +) else ( + echo Failed to create link. Please check if you have administrator privileges. +) + +:end + +pause \ No newline at end of file diff --git a/_cursor.ai/plugins.doc/echarts.doc/CECharts.doc.md b/_cursor.ai/plugins.doc/echarts.doc/CECharts.doc.md new file mode 100644 index 0000000..4b9df5b --- /dev/null +++ b/_cursor.ai/plugins.doc/echarts.doc/CECharts.doc.md @@ -0,0 +1,138 @@ +# CECharts 图表组件 + +## 功能说明 + +该组件是对百度ECharts的封装,用于在小程序环境中渲染各类图表,支持触摸交互和数据更新。支持柱状图、折线图、饼图等各种ECharts支持的图表类型,组件内部已优化tooltip显示和触摸事件处理。 + +## 引用方式 + +```js +import { CECharts } from '@components/plugins/echarts'; +``` + +## 组件参数 + +- `canvasId` (String,默认值:随机生成):画布ID,用于标识图表实例 +- `disableTouch` (Boolean,默认值:false):是否禁用触摸交互 +- `onReady` (Function,可选):图表准备就绪的回调函数 + - 参数:`chart` (Object) ECharts实例对象,可通过此对象设置图表配置和数据 + +## 实例方法 + +- `init`:初始化图表方法 + - `callback` (Function,必填):初始化完成的回调函数,参数为 `(canvas, width, height, dpr)` +- `canvasToTempFilePath`:将图表转为临时文件路径 + - `opt` (Object,必填):微信小程序 canvasToTempFilePath 的参数对象 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <view class="chart-container"> + <CECharts + ref="salesChart" + :onReady="handleSalesChartReady" + /> + </view> + </view> +</template> + +<script> +import { CECharts } from '@components/plugins/echarts'; + +export default { + components: { + CECharts + }, + data() { + return { + salesData: [] + } + }, + methods: { + // 图表准备就绪回调 + handleSalesChartReady(chart) { + // 保存图表实例 + this.chart = chart; + + // 设置图表配置 + chart.setOption({ + title: { + text: '月度销售统计', + left: 'center' + }, + tooltip: { + trigger: 'axis' + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true + }, + xAxis: { + type: 'category', + data: ['1月', '2月', '3月', '4月', '5月', '6月'] + }, + yAxis: { + type: 'value' + }, + series: [{ + name: '销量', + type: 'bar', + data: [10, 52, 200, 334, 390, 330] + }] + }); + }, + + // 更新图表数据 + updateChartData(newData) { + if (!this.chart) return; + + this.chart.setOption({ + series: [{ + data: newData + }] + }); + } + }, + mounted() { + // 获取数据 + setTimeout(() => { + const newData = [50, 120, 180, 80, 70, 110]; + this.updateChartData(newData); + }, 2000); + } +} +</script> +``` + +### 保存图表为图片 + +```js +// 保存图表为图片 +saveChartAsImage() { + const _this = this; + this.$refs.salesChart.canvasToTempFilePath({ + success(res) { + console.log('保存成功:', res.tempFilePath); + // 可以进一步使用 wx.saveImageToPhotosAlbum 保存到相册 + }, + fail(err) { + console.error('保存失败:', err); + } + }); +} +``` + +## 注意事项 + +- 该组件暂时仅适用于小程序环境,文件名为 `CECharts.weapp.vue` +- 在小程序端使用需要确保 canvas 的 type 属性设置为 "2d" +- 小程序对 canvas 的性能有一定限制,复杂图表可能会有性能问题 +- 组件自动会处理触摸事件,支持图表交互 +- 组件已禁用了渐进式渲染(progressive: 0)以提高兼容性 +- 组件内置了默认的 tooltip 样式和位置优化逻辑 \ No newline at end of file diff --git a/_cursor.ai/plugins.doc/filter.doc/CFilter.doc.md b/_cursor.ai/plugins.doc/filter.doc/CFilter.doc.md new file mode 100644 index 0000000..f2c67ad --- /dev/null +++ b/_cursor.ai/plugins.doc/filter.doc/CFilter.doc.md @@ -0,0 +1,132 @@ +# CFilter 筛选组件 + +## 功能说明 + +该组件用于实现页面筛选功能,包含顶部筛选条和展开的更多筛选抽屉,支持多种筛选控件类型,包括选择器、日期范围、单选和输入框等。 + +## 引用方式 + +```js +import { CFilter } from '@components/plugins/filter'; +``` + +## 组件参数 + +- `filterData` (Object,默认值:{}):筛选数据对象 +- `bar` (Object,默认值:{}):筛选横条项目配置 + - `type` (String):控件类型,可选值有 'select'、'dateRange'、'input' + - `label` (String):显示的标签 + - `name` (String):字段名称 + - `cancelable` (Boolean):是否可取消选择,仅对select类型有效 +- `items` (Array,默认值:[]):筛选展开层项目列表配置 + - 每项格式同bar,但type可选值多一个'radio' +- `selectOptions` (Object,默认值:{}):各个项目的选项列表 + - 格式为 `{ 字段名: [{label: '显示文本', value: '值'}] }` +- `onChange` (Function,必填):筛选变化时的回调函数 + - 参数:`filterData` (Object) 变更后的筛选数据对象 + +## 实例方法 + +组件没有对外暴露的实例方法 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CFilter + :filterData="filterData" + :bar="barConfig" + :items="itemsConfig" + :selectOptions="selectOptions" + :onChange="handleFilterChange" + /> + + <!-- 列表内容 --> + <view class="list"> + <!-- 根据筛选结果显示内容 --> + </view> + </view> +</template> + +<script> +import { CFilter } from '@components/plugins/filter'; + +export default { + components: { + CFilter + }, + data() { + return { + // 筛选数据 + filterData: { + status: 1, + memberType: '', + dateRange: '', + keyword: '' + }, + // 顶部筛选条配置 + barConfig: { + type: 'select', + label: '状态', + name: 'status', + cancelable: false + }, + // 更多筛选项配置 + itemsConfig: [ + { + type: 'select', + label: '会员类型', + name: 'memberType' + }, + { + type: 'dateRange', + label: '创建日期', + name: 'dateRange' + }, + { + type: 'input', + label: '关键字', + name: 'keyword' + } + ], + // 选项数据 + selectOptions: { + status: [ + { label: '全部', value: '' }, + { label: '正常', value: 1 }, + { label: '停用', value: 0 } + ], + memberType: [ + { label: '全部', value: '' }, + { label: '普通会员', value: 'normal' }, + { label: 'VIP会员', value: 'vip' } + ] + } + } + }, + methods: { + // 筛选变化回调 + handleFilterChange(filterData) { + console.log('筛选条件变化:', filterData); + // 根据筛选条件获取数据 + this.getListData(filterData); + }, + + // 获取列表数据 + getListData(params) { + // 请求数据 + } + } +} +</script> +``` + +## 注意事项 + +- 筛选条(bar)只能配置一个筛选项 +- 抽屉式筛选(items)可以配置多个筛选项 +- 每个筛选项的类型(type)必须与组件类型匹配 +- select和radio类型的选项必须在selectOptions中配置对应的选项数据 \ No newline at end of file diff --git a/_cursor.ai/plugins.doc/infiniteScroll.doc/CInfiniteScroll.doc.md b/_cursor.ai/plugins.doc/infiniteScroll.doc/CInfiniteScroll.doc.md new file mode 100644 index 0000000..42f04ea --- /dev/null +++ b/_cursor.ai/plugins.doc/infiniteScroll.doc/CInfiniteScroll.doc.md @@ -0,0 +1,91 @@ +# CInfiniteScroll 无限滚动组件 + +## 功能说明 + +该组件实现了移动端常见的下拉刷新和上拉加载更多功能,支持自动加载第一页和手动触发加载,适用于列表展示场景。内置了加载状态、空数据状态和加载完成状态的显示,使用简单便捷。 + +## 引用方式 + +```js +import { CInfiniteScroll } from '@components/plugins/infiniteScroll'; +``` + +## 组件参数 + +- `autoInit` (Boolean,默认值:false):是否自动初始化(自动加载第一页) +- `onLoadMore` (Function,必填):发起加载页面数据的回调函数 + - `current` (Number):当前页页码 + - `next` (Number):需要加载的页面页码 + - `success` (Function):当加载成功后调用 + - 参数:`{ pageTotal: Number }` 总页数 + - `fail` (Function):加载失败后调用 + +## 实例方法 + +- `$initScroll`:初始化加载第一页数据 +- `$refresh`:重置并刷新数据 + - `autoStart` (String,可选):当值为 'off' 时不会自动加载,其他值或不传时会立即加载 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <CInfiniteScroll + ref="scrollList" + :autoInit="true" + :onLoadMore="handleLoadMore" + > + <view class="list-item" v-for="(item, index) in list" :key="index"> + {{ item.name }} + </view> + </CInfiniteScroll> + </view> +</template> + +<script> +import { CInfiniteScroll } from '@components/plugins/infiniteScroll'; +import { $fetchCommon } from '@fetchers/FCommon'; + +export default { + components: { + CInfiniteScroll + }, + data() { + return { + list: [] + } + }, + methods: { + handleLoadMore({ current, next, success, fail }) { + $fetchCommon.getList({ page: next }).then(res => { + // 拼接数据 + if (current === 0) { + this.list = res.list; + } else { + this.list = this.list.concat(res.list); + } + success({ + pageTotal: res.pageTotal + }); + }).catch(() => { + fail(); + }); + }, + // 手动刷新列表 + refreshList() { + this.$refs.scrollList.$refresh(); + } + } +} +</script> +``` + +## 注意事项 + +- 组件通过检测滚动位置自动触发加载更多功能 +- 下拉刷新功能仅在滚动到顶部时可用 +- 加载状态会自动管理,包括"加载中"、"没有更多了"和"暂无数据"等提示 +- 组件内部对 H5 和小程序环境做了适配,在不同平台上使用相同的 API 即可 \ No newline at end of file diff --git a/_cursor.ai/plugins.doc/qrcode.doc/CQRCode.doc.md b/_cursor.ai/plugins.doc/qrcode.doc/CQRCode.doc.md new file mode 100644 index 0000000..2ba3a34 --- /dev/null +++ b/_cursor.ai/plugins.doc/qrcode.doc/CQRCode.doc.md @@ -0,0 +1,76 @@ +# CQRCode 二维码组件 + +## 功能说明 + +该组件用于在页面中生成二维码,支持在H5环境下保存二维码图片到本地,界面简洁直观。 + +## 引用方式 + +```js +import { CQRCode } from '@components/plugins/qrcode'; +``` + +## 组件参数 + +- `content` (String,默认值:'http://www.aisim.cn'):二维码内容,通常为URL地址 +- `size` (Number,默认值:200):二维码尺寸,单位为像素 +- `margin` (Number,默认值:2):二维码边距大小 +- `downloadable` (Boolean,默认值:false):是否显示"保存到手机"按钮 + +## 实例方法 + +组件没有对外暴露的实例方法 + +## 使用示例 + +### 基础用法 + +```html +<template> + <view class="page"> + <!-- 简单用法 --> + <CQRCode content="https://www.example.com" /> + </view> +</template> + +<script> +import { CQRCode } from '@components/plugins/qrcode'; + +export default { + components: { + CQRCode + } +} +</script> +``` + +### 带保存按钮的用法 + +```html +<template> + <view class="page"> + <CQRCode + content="https://www.example.com" + :size="300" + :margin="4" + :downloadable="true" + /> + </view> +</template> + +<script> +import { CQRCode } from '@components/plugins/qrcode'; + +export default { + components: { + CQRCode + } +} +</script> +``` + +## 注意事项 + +- 目前在H5环境中支持保存二维码图片到本地 +- 小程序环境下的保存功能尚未实现 +- 二维码内容变更时会自动重新渲染 \ No newline at end of file diff --git "a/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\350\257\267\346\261\202.prompts.md" "b/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\350\257\267\346\261\202.prompts.md" new file mode 100644 index 0000000..6021b06 --- /dev/null +++ "b/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\350\257\267\346\261\202.prompts.md" @@ -0,0 +1,79 @@ +# 指令:创建一个请求 + +我们以会员信息为例新开请求 + +## 第一步:添加请求数据JSON文件 + +在 mocks 文件夹下新建数据文件 `getUserInfo.json` +``` +src/public/static/项目名称/mocks/user/getUserInfo.json +``` + +内容为: +```json +{ + "state": { + "code": 2000, + "msg": "OK" + }, + "data": { + } +} +``` + + +## 第二步:在请求集中添加请求 + +在 `src/fetchers` 文件夹下,以 F 开头的请求请求集中,添加一个独立的请求方法,方法名和 json 文件一样 + +例如:在 `FUser.js` 中添加 `getUserInfo` +```js +class FUser extends Fetcher { + constructor() { + super({ + urlPrefix: ['/api/user/', '/mini/'], + }); + } + + // 读取会员信息 + getUserInfo(user) { + const url = this.spellURL('getUserInfo', 'User/Info'); + const send = { + ...this.transKeyName('underline', user), + }; + return this.post(url, send); + } + +} + +export const $fetchUser = new FUser(); +``` + +如果创建一个全新的请求集,那么构造器的 urlPrefix 参数的第一个值为 `'/api/分类名/'` + +## 第三步:在数据控制器中调用请求 + +在需要的需要的数据控制文件中: +- 使用 `import` 引入需要的使用的请求集 +- 在类中,添加一个独立方法,调用对应的数据请求 + +```js +import { $fetchUser } from '@fetchers/FUser'; + +export class PLogisSender extends Pilot { + + // 加载用户详情 + onLoadUserDetail(user) { + Taro.showLoading(); + $fetchUser.getUserDetail(user) + .then(res => { + Taro.hideLoading(); + if (!res) { + return; + } + this.userDetail = res.detail; + }); + } + +} +``` diff --git "a/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\351\241\265\351\235\242.prompts.md" "b/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\351\241\265\351\235\242.prompts.md" new file mode 100644 index 0000000..42781bf --- /dev/null +++ "b/_cursor.ai/prompts/\345\210\233\345\273\272\344\270\200\344\270\252\351\241\265\351\235\242.prompts.md" @@ -0,0 +1,70 @@ +# 指令:创建一个页面 + +我们以会员中心为例新开页面 + +## 第一步:创建界面文件夹 +在界面层的目录 `src/pages/某分类名/` 下新建页面文件夹 `userCenter` +``` +src/pages/user/userCenter +``` + +## 第二步:创建界面配置文件 +在刚刚新建的文件夹下新建配置文件 `userCenter.config.js` + +内容为: +```js +export default { + navigationBarTitleText: '会员中心', +} +``` + +## 第三步:创建空白界面 +继续在刚刚新建的文件夹下新建界面文件 `userCenter.vue` + +内容参考文件 `src/components/_cursor.ai/rules/type-surface.mdc` 中的空白模版 + +### 样式 + +同时创建样式文件 `userCenter.scss` +```css +/** + * userCenter + * @author 作者 + */ + +@import "../../../components/common/sassMixin"; + +.user-center {} +``` + +## 第四步:创建数据控制器 +在业务数据控制层的目录 `src/pilots/某分类名/` 下创建数据控制器 `PUserCenter.js` + +内容参考文件:`src/components/_cursor.ai/rules/type-pilot.mdc` 中的空白模版 + + +## 第五步:添加页面路由 +在路由文件 `src/app.config.js` 中,添加会员中心的路由 + +```js +export default { + pages: [ + 'pages/home/index/index', + // 添加会员中心 + 'pages/user/userCenter/userCenter', + ], +}; +``` + +## 第六步:添加首页入口 +在首页菜单配置文件 `src/fetchers/datas/menu.json` 文件中,在 `menu` 属性的数组中,添加一条数据 + +```json +{ + "authId": 1, + "name": "会员中心", + "url": "/pages/user/userCenter/userCenter", + "icon": "" +} +``` +注意:`authId` 不要重复 diff --git "a/_cursor.ai/prompts/\346\233\264\346\226\260\345\205\254\345\205\261\347\273\204\344\273\266\346\226\207\346\241\243.prompts.md" "b/_cursor.ai/prompts/\346\233\264\346\226\260\345\205\254\345\205\261\347\273\204\344\273\266\346\226\207\346\241\243.prompts.md" new file mode 100644 index 0000000..eed6f00 --- /dev/null +++ "b/_cursor.ai/prompts/\346\233\264\346\226\260\345\205\254\345\205\261\347\273\204\344\273\266\346\226\207\346\241\243.prompts.md" @@ -0,0 +1,61 @@ +# 更新公共组件文档 + +## 第一步,检查组件 + +### 根据组件路径,检查是否为公共组件 + +检查当前组件路径,如果不在公共组件目录内,则不是公共组件,不适用本更新策略,任务跳过 + +公共组件的三个目录: +- src/components/forms +- src/components/layout +- src/components/plugins + +### 读取组件,检查是否为 Vue 组件 + +根据组件内容,判断为是不是 Vue 组件,如果不是,任务跳过 + +## 第二步,检查文档是否已经存在 + +每一个公共组件,都有一份组件文档 + +如果当前组件为: +src/components/forms/checkbox/CCheckBox.vue + +那么它对应的文档为: +src/components/_cursor.ai/forms.doc/checkbox.doc/CCheckBox.doc.md + +其规律为: +- 组件的文档都在 src/components/_cursor.ai/ 文件夹下,有类似的目录结构 +- 组件文件夹 forms/ 对应文档文件夹 forms.doc/(名称增加 `.doc`) +- 组件子文件夹 checkbox/ 对应文档子文件夹 checkbox.doc/(名称增加 `.doc`) +- 组件文件 CCheckBox.vue 对应文档文件 CCheckBox.doc.md(名称增加 `.doc`,但扩展名不变) + +## 第三步,如果文档不存在,创建 + +依照位置与命名规律,按照文档格式创建对应的组件文档 + +## 第四步,如果文档已存在,更新文档 + +读取文档内容,将组件功能与文档对比 +- 判断文档是否符合文档格式的要求,如果不符合,请按格式修改 +- 判断是否需要修剪已有内容和增加新内容,如果有,更新文档 + +## 文档格式 + +文档格式为markdown,每份组件需要包含一下项目: +- 由组件文件名+组件中文名组成大标题 +- 功能说明 +- 引用方式 +- 组件参数 + - 请使用列表形式,例如:- `paramName` (String,可选):参数说明 + - 如果是可枚举的几个具体值,请以二级列表的形式说明每个值,例如:- `enumValue`:枚举值说明 + - 如果是回调函数,请以二级列表的形式说明函数的参数和返回值,例如:- 参数 `paramName` (String,可选):参数说明 +- 实例方法 + - 是指 methods 中,名称仅以 `$` 符号开头的方法,没有 `$` 可省略 + - 如果方法有参数,请以二级列表形式说明方法的参数和返回值,例如:- 参数 `paramName` (String,可选):参数说明 +- 使用示例 + - 如果组件有多种使用模式,请分别提供不同模式的示例 +- 注意事项 + - 重要或需要注意的问题 + - 特殊的技术点或使用场景 \ No newline at end of file diff --git "a/_cursor.ai/prompts/\346\233\264\346\226\260\347\273\204\344\273\266\347\233\256\345\275\225.prompts.md" "b/_cursor.ai/prompts/\346\233\264\346\226\260\347\273\204\344\273\266\347\233\256\345\275\225.prompts.md" new file mode 100644 index 0000000..0c4c375 --- /dev/null +++ "b/_cursor.ai/prompts/\346\233\264\346\226\260\347\273\204\344\273\266\347\233\256\345\275\225.prompts.md" @@ -0,0 +1,35 @@ +# 更新公共组件目录 + +## 第一步,读取列表 + +依次读取三个公共组件文件夹的文件列表 +- `src/components/_cursor.ai/forms.doc/` +- `src/components/_cursor.ai/layout.doc/` +- `src/components/_cursor.ai/plugins.doc/` + +## 第二步,读取目录文档 + +公共组件目录文件路径为: +`src/components/_cursor.ai/组件目录.md` + +请读取此目录的内容,找出目录中未收录的公共组件 + +## 第三步,补充目录 + +对于目录中没有收录的组件,请添加进来,格式为 + +``` +- **组件英文名 组件中文名** + 功能说明 + [组件文档 »](组件文档地址) + [组件代码 »](组件代码文件地址) +``` + +例如: +``` +- **CAlert 弹窗** + 基于 Taro UI 的模态框(AtModal)封装的alert弹窗组件,支持Alert(提示)和Confirm(确认)两种模式 + [组件文档 »](/src/components/_cursor.ai/layout.doc/alert.doc/CAlert.doc.md) + [组件代码 »](/src/components/layout/alert/CAlert.vue) +``` + diff --git "a/_cursor.ai/102-\345\274\200\345\217\221\350\247\204\350\214\203.md" b/_cursor.ai/rules/all-dev-specification.mdc similarity index 74% rename from "_cursor.ai/102-\345\274\200\345\217\221\350\247\204\350\214\203.md" rename to _cursor.ai/rules/all-dev-specification.mdc index c73d973..f02256a 100644 --- "a/_cursor.ai/102-\345\274\200\345\217\221\350\247\204\350\214\203.md" +++ b/_cursor.ai/rules/all-dev-specification.mdc @@ -1,39 +1,24 @@ +--- +description: +globs: +alwaysApply: true +--- + # 开发规范 -- [开发规范](#开发规范) - - [规范宗旨](#规范宗旨) - - [命名问题](#命名问题) - - [命名需要具有实义](#命名需要具有实义) - - [文件命名](#文件命名) - - [界面层文件](#界面层文件) - - [数据控制层文件](#数据控制层文件) - - [组件文件](#组件文件) - - [请求文件](#请求文件) - - [样式命名](#样式命名) - - [界面样式](#界面样式) - - [组件样式](#组件样式) - - [JS 变量和属性的命名](#js-变量和属性的命名) - - [变量命名的特别约定](#变量命名的特别约定) - - [JS 方法的命名](#js-方法的命名) - - [在数据控制层](#在数据控制层) - - [在组件内](#在组件内) - - [书写问题](#书写问题) - - [JS 中的书写](#js-中的书写) - - [变量定义符](#变量定义符) - - [模版中的函数调用](#模版中的函数调用) - - [CSS 属性顺序](#css-属性顺序) +## 开发规范宗旨 -## 规范宗旨 - -**代码首先是给人读的,其次才是给机器运行**,所以让代码容易阅读是应尽的责任 - -除了尽可能不写难懂的代码外,我们还需要尽量遵守统一的规范,来帮助我们更高效率的相互阅读代码 - -## 命名问题 +**代码首先是给人读的,其次才是给机器运行**,所以让代码容易阅读是你应尽的责任 +包括但不限于以下内容: +- 给变量、函数命名时,需要具有实义 +- 避免使用难懂的语法或设计,用最简单的方式解决问题 +- 避免单块代码过长,拆分为多个更简短的函数或方法 +- 避免代码重复,通过封装函数、类或模块进行复用,封装遵守单一职责原则 +- 写注释应解释 "为什么" 而不仅仅是 "做什么" ### 命名需要具有实义 -命名的核心原则是名称含有具体的实体含义 +命名的原则是名称中有具体实体含义的词汇 比如没有实义的 @@ -49,111 +34,7 @@ 改进后,从函数名一看便知,这是商品被选中了以后的需要进行的操作,又因为具备了在同一个页面的唯一性,能帮我们快速获取信息,知道这是哪个业务的哪个环节,大幅提高阅读效率,这就是实义 -### 文件命名 - -常用文件命名约定 - -#### 界面层文件 - -界面层文件(.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(); -``` - -### 样式命名 - -样式名的命名,采用横杠连接 - -```css -.page-name {} -``` - -#### 界面样式 - -任何一个界面必定包含众多样式,所以: - -* 最父级样式名固定,与界面名称一直 -* 中间层,需要以父级为前缀,接板块说明 -* 末端则可单名,且尽量不含实义 - -```css -/** 在界面 userList.vue 中,最父级样式名名称固定 **/ -.user-list { - - /** 中间多层,以父级为前缀 **/ - .user-list-title { - - /** 末端可单名,尽量非实义 **/ - .left, - .text, - .icon, - .sub, - .close {} - } -} -``` - -#### 组件样式 - -组件样式名必须已 `c-` 开头,最父级样式名必须与组件名挂钩 - -例如,组件 CMenu.vue 中,最父级样式名必须是: - -```css -.c-menu {} -``` +## 常见命名要求 ### JS 变量和属性的命名 @@ -178,6 +59,8 @@ ```js // 允许 is 开头的变量名 let isOpened = false; +// 更好的命名 +let selectorOpened = false; ``` **事件变量用 evt** @@ -300,9 +183,123 @@ } ``` -## 书写问题 +### 样式命名 -书写规则都由编辑器自动格式化即可,这里主要说明非格式化的几点约定 +样式名的命名,采用横杠连接 + +```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 中的书写 @@ -333,17 +330,46 @@ * 对于普通数据,如果明确要变更变量值,才使用 `let` * 对于引用数据,如果明确需要重新赋值一个引用对象,才使用 `let` +#### 变量判断 + +为了避免弱类型带来的bug追踪困难,变量判断需要使用恒等号,必须判断类型一致 + +```js +// 需要同时判断值和类型 +if (order.state === 1) {} +``` + #### 模版中的函数调用 -模版中调用函数,必须显式的书写 evt 变量和箭头函数,即: `evt => fncName()` -我们把 Taro 自身组件视为普通元素,元素绑定事件使用 `@tap` 的方式,非元素皆是组件,组件使用 `:onClick` 的方式绑定 +- 模版中调用函数,为了便于阅读,必须显式的书写 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 属性写重复,请按如下顺序书写: diff --git a/_cursor.ai/rules/all-project-info.mdc b/_cursor.ai/rules/all-project-info.mdc new file mode 100644 index 0000000..d4b6cef --- /dev/null +++ b/_cursor.ai/rules/all-project-info.mdc @@ -0,0 +1,128 @@ +--- +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/201-\350\257\267\346\261\202\345\261\202\345\237\272\347\261\273Fetcher.md" b/_cursor.ai/rules/fit-base-fetcher.mdc similarity index 78% rename from "_cursor.ai/201-\350\257\267\346\261\202\345\261\202\345\237\272\347\261\273Fetcher.md" rename to _cursor.ai/rules/fit-base-fetcher.mdc index 5166145..20ee33f 100644 --- "a/_cursor.ai/201-\350\257\267\346\261\202\345\261\202\345\237\272\347\261\273Fetcher.md" +++ b/_cursor.ai/rules/fit-base-fetcher.mdc @@ -1,25 +1,10 @@ -# 请求层基类 Fetcher.js +--- +description: 数据控制器基类,所有数据控制器都必须继承此类 +globs: +alwaysApply: false +--- -- [请求层基类 Fetcher.js](#请求层基类-fetcherjs) - - [功能说明](#功能说明) - - [引用方法](#引用方法) - - [构造函数](#构造函数) - - [主要方法](#主要方法) - - [`spellURL(devSuffix, serSuffix)`](#spellurldevsuffix-sersuffix) - - [`get(url, data, options)`](#geturl-data-options) - - [`post(url, data, options)`](#posturl-data-options) - - [`query(type, url, data, options)`](#querytype-url-data-options) - - [`stringToCamel(str)`](#stringtocamelstr) - - [`stringToUnderline(str)`](#stringtounderlinestr) - - [`transKeyName(type, json)`](#transkeynametype-json) - - [请求的 options 配置](#请求的-options-配置) - - [`hostType` 主机类型](#hosttype-主机类型) - - [`silence` 静音请求](#silence-静音请求) - - [响应处理](#响应处理) - - [处理流程](#处理流程) - - [前端统一响应数据格式](#前端统一响应数据格式) - - [前端统一响应状态码](#前端统一响应状态码) - - [请求调用](#请求调用) +# 请求层基类 Fetcher.js ## 功能说明 @@ -29,15 +14,14 @@ ## 引用方法 ```js -import { - Fetcher -} from '@components/bases/Fetcher'; +import { Fetcher } from '@components/bases/Fetcher'; ``` ## 构造函数 -构造函数接受一个配置对象 options 作为参数,包含如下属性: -* **urlPrefix** 数组,包含两项,第一项为本地 Mock 的 URL 前缀,第二项为服务器接口的前缀 +构造函数接受一个配置对象作为参数,包含如下属性: + +- **urlPrefix** 数组,包含两项,第一项为本地 Mock 的 URL 前缀,第二项为服务器接口的前缀 ```js class FCommon extends Fetcher { @@ -133,7 +117,7 @@ - `type` (String):请求类型(如 `'get'`、`'post'`) - `url` (String):请求的 URL 地址 - `data` (Object,可选):请求参数 -- `options` (Object,可选):请求配置 +- `options` (Object,可选):请求配置(参照下文) **返回值** - (Promise):返回请求结果的 Promise @@ -195,7 +179,7 @@ **注意事项** - 支持嵌套对象的递归转换 -## 请求的 options 配置 +## 请求 query 的 options 配置 ### `hostType` 主机类型 @@ -277,7 +261,7 @@ } ``` -注意,为了保证接口数据的扩展性,data 只能接 Object 类型,禁止接其他类型 +注意,为了保证接口数据的扩展性,**data 只能接 Object 类型**,禁止接其他类型 ### 前端统一响应状态码 @@ -294,23 +278,31 @@ ```js import { Fetcher } from '@components/bases/Fetcher'; -class FCommon extends Fetcher {} +// 请求集 +class FCommon extends Fetcher { + + getUserInfo(user) { + const url = this.spellURL('getUserInfo', 'User/info'); + const send = {..user}; + return this.post(url, send); + } + +} // 全局单例 export const $fetchCommon = new FCommon(); ``` ```js -import { - $fetchCommon -} from '@fetchers/FCommon'; +import { $fetchCommon } from '@fetchers/FCommon'; -export class PPageName extends Pilot { +// 数据控制器 +export class PUserDetail extends Pilot { - // 在数据控制层中,发请求示例 onLoadUserInfo() { Taro.showLoading(); - $fetchCommon.getUserInfo(this.userId) + // 调用请求集发请求示例 + $fetchCommon.getUserInfo({uid: this.userId}) .then(res => { Taro.hideLoading(); if (!res) { @@ -322,3 +314,5 @@ } ``` + + diff --git "a/_cursor.ai/202-\346\225\260\346\215\256\346\216\247\345\210\266\345\261\202\345\237\272\347\261\273Pilot.md" b/_cursor.ai/rules/fit-base-pilot.mdc similarity index 87% rename from "_cursor.ai/202-\346\225\260\346\215\256\346\216\247\345\210\266\345\261\202\345\237\272\347\261\273Pilot.md" rename to _cursor.ai/rules/fit-base-pilot.mdc index 63bfadb..ef70d86 100644 --- "a/_cursor.ai/202-\346\225\260\346\215\256\346\216\247\345\210\266\345\261\202\345\237\272\347\261\273Pilot.md" +++ b/_cursor.ai/rules/fit-base-pilot.mdc @@ -1,18 +1,10 @@ -# 数据控制层基类 Pilot.js +--- +description: 数据控制器基类,所有数据控制器都需要继承此类 +globs: +alwaysApply: false +--- -- [数据控制层基类 Pilot.js](#数据控制层基类-pilotjs) - - [功能说明](#功能说明) - - [引用方法](#引用方法) - - [主要方法](#主要方法) - - [`createOptions(dataAdd)`](#createoptionsdataadd) - - [`transAssets(assets)`](#transassetsassets) - - [静态图片地址转换说明](#静态图片地址转换说明) - - [页面合并流程](#页面合并流程) - - [第一步:生成合并对象](#第一步生成合并对象) - - [第二步:实际合并](#第二步实际合并) - - [页面能力扩展](#页面能力扩展) - - [跨页面通讯](#跨页面通讯) - - [跨端通讯](#跨端通讯) +# 数据控制层基类 Pilot.js ## 功能说明 @@ -25,9 +17,7 @@ ## 引用方法 ```js -import { - Pilot -} from '@components/bases/Pilot'; +import { Pilot } from '@components/bases/Pilot'; ``` ## 主要方法 @@ -159,9 +149,7 @@ ```js // 引入界面层对应数据控制器 -import { - PPageName -} from '@pilots/pilotGroup/PPageName'; +import { PPageName } from '@pilots/pilotGroup/PPageName'; export default { name: 'PageName', 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-component.mdc b/_cursor.ai/rules/type-component.mdc new file mode 100644 index 0000000..8d4918b --- /dev/null +++ b/_cursor.ai/rules/type-component.mdc @@ -0,0 +1,57 @@ +--- +description: +globs: src/pages/**/**/cmpt/*.vue,src/components/**/**/*.vue +alwaysApply: false +--- + +# 界面子组件&公共组件 + +界面子组件与公共组件,就是一个普通 Vue 组件 + +## 子组件空白模板 + +```html +/** + * CExample - 示范组件 + * @author Tevin + */ + +<template> + <view class="c-example"> + <!-- 组件内容 --> + <AtButton + type="primary" + size="small" + :onClick="evt => handleOpen()" + >示例按钮</AtButton> + </view> +</template> + +<script> +import Taro from '@tarojs/taro'; +import { AtButton } from 'taro-ui-vue'; +import './cExample.scss'; + +export default { + name: 'CExample', + components: { + AtButton, + }, + props: { + onOpened: Function, + }, + data() { + return { + }; + }, + computed: { + }, + methods: { + handleOpen() { + this.onOpened(); + } + }, + mounted() {}, +}; +</script> +``` diff --git a/_cursor.ai/rules/type-fetchers.mdc b/_cursor.ai/rules/type-fetchers.mdc new file mode 100644 index 0000000..babdf7e --- /dev/null +++ b/_cursor.ai/rules/type-fetchers.mdc @@ -0,0 +1,38 @@ +--- +description: +globs: src/fetchers/F*.js +alwaysApply: false +--- + +# 请求集 + +## 请求集空白模板 + +```js +/** + * FCommon - 公用请求集 + * @author 作者 + */ + +import { Fetcher } from '@components/bases/Fetcher'; + +class FCommon extends Fetcher { + + constructor() { + super({ + // url前缀(本地路径, 服务器路径) + urlPrefix: ['/api/common/', '/serverPath/'], + }); + } + + // 读取页面详情 + getPageDetail() { + const url = this.spellURL('getPageDetail', 'page/Detail'); + const send = {}; + return this.post(url, send); + } + +} + +export const $fetchCommon = new FCommon(); +``` diff --git a/_cursor.ai/rules/type-pilot.mdc b/_cursor.ai/rules/type-pilot.mdc new file mode 100644 index 0000000..96010c5 --- /dev/null +++ b/_cursor.ai/rules/type-pilot.mdc @@ -0,0 +1,47 @@ +--- +description: +globs: src/pilots/**/P*.js +alwaysApply: false +--- + +# 数据控制器 + +## 数据控制器空白模板 + +```js +/** + * PPageName - 页面名称 + * @author 作者 + */ + +import Taro from '@tarojs/taro'; +import { Pilot } from '@components/bases/Pilot'; +import { $fetchCommon } from '@fetchers/FCommon'; + +export class PPageName extends Pilot { + + $data() { + return {}; + } + + $mounted() { + this.onLoadDataResource(); + } + + // 加载用户详情 + onLoadDataResource() { + Taro.showLoading(); + $fetchCommon.getPageDetail() + .then(res => { + Taro.hideLoading(); + if (!res) { + return; + } + // do something + }); + } + +} +``` + +说明:请求异常由请求层的基类自动处理,数据控制层跳过错误的逻辑,只处理请求成功的后续业务 \ No newline at end of file diff --git a/_cursor.ai/rules/type-surface.mdc b/_cursor.ai/rules/type-surface.mdc new file mode 100644 index 0000000..a501c45 --- /dev/null +++ b/_cursor.ai/rules/type-surface.mdc @@ -0,0 +1,43 @@ +--- +description: +globs: src/pages/**/**/*.vue +alwaysApply: false +--- + +# 界面 + +## 界面空白模板 + +```html +/** +* pageName - 页面名称 +* @author 作者 +*/ + +<template> + <CPage> + <CNavBar title="页面名称" /> + <CContent class="page-name"> + <!-- 页面内容 --> + </CContent> + </CPage> +</template> + +<script> +import Taro from '@tarojs/taro'; +import {} from 'taro-ui-vue'; +import { PPageName } from '@pilots/pilotGroup/PPageName'; +import { CPage, CContent, CNavBar } from '@components/layout/h5Page'; +import './pageName.scss'; + +export default { + name: 'PageName', + components: {}, + ...new PPageName().createOptions(), +}; +</script> +``` + +说明: +- H5 界面需要 CPage、CContent、CNavBar 这三个排版组件作为页面的基础布局(在小程序中则不需要) + diff --git "a/_cursor.ai/\345\267\245\344\275\234\345\205\261\350\257\206.md" "b/_cursor.ai/\345\267\245\344\275\234\345\205\261\350\257\206.md" new file mode 100644 index 0000000..f0135bf --- /dev/null +++ "b/_cursor.ai/\345\267\245\344\275\234\345\205\261\350\257\206.md" @@ -0,0 +1,138 @@ +# 前端工作共识 + +尽管我们来自五湖四海,有着各自不同的编码习惯,但现在我们聚集在一起组成了一个团队。为了使团队合作更加流畅,减少沟通成本和内耗,我们需要在以下几个方面达成共识。 + +## 1. 代码首先是给人读的,其次才是给机器运行 + +团队开发与个人开发最大的区别在于:你身处一个团队中,大家需要相互配合。因此,**让代码容易阅读是你应尽的责任**。 + +### 1.1 代码命名必须有实体意义 + +> 好的代码,就像阅读小说一样流畅自然,看到哪里就能理解到哪里,是一种愉悦的体验,而不是像侦探破案一样需要推理猜测。 + +要实现良好的阅读体验,代码命名必须包含与业务相关的真实实体名词: + +| 改前 | 改后 | 意义 | 实体词 | +| :--------------- | :--------------------- | :----------------- | :-------- | +| onValueChange | **onNoteValueChange** | 备注的值改变 | note | +| onSelectChange | **onFillSelectChange** | 充装记录的选择变化 | fill | +| onSelectedChange | **onSpecSelectChange** | 充装规格的选择变化 | spec | +| onItemChange | **onPriceTypeChange** | 价格类型变化 | priceType | + +这样的命名方式带来两个显著好处: +1. 做无关模块开发时可以快速跳过不相关代码 +2. 做相关模块开发时一眼就能确定要查看的代码 + +同时,让实体名词承担功能表述的作用,能让代码维护更依赖阅读而不是记忆,**极大减轻记忆负担** + +### 1.2 设计良好的模块与分层结构 + +优秀的代码结构应该是有序且层次分明的,而非杂乱无章的流水账。良好的模块与分层设计能帮助我们更高效地掌控和阅读代码: + +#### 1.2.1 显示与业务分离 +将显示层与业务层清晰分开,各司其职: +- 显示层:专注于信息呈现和用户交互 +- 业务层:专注于数据处理和业务逻辑 + +这种分离使我们能快速定位问题所在——是界面问题还是数据问题,一目了然 + +#### 1.2.2 主流程与次要操作分层 +对于复杂的操作流程,我们应将各个操作步骤封装为独立组件: +- 主流程代码:保持简洁明了,仅包含关键流程和组件引用 +- 子组件:承担具体操作细节,各自负责单一职责 + +这样设计后,主流程代码变得清晰易读,概览全局更加轻松。而当需要关注某个具体步骤时,只需查看相应的子组件即可,无需关注其他部分,大大提高了代码的掌控效率 + +#### 1.2.3 公共代码复用 +模块化设计使代码复用变得自然而简单: +- 通用功能抽离为独立公共组件 +- 跨页面的相似逻辑统一封装子组件 +- 跨项目公共资源共享,减轻决策负担 + +这不仅减少了代码冗余,还提高了开发效率和代码质量 + +## 2. 思考先行,开发随后 + +### 2.1 理解清楚需求再动手 + +由于我们的需求文档通常由老板和总监编写,没有专职人员完善需求细节,因此某些需求描述可能比较模糊 +这种情况下,如果对业务理解不深,很容易导致开发的功能与实际需求不一致,最终导致需要推倒重来 + +因此,我们需要在开发过程中不断积累对业务的理解,至少要达到能发现需求歧义的程度 + +有歧义是正常的,老板和总监对业务太熟悉了,往往会自然而然地认为某些理解是理所当然的 +而对业务不熟悉的人,从需求文字字面出发,从而可能产生不同理解 + +发现歧义时,请及时咨询我或旭诚,问清楚具体要求,一定要完全理解需求后再开始开发 + +### 2.2 设计清晰方案再动手 + +除了业务需求,在开发前还应该梳理清楚技术形式、模块拆分和实现方式,以最简洁的方案实现需求 +切忌盲目开工,为求速度而书写毫无章法的代码 + +如果不重视工程质量,因为前端代码过于灵活,一段时间后代码混乱到无法维护了,就被迫需要重构,我们应该避免这种事情发生 + +只关注结果产量而不重视工程质量,这种代码在代码审查中一旦发现,就会被要求返工重写 + +## 3. 用户体验是工作的一部分 + +用户体验是我们前端开发者需要主动思考的问题,这是我们工作的重要组成部分 + +我们公司的用户体验价值观是:**宁可技术实现复杂一些,也要让用户操作尽可能简单** + +这不仅仅是口号,我们有着深入的实践经验: + +### 3.1 前端技术密集型案例 + +我们电子秤开票界面,有个支持全键盘操作表格,有众多客户反馈,我们经过讨论确认后,专门开发的用户体验改进功能 + +这个表格看似简单,用起来也简单,这正是我们的目标 + +但在技术层面,它是表格和表单的复合体,支持表格回车自动加行、方向键自由切换不同组件的焦点,以及表单验证等,集众多复杂功能于一体 + +### 3.2 业务复杂型案例 + +建档 app 中的"气瓶制造单位"选项 +用户看到的只是一个普通下拉选框,旁边有个【管理】按钮可以增减选项内容,功能表面上看是很简单的 + +但实际上,选项内容的变更需在同公司多个手机间同步,且这些厂家名称没有数据 ID,只能通过文字精确匹配 +同时,输入名称必须符合工商注册名称,不能使用口语化的俗称,否则档案上传监管平台会通不过 +还有,允许客户删除他们不需要的选项,仅从需要的厂家中选(对于新客户,支持一键恢复全国所有厂家,然后再慢慢删) + +这种复杂性对用户是完全透明的 + +## 4. 拥抱 AI + +### 4.1 价值重塑 + +当今世界正在被 AI 技术逐步重构,在这场技术变革浪潮中,提示词工程师已成为不可或缺的关键角色 + +这并不意味着我们过去学习的技术知识变得无用,提示词工程师需要承担起工程建设的全部责任 +当项目出现问题时,责任不在于 AI 而在指导 AI 的工程师,这正是工程师价值的核心所在 + +同时,我们开发的产品是为人服务的 +无论 Agent 多么智能,它都无法和真实的人那样体验产品,因此我们需要清晰地指导 AI 完成什么任务,告诉它做好了没,确保产品能够满足人的需求 + +### 4.2 开发流程中的应用 + +在代码生成方面,AI 能快速生成组件、功能、工作流程,但核心业务逻辑仍需人工审核 + +代码质量审查上,AI 能检查潜在bug、发现安全隐患、提示缺失注释,全方位提升代码质量标准 + +文档编写与维护中,AI 协助编写各类文档,保证完整性和一致性,减轻维护负担 + +### 4.3 辅助开发的基本原则 + +首先,我们需要了解 AI 的能力边界 +AI 工具虽然强大但并非万能,我们需要理解它的优势和局限性。它在代码生成、问题诊断、知识获取等方面可以发挥巨大作用,但创造性设计、用户体验把控和业务理解仍然需要人类的专业判断 + +其次,我们应该提高提示词工程能力 +良好的提示词能让 AI 输出更准确、更符合项目需求的结果。学会如何结构化描述问题、提供足够上下文、明确说明需求和约束,是高效使用 AI 的关键技能 + +最后,我们必须保持技术掌控和结果的主导权 +AI 只是工具而非替代品。对生成的代码需要保持审查和理解,确保它符合我们的架构和规范,保持对代码的掌控力和理解力是开发者的基本要求 + +### 4.4 调整心态,面向未来 + +拥抱 AI 不是简单地使用工具,而是重新思考我们的工作方式和价值定位 +在这个转变过程中,持续学习、开放心态和职业成长将是我们适应新时代的关键要素 diff --git "a/_cursor.ai/\346\226\207\346\241\243\350\257\264\346\230\216.md" "b/_cursor.ai/\346\226\207\346\241\243\350\257\264\346\230\216.md" new file mode 100644 index 0000000..3515ffc --- /dev/null +++ "b/_cursor.ai/\346\226\207\346\241\243\350\257\264\346\230\216.md" @@ -0,0 +1,47 @@ +# 文件夹介绍 + +## 1. rules 文件夹 + +rules 是指 AI 根据一定条件自动读取的工作环境设定,是我们控制项目代码生成质量的重要手段 + +### 1.1 规则类型:全局使用 `Always` + +所有的聊天(Agent、Ask、Edit)和 ctrl+k 编辑,都会参考此规则生成代码 + +- [系统角色](/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) + +### 1.2 规则类型:按路径匹配 `Auto-Attached` + +当文件名称或路径匹配上时,会参考此规则生成代码 + +- [请求集](/src/components/_cursor.ai/rules/type-fetchers.mdc) +- [数据控制器](/src/components/_cursor.ai/rules/type-pilot.mdc) +- [界面](/src/components/_cursor.ai/rules/type-surface.mdc) +- [子组件&公共组件](/src/components/_cursor.ai/rules/type-component.mdc) + +### 1.3 规则类型:自主决定 `Agent-Requested` + +在 Agent 模式下,由 AI 根据 Description 的文字描述,自主决定是否需要参考此规则生成代码 +非 Agent 模式需要我们自己 @ 此规则才能生效 + +- [请求集基类](/src/components/_cursor.ai/rules/fit-base-fetcher.mdc) +- [数据控制器基类](/src/components/_cursor.ai/rules/fit-base-pilot.mdc) +- 表单验证规则 + +## 2. prompts 文件夹 + +prompts 存放我们主动提出的指令 +和我们在聊天窗口要求 ai 完成工作一样,就是对需要经常执行的有具体要求的操作,存下来方便反复使用 + +## 3. 文档文件夹 + +公共组件库组件对应的文档 + +| 文档目录 | 对应的组件目录 | +| ------------- | ----------------------- | +| `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/\347\273\204\344\273\266\347\233\256\345\275\225.md" "b/_cursor.ai/\347\273\204\344\273\266\347\233\256\345\275\225.md" new file mode 100644 index 0000000..db3bd9d --- /dev/null +++ "b/_cursor.ai/\347\273\204\344\273\266\347\233\256\345\275\225.md" @@ -0,0 +1,176 @@ +# 公共组件目录 + +移动端全部公共组件目录 + +## 1. 表单类组件 + +### 1.1 输入控件 + +- **CInput 文本输入框** + 基础的文本输入框组件,用于在表单中收集用户的文本输入 + [组件文档 »](/src/components/_cursor.ai/forms.doc/input.doc/CInput.doc.md) + [组件代码 »](/src/components/forms/input/CInput.vue) + +- **CInputPhoneCode 手机验证码输入框** + 带验证码功能的手机号输入组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/input.doc/CInputPhoneCode.doc.md) + [组件代码 »](/src/components/forms/input/CInputPhoneCode.vue) + +- **CInputScanCode 扫码输入框** + 带扫码功能的输入框组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/input.doc/CInputScanCode.doc.md) + [组件代码 »](/src/components/forms/input/CInputScanCode.vue) + +- **CInputExpressCode 快递单号输入框** + 快递单号输入组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/input.doc/CInputExpressCode.doc.md) + [组件代码 »](/src/components/forms/input/CInputExpressCode.vue) + +- **CTextarea 多行文本输入** + 多行文本输入组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/textarea.doc/CTextarea.doc.md) + [组件代码 »](/src/components/forms/textarea/CTextarea.vue) + +### 1.2 选择控件 + +- **CSelect 选择器** + 下拉选择组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/select.doc/CSelect.doc.md) + [组件代码 »](/src/components/forms/select/CSelect.vue) + +- **CDatePicker 日期选择器** + 日期选择组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/datePicker.doc/CDatePicker.doc.md) + [组件代码 »](/src/components/forms/datePicker/CDatePicker.vue) + +- **CChinaArea 中国区域选择器** + 中国省市区选择器组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/chinaArea.doc/CChinaArea.doc.md) + [组件代码 »](/src/components/forms/chinaArea/CChinaArea.vue) + +- **CImagePicker 图片选择器** + 图片上传选择组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/imagePicker.doc/CImagePicker.doc.md) + [组件代码 »](/src/components/forms/imagePicker/CImagePicker.vue) + +### 1.3 数值控件 + +- **CNumberStep 数字步进器** + 数字加减步进器组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/numberStep.doc/CNumberStep.doc.md) + [组件代码 »](/src/components/forms/numberStep/CNumberStep.vue) + +- **CNumberValve 数字阀值** + 数字阀值设置组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/numberValve.doc/CNumberValve.doc.md) + [组件代码 »](/src/components/forms/numberValve/CNumberValve.vue) + +### 1.4 状态控件 + +- **CCheckbox 复选框** + 复选框组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/checkbox.doc/CCheckbox.doc.md) + [组件代码 »](/src/components/forms/checkbox/CCheckbox.vue) + +- **CSwitch 开关** + 开关切换组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/switch.doc/CSwitch.doc.md) + [组件代码 »](/src/components/forms/switch/CSwitch.vue) + +### 1.5 特殊表单组件 + +- **CForm 表单** + 表单容器组件,用于管理表单数据和验证 + [组件文档 »](/src/components/_cursor.ai/forms.doc/form.doc/CForm.doc.md) + [组件代码 »](/src/components/forms/form/CForm.vue) + +- **CUserSignature 用户签名** + 用户手写签名组件 + [组件文档 »](/src/components/_cursor.ai/forms.doc/userSignature.doc/CUserSignature.doc.md) + [组件代码 »](/src/components/forms/userSignature/CUserSignature.vue) + +## 2. 排版类组件 + +### 2.1 容器组件 + +- **CH5Page H5页面** + H5页面容器组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/h5Page.doc/CH5Page.doc.md) + [组件代码 »](/src/components/layout/h5Page/CH5Page.vue) + +- **CCard 卡片** + 卡片容器组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/card.doc/CCard.doc.md) + [组件代码 »](/src/components/layout/card/CCard.vue) + +- **CDrawer 抽屉** + 抽屉组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/drawer.doc/CDrawer.doc.md) + [组件代码 »](/src/components/layout/drawer/CDrawer.vue) + +### 2.2 导航组件 + +- **CNavCustomBar 自定义导航栏** + 自定义顶部导航栏组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/navCustomBar.doc/CNavCustomBar.doc.md) + [组件代码 »](/src/components/layout/navCustomBar/CNavCustomBar.vue) + +- **CHomeNav 首页导航** + 首页导航组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/homeNav.doc/CHomeNav.doc.md) + [组件代码 »](/src/components/layout/homeNav/CHomeNav.vue) + +- **CAnchor 锚点** + 页面锚点组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/anchor.doc/CAnchor.doc.md) + [组件代码 »](/src/components/layout/anchor/CAnchor.vue) + +### 2.3 交互提示组件 + +- **CAlert 弹窗** + 基于 Taro UI 的模态框封装的alert弹窗组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/alert.doc/CAlert.doc.md) + [组件代码 »](/src/components/layout/alert/CAlert.vue) + +- **CWaiting 等待提示** + 加载等待提示组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/waiting.doc/CWaiting.doc.md) + [组件代码 »](/src/components/layout/waiting/CWaiting.vue) + +### 2.4 数据展示组件 + +- **CNumerical 数值显示** + 数值显示组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/numerical.doc/CNumerical.doc.md) + [组件代码 »](/src/components/layout/numerical/CNumerical.vue) + +- **CDescription 描述列表** + 描述列表组件 + [组件文档 »](/src/components/_cursor.ai/layout.doc/description.doc/CDescription.doc.md) + [组件代码 »](/src/components/layout/description/CDescription.vue) + +## 3. 复杂插件类组件 + +### 3.1 数据可视化 + +- **CEcharts 图表** + 基于 Echarts 的图表组件 + [组件文档 »](/src/components/_cursor.ai/plugins.doc/echarts.doc/CEcharts.doc.md) + [组件代码 »](/src/components/plugins/echarts/CEcharts.vue) + +- **CQrcode 二维码** + 二维码生成组件 + [组件文档 »](/src/components/_cursor.ai/plugins.doc/qrcode.doc/CQrcode.doc.md) + [组件代码 »](/src/components/plugins/qrcode/CQrcode.vue) + +### 3.2 数据交互 + +- **CFilter 筛选器** + 数据筛选器组件 + [组件文档 »](/src/components/_cursor.ai/plugins.doc/filter.doc/CFilter.doc.md) + [组件代码 »](/src/components/plugins/filter/CFilter.vue) + +- **CInfiniteScroll 无限滚动** + 无限滚动加载组件 + [组件文档 »](/src/components/_cursor.ai/plugins.doc/infiniteScroll.doc/CInfiniteScroll.doc.md) + [组件代码 »](/src/components/plugins/infiniteScroll/CInfiniteScroll.vue) diff --git a/forms/checkbox/CCheckBox.vue b/forms/checkbox/CCheckBox.vue index ef4718d..aa9c01a 100644 --- a/forms/checkbox/CCheckBox.vue +++ b/forms/checkbox/CCheckBox.vue @@ -1,5 +1,8 @@ /** * CCheckBox + * 复选框组件,用于在表单中提供多选或单选功能 + * 支持两种显示模式:直接显示模式和弹窗选择模式 + * 支持两种选择类型:多选和单选 * @author Tevin */ @@ -142,9 +145,8 @@ return; } const selectedIndex = this.options.findIndex( - opt => opt.value === item + opt => opt.value === item, ); - console.log(item, selectedIndex); if (selectedIndex < 0) { return; } @@ -155,7 +157,7 @@ // 单选 else if (this.boxType === 'radio') { const next = evt[evt.length - 1]; - const selectedIndex = this.options.find(opt => opt.value === item); + const selectedIndex = this.options.findIndex(opt => opt.value === next); if (selectedIndex < 0) { this.itemRes.onChange(''); } else { diff --git a/forms/chinaArea/CChinaArea.vue b/forms/chinaArea/CChinaArea.vue index d16511d..faadcf5 100644 --- a/forms/chinaArea/CChinaArea.vue +++ b/forms/chinaArea/CChinaArea.vue @@ -1,5 +1,8 @@ /** * CChinaArea - 表单项,中国地址三级联动 + * 中国地区选择器组件,用于在表单中选择省市区地址 + * 内置完整的中国行政区划数据,支持多级联动选择 + * 支持自动通过地理定位获取省市区 * @author Tevin */ diff --git a/forms/datePicker/CDatePicker.vue b/forms/datePicker/CDatePicker.vue index 34bdb00..dd2c173 100644 --- a/forms/datePicker/CDatePicker.vue +++ b/forms/datePicker/CDatePicker.vue @@ -1,5 +1,8 @@ /** * CDatePicker - 选择日期范围操作 + * 日期选择组件,用于在表单中选择日期或日期范围 + * 支持三种选择模式:日期选择、日期时间选择和日期范围选择 + * 可限制日期选择范围,支持清除功能和只读模式 * @author Tevin */ diff --git a/forms/form/CForm.vue b/forms/form/CForm.vue index 944feef..7245951 100644 --- a/forms/form/CForm.vue +++ b/forms/form/CForm.vue @@ -1,5 +1,8 @@ /** * CForm - 表单套装组件,套件的主体 + * 表单容器组件,用于管理表单数据、处理表单验证和提交 + * 支持自动滚动到错误位置,提供表单项变化回调和表单完成回调 + * 提供手动提交、预验证和错误设置等功能 * @author Tevin */ @@ -142,7 +145,7 @@ Object.keys(errors).forEach(errorKey => { if (typeof this.validators[errorKey] !== 'undefined') { checklist.push( - this.validators[errorKey]('setError', errors[errorKey]) + this.validators[errorKey]('setError', errors[errorKey]), ); } else { unchecks.push(errors[errorKey]); diff --git a/forms/imagePicker/CImagePicker.vue b/forms/imagePicker/CImagePicker.vue index b5a5c3b..e5f4238 100644 --- a/forms/imagePicker/CImagePicker.vue +++ b/forms/imagePicker/CImagePicker.vue @@ -1,5 +1,8 @@ /** * CImagePicker + * 图片选择器组件,用于在表单中上传和管理图片 + * 支持单张和多张图片上传,并提供压缩、预览等功能 + * 可限制上传图片的数量和来源(相册/相机) * @author Tevin */ diff --git a/forms/input/CInput.vue b/forms/input/CInput.vue index ef6e805..d20866e 100644 --- a/forms/input/CInput.vue +++ b/forms/input/CInput.vue @@ -1,5 +1,7 @@ /** * CInput - 表单项,文本输入框 + * 用于在表单中收集用户的文本输入 + * 支持多种输入类型,可以设置占位提示文本,并且支持显示单位标识 * @author Tevin */ diff --git a/forms/numberStep/CNumberStep.vue b/forms/numberStep/CNumberStep.vue index b9ec157..b8b607c 100644 --- a/forms/numberStep/CNumberStep.vue +++ b/forms/numberStep/CNumberStep.vue @@ -1,5 +1,7 @@ /** * CNumberStep + * 数字步进器组件,用于在表单中输入数字并通过步进按钮增减数值 + * 支持设置数值范围、步长、奇偶修正和单位显示 * @author Tevin */ diff --git a/forms/numberValve/CNumberValve.vue b/forms/numberValve/CNumberValve.vue index f642855..777778b 100644 --- a/forms/numberValve/CNumberValve.vue +++ b/forms/numberValve/CNumberValve.vue @@ -1,5 +1,8 @@ /** * CNumberValve + * 数值滑块组件,用于在表单中通过滑块选择数值 + * 提供了滑动条和增减按钮两种方式来调整数值 + * 支持设置数值范围、步长和单位显示 * @author Tevin */ @@ -210,7 +213,7 @@ currentNext = Math.min(currentNext, this.range[1]); // 设置 const sliderLeft = Math.round( - ((currentNext - this.range[0]) / (this.range[1] - this.range[0])) * 100 + ((currentNext - this.range[0]) / (this.range[1] - this.range[0])) * 100, ); this.sliderLeft = sliderLeft; this.current = currentNext; diff --git a/forms/select/CSelect.vue b/forms/select/CSelect.vue index 5183bb9..aae68bb 100644 --- a/forms/select/CSelect.vue +++ b/forms/select/CSelect.vue @@ -1,5 +1,8 @@ /** * CSelect + * 下拉选择组件,用于在表单中提供选项选择功能 + * 支持两种选择模式:下拉选择模式和跳转页面选择模式 + * 可配置为只读模式,并支持自定义占位文本 * @author Tevin */ diff --git a/forms/switch/CSwitch.vue b/forms/switch/CSwitch.vue index 9d459d9..6917985 100644 --- a/forms/switch/CSwitch.vue +++ b/forms/switch/CSwitch.vue @@ -1,5 +1,8 @@ /** * CSwitch + * 开关组件,用于在表单中提供开关选择功能 + * 基于 AtSwitch 封装,支持只读模式 + * 能够显示必填和错误状态 * @author Tevin */ diff --git a/forms/textarea/CTextArea.vue b/forms/textarea/CTextArea.vue index b6aec4b..a72589e 100644 --- a/forms/textarea/CTextArea.vue +++ b/forms/textarea/CTextArea.vue @@ -1,5 +1,8 @@ /** * CTextArea + * 多行文本输入组件,用于在表单中收集用户的多行文本输入 + * 支持设置输入区域高度,可以通过行数或像素值来控制 + * 支持只读模式和自动增高功能 * @author Tevin */ diff --git a/forms/userSignature/CSignatureLayer.vue b/forms/userSignature/CSignatureLayer.vue index 0d9c676..b5d71d1 100644 --- a/forms/userSignature/CSignatureLayer.vue +++ b/forms/userSignature/CSignatureLayer.vue @@ -16,10 +16,10 @@ <canvas class="drawing" ref="drawing" - :canvasId="cavId" - :width="cavWidth" - :height="cavHeight" + type="2d" + :id="cavId" :disableScroll="true" + v-if="cavShow" @touchstart="evt => handleWriteStart(evt)" @touchmove="evt => handleWriteMove(evt)" @touchend="evt => handleWriteEnd(evt)" @@ -45,8 +45,8 @@ import Taro from '@tarojs/taro'; import { $ } from '@tarojs/extend'; import { AtFloatLayout, AtButton } from 'taro-ui-vue'; -import './cSignatureLayer.scss'; import { Tools } from '@components/common/Tools'; +import './cSignatureLayer.scss'; export default { name: 'CSignatureLayer', @@ -59,8 +59,7 @@ return { cavId: 'signCanvas-' + Date.now() + '-' + parseInt(Math.random() * 10000), layerOpened: false, - cavWidth: 0, - cavHeight: 0, + cavShow: false, curPoint: {}, lastPoint: {}, curLine: [], @@ -70,40 +69,63 @@ chirography: [], // 初始画圆的半径 radius: 1, + canvasContext: null, }; }, methods: { _initDraw() { - this.canvasContext = Taro.createCanvasContext(this.cavId, this); - const $container = $(this.$refs.drawing).parent(); - setTimeout(() => { - $container.width().then(w => (this.cavWidth = w)); - $container.height().then(h => (this.cavHeight = h)); - }, 0); + const query = Taro.createSelectorQuery(); + query + .select(`#${this.cavId}`) + .fields({ node: true, size: true }) + .exec(res => { + // Canvas 对象 + const canvas = res[0].node; + // Canvas 画布的实际绘制宽高 + const renderWidth = res[0].width; + const renderHeight = res[0].height; + // Canvas 绘制上下文 + this.canvasContext = canvas.getContext('2d'); + this.canvas = canvas; + // 初始化画布大小 + const dpr = Taro.getSystemInfoSync().pixelRatio; + canvas.width = renderWidth * dpr; + canvas.height = renderHeight * dpr; + this.canvasContext.scale(dpr, dpr); + // 初始化变量 + this.handleRestDraw(); + }); }, $onDraw(callback) { - this._initDraw(); this.layerOpened = true; this._callback = callback; - setTimeout(() => { - this.handleRestDraw(); - }, 10); + this.$nextTick(() => { + setTimeout(() => { + this.cavShow = true; + setTimeout(() => { + this._initDraw(); + }, 100); + }, 300); + }); }, handleRestDraw() { this.firstTouch = true; this.curLine = []; this.chirography = []; - this.canvasContext.clearRect(0, 0, this.cavWidth, this.cavHeight); - this.canvasContext.draw(); + this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height); }, handleClose() { this.layerOpened = false; + this.cavShow = false; + setTimeout(() => { + this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height); + }, 100); }, handleWriteStart(evt) { - if (evt.type != 'touchstart') { - return false; + if (evt.type !== 'touchstart' || !this.canvasContext) { + return; } - this.canvasContext.setFillStyle('#1A1A1A'); + this.canvasContext.fillStyle = '#111111'; const point = { x: evt.touches[0].x, y: evt.touches[0].y, @@ -126,8 +148,8 @@ this._pointToLine(this.curLine); }, handleWriteMove(evt) { - if (evt.type != 'touchmove') { - return false; + if (evt.type !== 'touchmove') { + return; } if (evt.cancelable) { // 判断默认行为是否已经被禁用 @@ -151,8 +173,8 @@ this._pointToLine(this.curLine); }, handleWriteEnd(evt) { - if (evt.type != 'touchend') { - return 0; + if (evt.type !== 'touchend') { + return; } const point = { x: evt.changedTouches[0].x, @@ -181,14 +203,14 @@ if (point.x > this.cutArea.right) { this.cutArea.right = point.x; } - if (this.cavWidth - point.x <= 0) { - this.cutArea.right = this.cavWidth; + if (this.canvas.width - point.x <= 0) { + this.cutArea.right = this.canvas.width; } if (point.y > this.cutArea.bottom) { this.cutArea.bottom = point.y; } - if (this.cavHeight - point.y <= 0) { - this.cutArea.bottom = this.cavHeight; + if (this.canvas.height - point.y <= 0) { + this.cutArea.bottom = this.canvas.height; } if (point.x < this.cutArea.left) { this.cutArea.left = point.x; @@ -279,27 +301,28 @@ r2 = (line[1].r + line[0].r) / 2; } let n = 5; - let point = []; + let points = []; for (let i = 0; i < n; i++) { let t = i / (n - 1); let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2; let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2; let r = lastRadius + ((this.radius - lastRadius) / n) * i; - point.push({ x: x, y: y, r: r }); - if (point.length == 3) { - let a = this._ctaCalc( - point[0].x, - point[0].y, - point[0].r, - point[1].x, - point[1].y, - point[1].r, - point[2].x, - point[2].y, - point[2].r, + points.push({ x: x, y: y, r: r }); + if (points.length == 3) { + this._bethelDraw( + this._ctaCalc( + points[0].x, + points[0].y, + points[0].r, + points[1].x, + points[1].y, + points[1].r, + points[2].x, + points[2].y, + points[2].r, + ), ); - this._bethelDraw(a, true); - point = [{ x: x, y: y, r: r }]; + points = [{ x: x, y: y, r: r }]; } } this.curLine = line; @@ -382,64 +405,75 @@ } return a; }, - _bethelDraw(point, isFill) { + _bethelDraw(points) { const ctx = this.canvasContext; ctx.beginPath(); - ctx.moveTo(point[0].mx, point[0].my); - for (let i = 1; i < point.length; i++) { + ctx.moveTo(points[0].mx, points[0].my); + for (let i = 1; i < points.length; i++) { ctx.bezierCurveTo( - point[i].c1x, - point[i].c1y, - point[i].c2x, - point[i].c2y, - point[i].ex, - point[i].ey, + points[i].c1x, + points[i].c1y, + points[i].c2x, + points[i].c2y, + points[i].ex, + points[i].ey, ); } + ctx.closePath(); ctx.stroke(); - if (isFill !== undefined) { - // 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 - ctx.fill(); - } - ctx.draw(true); + ctx.fill(); }, _brushingGround(callback) { Taro.canvasToTempFilePath({ - canvasId: this.cavId, + canvas: this.canvas, x: 0, y: 0, - width: Math.ceil(this.cavWidth), - height: Math.ceil(this.cavHeight), - destWidth: Math.ceil(this.cavWidth), - destHeight: Math.ceil(this.cavHeight), + width: Math.ceil(this.canvas.width), + height: Math.ceil(this.canvas.height), + destWidth: Math.ceil(this.canvas.width), + destHeight: Math.ceil(this.canvas.height), quality: 1, fileType: 'png', success: res => { const ctx = this.canvasContext; - ctx.setFillStyle('#ffffff'); - ctx.fillRect(0, 0, this.cavWidth, this.cavHeight); - ctx.drawImage(res.tempFilePath, 0, 0); - ctx.draw(false, () => { + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + // 重新绘制 + const image = this.canvas.createImage(); + image.onload = () => { + const dpr = Taro.getSystemInfoSync().pixelRatio; + ctx.drawImage( + image, + 0, + 0, + this.canvas.width / dpr, + this.canvas.height / dpr, + ); callback(); - }); + }; + image.src = res.tempFilePath; }, }); }, handleSaveDraw() { if (this.firstTouch) { Tools.toast('请书写签名!'); + return; } this._brushingGround(() => { const delta = 20; const clipArea = { x: 0, y: 0, w: 0, h: 0 }; clipArea.x = Math.max(this.cutArea.left - delta, 0); clipArea.y = Math.max(this.cutArea.top - delta, 0); - const realRight = Math.min(this.cutArea.right + delta, this.cavWidth); - const realBottom = Math.min(this.cutArea.bottom + delta, this.cavHeight); + const realRight = Math.min(this.cutArea.right + delta, this.canvas.width); + const realBottom = Math.min( + this.cutArea.bottom + delta, + this.canvas.height, + ); clipArea.w = realRight - clipArea.x; clipArea.h = realBottom - clipArea.y; Taro.canvasToTempFilePath({ - canvasId: this.cavId, + canvas: this.canvas, x: clipArea.x, y: clipArea.y, width: clipArea.w, @@ -456,10 +490,6 @@ }); }, }, - mounted() { - this.$nextTick(() => { - this._initDraw(); - }); - }, + mounted() {}, }; </script> \ No newline at end of file diff --git a/forms/userSignature/cSignatureLayer.scss b/forms/userSignature/cSignatureLayer.scss index 3a1d751..e64b271 100644 --- a/forms/userSignature/cSignatureLayer.scss +++ b/forms/userSignature/cSignatureLayer.scss @@ -19,7 +19,7 @@ } } .c-signature-layer-btns { - @include position(absolute, n 0 0 n); + @include position(absolute, n 0 0 n, 3); width: 100%; height: 92px; font-size: 0; @@ -32,7 +32,7 @@ position: relative; height: calc(100% - 92px); .size-box-top { - @include position(absolute, 3% 3% n n); + @include position(absolute, 3% 3% n n, 2); width: 94%; height: 36px; pointer-events: none; @@ -56,7 +56,7 @@ } } .size-box-bottom { - @include position(absolute, n 3% 3% n); + @include position(absolute, n 3% 3% n, 2); width: 94%; height: 36px; pointer-events: none; -- Gitblit v1.9.1