first commit
This commit is contained in:
151
docs/uView使用指南.md
Normal file
151
docs/uView使用指南.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# uView-Plus 使用指南
|
||||
|
||||
## 已完成集成步骤
|
||||
|
||||
1. ✅ 安装依赖:`npm install uview-plus --save`
|
||||
2. ✅ 在 `main.js` 中引入并注册
|
||||
3. ✅ 创建 `src/uni.scss` 并引入 uView 主题变量
|
||||
4. ✅ 在 `vite.config.js` 中配置 SCSS 预处理器
|
||||
5. ✅ 在 `pages.json` 中配置 easycom 自动引入
|
||||
|
||||
## 配置说明
|
||||
|
||||
### uni.scss
|
||||
```scss
|
||||
/* uView-Plus 主题变量配置 */
|
||||
@import 'uview-plus/theme.scss';
|
||||
```
|
||||
|
||||
### vite.config.js
|
||||
```javascript
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: '@import "@/uni.scss"; @import "@/styles/variables.scss"; @import "@/styles/mixins.scss";',
|
||||
api: 'modern-compiler',
|
||||
silenceDeprecations: ['legacy-js-api', 'import']
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### pages.json
|
||||
```json
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常用组件示例
|
||||
|
||||
### 1. 按钮 (u-button)
|
||||
```vue
|
||||
<u-button type="primary" text="主要按钮"></u-button>
|
||||
<u-button type="success" text="成功按钮"></u-button>
|
||||
<u-button type="warning" text="警告按钮"></u-button>
|
||||
```
|
||||
|
||||
### 2. 加载动画 (u-loading-icon)
|
||||
```vue
|
||||
<u-loading-icon mode="spinner"></u-loading-icon>
|
||||
<u-loading-icon mode="circle"></u-loading-icon>
|
||||
```
|
||||
|
||||
### 3. 弹窗 (u-popup)
|
||||
```vue
|
||||
<u-popup v-model:show="show" mode="bottom" :round="10">
|
||||
<view class="content">弹窗内容</view>
|
||||
</u-popup>
|
||||
```
|
||||
|
||||
### 4. 输入框 (u-input)
|
||||
```vue
|
||||
<u-input v-model="value" placeholder="请输入内容"></u-input>
|
||||
```
|
||||
|
||||
### 5. 表单 (u-form)
|
||||
```vue
|
||||
<u-form :model="form" ref="formRef">
|
||||
<u-form-item label="姓名" prop="name">
|
||||
<u-input v-model="form.name" placeholder="请输入姓名"></u-input>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
```
|
||||
|
||||
### 6. Toast 提示
|
||||
```javascript
|
||||
// 在组件中使用
|
||||
this.$u.toast('提示内容')
|
||||
```
|
||||
|
||||
### 7. 日历 (u-calendar)
|
||||
```vue
|
||||
<u-calendar v-model:show="show" @confirm="confirm"></u-calendar>
|
||||
```
|
||||
|
||||
### 8. 时间选择器 (u-datetime-picker)
|
||||
```vue
|
||||
<u-datetime-picker
|
||||
v-model:show="show"
|
||||
v-model="currentDate"
|
||||
mode="datetime"
|
||||
@confirm="confirm"
|
||||
></u-datetime-picker>
|
||||
```
|
||||
|
||||
## 可以优化的现有组件
|
||||
|
||||
### LoadingOverlay.vue
|
||||
可以使用 `u-loading-icon` 替代自定义加载动画:
|
||||
```vue
|
||||
<template>
|
||||
<u-overlay :show="visible">
|
||||
<view class="loading-box">
|
||||
<u-loading-icon mode="spinner" size="40" color="#ffffff"></u-loading-icon>
|
||||
</view>
|
||||
</u-overlay>
|
||||
</template>
|
||||
```
|
||||
|
||||
### BookingPopup.vue
|
||||
可以使用 `u-popup` 替代自定义弹窗:
|
||||
```vue
|
||||
<u-popup
|
||||
v-model:show="visible"
|
||||
mode="bottom"
|
||||
:round="16"
|
||||
:closeable="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 弹窗内容 -->
|
||||
</u-popup>
|
||||
```
|
||||
|
||||
可以使用 `u-form` 和 `u-form-item` 优化表单:
|
||||
```vue
|
||||
<u-form :model="formData" ref="formRef" :rules="rules">
|
||||
<u-form-item label="姓名" prop="name" required>
|
||||
<u-input v-model="formData.name" placeholder="请填写您的姓名"></u-input>
|
||||
</u-form-item>
|
||||
<u-form-item label="电话" prop="phone" required>
|
||||
<u-input v-model="formData.phone" type="number" maxlength="11"></u-input>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
```
|
||||
|
||||
可以使用 `u-calendar` 替代自定义日期选择器
|
||||
|
||||
## 文档链接
|
||||
|
||||
- 官方文档:https://uview-plus.jiangruyi.com/
|
||||
- 组件列表:https://uview-plus.jiangruyi.com/components/intro.html
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. uview-plus 是专为 Vue3 + uni-app 设计的组件库
|
||||
2. 组件通过 easycom 自动引入,无需手动 import
|
||||
3. 所有组件以 `u-` 开头
|
||||
4. 支持主题定制和暗黑模式
|
||||
403
docs/多端适配方案.md
Normal file
403
docs/多端适配方案.md
Normal file
@@ -0,0 +1,403 @@
|
||||
|
||||
|
||||
|
||||
# 多端适配方案
|
||||
|
||||
## 一、平台差异处理
|
||||
|
||||
### 1.1 条件编译
|
||||
使用 UniApp 的条件编译处理不同平台的差异:
|
||||
|
||||
```javascript
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序特有代码
|
||||
wx.login()
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5特有代码
|
||||
window.location.href = '/login'
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// App特有代码
|
||||
plus.runtime.restart()
|
||||
// #endif
|
||||
```
|
||||
|
||||
### 1.2 平台判断工具
|
||||
使用封装的平台判断工具:
|
||||
|
||||
```javascript
|
||||
import { getPlatform, isMiniProgram, isH5, isApp } from '@/utils/platform'
|
||||
|
||||
if (isMiniProgram()) {
|
||||
// 小程序端逻辑
|
||||
} else if (isH5()) {
|
||||
// H5端逻辑
|
||||
} else if (isApp()) {
|
||||
// App端逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 二、API 适配
|
||||
|
||||
### 2.1 统一 API 封装
|
||||
对不同平台的 API 进行统一封装:
|
||||
|
||||
```javascript
|
||||
// 跨端导航
|
||||
export const navigateTo = (url, params = {}) => {
|
||||
const query = Object.keys(params)
|
||||
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
||||
.join('&')
|
||||
const fullUrl = query ? `${url}?${query}` : url
|
||||
|
||||
uni.navigateTo({ url: fullUrl })
|
||||
}
|
||||
|
||||
// 跨端存储
|
||||
export const setStorage = (key, value) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.setStorageSync(key, value)
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.storage.setItem(key, JSON.stringify(value))
|
||||
// #endif
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 第三方登录适配
|
||||
不同平台的第三方登录实现:
|
||||
|
||||
```javascript
|
||||
// 微信登录
|
||||
export const wechatLogin = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
resolve(res.code)
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5微信授权登录
|
||||
const appId = 'YOUR_APP_ID'
|
||||
const redirectUri = encodeURIComponent(window.location.href)
|
||||
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo#wechat_redirect`
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 三、样式适配
|
||||
|
||||
### 3.1 尺寸单位适配
|
||||
- **小程序端**:使用 `rpx`(750rpx = 屏幕宽度)
|
||||
- **H5端**:使用 `rem` 或 `vw`
|
||||
- **App端**:使用 `rpx` 或 `upx`
|
||||
|
||||
```scss
|
||||
// 统一使用 rpx,UniApp 会自动转换
|
||||
.container {
|
||||
width: 750rpx;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 安全区域适配
|
||||
处理刘海屏、底部安全区域:
|
||||
|
||||
```scss
|
||||
.page {
|
||||
// 顶部安全区域
|
||||
padding-top: constant(safe-area-inset-top);
|
||||
padding-top: env(safe-area-inset-top);
|
||||
|
||||
// 底部安全区域
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 状态栏高度适配
|
||||
```javascript
|
||||
// 获取状态栏高度
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const statusBarHeight = systemInfo.statusBarHeight
|
||||
|
||||
// 在样式中使用
|
||||
const navBarHeight = statusBarHeight + 44 // 44为导航栏高度
|
||||
```
|
||||
|
||||
## 四、功能适配
|
||||
|
||||
### 4.1 分享功能
|
||||
```javascript
|
||||
// 小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '分享标题',
|
||||
path: '/pages/index/index',
|
||||
imageUrl: '/static/share.png'
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// H5分享
|
||||
// #ifdef H5
|
||||
const shareToWechat = () => {
|
||||
// 调用微信 JS-SDK
|
||||
wx.updateAppMessageShareData({
|
||||
title: '分享标题',
|
||||
desc: '分享描述',
|
||||
link: window.location.href,
|
||||
imgUrl: 'https://example.com/share.png'
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
```
|
||||
|
||||
### 4.2 支付功能
|
||||
```javascript
|
||||
// 统一支付接口
|
||||
export const pay = (orderInfo) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.requestPayment({
|
||||
timeStamp: orderInfo.timeStamp,
|
||||
nonceStr: orderInfo.nonceStr,
|
||||
package: orderInfo.package,
|
||||
signType: 'MD5',
|
||||
paySign: orderInfo.paySign,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5微信支付
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', orderInfo, (res) => {
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
resolve(res)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// App支付
|
||||
plus.payment.request('wxpay', orderInfo, resolve, reject)
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 定位功能
|
||||
```javascript
|
||||
// 获取位置
|
||||
export const getLocation = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success: (res) => {
|
||||
resolve({
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
// #ifdef H5
|
||||
// H5使用浏览器定位API
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
resolve({
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude
|
||||
})
|
||||
},
|
||||
reject
|
||||
)
|
||||
} else {
|
||||
reject(new Error('浏览器不支持定位'))
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
reject(err)
|
||||
// #endif
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 五、性能适配
|
||||
|
||||
### 5.1 小程序分包
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/user/index"
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "subPackages/order",
|
||||
"pages": [
|
||||
"list/index",
|
||||
"detail/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "subPackages/product",
|
||||
"pages": [
|
||||
"list/index",
|
||||
"detail/index"
|
||||
]
|
||||
}
|
||||
],
|
||||
"preloadRule": {
|
||||
"pages/index/index": {
|
||||
"network": "all",
|
||||
"packages": ["subPackages/order"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 图片懒加载
|
||||
```vue
|
||||
<template>
|
||||
<!-- 小程序端使用 lazy-load -->
|
||||
<image
|
||||
:src="imageSrc"
|
||||
lazy-load
|
||||
mode="aspectFill"
|
||||
/>
|
||||
|
||||
<!-- H5端使用 Intersection Observer -->
|
||||
<img
|
||||
v-lazy="imageSrc"
|
||||
alt="图片"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 5.3 长列表优化
|
||||
```vue
|
||||
<template>
|
||||
<!-- 使用虚拟列表 -->
|
||||
<recycle-list :list="dataList">
|
||||
<template v-slot="{ item }">
|
||||
<view class="list-item">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</template>
|
||||
</recycle-list>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 六、兼容性处理
|
||||
|
||||
### 6.1 API 兼容性检查
|
||||
```javascript
|
||||
// 检查API是否支持
|
||||
if (uni.canIUse('getSystemInfoSync')) {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
}
|
||||
|
||||
// 版本号比较
|
||||
const compareVersion = (v1, v2) => {
|
||||
const arr1 = v1.split('.')
|
||||
const arr2 = v2.split('.')
|
||||
const len = Math.max(arr1.length, arr2.length)
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(arr1[i] || 0)
|
||||
const num2 = parseInt(arr2[i] || 0)
|
||||
|
||||
if (num1 > num2) return 1
|
||||
if (num1 < num2) return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
if (compareVersion(systemInfo.SDKVersion, '2.10.0') >= 0) {
|
||||
// 支持新API
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 样式兼容性
|
||||
```scss
|
||||
// 使用 CSS 变量实现主题切换
|
||||
:root {
|
||||
--primary-color: #1890ff;
|
||||
--text-color: #333;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
// 使用 autoprefixer 自动添加前缀
|
||||
.flex-container {
|
||||
display: flex;
|
||||
// 自动添加 -webkit-flex
|
||||
}
|
||||
```
|
||||
|
||||
## 七、调试技巧
|
||||
|
||||
### 7.1 多端调试
|
||||
- **微信小程序**:使用微信开发者工具
|
||||
- **H5**:使用浏览器开发者工具
|
||||
- **App**:使用 HBuilderX 真机调试
|
||||
|
||||
### 7.2 日志输出
|
||||
```javascript
|
||||
// 开发环境输出日志
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('调试信息:', data)
|
||||
}
|
||||
|
||||
// 使用条件编译输出平台信息
|
||||
// #ifdef MP-WEIXIN
|
||||
console.log('当前平台:微信小程序')
|
||||
// #endif
|
||||
```
|
||||
|
||||
### 7.3 错误监控
|
||||
```javascript
|
||||
// 全局错误捕获
|
||||
uni.onError((error) => {
|
||||
console.error('全局错误:', error)
|
||||
// 上报到错误监控平台
|
||||
})
|
||||
|
||||
// 网络错误监控
|
||||
uni.onNetworkStatusChange((res) => {
|
||||
if (!res.isConnected) {
|
||||
uni.showToast({
|
||||
title: '网络已断开',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
300
docs/开发规范.md
Normal file
300
docs/开发规范.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# 开发规范文档
|
||||
|
||||
## 一、命名规范
|
||||
|
||||
### 1.1 文件命名
|
||||
- **页面文件**:使用小写字母,多个单词用短横线连接,如 `user-profile.vue`
|
||||
- **组件文件**:使用大驼峰命名,如 `UserCard.vue`、`LoadingView.vue`
|
||||
- **工具类文件**:使用小写字母,多个单词用短横线连接,如 `request.js`、`storage.js`
|
||||
- **常量文件**:使用小写字母,如 `constants.js`、`env.js`
|
||||
|
||||
### 1.2 变量命名
|
||||
- **普通变量**:使用小驼峰命名,如 `userName`、`userInfo`
|
||||
- **常量**:使用全大写字母,单词间用下划线连接,如 `API_BASE_URL`、`MAX_RETRY_COUNT`
|
||||
- **私有变量**:以下划线开头,如 `_privateMethod`
|
||||
- **布尔值**:以 `is`、`has`、`can` 等开头,如 `isLogin`、`hasPermission`
|
||||
|
||||
### 1.3 函数命名
|
||||
- **普通函数**:使用小驼峰命名,动词开头,如 `getUserInfo`、`handleClick`
|
||||
- **事件处理函数**:以 `handle` 或 `on` 开头,如 `handleSubmit`、`onLoad`
|
||||
- **工具函数**:使用动词开头,如 `formatDate`、`validatePhone`
|
||||
|
||||
### 1.4 组件命名
|
||||
- **全局组件**:使用大驼峰命名,至少两个单词,如 `LoadingView`、`EmptyView`
|
||||
- **页面组件**:使用大驼峰命名,如 `LoginPage`、`UserProfile`
|
||||
- **业务组件**:使用大驼峰命名,体现业务含义,如 `UserCard`、`OrderList`
|
||||
|
||||
## 二、代码注释规范
|
||||
|
||||
### 2.1 文件注释
|
||||
每个文件开头必须包含文件说明注释:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 用户相关接口
|
||||
* @author 张三
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
```
|
||||
|
||||
### 2.2 函数注释
|
||||
关键函数必须添加注释,说明功能、参数、返回值:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {object} data 登录数据
|
||||
* @param {string} data.phone 手机号
|
||||
* @param {string} data.password 密码
|
||||
* @returns {Promise<object>} 登录结果
|
||||
*/
|
||||
export const login = (data) => {
|
||||
return request.post('/auth/login', data)
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 复杂逻辑注释
|
||||
对于复杂的业务逻辑,必须添加行内注释:
|
||||
|
||||
```javascript
|
||||
// 检查Token是否过期
|
||||
if (tokenExpireTime < Date.now()) {
|
||||
// Token已过期,刷新Token
|
||||
await refreshToken()
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 TODO注释
|
||||
待完成的功能使用 TODO 标记:
|
||||
|
||||
```javascript
|
||||
// TODO: 添加图片压缩功能
|
||||
// FIXME: 修复在iOS端的兼容性问题
|
||||
```
|
||||
|
||||
## 三、代码风格规范
|
||||
|
||||
### 3.1 缩进与空格
|
||||
- 使用 2 个空格缩进
|
||||
- 运算符前后加空格
|
||||
- 逗号后加空格
|
||||
- 对象属性冒号后加空格
|
||||
|
||||
### 3.2 引号使用
|
||||
- JavaScript 统一使用单引号
|
||||
- HTML 属性统一使用双引号
|
||||
|
||||
### 3.3 分号使用
|
||||
- 语句结尾不使用分号(根据 Prettier 配置)
|
||||
|
||||
### 3.4 代码长度
|
||||
- 单行代码不超过 100 个字符
|
||||
- 函数代码行数不超过 50 行
|
||||
- 文件代码行数不超过 500 行
|
||||
|
||||
## 四、Vue 组件规范
|
||||
|
||||
### 4.1 组件结构顺序
|
||||
```vue
|
||||
<template>
|
||||
<!-- 模板内容 -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 1. 导入
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 2. 组件定义
|
||||
export default {
|
||||
name: 'ComponentName',
|
||||
components: {},
|
||||
props: {},
|
||||
emits: [],
|
||||
setup() {
|
||||
// 3. 响应式数据
|
||||
// 4. 计算属性
|
||||
// 5. 方法
|
||||
// 6. 生命周期
|
||||
// 7. 返回
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 样式内容 */
|
||||
</style>
|
||||
```
|
||||
|
||||
### 4.2 Props 定义
|
||||
Props 必须定义类型、默认值和校验:
|
||||
|
||||
```javascript
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: (value) => value >= 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 事件命名
|
||||
- 使用短横线命名:`@update-value`
|
||||
- 使用动词开头:`@click`、`@change`、`@submit`
|
||||
|
||||
## 五、Git 提交规范
|
||||
|
||||
### 5.1 提交信息格式
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
### 5.2 Type 类型
|
||||
- `feat`: 新功能
|
||||
- `fix`: 修复 Bug
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整
|
||||
- `refactor`: 重构代码
|
||||
- `perf`: 性能优化
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建/工具链相关
|
||||
|
||||
### 5.3 提交示例
|
||||
```
|
||||
feat(login): 添加手机号验证码登录功能
|
||||
|
||||
- 实现验证码发送接口
|
||||
- 添加验证码输入组件
|
||||
- 完成登录逻辑
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
## 六、API 接口规范
|
||||
|
||||
### 6.1 接口文件组织
|
||||
按业务模块划分接口文件:
|
||||
```
|
||||
api/
|
||||
├── modules/
|
||||
│ ├── auth.js # 认证相关
|
||||
│ ├── user.js # 用户相关
|
||||
│ └── order.js # 订单相关
|
||||
└── index.js # 统一导出
|
||||
```
|
||||
|
||||
### 6.2 接口命名
|
||||
- 使用动词开头:`getUserInfo`、`updateUserInfo`
|
||||
- RESTful 风格:`getList`、`getDetail`、`create`、`update`、`delete`
|
||||
|
||||
### 6.3 接口注释
|
||||
每个接口必须添加注释说明:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param {string} userId 用户ID
|
||||
* @returns {Promise<object>} 用户信息
|
||||
*/
|
||||
export const getUserInfo = (userId) => {
|
||||
return request.get(`/user/${userId}`)
|
||||
}
|
||||
```
|
||||
|
||||
## 七、样式规范
|
||||
|
||||
### 7.1 选择器命名
|
||||
- 使用短横线命名:`.user-card`、`.login-form`
|
||||
- 避免使用 ID 选择器
|
||||
- 避免使用标签选择器
|
||||
|
||||
### 7.2 样式组织
|
||||
```scss
|
||||
.component-name {
|
||||
// 1. 定位属性
|
||||
position: relative;
|
||||
|
||||
// 2. 盒模型属性
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
|
||||
// 3. 文本属性
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
|
||||
// 4. 其他属性
|
||||
background-color: #fff;
|
||||
|
||||
// 5. 嵌套选择器
|
||||
.child-element {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 单位使用
|
||||
- 小程序端:使用 `rpx`
|
||||
- H5 端:使用 `rem` 或 `vw`
|
||||
- 固定尺寸:使用 `px`
|
||||
|
||||
## 八、性能优化规范
|
||||
|
||||
### 8.1 图片优化
|
||||
- 使用合适的图片格式(WebP、JPEG、PNG)
|
||||
- 压缩图片大小
|
||||
- 使用图片懒加载
|
||||
- 使用雪碧图合并小图标
|
||||
|
||||
### 8.2 代码优化
|
||||
- 避免在循环中进行复杂计算
|
||||
- 使用防抖和节流
|
||||
- 合理使用计算属性和侦听器
|
||||
- 及时清理定时器和事件监听
|
||||
|
||||
### 8.3 网络优化
|
||||
- 合并接口请求
|
||||
- 使用请求缓存
|
||||
- 实现请求重试机制
|
||||
- 避免重复请求
|
||||
|
||||
## 九、安全规范
|
||||
|
||||
### 9.1 数据安全
|
||||
- 敏感信息加密存储
|
||||
- Token 定期刷新
|
||||
- 防止 XSS 攻击
|
||||
- 防止 CSRF 攻击
|
||||
|
||||
### 9.2 权限控制
|
||||
- 实现路由权限控制
|
||||
- 实现接口权限控制
|
||||
- 实现按钮级权限控制
|
||||
|
||||
## 十、测试规范
|
||||
|
||||
### 10.1 单元测试
|
||||
- 工具函数必须编写单元测试
|
||||
- 测试覆盖率不低于 80%
|
||||
- 使用 Jest 测试框架
|
||||
|
||||
### 10.2 集成测试
|
||||
- 关键业务流程必须编写集成测试
|
||||
- 使用 uni-automator 进行自动化测试
|
||||
|
||||
### 10.3 测试命名
|
||||
```javascript
|
||||
describe('工具函数测试', () => {
|
||||
test('应该正确验证手机号', () => {
|
||||
expect(isPhone('13800138000')).toBe(true)
|
||||
})
|
||||
})
|
||||
```
|
||||
531
docs/性能优化清单.md
Normal file
531
docs/性能优化清单.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# 性能优化清单
|
||||
|
||||
## 一、首屏加载优化
|
||||
|
||||
### 1.1 代码分割
|
||||
```javascript
|
||||
// 路由懒加载
|
||||
const UserProfile = () => import('@/pages/user/profile')
|
||||
|
||||
// 组件懒加载
|
||||
components: {
|
||||
HeavyComponent: () => import('@/components/HeavyComponent.vue')
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 小程序分包加载
|
||||
```json
|
||||
{
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "subPackages/order",
|
||||
"pages": ["list/index", "detail/index"]
|
||||
}
|
||||
],
|
||||
"preloadRule": {
|
||||
"pages/index/index": {
|
||||
"network": "all",
|
||||
"packages": ["subPackages/order"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 资源预加载
|
||||
```javascript
|
||||
// 预加载关键资源
|
||||
onLoad() {
|
||||
// 预加载下一页数据
|
||||
this.preloadNextPage()
|
||||
|
||||
// 预加载图片
|
||||
uni.preloadImage({
|
||||
src: '/static/images/banner.jpg'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 骨架屏
|
||||
```vue
|
||||
<template>
|
||||
<view v-if="loading" class="skeleton">
|
||||
<view class="skeleton-avatar" />
|
||||
<view class="skeleton-line" />
|
||||
<view class="skeleton-line" />
|
||||
</view>
|
||||
<view v-else class="content">
|
||||
<!-- 实际内容 -->
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 二、渲染性能优化
|
||||
|
||||
### 2.1 虚拟列表
|
||||
对于长列表使用虚拟滚动:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<recycle-list
|
||||
:list="dataList"
|
||||
:item-height="100"
|
||||
>
|
||||
<template v-slot="{ item }">
|
||||
<view class="list-item">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</template>
|
||||
</recycle-list>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2.2 图片懒加载
|
||||
```vue
|
||||
<template>
|
||||
<!-- 小程序端 -->
|
||||
<image
|
||||
:src="imageSrc"
|
||||
lazy-load
|
||||
mode="aspectFill"
|
||||
/>
|
||||
|
||||
<!-- H5端使用 Intersection Observer -->
|
||||
<img
|
||||
v-lazy="imageSrc"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2.3 防抖与节流
|
||||
```javascript
|
||||
import { debounce, throttle } from '@/utils/performance'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
// 搜索输入防抖
|
||||
handleSearch: debounce(function(keyword) {
|
||||
this.search(keyword)
|
||||
}, 300),
|
||||
|
||||
// 滚动事件节流
|
||||
handleScroll: throttle(function(e) {
|
||||
this.updateScrollPosition(e)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 条件渲染优化
|
||||
```vue
|
||||
<template>
|
||||
<!-- 使用 v-show 代替 v-if(频繁切换) -->
|
||||
<view v-show="isVisible">内容</view>
|
||||
|
||||
<!-- 使用 v-if 代替 v-show(不常切换) -->
|
||||
<view v-if="hasPermission">内容</view>
|
||||
|
||||
<!-- 使用 v-once 渲染静态内容 -->
|
||||
<view v-once>{{ staticContent }}</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 三、网络请求优化
|
||||
|
||||
### 3.1 请求合并
|
||||
```javascript
|
||||
// 合并多个接口请求
|
||||
async loadPageData() {
|
||||
const [bannerData, menuData, contentData] = await Promise.all([
|
||||
api.getBanner(),
|
||||
api.getMenu(),
|
||||
api.getContent()
|
||||
])
|
||||
|
||||
this.bannerList = bannerData
|
||||
this.menuList = menuData
|
||||
this.contentList = contentData
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 请求缓存
|
||||
```javascript
|
||||
class RequestCache {
|
||||
constructor() {
|
||||
this.cache = new Map()
|
||||
this.cacheTime = 5 * 60 * 1000 // 5分钟
|
||||
}
|
||||
|
||||
get(key) {
|
||||
const item = this.cache.get(key)
|
||||
if (!item) return null
|
||||
|
||||
if (Date.now() - item.timestamp > this.cacheTime) {
|
||||
this.cache.delete(key)
|
||||
return null
|
||||
}
|
||||
|
||||
return item.data
|
||||
}
|
||||
|
||||
set(key, data) {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 使用缓存
|
||||
const cache = new RequestCache()
|
||||
|
||||
export const getUserInfo = async (userId) => {
|
||||
const cacheKey = `user_${userId}`
|
||||
const cached = cache.get(cacheKey)
|
||||
|
||||
if (cached) return cached
|
||||
|
||||
const data = await request.get(`/user/${userId}`)
|
||||
cache.set(cacheKey, data)
|
||||
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 请求重试
|
||||
```javascript
|
||||
async function requestWithRetry(config, retryCount = 0) {
|
||||
try {
|
||||
return await request(config)
|
||||
} catch (error) {
|
||||
if (retryCount < 3) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
return requestWithRetry(config, retryCount + 1)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 请求取消
|
||||
```javascript
|
||||
class RequestManager {
|
||||
constructor() {
|
||||
this.pendingRequests = new Map()
|
||||
}
|
||||
|
||||
addRequest(key, requestTask) {
|
||||
// 取消之前的相同请求
|
||||
if (this.pendingRequests.has(key)) {
|
||||
this.pendingRequests.get(key).abort()
|
||||
}
|
||||
this.pendingRequests.set(key, requestTask)
|
||||
}
|
||||
|
||||
removeRequest(key) {
|
||||
this.pendingRequests.delete(key)
|
||||
}
|
||||
|
||||
cancelAll() {
|
||||
this.pendingRequests.forEach(task => task.abort())
|
||||
this.pendingRequests.clear()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 四、内存优化
|
||||
|
||||
### 4.1 及时清理
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
observer: null
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
// 清理定时器
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
|
||||
// 清理观察器
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
this.observer = null
|
||||
}
|
||||
|
||||
// 清理事件监听
|
||||
uni.offNetworkStatusChange()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 大数据处理
|
||||
```javascript
|
||||
// 分批处理大数据
|
||||
function processBigData(data, batchSize = 100) {
|
||||
const batches = []
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
batches.push(data.slice(i, i + batchSize))
|
||||
}
|
||||
|
||||
return batches.reduce((promise, batch) => {
|
||||
return promise.then(() => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
processBatch(batch)
|
||||
resolve()
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
}, Promise.resolve())
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 WeakMap 使用
|
||||
```javascript
|
||||
// 使用 WeakMap 避免内存泄漏
|
||||
const cache = new WeakMap()
|
||||
|
||||
function cacheData(obj, data) {
|
||||
cache.set(obj, data)
|
||||
}
|
||||
|
||||
function getCachedData(obj) {
|
||||
return cache.get(obj)
|
||||
}
|
||||
```
|
||||
|
||||
## 五、图片优化
|
||||
|
||||
### 5.1 图片压缩
|
||||
```javascript
|
||||
// 上传前压缩图片
|
||||
function compressImage(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.compressImage({
|
||||
src: filePath,
|
||||
quality: 80,
|
||||
success: (res) => resolve(res.tempFilePath),
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 图片格式选择
|
||||
- **照片**:使用 JPEG 格式
|
||||
- **图标**:使用 PNG 或 SVG 格式
|
||||
- **动画**:使用 GIF 或 WebP 格式
|
||||
- **透明背景**:使用 PNG 格式
|
||||
|
||||
### 5.3 响应式图片
|
||||
```vue
|
||||
<template>
|
||||
<image
|
||||
:src="getImageUrl()"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getImageUrl() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const dpr = systemInfo.pixelRatio
|
||||
|
||||
// 根据设备像素比选择图片
|
||||
if (dpr >= 3) {
|
||||
return this.image + '@3x.jpg'
|
||||
} else if (dpr >= 2) {
|
||||
return this.image + '@2x.jpg'
|
||||
}
|
||||
return this.image + '.jpg'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 5.4 图片预加载
|
||||
```javascript
|
||||
function preloadImages(urls) {
|
||||
return Promise.all(
|
||||
urls.map(url => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 六、小程序特定优化
|
||||
|
||||
### 6.1 setData 优化
|
||||
```javascript
|
||||
// 避免频繁 setData
|
||||
let updateTimer = null
|
||||
let pendingData = {}
|
||||
|
||||
function batchUpdate(data) {
|
||||
Object.assign(pendingData, data)
|
||||
|
||||
if (updateTimer) return
|
||||
|
||||
updateTimer = setTimeout(() => {
|
||||
this.setData(pendingData)
|
||||
pendingData = {}
|
||||
updateTimer = null
|
||||
}, 16) // 约60fps
|
||||
}
|
||||
|
||||
// 避免 setData 传递大数据
|
||||
// 不好的做法
|
||||
this.setData({
|
||||
list: this.data.list.concat(newList)
|
||||
})
|
||||
|
||||
// 好的做法
|
||||
this.setData({
|
||||
[`list[${this.data.list.length}]`]: newItem
|
||||
})
|
||||
```
|
||||
|
||||
### 6.2 自定义组件
|
||||
```javascript
|
||||
// 使用自定义组件代替模板
|
||||
Component({
|
||||
options: {
|
||||
// 启用纯数据字段
|
||||
pureDataPattern: /^_/,
|
||||
// 启用多slot支持
|
||||
multipleSlots: true
|
||||
},
|
||||
|
||||
properties: {
|
||||
title: String
|
||||
},
|
||||
|
||||
data: {
|
||||
_privateData: 'private' // 纯数据字段,不参与渲染
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 6.3 分包预下载
|
||||
```json
|
||||
{
|
||||
"preloadRule": {
|
||||
"pages/index/index": {
|
||||
"network": "all",
|
||||
"packages": ["subPackages/order"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 七、H5 特定优化
|
||||
|
||||
### 7.1 PWA 支持
|
||||
```javascript
|
||||
// 注册 Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
}
|
||||
|
||||
// sw.js
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open('v1').then((cache) => {
|
||||
return cache.addAll([
|
||||
'/',
|
||||
'/static/css/main.css',
|
||||
'/static/js/main.js'
|
||||
])
|
||||
})
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
### 7.2 CDN 加速
|
||||
```javascript
|
||||
// 静态资源使用 CDN
|
||||
const CDN_URL = 'https://cdn.example.com'
|
||||
|
||||
export const getStaticUrl = (path) => {
|
||||
return `${CDN_URL}${path}`
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 Gzip 压缩
|
||||
```javascript
|
||||
// vite.config.js
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
viteCompression({
|
||||
algorithm: 'gzip',
|
||||
ext: '.gz'
|
||||
})
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 八、性能监控
|
||||
|
||||
### 8.1 性能指标收集
|
||||
```javascript
|
||||
// 页面加载时间
|
||||
const pageLoadTime = performance.timing.loadEventEnd - performance.timing.navigationStart
|
||||
|
||||
// 首屏渲染时间
|
||||
const firstPaintTime = performance.getEntriesByType('paint')[0].startTime
|
||||
|
||||
// 接口请求时间
|
||||
const apiTime = performance.getEntriesByType('resource')
|
||||
.filter(item => item.initiatorType === 'xmlhttprequest')
|
||||
```
|
||||
|
||||
### 8.2 性能上报
|
||||
```javascript
|
||||
function reportPerformance(data) {
|
||||
// 上报到监控平台
|
||||
uni.request({
|
||||
url: 'https://monitor.example.com/report',
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: 'performance',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 九、优化检查清单
|
||||
|
||||
- [ ] 启用代码分割和懒加载
|
||||
- [ ] 实现图片懒加载
|
||||
- [ ] 使用虚拟列表处理长列表
|
||||
- [ ] 添加请求缓存机制
|
||||
- [ ] 实现请求重试机制
|
||||
- [ ] 合并并发请求
|
||||
- [ ] 压缩图片资源
|
||||
- [ ] 使用 CDN 加速静态资源
|
||||
- [ ] 启用 Gzip 压缩
|
||||
- [ ] 小程序分包加载
|
||||
- [ ] 优化 setData 调用
|
||||
- [ ] 及时清理定时器和监听器
|
||||
- [ ] 使用防抖和节流
|
||||
- [ ] 添加骨架屏
|
||||
- [ ] 实现性能监控
|
||||
Reference in New Issue
Block a user