Compare commits
17 Commits
c978a5bf64
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11eb5f2db8 | ||
|
|
56a33707d5 | ||
|
|
1b305fc2bd | ||
|
|
a780ee6b2c | ||
|
|
90ee38a57b | ||
|
|
84b84dd951 | ||
|
|
941112dd4c | ||
|
|
314b507748 | ||
|
|
a3680f7d3e | ||
|
|
711779dc57 | ||
|
|
edd64cda47 | ||
|
|
88a931976d | ||
|
|
96bc2d92a2 | ||
|
|
a9c5c4a904 | ||
|
|
5349b80cf8 | ||
|
|
56c1320e40 | ||
|
|
c5c31e8088 |
311
README.md
311
README.md
@@ -1,296 +1,77 @@
|
|||||||
# 武术评分系统 - UniApp版本
|
# 武术赛事管理系统 - 裁判端
|
||||||
|
|
||||||
这是一个基于UniApp开发的武术比赛评分系统,支持H5和小程序平台。
|
基于 UniApp 开发的武术比赛评分小程序,支持 H5 和微信小程序。
|
||||||
|
|
||||||
## 项目简介
|
## 在线访问
|
||||||
|
|
||||||
本项目是一个完整的武术比赛评分管理系统,包含评委打分、裁判长修改评分、多场地管理等功能。
|
| 服务 | 地址 |
|
||||||
|
|------|------|
|
||||||
|
| 裁判端 H5 | https://martial-mini.aitisai.com |
|
||||||
|
| 后端 API | https://martial-api.aitisai.com |
|
||||||
|
|
||||||
## 功能特点
|
## 功能特点
|
||||||
|
|
||||||
- ✅ 支持H5和微信小程序双平台
|
- 裁判员登录与评分
|
||||||
- ✅ 评委登录与评分功能
|
- 主裁判评分审核
|
||||||
- ✅ 裁判长评分修改功能
|
- 总裁最终确认
|
||||||
- ✅ 多场地、多项目切换
|
- 多场地切换
|
||||||
- ✅ 扣分项多选功能
|
- 扣分项管理
|
||||||
- ✅ 精确到0.001分的评分控制
|
- 精确到 0.001 分的评分控制
|
||||||
- ✅ 实时评分统计
|
|
||||||
|
|
||||||
## 页面结构
|
## 三级裁判系统
|
||||||
|
|
||||||
### 1. 登录页面 (`pages/login/login.vue`)
|
```
|
||||||
- 输入比赛编码
|
裁判员 → 主裁判 → 总裁
|
||||||
- 输入评委邀请码
|
↓ ↓ ↓
|
||||||
- 验证登录
|
打分 审核修改 最终确认
|
||||||
|
```
|
||||||
### 2. 评分列表页 (`pages/score-list/score-list.vue`)
|
|
||||||
- 显示比赛信息
|
|
||||||
- 查看选手列表
|
|
||||||
- 评委评分入口
|
|
||||||
- 显示我的评分和总分
|
|
||||||
|
|
||||||
### 3. 修改评分页 (`pages/modify-score/modify-score.vue`)
|
|
||||||
- 裁判长专用功能
|
|
||||||
- 查看所有评委评分
|
|
||||||
- 修改总分(±0.001分)
|
|
||||||
- 添加修改备注
|
|
||||||
|
|
||||||
### 4. 多场地评分列表页 (`pages/score-list-multi/score-list-multi.vue`)
|
|
||||||
- 场地切换功能
|
|
||||||
- 项目切换功能
|
|
||||||
- 裁判长可查看所有场地
|
|
||||||
- 普通评委仅看自己的场地
|
|
||||||
|
|
||||||
### 5. 评分详情页 (`pages/score-detail/score-detail.vue`)
|
|
||||||
- 评委打分界面
|
|
||||||
- 分数精确调整(±0.001分)
|
|
||||||
- 扣分项多选
|
|
||||||
- 添加评分备注
|
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- **框架**: UniApp
|
- **框架**: UniApp
|
||||||
- **前端**: Vue.js 2.x
|
- **平台**: H5、微信小程序
|
||||||
- **样式**: CSS3 (支持rpx单位)
|
- **样式**: SCSS
|
||||||
- **平台支持**: H5、微信小程序
|
|
||||||
|
|
||||||
## 目录结构
|
## 快速开始
|
||||||
|
|
||||||
```
|
|
||||||
martial-admin-mini/
|
|
||||||
├── common/ # 公共资源
|
|
||||||
│ └── common.css # 全局样式
|
|
||||||
├── pages/ # 页面目录
|
|
||||||
│ ├── login/ # 登录页面
|
|
||||||
│ ├── score-list/ # 评分列表页
|
|
||||||
│ ├── modify-score/ # 修改评分页
|
|
||||||
│ ├── score-list-multi/ # 多场地列表页
|
|
||||||
│ └── score-detail/ # 评分详情页
|
|
||||||
├── image/ # 设计图片
|
|
||||||
├── App.vue # 应用配置
|
|
||||||
├── manifest.json # 应用配置清单
|
|
||||||
├── pages.json # 页面配置
|
|
||||||
└── package.json # 项目依赖
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速开始(推荐方式)
|
|
||||||
|
|
||||||
### 使用HBuilderX运行(最简单)
|
|
||||||
|
|
||||||
1. **下载并安装HBuilderX**
|
|
||||||
- 官网下载:https://www.dcloud.io/hbuilderx.html
|
|
||||||
- 建议下载标准版或App开发版
|
|
||||||
|
|
||||||
2. **打开项目**
|
|
||||||
- 打开HBuilderX
|
|
||||||
- 点击菜单:文件 -> 打开目录
|
|
||||||
- 选择本项目的根目录:`martial-admin-mini`
|
|
||||||
|
|
||||||
3. **运行到H5**
|
|
||||||
- 在项目管理器中,右键点击项目根目录
|
|
||||||
- 选择:运行 -> 运行到浏览器 -> Chrome(或其他浏览器)
|
|
||||||
- 浏览器会自动打开并显示项目
|
|
||||||
|
|
||||||
4. **运行到微信小程序**
|
|
||||||
- 首先安装并打开微信开发者工具
|
|
||||||
- 在HBuilderX中,右键点击项目根目录
|
|
||||||
- 选择:运行 -> 运行到小程序模拟器 -> 微信开发者工具
|
|
||||||
- 首次运行会提示配置小程序工具路径,按提示配置即可
|
|
||||||
|
|
||||||
### 常见问题解决
|
|
||||||
|
|
||||||
#### 问题1:HBuilderX提示"未安装依赖"或"缺少插件"
|
|
||||||
**解决方案:**
|
|
||||||
- 点击HBuilderX顶部菜单:工具 -> 插件安装
|
|
||||||
- 安装以下插件:
|
|
||||||
- uni-app编译器(必需)
|
|
||||||
- App真机运行(如需真机调试)
|
|
||||||
- 微信小程序支持(如需开发小程序)
|
|
||||||
|
|
||||||
#### 问题2:运行时报错"Cannot find module"
|
|
||||||
**解决方案:**
|
|
||||||
```bash
|
```bash
|
||||||
# 在项目根目录执行
|
# 安装依赖
|
||||||
npm install
|
npm install
|
||||||
```
|
|
||||||
|
|
||||||
#### 问题3:小程序无法运行
|
# H5 开发
|
||||||
**解决方案:**
|
|
||||||
1. 确保已安装微信开发者工具
|
|
||||||
2. 在HBuilderX中配置微信开发者工具路径:
|
|
||||||
- 工具 -> 设置 -> 运行配置 -> 小程序运行配置
|
|
||||||
- 配置微信开发者工具的安装路径
|
|
||||||
3. 在manifest.json中配置小程序appid(或使用测试号)
|
|
||||||
|
|
||||||
#### 问题4:H5运行时样式异常
|
|
||||||
**解决方案:**
|
|
||||||
- 清除浏览器缓存后重新运行
|
|
||||||
- 或使用Ctrl+F5强制刷新
|
|
||||||
|
|
||||||
## 开发指南
|
|
||||||
|
|
||||||
### 环境要求
|
|
||||||
|
|
||||||
- HBuilderX 3.0+ (推荐)
|
|
||||||
- 或 Node.js 12+ + Vue CLI
|
|
||||||
- 微信开发者工具 (小程序开发需要)
|
|
||||||
|
|
||||||
### 方式一:使用HBuilderX(推荐)
|
|
||||||
|
|
||||||
1. 使用HBuilderX打开项目根目录
|
|
||||||
2. 点击运行 -> 运行到浏览器 (H5开发)
|
|
||||||
3. 或点击运行 -> 运行到小程序模拟器 -> 微信开发者工具
|
|
||||||
|
|
||||||
### 方式二:使用命令行
|
|
||||||
|
|
||||||
#### 安装依赖
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 运行项目
|
|
||||||
|
|
||||||
**H5开发**
|
|
||||||
```bash
|
|
||||||
npm run dev:h5
|
npm run dev:h5
|
||||||
```
|
|
||||||
|
|
||||||
**微信小程序开发**
|
# 微信小程序开发
|
||||||
```bash
|
|
||||||
npm run dev:mp-weixin
|
npm run dev:mp-weixin
|
||||||
```
|
|
||||||
|
|
||||||
**构建H5**
|
# 构建 H5
|
||||||
```bash
|
|
||||||
npm run build:h5
|
npm run build:h5
|
||||||
```
|
```
|
||||||
|
|
||||||
**构建微信小程序**
|
## 项目结构
|
||||||
```bash
|
|
||||||
npm run build:mp-weixin
|
|
||||||
```
|
|
||||||
|
|
||||||
## 设计还原说明
|
|
||||||
|
|
||||||
本项目严格按照提供的5张设计图进行一比一还原:
|
|
||||||
|
|
||||||
1. **颜色方案**
|
|
||||||
- 主色调:绿色 (#1B7C5E - #2A9D7E 渐变)
|
|
||||||
- 强调色:红色 (#FF4D6A) - 用于提示信息
|
|
||||||
- 背景色:浅灰 (#F5F5F5)
|
|
||||||
|
|
||||||
2. **字体大小**
|
|
||||||
- 导航标题:36rpx
|
|
||||||
- 页面标题:40rpx
|
|
||||||
- 正文内容:26-32rpx
|
|
||||||
- 提示文字:22-24rpx
|
|
||||||
|
|
||||||
3. **间距与圆角**
|
|
||||||
- 卡片圆角:16rpx
|
|
||||||
- 按钮圆角:8-16rpx
|
|
||||||
- 标准间距:30rpx
|
|
||||||
- 内边距:20-40rpx
|
|
||||||
|
|
||||||
4. **交互效果**
|
|
||||||
- 按钮点击反馈
|
|
||||||
- 分数增减控制
|
|
||||||
- 多选框交互
|
|
||||||
- 页面跳转动画
|
|
||||||
|
|
||||||
## 功能说明
|
|
||||||
|
|
||||||
### 评分规则
|
|
||||||
|
|
||||||
- 分数范围:5.0 - 10.0 分
|
|
||||||
- 精度:0.001 分
|
|
||||||
- 评委评分保留3位小数
|
|
||||||
- 裁判长可修改总分 ±0.005 分
|
|
||||||
|
|
||||||
### 权限区分
|
|
||||||
|
|
||||||
- **普通评委**:仅能查看和评分自己负责的场地和项目
|
|
||||||
- **裁判长**:可查看所有场地和项目,可修改评分
|
|
||||||
|
|
||||||
### 扣分项
|
|
||||||
|
|
||||||
- 支持多选
|
|
||||||
- 每个项目可配置不同的扣分项
|
|
||||||
- 扣分项选择后自动计入总分
|
|
||||||
|
|
||||||
## 🚀 API对接状态
|
|
||||||
|
|
||||||
### ✅ 前端已完全准备就绪(100%)
|
|
||||||
|
|
||||||
本项目已完成API对接准备工作,可以立即开始后端对接:
|
|
||||||
|
|
||||||
- ✅ **dataAdapter架构** - 支持Mock/API双模式无缝切换
|
|
||||||
- ✅ **API接口定义** - 9个接口全部定义完成
|
|
||||||
- ✅ **网络请求封装** - 统一的错误处理和Token管理
|
|
||||||
- ✅ **Mock数据完整** - 可独立演示所有功能
|
|
||||||
- ✅ **文档体系完善** - 21个文档,约25,000+行
|
|
||||||
|
|
||||||
### 📋 快速开始API对接
|
|
||||||
|
|
||||||
#### 1. 配置后端地址(30秒)
|
|
||||||
|
|
||||||
编辑 `config/env.config.js`:
|
|
||||||
```javascript
|
|
||||||
apiBaseURL: 'http://localhost:8080' // 修改为实际后端地址
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 切换数据模式
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Mock模式(后端未就绪时)
|
|
||||||
dataMode: 'mock'
|
|
||||||
|
|
||||||
// API模式(后端就绪后)
|
|
||||||
dataMode: 'api'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 查看文档
|
|
||||||
|
|
||||||
- **快速上手**: [API对接快速启动指南.md](doc/API对接快速启动指南.md) - 5分钟快速上手
|
|
||||||
- **后端开发**: [后端接口开发清单.md](doc/后端接口开发清单.md) - 详细的开发规范
|
|
||||||
- **前端联调**: [前端API对接指南.md](doc/前端API对接指南.md) - 前端联调指南
|
|
||||||
- **快速参考**: [快速参考.md](快速参考.md) - 一页纸快速参考
|
|
||||||
|
|
||||||
### ⚠️ 后端待开发接口(5个)
|
|
||||||
|
|
||||||
| 接口 | 路径 | 优先级 | 工作量 |
|
|
||||||
|------|------|--------|--------|
|
|
||||||
| 登录验证 | `POST /api/mini/login` | 🔴 高 | 2天 |
|
|
||||||
| 普通评委选手列表 | `GET /api/mini/athletes` | 🔴 高 | 1天 |
|
|
||||||
| 裁判长选手列表 | `GET /api/mini/athletes/admin` | 🟡 中 | 1天 |
|
|
||||||
| 评分详情 | `GET /api/mini/score/detail/{id}` | 🟡 中 | 1天 |
|
|
||||||
| 修改评分 | `PUT /api/mini/score/modify` | 🟡 中 | 1天 |
|
|
||||||
|
|
||||||
**预计总工作量**: 6人天(约1周)
|
|
||||||
|
|
||||||
详细规范请查看:[后端接口开发清单.md](doc/后端接口开发清单.md)
|
|
||||||
|
|
||||||
### 📊 项目状态
|
|
||||||
|
|
||||||
```
|
```
|
||||||
前端开发: ████████████████████ 100% ✅
|
martial-admin-mini/
|
||||||
后端开发: ████████░░░░░░░░░░░░ 44% ⚠️
|
├── pages/
|
||||||
文档完成: ████████████████████ 100% ✅
|
│ ├── login/ # 登录页
|
||||||
|
│ ├── score-list/ # 评分列表
|
||||||
|
│ ├── score-list-multi/ # 多场地评分
|
||||||
|
│ ├── score-detail/ # 评分详情
|
||||||
|
│ └── modify-score/ # 修改评分(裁判长)
|
||||||
|
├── components/ # 公共组件
|
||||||
|
├── static/ # 静态资源
|
||||||
|
├── pages.json # 页面配置
|
||||||
|
└── manifest.json # 应用配置
|
||||||
```
|
```
|
||||||
|
|
||||||
查看实时状态:[项目状态看板.md](项目状态看板.md)
|
## 相关仓库
|
||||||
|
|
||||||
## 注意事项
|
| 仓库 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| [martial-master](https://git.waypeak.work/martial/martial-master) | 后端 API |
|
||||||
|
| [martial-web](https://git.waypeak.work/martial/martial-web) | 管理后台 |
|
||||||
|
| [martial-mini](https://git.waypeak.work/martial/martial-mini) | 用户端小程序 |
|
||||||
|
|
||||||
1. ✅ 本项目已完成API对接准备,支持Mock/API双模式
|
---
|
||||||
2. ✅ Mock模式下所有功能可独立演示
|
|
||||||
3. ✅ API模式下需要后端实现5个专用接口
|
|
||||||
4. ✅ 页面跳转已配置,可直接运行演示
|
|
||||||
5. ✅ 适配了主流手机屏幕尺寸
|
|
||||||
|
|
||||||
## 许可证
|
**最后更新**: 2024-12-29
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
|
|
||||||
如有问题或建议,请联系项目负责人。
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import request from '@/utils/request.js'
|
|||||||
* 获取选手列表(根据裁判类型返回不同数据)
|
* 获取选手列表(根据裁判类型返回不同数据)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.judgeId - 评委ID
|
* @param {String} params.judgeId - 评委ID
|
||||||
* @param {Number} params.refereeType - 裁判类型(1-裁判长, 2-普通裁判)
|
* @param {Number} params.refereeType - 裁判类型(1-主裁判, 2-裁判员)
|
||||||
* @param {String} params.venueId - 场地ID(可选)
|
* @param {String} params.venueId - 场地ID(可选)
|
||||||
* @param {String} params.projectId - 项目ID(可选)
|
* @param {String} params.projectId - 项目ID(可选)
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*
|
*
|
||||||
* 普通裁判:返回待评分的选手列表
|
* 裁判员:返回待评分的选手列表
|
||||||
* 裁判长:返回已有评分的选手列表
|
* 主裁判:返回已有评分的选手列表
|
||||||
*
|
*
|
||||||
* 后端路径: GET /api/mini/score/athletes
|
* 后端路径: GET /api/mini/score/athletes
|
||||||
*/
|
*/
|
||||||
@@ -23,27 +23,40 @@ export function getMyAthletes(params) {
|
|||||||
return request({
|
return request({
|
||||||
url: '/mini/score/athletes',
|
url: '/mini/score/athletes',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params, // GET 请求使用 params
|
params: {
|
||||||
|
...params,
|
||||||
|
size: 200 // 确保获取所有选手
|
||||||
|
},
|
||||||
showLoading: true
|
showLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.competitionId - 比赛ID
|
* @param {String} params.competitionId - 比赛ID
|
||||||
* @param {String} params.venueId - 场地ID
|
* @param {String} params.venueId - 场地ID
|
||||||
* @param {String} params.projectId - 项目ID
|
* @param {String} params.projectId - 项目ID
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*
|
*
|
||||||
* 注意:此接口需要后端实现
|
* 实际调用 /mini/score/athletes 接口,传递 refereeType=1
|
||||||
* 建议路径: GET /api/mini/athletes/admin
|
|
||||||
*/
|
*/
|
||||||
export function getAthletesForAdmin(params) {
|
export function getAthletesForAdmin(params) {
|
||||||
|
// 从 globalData 获取 judgeId
|
||||||
|
const app = getApp()
|
||||||
|
const globalData = app.globalData || {}
|
||||||
|
const judgeId = globalData.judgeId
|
||||||
|
|
||||||
return request({
|
return request({
|
||||||
url: '/mini/athletes/admin',
|
url: '/mini/score/athletes',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params, // GET 请求使用 params
|
params: {
|
||||||
|
judgeId: judgeId,
|
||||||
|
refereeType: 1, // 主裁判
|
||||||
|
venueId: params.venueId,
|
||||||
|
projectId: params.projectId,
|
||||||
|
size: 200 // 确保获取所有选手
|
||||||
|
},
|
||||||
showLoading: true
|
showLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -90,55 +103,3 @@ export default {
|
|||||||
getVenues,
|
getVenues,
|
||||||
getProjects
|
getProjects
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 后端接口规范:
|
|
||||||
*
|
|
||||||
* GET /api/mini/score/athletes
|
|
||||||
*
|
|
||||||
* 请求参数:
|
|
||||||
* {
|
|
||||||
* "judgeId": "456",
|
|
||||||
* "refereeType": 2, // 1-裁判长, 2-普通裁判
|
|
||||||
* "venueId": "1", // 可选
|
|
||||||
* "projectId": "5" // 可选
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* 响应(普通裁判 - 待评分选手):
|
|
||||||
* {
|
|
||||||
* "code": 200,
|
|
||||||
* "success": true,
|
|
||||||
* "msg": "操作成功",
|
|
||||||
* "data": [
|
|
||||||
* {
|
|
||||||
* "athleteId": 1,
|
|
||||||
* "name": "张三",
|
|
||||||
* "number": "123-4567898275",
|
|
||||||
* "team": "少林寺武术大学院",
|
|
||||||
* "projectName": "女子组长拳",
|
|
||||||
* "orderNum": 1,
|
|
||||||
* "competitionStatus": 0
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* 响应(裁判长 - 已有评分选手):
|
|
||||||
* {
|
|
||||||
* "code": 200,
|
|
||||||
* "success": true,
|
|
||||||
* "msg": "操作成功",
|
|
||||||
* "data": [
|
|
||||||
* {
|
|
||||||
* "athleteId": 1,
|
|
||||||
* "name": "张三",
|
|
||||||
* "number": "123-4567898275",
|
|
||||||
* "team": "少林寺武术大学院",
|
|
||||||
* "projectName": "女子组长拳",
|
|
||||||
* "orderNum": 1,
|
|
||||||
* "totalScore": 8.907,
|
|
||||||
* "scoredJudgeCount": 3,
|
|
||||||
* "competitionStatus": 2
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default {
|
|||||||
* "msg": "登录成功",
|
* "msg": "登录成功",
|
||||||
* "data": {
|
* "data": {
|
||||||
* "token": "xxx",
|
* "token": "xxx",
|
||||||
* "refereeType": 2, // 1-裁判长, 2-普通裁判
|
* "refereeType": 2, // 1-主裁判, 2-裁判员
|
||||||
* "matchId": "123",
|
* "matchId": "123",
|
||||||
* "matchName": "2025年全国武术散打锦标赛...",
|
* "matchName": "2025年全国武术散打锦标赛...",
|
||||||
* "matchTime": "2025年6月25日 9:00",
|
* "matchTime": "2025年6月25日 9:00",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default {
|
|||||||
getMyAthletes: athleteApi.getMyAthletes,
|
getMyAthletes: athleteApi.getMyAthletes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params - { competitionId, venueId, projectId }
|
* @param {Object} params - { competitionId, venueId, projectId }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -82,14 +82,14 @@ export default {
|
|||||||
submitScore: scoreApi.submitScore,
|
submitScore: scoreApi.submitScore,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params - { athleteId }
|
* @param {Object} params - { athleteId }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getScoreDetail: scoreApi.getScoreDetail,
|
getScoreDetail: scoreApi.getScoreDetail,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} data - { athleteId, modifierId, modifiedScore, note }
|
* @param {Object} data - { athleteId, modifierId, modifiedScore, note }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +127,7 @@ export default {
|
|||||||
* 1. 需要实现的新接口(小程序专用):
|
* 1. 需要实现的新接口(小程序专用):
|
||||||
* - POST /api/mini/login # 登录验证
|
* - POST /api/mini/login # 登录验证
|
||||||
* - GET /api/mini/athletes # 普通评委选手列表
|
* - GET /api/mini/athletes # 普通评委选手列表
|
||||||
* - GET /api/mini/athletes/admin # 裁判长选手列表
|
* - GET /api/mini/athletes/admin # 主裁判选手列表
|
||||||
* - GET /api/mini/score/detail/{athleteId} # 评分详情
|
* - GET /api/mini/score/detail/{athleteId} # 评分详情
|
||||||
* - PUT /api/mini/score/modify # 修改评分
|
* - PUT /api/mini/score/modify # 修改评分
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function submitScore(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
@@ -61,7 +61,7 @@ export function getScoreDetail(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @param {String} data.athleteId - 选手ID
|
* @param {String} data.athleteId - 选手ID
|
||||||
* @param {String} data.modifierId - 修改人ID
|
* @param {String} data.modifierId - 修改人ID
|
||||||
@@ -86,7 +86,7 @@ export function modifyScore(data) {
|
|||||||
* 获取选手列表
|
* 获取选手列表
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.judgeId - 裁判ID
|
* @param {String} params.judgeId - 裁判ID
|
||||||
* @param {Number} params.refereeType - 裁判类型(1-裁判长, 2-普通裁判)
|
* @param {Number} params.refereeType - 裁判类型(1-主裁判, 2-裁判员)
|
||||||
* @param {String} params.projectId - 项目ID(可选)
|
* @param {String} params.projectId - 项目ID(可选)
|
||||||
* @param {String} params.venueId - 场地ID(可选)
|
* @param {String} params.venueId - 场地ID(可选)
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
@@ -180,7 +180,7 @@ export default {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* 实现逻辑:
|
* 实现逻辑:
|
||||||
* 1. 验证权限(只有裁判长可以修改)
|
* 1. 验证权限(只有主裁判可以修改)
|
||||||
* 2. 保存 originalScore(如果是第一次修改)
|
* 2. 保存 originalScore(如果是第一次修改)
|
||||||
* 3. 更新 totalScore
|
* 3. 更新 totalScore
|
||||||
* 4. 记录 modifyReason 和 modifyTime
|
* 4. 记录 modifyReason 和 modifyTime
|
||||||
|
|||||||
213
index.html
213
index.html
@@ -3,32 +3,229 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<!-- 关键:使用最严格的 viewport 设置 -->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
<title>武术评分系统</title>
|
<title>武术评分系统</title>
|
||||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
touch-action: manipulation;
|
/* 允许垂直滚动,但禁用其他触摸动作 */
|
||||||
-webkit-touch-callout: none;
|
touch-action: pan-y !important;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-touch-callout: none !important;
|
||||||
|
-webkit-tap-highlight-color: transparent !important;
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
user-select: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 针对按钮元素完全禁用所有触摸动作 */
|
||||||
|
button,
|
||||||
|
.control-btn,
|
||||||
|
[class*="btn"],
|
||||||
|
[class*="control"],
|
||||||
|
.decrease,
|
||||||
|
.increase {
|
||||||
|
touch-action: none !important;
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
user-select: none !important;
|
||||||
|
-webkit-touch-callout: none !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 允许输入框正常交互 */
|
||||||
|
input, textarea {
|
||||||
|
touch-action: manipulation !important;
|
||||||
|
-webkit-user-select: text !important;
|
||||||
|
user-select: text !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 防止页面整体缩放 */
|
||||||
|
html, body {
|
||||||
|
touch-action: pan-y !important;
|
||||||
|
-ms-touch-action: pan-y !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
// 禁用 iOS Safari 双击缩放
|
// UniApp H5 专用:iOS Safari 双击缩放终极解决方案
|
||||||
(function() {
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var lastTouchEnd = 0;
|
var lastTouchEnd = 0;
|
||||||
|
var touchStartTime = 0;
|
||||||
|
var touchCount = 0;
|
||||||
|
var resetTimer = null;
|
||||||
|
var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||||
|
|
||||||
|
console.log('iOS 检测:', isIOS);
|
||||||
|
console.log('User Agent:', navigator.userAgent);
|
||||||
|
|
||||||
|
// 方案1: 全局拦截 touchstart - 最高优先级
|
||||||
|
document.addEventListener('touchstart', function(event) {
|
||||||
|
var now = Date.now();
|
||||||
|
touchStartTime = now;
|
||||||
|
|
||||||
|
// 清除重置计时器
|
||||||
|
if (resetTimer) {
|
||||||
|
clearTimeout(resetTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是快速连续触摸
|
||||||
|
var timeSinceLastTouch = now - lastTouchEnd;
|
||||||
|
|
||||||
|
if (timeSinceLastTouch < 350) {
|
||||||
|
touchCount++;
|
||||||
|
|
||||||
|
// 如果是第二次或更多次快速触摸,立即阻止
|
||||||
|
if (touchCount >= 1) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
console.log('阻止快速连续触摸', touchCount, timeSinceLastTouch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
touchCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 600ms 后重置计数器
|
||||||
|
resetTimer = setTimeout(function() {
|
||||||
|
touchCount = 0;
|
||||||
|
}, 600);
|
||||||
|
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
// 方案2: 全局拦截 touchend
|
||||||
document.addEventListener('touchend', function(event) {
|
document.addEventListener('touchend', function(event) {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
if (now - lastTouchEnd <= 300) {
|
var touchDuration = now - touchStartTime;
|
||||||
|
var timeSinceLastTouch = now - lastTouchEnd;
|
||||||
|
|
||||||
|
// 如果触摸时间很短(<150ms)且距离上次触摸很近(<350ms),很可能是双击
|
||||||
|
if (touchDuration < 150 && timeSinceLastTouch < 350 && timeSinceLastTouch > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
console.log('阻止疑似双击', touchDuration, timeSinceLastTouch);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTouchEnd = now;
|
lastTouchEnd = now;
|
||||||
}, { passive: false });
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
// 禁用双击缩放
|
// 方案3: 完全禁用 dblclick 事件
|
||||||
document.addEventListener('dblclick', function(event) {
|
document.addEventListener('dblclick', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, { passive: false });
|
event.stopPropagation();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
console.log('阻止 dblclick 事件');
|
||||||
|
return false;
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
// 方案4: 禁用手势缩放
|
||||||
|
document.addEventListener('gesturestart', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
console.log('阻止 gesturestart');
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
document.addEventListener('gesturechange', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
document.addEventListener('gestureend', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
// 方案5: 监听 click 事件,过滤快速连续点击
|
||||||
|
var lastClickTime = 0;
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
var now = Date.now();
|
||||||
|
var timeSinceLastClick = now - lastClickTime;
|
||||||
|
|
||||||
|
// 如果距离上次点击小于350ms,阻止
|
||||||
|
if (timeSinceLastClick < 350 && timeSinceLastClick > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
console.log('阻止快速连续点击', timeSinceLastClick);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastClickTime = now;
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
|
||||||
|
// 方案6: 针对按钮元素的特殊处理
|
||||||
|
function addButtonProtection() {
|
||||||
|
var selectors = [
|
||||||
|
'.control-btn',
|
||||||
|
'.control-btn.decrease',
|
||||||
|
'.control-btn.increase',
|
||||||
|
'button',
|
||||||
|
'[class*="btn"]'
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(function(selector) {
|
||||||
|
var elements = document.querySelectorAll(selector);
|
||||||
|
elements.forEach(function(element) {
|
||||||
|
// 移除所有现有的事件监听器(通过克隆节点)
|
||||||
|
var newElement = element.cloneNode(true);
|
||||||
|
element.parentNode.replaceChild(newElement, element);
|
||||||
|
|
||||||
|
// 添加新的保护性事件监听器
|
||||||
|
['touchstart', 'touchend', 'touchmove', 'click', 'dblclick'].forEach(function(eventType) {
|
||||||
|
newElement.addEventListener(eventType, function(e) {
|
||||||
|
if (eventType === 'dblclick') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, { passive: false, capture: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM 加载完成后添加按钮保护
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
setTimeout(addButtonProtection, 100);
|
||||||
|
// 使用 MutationObserver 监听 DOM 变化
|
||||||
|
var observer = new MutationObserver(function(mutations) {
|
||||||
|
addButtonProtection();
|
||||||
|
});
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(addButtonProtection, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方案7: 使用 CSS 强制禁用缩放
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.innerHTML = `
|
||||||
|
* {
|
||||||
|
touch-action: pan-y !important;
|
||||||
|
}
|
||||||
|
.control-btn,
|
||||||
|
.control-btn *,
|
||||||
|
button,
|
||||||
|
button * {
|
||||||
|
touch-action: none !important;
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
user-select: none !important;
|
||||||
|
-webkit-touch-callout: none !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
console.log('iOS Safari 双击缩放防护已启用 - UniApp H5 专用版本');
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* 获取选手列表(根据裁判类型返回不同数据)
|
* 获取选手列表(根据裁判类型返回不同数据)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.judgeId - 评委ID
|
* @param {String} params.judgeId - 评委ID
|
||||||
* @param {Number} params.refereeType - 裁判类型(1-裁判长, 2-普通裁判)
|
* @param {Number} params.refereeType - 裁判类型(1-主裁判, 2-裁判员)
|
||||||
* @param {String} params.venueId - 场地ID(可选)
|
* @param {String} params.venueId - 场地ID(可选)
|
||||||
* @param {String} params.projectId - 项目ID(可选)
|
* @param {String} params.projectId - 项目ID(可选)
|
||||||
* @returns {Array} 选手列表
|
* @returns {Array} 选手列表
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
export function getMyAthletes(params) {
|
export function getMyAthletes(params) {
|
||||||
const { refereeType } = params
|
const { refereeType } = params
|
||||||
|
|
||||||
// 裁判长:返回已有评分的选手
|
// 主裁判:返回已有评分的选手
|
||||||
if (refereeType === 1) {
|
if (refereeType === 1) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -54,7 +54,7 @@ export function getMyAthletes(params) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 普通裁判:返回待评分的选手
|
// 裁判员:返回待评分的选手
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
athleteId: 3,
|
athleteId: 3,
|
||||||
@@ -80,7 +80,7 @@ export function getMyAthletes(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.competitionId - 比赛ID
|
* @param {String} params.competitionId - 比赛ID
|
||||||
* @param {String} params.venueId - 场地ID
|
* @param {String} params.venueId - 场地ID
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default {
|
|||||||
getMyAthletes: athleteMock.getMyAthletes,
|
getMyAthletes: athleteMock.getMyAthletes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params - { competitionId, venueId, projectId }
|
* @param {Object} params - { competitionId, venueId, projectId }
|
||||||
* @returns {Array} 选手列表(带评分统计)
|
* @returns {Array} 选手列表(带评分统计)
|
||||||
*/
|
*/
|
||||||
@@ -70,14 +70,14 @@ export default {
|
|||||||
submitScore: scoreMock.submitScore,
|
submitScore: scoreMock.submitScore,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params - { athleteId }
|
* @param {Object} params - { athleteId }
|
||||||
* @returns {Object} 评分详情(选手信息+评委评分)
|
* @returns {Object} 评分详情(选手信息+评委评分)
|
||||||
*/
|
*/
|
||||||
getScoreDetail: scoreMock.getScoreDetail,
|
getScoreDetail: scoreMock.getScoreDetail,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} params - { athleteId, modifierId, modifiedScore, note }
|
* @param {Object} params - { athleteId, modifierId, modifiedScore, note }
|
||||||
* @returns {Object} 修改结果
|
* @returns {Object} 修改结果
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ export function login(params) {
|
|||||||
// 返回Mock登录数据
|
// 返回Mock登录数据
|
||||||
return {
|
return {
|
||||||
token: 'mock_token_' + Date.now(),
|
token: 'mock_token_' + Date.now(),
|
||||||
refereeType: role === 'pub' ? 2 : 1, // 1-裁判长, 2-普通裁判
|
refereeType: role === 'pub' ? 2 : 1, // 1-主裁判, 2-裁判员
|
||||||
matchId: matchCode || '200', // 使用传入的比赛编码,默认200
|
matchId: matchCode || '200', // 使用传入的比赛编码,默认200
|
||||||
matchName: '2025年全国武术散打锦标赛暨第十七届世界武术锦标赛选拔赛',
|
matchName: '2025年全国武术散打锦标赛暨第十七届世界武术锦标赛选拔赛',
|
||||||
matchTime: '2025年6月25日 9:00',
|
matchTime: '2025年6月25日 9:00',
|
||||||
judgeId: '456',
|
judgeId: '456',
|
||||||
judgeName: '欧阳丽娜',
|
judgeName: '欧阳丽娜',
|
||||||
// 普通评委有固定场地,裁判长可以查看所有场地
|
// 普通评委有固定场地,主裁判可以查看所有场地
|
||||||
venueId: role === 'pub' ? '1' : null,
|
venueId: role === 'pub' ? '1' : null,
|
||||||
venueName: role === 'pub' ? '第一场地' : null,
|
venueName: role === 'pub' ? '第一场地' : null,
|
||||||
// 分配的项目列表
|
// 分配的项目列表
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function submitScore(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @returns {Object} 评分详情
|
* @returns {Object} 评分详情
|
||||||
@@ -131,10 +131,10 @@ export function getScoreDetail(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @param {String} params.modifierId - 修改人ID(裁判长)
|
* @param {String} params.modifierId - 修改人ID(主裁判)
|
||||||
* @param {Number} params.modifiedScore - 修改后的分数
|
* @param {Number} params.modifiedScore - 修改后的分数
|
||||||
* @param {String} params.note - 修改原因
|
* @param {String} params.note - 修改原因
|
||||||
* @returns {Object} 修改结果
|
* @returns {Object} 修改结果
|
||||||
|
|||||||
@@ -122,14 +122,14 @@ export default {
|
|||||||
// 保存用户信息到全局数据
|
// 保存用户信息到全局数据
|
||||||
getApp().globalData = {
|
getApp().globalData = {
|
||||||
userRole, // 'pub' 或 'admin'
|
userRole, // 'pub' 或 'admin'
|
||||||
refereeType, // 1-裁判长, 2-普通裁判
|
refereeType, // 1-主裁判, 2-裁判员
|
||||||
matchCode: this.matchCode,
|
matchCode: this.matchCode,
|
||||||
matchId,
|
matchId,
|
||||||
matchName,
|
matchName,
|
||||||
matchTime,
|
matchTime,
|
||||||
judgeId,
|
judgeId,
|
||||||
judgeName,
|
judgeName,
|
||||||
venueId, // 普通评委有场地,裁判长为null
|
venueId, // 普通评委有场地,主裁判为null
|
||||||
venueName,
|
venueName,
|
||||||
projects, // 分配的项目列表(从登录接口返回)
|
projects, // 分配的项目列表(从登录接口返回)
|
||||||
currentProjectIndex: 0 // 当前选中的项目索引
|
currentProjectIndex: 0 // 当前选中的项目索引
|
||||||
@@ -155,12 +155,12 @@ export default {
|
|||||||
// 根据角色跳转到不同页面
|
// 根据角色跳转到不同页面
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (userRole === 'admin') {
|
if (userRole === 'admin') {
|
||||||
// 裁判长跳转到多场地列表页(可以修改评分)
|
// 主裁判跳转到多场地列表页(可以修改评分)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/score-list-multi/score-list-multi'
|
url: '/pages/score-list-multi/score-list-multi'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 普通裁判跳转到评分列表页(可以评分)
|
// 裁判员跳转到评分列表页(可以评分)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/score-list/score-list'
|
url: '/pages/score-list/score-list'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -50,7 +50,13 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="score-control">
|
<view class="score-control">
|
||||||
<view class="control-btn decrease" @touchstart.prevent="decreaseScore" @click.prevent="decreaseScore">
|
<!-- 减分按钮 - 使用 catchtouchstart 阻止事件冒泡 -->
|
||||||
|
<view
|
||||||
|
class="control-btn decrease"
|
||||||
|
@touchstart="onDecreaseStart"
|
||||||
|
@touchend="onDecreaseEnd"
|
||||||
|
@touchcancel="onTouchCancel"
|
||||||
|
>
|
||||||
<text class="btn-symbol">-</text>
|
<text class="btn-symbol">-</text>
|
||||||
<text class="btn-value">-0.001</text>
|
<text class="btn-value">-0.001</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -60,15 +66,17 @@
|
|||||||
<text class="no-modify-text">可不改</text>
|
<text class="no-modify-text">可不改</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="control-btn increase" @touchstart.prevent="increaseScore" @click.prevent="increaseScore">
|
<!-- 加分按钮 - 使用 catchtouchstart 阻止事件冒泡 -->
|
||||||
|
<view
|
||||||
|
class="control-btn increase"
|
||||||
|
@touchstart="onIncreaseStart"
|
||||||
|
@touchend="onIncreaseEnd"
|
||||||
|
@touchcancel="onTouchCancel"
|
||||||
|
>
|
||||||
<text class="btn-symbol">+</text>
|
<text class="btn-symbol">+</text>
|
||||||
<text class="btn-value">+0.001</text>
|
<text class="btn-value">+0.001</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view class="modify-tip">
|
|
||||||
裁判长修改:保留3位小数点,超过上限或下限时,按钮置灰
|
|
||||||
</view> -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 备注 -->
|
<!-- 备注 -->
|
||||||
@@ -114,7 +122,15 @@ export default {
|
|||||||
originalScore: 8.000,
|
originalScore: 8.000,
|
||||||
note: '',
|
note: '',
|
||||||
minScore: 5.0,
|
minScore: 5.0,
|
||||||
maxScore: 10.0
|
maxScore: 10.0,
|
||||||
|
// 防止双击的状态管理
|
||||||
|
isTouching: false,
|
||||||
|
touchTimer: null,
|
||||||
|
lastTouchTime: 0,
|
||||||
|
// 长按相关
|
||||||
|
longPressTimer: null,
|
||||||
|
longPressInterval: null,
|
||||||
|
isLongPressing: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -124,9 +140,9 @@ export default {
|
|||||||
const globalData = app.globalData || {}
|
const globalData = app.globalData || {}
|
||||||
|
|
||||||
// 获取当前选手信息(从 score-list-multi 页面传递)
|
// 获取当前选手信息(从 score-list-multi 页面传递)
|
||||||
const currentAthlete = globalData.currentAthlete || {}
|
const currentAthlete = globalData.currentAthlete ||
|
||||||
|
|
||||||
// 获取裁判长ID
|
// 获取主裁判ID
|
||||||
this.modifierId = globalData.judgeId
|
this.modifierId = globalData.judgeId
|
||||||
|
|
||||||
// 调试信息
|
// 调试信息
|
||||||
@@ -141,9 +157,151 @@ export default {
|
|||||||
if (currentAthlete.athleteId) {
|
if (currentAthlete.athleteId) {
|
||||||
await this.loadScoreDetail(currentAthlete.athleteId)
|
await this.loadScoreDetail(currentAthlete.athleteId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// H5 平台特殊处理:禁用双击缩放
|
||||||
|
// #ifdef H5
|
||||||
|
this.disableDoubleTapZoom()
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnload() {
|
||||||
|
// 清理定时器
|
||||||
|
this.clearAllTimers()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
// #ifdef H5
|
||||||
|
disableDoubleTapZoom() {
|
||||||
|
// 在 H5 环境下,添加额外的事件监听来防止双击缩放
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const decreaseBtn = document.querySelector('.control-btn.decrease')
|
||||||
|
const increaseBtn = document.querySelector('.control-btn.increase')
|
||||||
|
|
||||||
|
const preventZoom = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decreaseBtn) {
|
||||||
|
decreaseBtn.addEventListener('touchstart', preventZoom, { passive: false, capture: true })
|
||||||
|
decreaseBtn.addEventListener('touchend', preventZoom, { passive: false, capture: true })
|
||||||
|
decreaseBtn.addEventListener('touchmove', preventZoom, { passive: false, capture: true })
|
||||||
|
decreaseBtn.addEventListener('click', preventZoom, { passive: false, capture: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (increaseBtn) {
|
||||||
|
increaseBtn.addEventListener('touchstart', preventZoom, { passive: false, capture: true })
|
||||||
|
increaseBtn.addEventListener('touchend', preventZoom, { passive: false, capture: true })
|
||||||
|
increaseBtn.addEventListener('touchmove', preventZoom, { passive: false, capture: true })
|
||||||
|
increaseBtn.addEventListener('click', preventZoom, { passive: false, capture: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
clearAllTimers() {
|
||||||
|
if (this.touchTimer) {
|
||||||
|
clearTimeout(this.touchTimer)
|
||||||
|
this.touchTimer = null
|
||||||
|
}
|
||||||
|
if (this.longPressTimer) {
|
||||||
|
clearTimeout(this.longPressTimer)
|
||||||
|
this.longPressTimer = null
|
||||||
|
}
|
||||||
|
if (this.longPressInterval) {
|
||||||
|
clearInterval(this.longPressInterval)
|
||||||
|
this.longPressInterval = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 减分按钮 - touchstart
|
||||||
|
onDecreaseStart(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
// 防止快速连续触摸(300ms内的触摸被忽略)
|
||||||
|
if (now - this.lastTouchTime < 300) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastTouchTime = now
|
||||||
|
this.isTouching = true
|
||||||
|
|
||||||
|
// 立即执行一次减分
|
||||||
|
this.decreaseScore()
|
||||||
|
|
||||||
|
// 设置长按定时器(500ms后开始连续减分)
|
||||||
|
this.longPressTimer = setTimeout(() => {
|
||||||
|
this.isLongPressing = true
|
||||||
|
// 每100ms执行一次减分
|
||||||
|
this.longPressInterval = setInterval(() => {
|
||||||
|
this.decreaseScore()
|
||||||
|
}, 100)
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 减分按钮 - touchend
|
||||||
|
onDecreaseEnd(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
this.isTouching = false
|
||||||
|
this.isLongPressing = false
|
||||||
|
this.clearAllTimers()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加分按钮 - touchstart
|
||||||
|
onIncreaseStart(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
|
// 防止快速连续触摸(300ms内的触摸被忽略)
|
||||||
|
if (now - this.lastTouchTime < 300) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastTouchTime = now
|
||||||
|
this.isTouching = true
|
||||||
|
|
||||||
|
// 立即执行一次加分
|
||||||
|
this.increaseScore()
|
||||||
|
|
||||||
|
// 设置长按定时器(500ms后开始连续加分)
|
||||||
|
this.longPressTimer = setTimeout(() => {
|
||||||
|
this.isLongPressing = true
|
||||||
|
// 每100ms执行一次加分
|
||||||
|
this.longPressInterval = setInterval(() => {
|
||||||
|
this.increaseScore()
|
||||||
|
}, 100)
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加分按钮 - touchend
|
||||||
|
onIncreaseEnd(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
this.isTouching = false
|
||||||
|
this.isLongPressing = false
|
||||||
|
this.clearAllTimers()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 触摸取消
|
||||||
|
onTouchCancel(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
this.isTouching = false
|
||||||
|
this.isLongPressing = false
|
||||||
|
this.clearAllTimers()
|
||||||
|
},
|
||||||
|
|
||||||
async loadScoreDetail(athleteId) {
|
async loadScoreDetail(athleteId) {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
@@ -151,9 +309,6 @@ export default {
|
|||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔥 关键改动:使用 dataAdapter 获取评分详情
|
|
||||||
// Mock模式:调用 mock/score.js 的 getScoreDetail 函数
|
|
||||||
// API模式:调用 api/score.js 的 getScoreDetail 函数(GET /api/mini/score/detail/{athleteId})
|
|
||||||
const response = await dataAdapter.getData('getScoreDetail', {
|
const response = await dataAdapter.getData('getScoreDetail', {
|
||||||
athleteId: athleteId
|
athleteId: athleteId
|
||||||
})
|
})
|
||||||
@@ -202,12 +357,26 @@ export default {
|
|||||||
decreaseScore() {
|
decreaseScore() {
|
||||||
if (this.currentScore > this.minScore) {
|
if (this.currentScore > this.minScore) {
|
||||||
this.currentScore = parseFloat((this.currentScore - 0.001).toFixed(3))
|
this.currentScore = parseFloat((this.currentScore - 0.001).toFixed(3))
|
||||||
|
|
||||||
|
// 添加触觉反馈(仅在支持的平台)
|
||||||
|
// #ifndef H5
|
||||||
|
uni.vibrateShort({
|
||||||
|
type: 'light'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
increaseScore() {
|
increaseScore() {
|
||||||
if (this.currentScore < this.maxScore) {
|
if (this.currentScore < this.maxScore) {
|
||||||
this.currentScore = parseFloat((this.currentScore + 0.001).toFixed(3))
|
this.currentScore = parseFloat((this.currentScore + 0.001).toFixed(3))
|
||||||
|
|
||||||
|
// 添加触觉反馈(仅在支持的平台)
|
||||||
|
// #ifndef H5
|
||||||
|
uni.vibrateShort({
|
||||||
|
type: 'light'
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -236,14 +405,16 @@ export default {
|
|||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔥 关键改动:使用 dataAdapter 修改评分
|
// 获取场地ID
|
||||||
// Mock模式:调用 mock/score.js 的 modifyScore 函数
|
const app = getApp()
|
||||||
// API模式:调用 api/score.js 的 modifyScore 函数(PUT /api/mini/score/modify)
|
const venueId = app.globalData?.currentVenueId
|
||||||
|
|
||||||
const response = await dataAdapter.getData('modifyScore', {
|
const response = await dataAdapter.getData('modifyScore', {
|
||||||
athleteId: this.athleteInfo.athleteId,
|
athleteId: this.athleteInfo.athleteId,
|
||||||
modifierId: this.modifierId,
|
modifierId: this.modifierId,
|
||||||
modifiedScore: this.currentScore,
|
modifiedScore: this.currentScore,
|
||||||
note: this.note
|
note: this.note,
|
||||||
|
venueId: venueId // 添加场地ID
|
||||||
})
|
})
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
@@ -459,18 +630,22 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.control-btn {
|
.control-btn {
|
||||||
touch-action: manipulation;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
width: 140rpx;
|
width: 140rpx;
|
||||||
height: 140rpx;
|
height: 140rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: #F5F5F5;
|
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
/* 关键:禁用所有可能导致缩放的触摸行为 */
|
||||||
|
touch-action: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn.decrease {
|
.control-btn.decrease {
|
||||||
@@ -484,6 +659,7 @@ export default {
|
|||||||
.btn-symbol {
|
.btn-symbol {
|
||||||
font-size: 48rpx;
|
font-size: 48rpx;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn.decrease .btn-symbol {
|
.control-btn.decrease .btn-symbol {
|
||||||
@@ -497,6 +673,7 @@ export default {
|
|||||||
.btn-value {
|
.btn-value {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn.decrease .btn-value {
|
.control-btn.decrease .btn-value {
|
||||||
@@ -525,13 +702,6 @@ export default {
|
|||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modify-tip {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #FF4D6A;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 备注 */
|
/* 备注 */
|
||||||
.note-section {
|
.note-section {
|
||||||
margin: 30rpx;
|
margin: 30rpx;
|
||||||
|
|||||||
@@ -31,28 +31,22 @@
|
|||||||
<view class="score-control">
|
<view class="score-control">
|
||||||
<view class="control-btn decrease" @click="decreaseScore">
|
<view class="control-btn decrease" @click="decreaseScore">
|
||||||
<text class="btn-symbol">-</text>
|
<text class="btn-symbol">-</text>
|
||||||
<!-- <text class="btn-value">-0.001</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="score-display">
|
<view class="score-display" @click="showScoreInput">
|
||||||
<text class="current-score">{{ currentScore.toFixed(3) }}</text>
|
<text class="current-score">{{ currentScore.toFixed(3) }}</text>
|
||||||
|
<text class="edit-hint">点击编辑</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="control-btn increase" @click="increaseScore">
|
<view class="control-btn increase" @click="increaseScore">
|
||||||
<text class="btn-symbol">+</text>
|
<text class="btn-symbol">+</text>
|
||||||
<!-- <text class="btn-value">+0.001</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view class="judge-tip">
|
|
||||||
裁判评分:保留3位小数点,超过上限或下限时,按钮置灰
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 扣分项 -->
|
<!-- 扣分项 -->
|
||||||
<view class="deduction-section">
|
<view class="deduction-section">
|
||||||
<view class="deduction-header">
|
<view class="deduction-header">
|
||||||
<text class="deduction-label">扣分项:</text>
|
<text class="deduction-label">扣分项:</text>
|
||||||
<!-- <text class="deduction-hint">扣分项多选</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="deduction-list">
|
<view class="deduction-list">
|
||||||
@@ -82,12 +76,35 @@
|
|||||||
v-model="note"
|
v-model="note"
|
||||||
maxlength="200"
|
maxlength="200"
|
||||||
/>
|
/>
|
||||||
<!-- <text class="optional-text">可不填</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
<!-- 提交按钮 -->
|
||||||
<button class="submit-btn" @click="handleSubmit">提交</button>
|
<button class="submit-btn" @click="handleSubmit">提交</button>
|
||||||
|
|
||||||
|
<!-- 分数输入弹窗 -->
|
||||||
|
<view v-if="showInputModal" class="modal-overlay" @click="hideScoreInput">
|
||||||
|
<view class="modal-content" @click.stop>
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">输入分数</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<input
|
||||||
|
type="digit"
|
||||||
|
class="score-input"
|
||||||
|
v-model="inputScore"
|
||||||
|
placeholder="请输入5-10之间的分数"
|
||||||
|
:focus="showInputModal"
|
||||||
|
@confirm="confirmScoreInput"
|
||||||
|
/>
|
||||||
|
<text class="input-hint">分数范围:{{ minScore }} - {{ maxScore }},保留3位小数</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="modal-btn cancel" @click="hideScoreInput">取消</button>
|
||||||
|
<button class="modal-btn confirm" @click="confirmScoreInput">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -113,16 +130,16 @@ export default {
|
|||||||
note: '',
|
note: '',
|
||||||
minScore: 5.0,
|
minScore: 5.0,
|
||||||
maxScore: 10.0,
|
maxScore: 10.0,
|
||||||
deductions: []
|
deductions: [],
|
||||||
|
showInputModal: false,
|
||||||
|
inputScore: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
// 获取全局数据
|
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
const globalData = app.globalData || {}
|
const globalData = app.globalData || {}
|
||||||
|
|
||||||
// 加载当前选手信息(从 score-list 页面传递)
|
|
||||||
const currentAthlete = globalData.currentAthlete || {}
|
const currentAthlete = globalData.currentAthlete || {}
|
||||||
this.player = {
|
this.player = {
|
||||||
athleteId: currentAthlete.athleteId || '',
|
athleteId: currentAthlete.athleteId || '',
|
||||||
@@ -132,18 +149,15 @@ export default {
|
|||||||
number: currentAthlete.number || ''
|
number: currentAthlete.number || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选手已评分,加载其原有评分
|
|
||||||
if (currentAthlete.scored && currentAthlete.myScore) {
|
if (currentAthlete.scored && currentAthlete.myScore) {
|
||||||
this.currentScore = currentAthlete.myScore
|
this.currentScore = currentAthlete.myScore
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载评委ID和项目ID
|
|
||||||
this.judgeId = globalData.judgeId
|
this.judgeId = globalData.judgeId
|
||||||
this.projectId = globalData.currentProjectId || ''
|
this.projectId = globalData.currentProjectId || ''
|
||||||
this.competitionId = globalData.matchId || globalData.matchCode || ''
|
this.competitionId = globalData.matchId || globalData.matchCode || ''
|
||||||
this.venueId = globalData.currentVenueId || globalData.venueId || ''
|
this.venueId = globalData.currentVenueId || globalData.venueId || ''
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('评分详情页加载:', {
|
console.log('评分详情页加载:', {
|
||||||
athlete: this.player,
|
athlete: this.player,
|
||||||
@@ -155,22 +169,17 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载扣分项列表
|
|
||||||
await this.loadDeductions()
|
await this.loadDeductions()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async loadDeductions() {
|
async loadDeductions() {
|
||||||
try {
|
try {
|
||||||
// 🔥 关键改动:使用 dataAdapter 获取扣分项列表
|
|
||||||
// Mock模式:调用 mock/score.js 的 getDeductions 函数
|
|
||||||
// API模式:调用 api/score.js 的 getDeductions 函数(GET /martial/deductionItem/list)
|
|
||||||
const response = await dataAdapter.getData('getDeductions', {
|
const response = await dataAdapter.getData('getDeductions', {
|
||||||
projectId: this.projectId
|
projectId: this.projectId
|
||||||
})
|
})
|
||||||
|
|
||||||
// 为每个扣分项添加 checked 状态,并映射字段名
|
const records = response.data && response.data.records ? response.data.records : []
|
||||||
const records = response.data?.records || []
|
|
||||||
this.deductions = records.map(item => ({
|
this.deductions = records.map(item => ({
|
||||||
deductionId: item.id,
|
deductionId: item.id,
|
||||||
deductionName: item.itemName,
|
deductionName: item.itemName,
|
||||||
@@ -178,7 +187,6 @@ export default {
|
|||||||
checked: false
|
checked: false
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('扣分项加载成功:', this.deductions)
|
console.log('扣分项加载成功:', this.deductions)
|
||||||
}
|
}
|
||||||
@@ -201,7 +209,6 @@ export default {
|
|||||||
delta: 1,
|
delta: 1,
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('返回失败:', err)
|
console.error('返回失败:', err)
|
||||||
// 如果返回失败,尝试跳转到评分列表页
|
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: '/pages/score-list/score-list'
|
url: '/pages/score-list/score-list'
|
||||||
})
|
})
|
||||||
@@ -221,12 +228,44 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showScoreInput() {
|
||||||
|
this.inputScore = this.currentScore.toFixed(3)
|
||||||
|
this.showInputModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
hideScoreInput() {
|
||||||
|
this.showInputModal = false
|
||||||
|
this.inputScore = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmScoreInput() {
|
||||||
|
const score = parseFloat(this.inputScore)
|
||||||
|
|
||||||
|
if (isNaN(score)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入有效的数字',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score < this.minScore || score > this.maxScore) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `分数必须在${this.minScore}-${this.maxScore}之间`,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScore = parseFloat(score.toFixed(3))
|
||||||
|
this.hideScoreInput()
|
||||||
|
},
|
||||||
|
|
||||||
toggleDeduction(index) {
|
toggleDeduction(index) {
|
||||||
this.deductions[index].checked = !this.deductions[index].checked
|
this.deductions[index].checked = !this.deductions[index].checked
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleSubmit() {
|
async handleSubmit() {
|
||||||
// 验证评分范围
|
|
||||||
if (this.currentScore < this.minScore || this.currentScore > this.maxScore) {
|
if (this.currentScore < this.minScore || this.currentScore > this.maxScore) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: `评分必须在${this.minScore}-${this.maxScore}分之间`,
|
title: `评分必须在${this.minScore}-${this.maxScore}分之间`,
|
||||||
@@ -235,7 +274,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证必需字段
|
|
||||||
if (!this.competitionId) {
|
if (!this.competitionId) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '缺少比赛ID,请重新登录',
|
title: '缺少比赛ID,请重新登录',
|
||||||
@@ -252,7 +290,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集选中的扣分项ID
|
|
||||||
const selectedDeductions = this.deductions
|
const selectedDeductions = this.deductions
|
||||||
.filter(item => item.checked)
|
.filter(item => item.checked)
|
||||||
.map(item => item.deductionId)
|
.map(item => item.deductionId)
|
||||||
@@ -263,7 +300,6 @@ export default {
|
|||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 准备提交数据
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
athleteId: this.player.athleteId,
|
athleteId: this.player.athleteId,
|
||||||
judgeId: this.judgeId,
|
judgeId: this.judgeId,
|
||||||
@@ -275,19 +311,14 @@ export default {
|
|||||||
note: this.note
|
note: this.note
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调试日志:打印提交数据
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('准备提交评分数据:', submitData)
|
console.log('准备提交评分数据:', submitData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 关键改动:使用 dataAdapter 提交评分
|
|
||||||
// Mock模式:调用 mock/score.js 的 submitScore 函数
|
|
||||||
// API模式:调用 api/score.js 的 submitScore 函数(POST /martial/score/submit)
|
|
||||||
const response = await dataAdapter.getData('submitScore', submitData)
|
const response = await dataAdapter.getData('submitScore', submitData)
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('评分提交成功:', {
|
console.log('评分提交成功:', {
|
||||||
athleteId: this.player.athleteId,
|
athleteId: this.player.athleteId,
|
||||||
@@ -297,14 +328,12 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示成功提示
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '提交成功',
|
title: '提交成功',
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
duration: 1500
|
duration: 1500
|
||||||
})
|
})
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
@@ -485,6 +514,14 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display:active {
|
||||||
|
background-color: rgba(27, 124, 94, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-score {
|
.current-score {
|
||||||
@@ -493,6 +530,12 @@ export default {
|
|||||||
color: #1B7C5E;
|
color: #1B7C5E;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-hint {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.judge-tip {
|
.judge-tip {
|
||||||
padding: 0 30rpx;
|
padding: 0 30rpx;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
@@ -628,4 +671,95 @@ export default {
|
|||||||
.submit-btn:active {
|
.submit-btn:active {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分数输入弹窗 */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 40rpx 30rpx 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20rpx 30rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 90rpx;
|
||||||
|
border: 2rpx solid #E0E0E0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #1B7C5E;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input:focus {
|
||||||
|
border-color: #1B7C5E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx solid #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.cancel {
|
||||||
|
color: #666666;
|
||||||
|
border-right: 1rpx solid #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.confirm {
|
||||||
|
color: #1B7C5E;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:active {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<view class="venue-tip">
|
<view class="venue-tip">
|
||||||
<!-- <text class="tip-bold">裁判长可看见所有场地和项目</text> -->
|
<!-- <text class="tip-bold">主裁判可看见所有场地和项目</text> -->
|
||||||
<!-- <text class="tip-normal">(场地和项目可动态全部),可以点击切换</text> -->
|
<!-- <text class="tip-normal">(场地和项目可动态全部),可以点击切换</text> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -68,13 +68,20 @@
|
|||||||
<view class="player-header">
|
<view class="player-header">
|
||||||
<view class="player-name">{{ player.name }}</view>
|
<view class="player-name">{{ player.name }}</view>
|
||||||
|
|
||||||
<!-- 已评分:显示总分和修改按钮 -->
|
<!-- 动作区域:始终显示 -->
|
||||||
<view class="action-area" v-if="player.totalScore">
|
<view class="action-area">
|
||||||
<text class="total-score">总分:{{ player.totalScore }}</text>
|
<!-- 已评分:显示总分和修改按钮 -->
|
||||||
<view class="chief-actions">
|
<template v-if="player.scoringComplete && player.totalScore > 0">
|
||||||
<!-- <text class="chief-hint">裁判长功能:修改评分、修改按钮需等总分出来才出现</text> -->
|
<text class="total-score">总分:{{ player.totalScore }}</text>
|
||||||
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
<view class="chief-actions">
|
||||||
</view>
|
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 未评分:显示评分中提示 -->
|
||||||
|
<template v-else>
|
||||||
|
<text class="scoring-status">评分中...</text>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -123,12 +130,12 @@ export default {
|
|||||||
time: globalData.matchTime || '比赛时间'
|
time: globalData.matchTime || '比赛时间'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注意:裁判长没有固定场地和项目,需要查看所有
|
// 注意:主裁判没有固定场地和项目,需要查看所有
|
||||||
this.competitionId = globalData.matchId
|
this.competitionId = globalData.matchId
|
||||||
|
|
||||||
// 调试信息
|
// 调试信息
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('裁判长列表页加载:', {
|
console.log('主裁判列表页加载:', {
|
||||||
userRole: globalData.userRole,
|
userRole: globalData.userRole,
|
||||||
competitionId: this.competitionId
|
competitionId: this.competitionId
|
||||||
})
|
})
|
||||||
@@ -205,7 +212,7 @@ export default {
|
|||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔥 关键改动:使用 dataAdapter 获取选手列表(裁判长视图)
|
// 🔥 关键改动:使用 dataAdapter 获取选手列表(主裁判视图)
|
||||||
// Mock模式:调用 mock/athlete.js 的 getAthletesForAdmin 函数
|
// Mock模式:调用 mock/athlete.js 的 getAthletesForAdmin 函数
|
||||||
// API模式:调用 api/athlete.js 的 getAthletesForAdmin 函数(GET /api/mini/athletes/admin)
|
// API模式:调用 api/athlete.js 的 getAthletesForAdmin 函数(GET /api/mini/athletes/admin)
|
||||||
const response = await dataAdapter.getData('getAthletesForAdmin', {
|
const response = await dataAdapter.getData('getAthletesForAdmin', {
|
||||||
@@ -217,11 +224,11 @@ export default {
|
|||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 保存选手列表
|
// 保存选手列表
|
||||||
this.players = response.data || []
|
this.players = (response.data.records || response.data) || []
|
||||||
|
|
||||||
// 计算评分统计(裁判长视图:统计有总分的选手)
|
// 计算评分统计(主裁判视图:统计有总分的选手)
|
||||||
this.totalCount = this.players.length
|
this.totalCount = this.players.length
|
||||||
this.scoredCount = this.players.filter(p => p.totalScore).length
|
this.scoredCount = this.players.filter(p => p.scoringComplete).length
|
||||||
|
|
||||||
// 调试信息
|
// 调试信息
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
@@ -509,6 +516,15 @@ export default {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scoring-status {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #FF9800;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background-color: #FFF3E0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.chief-actions {
|
.chief-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
>
|
>
|
||||||
{{ project.projectName }}
|
{{ project.projectName }}
|
||||||
</view>
|
</view>
|
||||||
|
<view class="no-project-tip" v-if="projects.length === 0">当前场地暂无比赛项目</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选手列表 -->
|
<!-- 选手列表 -->
|
||||||
<view class="player-list">
|
<view class="player-list" v-if="projects.length > 0">
|
||||||
<!-- 遍历选手列表 -->
|
<!-- 遍历选手列表 -->
|
||||||
<view
|
<view
|
||||||
class="player-card"
|
class="player-card"
|
||||||
@@ -60,20 +61,37 @@
|
|||||||
<view class="player-header">
|
<view class="player-header">
|
||||||
<view class="player-name">{{ player.name }}</view>
|
<view class="player-name">{{ player.name }}</view>
|
||||||
|
|
||||||
<!-- 裁判长:显示总分和已评分裁判数 -->
|
<!-- 主裁判:显示总分和已评分裁判数 -->
|
||||||
<view class="player-scores" v-if="refereeType === 1">
|
<view class="player-scores" v-if="refereeType === 1">
|
||||||
<text class="total-score">总分:{{ player.totalScore || '未评分' }}</text>
|
<text class="total-score">
|
||||||
<text class="judge-count">已评分:{{ player.scoredJudgeCount || 0 }}人</text>
|
总分:{{ player.scoringComplete ? player.totalScore : '评分中' }}
|
||||||
|
</text>
|
||||||
|
<text class="judge-count">
|
||||||
|
已评分:{{ player.scoredJudgeCount || 0 }}/{{ player.requiredJudgeCount || 0 }}人
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 普通裁判:显示评分按钮 -->
|
<!-- 裁判员:根据评分状态显示不同内容 -->
|
||||||
<button
|
<view class="judge-action" v-else>
|
||||||
class="score-btn"
|
<!-- 已评分:显示分数和修改按钮 -->
|
||||||
v-else
|
<view class="scored-info" v-if="player.scored">
|
||||||
@click.stop="goToScoreDetail(player)"
|
<text class="my-score-text">我的评分:{{ player.myScore }}</text>
|
||||||
>
|
<button
|
||||||
评分
|
class="score-btn modify-btn"
|
||||||
</button>
|
@click.stop="goToScoreDetail(player)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<!-- 未评分:显示评分按钮 -->
|
||||||
|
<button
|
||||||
|
class="score-btn"
|
||||||
|
v-else
|
||||||
|
@click.stop="goToScoreDetail(player)"
|
||||||
|
>
|
||||||
|
评分
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="player-info">
|
<view class="player-info">
|
||||||
@@ -107,7 +125,7 @@ export default {
|
|||||||
},
|
},
|
||||||
judgeId: '',
|
judgeId: '',
|
||||||
matchId: '',
|
matchId: '',
|
||||||
refereeType: 2, // 裁判类型(1-裁判长, 2-普通裁判)
|
refereeType: 2, // 裁判类型(1-主裁判, 2-裁判员)
|
||||||
venues: [], // 所有场地列表
|
venues: [], // 所有场地列表
|
||||||
currentVenueIndex: 0, // 当前选中的场地索引
|
currentVenueIndex: 0, // 当前选中的场地索引
|
||||||
projects: [], // 所有项目列表
|
projects: [], // 所有项目列表
|
||||||
@@ -132,7 +150,7 @@ export default {
|
|||||||
|
|
||||||
this.judgeId = globalData.judgeId
|
this.judgeId = globalData.judgeId
|
||||||
this.matchId = globalData.matchId || globalData.matchCode
|
this.matchId = globalData.matchId || globalData.matchCode
|
||||||
this.refereeType = globalData.refereeType || 2 // 默认为普通裁判
|
this.refereeType = globalData.refereeType || 2 // 默认为裁判员
|
||||||
|
|
||||||
// 调试信息
|
// 调试信息
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
@@ -260,15 +278,15 @@ export default {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理选手卡片点击
|
* 处理选手卡片点击
|
||||||
* - 裁判长:跳转到查看详情页面
|
* - 主裁判:跳转到查看详情页面
|
||||||
* - 普通裁判:不处理(通过评分按钮跳转)
|
* - 裁判员:不处理(通过评分按钮跳转)
|
||||||
*/
|
*/
|
||||||
handlePlayerClick(player) {
|
handlePlayerClick(player) {
|
||||||
if (this.refereeType === 1) {
|
if (this.refereeType === 1) {
|
||||||
// 裁判长:查看评分详情
|
// 主裁判:查看评分详情
|
||||||
this.goToScoreDetail(player)
|
this.goToScoreDetail(player)
|
||||||
}
|
}
|
||||||
// 普通裁判不处理卡片点击,只能通过评分按钮跳转
|
// 裁判员不处理卡片点击,只能通过评分按钮跳转
|
||||||
},
|
},
|
||||||
|
|
||||||
goToScoreDetail(player) {
|
goToScoreDetail(player) {
|
||||||
@@ -634,6 +652,29 @@ export default {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.judge-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scored-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-score-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1B7C5E;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modify-btn {
|
||||||
|
background: linear-gradient(135deg, #FF9500 0%, #FFB340 100%);
|
||||||
|
padding: 10rpx 30rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.player-info {
|
.player-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -645,4 +686,12 @@ export default {
|
|||||||
color: #666666;
|
color: #666666;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-project-tip {
|
||||||
|
padding: 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 28rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ export function getMyAthletes(params) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
refereeType: 2 // 普通裁判
|
refereeType: 2 // 裁判员
|
||||||
},
|
},
|
||||||
showLoading: true
|
showLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.competitionId - 比赛ID
|
* @param {String} params.competitionId - 比赛ID
|
||||||
* @param {String} params.venueId - 场地ID
|
* @param {String} params.venueId - 场地ID
|
||||||
@@ -51,7 +51,7 @@ export function getAthletesForAdmin(params) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
refereeType: 1 // 裁判长
|
refereeType: 1 // 主裁判
|
||||||
},
|
},
|
||||||
showLoading: true
|
showLoading: true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default {
|
|||||||
getMyAthletes: athleteApi.getMyAthletes,
|
getMyAthletes: athleteApi.getMyAthletes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params - { competitionId, venueId, projectId }
|
* @param {Object} params - { competitionId, venueId, projectId }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -82,14 +82,14 @@ export default {
|
|||||||
submitScore: scoreApi.submitScore,
|
submitScore: scoreApi.submitScore,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params - { athleteId }
|
* @param {Object} params - { athleteId }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
getScoreDetail: scoreApi.getScoreDetail,
|
getScoreDetail: scoreApi.getScoreDetail,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} data - { athleteId, modifierId, modifiedScore, note }
|
* @param {Object} data - { athleteId, modifierId, modifiedScore, note }
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +127,7 @@ export default {
|
|||||||
* 1. 需要实现的新接口(小程序专用):
|
* 1. 需要实现的新接口(小程序专用):
|
||||||
* - POST /api/mini/login # 登录验证
|
* - POST /api/mini/login # 登录验证
|
||||||
* - GET /api/mini/athletes # 普通评委选手列表
|
* - GET /api/mini/athletes # 普通评委选手列表
|
||||||
* - GET /api/mini/athletes/admin # 裁判长选手列表
|
* - GET /api/mini/athletes/admin # 主裁判选手列表
|
||||||
* - GET /api/mini/score/detail/{athleteId} # 评分详情
|
* - GET /api/mini/score/detail/{athleteId} # 评分详情
|
||||||
* - PUT /api/mini/score/modify # 修改评分
|
* - PUT /api/mini/score/modify # 修改评分
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function submitScore(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
@@ -61,7 +61,7 @@ export function getScoreDetail(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @param {String} data.athleteId - 选手ID
|
* @param {String} data.athleteId - 选手ID
|
||||||
* @param {String} data.modifierId - 修改人ID
|
* @param {String} data.modifierId - 修改人ID
|
||||||
@@ -158,7 +158,7 @@ export default {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* 实现逻辑:
|
* 实现逻辑:
|
||||||
* 1. 验证权限(只有裁判长可以修改)
|
* 1. 验证权限(只有主裁判可以修改)
|
||||||
* 2. 保存 originalScore(如果是第一次修改)
|
* 2. 保存 originalScore(如果是第一次修改)
|
||||||
* 3. 更新 totalScore
|
* 3. 更新 totalScore
|
||||||
* 4. 记录 modifyReason 和 modifyTime
|
* 4. 记录 modifyReason 和 modifyTime
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const ENV_CONFIG = {
|
|||||||
|
|
||||||
// API基础路径(dataMode为'api'时使用)
|
// API基础路径(dataMode为'api'时使用)
|
||||||
// uni.request 不支持 devServer proxy,必须用完整地址
|
// uni.request 不支持 devServer proxy,必须用完整地址
|
||||||
apiBaseURL: 'http://142.91.105.230:8123',
|
apiBaseURL: 'https://martial-api.aitisai.com',
|
||||||
|
|
||||||
// 请求超时时间(毫秒)
|
// 请求超时时间(毫秒)
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
@@ -39,7 +39,7 @@ const ENV_CONFIG = {
|
|||||||
// 生产环境配置
|
// 生产环境配置
|
||||||
production: {
|
production: {
|
||||||
dataMode: 'api',
|
dataMode: 'api',
|
||||||
apiBaseURL: 'https://api.yourdomain.com',
|
apiBaseURL: 'https://martial-api.aitisai.com',
|
||||||
debug: false,
|
debug: false,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
mockDelay: 0
|
mockDelay: 0
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function getMyAthletes(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.competitionId - 比赛ID
|
* @param {String} params.competitionId - 比赛ID
|
||||||
* @param {String} params.venueId - 场地ID
|
* @param {String} params.venueId - 场地ID
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default {
|
|||||||
getMyAthletes: athleteMock.getMyAthletes,
|
getMyAthletes: athleteMock.getMyAthletes,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取选手列表(裁判长)
|
* 获取选手列表(主裁判)
|
||||||
* @param {Object} params - { competitionId, venueId, projectId }
|
* @param {Object} params - { competitionId, venueId, projectId }
|
||||||
* @returns {Array} 选手列表(带评分统计)
|
* @returns {Array} 选手列表(带评分统计)
|
||||||
*/
|
*/
|
||||||
@@ -70,14 +70,14 @@ export default {
|
|||||||
submitScore: scoreMock.submitScore,
|
submitScore: scoreMock.submitScore,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params - { athleteId }
|
* @param {Object} params - { athleteId }
|
||||||
* @returns {Object} 评分详情(选手信息+评委评分)
|
* @returns {Object} 评分详情(选手信息+评委评分)
|
||||||
*/
|
*/
|
||||||
getScoreDetail: scoreMock.getScoreDetail,
|
getScoreDetail: scoreMock.getScoreDetail,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} params - { athleteId, modifierId, modifiedScore, note }
|
* @param {Object} params - { athleteId, modifierId, modifiedScore, note }
|
||||||
* @returns {Object} 修改结果
|
* @returns {Object} 修改结果
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function login(params) {
|
|||||||
matchTime: '2025年6月25日 9:00',
|
matchTime: '2025年6月25日 9:00',
|
||||||
judgeId: '456',
|
judgeId: '456',
|
||||||
judgeName: '欧阳丽娜',
|
judgeName: '欧阳丽娜',
|
||||||
// 普通评委有固定场地,裁判长可以查看所有场地
|
// 普通评委有固定场地,主裁判可以查看所有场地
|
||||||
venueId: role === 'pub' ? '1' : null,
|
venueId: role === 'pub' ? '1' : null,
|
||||||
venueName: role === 'pub' ? '第一场地' : null,
|
venueName: role === 'pub' ? '第一场地' : null,
|
||||||
// 分配的项目列表(对象数组格式)
|
// 分配的项目列表(对象数组格式)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function submitScore(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取评分详情(裁判长查看)
|
* 获取评分详情(主裁判查看)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @returns {Object} 评分详情
|
* @returns {Object} 评分详情
|
||||||
@@ -125,10 +125,10 @@ export function getScoreDetail(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改评分(裁判长)
|
* 修改评分(主裁判)
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.athleteId - 选手ID
|
* @param {String} params.athleteId - 选手ID
|
||||||
* @param {String} params.modifierId - 修改人ID(裁判长)
|
* @param {String} params.modifierId - 修改人ID(主裁判)
|
||||||
* @param {Number} params.modifiedScore - 修改后的分数
|
* @param {Number} params.modifiedScore - 修改后的分数
|
||||||
* @param {String} params.note - 修改原因
|
* @param {String} params.note - 修改原因
|
||||||
* @returns {Object} 修改结果
|
* @returns {Object} 修改结果
|
||||||
|
|||||||
@@ -36,6 +36,13 @@
|
|||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/general-judge/general-judge",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|||||||
556
src/pages/general-judge/general-judge.vue
Normal file
556
src/pages/general-judge/general-judge.vue
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 导航栏 -->
|
||||||
|
<view class="nav-bar">
|
||||||
|
<view class="nav-title">总裁评分系统</view>
|
||||||
|
<view class="nav-right">
|
||||||
|
<view class="logout-btn" @click="handleLogout">退出</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 用户信息 -->
|
||||||
|
<view class="user-info">
|
||||||
|
<view class="user-name">{{ judgeName }}</view>
|
||||||
|
<view class="user-role">总裁(裁判长)</view>
|
||||||
|
<view class="match-name">{{ matchName }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 场地选择 -->
|
||||||
|
<view class="venue-section">
|
||||||
|
<view class="section-title">选择场地</view>
|
||||||
|
<view class="venue-list">
|
||||||
|
<view
|
||||||
|
class="venue-item"
|
||||||
|
:class="{ active: selectedVenueId === null }"
|
||||||
|
@click="selectVenue(null)"
|
||||||
|
>
|
||||||
|
<text>全部场地</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-for="venue in venues"
|
||||||
|
:key="venue.id"
|
||||||
|
class="venue-item"
|
||||||
|
:class="{ active: selectedVenueId === venue.id }"
|
||||||
|
@click="selectVenue(venue.id)"
|
||||||
|
>
|
||||||
|
<text>{{ venue.venueName }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 待确认成绩列表 -->
|
||||||
|
<view class="result-section">
|
||||||
|
<view class="section-title">
|
||||||
|
待确认成绩
|
||||||
|
<text class="count">({{ pendingResults.length }})</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading" class="loading">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else-if="pendingResults.length === 0" class="empty">
|
||||||
|
<text>暂无待确认成绩</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="result-list">
|
||||||
|
<view
|
||||||
|
v-for="result in pendingResults"
|
||||||
|
:key="result.id"
|
||||||
|
class="result-item pending"
|
||||||
|
@click="showConfirmDialog(result)"
|
||||||
|
>
|
||||||
|
<view class="result-info">
|
||||||
|
<view class="player-name">{{ result.playerName }}</view>
|
||||||
|
<view class="team-name">{{ result.teamName }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="score-info">
|
||||||
|
<view class="chief-score">
|
||||||
|
<text class="label">主裁判分:</text>
|
||||||
|
<text class="value">{{ result.chiefJudgeScore || result.finalScore }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-tag pending">待确认</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 已确认成绩列表 -->
|
||||||
|
<view class="result-section">
|
||||||
|
<view class="section-title">
|
||||||
|
已确认成绩
|
||||||
|
<text class="count">({{ confirmedResults.length }})</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="confirmedResults.length === 0" class="empty">
|
||||||
|
<text>暂无已确认成绩</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="result-list">
|
||||||
|
<view
|
||||||
|
v-for="result in confirmedResults"
|
||||||
|
:key="result.id"
|
||||||
|
class="result-item confirmed"
|
||||||
|
>
|
||||||
|
<view class="result-info">
|
||||||
|
<view class="player-name">{{ result.playerName }}</view>
|
||||||
|
<view class="team-name">{{ result.teamName }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="score-info">
|
||||||
|
<view class="chief-score">
|
||||||
|
<text class="label">最终得分:</text>
|
||||||
|
<text class="value confirmed">{{ result.finalScore }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-tag confirmed">已确认</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认弹窗 -->
|
||||||
|
<view v-if="showDialog" class="dialog-mask" @click="closeDialog">
|
||||||
|
<view class="dialog-content" @click.stop>
|
||||||
|
<view class="dialog-title">确认/修改分数</view>
|
||||||
|
<view class="dialog-body">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">选手:</text>
|
||||||
|
<text class="value">{{ currentResult.playerName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="label">主裁判分:</text>
|
||||||
|
<text class="value">{{ currentResult.chiefJudgeScore || currentResult.finalScore }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">确认分数:</text>
|
||||||
|
<input
|
||||||
|
type="digit"
|
||||||
|
v-model="confirmScore"
|
||||||
|
placeholder="留空则确认原分数"
|
||||||
|
class="score-input"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">备注:</text>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="confirmNote"
|
||||||
|
placeholder="可选"
|
||||||
|
class="note-input"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="dialog-footer">
|
||||||
|
<button class="btn-cancel" @click="closeDialog">取消</button>
|
||||||
|
<button class="btn-confirm" @click="confirmResult">确认</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import config from "@/config/env.config.js"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
judgeName: "",
|
||||||
|
matchName: "",
|
||||||
|
matchId: null,
|
||||||
|
venues: [],
|
||||||
|
selectedVenueId: null,
|
||||||
|
pendingResults: [],
|
||||||
|
confirmedResults: [],
|
||||||
|
loading: false,
|
||||||
|
showDialog: false,
|
||||||
|
currentResult: {},
|
||||||
|
confirmScore: "",
|
||||||
|
confirmNote: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const app = getApp()
|
||||||
|
this.judgeName = app.globalData.judgeName || ""
|
||||||
|
this.matchName = app.globalData.matchName || ""
|
||||||
|
this.matchId = app.globalData.matchId
|
||||||
|
this.loadVenues()
|
||||||
|
this.loadAllResults()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadVenues() {
|
||||||
|
try {
|
||||||
|
const res = await uni.request({
|
||||||
|
url: config.apiBaseURL + "/mini/general/venues",
|
||||||
|
method: "GET",
|
||||||
|
data: { competitionId: this.matchId },
|
||||||
|
header: {
|
||||||
|
"Authorization": uni.getStorageSync("token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res[1].data.success) {
|
||||||
|
this.venues = res[1].data.data || []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("加载场地失败:", e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadAllResults() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
// 加载待确认成绩
|
||||||
|
const pendingRes = await uni.request({
|
||||||
|
url: config.apiBaseURL + "/mini/general/pending",
|
||||||
|
method: "GET",
|
||||||
|
data: { competitionId: this.matchId },
|
||||||
|
header: {
|
||||||
|
"Authorization": uni.getStorageSync("token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (pendingRes[1].data.success) {
|
||||||
|
let results = pendingRes[1].data.data || []
|
||||||
|
if (this.selectedVenueId) {
|
||||||
|
results = results.filter(r => r.venueId === this.selectedVenueId)
|
||||||
|
}
|
||||||
|
this.pendingResults = results
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载已确认成绩
|
||||||
|
const confirmedRes = await uni.request({
|
||||||
|
url: config.apiBaseURL + "/mini/general/confirmed",
|
||||||
|
method: "GET",
|
||||||
|
data: { competitionId: this.matchId },
|
||||||
|
header: {
|
||||||
|
"Authorization": uni.getStorageSync("token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (confirmedRes[1].data.success) {
|
||||||
|
let results = confirmedRes[1].data.data || []
|
||||||
|
if (this.selectedVenueId) {
|
||||||
|
results = results.filter(r => r.venueId === this.selectedVenueId)
|
||||||
|
}
|
||||||
|
this.confirmedResults = results
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("加载成绩失败:", e)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectVenue(venueId) {
|
||||||
|
this.selectedVenueId = venueId
|
||||||
|
this.loadAllResults()
|
||||||
|
},
|
||||||
|
showConfirmDialog(result) {
|
||||||
|
this.currentResult = result
|
||||||
|
this.confirmScore = ""
|
||||||
|
this.confirmNote = ""
|
||||||
|
this.showDialog = true
|
||||||
|
},
|
||||||
|
closeDialog() {
|
||||||
|
this.showDialog = false
|
||||||
|
this.currentResult = {}
|
||||||
|
},
|
||||||
|
async confirmResult() {
|
||||||
|
const app = getApp()
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: "提交中...", mask: true })
|
||||||
|
const res = await uni.request({
|
||||||
|
url: config.apiBaseURL + "/mini/general/confirm",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
resultId: String(this.currentResult.id),
|
||||||
|
generalJudgeId: String(app.globalData.judgeId),
|
||||||
|
score: this.confirmScore ? parseFloat(this.confirmScore) : null,
|
||||||
|
note: this.confirmNote || null
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": uni.getStorageSync("token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
uni.hideLoading()
|
||||||
|
if (res[1].data.success) {
|
||||||
|
uni.showToast({ title: "确认成功", icon: "success" })
|
||||||
|
this.closeDialog()
|
||||||
|
this.loadAllResults()
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res[1].data.msg || "确认失败", icon: "none" })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: "网络错误", icon: "none" })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleLogout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: "确定要退出登录吗?",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.removeStorageSync("token")
|
||||||
|
uni.reLaunch({ url: "/pages/login/login" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
padding-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
height: 90rpx;
|
||||||
|
background: linear-gradient(135deg, #8B4513 0%, #A0522D 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
background: linear-gradient(135deg, #8B4513 0%, #A0522D 100%);
|
||||||
|
padding: 30rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-role {
|
||||||
|
font-size: 28rpx;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.venue-section, .result-section {
|
||||||
|
margin: 20rpx;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.venue-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.venue-item {
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
background: #F5F5F5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.venue-item.active {
|
||||||
|
background: #8B4513;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading, .empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40rpx;
|
||||||
|
color: #999;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: #FAFAFA;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item.pending {
|
||||||
|
border-left: 6rpx solid #FF9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item.confirmed {
|
||||||
|
border-left: 6rpx solid #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-info {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chief-score .label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chief-score .value {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FF9800;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chief-score .value.confirmed {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.pending {
|
||||||
|
color: #FF9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag.confirmed {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
width: 80%;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1rpx solid #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row, .input-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row .label, .input-row .label {
|
||||||
|
width: 160rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row .value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input, .note-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 70rpx;
|
||||||
|
border: 1rpx solid #DDD;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx solid #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel, .btn-confirm {
|
||||||
|
flex: 1;
|
||||||
|
height: 90rpx;
|
||||||
|
line-height: 90rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: #F5F5F5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
background: #8B4513;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -129,7 +129,7 @@ export default {
|
|||||||
matchTime,
|
matchTime,
|
||||||
judgeId,
|
judgeId,
|
||||||
judgeName,
|
judgeName,
|
||||||
venueId, // 普通评委有场地,裁判长为null
|
venueId, // 普通评委有场地,主裁判为null
|
||||||
venueName,
|
venueName,
|
||||||
projects, // 分配的项目列表
|
projects, // 分配的项目列表
|
||||||
currentProjectIndex: 0 // 当前选中的项目索引
|
currentProjectIndex: 0 // 当前选中的项目索引
|
||||||
@@ -154,13 +154,18 @@ export default {
|
|||||||
|
|
||||||
// 根据角色跳转到不同页面
|
// 根据角色跳转到不同页面
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (userRole === 'admin') {
|
if (userRole === 'general') {
|
||||||
// 裁判长跳转到多场地列表页(可以修改评分)
|
// 总裁跳转到总裁专用页面
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/general-judge/general-judge'
|
||||||
|
})
|
||||||
|
} else if (userRole === 'admin') {
|
||||||
|
// 主裁判跳转到多场地列表页(可以修改评分)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/score-list-multi/score-list-multi'
|
url: '/pages/score-list-multi/score-list-multi'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 普通裁判跳转到评分列表页(可以评分)
|
// 裁判员跳转到评分列表页(可以评分)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/score-list/score-list'
|
url: '/pages/score-list/score-list'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是裁判长
|
// 检查是否是主裁判
|
||||||
if (globalData.userRole !== 'admin') {
|
if (globalData.userRole !== 'admin') {
|
||||||
console.warn('非裁判长用户,无权修改评分')
|
console.warn('非主裁判用户,无权修改评分')
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '无权限',
|
title: '无权限',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
@@ -166,7 +166,7 @@ export default {
|
|||||||
// 获取当前选手信息(从 score-list-multi 页面传递)
|
// 获取当前选手信息(从 score-list-multi 页面传递)
|
||||||
const currentAthlete = globalData.currentAthlete || {}
|
const currentAthlete = globalData.currentAthlete || {}
|
||||||
|
|
||||||
// 获取裁判长ID
|
// 获取主裁判ID
|
||||||
this.modifierId = globalData.judgeId
|
this.modifierId = globalData.judgeId
|
||||||
|
|
||||||
// 🔥 关键修复:先用传递过来的选手数据初始化页面
|
// 🔥 关键修复:先用传递过来的选手数据初始化页面
|
||||||
|
|||||||
@@ -24,35 +24,36 @@
|
|||||||
|
|
||||||
<!-- 评分提示 -->
|
<!-- 评分提示 -->
|
||||||
<view class="score-tip">
|
<view class="score-tip">
|
||||||
点击分数填写或拖动滑块打分(5-10分)
|
直接输入分数或使用加减按钮调整(5-10分)
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分数调整 -->
|
<!-- 分数调整 -->
|
||||||
<view class="score-control">
|
<view class="score-control">
|
||||||
<view class="control-btn decrease" @click="decreaseScore">
|
<view class="control-btn decrease" @click="decreaseScore">
|
||||||
<text class="btn-symbol">-</text>
|
<text class="btn-symbol">-</text>
|
||||||
<!-- <text class="btn-value">-0.001</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="score-display">
|
<view class="score-display">
|
||||||
<text class="current-score">{{ currentScore.toFixed(3) }}</text>
|
<input
|
||||||
|
type="digit"
|
||||||
|
class="score-input-inline"
|
||||||
|
:value="scoreInputValue"
|
||||||
|
@input="onScoreInput"
|
||||||
|
@blur="onScoreBlur"
|
||||||
|
@confirm="onScoreConfirm"
|
||||||
|
placeholder="8.000"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="control-btn increase" @click="increaseScore">
|
<view class="control-btn increase" @click="increaseScore">
|
||||||
<text class="btn-symbol">+</text>
|
<text class="btn-symbol">+</text>
|
||||||
<!-- <text class="btn-value">+0.001</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view class="judge-tip">
|
|
||||||
裁判评分:保留3位小数点,超过上限或下限时,按钮置灰
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 扣分项 -->
|
<!-- 扣分项 -->
|
||||||
<view class="deduction-section">
|
<view class="deduction-section">
|
||||||
<view class="deduction-header">
|
<view class="deduction-header">
|
||||||
<text class="deduction-label">扣分项:</text>
|
<text class="deduction-label">扣分项:</text>
|
||||||
<!-- <text class="deduction-hint">扣分项多选</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="deduction-list">
|
<view class="deduction-list">
|
||||||
@@ -82,12 +83,35 @@
|
|||||||
v-model="note"
|
v-model="note"
|
||||||
maxlength="200"
|
maxlength="200"
|
||||||
/>
|
/>
|
||||||
<!-- <text class="optional-text">可不填</text> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
<!-- 提交按钮 -->
|
||||||
<button class="submit-btn" @click="handleSubmit">提交</button>
|
<button class="submit-btn" @click="handleSubmit">提交</button>
|
||||||
|
|
||||||
|
<!-- 分数输入弹窗 -->
|
||||||
|
<view v-if="showInputModal" class="modal-overlay" @click="hideScoreInput">
|
||||||
|
<view class="modal-content" @click.stop>
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">输入分数</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-body">
|
||||||
|
<input
|
||||||
|
type="digit"
|
||||||
|
class="score-input"
|
||||||
|
v-model="inputScore"
|
||||||
|
placeholder="请输入5-10之间的分数"
|
||||||
|
:focus="showInputModal"
|
||||||
|
@confirm="confirmScoreInput"
|
||||||
|
/>
|
||||||
|
<text class="input-hint">分数范围:{{ minScore }} - {{ maxScore }},保留3位小数</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="modal-btn cancel" @click="hideScoreInput">取消</button>
|
||||||
|
<button class="modal-btn confirm" @click="confirmScoreInput">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -107,50 +131,28 @@ export default {
|
|||||||
},
|
},
|
||||||
judgeId: '',
|
judgeId: '',
|
||||||
projectId: '',
|
projectId: '',
|
||||||
|
competitionId: '',
|
||||||
|
venueId: '',
|
||||||
currentScore: 8.000,
|
currentScore: 8.000,
|
||||||
note: '',
|
note: '',
|
||||||
minScore: 5.0,
|
minScore: 5.0,
|
||||||
maxScore: 10.0,
|
maxScore: 10.0,
|
||||||
deductions: []
|
deductions: [],
|
||||||
|
showInputModal: false,
|
||||||
|
inputScore: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
scoreInputValue() {
|
||||||
|
return this.currentScore.toFixed(3)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
// 获取全局数据
|
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
const globalData = app.globalData || {}
|
const globalData = app.globalData || {}
|
||||||
|
|
||||||
// 检查登录状态
|
|
||||||
if (!globalData.judgeId || !globalData.token) {
|
|
||||||
console.warn('用户未登录,跳转到登录页')
|
|
||||||
uni.showToast({
|
|
||||||
title: '请先登录',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 1500
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/login/login'
|
|
||||||
})
|
|
||||||
}, 1500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有选手信息
|
|
||||||
if (!globalData.currentAthlete || !globalData.currentAthlete.athleteId) {
|
|
||||||
console.warn('没有选手信息,返回列表页')
|
|
||||||
uni.showToast({
|
|
||||||
title: '请选择选手',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 1500
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateBack()
|
|
||||||
}, 1500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载当前选手信息(从 score-list 页面传递)
|
|
||||||
const currentAthlete = globalData.currentAthlete || {}
|
const currentAthlete = globalData.currentAthlete || {}
|
||||||
this.player = {
|
this.player = {
|
||||||
athleteId: currentAthlete.athleteId || '',
|
athleteId: currentAthlete.athleteId || '',
|
||||||
@@ -160,57 +162,80 @@ export default {
|
|||||||
number: currentAthlete.number || ''
|
number: currentAthlete.number || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选手已评分,加载其原有评分
|
|
||||||
if (currentAthlete.scored && currentAthlete.myScore) {
|
if (currentAthlete.scored && currentAthlete.myScore) {
|
||||||
this.currentScore = currentAthlete.myScore
|
this.currentScore = currentAthlete.myScore
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载评委ID和项目ID
|
|
||||||
this.judgeId = globalData.judgeId
|
this.judgeId = globalData.judgeId
|
||||||
const projects = globalData.projects || []
|
this.projectId = globalData.currentProjectId || ''
|
||||||
const currentIndex = globalData.currentProjectIndex || 0
|
this.competitionId = globalData.matchId || globalData.matchCode || ''
|
||||||
const currentProject = projects[currentIndex] || {}
|
this.venueId = globalData.currentVenueId || globalData.venueId || ''
|
||||||
this.projectId = currentProject.projectId
|
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('评分详情页加载:', {
|
console.log('评分详情页加载:', {
|
||||||
athlete: this.player,
|
athlete: this.player,
|
||||||
judgeId: this.judgeId,
|
judgeId: this.judgeId,
|
||||||
projectId: this.projectId,
|
projectId: this.projectId,
|
||||||
|
competitionId: this.competitionId,
|
||||||
|
venueId: this.venueId,
|
||||||
initialScore: this.currentScore
|
initialScore: this.currentScore
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载扣分项列表
|
|
||||||
await this.loadDeductions()
|
await this.loadDeductions()
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
onScoreInput(e) {
|
||||||
|
// Allow typing, validation happens on blur
|
||||||
|
},
|
||||||
|
|
||||||
|
onScoreBlur(e) {
|
||||||
|
this.validateAndSetScore(e.detail.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
onScoreConfirm(e) {
|
||||||
|
this.validateAndSetScore(e.detail.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
validateAndSetScore(value) {
|
||||||
|
const score = parseFloat(value)
|
||||||
|
|
||||||
|
if (isNaN(score)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入有效的数字',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score < this.minScore || score > this.maxScore) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '分数必须在' + this.minScore + '-' + this.maxScore + '之间',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// Reset to valid range
|
||||||
|
this.currentScore = Math.max(this.minScore, Math.min(this.maxScore, score))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScore = parseFloat(score.toFixed(3))
|
||||||
|
},
|
||||||
|
|
||||||
async loadDeductions() {
|
async loadDeductions() {
|
||||||
try {
|
try {
|
||||||
// 🔥 关键改动:使用 dataAdapter 获取扣分项列表
|
|
||||||
// Mock模式:调用 mock/score.js 的 getDeductions 函数
|
|
||||||
// API模式:调用 api/score.js 的 getDeductions 函数(GET /blade-martial/deductionItem/list)
|
|
||||||
const response = await dataAdapter.getData('getDeductions', {
|
const response = await dataAdapter.getData('getDeductions', {
|
||||||
projectId: this.projectId
|
projectId: this.projectId
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取返回数据(兼容分页和非分页格式)
|
const records = response.data && response.data.records ? response.data.records : []
|
||||||
const responseData = response.data || {}
|
this.deductions = records.map(item => ({
|
||||||
const records = responseData.records || response.data || []
|
deductionId: item.id,
|
||||||
|
deductionName: item.itemName,
|
||||||
// 为每个扣分项添加 checked 状态,并映射字段名
|
deductionScore: parseFloat(item.deductionPoint || 0),
|
||||||
// 后端字段: id, itemName
|
|
||||||
// 前端字段: deductionId, deductionName
|
|
||||||
this.deductions = (Array.isArray(records) ? records : []).map(item => ({
|
|
||||||
deductionId: item.deductionId || item.id,
|
|
||||||
deductionName: item.deductionName || item.itemName,
|
|
||||||
deductionPoint: item.deductionPoint || 0,
|
|
||||||
checked: false
|
checked: false
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('扣分项加载成功:', this.deductions)
|
console.log('扣分项加载成功:', this.deductions)
|
||||||
}
|
}
|
||||||
@@ -225,7 +250,19 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
uni.navigateBack()
|
if (config.debug) {
|
||||||
|
console.log('返回上一页')
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1,
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('返回失败:', err)
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/score-list/score-list'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
decreaseScore() {
|
decreaseScore() {
|
||||||
@@ -240,12 +277,44 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showScoreInput() {
|
||||||
|
this.inputScore = this.currentScore.toFixed(3)
|
||||||
|
this.showInputModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
hideScoreInput() {
|
||||||
|
this.showInputModal = false
|
||||||
|
this.inputScore = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmScoreInput() {
|
||||||
|
const score = parseFloat(this.inputScore)
|
||||||
|
|
||||||
|
if (isNaN(score)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入有效的数字',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score < this.minScore || score > this.maxScore) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `分数必须在${this.minScore}-${this.maxScore}之间`,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentScore = parseFloat(score.toFixed(3))
|
||||||
|
this.hideScoreInput()
|
||||||
|
},
|
||||||
|
|
||||||
toggleDeduction(index) {
|
toggleDeduction(index) {
|
||||||
this.deductions[index].checked = !this.deductions[index].checked
|
this.deductions[index].checked = !this.deductions[index].checked
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleSubmit() {
|
async handleSubmit() {
|
||||||
// 验证评分范围
|
|
||||||
if (this.currentScore < this.minScore || this.currentScore > this.maxScore) {
|
if (this.currentScore < this.minScore || this.currentScore > this.maxScore) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: `评分必须在${this.minScore}-${this.maxScore}分之间`,
|
title: `评分必须在${this.minScore}-${this.maxScore}分之间`,
|
||||||
@@ -254,10 +323,25 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集选中的扣分项ID(转为数字类型,后端期望 List<Long>)
|
if (!this.competitionId) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '缺少比赛ID,请重新登录',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.projectId) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '缺少项目ID,请返回重新选择',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const selectedDeductions = this.deductions
|
const selectedDeductions = this.deductions
|
||||||
.filter(item => item.checked)
|
.filter(item => item.checked)
|
||||||
.map(item => String(item.deductionId))
|
.map(item => item.deductionId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
@@ -265,26 +349,25 @@ export default {
|
|||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔥 关键改动:使用 dataAdapter 提交评分
|
const submitData = {
|
||||||
// Mock模式:调用 mock/score.js 的 submitScore 函数
|
athleteId: this.player.athleteId,
|
||||||
// API模式:调用 api/score.js 的 submitScore 函数(POST /mini/score/submit)
|
judgeId: this.judgeId,
|
||||||
const app = getApp()
|
projectId: this.projectId,
|
||||||
const globalData = app.globalData || {}
|
competitionId: this.competitionId,
|
||||||
const response = await dataAdapter.getData('submitScore', {
|
venueId: this.venueId,
|
||||||
athleteId: String(this.player.athleteId),
|
|
||||||
judgeId: String(this.judgeId),
|
|
||||||
score: this.currentScore,
|
score: this.currentScore,
|
||||||
projectId: String(this.projectId),
|
|
||||||
competitionId: globalData.matchId ? String(globalData.matchId) : null,
|
|
||||||
venueId: globalData.venueId ? String(globalData.venueId) : null,
|
|
||||||
scheduleId: globalData.scheduleId ? String(globalData.scheduleId) : null,
|
|
||||||
deductions: selectedDeductions,
|
deductions: selectedDeductions,
|
||||||
note: this.note || ''
|
note: this.note
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (config.debug) {
|
||||||
|
console.log('准备提交评分数据:', submitData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await dataAdapter.getData('submitScore', submitData)
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 调试信息
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('评分提交成功:', {
|
console.log('评分提交成功:', {
|
||||||
athleteId: this.player.athleteId,
|
athleteId: this.player.athleteId,
|
||||||
@@ -294,14 +377,12 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示成功提示
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '提交成功',
|
title: '提交成功',
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
duration: 1500
|
duration: 1500
|
||||||
})
|
})
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}, 1500)
|
}, 1500)
|
||||||
@@ -341,12 +422,19 @@ export default {
|
|||||||
|
|
||||||
.nav-left {
|
.nav-left {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 30rpx;
|
left: 0;
|
||||||
width: 60rpx;
|
top: 0;
|
||||||
height: 60rpx;
|
width: 120rpx;
|
||||||
|
height: 90rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-left:active {
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-icon {
|
.back-icon {
|
||||||
@@ -475,12 +563,26 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
min-width: 240rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-score {
|
.score-input-inline {
|
||||||
font-size: 80rpx;
|
width: 200rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
font-size: 64rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1B7C5E;
|
color: #1B7C5E;
|
||||||
|
text-align: center;
|
||||||
|
border: 2rpx solid #E0E0E0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input-inline:focus {
|
||||||
|
border-color: #1B7C5E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.judge-tip {
|
.judge-tip {
|
||||||
@@ -618,4 +720,95 @@ export default {
|
|||||||
.submit-btn:active {
|
.submit-btn:active {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分数输入弹窗 */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
width: 600rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 40rpx 30rpx 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20rpx 30rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 90rpx;
|
||||||
|
border: 2rpx solid #E0E0E0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #1B7C5E;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input:focus {
|
||||||
|
border-color: #1B7C5E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1rpx solid #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.cancel {
|
||||||
|
color: #666666;
|
||||||
|
border-right: 1rpx solid #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.confirm {
|
||||||
|
color: #1B7C5E;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:active {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -146,9 +146,9 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是裁判长
|
// 检查是否是主裁判
|
||||||
if (globalData.userRole !== 'admin') {
|
if (globalData.userRole !== 'admin') {
|
||||||
console.warn('非裁判长用户,跳转到普通评分页')
|
console.warn('非主裁判用户,跳转到普通评分页')
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: '/pages/score-list/score-list'
|
url: '/pages/score-list/score-list'
|
||||||
})
|
})
|
||||||
@@ -161,7 +161,7 @@ export default {
|
|||||||
time: globalData.matchTime || ''
|
time: globalData.matchTime || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 globalData 获取场地信息(与普通裁判相同)
|
// 从 globalData 获取场地信息(与裁判员相同)
|
||||||
this.venueInfo = {
|
this.venueInfo = {
|
||||||
id: globalData.venueId,
|
id: globalData.venueId,
|
||||||
name: globalData.venueName || '场地'
|
name: globalData.venueName || '场地'
|
||||||
@@ -175,7 +175,7 @@ export default {
|
|||||||
|
|
||||||
// 调试信息
|
// 调试信息
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('裁判长列表页加载:', {
|
console.log('主裁判列表页加载:', {
|
||||||
userRole: globalData.userRole,
|
userRole: globalData.userRole,
|
||||||
judgeId: this.judgeId,
|
judgeId: this.judgeId,
|
||||||
venueId: this.venueInfo.id,
|
venueId: this.venueInfo.id,
|
||||||
@@ -280,7 +280,7 @@ export default {
|
|||||||
console.log('请求选手列表参数:', params)
|
console.log('请求选手列表参数:', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 裁判长使用 getAthletesForAdmin 接口
|
// 主裁判使用 getAthletesForAdmin 接口
|
||||||
const response = await dataAdapter.getData('getAthletesForAdmin', params)
|
const response = await dataAdapter.getData('getAthletesForAdmin', params)
|
||||||
|
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
@@ -300,7 +300,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.players = [...this.players, ...records]
|
this.players = [...this.players, ...records]
|
||||||
}
|
}
|
||||||
// 裁判长视图:统计有总分的选手
|
// 主裁判视图:统计有总分的选手
|
||||||
this.scoredCount = this.players.filter(p => p.totalScore).length
|
this.scoredCount = this.players.filter(p => p.totalScore).length
|
||||||
this.hasMore = this.players.length < total
|
this.hasMore = this.players.length < total
|
||||||
|
|
||||||
@@ -330,6 +330,7 @@ export default {
|
|||||||
goToModify(player) {
|
goToModify(player) {
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
app.globalData.currentAthlete = player
|
app.globalData.currentAthlete = player
|
||||||
|
app.globalData.currentProjectId = this.projectInfo.id
|
||||||
uni.navigateTo({ url: '/pages/modify-score/modify-score' })
|
uni.navigateTo({ url: '/pages/modify-score/modify-score' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<view class="nav-bar">
|
<view class="nav-bar">
|
||||||
<view class="nav-title">评分系统</view>
|
<view class="nav-title">评分系统</view>
|
||||||
<view class="nav-right">
|
<view class="nav-right">
|
||||||
|
<view class="logout-btn" @click="handleLogout">退出</view>
|
||||||
<view class="nav-dots">···</view>
|
<view class="nav-dots">···</view>
|
||||||
<view class="nav-circle">◎</view>
|
<view class="nav-circle">◎</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -23,9 +24,9 @@
|
|||||||
<view class="refresh-link" @click="handleRefresh">刷新</view>
|
<view class="refresh-link" @click="handleRefresh">刷新</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 项目筛选 -->
|
<!-- 项目筛选 - 横向滑动 -->
|
||||||
<view class="project-row">
|
<scroll-view class="project-scroll" scroll-x="true" :show-scrollbar="false">
|
||||||
<view class="project-grid">
|
<view class="project-row">
|
||||||
<view
|
<view
|
||||||
class="project-chip"
|
class="project-chip"
|
||||||
:class="{ active: index === currentProjectIndex }"
|
:class="{ active: index === currentProjectIndex }"
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
{{ project.projectName }}
|
{{ project.projectName }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</scroll-view>
|
||||||
|
<view class="no-project-tip" v-if="projects.length === 0">当前场地暂无比赛项目</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 评分统计 -->
|
<!-- 评分统计 -->
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选手列表 -->
|
<!-- 选手列表 -->
|
||||||
<view class="player-list">
|
<view class="player-list" v-if="projects.length > 0">
|
||||||
<!-- 选手卡片 -->
|
<!-- 选手卡片 -->
|
||||||
<view
|
<view
|
||||||
class="player-card"
|
class="player-card"
|
||||||
@@ -193,7 +195,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async onShow() {
|
async onShow() {
|
||||||
// 从评分详情页返回时刷新数据
|
|
||||||
if (!this.isFirstLoad) {
|
if (!this.isFirstLoad) {
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
console.log('页面显示,刷新数据')
|
console.log('页面显示,刷新数据')
|
||||||
@@ -232,34 +233,23 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
formatScore(score) {
|
formatScore(score) {
|
||||||
// 处理 null、undefined、-1 等无效值
|
|
||||||
if (score === null || score === undefined || score === -1 || score === '-1') {
|
if (score === null || score === undefined || score === -1 || score === '-1') {
|
||||||
return '--'
|
return '--'
|
||||||
}
|
}
|
||||||
// 如果是字符串类型的数字,直接返回
|
|
||||||
if (typeof score === 'string' && !isNaN(parseFloat(score))) {
|
if (typeof score === 'string' && !isNaN(parseFloat(score))) {
|
||||||
return score
|
return score
|
||||||
}
|
}
|
||||||
// 如果是数字类型,保留3位小数
|
|
||||||
if (typeof score === 'number') {
|
if (typeof score === 'number') {
|
||||||
return score.toFixed(3)
|
return score.toFixed(3)
|
||||||
}
|
}
|
||||||
return score
|
return score
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算选手总分
|
|
||||||
* 规则:所有裁判评分完成后,去掉一个最高分和一个最低分,取剩余分数的平均值
|
|
||||||
* @param {Object} player - 选手对象
|
|
||||||
* @returns {Number|null} 计算后的总分,如果未完成评分返回 null
|
|
||||||
*/
|
|
||||||
calculateTotalScore(player) {
|
calculateTotalScore(player) {
|
||||||
// 检查是否有裁判评分数据
|
|
||||||
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否所有裁判都已评分
|
|
||||||
const totalJudges = player.totalJudges || 0
|
const totalJudges = player.totalJudges || 0
|
||||||
const scoredCount = player.judgeScores.length
|
const scoredCount = player.judgeScores.length
|
||||||
|
|
||||||
@@ -267,34 +257,22 @@ export default {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取所有分数
|
|
||||||
const scores = player.judgeScores.map(j => parseFloat(j.score)).filter(s => !isNaN(s))
|
const scores = player.judgeScores.map(j => parseFloat(j.score)).filter(s => !isNaN(s))
|
||||||
|
|
||||||
if (scores.length < 3) {
|
if (scores.length < 3) {
|
||||||
// 少于3个评分无法去掉最高最低,直接取平均
|
|
||||||
if (scores.length === 0) return null
|
if (scores.length === 0) return null
|
||||||
const sum = scores.reduce((a, b) => a + b, 0)
|
const sum = scores.reduce((a, b) => a + b, 0)
|
||||||
return sum / scores.length
|
return sum / scores.length
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排序
|
|
||||||
scores.sort((a, b) => a - b)
|
scores.sort((a, b) => a - b)
|
||||||
|
|
||||||
// 去掉最高分和最低分
|
|
||||||
const trimmedScores = scores.slice(1, -1)
|
const trimmedScores = scores.slice(1, -1)
|
||||||
|
|
||||||
// 计算平均分
|
|
||||||
const sum = trimmedScores.reduce((a, b) => a + b, 0)
|
const sum = trimmedScores.reduce((a, b) => a + b, 0)
|
||||||
const average = sum / trimmedScores.length
|
const average = sum / trimmedScores.length
|
||||||
|
|
||||||
return average
|
return average
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查选手是否所有裁判都已评分
|
|
||||||
* @param {Object} player - 选手对象
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
isAllJudgesScored(player) {
|
isAllJudgesScored(player) {
|
||||||
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
||||||
return false
|
return false
|
||||||
@@ -303,11 +281,6 @@ export default {
|
|||||||
return totalJudges > 0 && player.judgeScores.length >= totalJudges
|
return totalJudges > 0 && player.judgeScores.length >= totalJudges
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取选手的显示总分
|
|
||||||
* @param {Object} player - 选手对象
|
|
||||||
* @returns {String} 格式化后的总分或 '--'
|
|
||||||
*/
|
|
||||||
getDisplayTotalScore(player) {
|
getDisplayTotalScore(player) {
|
||||||
const score = this.calculateTotalScore(player)
|
const score = this.calculateTotalScore(player)
|
||||||
if (score === null) {
|
if (score === null) {
|
||||||
@@ -316,17 +289,26 @@ export default {
|
|||||||
return score.toFixed(3)
|
return score.toFixed(3)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取裁判评分进度
|
|
||||||
* @param {Object} player - 选手对象
|
|
||||||
* @returns {String} 进度字符串,如 "3/6"
|
|
||||||
*/
|
|
||||||
getJudgeProgress(player) {
|
getJudgeProgress(player) {
|
||||||
const scored = player.judgeScores ? player.judgeScores.length : 0
|
const scored = player.judgeScores ? player.judgeScores.length : 0
|
||||||
const total = player.totalJudges || '?'
|
const total = player.totalJudges || '?'
|
||||||
return scored + '/' + total
|
return scored + '/' + total
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleLogout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: "确定要退出登录吗?",
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.removeStorageSync("judgeInfo")
|
||||||
|
uni.removeStorageSync("token")
|
||||||
|
uni.reLaunch({ url: "/pages/login/login" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
async handleRefresh() {
|
async handleRefresh() {
|
||||||
if (this.isLoading) return
|
if (this.isLoading) return
|
||||||
uni.showToast({ title: '刷新中...', icon: 'loading', duration: 1000 })
|
uni.showToast({ title: '刷新中...', icon: 'loading', duration: 1000 })
|
||||||
@@ -349,6 +331,7 @@ export default {
|
|||||||
const globalData = app.globalData || {}
|
const globalData = app.globalData || {}
|
||||||
const params = {
|
const params = {
|
||||||
matchCode: globalData.matchCode,
|
matchCode: globalData.matchCode,
|
||||||
|
competitionId: globalData.matchId,
|
||||||
judgeId: this.judgeId,
|
judgeId: this.judgeId,
|
||||||
venueId: this.venueInfo.id,
|
venueId: this.venueInfo.id,
|
||||||
projectId: this.projectInfo.id,
|
projectId: this.projectInfo.id,
|
||||||
@@ -413,6 +396,7 @@ export default {
|
|||||||
goToScoreDetail(player) {
|
goToScoreDetail(player) {
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
app.globalData.currentAthlete = player
|
app.globalData.currentAthlete = player
|
||||||
|
app.globalData.currentProjectId = this.projectInfo.id
|
||||||
uni.navigateTo({ url: '/pages/score-detail/score-detail' })
|
uni.navigateTo({ url: '/pages/score-detail/score-detail' })
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -468,6 +452,15 @@ export default {
|
|||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-dots, .nav-circle {
|
.nav-dots, .nav-circle {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
@@ -521,28 +514,30 @@ export default {
|
|||||||
color: #4A90D9;
|
color: #4A90D9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-row {
|
/* ==================== 项目筛选 - 横向滑动 ==================== */
|
||||||
display: flex;
|
.project-scroll {
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-grid {
|
.project-row {
|
||||||
display: grid;
|
display: inline-flex;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
flex-direction: row;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
|
padding: 4rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-chip {
|
.project-chip {
|
||||||
padding: 20rpx 12rpx;
|
display: inline-block;
|
||||||
|
padding: 20rpx 32rpx;
|
||||||
border: 2rpx solid #1B7C5E;
|
border: 2rpx solid #1B7C5E;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #1B7C5E;
|
color: #1B7C5E;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-chip.active {
|
.project-chip.active {
|
||||||
@@ -692,4 +687,12 @@ export default {
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-project-tip {
|
||||||
|
padding: 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 28rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export default new DataAdapter()
|
|||||||
* |---------------------|----------------------|---------------------|---------------|
|
* |---------------------|----------------------|---------------------|---------------|
|
||||||
* | login | mockData.login | apiService.login | 登录验证 |
|
* | login | mockData.login | apiService.login | 登录验证 |
|
||||||
* | getMyAthletes | mockData.getMyAthletes | apiService.getMyAthletes | 选手列表(评委) |
|
* | getMyAthletes | mockData.getMyAthletes | apiService.getMyAthletes | 选手列表(评委) |
|
||||||
* | getAthletesForAdmin | mockData.getAthletesForAdmin | apiService.getAthletesForAdmin | 选手列表(裁判长) |
|
* | getAthletesForAdmin | mockData.getAthletesForAdmin | apiService.getAthletesForAdmin | 选手列表(主裁判) |
|
||||||
* | submitScore | mockData.submitScore | apiService.submitScore | 提交评分 |
|
* | submitScore | mockData.submitScore | apiService.submitScore | 提交评分 |
|
||||||
* | getScoreDetail | mockData.getScoreDetail | apiService.getScoreDetail | 评分详情 |
|
* | getScoreDetail | mockData.getScoreDetail | apiService.getScoreDetail | 评分详情 |
|
||||||
* | modifyScore | mockData.modifyScore | apiService.modifyScore | 修改评分 |
|
* | modifyScore | mockData.modifyScore | apiService.modifyScore | 修改评分 |
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export default new DataAdapter()
|
|||||||
* |---------------------|----------------------|---------------------|---------------|
|
* |---------------------|----------------------|---------------------|---------------|
|
||||||
* | login | mockData.login | apiService.login | 登录验证 |
|
* | login | mockData.login | apiService.login | 登录验证 |
|
||||||
* | getMyAthletes | mockData.getMyAthletes | apiService.getMyAthletes | 选手列表(评委) |
|
* | getMyAthletes | mockData.getMyAthletes | apiService.getMyAthletes | 选手列表(评委) |
|
||||||
* | getAthletesForAdmin | mockData.getAthletesForAdmin | apiService.getAthletesForAdmin | 选手列表(裁判长) |
|
* | getAthletesForAdmin | mockData.getAthletesForAdmin | apiService.getAthletesForAdmin | 选手列表(主裁判) |
|
||||||
* | submitScore | mockData.submitScore | apiService.submitScore | 提交评分 |
|
* | submitScore | mockData.submitScore | apiService.submitScore | 提交评分 |
|
||||||
* | getScoreDetail | mockData.getScoreDetail | apiService.getScoreDetail | 评分详情 |
|
* | getScoreDetail | mockData.getScoreDetail | apiService.getScoreDetail | 评分详情 |
|
||||||
* | modifyScore | mockData.modifyScore | apiService.modifyScore | 修改评分 |
|
* | modifyScore | mockData.modifyScore | apiService.modifyScore | 修改评分 |
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
// 输出目录
|
|
||||||
outputDir: 'dist/build/h5',
|
outputDir: 'dist/build/h5',
|
||||||
|
|
||||||
// 静态资源目录
|
|
||||||
assetsDir: 'static',
|
assetsDir: 'static',
|
||||||
|
|
||||||
// 公共路径 - 重要!确保静态资源能正确加载
|
|
||||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||||
|
|
||||||
// 生产环境配置
|
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
|
|
||||||
// CSS 提取配置
|
|
||||||
css: {
|
css: {
|
||||||
extract: true,
|
extract: true,
|
||||||
sourceMap: false
|
sourceMap: false
|
||||||
},
|
},
|
||||||
|
|
||||||
// 开发服务器配置
|
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
open: true,
|
open: false,
|
||||||
|
disableHostCheck: true,
|
||||||
overlay: {
|
overlay: {
|
||||||
warnings: false,
|
warnings: false,
|
||||||
errors: true
|
errors: true
|
||||||
@@ -37,14 +27,10 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
chainWebpack: config => {
|
chainWebpack: config => {
|
||||||
// 禁用 gzip 大小报告
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
config.performance.hints(false)
|
config.performance.hints(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 CSS 文件正确处理
|
|
||||||
config.module
|
config.module
|
||||||
.rule('vue')
|
.rule('vue')
|
||||||
.use('vue-loader')
|
.use('vue-loader')
|
||||||
|
|||||||
Reference in New Issue
Block a user