mta-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +818 -0
  2. package/agents/_TEMPLATE.md +153 -0
  3. package/agents/flutter.agent.md +222 -0
  4. package/agents/i18n.agent.md +78 -0
  5. package/agents/logicflow.agent.md +97 -0
  6. package/agents/vue3.agent.md +176 -0
  7. package/agents/wechat-miniprogram.agent.md +89 -0
  8. package/bin/mta.cjs +132 -0
  9. package/common/i18n.md +385 -0
  10. package/common/typescript-strict.md +186 -0
  11. package/dist/index.d.ts +64 -0
  12. package/dist/index.js +6493 -0
  13. package/dist/index.js.map +1 -0
  14. package/package.json +81 -0
  15. package/standards/README.md +194 -0
  16. package/standards/core/code-generation.md +421 -0
  17. package/standards/core/code-style.md +308 -0
  18. package/standards/core/dart-base.md +572 -0
  19. package/standards/core/mandatory-rules.md +103 -0
  20. package/standards/core/typescript-base.md +179 -0
  21. package/standards/frameworks/flutter-ui-system.md +497 -0
  22. package/standards/frameworks/flutter.md +1268 -0
  23. package/standards/frameworks/pinia.md +172 -0
  24. package/standards/frameworks/vue3-composition.md +779 -0
  25. package/standards/frameworks/wechat-miniprogram.md +2177 -0
  26. package/standards/libraries/element-plus.md +1128 -0
  27. package/standards/libraries/i18n.md +360 -0
  28. package/standards/libraries/logicflow.md +1007 -0
  29. package/standards/patterns/api-layer.md +187 -0
  30. package/standards/patterns/component-design.md +200 -0
  31. package/standards/patterns/design-system-restoration.md +570 -0
  32. package/standards/patterns/vue-api-mock-layer.md +958 -0
  33. package/standards/patterns/vue-css-nesting.md +604 -0
  34. package/standards/troubleshooting-cases/flutter/textfield-vertical-centering.md +107 -0
  35. package/standards/workflows/design-restoration-guide.md +164 -0
  36. package/standards/workflows/large-project-split.md +359 -0
  37. package/standards/workflows/problem-diagnosis.md +280 -0
  38. package/standards/workflows/textfield-centering-guide.md +157 -0
  39. package/templates/README.md +144 -0
  40. package/templates/common/types/_CONFIG.md +12 -0
  41. package/templates/common/types/api.ts +39 -0
  42. package/templates/common/types/common.ts +70 -0
  43. package/templates/config-templates/agents-section.md +9 -0
  44. package/templates/config-templates/custom-section.md +6 -0
  45. package/templates/config-templates/header.md +29 -0
  46. package/templates/config-templates/workflow-minimal.md +44 -0
  47. package/templates/copilot-instructions-mcp-optimized.md +158 -0
  48. package/templates/vue/api-layer/_CONFIG.md +145 -0
  49. package/templates/vue/api-layer/index.ts +58 -0
  50. package/templates/vue/api-layer/mock/index.ts +122 -0
  51. package/templates/vue/api-layer/modules/_template.ts +109 -0
  52. package/templates/vue/api-layer/modules/index.ts +16 -0
  53. package/templates/vue/api-layer/request.ts +279 -0
  54. package/templates/vue/api-layer/types.ts +80 -0
  55. package/troubleshooting/README.md +368 -0
  56. package/troubleshooting/USAGE_GUIDE.md +289 -0
  57. package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +244 -0
  58. package/troubleshooting/flutter/component-/351/200/232/347/224/250/345/214/226/346/217/220/345/217/226.md +269 -0
  59. package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +240 -0
  60. package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +236 -0
  61. package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +214 -0
  62. package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +172 -0
  63. package/troubleshooting/flutter/sketch-/345/210/227/350/241/250item/345/214/272/345/237/237.md +212 -0
  64. package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +135 -0
  65. package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +201 -0
  66. package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +139 -0
  67. package/troubleshooting/flutter/sketch-/350/203/214/346/231/257/345/261/202/351/253/230/345/272/246.md +264 -0
  68. package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +120 -0
  69. package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +117 -0
  70. package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +107 -0
  71. package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +81 -0
  72. package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +130 -0
  73. package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +181 -0
  74. package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +148 -0
  75. package/troubleshooting/vue3/table-/350/276/271/346/241/206/351/227/256/351/242/230.md +178 -0
@@ -0,0 +1,2177 @@
1
+ # 微信小程序开发规范
2
+
3
+ > 基于微信官方开发文档与优质开源项目最佳实践
4
+ > 版本:适用于基础库 3.0+
5
+
6
+ ## 🎯 核心原则
7
+
8
+ 1. **组件化开发** - 充分利用自定义组件,提高代码复用性
9
+ 2. **数据驱动视图** - 使用 setData 更新视图,避免直接操作 DOM
10
+ 3. **性能优先** - 优化渲染性能,控制 setData 频率和数据大小
11
+ 4. **用户体验** - 完善的加载状态、错误处理和交互反馈
12
+ 5. **安全规范** - 敏感信息加密,接口鉴权,防止 XSS 攻击
13
+
14
+ ---
15
+
16
+ ## 📁 项目结构
17
+
18
+ ### 推荐目录结构
19
+
20
+ ```
21
+ miniprogram/
22
+ ├── app.js # 小程序逻辑
23
+ ├── app.json # 小程序公共配置
24
+ ├── app.wxss # 小程序公共样式
25
+ ├── sitemap.json # 搜索配置
26
+ ├── project.config.json # 项目配置
27
+ ├── project.private.config.json # 私有配置(不提交)
28
+
29
+ ├── pages/ # 页面目录
30
+ │ ├── index/
31
+ │ │ ├── index.js # 页面逻辑
32
+ │ │ ├── index.json # 页面配置
33
+ │ │ ├── index.wxml # 页面结构
34
+ │ │ └── index.wxss # 页面样式
35
+ │ └── ...
36
+
37
+ ├── components/ # 自定义组件
38
+ │ ├── user-card/
39
+ │ │ ├── index.js
40
+ │ │ ├── index.json
41
+ │ │ ├── index.wxml
42
+ │ │ └── index.wxss
43
+ │ └── ...
44
+
45
+ ├── utils/ # 工具函数
46
+ │ ├── request.js # 网络请求封装
47
+ │ ├── storage.js # 本地存储封装
48
+ │ ├── auth.js # 鉴权工具
49
+ │ └── util.js # 通用工具
50
+
51
+ ├── api/ # API 接口管理
52
+ │ ├── user.js
53
+ │ ├── product.js
54
+ │ └── order.js
55
+
56
+ ├── config/ # 配置文件
57
+ │ ├── env.js # 环境配置
58
+ │ └── constants.js # 常量定义
59
+
60
+ ├── models/ # 数据模型
61
+ │ └── user.js
62
+
63
+ ├── store/ # 全局状态管理(可选)
64
+ │ └── index.js
65
+
66
+ ├── styles/ # 公共样式
67
+ │ ├── variables.wxss # CSS 变量
68
+ │ └── common.wxss # 公共样式
69
+
70
+ └── images/ # 图片资源
71
+ ├── icons/
72
+ └── ...
73
+ ```
74
+
75
+ ### 文件命名规范
76
+
77
+ | 类型 | 规范 | 示例 |
78
+ |-----|------|-----|
79
+ | 页面 | kebab-case | `user-profile/` |
80
+ | 组件 | kebab-case | `product-card/` |
81
+ | JS 文件 | camelCase | `userService.js` |
82
+ | 常量文件 | UPPER_CASE | `API_CONFIG.js` |
83
+
84
+ ---
85
+
86
+ ## 📄 页面开发规范
87
+
88
+ ### Page 生命周期
89
+
90
+ ```javascript
91
+ // pages/user/user.js
92
+ Page({
93
+ /**
94
+ * 页面的初始数据
95
+ */
96
+ data: {
97
+ userInfo: null,
98
+ list: [],
99
+ loading: false,
100
+ page: 1,
101
+ hasMore: true
102
+ },
103
+
104
+ /**
105
+ * 生命周期函数--监听页面加载
106
+ * @param {Object} options - 页面参数
107
+ */
108
+ onLoad(options) {
109
+ // ✅ 获取路由参数
110
+ const { id } = options
111
+
112
+ // ✅ 初始化数据
113
+ this.fetchUserInfo(id)
114
+ this.fetchList()
115
+ },
116
+
117
+ /**
118
+ * 生命周期函数--监听页面初次渲染完成
119
+ */
120
+ onReady() {
121
+ // ✅ 可以进行页面节点操作
122
+ wx.setNavigationBarTitle({
123
+ title: '用户中心'
124
+ })
125
+ },
126
+
127
+ /**
128
+ * 生命周期函数--监听页面显示
129
+ */
130
+ onShow() {
131
+ // ✅ 页面每次显示时执行
132
+ // 适合刷新页面数据
133
+ },
134
+
135
+ /**
136
+ * 生命周期函数--监听页面隐藏
137
+ */
138
+ onHide() {
139
+ // ✅ 页面隐藏时执行
140
+ },
141
+
142
+ /**
143
+ * 生命周期函数--监听页面卸载
144
+ */
145
+ onUnload() {
146
+ // ✅ 清理定时器、取消请求等
147
+ if (this.timer) {
148
+ clearInterval(this.timer)
149
+ }
150
+ },
151
+
152
+ /**
153
+ * 页面相关事件处理函数--监听用户下拉动作
154
+ */
155
+ onPullDownRefresh() {
156
+ // ✅ 刷新数据
157
+ this.setData({
158
+ page: 1,
159
+ list: [],
160
+ hasMore: true
161
+ })
162
+
163
+ this.fetchList().then(() => {
164
+ wx.stopPullDownRefresh()
165
+ })
166
+ },
167
+
168
+ /**
169
+ * 页面上拉触底事件的处理函数
170
+ */
171
+ onReachBottom() {
172
+ // ✅ 加载更多
173
+ if (!this.data.hasMore || this.data.loading) return
174
+
175
+ this.setData({
176
+ page: this.data.page + 1
177
+ })
178
+ this.fetchList()
179
+ },
180
+
181
+ /**
182
+ * 用户点击右上角分享
183
+ */
184
+ onShareAppMessage() {
185
+ return {
186
+ title: '分享标题',
187
+ path: `/pages/user/user?id=${this.data.userInfo.id}`,
188
+ imageUrl: this.data.userInfo.avatar
189
+ }
190
+ },
191
+
192
+ /**
193
+ * 获取用户信息
194
+ */
195
+ async fetchUserInfo(id) {
196
+ try {
197
+ this.setData({ loading: true })
198
+
199
+ const res = await userApi.getUserInfo({ id })
200
+
201
+ this.setData({
202
+ userInfo: res.data
203
+ })
204
+ } catch (error) {
205
+ console.error('获取用户信息失败:', error)
206
+ wx.showToast({
207
+ title: '获取信息失败',
208
+ icon: 'none'
209
+ })
210
+ } finally {
211
+ this.setData({ loading: false })
212
+ }
213
+ },
214
+
215
+ /**
216
+ * 获取列表数据
217
+ */
218
+ async fetchList() {
219
+ try {
220
+ this.setData({ loading: true })
221
+
222
+ const { page } = this.data
223
+ const res = await userApi.getList({ page, pageSize: 10 })
224
+
225
+ this.setData({
226
+ list: page === 1 ? res.data.list : [...this.data.list, ...res.data.list],
227
+ hasMore: res.data.hasMore
228
+ })
229
+ } catch (error) {
230
+ console.error('获取列表失败:', error)
231
+ wx.showToast({
232
+ title: '加载失败',
233
+ icon: 'none'
234
+ })
235
+ } finally {
236
+ this.setData({ loading: false })
237
+ }
238
+ },
239
+
240
+ /**
241
+ * 跳转到详情页
242
+ */
243
+ handleNavigateToDetail(e) {
244
+ const { id } = e.currentTarget.dataset
245
+ wx.navigateTo({
246
+ url: `/pages/detail/detail?id=${id}`
247
+ })
248
+ }
249
+ })
250
+ ```
251
+
252
+ ### WXML 模板规范
253
+
254
+ ```xml
255
+ <!-- ✅ 正确示例 -->
256
+ <view class="container">
257
+ <!-- 加载状态 -->
258
+ <view wx:if="{{loading}}" class="loading">
259
+ <text>加载中...</text>
260
+ </view>
261
+
262
+ <!-- 数据展示 -->
263
+ <block wx:else>
264
+ <!-- 条件渲染 -->
265
+ <view wx:if="{{userInfo}}" class="user-info">
266
+ <image class="avatar" src="{{userInfo.avatar}}" mode="aspectFill" />
267
+ <text class="name">{{userInfo.name}}</text>
268
+ </view>
269
+
270
+ <!-- 列表渲染 - 必须添加 key -->
271
+ <view
272
+ wx:for="{{list}}"
273
+ wx:key="id"
274
+ class="item"
275
+ data-id="{{item.id}}"
276
+ bindtap="handleNavigateToDetail"
277
+ >
278
+ <text>{{item.title}}</text>
279
+ <text class="time">{{item.createTime}}</text>
280
+ </view>
281
+
282
+ <!-- 空状态 -->
283
+ <view wx:if="{{list.length === 0 && !loading}}" class="empty">
284
+ <text>暂无数据</text>
285
+ </view>
286
+ </block>
287
+ </view>
288
+
289
+ <!-- ❌ 错误示例 -->
290
+ <!-- 1. 缺少 wx:key -->
291
+ <view wx:for="{{list}}">
292
+ {{item.name}}
293
+ </view>
294
+
295
+ <!-- 2. 复杂的模板表达式 -->
296
+ <text>{{list.filter(item => item.active).map(i => i.name).join(', ')}}</text>
297
+ <!-- 应该在 JS 中处理 -->
298
+
299
+ <!-- 3. 内联样式过多 -->
300
+ <view style="width: 100px; height: 100px; background: red; margin: 10px;">
301
+ 内容
302
+ </view>
303
+ <!-- 应该使用 class -->
304
+ ```
305
+
306
+ ### WXSS 样式规范
307
+
308
+ ```css
309
+ /* ✅ 使用 CSS 变量 */
310
+ page {
311
+ --primary-color: #1aad19;
312
+ --text-color: #333;
313
+ --border-color: #e5e5e5;
314
+ --bg-color: #f5f5f5;
315
+ }
316
+
317
+ /* ✅ BEM 命名规范 */
318
+ .user-card {
319
+ padding: 20rpx;
320
+ background: #fff;
321
+ }
322
+
323
+ .user-card__avatar {
324
+ width: 100rpx;
325
+ height: 100rpx;
326
+ border-radius: 50%;
327
+ }
328
+
329
+ .user-card__name {
330
+ font-size: 32rpx;
331
+ color: var(--text-color);
332
+ }
333
+
334
+ .user-card__name--vip {
335
+ color: #ff9500;
336
+ }
337
+
338
+ /* ✅ 响应式单位 rpx */
339
+ .container {
340
+ width: 750rpx;
341
+ padding: 30rpx;
342
+ }
343
+
344
+ /* ✅ Flex 布局 */
345
+ .flex-row {
346
+ display: flex;
347
+ flex-direction: row;
348
+ align-items: center;
349
+ justify-content: space-between;
350
+ }
351
+
352
+ /* ❌ 避免使用固定像素 px(特殊情况除外) */
353
+ .bad-width {
354
+ width: 375px; /* 不推荐 */
355
+ }
356
+
357
+ /* ❌ 避免过深的选择器嵌套 */
358
+ .page .container .content .item .title .text {
359
+ /* 太深了! */
360
+ }
361
+ ```
362
+
363
+ ---
364
+
365
+ ## 🧩 组件开发规范
366
+
367
+ ### 自定义组件
368
+
369
+ ```javascript
370
+ // components/user-card/index.js
371
+ Component({
372
+ /**
373
+ * 组件的属性列表
374
+ */
375
+ properties: {
376
+ // ✅ 完整的属性定义
377
+ user: {
378
+ type: Object,
379
+ value: null,
380
+ observer(newVal, oldVal) {
381
+ // 属性变化时的处理
382
+ if (newVal) {
383
+ this._processUserData(newVal)
384
+ }
385
+ }
386
+ },
387
+
388
+ size: {
389
+ type: String,
390
+ value: 'medium', // small | medium | large
391
+ validator(value) {
392
+ return ['small', 'medium', 'large'].includes(value)
393
+ }
394
+ },
395
+
396
+ showActions: {
397
+ type: Boolean,
398
+ value: true
399
+ }
400
+ },
401
+
402
+ /**
403
+ * 组件的初始数据
404
+ */
405
+ data: {
406
+ processedUser: null,
407
+ isFollowing: false
408
+ },
409
+
410
+ /**
411
+ * 组件的方法列表
412
+ */
413
+ methods: {
414
+ /**
415
+ * 处理用户数据
416
+ * @private
417
+ */
418
+ _processUserData(user) {
419
+ // ✅ 私有方法使用 _ 前缀
420
+ this.setData({
421
+ processedUser: {
422
+ ...user,
423
+ displayName: user.nickname || user.name
424
+ }
425
+ })
426
+ },
427
+
428
+ /**
429
+ * 关注/取消关注
430
+ */
431
+ async handleFollow() {
432
+ const { user } = this.properties
433
+ const { isFollowing } = this.data
434
+
435
+ try {
436
+ if (isFollowing) {
437
+ await userApi.unfollow({ userId: user.id })
438
+ } else {
439
+ await userApi.follow({ userId: user.id })
440
+ }
441
+
442
+ this.setData({
443
+ isFollowing: !isFollowing
444
+ })
445
+
446
+ // ✅ 触发自定义事件
447
+ this.triggerEvent('followchange', {
448
+ userId: user.id,
449
+ isFollowing: !isFollowing
450
+ })
451
+
452
+ wx.showToast({
453
+ title: isFollowing ? '已取消关注' : '关注成功',
454
+ icon: 'success'
455
+ })
456
+ } catch (error) {
457
+ console.error('操作失败:', error)
458
+ wx.showToast({
459
+ title: '操作失败',
460
+ icon: 'none'
461
+ })
462
+ }
463
+ },
464
+
465
+ /**
466
+ * 跳转到用户主页
467
+ */
468
+ handleNavigateToProfile() {
469
+ const { user } = this.properties
470
+
471
+ // ✅ 触发导航事件,由父组件处理
472
+ this.triggerEvent('navigate', {
473
+ userId: user.id
474
+ })
475
+ }
476
+ },
477
+
478
+ /**
479
+ * 组件生命周期
480
+ */
481
+ lifetimes: {
482
+ attached() {
483
+ // ✅ 组件被挂载到页面时执行
484
+ },
485
+
486
+ detached() {
487
+ // ✅ 组件从页面移除时执行
488
+ // 清理定时器等
489
+ }
490
+ },
491
+
492
+ /**
493
+ * 组件所在页面的生命周期
494
+ */
495
+ pageLifetimes: {
496
+ show() {
497
+ // ✅ 页面显示时执行
498
+ },
499
+
500
+ hide() {
501
+ // ✅ 页面隐藏时执行
502
+ }
503
+ }
504
+ })
505
+ ```
506
+
507
+ ### 组件 WXML
508
+
509
+ ```xml
510
+ <!-- components/user-card/index.wxml -->
511
+ <view class="user-card user-card--{{size}}">
512
+ <!-- 用户信息 -->
513
+ <view class="user-card__info" bindtap="handleNavigateToProfile">
514
+ <image
515
+ class="user-card__avatar"
516
+ src="{{user.avatar}}"
517
+ mode="aspectFill"
518
+ />
519
+ <view class="user-card__detail">
520
+ <text class="user-card__name">{{processedUser.displayName}}</text>
521
+ <text class="user-card__desc">{{user.bio}}</text>
522
+ </view>
523
+ </view>
524
+
525
+ <!-- 操作按钮 -->
526
+ <view wx:if="{{showActions}}" class="user-card__actions">
527
+ <button
528
+ class="user-card__btn {{isFollowing ? 'user-card__btn--following' : ''}}"
529
+ bindtap="handleFollow"
530
+ >
531
+ {{isFollowing ? '已关注' : '关注'}}
532
+ </button>
533
+ </view>
534
+ </view>
535
+ ```
536
+
537
+ ### 组件配置
538
+
539
+ ```json
540
+ {
541
+ "component": true,
542
+ "usingComponents": {}
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ## 🌐 网络请求规范
549
+
550
+ ### 请求封装
551
+
552
+ ```javascript
553
+ // utils/request.js
554
+
555
+ const BASE_URL = 'https://api.example.com'
556
+ const TOKEN_KEY = 'auth_token'
557
+
558
+ /**
559
+ * 网络请求封装
560
+ * @param {Object} options - 请求配置
561
+ * @param {string} options.url - 请求路径
562
+ * @param {string} options.method - 请求方法
563
+ * @param {Object} options.data - 请求数据
564
+ * @param {Object} options.header - 请求头
565
+ * @param {boolean} options.needAuth - 是否需要鉴权
566
+ * @param {boolean} options.showLoading - 是否显示加载提示
567
+ * @returns {Promise}
568
+ */
569
+ function request(options) {
570
+ const {
571
+ url,
572
+ method = 'GET',
573
+ data = {},
574
+ header = {},
575
+ needAuth = true,
576
+ showLoading = true
577
+ } = options
578
+
579
+ // ✅ 显示加载提示
580
+ if (showLoading) {
581
+ wx.showLoading({
582
+ title: '加载中...',
583
+ mask: true
584
+ })
585
+ }
586
+
587
+ return new Promise((resolve, reject) => {
588
+ // ✅ 构建请求头
589
+ const requestHeader = {
590
+ 'content-type': 'application/json',
591
+ ...header
592
+ }
593
+
594
+ // ✅ 添加 Token
595
+ if (needAuth) {
596
+ const token = wx.getStorageSync(TOKEN_KEY)
597
+ if (token) {
598
+ requestHeader['Authorization'] = `Bearer ${token}`
599
+ }
600
+ }
601
+
602
+ wx.request({
603
+ url: `${BASE_URL}${url}`,
604
+ method,
605
+ data,
606
+ header: requestHeader,
607
+ success: (res) => {
608
+ if (showLoading) {
609
+ wx.hideLoading()
610
+ }
611
+
612
+ // ✅ 统一处理响应
613
+ const { statusCode, data } = res
614
+
615
+ if (statusCode === 200) {
616
+ // ✅ 业务成功
617
+ if (data.code === 0) {
618
+ resolve(data)
619
+ } else {
620
+ // ✅ 业务失败
621
+ const errorMsg = data.message || '请求失败'
622
+ wx.showToast({
623
+ title: errorMsg,
624
+ icon: 'none'
625
+ })
626
+ reject(new Error(errorMsg))
627
+ }
628
+ } else if (statusCode === 401) {
629
+ // ✅ 未授权,跳转登录
630
+ wx.removeStorageSync(TOKEN_KEY)
631
+ wx.redirectTo({
632
+ url: '/pages/login/login'
633
+ })
634
+ reject(new Error('未授权'))
635
+ } else {
636
+ // ✅ 其他错误
637
+ const errorMsg = '网络请求失败'
638
+ wx.showToast({
639
+ title: errorMsg,
640
+ icon: 'none'
641
+ })
642
+ reject(new Error(errorMsg))
643
+ }
644
+ },
645
+ fail: (error) => {
646
+ if (showLoading) {
647
+ wx.hideLoading()
648
+ }
649
+
650
+ // ✅ 网络错误处理
651
+ console.error('网络请求失败:', error)
652
+ wx.showToast({
653
+ title: '网络连接失败',
654
+ icon: 'none'
655
+ })
656
+ reject(error)
657
+ }
658
+ })
659
+ })
660
+ }
661
+
662
+ module.exports = {
663
+ request,
664
+
665
+ // ✅ 快捷方法
666
+ get(url, data, options = {}) {
667
+ return request({ url, method: 'GET', data, ...options })
668
+ },
669
+
670
+ post(url, data, options = {}) {
671
+ return request({ url, method: 'POST', data, ...options })
672
+ },
673
+
674
+ put(url, data, options = {}) {
675
+ return request({ url, method: 'PUT', data, ...options })
676
+ },
677
+
678
+ delete(url, data, options = {}) {
679
+ return request({ url, method: 'DELETE', data, ...options })
680
+ }
681
+ }
682
+ ```
683
+
684
+ ### API 管理
685
+
686
+ ```javascript
687
+ // api/user.js
688
+ const { get, post } = require('../utils/request')
689
+
690
+ module.exports = {
691
+ /**
692
+ * 获取用户信息
693
+ */
694
+ getUserInfo(data) {
695
+ return get('/user/info', data)
696
+ },
697
+
698
+ /**
699
+ * 更新用户信息
700
+ */
701
+ updateUserInfo(data) {
702
+ return post('/user/update', data)
703
+ },
704
+
705
+ /**
706
+ * 获取用户列表
707
+ */
708
+ getUserList(data) {
709
+ return get('/user/list', data)
710
+ },
711
+
712
+ /**
713
+ * 关注用户
714
+ */
715
+ followUser(data) {
716
+ return post('/user/follow', data)
717
+ },
718
+
719
+ /**
720
+ * 取消关注
721
+ */
722
+ unfollowUser(data) {
723
+ return post('/user/unfollow', data)
724
+ }
725
+ }
726
+ ```
727
+
728
+ ---
729
+
730
+ ## ☁️ 云开发规范
731
+
732
+ > 微信小程序云开发提供云函数、云数据库、云存储、云调用等能力
733
+
734
+ ### 云开发初始化
735
+
736
+ ```javascript
737
+ // app.js
738
+ App({
739
+ onLaunch() {
740
+ // ✅ 初始化云开发环境
741
+ if (!wx.cloud) {
742
+ console.error('请使用 2.2.3 或以上的基础库以使用云能力')
743
+ } else {
744
+ wx.cloud.init({
745
+ env: 'your-env-id', // 云开发环境 ID
746
+ traceUser: true // 记录用户访问记录
747
+ })
748
+ }
749
+ }
750
+ })
751
+ ```
752
+
753
+ ### 环境配置管理
754
+
755
+ ```javascript
756
+ // config/cloud.js
757
+
758
+ // ✅ 多环境配置
759
+ const ENV_CONFIG = {
760
+ development: {
761
+ envId: 'dev-xxxxx',
762
+ functionRoot: 'cloudfunctions'
763
+ },
764
+ production: {
765
+ envId: 'prod-xxxxx',
766
+ functionRoot: 'cloudfunctions'
767
+ }
768
+ }
769
+
770
+ const currentEnv = process.env.NODE_ENV || 'development'
771
+
772
+ module.exports = {
773
+ ...ENV_CONFIG[currentEnv],
774
+ // 云函数超时时间(毫秒)
775
+ timeout: 10000
776
+ }
777
+ ```
778
+
779
+ ---
780
+
781
+ ### 云函数开发规范
782
+
783
+ #### 云函数结构
784
+
785
+ ```javascript
786
+ // cloudfunctions/getUserInfo/index.js
787
+
788
+ const cloud = require('wx-server-sdk')
789
+
790
+ // ✅ 初始化云环境
791
+ cloud.init({
792
+ env: cloud.DYNAMIC_CURRENT_ENV
793
+ })
794
+
795
+ const db = cloud.database()
796
+
797
+ /**
798
+ * 获取用户信息云函数
799
+ * @param {Object} event - 云函数调用参数
800
+ * @param {string} event.userId - 用户 ID
801
+ * @returns {Object} 用户信息
802
+ */
803
+ exports.main = async (event, context) => {
804
+ // ✅ 获取调用者信息
805
+ const { OPENID, APPID, UNIONID } = cloud.getWXContext()
806
+
807
+ try {
808
+ // ✅ 参数验证
809
+ if (!event.userId) {
810
+ return {
811
+ success: false,
812
+ message: '缺少必要参数 userId'
813
+ }
814
+ }
815
+
816
+ // ✅ 数据库查询
817
+ const result = await db.collection('users')
818
+ .doc(event.userId)
819
+ .get()
820
+
821
+ // ✅ 返回统一格式
822
+ return {
823
+ success: true,
824
+ data: result.data,
825
+ openid: OPENID
826
+ }
827
+ } catch (error) {
828
+ // ✅ 错误处理
829
+ console.error('获取用户信息失败:', error)
830
+ return {
831
+ success: false,
832
+ message: error.message
833
+ }
834
+ }
835
+ }
836
+ ```
837
+
838
+ #### 小程序端调用云函数
839
+
840
+ ```javascript
841
+ // utils/cloudFunctions.js
842
+
843
+ /**
844
+ * 调用云函数封装
845
+ * @param {string} name - 云函数名称
846
+ * @param {Object} data - 传递参数
847
+ * @returns {Promise}
848
+ */
849
+ async function callFunction(name, data = {}) {
850
+ try {
851
+ wx.showLoading({ title: '处理中...', mask: true })
852
+
853
+ const res = await wx.cloud.callFunction({
854
+ name,
855
+ data
856
+ })
857
+
858
+ wx.hideLoading()
859
+
860
+ // ✅ 统一处理响应
861
+ if (res.result.success) {
862
+ return res.result
863
+ } else {
864
+ throw new Error(res.result.message || '操作失败')
865
+ }
866
+ } catch (error) {
867
+ wx.hideLoading()
868
+ console.error(`调用云函数 ${name} 失败:`, error)
869
+
870
+ wx.showToast({
871
+ title: error.message || '操作失败',
872
+ icon: 'none'
873
+ })
874
+
875
+ throw error
876
+ }
877
+ }
878
+
879
+ module.exports = {
880
+ callFunction,
881
+
882
+ // ✅ 具体业务方法
883
+ getUserInfo(userId) {
884
+ return callFunction('getUserInfo', { userId })
885
+ },
886
+
887
+ createOrder(orderData) {
888
+ return callFunction('createOrder', orderData)
889
+ }
890
+ }
891
+ ```
892
+
893
+ ---
894
+
895
+ ### 云数据库规范
896
+
897
+ #### 数据库集合设计
898
+
899
+ ```javascript
900
+ // ✅ 用户集合 (users)
901
+ {
902
+ _id: 'user_xxx',
903
+ _openid: 'oXXXX', // 用户 openid(自动生成)
904
+ nickname: '张三',
905
+ avatar: 'https://...',
906
+ phone: '13800138000',
907
+ createTime: new Date(),
908
+ updateTime: new Date()
909
+ }
910
+
911
+ // ✅ 订单集合 (orders)
912
+ {
913
+ _id: 'order_xxx',
914
+ _openid: 'oXXXX', // 下单用户
915
+ userId: 'user_xxx',
916
+ products: [
917
+ { id: 'prod_1', name: '商品1', price: 100, count: 2 }
918
+ ],
919
+ totalPrice: 200,
920
+ status: 'pending', // pending | paid | shipped | completed
921
+ createTime: new Date()
922
+ }
923
+ ```
924
+
925
+ #### 数据库操作封装
926
+
927
+ ```javascript
928
+ // utils/cloudDB.js
929
+
930
+ const db = wx.cloud.database()
931
+ const _ = db.command
932
+
933
+ /**
934
+ * 数据库操作工具类
935
+ */
936
+ class CloudDB {
937
+ constructor(collectionName) {
938
+ this.collection = db.collection(collectionName)
939
+ }
940
+
941
+ /**
942
+ * 添加记录
943
+ */
944
+ async add(data) {
945
+ try {
946
+ const res = await this.collection.add({
947
+ data: {
948
+ ...data,
949
+ createTime: new Date(),
950
+ updateTime: new Date()
951
+ }
952
+ })
953
+ return { success: true, id: res._id }
954
+ } catch (error) {
955
+ console.error('添加记录失败:', error)
956
+ throw error
957
+ }
958
+ }
959
+
960
+ /**
961
+ * 查询记录(分页)
962
+ */
963
+ async getList({ page = 1, pageSize = 10, where = {}, orderBy = 'createTime', order = 'desc' }) {
964
+ try {
965
+ const skip = (page - 1) * pageSize
966
+
967
+ const countRes = await this.collection.where(where).count()
968
+ const total = countRes.total
969
+
970
+ const res = await this.collection
971
+ .where(where)
972
+ .orderBy(orderBy, order)
973
+ .skip(skip)
974
+ .limit(pageSize)
975
+ .get()
976
+
977
+ return {
978
+ success: true,
979
+ data: res.data,
980
+ total,
981
+ page,
982
+ pageSize,
983
+ hasMore: skip + res.data.length < total
984
+ }
985
+ } catch (error) {
986
+ console.error('查询列表失败:', error)
987
+ throw error
988
+ }
989
+ }
990
+
991
+ /**
992
+ * 获取单条记录
993
+ */
994
+ async getById(id) {
995
+ try {
996
+ const res = await this.collection.doc(id).get()
997
+ return { success: true, data: res.data }
998
+ } catch (error) {
999
+ console.error('获取记录失败:', error)
1000
+ throw error
1001
+ }
1002
+ }
1003
+
1004
+ /**
1005
+ * 更新记录
1006
+ */
1007
+ async update(id, data) {
1008
+ try {
1009
+ await this.collection.doc(id).update({
1010
+ data: {
1011
+ ...data,
1012
+ updateTime: new Date()
1013
+ }
1014
+ })
1015
+ return { success: true }
1016
+ } catch (error) {
1017
+ console.error('更新记录失败:', error)
1018
+ throw error
1019
+ }
1020
+ }
1021
+
1022
+ /**
1023
+ * 删除记录
1024
+ */
1025
+ async remove(id) {
1026
+ try {
1027
+ await this.collection.doc(id).remove()
1028
+ return { success: true }
1029
+ } catch (error) {
1030
+ console.error('删除记录失败:', error)
1031
+ throw error
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ // ✅ 导出实例
1037
+ module.exports = {
1038
+ CloudDB,
1039
+ usersDB: new CloudDB('users'),
1040
+ ordersDB: new CloudDB('orders'),
1041
+ productsDB: new CloudDB('products')
1042
+ }
1043
+ ```
1044
+
1045
+ #### 使用示例
1046
+
1047
+ ```javascript
1048
+ // pages/user/user.js
1049
+ const { usersDB } = require('../../utils/cloudDB')
1050
+
1051
+ Page({
1052
+ data: {
1053
+ users: [],
1054
+ page: 1
1055
+ },
1056
+
1057
+ async onLoad() {
1058
+ await this.fetchUsers()
1059
+ },
1060
+
1061
+ async fetchUsers() {
1062
+ try {
1063
+ const res = await usersDB.getList({
1064
+ page: this.data.page,
1065
+ pageSize: 10,
1066
+ where: {
1067
+ // ✅ 查询条件
1068
+ status: 'active'
1069
+ }
1070
+ })
1071
+
1072
+ this.setData({
1073
+ users: res.data,
1074
+ hasMore: res.hasMore
1075
+ })
1076
+ } catch (error) {
1077
+ wx.showToast({
1078
+ title: '加载失败',
1079
+ icon: 'none'
1080
+ })
1081
+ }
1082
+ }
1083
+ })
1084
+ ```
1085
+
1086
+ ---
1087
+
1088
+ ### 云存储规范
1089
+
1090
+ #### 文件上传
1091
+
1092
+ ```javascript
1093
+ // utils/cloudStorage.js
1094
+
1095
+ /**
1096
+ * 上传文件到云存储
1097
+ * @param {string} filePath - 本地文件路径
1098
+ * @param {string} cloudPath - 云存储路径
1099
+ * @returns {Promise}
1100
+ */
1101
+ async function uploadFile(filePath, cloudPath) {
1102
+ try {
1103
+ wx.showLoading({ title: '上传中...', mask: true })
1104
+
1105
+ const res = await wx.cloud.uploadFile({
1106
+ cloudPath,
1107
+ filePath
1108
+ })
1109
+
1110
+ wx.hideLoading()
1111
+
1112
+ return {
1113
+ success: true,
1114
+ fileID: res.fileID
1115
+ }
1116
+ } catch (error) {
1117
+ wx.hideLoading()
1118
+ console.error('上传文件失败:', error)
1119
+
1120
+ wx.showToast({
1121
+ title: '上传失败',
1122
+ icon: 'none'
1123
+ })
1124
+
1125
+ throw error
1126
+ }
1127
+ }
1128
+
1129
+ /**
1130
+ * 选择并上传图片
1131
+ * @param {Object} options - 配置选项
1132
+ * @returns {Promise}
1133
+ */
1134
+ async function chooseAndUploadImage(options = {}) {
1135
+ const {
1136
+ count = 1,
1137
+ sizeType = ['compressed'],
1138
+ sourceType = ['album', 'camera'],
1139
+ folder = 'images'
1140
+ } = options
1141
+
1142
+ try {
1143
+ // ✅ 选择图片
1144
+ const chooseRes = await wx.chooseImage({
1145
+ count,
1146
+ sizeType,
1147
+ sourceType
1148
+ })
1149
+
1150
+ const uploadTasks = chooseRes.tempFilePaths.map((filePath, index) => {
1151
+ // ✅ 生成云存储路径
1152
+ const ext = filePath.split('.').pop()
1153
+ const cloudPath = `${folder}/${Date.now()}_${index}.${ext}`
1154
+
1155
+ return uploadFile(filePath, cloudPath)
1156
+ })
1157
+
1158
+ const results = await Promise.all(uploadTasks)
1159
+
1160
+ return {
1161
+ success: true,
1162
+ fileIDs: results.map(r => r.fileID)
1163
+ }
1164
+ } catch (error) {
1165
+ console.error('选择上传图片失败:', error)
1166
+ throw error
1167
+ }
1168
+ }
1169
+
1170
+ /**
1171
+ * 下载文件
1172
+ * @param {string} fileID - 云文件 ID
1173
+ * @returns {Promise}
1174
+ */
1175
+ async function downloadFile(fileID) {
1176
+ try {
1177
+ const res = await wx.cloud.downloadFile({
1178
+ fileID
1179
+ })
1180
+
1181
+ return {
1182
+ success: true,
1183
+ tempFilePath: res.tempFilePath
1184
+ }
1185
+ } catch (error) {
1186
+ console.error('下载文件失败:', error)
1187
+ throw error
1188
+ }
1189
+ }
1190
+
1191
+ /**
1192
+ * 删除文件
1193
+ * @param {Array<string>} fileIDs - 云文件 ID 数组
1194
+ * @returns {Promise}
1195
+ */
1196
+ async function deleteFiles(fileIDs) {
1197
+ try {
1198
+ const res = await wx.cloud.deleteFile({
1199
+ fileList: fileIDs
1200
+ })
1201
+
1202
+ return {
1203
+ success: true,
1204
+ fileList: res.fileList
1205
+ }
1206
+ } catch (error) {
1207
+ console.error('删除文件失败:', error)
1208
+ throw error
1209
+ }
1210
+ }
1211
+
1212
+ module.exports = {
1213
+ uploadFile,
1214
+ chooseAndUploadImage,
1215
+ downloadFile,
1216
+ deleteFiles
1217
+ }
1218
+ ```
1219
+
1220
+ #### 使用示例
1221
+
1222
+ ```javascript
1223
+ // pages/upload/upload.js
1224
+ const { chooseAndUploadImage } = require('../../utils/cloudStorage')
1225
+
1226
+ Page({
1227
+ data: {
1228
+ imageUrls: []
1229
+ },
1230
+
1231
+ async handleUploadImage() {
1232
+ try {
1233
+ const res = await chooseAndUploadImage({
1234
+ count: 3,
1235
+ folder: 'user-uploads'
1236
+ })
1237
+
1238
+ this.setData({
1239
+ imageUrls: [...this.data.imageUrls, ...res.fileIDs]
1240
+ })
1241
+
1242
+ wx.showToast({
1243
+ title: '上传成功',
1244
+ icon: 'success'
1245
+ })
1246
+ } catch (error) {
1247
+ // 错误已在封装函数中处理
1248
+ }
1249
+ }
1250
+ })
1251
+ ```
1252
+
1253
+ ---
1254
+
1255
+ ### 云调用规范
1256
+
1257
+ #### 发送订阅消息
1258
+
1259
+ ```javascript
1260
+ // cloudfunctions/sendMessage/index.js
1261
+ const cloud = require('wx-server-sdk')
1262
+
1263
+ cloud.init({
1264
+ env: cloud.DYNAMIC_CURRENT_ENV
1265
+ })
1266
+
1267
+ /**
1268
+ * 发送订阅消息
1269
+ */
1270
+ exports.main = async (event, context) => {
1271
+ const { touser, templateId, page, data } = event
1272
+
1273
+ try {
1274
+ const result = await cloud.openapi.subscribeMessage.send({
1275
+ touser,
1276
+ page,
1277
+ data,
1278
+ templateId,
1279
+ miniprogramState: 'formal' // formal | trial | developer
1280
+ })
1281
+
1282
+ return {
1283
+ success: true,
1284
+ data: result
1285
+ }
1286
+ } catch (error) {
1287
+ console.error('发送订阅消息失败:', error)
1288
+ return {
1289
+ success: false,
1290
+ message: error.message
1291
+ }
1292
+ }
1293
+ }
1294
+ ```
1295
+
1296
+ #### 生成小程序码
1297
+
1298
+ ```javascript
1299
+ // cloudfunctions/generateQRCode/index.js
1300
+ const cloud = require('wx-server-sdk')
1301
+
1302
+ cloud.init({
1303
+ env: cloud.DYNAMIC_CURRENT_ENV
1304
+ })
1305
+
1306
+ /**
1307
+ * 生成小程序码
1308
+ */
1309
+ exports.main = async (event, context) => {
1310
+ const { scene, page, width = 430 } = event
1311
+
1312
+ try {
1313
+ const result = await cloud.openapi.wxacode.getUnlimited({
1314
+ scene,
1315
+ page,
1316
+ width,
1317
+ autoColor: false,
1318
+ lineColor: { r: 0, g: 0, b: 0 }
1319
+ })
1320
+
1321
+ // ✅ 上传到云存储
1322
+ const upload = await cloud.uploadFile({
1323
+ cloudPath: `qrcodes/${Date.now()}.png`,
1324
+ fileContent: result.buffer
1325
+ })
1326
+
1327
+ return {
1328
+ success: true,
1329
+ fileID: upload.fileID
1330
+ }
1331
+ } catch (error) {
1332
+ console.error('生成小程序码失败:', error)
1333
+ return {
1334
+ success: false,
1335
+ message: error.message
1336
+ }
1337
+ }
1338
+ }
1339
+ ```
1340
+
1341
+ ---
1342
+
1343
+ ### 云开发最佳实践
1344
+
1345
+ #### 1. 数据库权限配置
1346
+
1347
+ ```json
1348
+ // 云数据库权限配置(在云开发控制台设置)
1349
+ {
1350
+ "read": "doc._openid == auth.openid", // 只能读取自己的数据
1351
+ "write": "doc._openid == auth.openid" // 只能写入自己的数据
1352
+ }
1353
+ ```
1354
+
1355
+ #### 2. 云函数并发控制
1356
+
1357
+ ```javascript
1358
+ // cloudfunctions/batchProcess/index.js
1359
+
1360
+ /**
1361
+ * 批量处理数据(控制并发)
1362
+ */
1363
+ exports.main = async (event, context) => {
1364
+ const { items } = event
1365
+ const BATCH_SIZE = 5 // 每批处理 5 个
1366
+
1367
+ try {
1368
+ const results = []
1369
+
1370
+ // ✅ 分批处理,避免超时
1371
+ for (let i = 0; i < items.length; i += BATCH_SIZE) {
1372
+ const batch = items.slice(i, i + BATCH_SIZE)
1373
+ const batchResults = await Promise.all(
1374
+ batch.map(item => processItem(item))
1375
+ )
1376
+ results.push(...batchResults)
1377
+ }
1378
+
1379
+ return {
1380
+ success: true,
1381
+ data: results
1382
+ }
1383
+ } catch (error) {
1384
+ console.error('批量处理失败:', error)
1385
+ return {
1386
+ success: false,
1387
+ message: error.message
1388
+ }
1389
+ }
1390
+ }
1391
+ ```
1392
+
1393
+ #### 3. 云函数错误监控
1394
+
1395
+ ```javascript
1396
+ // cloudfunctions/common/errorHandler.js
1397
+
1398
+ /**
1399
+ * 统一错误处理
1400
+ */
1401
+ function handleError(error, functionName) {
1402
+ // ✅ 记录错误日志
1403
+ console.error(`[${functionName}] Error:`, {
1404
+ message: error.message,
1405
+ stack: error.stack,
1406
+ timestamp: new Date()
1407
+ })
1408
+
1409
+ // ✅ 错误上报(可接入第三方监控)
1410
+ // reportError(functionName, error)
1411
+
1412
+ // ✅ 返回统一错误格式
1413
+ return {
1414
+ success: false,
1415
+ code: error.code || 'UNKNOWN_ERROR',
1416
+ message: error.message || '服务器错误'
1417
+ }
1418
+ }
1419
+
1420
+ module.exports = {
1421
+ handleError
1422
+ }
1423
+ ```
1424
+
1425
+ #### 4. 云存储安全规则
1426
+
1427
+ ```javascript
1428
+ // ✅ 限制文件大小和类型
1429
+ async function uploadWithValidation(filePath, options = {}) {
1430
+ const {
1431
+ maxSize = 5 * 1024 * 1024, // 最大 5MB
1432
+ allowedTypes = ['image/jpeg', 'image/png']
1433
+ } = options
1434
+
1435
+ try {
1436
+ // ✅ 获取文件信息
1437
+ const fileInfo = await wx.getFileInfo({ filePath })
1438
+
1439
+ // ✅ 验证文件大小
1440
+ if (fileInfo.size > maxSize) {
1441
+ throw new Error('文件大小超过限制')
1442
+ }
1443
+
1444
+ // ✅ 验证文件类型(需要额外检查)
1445
+ // 实际项目中应该检查文件扩展名或 MIME 类型
1446
+
1447
+ // ✅ 上传文件
1448
+ const cloudPath = `uploads/${Date.now()}_${Math.random()}.jpg`
1449
+ return await uploadFile(filePath, cloudPath)
1450
+ } catch (error) {
1451
+ console.error('上传验证失败:', error)
1452
+ throw error
1453
+ }
1454
+ }
1455
+ ```
1456
+
1457
+ ---
1458
+
1459
+ ### 云开发安全规范
1460
+
1461
+ #### 1. 敏感操作必须在云函数中执行
1462
+
1463
+ ```javascript
1464
+ // ❌ 错误:在小程序端直接操作敏感数据
1465
+ // pages/order/order.js
1466
+ await db.collection('orders').add({
1467
+ data: {
1468
+ userId: 'xxx',
1469
+ totalPrice: 100, // 价格可被篡改!
1470
+ status: 'paid' // 状态可被篡改!
1471
+ }
1472
+ })
1473
+
1474
+ // ✅ 正确:通过云函数处理
1475
+ // cloudfunctions/createOrder/index.js
1476
+ exports.main = async (event, context) => {
1477
+ const { OPENID } = cloud.getWXContext()
1478
+ const { productId, count } = event
1479
+
1480
+ // ✅ 在服务端计算价格
1481
+ const product = await db.collection('products').doc(productId).get()
1482
+ const totalPrice = product.data.price * count
1483
+
1484
+ // ✅ 创建订单
1485
+ await db.collection('orders').add({
1486
+ data: {
1487
+ _openid: OPENID,
1488
+ productId,
1489
+ count,
1490
+ totalPrice, // 服务端计算,安全
1491
+ status: 'pending',
1492
+ createTime: new Date()
1493
+ }
1494
+ })
1495
+
1496
+ return { success: true }
1497
+ }
1498
+ ```
1499
+
1500
+ #### 2. 数据库查询优化
1501
+
1502
+ ```javascript
1503
+ // ✅ 使用索引
1504
+ // 在云开发控制台为常用查询字段创建索引
1505
+
1506
+ // ✅ 避免全表扫描
1507
+ // ❌ 错误
1508
+ const res = await db.collection('orders').get() // 可能超出限制
1509
+
1510
+ // ✅ 正确:添加条件和限制
1511
+ const res = await db.collection('orders')
1512
+ .where({
1513
+ _openid: OPENID,
1514
+ status: 'pending'
1515
+ })
1516
+ .limit(20)
1517
+ .get()
1518
+ ```
1519
+
1520
+ #### 3. 云函数冷启动优化
1521
+
1522
+ ```javascript
1523
+ // ✅ 复用全局变量
1524
+ const cloud = require('wx-server-sdk')
1525
+ cloud.init()
1526
+
1527
+ const db = cloud.database() // 在函数外初始化
1528
+
1529
+ exports.main = async (event, context) => {
1530
+ // 直接使用已初始化的 db
1531
+ const res = await db.collection('users').get()
1532
+ return res
1533
+ }
1534
+ ```
1535
+
1536
+ ---
1537
+
1538
+ ## 💾 本地存储规范
1539
+
1540
+ ### 存储封装
1541
+
1542
+ ```javascript
1543
+ // utils/storage.js
1544
+
1545
+ /**
1546
+ * 设置存储
1547
+ * @param {string} key - 键名
1548
+ * @param {any} value - 值
1549
+ * @returns {Promise}
1550
+ */
1551
+ function setStorage(key, value) {
1552
+ return new Promise((resolve, reject) => {
1553
+ wx.setStorage({
1554
+ key,
1555
+ data: value,
1556
+ success: resolve,
1557
+ fail: reject
1558
+ })
1559
+ })
1560
+ }
1561
+
1562
+ /**
1563
+ * 获取存储
1564
+ * @param {string} key - 键名
1565
+ * @returns {Promise}
1566
+ */
1567
+ function getStorage(key) {
1568
+ return new Promise((resolve, reject) => {
1569
+ wx.getStorage({
1570
+ key,
1571
+ success: (res) => resolve(res.data),
1572
+ fail: reject
1573
+ })
1574
+ })
1575
+ }
1576
+
1577
+ /**
1578
+ * 同步设置存储
1579
+ */
1580
+ function setStorageSync(key, value) {
1581
+ try {
1582
+ wx.setStorageSync(key, value)
1583
+ return true
1584
+ } catch (error) {
1585
+ console.error('存储失败:', error)
1586
+ return false
1587
+ }
1588
+ }
1589
+
1590
+ /**
1591
+ * 同步获取存储
1592
+ */
1593
+ function getStorageSync(key, defaultValue = null) {
1594
+ try {
1595
+ const value = wx.getStorageSync(key)
1596
+ return value !== '' ? value : defaultValue
1597
+ } catch (error) {
1598
+ console.error('读取存储失败:', error)
1599
+ return defaultValue
1600
+ }
1601
+ }
1602
+
1603
+ /**
1604
+ * 移除存储
1605
+ */
1606
+ function removeStorage(key) {
1607
+ return new Promise((resolve, reject) => {
1608
+ wx.removeStorage({
1609
+ key,
1610
+ success: resolve,
1611
+ fail: reject
1612
+ })
1613
+ })
1614
+ }
1615
+
1616
+ /**
1617
+ * 清空存储
1618
+ */
1619
+ function clearStorage() {
1620
+ return new Promise((resolve, reject) => {
1621
+ wx.clearStorage({
1622
+ success: resolve,
1623
+ fail: reject
1624
+ })
1625
+ })
1626
+ }
1627
+
1628
+ module.exports = {
1629
+ setStorage,
1630
+ getStorage,
1631
+ setStorageSync,
1632
+ getStorageSync,
1633
+ removeStorage,
1634
+ clearStorage
1635
+ }
1636
+ ```
1637
+
1638
+ ### 存储命名规范
1639
+
1640
+ ```javascript
1641
+ // config/constants.js
1642
+
1643
+ // ✅ 统一管理存储 key
1644
+ const STORAGE_KEYS = {
1645
+ USER_INFO: 'user_info',
1646
+ AUTH_TOKEN: 'auth_token',
1647
+ SETTINGS: 'app_settings',
1648
+ CACHE_DATA: 'cache_data',
1649
+ SEARCH_HISTORY: 'search_history'
1650
+ }
1651
+
1652
+ module.exports = {
1653
+ STORAGE_KEYS
1654
+ }
1655
+ ```
1656
+
1657
+ ---
1658
+
1659
+ ## 🎯 性能优化
1660
+
1661
+ ### setData 优化
1662
+
1663
+ ```javascript
1664
+ // ❌ 错误:频繁调用 setData
1665
+ for (let i = 0; i < 100; i++) {
1666
+ this.setData({
1667
+ count: i
1668
+ })
1669
+ }
1670
+
1671
+ // ✅ 正确:合并多次 setData
1672
+ const updates = {}
1673
+ for (let i = 0; i < 100; i++) {
1674
+ updates.count = i
1675
+ }
1676
+ this.setData(updates)
1677
+
1678
+ // ❌ 错误:setData 数据过大
1679
+ this.setData({
1680
+ hugeList: [...Array(1000).keys()] // 一次传输大量数据
1681
+ })
1682
+
1683
+ // ✅ 正确:只更新需要的字段
1684
+ this.setData({
1685
+ [`list[${index}].name`]: newName // 局部更新
1686
+ })
1687
+
1688
+ // ❌ 错误:不必要的数据
1689
+ this.setData({
1690
+ userInfo: {
1691
+ ...user,
1692
+ _rawData: rawData, // 不需要在视图中使用的数据
1693
+ _cache: cache
1694
+ }
1695
+ })
1696
+
1697
+ // ✅ 正确:只传必要数据
1698
+ this.setData({
1699
+ userInfo: {
1700
+ id: user.id,
1701
+ name: user.name,
1702
+ avatar: user.avatar
1703
+ }
1704
+ })
1705
+ ```
1706
+
1707
+ ### 列表渲染优化
1708
+
1709
+ ```xml
1710
+ <!-- ✅ 使用虚拟列表(长列表) -->
1711
+ <recycle-view
1712
+ batch="{{batchSetRecycleData}}"
1713
+ height="{{height}}"
1714
+ >
1715
+ <recycle-item wx:for="{{list}}" wx:key="id">
1716
+ <view>{{item.name}}</view>
1717
+ </recycle-item>
1718
+ </recycle-view>
1719
+
1720
+ <!-- ✅ 使用分页加载 -->
1721
+ <scroll-view
1722
+ scroll-y
1723
+ bindscrolltolower="onReachBottom"
1724
+ lower-threshold="100"
1725
+ >
1726
+ <view wx:for="{{list}}" wx:key="id">
1727
+ {{item.name}}
1728
+ </view>
1729
+ </scroll-view>
1730
+
1731
+ <!-- ✅ 图片懒加载 -->
1732
+ <image
1733
+ src="{{item.image}}"
1734
+ lazy-load
1735
+ mode="aspectFill"
1736
+ />
1737
+ ```
1738
+
1739
+ ### 代码分包
1740
+
1741
+ ```json
1742
+ // app.json
1743
+ {
1744
+ "pages": [
1745
+ "pages/index/index",
1746
+ "pages/user/user"
1747
+ ],
1748
+ "subpackages": [
1749
+ {
1750
+ "root": "packageA",
1751
+ "pages": [
1752
+ "pages/detail/detail",
1753
+ "pages/list/list"
1754
+ ]
1755
+ },
1756
+ {
1757
+ "root": "packageB",
1758
+ "name": "vip",
1759
+ "pages": [
1760
+ "pages/vip/vip"
1761
+ ],
1762
+ "independent": true // 独立分包
1763
+ }
1764
+ ],
1765
+ "preloadRule": {
1766
+ "pages/index/index": {
1767
+ "network": "all",
1768
+ "packages": ["packageA"]
1769
+ }
1770
+ }
1771
+ }
1772
+ ```
1773
+
1774
+ ---
1775
+
1776
+ ## 🔐 安全规范
1777
+
1778
+ ### 敏感信息处理
1779
+
1780
+ ```javascript
1781
+ // ❌ 错误:直接存储敏感信息
1782
+ wx.setStorageSync('password', '123456')
1783
+
1784
+ // ✅ 正确:加密后存储
1785
+ const CryptoJS = require('crypto-js')
1786
+
1787
+ function encryptData(data, key) {
1788
+ return CryptoJS.AES.encrypt(JSON.stringify(data), key).toString()
1789
+ }
1790
+
1791
+ function decryptData(ciphertext, key) {
1792
+ const bytes = CryptoJS.AES.decrypt(ciphertext, key)
1793
+ return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
1794
+ }
1795
+ ```
1796
+
1797
+ ### XSS 防护
1798
+
1799
+ ```javascript
1800
+ // ✅ 转义用户输入
1801
+ function escapeHtml(text) {
1802
+ const map = {
1803
+ '&': '&amp;',
1804
+ '<': '&lt;',
1805
+ '>': '&gt;',
1806
+ '"': '&quot;',
1807
+ "'": '&#039;'
1808
+ }
1809
+ return text.replace(/[&<>"']/g, (m) => map[m])
1810
+ }
1811
+
1812
+ // 使用
1813
+ this.setData({
1814
+ safeContent: escapeHtml(userInput)
1815
+ })
1816
+ ```
1817
+
1818
+ ### 接口鉴权
1819
+
1820
+ ```javascript
1821
+ // utils/auth.js
1822
+
1823
+ const TOKEN_KEY = 'auth_token'
1824
+ const REFRESH_TOKEN_KEY = 'refresh_token'
1825
+
1826
+ /**
1827
+ * 保存 Token
1828
+ */
1829
+ function saveToken(token, refreshToken) {
1830
+ wx.setStorageSync(TOKEN_KEY, token)
1831
+ if (refreshToken) {
1832
+ wx.setStorageSync(REFRESH_TOKEN_KEY, refreshToken)
1833
+ }
1834
+ }
1835
+
1836
+ /**
1837
+ * 获取 Token
1838
+ */
1839
+ function getToken() {
1840
+ return wx.getStorageSync(TOKEN_KEY)
1841
+ }
1842
+
1843
+ /**
1844
+ * 清除 Token
1845
+ */
1846
+ function clearToken() {
1847
+ wx.removeStorageSync(TOKEN_KEY)
1848
+ wx.removeStorageSync(REFRESH_TOKEN_KEY)
1849
+ }
1850
+
1851
+ /**
1852
+ * 检查登录状态
1853
+ */
1854
+ function checkLogin() {
1855
+ return !!getToken()
1856
+ }
1857
+
1858
+ /**
1859
+ * 刷新 Token
1860
+ */
1861
+ async function refreshToken() {
1862
+ const refreshToken = wx.getStorageSync(REFRESH_TOKEN_KEY)
1863
+
1864
+ if (!refreshToken) {
1865
+ throw new Error('No refresh token')
1866
+ }
1867
+
1868
+ try {
1869
+ const res = await authApi.refreshToken({ refreshToken })
1870
+ saveToken(res.data.token, res.data.refreshToken)
1871
+ return res.data.token
1872
+ } catch (error) {
1873
+ clearToken()
1874
+ throw error
1875
+ }
1876
+ }
1877
+
1878
+ module.exports = {
1879
+ saveToken,
1880
+ getToken,
1881
+ clearToken,
1882
+ checkLogin,
1883
+ refreshToken
1884
+ }
1885
+ ```
1886
+
1887
+ ---
1888
+
1889
+ ## 📱 用户体验
1890
+
1891
+ ### 加载状态
1892
+
1893
+ ```javascript
1894
+ // ✅ 统一的 loading 管理
1895
+ class LoadingManager {
1896
+ constructor() {
1897
+ this.loadingCount = 0
1898
+ }
1899
+
1900
+ show(title = '加载中...') {
1901
+ this.loadingCount++
1902
+ if (this.loadingCount === 1) {
1903
+ wx.showLoading({
1904
+ title,
1905
+ mask: true
1906
+ })
1907
+ }
1908
+ }
1909
+
1910
+ hide() {
1911
+ this.loadingCount--
1912
+ if (this.loadingCount === 0) {
1913
+ wx.hideLoading()
1914
+ }
1915
+ }
1916
+
1917
+ clear() {
1918
+ this.loadingCount = 0
1919
+ wx.hideLoading()
1920
+ }
1921
+ }
1922
+
1923
+ const loadingManager = new LoadingManager()
1924
+
1925
+ module.exports = loadingManager
1926
+ ```
1927
+
1928
+ ### 错误处理
1929
+
1930
+ ```javascript
1931
+ // ✅ 统一错误处理
1932
+ function handleError(error, showToast = true) {
1933
+ console.error('Error:', error)
1934
+
1935
+ if (showToast) {
1936
+ const message = error.message || '操作失败,请重试'
1937
+ wx.showToast({
1938
+ title: message,
1939
+ icon: 'none',
1940
+ duration: 2000
1941
+ })
1942
+ }
1943
+
1944
+ // ✅ 上报错误到监控平台
1945
+ if (typeof wx.reportMonitor === 'function') {
1946
+ wx.reportMonitor('error', {
1947
+ message: error.message,
1948
+ stack: error.stack,
1949
+ timestamp: Date.now()
1950
+ })
1951
+ }
1952
+ }
1953
+
1954
+ module.exports = {
1955
+ handleError
1956
+ }
1957
+ ```
1958
+
1959
+ ### 交互反馈
1960
+
1961
+ ```javascript
1962
+ // ✅ 完善的用户反馈
1963
+
1964
+ // 成功提示
1965
+ wx.showToast({
1966
+ title: '操作成功',
1967
+ icon: 'success',
1968
+ duration: 2000
1969
+ })
1970
+
1971
+ // 失败提示
1972
+ wx.showToast({
1973
+ title: '操作失败',
1974
+ icon: 'none',
1975
+ duration: 2000
1976
+ })
1977
+
1978
+ // 确认对话框
1979
+ wx.showModal({
1980
+ title: '提示',
1981
+ content: '确认删除这条记录吗?',
1982
+ confirmText: '删除',
1983
+ confirmColor: '#ff4444',
1984
+ success: (res) => {
1985
+ if (res.confirm) {
1986
+ // 用户确认
1987
+ this.handleDelete()
1988
+ }
1989
+ }
1990
+ })
1991
+
1992
+ // 操作菜单
1993
+ wx.showActionSheet({
1994
+ itemList: ['拍照', '从相册选择'],
1995
+ success: (res) => {
1996
+ if (res.tapIndex === 0) {
1997
+ // 拍照
1998
+ } else if (res.tapIndex === 1) {
1999
+ // 选择照片
2000
+ }
2001
+ }
2002
+ })
2003
+ ```
2004
+
2005
+ ---
2006
+
2007
+ ## 🧪 调试与测试
2008
+
2009
+ ### 调试技巧
2010
+
2011
+ ```javascript
2012
+ // ✅ 环境判断
2013
+ const isDev = process.env.NODE_ENV === 'development'
2014
+
2015
+ if (isDev) {
2016
+ console.log('Debug info:', data)
2017
+ }
2018
+
2019
+ // ✅ 性能监控
2020
+ const startTime = Date.now()
2021
+ // ... 执行操作
2022
+ const endTime = Date.now()
2023
+ console.log(`操作耗时: ${endTime - startTime}ms`)
2024
+
2025
+ // ✅ 使用 vConsole
2026
+ if (isDev) {
2027
+ const VConsole = require('vconsole')
2028
+ new VConsole()
2029
+ }
2030
+ ```
2031
+
2032
+ ### 单元测试
2033
+
2034
+ ```javascript
2035
+ // test/utils/format.test.js
2036
+ const { formatDate, formatNumber } = require('../../utils/format')
2037
+
2038
+ describe('format utils', () => {
2039
+ test('formatDate should format timestamp correctly', () => {
2040
+ const timestamp = 1609459200000 // 2021-01-01 00:00:00
2041
+ expect(formatDate(timestamp)).toBe('2021-01-01')
2042
+ })
2043
+
2044
+ test('formatNumber should format number with comma', () => {
2045
+ expect(formatNumber(1234567)).toBe('1,234,567')
2046
+ })
2047
+ })
2048
+ ```
2049
+
2050
+ ---
2051
+
2052
+ ## ❌ 禁止模式
2053
+
2054
+ ### 代码层面
2055
+
2056
+ ```javascript
2057
+ // ❌ 直接修改 data
2058
+ this.data.count = 10 // 不会触发视图更新
2059
+
2060
+ // ✅ 使用 setData
2061
+ this.setData({
2062
+ count: 10
2063
+ })
2064
+
2065
+ // ❌ 在 WXML 中写复杂逻辑
2066
+ <view>{{list.filter(i => i.active).length}}</view>
2067
+
2068
+ // ✅ 在 JS 中计算
2069
+ computed() {
2070
+ return {
2071
+ activeCount: this.data.list.filter(i => i.active).length
2072
+ }
2073
+ }
2074
+
2075
+ // ❌ 没有错误处理
2076
+ async fetchData() {
2077
+ const res = await api.getData()
2078
+ this.setData({ data: res.data })
2079
+ }
2080
+
2081
+ // ✅ 完善的错误处理
2082
+ async fetchData() {
2083
+ try {
2084
+ const res = await api.getData()
2085
+ this.setData({ data: res.data })
2086
+ } catch (error) {
2087
+ console.error('获取数据失败:', error)
2088
+ wx.showToast({
2089
+ title: '加载失败',
2090
+ icon: 'none'
2091
+ })
2092
+ }
2093
+ }
2094
+ ```
2095
+
2096
+ ### 性能陷阱
2097
+
2098
+ ```javascript
2099
+ // ❌ 在循环中频繁调用 setData
2100
+ for (let i = 0; i < items.length; i++) {
2101
+ this.setData({
2102
+ [`items[${i}]`]: items[i]
2103
+ })
2104
+ }
2105
+
2106
+ // ✅ 一次性更新
2107
+ this.setData({
2108
+ items: items
2109
+ })
2110
+
2111
+ // ❌ setData 传递大量无用数据
2112
+ this.setData({
2113
+ hugeObject: {
2114
+ // 包含很多视图不需要的数据
2115
+ _internalState: {},
2116
+ _cache: {},
2117
+ displayData: {}
2118
+ }
2119
+ })
2120
+
2121
+ // ✅ 只传递必要数据
2122
+ this.setData({
2123
+ displayData: hugeObject.displayData
2124
+ })
2125
+ ```
2126
+
2127
+ ---
2128
+
2129
+ ## ✅ 最佳实践总结
2130
+
2131
+ ### 开发规范清单
2132
+
2133
+ - [ ] **文件组织**: 遵循推荐的目录结构
2134
+ - [ ] **命名规范**: 使用 kebab-case/camelCase
2135
+ - [ ] **代码注释**: 为复杂逻辑添加注释
2136
+ - [ ] **错误处理**: 所有异步操作都有 try-catch
2137
+ - [ ] **加载状态**: 异步操作显示 loading
2138
+ - [ ] **用户反馈**: 操作结果有明确提示
2139
+
2140
+ ### 性能优化清单
2141
+
2142
+ - [ ] **setData 优化**: 减少调用频率,控制数据大小
2143
+ - [ ] **列表优化**: 长列表使用虚拟列表或分页
2144
+ - [ ] **图片优化**: 使用 lazy-load,压缩图片
2145
+ - [ ] **代码分包**: 合理使用分包和预加载
2146
+ - [ ] **避免白屏**: 骨架屏/占位图
2147
+
2148
+ ### 安全规范清单
2149
+
2150
+ - [ ] **敏感信息**: 加密存储,不明文传输
2151
+ - [ ] **XSS 防护**: 转义用户输入
2152
+ - [ ] **接口鉴权**: Token 验证,刷新机制
2153
+ - [ ] **HTTPS**: 所有接口使用 HTTPS
2154
+ - [ ] **权限校验**: 敏感操作二次确认
2155
+
2156
+ ### 用户体验清单
2157
+
2158
+ - [ ] **加载提示**: 所有异步操作有反馈
2159
+ - [ ] **错误提示**: 清晰的错误信息
2160
+ - [ ] **空状态**: 无数据时显示空状态
2161
+ - [ ] **下拉刷新**: 列表支持下拉刷新
2162
+ - [ ] **上拉加载**: 长列表支持分页加载
2163
+
2164
+ ---
2165
+
2166
+ ## 📚 参考资源
2167
+
2168
+ - [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
2169
+ - [微信小程序开发指南](https://developers.weixin.qq.com/ebook?action=get_post_info&docid=0008aeea9a8978b00086a685851c0a)
2170
+ - [小程序性能优化指南](https://developers.weixin.qq.com/miniprogram/dev/framework/performance/)
2171
+ - [小程序安全指南](https://developers.weixin.qq.com/miniprogram/dev/framework/security.html)
2172
+
2173
+ ---
2174
+
2175
+ **维护团队**: MTA工作室
2176
+ **版本**: 1.0.0
2177
+ **更新日期**: 2025-12-17