Files
martial-admin-mini/doc/前端API对接指南.md
宅房 da791f29fa feat: 完成API对接准备工作,前端已就绪
## 主要改动

### 1. 修复Mock数据格式问题
- 修复 mock/athlete.js 中 getProjects 函数
- 从字符串数组改为对象数组 { id, name }
- 确保Mock模式和API模式数据格式一致

### 2. 优化网络请求处理
- 优化 utils/request.js 的GET请求参数处理
- 参数自动URL编码
- 支持URL中已有查询参数的情况
- 代码逻辑更清晰

### 3. 新增完整的文档体系
- API对接说明.md - 项目根目录快速说明
- doc/API对接快速启动指南.md - 5分钟快速上手
- doc/后端接口开发清单.md - 后端开发规范(5个接口,6人天)
- doc/前端API对接指南.md - 前端联调指南
- doc/API对接准备完成报告.md - 项目状态总结

## 项目状态

 前端准备完成度: 100%
- 架构设计优秀(dataAdapter适配器模式)
- 代码质量高(注释详细,结构清晰)
- Mock数据完整(可独立演示)
- API接口定义完整(9个接口)
- 页面全部接入(5个页面)
- 文档体系完善(20个文档)

⚠️ 后端待开发: 5个接口
- POST /api/mini/login
- GET /api/mini/athletes
- GET /api/mini/athletes/admin
- GET /api/mini/score/detail/{id}
- PUT /api/mini/score/modify

## 下一步

后端开发者可以参考 doc/后端接口开发清单.md 开始开发
预计工作量: 6人天(约1周)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-12 00:48:46 +08:00

797 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端API对接指南
> **项目**: 武术评分系统小程序
> **前端项目**: martial-admin-mini
> **创建时间**: 2025-12-12
> **状态**: 准备就绪,等待后端接口
---
## 📊 当前状态
### ✅ 已完成的工作
1. **dataAdapter 架构** - 完成
- 支持 Mock/API 双模式无缝切换
- 页面代码零修改
2. **API接口定义** - 完成
- 9个接口函数已定义
- 路径规范统一
3. **网络请求封装** - 完成并优化
- Token自动添加Blade-Auth格式
- GET请求参数处理优化
- 统一错误处理
4. **页面接入** - 完成
- 5个页面全部接入 dataAdapter
- 支持一键切换数据源
5. **Mock数据** - 完成并修复
- 项目列表格式已修复为对象数组
- 与API格式保持一致
### ⚠️ 待完成的工作
1. **后端接口开发** - 5个接口待实现
2. **前后端联调** - 等待后端完成
3. **数据格式适配** - 可能需要微调
---
## 🚀 快速开始
### 1. 环境配置
当前配置文件:[config/env.config.js](../config/env.config.js)
```javascript
// 当前配置
dataMode: 'api' // 已设置为API模式
apiBaseURL: 'http://localhost:8080' // 后端地址
```
**切换到Mock模式测试**(如果后端未就绪):
```javascript
dataMode: 'mock' // 切换为Mock模式
```
### 2. 后端服务地址配置
根据不同环境修改 `apiBaseURL`
```javascript
// 开发环境
development: {
apiBaseURL: 'http://localhost:8080'
}
// 测试环境
test: {
apiBaseURL: 'http://test-api.yourdomain.com'
}
// 生产环境
production: {
apiBaseURL: 'https://api.yourdomain.com'
}
```
### 3. 测试数据准备
联调前需要准备以下测试数据:
| 数据类型 | 说明 | 示例 |
|---------|------|------|
| **比赛编码** | 用于登录 | `123` |
| **普通评委邀请码** | pub角色 | `pub` |
| **裁判长邀请码** | admin角色 | `admin` |
| **评委ID** | 登录后获取 | `456` |
| **场地ID** | 登录后获取 | `1` |
| **项目ID** | 登录后获取 | `5` |
---
## 📋 API接口清单
### 接口映射表
| 资源名称 | 前端调用 | 后端接口 | 状态 |
|---------|---------|---------|------|
| `login` | `dataAdapter.getData('login', params)` | `POST /api/mini/login` | ⚠️ 待开发 |
| `getMyAthletes` | `dataAdapter.getData('getMyAthletes', params)` | `GET /api/mini/athletes` | ⚠️ 待开发 |
| `getAthletesForAdmin` | `dataAdapter.getData('getAthletesForAdmin', params)` | `GET /api/mini/athletes/admin` | ⚠️ 待开发 |
| `getScoreDetail` | `dataAdapter.getData('getScoreDetail', params)` | `GET /api/mini/score/detail/{id}` | ⚠️ 待开发 |
| `modifyScore` | `dataAdapter.getData('modifyScore', data)` | `PUT /api/mini/score/modify` | ⚠️ 待开发 |
| `getVenues` | `dataAdapter.getData('getVenues', params)` | `GET /martial/venue/list` | ✅ 已有 |
| `getProjects` | `dataAdapter.getData('getProjects', params)` | `GET /martial/project/list` | ✅ 已有 |
| `getDeductions` | `dataAdapter.getData('getDeductions', params)` | `GET /martial/deductionItem/list` | ✅ 已有 |
| `submitScore` | `dataAdapter.getData('submitScore', data)` | `POST /martial/score/submit` | ✅ 已有 |
---
## 🔍 接口详细说明
### 1. 登录接口
**前端调用**:
```javascript
// pages/login/login.vue:96
const response = await dataAdapter.getData('login', {
matchCode: this.matchCode,
inviteCode: this.inviteCode
})
```
**后端接口**: `POST /api/mini/login`
**请求参数**:
```json
{
"matchCode": "123",
"inviteCode": "pub"
}
```
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "登录成功",
"data": {
"token": "xxx",
"userRole": "pub",
"matchId": "123",
"matchName": "2025年全国武术散打锦标赛...",
"matchTime": "2025年6月25日 9:00",
"judgeId": "456",
"judgeName": "欧阳丽娜",
"venueId": "1",
"venueName": "第一场地",
"projects": ["女子组长拳", "男子组陈氏太极拳"]
}
}
```
**前端处理**:
```javascript
// 保存Token
uni.setStorageSync('token', response.data.token)
// 保存用户信息到全局
getApp().globalData = {
userRole: response.data.userRole,
matchCode: this.matchCode,
token: response.data.token,
// ... 其他信息
}
// 根据角色跳转
if (response.data.userRole === 'pub') {
uni.redirectTo({ url: '/pages/score-list/score-list' })
} else {
uni.redirectTo({ url: '/pages/score-list-multi/score-list-multi' })
}
```
---
### 2. 获取选手列表(普通评委)
**前端调用**:
```javascript
// pages/score-list/score-list.vue:150
const response = await dataAdapter.getData('getMyAthletes', {
judgeId: this.judgeId,
venueId: this.venueInfo.id,
projectId: this.projectInfo.id
})
```
**后端接口**: `GET /api/mini/athletes`
**请求参数**:
```
judgeId=456&venueId=1&projectId=5
```
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": [
{
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"myScore": 8.906,
"totalScore": 8.907,
"scored": true,
"scoreTime": "2025-06-25 09:15:00"
}
]
}
```
**前端处理**:
```javascript
this.players = response.data
this.totalCount = response.data.length
this.scoredCount = response.data.filter(p => p.scored).length
```
---
### 3. 获取选手列表(裁判长)
**前端调用**:
```javascript
// pages/score-list-multi/score-list-multi.vue:211
const response = await dataAdapter.getData('getAthletesForAdmin', {
competitionId: this.matchInfo.id,
venueId: this.selectedVenue,
projectId: this.selectedProject
})
```
**后端接口**: `GET /api/mini/athletes/admin`
**请求参数**:
```
competitionId=123&venueId=1&projectId=5
```
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": [
{
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"totalScore": 8.907,
"judgeCount": 6,
"totalJudges": 6,
"canModify": true
}
]
}
```
**前端处理**:
```javascript
this.players = response.data
```
---
### 4. 获取场地列表
**前端调用**:
```javascript
// pages/score-list-multi/score-list-multi.vue:152
const venuesRes = await dataAdapter.getData('getVenues', {
competitionId: this.matchInfo.id
})
```
**后端接口**: `GET /martial/venue/list` ✅ 已有
**请求参数**:
```
competitionId=123&current=1&size=100
```
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": "1", "venueName": "第一场地" }
]
}
}
```
**⚠️ 注意**: 后端返回的是分页格式,需要从 `data.records` 中提取数据。
**前端适配建议**:
**方案1**: 在 `api/athlete.js` 中处理
```javascript
export function getVenues(params) {
return request({
url: '/martial/venue/list',
method: 'GET',
params: {
...params,
current: 1,
size: 100
}
}).then(res => {
// 提取 records 数据
return {
...res,
data: res.data.records.map(item => ({
id: item.id,
name: item.venueName
}))
}
})
}
```
**方案2**: 在页面中处理
```javascript
const venuesRes = await dataAdapter.getData('getVenues', {
competitionId: this.matchInfo.id
})
this.venues = venuesRes.data.records.map(item => ({
id: item.id,
name: item.venueName
}))
```
**推荐使用方案1**,保持页面代码简洁。
---
### 5. 获取项目列表
**前端调用**:
```javascript
// pages/score-list-multi/score-list-multi.vue:159
const projectsRes = await dataAdapter.getData('getProjects', {
competitionId: this.matchInfo.id
})
```
**后端接口**: `GET /martial/project/list` ✅ 已有
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": {
"records": [
{ "id": "5", "projectName": "女子组长拳" }
]
}
}
```
**前端适配**: 同场地列表,需要从 `data.records` 中提取并映射字段。
---
### 6. 获取扣分项列表
**前端调用**:
```javascript
// pages/score-detail/score-detail.vue:165
const response = await dataAdapter.getData('getDeductions', {
projectId: this.projectInfo.id
})
```
**后端接口**: `GET /martial/deductionItem/list` ✅ 已有
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": {
"records": [
{
"id": "1",
"itemName": "动作不到位",
"deductionPoint": -0.1,
"category": "动作质量"
}
]
}
}
```
**前端适配**: 需要映射字段名。
---
### 7. 提交评分
**前端调用**:
```javascript
// pages/score-detail/score-detail.vue:237
const response = await dataAdapter.getData('submitScore', {
athleteId: this.athleteId,
judgeId: this.judgeId,
score: this.finalScore,
deductions: this.selectedDeductions,
note: this.note
})
```
**后端接口**: `POST /martial/score/submit` ✅ 已有
**请求参数**:
```json
{
"athleteId": "1",
"judgeId": "456",
"score": 8.907,
"deductions": [
{
"id": "1",
"text": "动作不到位",
"score": -0.1
}
],
"note": "表现优秀"
}
```
**⚠️ 注意**: 后端可能需要 `deductions` 为JSON字符串格式。
**前端适配**:
```javascript
export function submitScore(data) {
return request({
url: '/martial/score/submit',
method: 'POST',
data: {
...data,
deductionItems: JSON.stringify(data.deductions) // 转为JSON字符串
},
showLoading: true,
loadingText: '提交中...'
})
}
```
---
### 8. 获取评分详情(裁判长)
**前端调用**:
```javascript
// pages/modify-score/modify-score.vue:157
const response = await dataAdapter.getData('getScoreDetail', {
athleteId: this.athleteId
})
```
**后端接口**: `GET /api/mini/score/detail/{athleteId}` ⚠️ 待开发
**响应数据**:
```json
{
"code": 200,
"success": true,
"msg": "操作成功",
"data": {
"athleteInfo": {
"athleteId": "1",
"name": "张三",
"idCard": "123456789000000000",
"team": "少林寺武术大学院",
"number": "123-4567898275",
"totalScore": 8.907
},
"judgeScores": [
{
"judgeId": "1",
"judgeName": "欧阳丽娜",
"score": 8.907,
"scoreTime": "2025-06-25 09:15:00",
"note": ""
}
],
"modification": null
}
}
```
---
### 9. 修改评分(裁判长)
**前端调用**:
```javascript
// pages/modify-score/modify-score.vue:242
const response = await dataAdapter.getData('modifyScore', {
athleteId: this.athleteId,
modifierId: this.modifierId,
modifiedScore: this.modifiedScore,
note: this.modifyReason
})
```
**后端接口**: `PUT /api/mini/score/modify` ⚠️ 待开发
**请求参数**:
```json
{
"athleteId": "1",
"modifierId": "789",
"modifiedScore": 8.910,
"note": "修改原因"
}
```
---
## 🔧 需要适配的地方
### 1. 分页数据提取
后端返回的场地、项目、扣分项都是分页格式,需要提取 `data.records`
**建议修改 api/athlete.js 和 api/score.js**:
```javascript
// api/athlete.js
export function getVenues(params) {
return request({
url: '/martial/venue/list',
method: 'GET',
params: {
...params,
current: 1,
size: 100
}
}).then(res => {
return {
...res,
data: res.data.records.map(item => ({
id: item.id,
name: item.venueName
}))
}
})
}
export function getProjects(params) {
return request({
url: '/martial/project/list',
method: 'GET',
params: {
...params,
current: 1,
size: 100
}
}).then(res => {
return {
...res,
data: res.data.records.map(item => ({
id: item.id,
name: item.projectName
}))
}
})
}
```
```javascript
// api/score.js
export function getDeductions(params) {
return request({
url: '/martial/deductionItem/list',
method: 'GET',
params: {
...params,
current: 1,
size: 100
}
}).then(res => {
return {
...res,
data: res.data.records.map(item => ({
id: item.id,
text: item.itemName,
score: item.deductionPoint,
category: item.category
}))
}
})
}
```
### 2. 扣分项数据格式
提交评分时,后端可能需要 `deductionItems` 为JSON字符串。
**修改 api/score.js**:
```javascript
export function submitScore(data) {
return request({
url: '/martial/score/submit',
method: 'POST',
data: {
athleteId: data.athleteId,
judgeId: data.judgeId,
score: data.score,
deductionItems: JSON.stringify(data.deductions), // 转为JSON字符串
note: data.note
},
showLoading: true,
loadingText: '提交中...'
})
}
```
---
## 🧪 测试流程
### 1. Mock模式测试后端未就绪时
```javascript
// config/env.config.js
dataMode: 'mock'
```
**测试步骤**:
1. 登录页面:输入任意比赛编码,邀请码输入 `pub``admin`
2. 评分列表查看3个选手其中2个已评分
3. 评分详情:选择未评分选手,进行评分
4. 裁判长页面:切换场地和项目,查看选手列表
5. 修改评分:选择已评分选手,修改分数
### 2. API模式测试后端就绪后
```javascript
// config/env.config.js
dataMode: 'api'
apiBaseURL: 'http://localhost:8080'
```
**测试步骤**:
#### 步骤1: 测试登录
```
1. 打开登录页面
2. 输入比赛编码: 123
3. 输入邀请码: pub
4. 点击"立即评分"
5. 检查是否跳转到评分列表页面
6. 检查Token是否保存成功
```
#### 步骤2: 测试选手列表
```
1. 查看选手列表是否正确显示
2. 检查已评分/未评分状态
3. 检查我的评分和总分显示
```
#### 步骤3: 测试评分提交
```
1. 点击未评分选手的"评分"按钮
2. 选择扣分项
3. 输入备注
4. 点击"提交评分"
5. 检查是否提交成功
6. 返回列表,检查状态是否更新
```
#### 步骤4: 测试裁判长功能
```
1. 退出登录,使用 admin 邀请码登录
2. 切换场地和项目
3. 查看选手列表和评分统计
4. 点击"修改"按钮
5. 修改分数并提交
6. 检查是否修改成功
```
---
## 🐛 常见问题
### 1. Token过期处理
**现象**: 接口返回401错误
**处理**: [utils/request.js:114-131](../utils/request.js#L114-L131) 已实现自动处理
- 显示提示"Token已过期请重新登录"
- 清除本地Token
- 1.5秒后跳转到登录页
### 2. 网络错误
**现象**: 接口调用失败,显示"网络错误"
**排查**:
1. 检查后端服务是否启动
2. 检查 `apiBaseURL` 配置是否正确
3. 检查网络连接
4. 检查CORS跨域配置
### 3. 数据格式不匹配
**现象**: 接口返回数据,但页面显示异常
**排查**:
1. 打开调试模式: `config.debug = true`
2. 查看控制台日志
3. 检查响应数据格式
4. 对比Mock数据和API数据的差异
5.`api/*.js` 中添加数据转换
### 4. 分页数据提取
**现象**: 场地、项目列表显示为空
**原因**: 后端返回的是 `data.records`,不是 `data`
**解决**: 参考上面"需要适配的地方"章节
---
## 📝 联调检查清单
### 前端准备
- [x] dataAdapter 架构完成
- [x] API接口定义完成
- [x] request.js 优化完成
- [x] Mock数据格式修复
- [x] 页面接入完成
- [ ] 分页数据适配(等待后端确认格式)
- [ ] 扣分项格式适配(等待后端确认格式)
### 后端准备
- [ ] 5个小程序专用接口开发完成
- [ ] 测试数据准备完成
- [ ] Swagger文档更新
- [ ] 单元测试通过
### 联调测试
- [ ] 登录接口测试pub角色
- [ ] 登录接口测试admin角色
- [ ] 获取选手列表测试
- [ ] 提交评分测试
- [ ] 评分详情查看测试
- [ ] 修改评分测试
- [ ] Token过期处理测试
- [ ] 权限验证测试
- [ ] 场地切换测试
- [ ] 项目切换测试
---
## 📞 联系方式
如有问题,请联系:
- **前端负责人**: [待填写]
- **后端负责人**: [待填写]
---
**文档版本**: v1.0
**最后更新**: 2025-12-12
**相关文档**:
- [后端接口开发清单](./后端接口开发清单.md)
- [API接口测试指南](./API接口测试指南.md)