hookehuyr

docs: 重写 AGENTS.md 和 README.md 项目文档

- AGENTS.md 精简 Codex 协作说明,聚焦扫码打卡链路等既有约定
- README.md 补充核心功能、目录结构、开发命令等项目描述

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 # AGENTS.md 1 # AGENTS.md
2 2
3 -本文件为 Codex (Codex.ai/code) 在此代码库中工作时提供指导 3 +本文件为 Codex `lls_program` 仓库中的协作说明。目标不是介绍 Taro 通用知识,而是帮助后续修改尽量贴合这个项目当前的真实实现
4 4
5 ## 项目概述 5 ## 项目概述
6 6
7 -**lls_program** 是一个基于 Taro 4 + Vue 3 + NutUI 的微信小程序,名为"老来赛"。这是一个家庭活动和积分奖励管理系统。 7 +`lls_program` 是一个基于 Taro 4 + Vue 3 + NutUI 的微信小程序,当前核心业务包括:
8 +
9 +- 家庭创建、加入、成员资料维护
10 +- 活动页、广告页、海报打卡、扫码打卡
11 +- 积分、奖励、优惠券相关页面
12 +- 基于微信授权和 `sessionid` 的登录态流转
13 +
14 +最近一轮实现里,扫码打卡链路已经接入真实接口,并补上了注册来源归因、回跳续扫、地理围栏判断等逻辑。后续改动请优先遵守这些既有约定,不要在页面里重新长出一套平行逻辑。
8 15
9 ## 技术栈 16 ## 技术栈
10 17
11 -- **框架**: Taro 4.1.7 - 跨平台小程序框架 18 +- 框架:Taro `4.1.7`
12 -- **UI**: Vue 3.3 + Composition API (`<script setup>`) 19 +- UI:Vue `3.3`,统一使用 `<script setup>`
13 -- **UI 组件库**: NutUI Taro 4.3.13 (自动导入,无需手动引入) 20 +- UI 组件:NutUI Taro `4.3.13`
14 -- **样式**: TailwindCSS 3.4 + Less (组件特定样式) 21 +- 样式:TailwindCSS `3.4` + Less
15 -- **状态管理**: Pinia 3.0 + taro-plugin-pinia 22 +- 状态管理:Pinia `3.0` + `taro-plugin-pinia`
16 -- **HTTP 请求**: axios-miniprogram 2.7.2 23 +- 请求库:`axios-miniprogram`
17 -- **构建工具**: Webpack 5 24 +- 构建:Webpack 5
25 +- 测试:Vitest
26 +
27 +## 常用命令
18 28
19 -## 开发命令 29 +项目当前 `package.json` 里是标准 npm scripts,使用 `npm``pnpm` 都可以,但文档和命令示例优先按仓库现状写 `npm`
20 30
21 ```bash 31 ```bash
22 # 安装依赖 32 # 安装依赖
23 -pnpm install 33 +npm install
24 34
25 -# 开发(微信小程序) 35 +# 微信小程序开发
26 -pnpm run dev:weapp 36 +npm run dev:weapp
27 37
28 -# 生产构建 38 +# 微信小程序打包
29 -pnpm run build:weapp 39 +npm run build:weapp
30 40
31 -# 其他平台 41 +# H5 / 支付宝 / 抖音
32 -pnpm run dev:h5 # H5 开发 42 +npm run dev:h5
33 -pnpm run dev:alipay # 支付宝小程序 43 +npm run dev:alipay
34 -pnpm run dev:tt # 抖音小程序 44 +npm run dev:tt
35 -```
36 -
37 -## 架构设计
38 45
39 -### 核心目录结构 46 +# 代码质量
47 +npm run lint
48 +npm run format
40 49
50 +# 测试
51 +npm run test
52 +npm run test:run
53 +npm run test:coverage
41 ``` 54 ```
55 +
56 +## 目录结构
57 +
58 +```text
42 src/ 59 src/
43 -├── api/ # 按业务领域组织的 API 接口 60 +├── api/ # 只放接口调用
44 -├── assets/ # 静态资源(图片、样式) 61 +├── assets/ # 静态资源
45 -├── components/ # 可复用的 Vue 组件 62 +├── components/ # 通用组件
46 -├── composables/ # Vue 3 组合式函数 (useXxx) 63 +├── pages/ # 页面
47 -├── pages/ # Taro 页面(每个页面包含 index.vue + index.config.js) 64 +├── stores/ # Pinia 状态
48 -├── stores/ # Pinia 状态管理 65 +├── utils/ # 纯工具逻辑、流程辅助、请求封装
49 -├── utils/ # 工具函数 66 +├── app.config.js # 页面注册、权限声明
50 -├── app.config.js # Taro 应用配置(页面列表、窗口、权限)
51 └── app.less # 全局样式 67 └── app.less # 全局样式
52 ``` 68 ```
53 69
54 -### 路径别名 (config/index.js:30-38) 70 +当前和扫码打卡强相关的目录:
71 +
72 +- `src/api/map.js`
73 +- `src/pages/ScanCheckinList/`
74 +- `src/pages/ScanCheckinDetail/`
75 +- `src/pages/Welcome/`
76 +- `src/pages/AddProfile/`
77 +- `src/pages/CreateFamily/`
78 +- `src/pages/JoinFamily/`
79 +- `src/utils/checkinLocation.js`
80 +- `src/utils/scanCheckin.js`
81 +- `src/utils/returnUrl.js`
82 +- `src/utils/userProfile.js`
83 +- `src/components/RichTextRenderer.vue`
84 +
85 +## 路径别名
86 +
87 +项目已经配置好以下别名,新增代码优先使用别名,不要堆相对路径:
55 88
56 ```javascript 89 ```javascript
57 -@/utils src/utils 90 +@/utils
58 -@/components src/components 91 +@/components
59 -@/images src/assets/images 92 +@/assets
60 -@/assets src/assets 93 +@/api
61 -@/composables src/composables 94 +@/stores
62 -@/api src/api 95 +@/composables
63 -@/stores src/stores 96 +@/hooks
64 -@/hooks src/hooks
65 ``` 97 ```
66 98
67 -### 设计宽度配置 99 +## API 与请求约定
68 -
69 -- **NutUI 组件**: 375px (自动处理)
70 -- **其他所有内容**: 750px (Taro 标准)
71 -- `config/index.js` 中的 `designWidth` 函数根据文件路径自动切换
72 100
73 -## 核心 API 模式 101 +### 响应判断
74 102
75 -### API 响应格式 103 +所有接口统一按下面结构处理:
76 104
77 -所有 API 响应遵循以下结构:
78 ```javascript 105 ```javascript
79 { 106 {
80 - code: 1, // 1 = 成功,其他值 = 失败 107 + code: 1,
81 - data: {...}, // 响应数据 108 + data: {},
82 - msg: "message" // 错误/成功消息 109 + msg: '...'
83 } 110 }
84 ``` 111 ```
85 112
86 -**始终检查** `res.code === 1`(而不是 `res.code`)来判断成功 113 +必须显式判断 `res.code === 1`,不要写成 `if (res.code)`
87 114
88 -### 认证机制 (sessionid) 115 +### sessionid 机制
89 116
90 -**关键**: 项目使用 `sessionid` 进行认证(存储在 `wx.storage` 中): 117 +项目通过 `sessionid` 做服务端认证:
91 118
92 -1. **获取**: `src/utils/request.js:23-30` - `getSessionId()``wx.getStorageSync("sessionid")` 读取 119 +1. `src/utils/request.js` 每次请求前动态从 storage 读取 `sessionid`
93 -2. **设置**: 在 `miniProgramAuthAPI``loginAPI` 成功后设置 120 +2. 请求头通过 `config.headers.cookie = sessionid` 透传
94 -3. **使用**: 请求拦截器 (`request.js:75-78`) 设置 `config.headers.cookie = sessionid` 121 +3. 401 由现有登录/静默授权流程接管
95 -4. **清除**: 收到 401 响应或用户登出时
96 122
97 -⚠️ **重要**: sessionid **不**由前端用于判断登录状态(后端通过 401 响应来判断)。它只是传递给服务器的凭证。 123 +重要约定:
98 124
99 -### 请求拦截器 (src/utils/request.js:66-80) 125 +- 前端不要把 `sessionid` 当成“能否继续业务流程”的唯一判断条件
126 +- 是否能继续业务流程,应看接口结果和页面自己的业务条件
100 127
101 -```javascript 128 +### API 文件职责边界
102 -service.interceptors.request.use(config => {
103 - // 动态获取 sessionid 并设置到请求头
104 - const sessionid = getSessionId();
105 - if (sessionid) {
106 - config.headers.cookie = sessionid;
107 - }
108 - return config;
109 -})
110 -```
111 129
112 -### API 模块模式 (src/api/) 130 +`src/api/*.js` 只放接口定义和请求函数,不要把参数拼装、流程判断、字段归一化 helper 塞进去。
113 131
114 -每个 API 文件导出调用中央 `fn()` 辅助函数的函数 132 +正确做法
115 133
116 -```javascript 134 +- API 请求函数放 `src/api/`
117 -// src/api/common.js 135 +- payload 构建、字段解析、return_url 处理、地理围栏计算等 helper 放 `src/utils/`
118 -export const smsAPI = (params) => fn(fetch.post(Api.SMS, params));
119 -```
120 136
121 -关键 API 模块: 137 +当前已经落地的例子:
122 -- `common.js` - 短信验证码、上传凭证
123 -- `user.js` - 用户认证和个人信息
124 -- `family.js` - 家庭管理
125 -- `points.js` - 积分/奖励系统
126 -- `photo.js` - 照片/媒体处理
127 -- `organization.js` - 组织管理
128 138
129 -## Taro 小程序限制 139 +- `src/utils/userProfile.js`
140 + - `buildUpdateUserProfilePayload`
141 + - `isUserProfileComplete`
142 +- `src/utils/scanCheckin.js`
143 + - 负责从二维码内容里解析 `activity_id` / `detail_id`
144 +- `src/utils/returnUrl.js`
145 + - 负责 `return_url` 解码和拼接
146 +- `src/utils/checkinLocation.js`
147 + - 负责地理围栏判断
130 148
131 -### ❌ 禁止使用 Web API 149 +## 当前扫码打卡链路
132 150
133 -```javascript 151 +这是现在最需要保持稳定的一条业务链路,后续改动请先看清楚,不要只盯某一个页面。
134 -// 禁止 - 在小程序中会崩溃
135 -window.document.getElementById()
136 -localStorage
137 -window.location.href
138 -fetch()
139 -```
140 152
141 -### ✅ 必须使用 Taro API 153 +### 页面与接口
142 154
143 -```javascript 155 +- 列表页:`src/pages/ScanCheckinList/index.vue`
144 -// 正确 - 使用 Taro 等价 API 156 + - 使用 `getScanStageListAPI`
145 -Taro.createSelectorQuery() 157 +- 详情页:`src/pages/ScanCheckinDetail/index.vue`
146 -Taro.getStorage() / Taro.setStorage() 158 + - 使用 `getScanStageDetailAPI`
147 -Taro.navigateTo() 159 + - 使用 `submitScanCheckinAPI`
148 -Taro.request() 160 +- 接口定义集中在 `src/api/map.js`
149 -```
150 161
151 -### 页面生命周期(使用 Taro Hooks) 162 +### 当前流程
152 163
153 -```javascript 164 +1. 用户从活动或二维码入口进入扫码打卡相关页面
154 -import { useLoad, useShow, useReady } from '@tarojs/taro' 165 +2. 扫码链接会带 `activityId`,详情页自身还可能带 `reg_source``reg_stage_id`
166 +3. `ScanCheckinDetail` 点击“扫码打卡”时,先按现有路线检查“是否已有家庭”
167 +4. 没有家庭时,不直接扫码,先弹提示,再跳 `Welcome`
168 +5. `Welcome` 再决定是否先补资料、再创建家庭或加入家庭
169 +6. 完成资料和家庭链路后,通过 `return_url` 回到原扫码详情页
170 +7. 用户再次点击“扫码打卡”时:
171 + - 重新静默获取当前位置
172 + - 如果该关卡开启地理围栏,则先判断是否在范围内
173 + - 调起微信扫码
174 + - 从二维码结果里解析真实的 `activity_id` / `detail_id`
175 + - 调用打卡接口
176 +8. 打卡成功后跳转到 `ScanCheckinList`
155 177
156 -useLoad((options) => { 178 +### 关键业务约束
157 - // 页面加载(仅触发一次)- 适合获取路由参数
158 -})
159 179
160 -useShow(() => { 180 +- 打卡提交参数来自二维码内容,不来自详情页当前路由参数
161 - // 页面显示(每次显示都触发)- 适合刷新数据 181 +- 详情页路由参数主要用于展示、回跳和列表跳转
162 -}) 182 +- 注册来源归因字段当前只保留:
183 + - `reg_source`
184 + - `reg_stage_id`
185 +- `reg_activity_id` 已经不再需要,不要再传
186 +- “是否补全资料”的判断交给 `Welcome` 链路,不要在 `ScanCheckinDetail` 再复制一套资料完整性分支
163 187
164 -useReady(() => { 188 +## return_url 回跳约定
165 - // 页面首次渲染完成
166 -})
167 -```
168 189
169 -### ❌ 页面中避免使用 Vue 生命周期 190 +扫码打卡目前依赖多页串联回跳,任何一个页面处理不对,都会出现跳错页、路径双重编码、甚至 `redirectTo` 找不到页面。
170 191
171 -```javascript 192 +当前约定:
172 -// 不要使用 - 可能无法正常工作
173 -onMounted(() => { ... })
174 -onUnmounted(() => { ... })
175 -```
176 193
177 -## 组件指南 194 +- 统一使用 `src/utils/returnUrl.js`
195 +- 页面收到 `options.return_url` 后,先走 `normalizeReturnUrl`
196 +- 页面拼接下一个页面地址时,优先走 `appendReturnUrlParam`
197 +- 不要在页面里到处手写 `decodeURIComponent(options.return_url || '')`
178 198
179 -### 页面结构 199 +当前受这个约束影响较大的页面:
180 200
181 -每个页面目录包含: 201 +- `src/pages/Welcome/index.vue`
182 -- `index.vue` - 页面组件(必须使用 `<script setup>` 202 +- `src/pages/AddProfile/index.vue`
183 -- `index.config.js` - 页面特定配置(navigationBarTitleText 等) 203 +- `src/pages/CreateFamily/index.vue`
184 -- `index.less` - 页面特定样式(scoped) 204 +- `src/pages/JoinFamily/index.vue`
185 205
186 -### 组件命名规范 206 +## 注册资料与来源归因
187 207
188 -- **页面**: 目录名(如 `pages/Dashboard/` 208 +`buildUpdateUserProfilePayload` 目前已经抽到 `src/utils/userProfile.js`,专门负责把页面表单转成接口 payload。
189 -- **组件**: PascalCase 多单词命名(如 `PointsCollector.vue``FamilyAlbum.vue`
190 -- **API 文件**: camelCase(如 `miniProgramAuthAPI`
191 209
192 -### NutUI 自动导入 210 +当前约定:
193 211
194 -NutUI 组件通过 `unplugin-vue-components` 自动导入。**不要**手动导入: 212 +- `user.js` 里只保留接口调用
213 +- `buildUpdateUserProfilePayload` 不回迁到 API 文件
214 +- 注册来源字段在补资料接口里继续透传:
215 + - `reg_source`
216 + - `reg_stage_id`
217 +- `reg_stage_id` 需要按数值类型传给后端
195 218
196 -```vue 219 +## 地理围栏约定
197 -<!-- ✅ 正确 - 自动导入 -->
198 -<template>
199 - <nut-button type="primary">点击</nut-button>
200 -</template>
201 220
202 -<!-- ❌ 错误 - 不要导入 --> 221 +扫码详情页如果 `geo_enabled === true`,必须做范围判断。
203 -<script setup>
204 -import { Button } from '@nutui/nutui-taro'
205 -</script>
206 -```
207 222
208 -## 样式 223 +当前实现约定:
209 224
210 -### TailwindCSS + Less 混合使用 225 +- 判断逻辑统一放 `src/utils/checkinLocation.js`
226 +- 页面层只负责调用,不要在页面里自己算经纬度距离
227 +- 点击“扫码打卡”时重新调用 `Taro.getLocation()`,不依赖旧缓存
228 +- 关卡详情接口字段:
229 + - `geo_enabled`
230 + - `center_lng`
231 + - `center_lat`
232 + - `radius_meters`
211 233
212 -- **TailwindCSS**: 用于布局、间距、颜色、排版(80% 的样式) 234 +## 富文本与轮播图约定
213 -- **Less**: 用于组件特定样式、动画、深度选择器(20%)
214 235
215 -### Tailwind 配置 236 +扫码详情页当前已经不是单图和原生富文本直出:
216 237
217 -- **Content**: `./src/**/*.{html,js,ts,jsx,tsx,vue}` (tailwind.config.js:13) 238 +- 顶部 banner 使用 NutUI `nut-swiper`
218 -- **Preflight**: 禁用(小程序不需要) 239 +- 富文本显示统一走 `src/components/RichTextRenderer.vue`
219 -- **rem → rpx**: 由 `weapp-tailwindcss` 插件处理 (rem2rpx: true)
220 240
221 -### 样式指南 241 +后续如果再改扫码详情页:
222 242
223 -```vue 243 +- 不要再改回单张 `<image>`
224 -<style lang="less" scoped> 244 +- 不要再临时拼一个简化版富文本组件替代现有 `RichTextRenderer`
225 -/* ✅ 组件必须使用 scoped */
226 -.page-container {
227 - padding: 30px;
228 -}
229 245
230 -/* ✅ 使用 Less 处理深度选择器 */ 246 +## 页面开发约定
231 -.custom-element :deep(.nut-popup) {
232 - background-color: #fff;
233 -}
234 -</style>
235 -```
236 247
237 -## 状态管理 (Pinia) 248 +### 生命周期
238 249
239 -### Store 模式 250 +页面优先使用 Taro 生命周期:
240 251
241 ```javascript 252 ```javascript
242 -// src/stores/host.js 253 +import { useLoad, useShow, useDidShow, useReady } from '@tarojs/taro'
243 -import { defineStore } from 'pinia'
244 -
245 -export const hostStore = defineStore('host', {
246 - state: () => ({
247 - id: '',
248 - join_id: ''
249 - }),
250 - actions: {
251 - add(id) {
252 - this.id = id
253 - }
254 - }
255 -})
256 ``` 254 ```
257 255
258 -### 在组件中使用 256 +尽量避免把页面主流程写到 Vue 的 `onMounted` / `onUnmounted` 里。
259 257
260 -```vue 258 +### 页面结构
261 -<script setup>
262 -import { hostStore } from '@/stores/host'
263 259
264 -const host = hostStore() 260 +每个页面目录通常包含:
265 -host.add('123')
266 -</script>
267 -```
268 261
269 -## 常用模式 262 +- `index.vue`
263 +- `index.config.js`
264 +- `index.less`
270 265
271 -### 页面导航 266 +新增页面后必须同步注册到 `src/app.config.js`
272 267
273 -```javascript 268 +### 小程序 API 约束
274 -import Taro from '@tarojs/taro'
275 -
276 -// 跳转到页面
277 -Taro.navigateTo({
278 - url: '/pages/Detail/index?id=123'
279 -})
280 -
281 -// 重定向(无返回)
282 -Taro.redirectTo({
283 - url: '/pages/Login/index'
284 -})
285 -
286 -// 切换 Tab
287 -Taro.switchTab({
288 - url: '/pages/Dashboard/index'
289 -})
290 -
291 -// 获取路由参数
292 -useLoad((options) => {
293 - const { id } = options
294 -})
295 -```
296 269
297 -### 本地存储 270 +不要在页面或工具函数里使用浏览器 API,例如:
298 271
299 -```javascript 272 +- `window`
300 -// 异步(推荐) 273 +- `document`
301 -await Taro.setStorage({ key: 'user', data: userInfo }) 274 +- `localStorage`
302 -const { data } = await Taro.getStorage({ key: 'user' }) 275 +- 原生 `fetch`
303 276
304 -// 同步(谨慎使用) 277 +统一使用 Taro API,例如:
305 -Taro.setStorageSync('token', 'xxxx')
306 -const token = Taro.getStorageSync('token')
307 -```
308 278
309 -### 提示/弹窗 279 +- `Taro.navigateTo`
280 +- `Taro.redirectTo`
281 +- `Taro.switchTab`
282 +- `Taro.getStorage`
283 +- `Taro.scanCode`
284 +- `Taro.getLocation`
310 285
311 -```javascript 286 +## 样式约定
312 -// Toast 提示
313 -Taro.showToast({
314 - title: '操作成功',
315 - icon: 'success',
316 - duration: 2000
317 -})
318 -
319 -// Modal 弹窗
320 -Taro.showModal({
321 - title: '提示',
322 - content: '确定删除吗?',
323 - success: (res) => {
324 - if (res.confirm) {
325 - // 用户点击了确定
326 - }
327 - }
328 -})
329 -```
330 287
331 -## 页面注册 288 +- NutUI 组件自动导入,不要手动 import
289 +- 页面样式继续保持 Tailwind + Less 混合方式
290 +- Less 样式默认 `scoped`
291 +- 深层覆盖 NutUI 时使用 `:deep(...)`
332 292
333 -页面在 `src/app.config.js` 中注册: 293 +这个仓库已经有一个和扫码页相关的稳定样式经验:
334 -
335 -```javascript
336 -export default {
337 - pages: [
338 - 'pages/Dashboard/index',
339 - 'pages/MyFamily/index',
340 - 'pages/Activities/index',
341 - // ... 更多页面
342 - ]
343 -}
344 -```
345 294
346 -**创建新页面时**: 必须将其添加到此数组中。 295 +- `ScanCheckinDetail` 底部按钮采用固定定位和安全区留白
296 +- 如果只是调整按钮视觉位置,优先做 CSS 修改,不要顺手改业务逻辑
347 297
348 -## 构建输出 298 +## 权限与配置
349 299
350 -- **开发环境**: `dist/` 目录 300 +`src/app.config.js` 当前已经声明了位置权限:
351 -- **微信开发者工具**: 打开 `dist/` 作为项目根目录
352 301
353 -## 重要文件说明 302 +- `requiredPrivateInfos: ['getLocation']`
303 +- `permission['scope.userLocation']`
354 304
355 -### `src/utils/request.js` 305 +所以如果你在扫码打卡里继续用定位能力,优先复用现有授权前提,不要再设计一套重复授权流程。
356 306
357 -核心 HTTP 客户端,包含: 307 +## 修改建议
358 -- SessionID 注入
359 -- 401 响应处理
360 -- 401 时静默授权重定向
361 -- 错误处理
362 308
363 -### `src/utils/authRedirect.js` 309 +### 适合抽到 utils 的逻辑
364 310
365 -处理小程序登录流程的静默授权。 311 +- 二维码参数解析
312 +- 距离计算和范围判断
313 +- return_url 编解码
314 +- 用户资料 payload 构建
315 +- 纯字段映射或判空逻辑
366 316
367 -### `src/utils/tools.js` 317 +### 不适合放进 API 文件的逻辑
368 318
369 -通用工具函数: 319 +- 表单转 payload
370 -- `formatDate()` - 使用 moment.js 格式化日期 320 +- 页面跳转流程判断
371 -- `wxInfo()` - 平台检测(Android/iOS/微信) 321 +- 二维码内容解析
372 -- `hasEllipsis()` - 文本溢出检测 322 +- 路由参数兼容处理
323 +- 页面专用字段映射
373 324
374 -## 开发注意事项 325 +### 做改动前先检查
375 326
376 -1. **始终使用 Taro API** 而非 Web API 327 +- 这是页面职责,还是工具职责
377 -2. **检查 `res.code === 1`** 判断 API 成功(不是 `res.code` 328 +- 这是接口请求定义,还是接口参数拼装
378 -3. **NutUI 组件已自动导入** - 不要手动导入 329 +- 这是详情页展示参数,还是打卡提交真实参数
379 -4. **页面中使用 Taro 生命周期钩子**`useLoad``useShow` 330 +- 这是当前页下一跳,还是完整链路里的回跳页面
380 -5. **SessionID 动态获取** - 每次请求从存储中读取
381 -6. **已配置路径别名** - 使用 `@/components` 代替相对路径
382 -7. **设计宽度双模式**: NutUI 使用 375px,其他使用 750px
383 331
384 -## 平台差异 332 +## 交付标准
385 333
386 -项目通过 Taro 支持多平台: 334 +在这个仓库里完成修改时,优先做到:
387 -- **微信 (weapp)**: 主要目标平台
388 -- **H5**: Web 浏览器版本
389 -- **支付宝 (alipay)**: 支付宝小程序
390 -- **抖音 (tt)**: 字节跳动小程序
391 335
392 -平台特定代码可使用: 336 +- 与当前真实业务链路一致
393 -```javascript 337 +- 不在页面里复制已有 helper
394 -if (process.env.TARO_ENV === 'weapp') { 338 +- 不在 API 文件里塞 helper
395 - // 微信特定代码 339 +- 不破坏 `return_url` 回跳
396 -} 340 +- 不破坏扫码打卡的地理围栏与注册来源归因
397 -``` 341 +- 不把 Taro 小程序页面写成浏览器页面
......
1 -## 项目介绍 1 +# lls_program
2 2
3 -基于Taro4的微信小程序模版,集成了常用的功能,如登录、注册、列表、详情、购物车等。 3 +`lls_program` 是一个基于 Taro 4 + Vue 3 + NutUI 的微信小程序项目,当前业务围绕家庭管理、活动参与、积分奖励,以及海报/扫码打卡展开。
4 +
5 +这不是一个通用模板仓库。当前代码里已经有比较明确的业务结构,尤其是扫码打卡链路、资料补全链路、家庭创建/加入链路,后续开发建议直接沿用现有实现方式,而不是重新搭一套平行流程。
4 6
5 ## 技术栈 7 ## 技术栈
6 8
7 -- Taro4 9 +- Taro `4.1.7`
8 -- Vue3 10 +- Vue `3.3`
9 -- TypeScript 11 +- NutUI Taro `4.3.13`
10 -- Pinia 12 +- Pinia `3.0`
13 +- TailwindCSS `3.4`
11 - Less 14 - Less
15 +- axios-miniprogram
16 +- Vitest
17 +
18 +## 核心功能
19 +
20 +- 微信小程序登录与静默授权
21 +- 家庭创建、加入、家庭资料维护
22 +- 用户资料补全与注册来源归因
23 +- 活动详情、海报打卡、扫码打卡
24 +- 积分、奖励、优惠券相关页面
25 +- 地理位置相关能力与位置权限接入
26 +
27 +## 当前重点业务链路
28 +
29 +### 扫码打卡
30 +
31 +当前扫码打卡已接入真实接口,主要页面和工具如下:
32 +
33 +- 列表页:`src/pages/ScanCheckinList/index.vue`
34 +- 详情页:`src/pages/ScanCheckinDetail/index.vue`
35 +- API:`src/api/map.js`
36 +- 地理围栏:`src/utils/checkinLocation.js`
37 +- 扫码参数解析:`src/utils/scanCheckin.js`
38 +- 回跳参数处理:`src/utils/returnUrl.js`
39 +- 富文本渲染:`src/components/RichTextRenderer.vue`
40 +
41 +当前流程:
42 +
43 +1. 用户进入扫码打卡详情页
44 +2. 点击“扫码打卡”时先检查是否已有家庭
45 +3. 没有家庭则提示后跳转 `Welcome`
46 +4. `Welcome -> AddProfile -> Welcome -> CreateFamily/JoinFamily -> 原详情页`
47 +5. 返回详情页后再次扫码
48 +6. 若关卡启用地理围栏,先重新静默获取当前位置并判断范围
49 +7. 调起微信扫码
50 +8. 从二维码结果中解析真实的 `activity_id` / `detail_id`
51 +9. 调用真实打卡接口
52 +10. 成功后跳到扫码关卡列表页
53 +
54 +补充约定:
55 +
56 +- 打卡参数来自二维码内容,不来自当前页面路由参数
57 +- 注册来源归因目前通过 `reg_source``reg_stage_id` 透传
58 +- `reg_activity_id` 已经不再使用
59 +- `return_url` 回跳统一通过 `src/utils/returnUrl.js` 处理
12 60
13 ## 项目结构 61 ## 项目结构
14 62
15 -- src 63 +```text
16 - - api:请求接口 64 +src/
17 - - assets:静态资源 65 +├── api/ # 接口定义,只放请求相关代码
18 - - components:全局组件 66 +├── assets/ # 静态资源
19 - - config:项目配置 67 +├── components/ # 通用组件
20 - - pages:页面 68 +├── pages/ # 页面
21 - - stores:状态管理 69 +├── stores/ # Pinia 状态管理
22 - - utils:工具函数 70 +├── utils/ # 工具函数、流程辅助
23 - - app.config.js:项目配置 71 +├── app.config.js # 页面注册、权限声明
24 - - app.js:应用入口 72 +└── app.less # 全局样式
25 - - app.less:全局样式 73 +```
26 -- taro.config.js:Taro配置 74 +
27 -- tsconfig.json:TypeScript配置 75 +几个常用目录说明:
28 -- package.json:依赖配置 76 +
29 - 77 +- `src/api/user.js`:用户认证、资料接口
30 -## 项目运行 78 +- `src/api/family.js`:家庭相关接口
31 - 79 +- `src/api/map.js`:地图、扫码关卡、扫码打卡相关接口
32 -1. 安装依赖 80 +- `src/utils/request.js`:请求拦截器、`sessionid` 注入
81 +- `src/utils/userProfile.js`:资料相关 helper
82 +- `src/components/RichTextRenderer.vue`:富文本渲染组件
83 +
84 +## 开发约定
85 +
86 +### API 与 helper 分层
87 +
88 +- `src/api/*.js` 只放接口调用
89 +- 参数拼装、字段解析、距离计算、回跳处理等逻辑放 `src/utils/`
90 +
91 +例如:
92 +
93 +- `buildUpdateUserProfilePayload``src/utils/userProfile.js`
94 +- 扫码结果解析在 `src/utils/scanCheckin.js`
95 +- 地理围栏判断在 `src/utils/checkinLocation.js`
96 +
97 +### 请求返回判断
98 +
99 +所有接口都要显式判断:
100 +
101 +```javascript
102 +if (res.code === 1) {
103 + // success
104 +}
105 +```
106 +
107 +不要写成:
108 +
109 +```javascript
110 +if (res.code) {
111 + // 不推荐
112 +}
113 +```
114 +
115 +### 小程序环境约束
116 +
117 +页面里不要使用浏览器 API:
118 +
119 +- `window`
120 +- `document`
121 +- `localStorage`
122 +- 原生 `fetch`
123 +
124 +统一使用 Taro API:
125 +
126 +- `Taro.navigateTo`
127 +- `Taro.redirectTo`
128 +- `Taro.switchTab`
129 +- `Taro.scanCode`
130 +- `Taro.getLocation`
131 +
132 +### 页面生命周期
133 +
134 +页面优先使用 Taro 提供的生命周期:
135 +
136 +- `useLoad`
137 +- `useShow`
138 +- `useDidShow`
139 +- `useReady`
140 +
141 +## 登录与认证
142 +
143 +项目当前通过 `sessionid` 维持服务端登录态:
144 +
145 +- 从 storage 读取 `sessionid`
146 +- 在请求拦截器中注入到 `cookie`
147 +- 401 交给现有授权/跳转逻辑处理
148 +
149 +需要注意:
150 +
151 +- `sessionid` 只是认证凭证
152 +- 它不等于“用户一定能继续后续业务流程”
153 +- 业务上是否可继续,仍要结合资料、家庭、活动规则等条件判断
154 +
155 +## 本地开发
156 +
157 +### 安装依赖
33 158
34 ```bash 159 ```bash
35 npm install 160 npm install
36 ``` 161 ```
37 162
38 -2. 运行项目 163 +### 微信小程序开发
39 164
40 ```bash 165 ```bash
41 npm run dev:weapp 166 npm run dev:weapp
42 ``` 167 ```
43 168
44 -3. 打包项目 169 +### 微信小程序构建
45 170
46 ```bash 171 ```bash
47 npm run build:weapp 172 npm run build:weapp
48 ``` 173 ```
174 +
175 +### 其他常用命令
176 +
177 +```bash
178 +npm run dev:h5
179 +npm run dev:alipay
180 +npm run dev:tt
181 +npm run lint
182 +npm run format
183 +npm run test:run
184 +```
185 +
186 +## 页面注册与权限
187 +
188 +页面统一在 `src/app.config.js` 中注册。
189 +
190 +当前已经声明位置相关能力:
191 +
192 +- `requiredPrivateInfos: ['getLocation']`
193 +- `permission.scope.userLocation`
194 +
195 +这也是扫码打卡地理围栏能力能直接接上的基础配置。
196 +
197 +## 备注
198 +
199 +- NutUI 组件是自动导入的,不需要手动 import
200 +- `ScanCheckinDetail` 顶部 banner 现在使用 NutUI `nut-swiper`
201 +- `ScanCheckinDetail` 富文本展示统一使用 `RichTextRenderer`
202 +- 如果只是调整扫码详情页底部按钮视觉位置,优先改样式,不要顺手改业务逻辑
......