# 前端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¤t=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)