@coze-arch/cli 0.0.1-alpha.dffbaa → 0.0.1-alpha.e02657
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.
- package/lib/__templates__/expo/.coze +7 -2
- package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +46 -0
- package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +229 -0
- package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +47 -0
- package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +34 -0
- package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +46 -0
- package/lib/__templates__/expo/README.md +68 -7
- package/lib/__templates__/expo/_gitignore +1 -1
- package/lib/__templates__/expo/_npmrc +2 -4
- package/lib/__templates__/expo/client/app/+not-found.tsx +15 -64
- package/lib/__templates__/expo/client/app/_layout.tsx +15 -12
- package/lib/__templates__/expo/client/app/index.tsx +1 -0
- package/lib/__templates__/expo/client/app.config.ts +76 -0
- package/lib/__templates__/expo/client/components/Screen.tsx +1 -17
- package/lib/__templates__/expo/client/components/ThemedText.tsx +33 -0
- package/lib/__templates__/expo/client/components/ThemedView.tsx +37 -0
- package/lib/__templates__/expo/client/constants/theme.ts +117 -58
- package/lib/__templates__/expo/client/declarations.d.ts +5 -0
- package/lib/__templates__/expo/{eslint.config.mjs → client/eslint.config.mjs} +33 -10
- package/lib/__templates__/expo/client/hooks/useColorScheme.tsx +48 -0
- package/lib/__templates__/expo/client/hooks/useSafeRouter.ts +152 -0
- package/lib/__templates__/expo/client/hooks/useTheme.ts +26 -6
- package/lib/__templates__/expo/client/metro.config.js +121 -0
- package/lib/__templates__/expo/client/package.json +95 -0
- package/lib/__templates__/expo/client/screens/demo/index.tsx +25 -0
- package/lib/__templates__/expo/client/screens/demo/styles.ts +28 -0
- package/lib/__templates__/expo/client/scripts/install-missing-deps.js +1 -0
- package/lib/__templates__/expo/client/tsconfig.json +24 -0
- package/lib/__templates__/expo/client/utils/index.ts +22 -0
- package/lib/__templates__/expo/eslint-plugins/fontawesome6/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/fontawesome6/names.js +1889 -0
- package/lib/__templates__/expo/eslint-plugins/fontawesome6/rule.js +174 -0
- package/lib/__templates__/expo/eslint-plugins/fontawesome6/v5-only-names.js +388 -0
- package/lib/__templates__/expo/eslint-plugins/react-native/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/react-native/rule.js +64 -0
- package/lib/__templates__/expo/eslint-plugins/reanimated/index.js +9 -0
- package/lib/__templates__/expo/eslint-plugins/reanimated/rule.js +88 -0
- package/lib/__templates__/expo/package.json +16 -103
- package/lib/__templates__/expo/patches/expo@54.0.33.patch +45 -0
- package/lib/__templates__/expo/pnpm-lock.yaml +1375 -3165
- package/lib/__templates__/expo/pnpm-workspace.yaml +3 -0
- package/lib/__templates__/expo/server/build.js +21 -0
- package/lib/__templates__/expo/server/package.json +32 -0
- package/lib/__templates__/expo/server/src/index.ts +20 -0
- package/lib/__templates__/expo/server/tsconfig.json +24 -0
- package/lib/__templates__/expo/template.config.js +57 -0
- package/lib/__templates__/expo/tsconfig.json +1 -24
- package/lib/__templates__/nextjs/.babelrc +15 -0
- package/lib/__templates__/nextjs/.coze +1 -0
- package/lib/__templates__/nextjs/_npmrc +1 -0
- package/lib/__templates__/nextjs/next.config.ts +12 -0
- package/lib/__templates__/nextjs/package.json +11 -2
- package/lib/__templates__/nextjs/pnpm-lock.yaml +2606 -1723
- package/lib/__templates__/nextjs/scripts/prepare.sh +9 -0
- package/lib/__templates__/nextjs/src/app/globals.css +109 -89
- package/lib/__templates__/nextjs/src/app/layout.tsx +5 -14
- package/lib/__templates__/nextjs/src/app/page.tsx +18 -48
- package/lib/__templates__/nextjs/src/components/ui/resizable.tsx +29 -22
- package/lib/__templates__/nextjs/src/components/ui/sidebar.tsx +228 -230
- package/lib/__templates__/nextjs/template.config.js +67 -2
- package/lib/__templates__/taro/.coze +14 -0
- package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.sh +19 -0
- package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +13 -0
- package/lib/__templates__/taro/.cozeproj/scripts/dev_build.sh +17 -0
- package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +58 -0
- package/lib/__templates__/taro/.cozeproj/scripts/init_env.sh +5 -0
- package/lib/__templates__/taro/.cozeproj/scripts/pack.sh +1 -0
- package/lib/__templates__/taro/README.md +687 -0
- package/lib/__templates__/taro/_gitignore +40 -0
- package/lib/__templates__/taro/_npmrc +18 -0
- package/lib/__templates__/taro/babel.config.js +12 -0
- package/lib/__templates__/taro/config/dev.ts +9 -0
- package/lib/__templates__/taro/config/index.ts +173 -0
- package/lib/__templates__/taro/config/prod.ts +35 -0
- package/lib/__templates__/taro/eslint.config.mjs +57 -0
- package/lib/__templates__/taro/key/private.appid.key +0 -0
- package/lib/__templates__/taro/package.json +95 -0
- package/lib/__templates__/taro/pnpm-lock.yaml +22430 -0
- package/lib/__templates__/taro/pnpm-workspace.yaml +2 -0
- package/lib/__templates__/taro/project.config.json +15 -0
- package/lib/__templates__/taro/server/nest-cli.json +10 -0
- package/lib/__templates__/taro/server/package.json +38 -0
- package/lib/__templates__/taro/server/src/app.controller.ts +23 -0
- package/lib/__templates__/taro/server/src/app.module.ts +10 -0
- package/lib/__templates__/taro/server/src/app.service.ts +8 -0
- package/lib/__templates__/taro/server/src/interceptors/http-status.interceptor.ts +23 -0
- package/lib/__templates__/taro/server/src/main.ts +37 -0
- package/lib/__templates__/taro/server/tsconfig.json +24 -0
- package/lib/__templates__/taro/src/app.config.ts +11 -0
- package/lib/__templates__/taro/src/app.css +52 -0
- package/lib/__templates__/taro/src/app.ts +14 -0
- package/lib/__templates__/taro/src/index.html +20 -0
- package/lib/__templates__/taro/src/network.ts +39 -0
- package/lib/__templates__/taro/src/pages/index/index.config.ts +3 -0
- package/lib/__templates__/taro/src/pages/index/index.css +1 -0
- package/lib/__templates__/taro/src/pages/index/index.tsx +33 -0
- package/lib/__templates__/taro/src/utils/h5-styles.ts +22 -0
- package/lib/__templates__/taro/src/utils/wx-debug.ts +23 -0
- package/lib/__templates__/taro/stylelint.config.mjs +4 -0
- package/lib/__templates__/taro/template.config.js +68 -0
- package/lib/__templates__/taro/tsconfig.json +29 -0
- package/lib/__templates__/taro/types/global.d.ts +32 -0
- package/lib/__templates__/templates.json +93 -43
- package/lib/__templates__/vite/.coze +1 -0
- package/lib/__templates__/vite/_npmrc +1 -0
- package/lib/__templates__/vite/eslint.config.mjs +9 -0
- package/lib/__templates__/vite/package.json +11 -2
- package/lib/__templates__/vite/pnpm-lock.yaml +3232 -243
- package/lib/__templates__/vite/scripts/prepare.sh +9 -0
- package/lib/__templates__/vite/src/main.ts +17 -48
- package/lib/__templates__/vite/template.config.js +67 -6
- package/lib/cli.js +1026 -134
- package/package.json +8 -3
- package/lib/__templates__/expo/.cozeproj/scripts/deploy_build.sh +0 -116
- package/lib/__templates__/expo/.cozeproj/scripts/deploy_run.sh +0 -239
- package/lib/__templates__/expo/app.json +0 -63
- package/lib/__templates__/expo/babel.config.js +0 -9
- package/lib/__templates__/expo/client/app/(tabs)/_layout.tsx +0 -43
- package/lib/__templates__/expo/client/app/(tabs)/home.tsx +0 -1
- package/lib/__templates__/expo/client/app/(tabs)/index.tsx +0 -7
- package/lib/__templates__/expo/client/hooks/useColorScheme.ts +0 -1
- package/lib/__templates__/expo/client/index.js +0 -12
- package/lib/__templates__/expo/client/screens/home/index.tsx +0 -51
- package/lib/__templates__/expo/client/screens/home/styles.ts +0 -60
- package/lib/__templates__/expo/metro.config.js +0 -53
- package/lib/__templates__/expo/src/index.ts +0 -12
- package/lib/__templates__/nextjs/.vscode/settings.json +0 -121
- package/lib/__templates__/vite/.vscode/settings.json +0 -7
- /package/lib/__templates__/expo/{eslint-formatter-simple.mjs → client/eslint-formatter-simple.mjs} +0 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
# Coze Mini Program
|
|
2
|
+
|
|
3
|
+
这是一个基于 [Taro 4](https://docs.taro.zone/docs/) + [Nest.js](https://nestjs.com/) 的前后端分离项目,由扣子编程 CLI 创建。
|
|
4
|
+
|
|
5
|
+
## 技术栈
|
|
6
|
+
|
|
7
|
+
- **整体框架**: Taro 4.1.9
|
|
8
|
+
- **语言**: TypeScript 5.4.5
|
|
9
|
+
- **渲染**: React 18.0.0
|
|
10
|
+
- **样式**: TailwindCSS 4.1.18
|
|
11
|
+
- **Tailwind 适配层**: weapp-tailwindcss 4.9.2
|
|
12
|
+
- **状态管理**: Zustand 5.0.9
|
|
13
|
+
- **图标库**: lucide-react 0.511.0
|
|
14
|
+
- **工程化**: Vite 4.2.0
|
|
15
|
+
- **包管理**: pnpm
|
|
16
|
+
- **运行时**: Node.js >= 18
|
|
17
|
+
- **服务端**: NestJS 10.4.15
|
|
18
|
+
- **数据库 ORM**: Drizzle ORM 0.45.1
|
|
19
|
+
- **类型校验**: Zod 4.3.5
|
|
20
|
+
|
|
21
|
+
## 项目结构
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
├── .cozeproj/ # Coze 平台配置
|
|
25
|
+
│ └── scripts/ # 构建和运行脚本
|
|
26
|
+
├── config/ # Taro 构建配置
|
|
27
|
+
│ ├── index.ts # 主配置文件
|
|
28
|
+
│ ├── dev.ts # 开发环境配置
|
|
29
|
+
│ └── prod.ts # 生产环境配置
|
|
30
|
+
├── server/ # NestJS 后端服务
|
|
31
|
+
│ └── src/
|
|
32
|
+
│ ├── main.ts # 服务入口
|
|
33
|
+
│ ├── app.module.ts # 根模块
|
|
34
|
+
│ ├── app.controller.ts # 应用控制器
|
|
35
|
+
│ └── app.service.ts # 应用服务
|
|
36
|
+
├── src/ # 前端源码
|
|
37
|
+
│ ├── pages/ # 页面组件
|
|
38
|
+
│ ├── utils/ # 工具函数
|
|
39
|
+
│ ├── app.ts # 应用入口
|
|
40
|
+
│ ├── app.config.ts # 应用配置
|
|
41
|
+
│ └── app.css # 全局样式
|
|
42
|
+
├── types/ # TypeScript 类型定义
|
|
43
|
+
├── key/ # 小程序密钥(CI 上传用)
|
|
44
|
+
├── .env.local # 环境变量
|
|
45
|
+
└── project.config.json # 微信小程序项目配置
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 快速开始
|
|
49
|
+
|
|
50
|
+
### 安装依赖
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 本地开发
|
|
57
|
+
|
|
58
|
+
同时启动 H5 前端和 NestJS 后端:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pnpm dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- 前端地址:http://localhost:5000
|
|
65
|
+
- 后端地址:http://localhost:3000
|
|
66
|
+
|
|
67
|
+
单独启动:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pnpm dev:web # 仅 H5 前端
|
|
71
|
+
pnpm dev:weapp # 仅微信小程序
|
|
72
|
+
pnpm dev:server # 仅后端服务
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 构建
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pnpm build # 构建所有(H5 + 小程序 + 后端)
|
|
79
|
+
pnpm build:web # 仅构建 H5,输出到 dist-web
|
|
80
|
+
pnpm build:weapp # 仅构建微信小程序,输出到 dist
|
|
81
|
+
pnpm build:server # 仅构建后端
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 预览小程序
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm preview:weapp # 构建并生成预览小程序二维码
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 前端核心开发规范
|
|
91
|
+
|
|
92
|
+
### 新建页面流程
|
|
93
|
+
|
|
94
|
+
1. 在 \`src/pages/\` 下创建页面目录
|
|
95
|
+
2. 创建 \`index.tsx\`(页面组件)
|
|
96
|
+
3. 创建 \`index.config.ts\`(页面配置)
|
|
97
|
+
4. 创建 \`index.css\`(页面样式,可选)
|
|
98
|
+
5. 在 \`src/app.config.ts\` 的 \`pages\` 数组中注册页面路径
|
|
99
|
+
|
|
100
|
+
或使用 Taro 脚手架命令:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pnpm new # 交互式创建页面/组件
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 常用 Taro 组件
|
|
107
|
+
|
|
108
|
+
引入方式
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { Text } from '@tarojs/components'
|
|
112
|
+
```
|
|
113
|
+
- 基础组件
|
|
114
|
+
- Text
|
|
115
|
+
- Icon
|
|
116
|
+
- Progress
|
|
117
|
+
- RichText
|
|
118
|
+
- 表单组件
|
|
119
|
+
- Button
|
|
120
|
+
- Checkbox
|
|
121
|
+
- CheckboxGroup
|
|
122
|
+
- Editor
|
|
123
|
+
- Form
|
|
124
|
+
- Input
|
|
125
|
+
- Label
|
|
126
|
+
- Picker
|
|
127
|
+
- PickerView
|
|
128
|
+
- PickerViewColumn
|
|
129
|
+
- Radio
|
|
130
|
+
- RadioGroup
|
|
131
|
+
- Slider
|
|
132
|
+
- Switch
|
|
133
|
+
- Textarea
|
|
134
|
+
- 导航组件
|
|
135
|
+
- FunctionalPageNavigator
|
|
136
|
+
- NavigationBar
|
|
137
|
+
- Navigator
|
|
138
|
+
- TabItem
|
|
139
|
+
- Tabs
|
|
140
|
+
- 媒体组件
|
|
141
|
+
- Camera
|
|
142
|
+
- Image
|
|
143
|
+
- Video
|
|
144
|
+
- 视图容器
|
|
145
|
+
- ScrollView
|
|
146
|
+
- Swiper
|
|
147
|
+
- SwiperItem
|
|
148
|
+
- View
|
|
149
|
+
|
|
150
|
+
### 路径别名
|
|
151
|
+
|
|
152
|
+
项目配置了 `@/*` 路径别名指向 `src/*`:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { SomeComponent } from '@/components/SomeComponent'
|
|
156
|
+
import { useUserStore } from '@/stores/user'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 代码模板
|
|
160
|
+
|
|
161
|
+
#### 页面组件 (TypeScript + React)
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// src/pages/example/index.tsx
|
|
165
|
+
import { View, Text } from '@tarojs/components'
|
|
166
|
+
import { useLoad, useDidShow } from '@tarojs/taro'
|
|
167
|
+
import type { FC } from 'react'
|
|
168
|
+
import './index.css'
|
|
169
|
+
|
|
170
|
+
const ExamplePage: FC = () => {
|
|
171
|
+
useLoad(() => {
|
|
172
|
+
console.log('Page loaded.')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
useDidShow(() => {
|
|
176
|
+
console.log('Page showed.')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<View className="flex flex-col items-center p-4">
|
|
181
|
+
<Text className="text-lg font-bold">Hello Taro!</Text>
|
|
182
|
+
</View>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default ExamplePage
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### 页面配置
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// src/pages/example/index.config.ts
|
|
193
|
+
import { definePageConfig } from '@tarojs/taro'
|
|
194
|
+
|
|
195
|
+
export default definePageConfig({
|
|
196
|
+
navigationBarTitleText: '示例页面',
|
|
197
|
+
enablePullDownRefresh: true,
|
|
198
|
+
backgroundTextStyle: 'dark',
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### 应用配置
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// src/app.config.ts
|
|
206
|
+
import { defineAppConfig } from '@tarojs/taro'
|
|
207
|
+
|
|
208
|
+
export default defineAppConfig({
|
|
209
|
+
pages: [
|
|
210
|
+
'pages/index/index',
|
|
211
|
+
'pages/example/index',
|
|
212
|
+
],
|
|
213
|
+
window: {
|
|
214
|
+
backgroundTextStyle: 'light',
|
|
215
|
+
navigationBarBackgroundColor: '#fff',
|
|
216
|
+
navigationBarTitleText: 'App',
|
|
217
|
+
navigationBarTextStyle: 'black',
|
|
218
|
+
},
|
|
219
|
+
// TabBar 配置 (可选)
|
|
220
|
+
// tabBar: {
|
|
221
|
+
// list: [
|
|
222
|
+
// { pagePath: 'pages/index/index', text: '首页' },
|
|
223
|
+
// ],
|
|
224
|
+
// },
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 发送请求
|
|
229
|
+
|
|
230
|
+
**IMPORTANT: 禁止直接使用 Taro.request、Taro.uploadFile、Taro.downloadFile,使用 Network.request、Network.uploadFile、Network.downloadFile 替代。**
|
|
231
|
+
|
|
232
|
+
Network 是对 Taro.request、Taro.uploadFile、Taro.downloadFile 的封装,自动添加项目域名前缀,参数与 Taro 一致。
|
|
233
|
+
|
|
234
|
+
✅ 正确使用方式
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { Network } from '@/network'
|
|
238
|
+
|
|
239
|
+
// GET 请求
|
|
240
|
+
const data = await Network.request({
|
|
241
|
+
url: '/api/hello'
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// POST 请求
|
|
245
|
+
const result = await Network.request({
|
|
246
|
+
url: '/api/user/login',
|
|
247
|
+
method: 'POST',
|
|
248
|
+
data: { username, password }
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// 文件上传
|
|
252
|
+
await Network.uploadFile({
|
|
253
|
+
url: '/api/upload',
|
|
254
|
+
filePath: tempFilePath,
|
|
255
|
+
name: 'file'
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// 文件下载
|
|
259
|
+
await Network.downloadFile({
|
|
260
|
+
url: '/api/download/file.pdf'
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
❌ 错误用法
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import Taro from '@tarojs/taro'
|
|
268
|
+
|
|
269
|
+
// ❌ 会导致自动域名拼接无法生效,除非是特殊指定域名
|
|
270
|
+
const data = await Network.request({
|
|
271
|
+
url: 'http://localhost/api/hello'
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// ❌ 不要直接使用 Taro.request
|
|
275
|
+
await Taro.request({ url: '/api/hello' })
|
|
276
|
+
|
|
277
|
+
// ❌ 不要直接使用 Taro.uploadFile
|
|
278
|
+
await Taro.uploadFile({ url: '/api/upload', filePath, name: 'file' })
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Zustand 状态管理
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// src/stores/user.ts
|
|
285
|
+
import { create } from 'zustand'
|
|
286
|
+
|
|
287
|
+
interface UserState {
|
|
288
|
+
userInfo: UserInfo | null
|
|
289
|
+
token: string
|
|
290
|
+
setUserInfo: (info: UserInfo) => void
|
|
291
|
+
setToken: (token: string) => void
|
|
292
|
+
logout: () => void
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
interface UserInfo {
|
|
296
|
+
id: string
|
|
297
|
+
name: string
|
|
298
|
+
avatar: string
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export const useUserStore = create<UserState>((set) => ({
|
|
302
|
+
userInfo: null,
|
|
303
|
+
token: '',
|
|
304
|
+
setUserInfo: (info) => set({ userInfo: info }),
|
|
305
|
+
setToken: (token) => set({ token }),
|
|
306
|
+
logout: () => set({ userInfo: null, token: '' }),
|
|
307
|
+
}))
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Taro 生命周期 Hooks
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import {
|
|
314
|
+
useLoad, // 页面加载 (onLoad)
|
|
315
|
+
useReady, // 页面初次渲染完成 (onReady)
|
|
316
|
+
useDidShow, // 页面显示 (onShow)
|
|
317
|
+
useDidHide, // 页面隐藏 (onHide)
|
|
318
|
+
usePullDownRefresh, // 下拉刷新 (onPullDownRefresh)
|
|
319
|
+
useReachBottom, // 触底加载 (onReachBottom)
|
|
320
|
+
useShareAppMessage, // 分享 (onShareAppMessage)
|
|
321
|
+
useRouter, // 获取路由参数
|
|
322
|
+
} from '@tarojs/taro'
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 路由导航
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import Taro from '@tarojs/taro'
|
|
329
|
+
|
|
330
|
+
// 保留当前页面,跳转到新页面
|
|
331
|
+
Taro.navigateTo({ url: '/pages/detail/index?id=1' })
|
|
332
|
+
|
|
333
|
+
// 关闭当前页面,跳转到新页面
|
|
334
|
+
Taro.redirectTo({ url: '/pages/detail/index' })
|
|
335
|
+
|
|
336
|
+
// 跳转到 tabBar 页面
|
|
337
|
+
Taro.switchTab({ url: '/pages/index/index' })
|
|
338
|
+
|
|
339
|
+
// 返回上一页
|
|
340
|
+
Taro.navigateBack({ delta: 1 })
|
|
341
|
+
|
|
342
|
+
// 获取路由参数
|
|
343
|
+
const router = useRouter()
|
|
344
|
+
const { id } = router.params
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 图标使用 (lucide-react)
|
|
348
|
+
|
|
349
|
+
项目集成了 [lucide-react](https://lucide.dev/) 图标库,提供丰富的 SVG 图标:
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import { View } from '@tarojs/components'
|
|
353
|
+
import { Home, Settings, User, Search, Heart, Star } from 'lucide-react'
|
|
354
|
+
|
|
355
|
+
const IconDemo = () => {
|
|
356
|
+
return (
|
|
357
|
+
<View className="flex gap-4">
|
|
358
|
+
<Home size={24} color="#333" />
|
|
359
|
+
<Settings size={24} className="text-blue-500" />
|
|
360
|
+
<User size={20} strokeWidth={1.5} />
|
|
361
|
+
<Search size={24} />
|
|
362
|
+
<Heart size={24} fill="red" color="red" />
|
|
363
|
+
<Star size={24} className="text-yellow-500" />
|
|
364
|
+
</View>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
常用属性:
|
|
370
|
+
- `size` - 图标大小(默认 24)
|
|
371
|
+
- `color` - 图标颜色
|
|
372
|
+
- `strokeWidth` - 线条粗细(默认 2)
|
|
373
|
+
- `className` - 支持 Tailwind 类名
|
|
374
|
+
|
|
375
|
+
更多图标请访问:https://lucide.dev/icons
|
|
376
|
+
|
|
377
|
+
### Tailwind CSS 样式开发
|
|
378
|
+
|
|
379
|
+
IMPORTANT:必须使用 tailwindcss 实现样式,只有在必要情况下才能 fallback 到 css / less
|
|
380
|
+
|
|
381
|
+
> 项目已集成 Tailwind CSS 4.x + weapp-tailwindcss,支持跨端原子化样式:
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
<View className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
|
385
|
+
<Text className="text-2xl font-bold text-blue-600 mb-4">标题</Text>
|
|
386
|
+
<View className="w-full px-4">
|
|
387
|
+
<Button className="w-full bg-blue-500 text-white rounded-lg py-3">
|
|
388
|
+
按钮
|
|
389
|
+
</Button>
|
|
390
|
+
</View>
|
|
391
|
+
</View>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### 性能优化
|
|
395
|
+
|
|
396
|
+
#### 图片懒加载
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
import { Image } from '@tarojs/components'
|
|
400
|
+
|
|
401
|
+
<Image src={imageUrl} lazyLoad mode="aspectFill" />
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### 虚拟列表
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import { VirtualList } from '@tarojs/components'
|
|
408
|
+
|
|
409
|
+
<VirtualList
|
|
410
|
+
height={500}
|
|
411
|
+
itemData={list}
|
|
412
|
+
itemCount={list.length}
|
|
413
|
+
itemSize={100}
|
|
414
|
+
renderItem={({ index, style, data }) => (
|
|
415
|
+
<View style={style}>{data[index].name}</View>
|
|
416
|
+
)}
|
|
417
|
+
/>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### 分包加载
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// src/app.config.ts
|
|
424
|
+
export default defineAppConfig({
|
|
425
|
+
pages: ['pages/index/index'],
|
|
426
|
+
subPackages: [
|
|
427
|
+
{
|
|
428
|
+
root: 'packageA',
|
|
429
|
+
pages: ['pages/detail/index'],
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
})
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### 小程序限制
|
|
436
|
+
|
|
437
|
+
| 限制项 | 说明 |
|
|
438
|
+
| -------- | ---------------------------------------- |
|
|
439
|
+
| 主包体积 | ≤ 2MB |
|
|
440
|
+
| 总包体积 | ≤ 20MB |
|
|
441
|
+
| 域名配置 | 生产环境需在小程序后台配置合法域名 |
|
|
442
|
+
| 本地开发 | 需在微信开发者工具开启「不校验合法域名」 |
|
|
443
|
+
|
|
444
|
+
### 权限配置
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
// src/app.config.ts
|
|
448
|
+
export default defineAppConfig({
|
|
449
|
+
// ...其他配置
|
|
450
|
+
permission: {
|
|
451
|
+
'scope.userLocation': {
|
|
452
|
+
desc: '你的位置信息将用于小程序位置接口的效果展示'
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
requiredPrivateInfos: ['getLocation', 'chooseAddress']
|
|
456
|
+
})
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### 位置服务
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// 需先在 app.config.ts 中配置 permission
|
|
463
|
+
async function getLocation(): Promise<Taro.getLocation.SuccessCallbackResult> {
|
|
464
|
+
return await Taro.getLocation({ type: 'gcj02' })
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## 后端核心开发规范
|
|
469
|
+
|
|
470
|
+
本项目后端基于 NestJS + TypeScript 构建,提供高效、可扩展的服务端能力。
|
|
471
|
+
|
|
472
|
+
### 项目结构
|
|
473
|
+
|
|
474
|
+
```sh
|
|
475
|
+
.
|
|
476
|
+
├── server/ # NestJS 后端服务
|
|
477
|
+
│ └── src/
|
|
478
|
+
│ ├── main.ts # 服务入口
|
|
479
|
+
│ ├── app.module.ts # 根模块
|
|
480
|
+
│ ├── app.controller.ts # 根控制器
|
|
481
|
+
│ └── app.service.ts # 根服务
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### 开发命令
|
|
485
|
+
|
|
486
|
+
```sh
|
|
487
|
+
pnpm dev:server // 启动开发服务 (热重载, 默认端口 3000)
|
|
488
|
+
pnpm build:server // 构建生产版本
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 新建模块流程 (CLI)
|
|
492
|
+
|
|
493
|
+
快速生成样板代码:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
cd server
|
|
497
|
+
|
|
498
|
+
# 生成完整的 CRUD 资源 (包含 Module, Controller, Service, DTO, Entity)
|
|
499
|
+
npx nest g resource modules/product
|
|
500
|
+
|
|
501
|
+
# 仅生成特定部分
|
|
502
|
+
npx nest g module modules/order
|
|
503
|
+
npx nest g controller modules/order
|
|
504
|
+
npx nest g service modules/order
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 环境变量配置
|
|
508
|
+
|
|
509
|
+
在 server/ 根目录创建 .env 文件:
|
|
510
|
+
|
|
511
|
+
```sh
|
|
512
|
+
## 服务端口
|
|
513
|
+
PORT=3000
|
|
514
|
+
|
|
515
|
+
## 微信小程序配置
|
|
516
|
+
WX_APP_ID=你的AppID
|
|
517
|
+
WX_APP_SECRET=你的AppSecret
|
|
518
|
+
|
|
519
|
+
## JWT 密钥
|
|
520
|
+
JWT_SECRET=your-super-secret-key
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
在代码中使用 @nestjs/config 读取环境变量:
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
import { ConfigService } from '@nestjs/config';
|
|
527
|
+
|
|
528
|
+
// 在 Service 中注入
|
|
529
|
+
constructor(private configService: ConfigService) {}
|
|
530
|
+
|
|
531
|
+
getWxConfig() {
|
|
532
|
+
return {
|
|
533
|
+
appId: this.configService.get<string>('WX_APP_ID'),
|
|
534
|
+
secret: this.configService.get<string>('WX_APP_SECRET'),
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 标准响应封装
|
|
540
|
+
|
|
541
|
+
建议使用拦截器 (Interceptor) 统一 API 响应格式:
|
|
542
|
+
|
|
543
|
+
```typeScript
|
|
544
|
+
// src/common/interceptors/transform.interceptor.ts
|
|
545
|
+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
546
|
+
import { Observable } from 'rxjs';
|
|
547
|
+
import { map } from 'rxjs/operators';
|
|
548
|
+
|
|
549
|
+
export interface Response<T> {
|
|
550
|
+
code: number;
|
|
551
|
+
data: T;
|
|
552
|
+
message: string;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
@Injectable()
|
|
556
|
+
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
|
|
557
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
|
|
558
|
+
return next.handle().pipe(
|
|
559
|
+
map((data) => ({
|
|
560
|
+
code: 200,
|
|
561
|
+
data,
|
|
562
|
+
message: 'success',
|
|
563
|
+
})),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
在 main.ts 中全局注册:
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
app.useGlobalInterceptors(new TransformInterceptor());
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### 微信登录后端实现
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// src/modules/auth/auth.service.ts
|
|
579
|
+
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
580
|
+
import { HttpService } from '@nestjs/axios';
|
|
581
|
+
import { ConfigService } from '@nestjs/config';
|
|
582
|
+
import { lastValueFrom } from 'rxjs';
|
|
583
|
+
|
|
584
|
+
@Injectable()
|
|
585
|
+
export class AuthService {
|
|
586
|
+
constructor(
|
|
587
|
+
private httpService: HttpService,
|
|
588
|
+
private configService: ConfigService,
|
|
589
|
+
) {}
|
|
590
|
+
|
|
591
|
+
async code2Session(code: string) {
|
|
592
|
+
const appId = this.configService.get('WX_APP_ID');
|
|
593
|
+
const secret = this.configService.get('WX_APP_SECRET');
|
|
594
|
+
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
|
|
595
|
+
|
|
596
|
+
const { data } = await lastValueFrom(this.httpService.get(url));
|
|
597
|
+
|
|
598
|
+
if (data.errcode) {
|
|
599
|
+
throw new UnauthorizedException(`微信登录失败: ${data.errmsg}`);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return data; // 包含 openid, session_key
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### 异常处理
|
|
608
|
+
|
|
609
|
+
使用全局异常过滤器 (Filter) 统一错误响应:
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
// src/common/filters/http-exception.filter.ts
|
|
613
|
+
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
|
|
614
|
+
import { Response } from 'express';
|
|
615
|
+
|
|
616
|
+
@Catch(HttpException)
|
|
617
|
+
export class HttpExceptionFilter implements ExceptionFilter {
|
|
618
|
+
catch(exception: HttpException, host: ArgumentsHost) {
|
|
619
|
+
const ctx = host.switchToHttp();
|
|
620
|
+
const response = ctx.getResponse<Response>();
|
|
621
|
+
const status = exception.getStatus();
|
|
622
|
+
const exceptionResponse = exception.getResponse();
|
|
623
|
+
|
|
624
|
+
response.status(status).json({
|
|
625
|
+
code: status,
|
|
626
|
+
message: typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as any).message,
|
|
627
|
+
data: null,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
在 main.ts 中注册:
|
|
634
|
+
|
|
635
|
+
```
|
|
636
|
+
app.useGlobalFilters(new HttpExceptionFilter());
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### 数据库 (Drizzle ORM)
|
|
640
|
+
|
|
641
|
+
推荐使用 [Drizzle ORM](https://orm.drizzle.team/),已预安装。
|
|
642
|
+
|
|
643
|
+
### 类型校验 (Zod)
|
|
644
|
+
|
|
645
|
+
项目集成了 [Zod](https://zod.dev/) 用于运行时类型校验。
|
|
646
|
+
|
|
647
|
+
#### 定义 Schema
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
import { z } from 'zod';
|
|
651
|
+
|
|
652
|
+
// 基础类型
|
|
653
|
+
const userSchema = z.object({
|
|
654
|
+
id: z.number(),
|
|
655
|
+
name: z.string().min(1).max(50),
|
|
656
|
+
email: z.string().email(),
|
|
657
|
+
age: z.number().int().positive().optional(),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// 从 schema 推导 TypeScript 类型
|
|
661
|
+
type User = z.infer<typeof userSchema>;
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### 请求校验
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
// src/modules/user/dto/create-user.dto.ts
|
|
668
|
+
import { z } from 'zod';
|
|
669
|
+
|
|
670
|
+
export const createUserSchema = z.object({
|
|
671
|
+
nickname: z.string().min(1, '昵称不能为空').max(20, '昵称最多20个字符'),
|
|
672
|
+
avatar: z.string().url('头像必须是有效的URL').optional(),
|
|
673
|
+
phone: z.string().regex(/^1[3-9]\d{9}$/, '手机号格式不正确').optional(),
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
export type CreateUserDto = z.infer<typeof createUserSchema>;
|
|
677
|
+
|
|
678
|
+
// 在 Controller 中使用
|
|
679
|
+
@Post()
|
|
680
|
+
create(@Body() body: unknown) {
|
|
681
|
+
const result = createUserSchema.safeParse(body);
|
|
682
|
+
if (!result.success) {
|
|
683
|
+
throw new BadRequestException(result.error.errors);
|
|
684
|
+
}
|
|
685
|
+
return this.userService.create(result.data);
|
|
686
|
+
}
|
|
687
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
|
|
5
|
+
# Production
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
dist-*/
|
|
9
|
+
|
|
10
|
+
# Misc
|
|
11
|
+
.DS_Store
|
|
12
|
+
.env
|
|
13
|
+
# .env.local
|
|
14
|
+
.env.development.local
|
|
15
|
+
.env.test.local
|
|
16
|
+
.env.production.local
|
|
17
|
+
|
|
18
|
+
# Logs
|
|
19
|
+
npm-debug.log*
|
|
20
|
+
yarn-debug.log*
|
|
21
|
+
yarn-error.log*
|
|
22
|
+
pnpm-debug.log*
|
|
23
|
+
|
|
24
|
+
# Editor directories and files
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.suo
|
|
28
|
+
*.ntvs*
|
|
29
|
+
*.njsproj
|
|
30
|
+
*.sln
|
|
31
|
+
*.sw?
|
|
32
|
+
|
|
33
|
+
# Taro specific
|
|
34
|
+
.taro/
|
|
35
|
+
|
|
36
|
+
# OS X
|
|
37
|
+
.DS_Store
|
|
38
|
+
|
|
39
|
+
# Key
|
|
40
|
+
# key/
|