hookehuyr

fix(checkin): 修复 mockData 语法错误并完善地图活动功能

主要修改:
- 修复 mockData.js 的语法错误(移除孤立的 return 语句)
- 修复 generateApiFromOpenAPI.js 的 brace-style 代码风格问题
- 实现 CheckinMap 页面与 map_activity API 的集成
- 添加完整的 API 规范文档
- 更新 API 代码生成脚本

技术细节:
- 清理 mockData.js 中遗留的孤立代码片段
- 移除未使用的函数参数以消除警告
- 修复 ESLint brace-style 规则错误(9处)
- 完善 map_activity API 接口定义
- 添加 API Mock 数据支持开发环境测试

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
# 打卡
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
post:
summary: 打卡
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: checkin
schema:
type: string
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
activity_id:
description: 活动ID
example: ''
type: string
detail_id:
description: 打卡点ID
example: '828360'
type: string
openid:
example: oAHBN10P-hn-vF1cTY4tQeStQFmU
type: string
examples: {}
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
x-apifox-orders:
- code
- msg
required:
- code
- msg
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: integrating
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417072141-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 地图活动详情
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 地图活动详情
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: detail
schema:
type: string
- name: id
in: query
description: 活动ID
required: false
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
data:
type: object
properties:
url:
type: string
title: 地图网址
id:
type: string
title: 活动ID
cover:
type: string
title: 封面图
tittle:
type: string
title: 标题
begin_date:
type: string
title: 开始时间
end_date:
type: string
title: 结束时间
is_ended:
type: boolean
title: 活动是否已经结束
is_begin:
type: boolean
title: 活动是否开始
first_checkin_points:
type: integer
title: 首次打卡获得积分
required_checkin_count:
type: integer
title: 需要打卡几次,才能完成活动
complete_points:
type: integer
title: 完成活动获得多少积分
discount_title:
type: string
title: 打卡点底部优惠标题
x-apifox-orders:
- id
- cover
- tittle
- begin_date
- end_date
- is_ended
- is_begin
- url
- first_checkin_points
- required_checkin_count
- complete_points
- discount_title
required:
- url
- end_date
- begin_date
- id
- cover
- tittle
- first_checkin_points
- required_checkin_count
- complete_points
- discount_title
x-apifox-orders:
- code
- msg
- data
required:
- code
- msg
- data
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: integrating
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417075691-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 是否已经打卡
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 是否已经打卡
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: is_checked
schema:
type: string
- name: detail_id
in: query
description: 打卡点ID
required: false
example: '828359'
schema:
type: string
- name: openid
in: query
description: ''
required: false
example: oAHBN10P-hn-vF1cTY4tQeStQFmU
schema:
type: string
- name: activity_id
in: query
description: 活动ID
required: false
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
data:
type: object
properties:
is_checked:
type: boolean
title: 是否已经打卡
x-apifox-orders:
- is_checked
required:
- is_checked
x-apifox-orders:
- code
- msg
- data
required:
- code
- msg
- data
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: integrating
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417072140-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 地图活动列表
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 地图活动列表
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: list
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
data:
type: array
items:
type: object
properties:
url:
type: string
title: 地图网址
id:
type: string
title: 活动ID
cover:
type: string
title: 封面图
tittle:
type: string
title: 标题
begin_date:
type: string
title: 开始时间
end_date:
type: string
title: 结束时间
x-apifox-orders:
- id
- cover
- tittle
- begin_date
- end_date
- url
required:
- url
- end_date
- begin_date
- id
- cover
- tittle
x-apifox-orders:
- code
- msg
- data
required:
- code
- msg
- data
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: integrating
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417072114-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 获取海报
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 获取海报
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: poster
schema:
type: string
- name: activity_id
in: query
description: 活动ID
required: false
example:
- ''
schema:
type: string
- name: detail_id
in: query
description: 关卡ID
required: false
example: ''
schema:
type: string
- name: env_version
in: query
description: 小程序版本。正式版为 "release",体验版为 "trial"。默认是正式版
required: false
example: trial
schema:
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
data:
type: object
properties:
details:
type: array
items:
type: object
properties:
id:
type: integer
title: 关卡ID
name:
type: string
title: 关卡名称
background_url:
type: string
title: 关卡背景图
main_slogan:
type: string
sub_slogan:
type: string
is_checked:
type: boolean
title: 是否已经打卡
x-apifox-orders:
- id
- name
- is_checked
- background_url
- main_slogan
- sub_slogan
required:
- id
- name
- background_url
- main_slogan
- sub_slogan
- is_checked
title: 关卡列表
family:
type: object
properties:
id:
type: integer
title: 家庭ID
name:
type: string
title: 家庭名称
avatar_url:
type: string
title: 家庭头像
x-apifox-orders:
- id
- name
- avatar_url
required:
- id
- name
- avatar_url
title: 用户的当前家庭
show_detail_index:
type: integer
title: 当前应该显示第几个关卡
description: 从 0 开始计数
end_date:
type: string
title: 活动截止时间
qrcode_url:
type: string
title: 小程序码
title:
type: string
title: 海报标题
begin_date:
type: string
title: 活动开始日期
x-apifox-orders:
- title
- begin_date
- end_date
- details
- show_detail_index
- family
- qrcode_url
required:
- title
- details
- family
- show_detail_index
- end_date
- qrcode_url
- begin_date
x-apifox-orders:
- code
- msg
- data
required:
- code
- msg
- data
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: integrating
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417072142-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
# 上传海报背景
## OpenAPI Specification
```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
post:
summary: 上传海报背景
deprecated: false
description: ''
tags:
- 老来赛/地图-新版多活动
parameters:
- name: f
in: query
description: ''
required: true
example: walk
schema:
type: string
- name: a
in: query
description: ''
required: true
example: map_activity
schema:
type: string
- name: t
in: query
description: ''
required: true
example: save_poster_background
schema:
type: string
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
activity_id:
description: 活动ID
example: ''
type: string
detail_id:
description: 打卡点ID
example: '828359'
type: string
poster_background_url:
description: 关卡海报背景
example: >-
https://cdn.ipadbiz.cn/space_34093/t0122259914a77e9a57_FiYxd1DK70vLJ53Po8g2Y6JmqMeQ.jpg
type: string
examples: {}
responses:
'200':
description: ''
content:
application/json:
schema:
type: object
properties:
code:
type: integer
msg:
type: string
x-apifox-orders:
- code
- msg
required:
- code
- msg
headers: {}
x-apifox-name: 成功
x-apifox-ordering: 0
security: []
x-apifox-folder: 老来赛/地图-新版多活动
x-apifox-status: developing
x-run-in-apifox: https://app.apifox.com/web/project/1753326/apis/api-417072143-run
components:
schemas: {}
responses: {}
securitySchemes: {}
servers: []
security: []
```
......@@ -18,14 +18,14 @@
"build:rn": "taro build --type rn",
"build:qq": "taro build --type qq",
"build:quickapp": "taro build --type quickapp",
"dev:weapp": "npm run build:weapp -- --watch",
"dev:swan": "npm run build:swan -- --watch",
"dev:alipay": "npm run build:alipay -- --watch",
"dev:tt": "npm run build:tt -- --watch",
"dev:h5": "npm run build:h5 -- --watch",
"dev:rn": "npm run build:rn -- --watch",
"dev:qq": "npm run build:qq -- --watch",
"dev:quickapp": "npm run build:quickapp -- --watch",
"dev:weapp": "NODE_ENV=development npm run build:weapp -- --watch",
"dev:swan": "NODE_ENV=development npm run build:swan -- --watch",
"dev:alipay": "NODE_ENV=development npm run build:alipay -- --watch",
"dev:tt": "NODE_ENV=development npm run build:tt -- --watch",
"dev:h5": "NODE_ENV=development npm run build:h5 -- --watch",
"dev:rn": "NODE_ENV=development npm run build:rn -- --watch",
"dev:qq": "NODE_ENV=development npm run build:qq -- --watch",
"dev:quickapp": "NODE_ENV=development npm run build:quickapp -- --watch",
"postinstall": "weapp-tw patch",
"prepare": "husky install",
"test": "vitest",
......@@ -33,7 +33,8 @@
"test:coverage": "vitest --coverage",
"test:run": "vitest run",
"lint": "eslint ./src --ext .vue,.js",
"format": "prettier --write \"src/**/*.{js,vue,less}\""
"format": "prettier --write \"src/**/*.{js,vue,less}\"",
"api:generate": "node scripts/generateApiFromOpenAPI.js"
},
"browserslist": [
"last 3 versions",
......
# API 文档生成指南
本项目的 API 文档采用手动维护的方式。
## 📝 工作流程
### 1. 维护 OpenAPI 文档
`docs/api-specs/` 目录中维护 OpenAPI 文档。
#### 目录结构
```
docs/api-specs/
├── user/ # 用户模块
│ ├── login.md
│ ├── login_status.md
│ └── ...
├── favorite/ # 收藏模块
│ ├── add.md
│ ├── del.md
│ └── list.md
└── ...
```
#### 文档格式
每个 `.md` 文件包含:
- 接口描述(Markdown 格式)
- OpenAPI 3.0.1 规范(YAML 格式)
**示例**`docs/api-specs/user/login.md`):
\`\`\`markdown
# 登录并绑定 OpenID
## 接口信息
- **方法**: POST
- **路径**: /srv/?a=user&t=login
- **标签**: user
## OpenAPI 规范
\`\`\`yaml
openapi: 3.0.0
info:
title: 登录并绑定 OpenID
description: 使用手机号和验证码登录,并绑定到 OpenID
version: 1.0.0
paths:
/srv/?:
post:
summary: 登录并绑定 OpenID
description: 使用手机号和验证码登录,并绑定到 OpenID
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- f
- a
- t
properties:
f:
type: string
description: 业务模块标识
example: manulife
a:
type: string
description: 模块名(user)
example: user
t:
type: string
description: 接口类型(login)
example: login
phone:
type: string
description: 手机号
example: '13800138000'
code:
type: string
description: 验证码
example: '123456'
openid:
type: string
description: 微信 OpenID
example: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
code:
type: number
description: 状态码(0=失败,1=成功)
msg:
type: string
description: 消息
data:
type: object
description: 用户信息
properties:
id:
type: number
description: 用户 ID
avatar:
type: string
description: 头像 URL
name:
type: string
description: 姓名
\`\`\`
\`\`\`
### 2. 生成 API 代码
运行生成脚本:
```bash
node scripts/generateApiFromOpenAPI.js
```
#### 生成内容
脚本会:
1. 扫描 `docs/api-specs/` 目录
2. 解析每个 `.md` 文件中的 OpenAPI 规范
3. 生成对应的 JavaScript API 文件到 `src/api/` 目录
#### 输出示例
\`\`\`
=== OpenAPI 转 API 文档生成器 ===
输入目录: /Users/huyirui/program/itomix/git/manulife-weapp/docs/api-specs
输出目录: /Users/huyirui/program/itomix/git/manulife-weapp/src/api
💾 备份当前 OpenAPI 文档...
找到 9 个模块: event, favorite, feedback, file, get_file_list, get_product, news, user, wechat
处理模块: user
找到 5 个 API 文档
✓ get_profile: 获取个人信息
✓ login: 登录并绑定openid
✓ login_status: 查询登录状态
✓ logout: 退出登录并解绑openid
✓ update_profile: 更新个人资料
📝 生成文件: /Users/huyirui/program/itomix/git/manulife-weapp/src/api/user.js
✅ API 文档生成完成!
\`\`\`
### 3. 使用生成的 API
在组件中导入并使用:
\`\`\`javascript
import { loginAPI, getUserProfileAPI } from '@/api/user';
// 登录
const result = await loginAPI({
phone: '13800138000',
code: '123456',
openid: 'oXXXX-XXXXXXXXXXXXXXXXXXX'
});
if (result.code === 1) {
console.log('登录成功', result.data);
}
\`\`\`
## 🔧 高级功能
### API 变更检测
脚本会自动检测 API 变更:
-**新增接口** - 检测到新的 API 文档
- ⚠️ **修改接口** - 检测到 API 规范变更
-**删除接口** - 检测到删除的 API 文档
#### 变更报告示例
\`\`\`
🔍 开始检测 API 变更...
📦 新增模块: user
包含 2 个新增接口:
• POST /srv/?a=user&t=login - 登录并绑定 OpenID
• GET /srv/?a=user&t=get_profile - 获取个人信息
📦 对比范围: 9 个旧接口 → 11 个新接口
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 新增接口 (2):
+ login - 登录并绑定 OpenID
+ get_profile - 获取个人信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总计: 2 新增, 0 修改, 0 删除
✅ 未检测到破坏性变更
\`\`\`
## 📚 最佳实践
### 1. 文档命名
- 使用语义化文件名(如 `login.md`, `get_list.md`
- 避免使用通用名称(如 `api.md`, `endpoint.md`
### 2. 参数定义
- **必填参数**:在 `required` 数组中列出
- **可选参数**:不在 `required`
- **描述**:为每个参数添加清晰的 `description`
- **示例**:为每个参数添加 `example`
### 3. 响应结构
- 统一使用 `{ code, msg, data }` 格式
- `code`: 状态码(0=失败,1=成功)
- `msg`: 消息说明
- `data`: 数据内容
### 4. 文档分组
- 按业务模块分组(user, favorite, product 等)
- 每个模块一个目录
- 相关接口放在同一目录
## 🛠️ 故障排除
### 问题:YAML 解析失败
**错误信息**
\`\`\`
✗ login.md: 解析失败 - YAML 代码块格式错误
\`\`\`
**解决方案**
- 检查 YAML 代码块是否正确包裹在 `\`\`\`yaml``\`\`\`` 之间
- 检查 YAML 缩进是否正确(使用空格,不要使用 Tab)
- 使用在线 YAML 验证器验证格式
### 问题:未找到 YAML 代码块
**错误信息**
\`\`\`
⚠️ login.md: 未找到 YAML 代码块
\`\`\`
**解决方案**
- 确保文档中包含 `\`\`\`yaml` 代码块
- 检查代码块格式是否正确
### 问题:生成的 API 代码为空
**可能原因**
1. OpenAPI 文档格式不正确
2. `paths``requestBody` 定义缺失
**解决方案**
- 检查 OpenAPI 文档结构是否完整
- 参考本文档中的示例格式
## 📖 参考资料
- [OpenAPI 3.0 规范](https://swagger.io/specification/)
- [YAML 语法指南](https://yaml.org/spec/1.2/spec.html)
- [项目 API 文档目录](../docs/api-specs/)
# OpenAPI 转 API 文档生成器 - 快速开始
## 🎯 一分钟快速上手
### 1️⃣ 创建 OpenAPI 文档
`docs/api-specs/` 目录下创建模块和接口文档:
```bash
# 创建新模块
mkdir -p docs/api-specs/product
# 创建接口文档
touch docs/api-specs/product/getList.md
```
### 2️⃣ 编写 OpenAPI 规范
编辑 `getList.md`
```markdown
# 获取商品列表
## OpenAPI Specification
\```yaml
openapi: 3.0.1
info:
title: ''
version: 1.0.0
paths:
/srv/:
get:
summary: 获取商品列表
tags:
- 商品
parameters:
- name: a
in: query
example: product_list
- name: f
in: query
example: behalo
responses:
'200':
description: 成功
\```
```
### 3️⃣ 生成 API 文件
```bash
pnpm api:generate
```
### 4️⃣ 使用生成的 API
```javascript
import { getListAPI } from '@/api/product';
const result = await getListAPI({ page: 1, pageSize: 10 });
```
## ✅ 验证结果
运行测试脚本验证生成的文件:
```bash
node scripts/test-generate.js
```
## 📂 文件结构
```
manulife-weapp/
├── docs/
│ ├── api-specs/ # API 规范文档源目录
│ │ └── user/ # 模块目录
│ │ └── getUserInfo.md
│ ├── OPENAPI_TO_API_GUIDE.md # 详细使用指南
│ └── API_USAGE_EXAMPLES.md # API 使用示例
├── scripts/
│ ├── generateApiFromOpenAPI.js # 生成器核心脚本
│ └── test-generate.js # 测试脚本
├── src/
│ └── api/ # 生成的 API 文件目录
│ ├── user.js # 自动生成
│ ├── wx/
│ └── index.js
└── package.json # 包含 api:generate 命令
```
## 🔄 工作流程
```mermaid
graph LR
A[编写 OpenAPI 文档] --> B[运行 pnpm api:generate]
B --> C[生成 API 文件]
C --> D[在项目中使用]
D --> E[需要修改接口]
E --> A
```
## 🎨 常见场景
### 场景 1: 批量生成多个接口
```bash
docs/api-specs/
├── user/
│ ├── getUserInfo.md
│ ├── updateProfile.md
│ └── changePassword.md
└── order/
├── getList.md
└── getDetail.md
```
运行 `pnpm api:generate` 后生成:
```
src/api/
├── user.js # 包含 3 个接口
└── order.js # 包含 2 个接口
```
### 场景 2: 更新已有接口
1. 修改 `docs/api-specs/user/getUserInfo.md`
2. 运行 `pnpm api:generate`
3. `src/api/user.js` 自动更新
### 场景 3: 添加新模块
1. 创建 `docs/api-specs/payment/`
2. 添加接口文档
3. 运行生成命令
4. 自动生成 `src/api/payment.js`
## ⚙️ 配置和自定义
### 修改输出目录
编辑 `scripts/generateApiFromOpenAPI.js`
```javascript
const outputDir = path.resolve(__dirname, '../src/api');
// 改为你想要的目录
const outputDir = path.resolve(__dirname, '../src/apis');
```
### 修改命名规则
编辑 `toCamelCase()``toPascalCase()` 函数。
### 修改生成模板
编辑 `generateApiFileContent()` 函数。
## 🐛 调试技巧
### 启用详细日志
在脚本中添加更多 console.log:
```javascript
console.log('解析的 API 信息:', JSON.stringify(apiInfo, null, 2));
```
### 单独测试某个模块
修改脚本中的模块过滤逻辑。
### 查看生成的中间数据
添加调试输出查看 YAML 解析结果。
## 📞 获取帮助
- 详细指南:[OpenAPI 转 API 文档生成器指南](./OPENAPI_TO_API_GUIDE.md)
- 使用示例:[API 使用示例](./API_USAGE_EXAMPLES.md)
- 项目架构:[CLAUDE.md](../CLAUDE.md)
## 🎉 开始使用
现在你已经准备好了!开始创建你的第一个 OpenAPI 文档吧。
```bash
# 1. 创建模块目录
mkdir -p docs/api-specs/your-module
# 2. 创建接口文档(参考 docs/api-specs/user/getUserInfo.md)
# 3. 生成 API
pnpm api:generate
# 4. 查看生成的文件
cat src/api/your-module.js
# 5. 开始使用
```
祝你编码愉快!🚀
This diff is collapsed. Click to expand it.
#!/bin/bash
###############################################################################
# CHANGELOG 漏记检查脚本
#
# 功能:
# 1. 扫描最近 N 天的 git 提交记录
# 2. 对比 CHANGELOG.md 中的记录
# 3. 生成漏记报告
#
# 使用:
# ./scripts/check-changelog.sh [days]
#
# 示例:
# ./scripts/check-changelog.sh 7 # 检查最近 7 天
# ./scripts/check-changelog.sh 30 # 检查最近 30 天
# ./scripts/check-changelog.sh # 检查所有提交
#
###############################################################################
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 默认参数
DAYS=${1:-7} # 默认检查最近 7 天
CHANGELOG_FILE="docs/CHANGELOG.md"
echo -e "${BLUE}======================================${NC}"
echo -e "${BLUE} CHANGELOG 漏记检查工具${NC}"
echo -e "${BLUE}======================================${NC}"
echo ""
echo -e "检查范围: 最近 ${DAYS} 天"
echo ""
# 检查 CHANGELOG 文件是否存在
if [ ! -f "$CHANGELOG_FILE" ]; then
echo -e "${RED}错误: CHANGELOG 文件不存在: $CHANGELOG_FILE${NC}"
exit 1
fi
# 1. 获取 git 提交记录
echo -e "${BLUE}[1/4] 正在获取 git 提交记录...${NC}"
if [ "$DAYS" = "0" ]; then
# 检查所有提交
GIT_LOG=$(git log --all --pretty=format:"%h|%ad|%s" --date=short)
else
# 检查最近 N 天的提交
GIT_LOG=$(git log --since="$DAYS days ago" --pretty=format:"%h|%ad|%s" --date=short)
fi
TOTAL_COMMITS=$(echo "$GIT_LOG" | wc -l | tr -d ' ')
echo -e " 找到 ${GREEN}$TOTAL_COMMITS${NC} 个提交"
# 2. 解析 CHANGELOG 中的记录
echo -e "${BLUE}[2/4] 正在解析 CHANGELOG 记录...${NC}"
# 提取 CHANGELOG 中的日期和描述
CHANGELOG_ENTRIES=$(grep "^## \[" "$CHANGELOG_FILE" | sed 's/^## \[//' | sed 's/\].*//' | sort -u)
TOTAL_CHANGELOG=$(echo "$CHANGELOG_ENTRIES" | wc -l | tr -d ' ')
echo -e " 找到 ${GREEN}$TOTAL_CHANGELOG${NC} 条记录"
# 3. 对比分析
echo -e "${BLUE}[3/4] 正在对比分析...${NC}"
# 统计每个日期的提交数量
COMMITS_BY_DATE=$(echo "$GIT_LOG" | awk -F'|' '{print $2}' | sort | uniq -c | sort -rn)
echo ""
echo -e "${YELLOW}📊 每日提交统计:${NC}"
echo "$COMMITS_BY_DATE" | head -20
# 检查哪些日期有提交但 CHANGELOG 没有记录
echo ""
echo -e "${YELLOW}🔍 可能漏记的日期:${NC}"
MISSING_DATES=0
while IFS='|' read -r count date; do
date=$(echo "$date" | awk '{print $2}')
# 检查 CHANGELOG 中是否有这个日期的记录
if ! echo "$CHANGELOG_ENTRIES" | grep -q "$date"; then
echo -e " ${RED}${NC} $date - ${RED}$count 个提交未记录${NC}"
MISSING_DATES=$((MISSING_DATES + 1))
fi
done <<< "$COMMITS_BY_DATE"
if [ $MISSING_DATES -eq 0 ]; then
echo -e " ${GREEN}${NC} 所有提交都已记录"
fi
# 4. 生成详细报告
echo ""
echo -e "${BLUE}[4/4] 生成详细报告...${NC}"
# 临时文件
TEMP_REPORT=$(mktemp)
# 输出报告头
cat > "$TEMP_REPORT" << 'EOF'
# CHANGELOG 漏记详细报告
## 检查日期
- 检查范围: 最近 {DAYS} 天
- 生成时间: {TIMESTAMP}
## 统计摘要
- Git 提交总数: {TOTAL_COMMITS}
- CHANGELOG 记录数: {TOTAL_CHANGELOG}
- 可能漏记的提交: {MISSING_COMMITS}
## 漏记详情
EOF
# 替换模板变量
sed -i.bak "s/{DAYS}/$DAYS/g" "$TEMP_REPORT"
sed -i.bak "s/{TIMESTAMP}/$(date '+%Y-%m-%d %H:%M:%S')/g" "$TEMP_REPORT"
sed -i.bak "s/{TOTAL_COMMITS}/$TOTAL_COMMITS/g" "$TEMP_REPORT"
sed -i.bak "s/{TOTAL_CHANGELOG}/$TOTAL_CHANGELOG/g" "$TEMP_REPORT"
# 计算可能漏记的提交数
MISSING_COMMITS=0
while IFS='|' read -r count date; do
date=$(echo "$date" | awk '{print $2}')
if ! echo "$CHANGELOG_ENTRIES" | grep -q "$date"; then
MISSING_COMMITS=$((MISSING_COMMITS + count))
fi
done <<< "$COMMITS_BY_DATE"
sed -i.bak "s/{MISSING_COMMITS}/$MISSING_COMMITS/g" "$TEMP_REPORT"
# 如果有漏记,列出详细提交
if [ $MISSING_COMMITS -gt 0 ]; then
echo "" >> "$TEMP_REPORT"
echo "### 未记录的提交详情" >> "$TEMP_REPORT"
echo "" >> "$TEMP_REPORT"
while IFS='|' read -r count date; do
date_only=$(echo "$date" | awk '{print $2}')
if ! echo "$CHANGELOG_ENTRIES" | grep -q "$date_only"; then
echo "**$date_only** ($count 个提交):" >> "$TEMP_REPORT"
echo "$GIT_LOG" | grep "$date_only" | awk -F'|' '{print "- " $3}' >> "$TEMP_REPORT"
echo "" >> "$TEMP_REPORT"
fi
done <<< "$COMMITS_BY_DATE"
else
echo "" >> "$TEMP_REPORT"
echo "### ✅ 完整性检查" >> "$TEMP_REPORT"
echo "" >> "$TEMP_REPORT"
echo "所有提交都已在 CHANGELOG 中记录!" >> "$TEMP_REPORT"
fi
# 删除备份文件
rm -f "$TEMP_REPORT.bak"
# 输出报告
echo ""
echo -e "${BLUE}======================================${NC}"
echo -e "${BLUE} 检查完成${NC}"
echo -e "${BLUE}======================================${NC}"
echo ""
cat "$TEMP_REPORT"
# 保存报告
REPORT_FILE="docs/changelog-check-report-$(date +%Y%m%d).md"
mv "$TEMP_REPORT" "$REPORT_FILE"
echo ""
echo -e "${GREEN}✓ 详细报告已保存到: $REPORT_FILE${NC}"
# 5. 给出建议
echo ""
echo -e "${YELLOW}💡 建议:${NC}"
if [ $MISSING_COMMITS -gt 0 ]; then
echo -e " ${YELLOW}1.${NC} 查看 $REPORT_FILE 了解漏记详情"
echo -e " ${YELLOW}2.${NC} 更新 CHANGELOG.md 补充漏记的记录"
echo -e " ${YELLOW}3.${NC} 使用标准格式添加记录(参考文档顶部模板)"
else
echo -e " ${GREEN}${NC} CHANGELOG 记录完整,继续保持!"
fi
echo ""
echo -e "${BLUE}======================================${NC}"
echo ""
# 返回退出码
if [ $MISSING_COMMITS -gt 0 ]; then
exit 1 # 有漏记,返回非零退出码
else
exit 0 # 无漏记,返回零
fi
This diff is collapsed. Click to expand it.
/**
* 测试生成的 API 文件
*/
const path = require('path')
const fs = require('fs')
// 测试导入生成的 API
const userApiPath = path.resolve(__dirname, '../src/api/user.js')
console.log('=== 测试生成的 API 文件 ===\n')
if (fs.existsSync(userApiPath)) {
const content = fs.readFileSync(userApiPath, 'utf8')
console.log('✅ API 文件生成成功\n')
console.log('文件内容:')
console.log('─'.repeat(60))
console.log(content)
console.log('─'.repeat(60))
// 验证关键部分
const checks = [
{ name: '导入 fn 和 fetch', pattern: /import \{ fn, fetch \} from '@\/api\/fn'/ },
{ name: 'Api 常量定义', pattern: /const Api = \{/ },
{ name: '导出函数', pattern: /export const getUserInfoAPI/ },
{ name: 'JSDoc 注释', pattern: /\/\*\*[\s\S]*?\*\// },
{ name: '正确的 action', pattern: /a=user_info/ },
]
console.log('\n验证结果:')
checks.forEach(check => {
const passed = check.pattern.test(content)
console.log(`${passed ? '✅' : '❌'} ${check.name}`)
})
console.log('\n✅ 所有验证通过!')
} else {
console.log('❌ API 文件不存在,请先运行 pnpm api:generate')
}
import { fn, fetch } from '@/api/fn'
const Api = {
Checkin: '/srv/?a=map_activity&t=checkin',
Detail: '/srv/?a=map_activity&t=detail',
IsChecked: '/srv/?a=map_activity&t=is_checked',
List: '/srv/?a=map_activity&t=list',
Poster: '/srv/?a=map_activity&t=poster',
SavePosterBackground: '/srv/?a=map_activity&t=save_poster_background',
}
/**
* @description 打卡
* @remark
* @param {Object} params 请求参数
* @param {string} params.activity_id (可选) 活动ID
* @param {string} params.detail_id (可选) 打卡点ID
* @param {string} params.openid (可选)
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: any;
* }>}
*/
export const checkinAPI = params => fn(fetch.post(Api.Checkin, params))
/**
* @description 地图活动详情
* @remark
* @param {Object} params 请求参数
* @param {string} params.id (可选) 活动ID
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
url: string; // 地图网址
id: string; // 活动ID
cover: string; // 封面图
tittle: string; // 标题
begin_date: string; // 开始时间
end_date: string; // 结束时间
is_ended: boolean; // 活动是否已经结束
is_begin: boolean; // 活动是否开始
first_checkin_points: integer; // 首次打卡获得积分
required_checkin_count: integer; // 需要打卡几次,才能完成活动
complete_points: integer; // 完成活动获得多少积分
discount_title: string; // 打卡点底部优惠标题
* };
* }>}
*/
export const detailAPI = params => fn(fetch.get(Api.Detail, params))
/**
* @description 是否已经打卡
* @remark
* @param {Object} params 请求参数
* @param {string} params.detail_id (可选) 打卡点ID
* @param {string} params.openid (可选)
* @param {string} params.activity_id (可选) 活动ID
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
is_checked: boolean; // 是否已经打卡
* };
* }>}
*/
export const isCheckedAPI = params => fn(fetch.get(Api.IsChecked, params))
/**
* @description 地图活动列表
* @remark
* @param {Object} params 请求参数
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: Array<{
url: string; // 地图网址
id: string; // 活动ID
cover: string; // 封面图
tittle: string; // 标题
begin_date: string; // 开始时间
end_date: string; // 结束时间
* }>;
* }>}
*/
export const listAPI = params => fn(fetch.get(Api.List, params))
/**
* @description 获取海报
* @remark
* @param {Object} params 请求参数
* @param {string} params.activity_id (可选) 活动ID
* @param {string} params.detail_id (可选) 关卡ID
* @param {string} params.env_version (可选) 小程序版本。正式版为 "release",体验版为 "trial"。默认是正式版
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: {
details: Array<{
id: integer; // 关卡ID
name: string; // 关卡名称
background_url: string; // 关卡背景图
main_slogan: string; //
sub_slogan: string; //
is_checked: boolean; // 是否已经打卡
}>;
family: {
id: integer; // 家庭ID
name: string; // 家庭名称
avatar_url: string; // 家庭头像
};
show_detail_index: integer; // 从 0 开始计数
end_date: string; // 活动截止时间
qrcode_url: string; // 小程序码
title: string; // 海报标题
begin_date: string; // 活动开始日期
* };
* }>}
*/
export const posterAPI = params => fn(fetch.get(Api.Poster, params))
/**
* @description 上传海报背景
* @remark
* @param {Object} params 请求参数
* @param {string} params.activity_id (可选) 活动ID
* @param {string} params.detail_id (可选) 打卡点ID
* @param {string} params.poster_background_url (可选) 关卡海报背景
* @returns {Promise<{
* code: number; // 状态码
* msg: string; // 消息
* data: any;
* }>}
*/
export const savePosterBackgroundAPI = params => fn(fetch.post(Api.SavePosterBackground, params))
......@@ -37,54 +37,68 @@
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import BottomNav from '@/components/BottomNav.vue'
import { listAPI } from '@/api/map_activity'
import { mockMapActivityListAPI } from '@/utils/mockData'
import { useLoad } from '@tarojs/taro'
// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
/**
* 便民地图列表数据
*/
const mapList = ref([])
const loading = ref(false)
/**
* 格式化 API 数据为页面所需格式
* @param {Array} list - API 返回的活动列表
* @returns {Array} 格式化后的活动列表
*/
const formatMapList = list => {
return list.map(item => ({
id: item.id,
title: item.tittle, // API 返回的是 tittle,映射为 title
cover: item.cover,
timeRange: `${item.begin_date}~${item.end_date}`,
activityId: item.id, // 使用 id 作为 activityId
}))
}
/**
* Mock 便民地图数据
* 获取地图活动列表
*/
const mapList = ref([
{
id: 1,
title: '重阳登高打卡',
cover: 'https://picsum.photos/400/300?random=1',
timeRange: '2025.09.06~2025.10.31',
activityId: 'chongyang_2024',
},
{
id: 2,
title: '公园晨跑打卡',
cover: 'https://picsum.photos/400/300?random=2',
timeRange: '2025.09.01~2025.12.31',
activityId: 'morning_run_2024',
},
{
id: 3,
title: '社区健身打卡',
cover: 'https://picsum.photos/400/300?random=3',
timeRange: '2025.08.01~2025.12.31',
activityId: 'community_fitness',
},
{
id: 4,
title: '周末徒步打卡',
cover: 'https://picsum.photos/400/300?random=4',
timeRange: '2025.09.15~2025.11.30',
activityId: 'weekend_hike',
},
{
id: 5,
title: '秋日赏菊打卡',
cover: 'https://picsum.photos/400/300?random=5',
timeRange: '2025.10.15~2025.11.15',
activityId: 'autumn_chrysanthemum',
},
{
id: 6,
title: '古镇文化打卡',
cover: 'https://picsum.photos/400/300?random=6',
timeRange: '2025.10.01~2025.10.31',
activityId: 'ancient_town',
},
])
const fetchMapList = async () => {
if (loading.value) {
return
}
loading.value = true
try {
const params = {}
// 根据开关选择使用真实 API 或 Mock 数据
const res = USE_MOCK_DATA ? await mockMapActivityListAPI(params) : await listAPI(params)
if (res.code === 1 && res.data) {
mapList.value = formatMapList(res.data)
} else {
Taro.showToast({
title: res.msg || '获取活动列表失败',
icon: 'none',
})
}
} catch (err) {
console.error('获取地图活动列表失败:', err)
Taro.showToast({
title: '网络异常,请重试',
icon: 'none',
})
} finally {
loading.value = false
}
}
/**
* 处理卡片点击事件
......@@ -107,6 +121,11 @@ const handleEnter = item => {
url: `/pages/ActivitiesCover/index?activityId=${item.activityId}&title=${encodeURIComponent(item.title)}`,
})
}
// 页面加载时获取列表
useLoad(() => {
fetchMapList()
})
</script>
<style lang="less">
......
/**
* @Description: Mock 数据生成工具 - 用于测试地图活动功能
* @Date: 2026-02-09
*
* 支持的 API Mock:
* - listAPI: 地图活动列表
* - detailAPI: 地图活动详情
* - isCheckedAPI: 是否已打卡
* - posterAPI: 获取海报
*/
// ============================================================================
// 工具函数
// ============================================================================
/**
* 随机图片 URL 生成器
* @param {number} width - 图片宽度
* @param {number} height - 图片高度
* @param {number} seed - 随机种子
* @returns {string} 图片 URL
*/
function randomImage(width = 400, height = 300, seed = 1) {
return `https://picsum.photos/${width}/${height}?random=${seed}`
}
/**
* 模拟网络延迟
* @param {number} min - 最小延迟(ms)
* @param {number} max - 最大延迟(ms)
* @returns {Promise}
*/
function mockDelay(min = 100, max = 300) {
const delay = Math.random() * (max - min) + min
return new Promise(resolve => setTimeout(resolve, delay))
}
// ============================================================================
// 1. 地图活动列表 Mock (listAPI)
// ============================================================================
const ACTIVITY_NAMES = [
'重阳登高打卡',
'公园晨跑打卡',
'社区健身打卡',
'周末徒步打卡',
'秋日赏菊打卡',
'古镇文化打卡',
'健康骑行活动',
'亲子运动会',
'户外拓展训练',
'城市定向挑战',
]
/**
* 生成地图活动列表项
* @param {number} id - 活动 ID
* @returns {Object} 活动对象
*/
function generateMapActivityItem(id) {
const activityName = ACTIVITY_NAMES[Math.floor(Math.random() * ACTIVITY_NAMES.length)]
const now = new Date()
const startDate = new Date(now.getTime() + Math.random() * 30 * 24 * 60 * 60 * 1000)
const endDate = new Date(startDate.getTime() + (30 + Math.random() * 60) * 24 * 60 * 60 * 1000)
// 格式化日期为 YYYY.MM.DD
const formatDate = date => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}.${month}.${day}`
}
return {
id: String(id),
tittle: activityName,
cover: randomImage(400, 300, id),
begin_date: formatDate(startDate),
end_date: formatDate(endDate),
url: '',
}
}
/**
* Mock: listAPI (地图活动列表)
* @param {Object} params - 请求参数
* @returns {Promise<{code: number, msg: string, data: Array}>}
*/
export async function mockMapActivityListAPI() {
await mockDelay()
const list = []
const total = 6
for (let i = 0; i < total; i++) {
list.push(generateMapActivityItem(i + 1))
}
console.log(`[Mock] listAPI - 地图活动列表,共${list.length}条`)
return {
code: 1,
msg: 'success',
data: list,
}
}
// ============================================================================
// 2. 地图活动详情 Mock (detailAPI)
// ============================================================================
/**
* Mock: detailAPI (地图活动详情)
* @param {Object} params - 请求参数
* @param {string} params.id - 活动 ID
* @returns {Promise<{code: number, msg: string, data: Object}>}
*/
export async function mockMapActivityDetailAPI(params) {
await mockDelay()
const { id } = params
const item = generateMapActivityItem(parseInt(id) || 1)
console.log(`[Mock] detailAPI - 活动详情,ID:${id}`)
return {
code: 1,
msg: 'success',
data: {
...item,
url: 'https://example.com/map',
is_ended: false,
is_begin: true,
first_checkin_points: 10,
required_checkin_count: 5,
complete_points: 50,
discount_title: '打卡点优惠信息',
},
}
}
// ============================================================================
// 3. 是否已打卡 Mock (isCheckedAPI)
// ============================================================================
/**
* Mock: isCheckedAPI (是否已打卡)
* @param {Object} params - 请求参数
* @param {string} params.detail_id - 打卡点 ID
* @returns {Promise<{code: number, msg: string, data: Object}>}
*/
export async function mockIsCheckedAPI(params) {
await mockDelay()
const { detail_id } = params
const isChecked = parseInt(detail_id) % 3 === 0
console.log(`[Mock] isCheckedAPI - 打卡点${detail_id},${isChecked ? '已打卡' : '未打卡'}`)
return {
code: 1,
msg: 'success',
data: {
is_checked: isChecked,
},
}
}
// ============================================================================
// 4. 获取海报 Mock (posterAPI)
// ============================================================================
/**
* Mock: posterAPI (获取海报)
* @param {Object} params - 请求参数
* @param {string} params.activity_id - 活动 ID
* @returns {Promise<{code: number, msg: string, data: Object}>}
*/
export async function mockPosterAPI(params) {
await mockDelay()
const { activity_id } = params
console.log(`[Mock] posterAPI - 获取海报,活动ID:${activity_id}`)
return {
code: 1,
msg: 'success',
data: {
details: [
{
id: 1,
name: '起点打卡',
background_url: randomImage(750, 1200, 20),
main_slogan: '开启健康之旅',
sub_slogan: '坚持就是胜利',
is_checked: true,
},
{
id: 2,
name: '山顶打卡',
background_url: randomImage(750, 1200, 21),
main_slogan: '登高望远',
sub_slogan: '风景这边独好',
is_checked: false,
},
],
family: {
id: 123,
name: '快乐家庭',
avatar_url: randomImage(200, 200, 30),
},
show_detail_index: 0,
end_date: '2025.10.31',
qrcode_url: 'https://example.com/qrcode.jpg',
title: '重阳登高打卡',
begin_date: '2025.09.06',
},
}
}
// ============================================================================
// 导出统一 Mock API 调用器
// ============================================================================
/**
* Mock API 调用器
* @param {string} apiName - API 名称
* @param {Object} params - 请求参数
* @returns {Promise}
*/
export async function mockAPI(apiName, params) {
switch (apiName) {
case 'listAPI':
return await mockMapActivityListAPI(params)
case 'detailAPI':
return await mockMapActivityDetailAPI(params)
case 'isCheckedAPI':
return await mockIsCheckedAPI(params)
case 'posterAPI':
return await mockPosterAPI(params)
default:
console.warn(`[Mock] 未知的 API: ${apiName}`)
return { code: 0, msg: 'Unknown API', data: null }
}
}
/**
* Mock 地图活动详情数据
* @param {string} activityId - 活动 ID
* @returns {Object} 地图活动详情
*/
export const mockMapActivityDetail = () => {
return {
url: 'https://example.com/map',
id: '1',
cover: randomImage(750, 500, 10),
tittle: '重阳登高打卡',
begin_date: '2025.09.06',
end_date: '2025.10.31',
is_ended: false,
is_begin: true,
first_checkin_points: 10,
required_checkin_count: 5,
complete_points: 50,
discount_title: '打卡点优惠信息',
}
}
/**
* Mock 是否已打卡数据
* @param {string} detailId - 打卡点 ID
* @returns {boolean} 是否已打卡
*/
export const mockIsChecked = detailId => {
// 偶尔返回已打卡
return parseInt(detailId) % 3 === 0
}
/**
* Mock 海报数据
* @param {string} activityId - 活动 ID
* @returns {Object} 海报数据
*/
export const mockPoster = activityId => {
return {
details: [
{
id: 1,
name: '起点打卡',
background_url: randomImage(750, 1200, 20),
main_slogan: '开启健康之旅',
sub_slogan: '坚持就是胜利',
is_checked: true,
},
{
id: 2,
name: '山顶打卡',
background_url: randomImage(750, 1200, 21),
main_slogan: '登高望远',
sub_slogan: '风景这边独好',
is_checked: false,
},
],
family: {
id: 123,
name: '快乐家庭',
avatar_url: randomImage(200, 200, 30),
},
show_detail_index: 0,
end_date: '2025.10.31',
qrcode_url: 'https://example.com/qrcode.jpg',
title: '重阳登高打卡',
begin_date: '2025.09.06',
}
}