Compare commits
14 Commits
6f3b8db273
...
devops
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a931976d | ||
|
|
96bc2d92a2 | ||
|
|
a9c5c4a904 | ||
|
|
5349b80cf8 | ||
|
|
56c1320e40 | ||
|
|
c5c31e8088 | ||
|
|
c978a5bf64 | ||
|
|
f9efd8baa8 | ||
|
|
53c865a076 | ||
|
|
569f8a14d1 | ||
|
|
bcf040bb15 | ||
|
|
7620d9bf96 | ||
|
|
736aa08fba | ||
|
|
e8a2a5cef6 |
31
App.vue
31
App.vue
@@ -2,17 +2,44 @@
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
// 禁用 iOS Safari 双击缩放
|
||||
this.disableDoubleTapZoom()
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
},
|
||||
methods: {
|
||||
disableDoubleTapZoom() {
|
||||
// #ifdef H5
|
||||
let lastTouchEnd = 0
|
||||
document.documentElement.addEventListener('touchstart', function(event) {
|
||||
if (event.touches.length > 1) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}, { passive: false })
|
||||
|
||||
document.documentElement.addEventListener('touchend', function(event) {
|
||||
const now = Date.now()
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault()
|
||||
}
|
||||
lastTouchEnd = now
|
||||
}, { passive: false })
|
||||
|
||||
// 禁用手势缩放
|
||||
document.documentElement.addEventListener('gesturestart', function(event) {
|
||||
event.preventDefault()
|
||||
}, { passive: false })
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||||
@import "common/common.css";
|
||||
/* 注意要写在第一行,同时给style标签加入lang=scss属性 */
|
||||
@import common/common.css;
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,10 @@ export function getMyAthletes(params) {
|
||||
return request({
|
||||
url: '/mini/score/athletes',
|
||||
method: 'GET',
|
||||
params: params, // GET 请求使用 params
|
||||
params: {
|
||||
...params,
|
||||
size: 200 // 确保获取所有选手
|
||||
},
|
||||
showLoading: true
|
||||
})
|
||||
}
|
||||
@@ -36,14 +39,24 @@ export function getMyAthletes(params) {
|
||||
* @param {String} params.projectId - 项目ID
|
||||
* @returns {Promise}
|
||||
*
|
||||
* 注意:此接口需要后端实现
|
||||
* 建议路径: GET /api/mini/athletes/admin
|
||||
* 实际调用 /mini/score/athletes 接口,传递 refereeType=1
|
||||
*/
|
||||
export function getAthletesForAdmin(params) {
|
||||
// 从 globalData 获取 judgeId
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
const judgeId = globalData.judgeId
|
||||
|
||||
return request({
|
||||
url: '/mini/athletes/admin',
|
||||
url: '/mini/score/athletes',
|
||||
method: 'GET',
|
||||
params: params, // GET 请求使用 params
|
||||
params: {
|
||||
judgeId: judgeId,
|
||||
refereeType: 1, // 裁判长
|
||||
venueId: params.venueId,
|
||||
projectId: params.projectId,
|
||||
size: 200 // 确保获取所有选手
|
||||
},
|
||||
showLoading: true
|
||||
})
|
||||
}
|
||||
@@ -90,55 +103,3 @@ export default {
|
||||
getVenues,
|
||||
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
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -28,3 +28,14 @@ button::after {
|
||||
input {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 防止 iOS Safari 双击缩放 */
|
||||
button, .control-btn, [class*="btn"] {
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 全局禁用双击缩放 */
|
||||
html {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ const ENV_CONFIG = {
|
||||
dataMode: 'api',
|
||||
|
||||
// API基础路径(dataMode为'api'时使用)
|
||||
apiBaseURL: 'http://localhost:8123',
|
||||
// uni.request 不支持 devServer proxy,必须用完整地址
|
||||
apiBaseURL: 'http://142.91.105.230:8123',
|
||||
|
||||
// 调试模式
|
||||
debug: true,
|
||||
|
||||
// 请求超时时间(毫秒)
|
||||
timeout: 30000,
|
||||
|
||||
166
doc/Linux命令行编译样式问题修复记录.md
Normal file
166
doc/Linux命令行编译样式问题修复记录.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# Linux/Mac 命令行编译样式异常问题修复记录
|
||||
|
||||
## 问题描述
|
||||
|
||||
在 Windows 上使用 HBuilderX 运行项目时样式正常,但在 Linux/Mac 上使用 `npm run dev:h5` 命令行编译时,页面样式完全异常,组件的 scoped 样式没有生效。
|
||||
|
||||
## 问题现象
|
||||
|
||||
- 页面布局错乱
|
||||
- rpx 单位没有被转换为 px
|
||||
- 组件的 scoped 样式没有被正确打包
|
||||
|
||||
## 根本原因
|
||||
|
||||
### 1. postcss.config.js 配置覆盖问题
|
||||
|
||||
项目中自定义的 `postcss.config.js` 文件覆盖了 uni-app 的默认 postcss 配置:
|
||||
|
||||
```javascript
|
||||
// 原配置 - 问题配置
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
autoprefixer()
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
这个配置**没有包含 uni-app 的 postcss 插件**,导致:
|
||||
- rpx 单位没有被转换为 `%?数值?%` 占位符格式
|
||||
- uni-app 运行时无法在浏览器中将占位符转换为实际的 px 值
|
||||
|
||||
### 2. HBuilderX vs 命令行编译的区别
|
||||
|
||||
| 特性 | HBuilderX | 命令行 (npm run dev:h5) |
|
||||
|------|-----------|------------------------|
|
||||
| 编译器 | 内置优化版编译器 | 依赖 node_modules |
|
||||
| rpx 处理 | 自动转换 | 需要 postcss 插件 |
|
||||
| 样式处理 | 完善的内置处理 | 依赖配置文件 |
|
||||
| 版本兼容 | 内部统一管理 | 可能存在版本冲突 |
|
||||
|
||||
### 3. PostCSS 版本兼容问题
|
||||
|
||||
- 项目使用 `postcss-loader@3.0.0`(旧版)
|
||||
- 但 `postcss` 被升级到了 8.x 版本
|
||||
- postcss-loader 3.x 与 postcss 8 不兼容
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 修改 postcss.config.js
|
||||
|
||||
添加 uni-app 的 postcss 插件:
|
||||
|
||||
```javascript
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
// 引入 uni-app 的 postcss 插件来处理 rpx 转换
|
||||
const uniappPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
uniappPlugin,
|
||||
autoprefixer
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 降级 postcss 版本
|
||||
|
||||
```bash
|
||||
npm install postcss@7 --save --legacy-peer-deps
|
||||
```
|
||||
|
||||
### 3. 降级 sass 版本(可选,提高兼容性)
|
||||
|
||||
```bash
|
||||
npm install sass@1.32.13 --save
|
||||
```
|
||||
|
||||
## 修复后的依赖版本
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"postcss": "^7.0.39",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"sass": "^1.32.13"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 验证方法
|
||||
|
||||
1. 构建项目:
|
||||
```bash
|
||||
npm run build:h5
|
||||
```
|
||||
|
||||
2. 检查构建后的 JS 文件中 rpx 是否被转换:
|
||||
```bash
|
||||
# 应该看到 %?90?% 这样的占位符,而不是 90rpx
|
||||
grep -oE "height:%\?[0-9]+\?%" dist/build/h5/static/js/pages-login-login.*.js
|
||||
```
|
||||
|
||||
3. 启动开发服务器验证样式:
|
||||
```bash
|
||||
npm run dev:h5
|
||||
# 访问 http://localhost:8080 查看样式
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### uni-app 的 rpx 转换流程
|
||||
|
||||
1. **编译时**:postcss 插件将 `90rpx` 转换为 `%?90?%` 占位符
|
||||
2. **运行时**:uni-app 的 Vue 运行时根据屏幕宽度将占位符转换为实际 px 值
|
||||
3. **计算公式**:`px = rpx * (屏幕宽度 / 750)`
|
||||
|
||||
### 相关文件
|
||||
|
||||
- `/postcss.config.js` - PostCSS 配置
|
||||
- `/node_modules/@dcloudio/vue-cli-plugin-uni/packages/postcss/index.js` - uni-app postcss 插件
|
||||
- `/node_modules/@dcloudio/vue-cli-plugin-uni/packages/h5-vue/dist/vue.runtime.esm.js` - 运行时 rpx 转换
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **不要随意升级 postcss 版本**:postcss-loader 3.x 只兼容 postcss 7.x
|
||||
2. **保留 uni-app postcss 插件**:这是 rpx 转换的关键
|
||||
3. **Node.js 版本建议**:使用 Node 16.x 以获得最佳兼容性
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js: 16.x (推荐 16.20.2)
|
||||
- npm: 8.x
|
||||
- postcss: 7.x
|
||||
- postcss-loader: 3.x
|
||||
|
||||
## 相关命令
|
||||
|
||||
```bash
|
||||
# 安装 nvm(如果没有)
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||
|
||||
# 切换到 Node 16
|
||||
nvm install 16
|
||||
nvm use 16
|
||||
|
||||
# 重新安装依赖
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
|
||||
# 开发模式
|
||||
npm run dev:h5
|
||||
|
||||
# 生产构建
|
||||
npm run build:h5
|
||||
```
|
||||
|
||||
## 修复日期
|
||||
|
||||
2024-12-17
|
||||
|
||||
## 修复分支
|
||||
|
||||
`devops`
|
||||
225
index.html
225
index.html
@@ -3,15 +3,236 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<!-- 关键:使用最严格的 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="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>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
||||
<style>
|
||||
* {
|
||||
/* 允许垂直滚动,但禁用其他触摸动作 */
|
||||
touch-action: pan-y !important;
|
||||
-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>
|
||||
<script>
|
||||
// UniApp H5 专用:iOS Safari 双击缩放终极解决方案
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
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) {
|
||||
var now = Date.now();
|
||||
var touchDuration = now - touchStartTime;
|
||||
var timeSinceLastTouch = now - lastTouchEnd;
|
||||
|
||||
// 如果触摸时间很短(<150ms)且距离上次触摸很近(<350ms),很可能是双击
|
||||
if (touchDuration < 150 && timeSinceLastTouch < 350 && timeSinceLastTouch > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
console.log('阻止疑似双击', touchDuration, timeSinceLastTouch);
|
||||
return false;
|
||||
}
|
||||
|
||||
lastTouchEnd = now;
|
||||
}, { passive: false, capture: true });
|
||||
|
||||
// 方案3: 完全禁用 dblclick 事件
|
||||
document.addEventListener('dblclick', function(event) {
|
||||
event.preventDefault();
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>请开启JavaScript运行本应用</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1565
package-lock.json
generated
1565
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,9 @@
|
||||
"css-loader": "^3.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"postcss": "^7.0.39",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"sass": "^1.96.0",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^10.5.2",
|
||||
"thread-loader": "^2.1.3",
|
||||
"url-loader": "^4.1.1",
|
||||
@@ -43,6 +44,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"mini-types": "*",
|
||||
"postcss-comment": "^2.0.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -50,7 +50,13 @@
|
||||
</view>
|
||||
|
||||
<view class="score-control">
|
||||
<view class="control-btn decrease" @click="decreaseScore">
|
||||
<!-- 减分按钮 - 使用 catchtouchstart 阻止事件冒泡 -->
|
||||
<view
|
||||
class="control-btn decrease"
|
||||
@touchstart="onDecreaseStart"
|
||||
@touchend="onDecreaseEnd"
|
||||
@touchcancel="onTouchCancel"
|
||||
>
|
||||
<text class="btn-symbol">-</text>
|
||||
<text class="btn-value">-0.001</text>
|
||||
</view>
|
||||
@@ -60,15 +66,17 @@
|
||||
<text class="no-modify-text">可不改</text>
|
||||
</view>
|
||||
|
||||
<view class="control-btn increase" @click="increaseScore">
|
||||
<!-- 加分按钮 - 使用 catchtouchstart 阻止事件冒泡 -->
|
||||
<view
|
||||
class="control-btn increase"
|
||||
@touchstart="onIncreaseStart"
|
||||
@touchend="onIncreaseEnd"
|
||||
@touchcancel="onTouchCancel"
|
||||
>
|
||||
<text class="btn-symbol">+</text>
|
||||
<text class="btn-value">+0.001</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="modify-tip">
|
||||
裁判长修改:保留3位小数点,超过上限或下限时,按钮置灰
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
@@ -114,7 +122,15 @@ export default {
|
||||
originalScore: 8.000,
|
||||
note: '',
|
||||
minScore: 5.0,
|
||||
maxScore: 10.0
|
||||
maxScore: 10.0,
|
||||
// 防止双击的状态管理
|
||||
isTouching: false,
|
||||
touchTimer: null,
|
||||
lastTouchTime: 0,
|
||||
// 长按相关
|
||||
longPressTimer: null,
|
||||
longPressInterval: null,
|
||||
isLongPressing: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -124,7 +140,7 @@ export default {
|
||||
const globalData = app.globalData || {}
|
||||
|
||||
// 获取当前选手信息(从 score-list-multi 页面传递)
|
||||
const currentAthlete = globalData.currentAthlete || {}
|
||||
const currentAthlete = globalData.currentAthlete ||
|
||||
|
||||
// 获取裁判长ID
|
||||
this.modifierId = globalData.judgeId
|
||||
@@ -141,9 +157,151 @@ export default {
|
||||
if (currentAthlete.athleteId) {
|
||||
await this.loadScoreDetail(currentAthlete.athleteId)
|
||||
}
|
||||
|
||||
// H5 平台特殊处理:禁用双击缩放
|
||||
// #ifdef H5
|
||||
this.disableDoubleTapZoom()
|
||||
// #endif
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 清理定时器
|
||||
this.clearAllTimers()
|
||||
},
|
||||
|
||||
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) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
@@ -151,9 +309,6 @@ export default {
|
||||
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', {
|
||||
athleteId: athleteId
|
||||
})
|
||||
@@ -202,12 +357,26 @@ export default {
|
||||
decreaseScore() {
|
||||
if (this.currentScore > this.minScore) {
|
||||
this.currentScore = parseFloat((this.currentScore - 0.001).toFixed(3))
|
||||
|
||||
// 添加触觉反馈(仅在支持的平台)
|
||||
// #ifndef H5
|
||||
uni.vibrateShort({
|
||||
type: 'light'
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
|
||||
increaseScore() {
|
||||
if (this.currentScore < this.maxScore) {
|
||||
this.currentScore = parseFloat((this.currentScore + 0.001).toFixed(3))
|
||||
|
||||
// 添加触觉反馈(仅在支持的平台)
|
||||
// #ifndef H5
|
||||
uni.vibrateShort({
|
||||
type: 'light'
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
|
||||
@@ -236,14 +405,16 @@ export default {
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 修改评分
|
||||
// Mock模式:调用 mock/score.js 的 modifyScore 函数
|
||||
// API模式:调用 api/score.js 的 modifyScore 函数(PUT /api/mini/score/modify)
|
||||
// 获取场地ID
|
||||
const app = getApp()
|
||||
const venueId = app.globalData?.currentVenueId
|
||||
|
||||
const response = await dataAdapter.getData('modifyScore', {
|
||||
athleteId: this.athleteInfo.athleteId,
|
||||
modifierId: this.modifierId,
|
||||
modifiedScore: this.currentScore,
|
||||
note: this.note
|
||||
note: this.note,
|
||||
venueId: venueId // 添加场地ID
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
@@ -465,8 +636,16 @@ export default {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #F5F5F5;
|
||||
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 {
|
||||
@@ -480,6 +659,7 @@ export default {
|
||||
.btn-symbol {
|
||||
font-size: 48rpx;
|
||||
font-weight: 300;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.control-btn.decrease .btn-symbol {
|
||||
@@ -493,6 +673,7 @@ export default {
|
||||
.btn-value {
|
||||
font-size: 24rpx;
|
||||
margin-top: 8rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.control-btn.decrease .btn-value {
|
||||
@@ -521,13 +702,6 @@ export default {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.modify-tip {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 备注 */
|
||||
.note-section {
|
||||
margin: 30rpx;
|
||||
|
||||
@@ -68,13 +68,20 @@
|
||||
<view class="player-header">
|
||||
<view class="player-name">{{ player.name }}</view>
|
||||
|
||||
<!-- 已评分:显示总分和修改按钮 -->
|
||||
<view class="action-area" v-if="player.totalScore">
|
||||
<text class="total-score">总分:{{ player.totalScore }}</text>
|
||||
<view class="chief-actions">
|
||||
<!-- <text class="chief-hint">裁判长功能:修改评分、修改按钮需等总分出来才出现</text> -->
|
||||
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
||||
</view>
|
||||
<!-- 动作区域:始终显示 -->
|
||||
<view class="action-area">
|
||||
<!-- 已评分:显示总分和修改按钮 -->
|
||||
<template v-if="player.scoringComplete && player.totalScore > 0">
|
||||
<text class="total-score">总分:{{ player.totalScore }}</text>
|
||||
<view class="chief-actions">
|
||||
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 未评分:显示评分中提示 -->
|
||||
<template v-else>
|
||||
<text class="scoring-status">评分中...</text>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -217,11 +224,11 @@ export default {
|
||||
uni.hideLoading()
|
||||
|
||||
// 保存选手列表
|
||||
this.players = response.data || []
|
||||
this.players = (response.data.records || response.data) || []
|
||||
|
||||
// 计算评分统计(裁判长视图:统计有总分的选手)
|
||||
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) {
|
||||
@@ -389,6 +396,8 @@ export default {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.venue-tab.active {
|
||||
@@ -441,6 +450,8 @@ export default {
|
||||
color: #666666;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.project-btn.active {
|
||||
@@ -505,6 +516,15 @@ export default {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.scoring-status {
|
||||
font-size: 26rpx;
|
||||
color: #FF9800;
|
||||
font-weight: 500;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #FFF3E0;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.chief-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -527,6 +547,8 @@ export default {
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.modify-btn:active {
|
||||
|
||||
@@ -62,18 +62,35 @@
|
||||
|
||||
<!-- 裁判长:显示总分和已评分裁判数 -->
|
||||
<view class="player-scores" v-if="refereeType === 1">
|
||||
<text class="total-score">总分:{{ player.totalScore || '未评分' }}</text>
|
||||
<text class="judge-count">已评分:{{ player.scoredJudgeCount || 0 }}人</text>
|
||||
<text class="total-score">
|
||||
总分:{{ player.scoringComplete ? player.totalScore : '评分中' }}
|
||||
</text>
|
||||
<text class="judge-count">
|
||||
已评分:{{ player.scoredJudgeCount || 0 }}/{{ player.requiredJudgeCount || 0 }}人
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 普通裁判:显示评分按钮 -->
|
||||
<button
|
||||
class="score-btn"
|
||||
v-else
|
||||
@click.stop="goToScoreDetail(player)"
|
||||
>
|
||||
评分
|
||||
</button>
|
||||
<!-- 普通裁判:根据评分状态显示不同内容 -->
|
||||
<view class="judge-action" v-else>
|
||||
<!-- 已评分:显示分数和修改按钮 -->
|
||||
<view class="scored-info" v-if="player.scored">
|
||||
<text class="my-score-text">我的评分:{{ player.myScore }}</text>
|
||||
<button
|
||||
class="score-btn modify-btn"
|
||||
@click.stop="goToScoreDetail(player)"
|
||||
>
|
||||
修改
|
||||
</button>
|
||||
</view>
|
||||
<!-- 未评分:显示评分按钮 -->
|
||||
<button
|
||||
class="score-btn"
|
||||
v-else
|
||||
@click.stop="goToScoreDetail(player)"
|
||||
>
|
||||
评分
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="player-info">
|
||||
@@ -634,6 +651,29 @@ export default {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
// 引入 uni-app 的 postcss 插件来处理 rpx 转换
|
||||
// 使用 postcss.plugin 旧版 API (postcss-loader 3.x 兼容)
|
||||
const postcss = require('postcss')
|
||||
const uniappPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
autoprefixer()
|
||||
uniappPlugin,
|
||||
autoprefixer
|
||||
]
|
||||
}
|
||||
|
||||
7
postcss.config.js.bak
Normal file
7
postcss.config.js.bak
Normal file
@@ -0,0 +1,7 @@
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
autoprefixer()
|
||||
]
|
||||
}
|
||||
@@ -24,9 +24,12 @@ import request from '@/utils/request.js'
|
||||
*/
|
||||
export function getMyAthletes(params) {
|
||||
return request({
|
||||
url: '/api/mini/athletes',
|
||||
url: '/mini/score/athletes',
|
||||
method: 'GET',
|
||||
params: params, // GET 请求使用 params
|
||||
params: {
|
||||
...params,
|
||||
refereeType: 2 // 普通裁判
|
||||
},
|
||||
showLoading: true
|
||||
})
|
||||
}
|
||||
@@ -44,9 +47,12 @@ export function getMyAthletes(params) {
|
||||
*/
|
||||
export function getAthletesForAdmin(params) {
|
||||
return request({
|
||||
url: '/api/mini/athletes/admin',
|
||||
url: '/mini/score/athletes',
|
||||
method: 'GET',
|
||||
params: params, // GET 请求使用 params
|
||||
params: {
|
||||
...params,
|
||||
refereeType: 1 // 裁判长
|
||||
},
|
||||
showLoading: true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import request from '@/utils/request.js'
|
||||
*/
|
||||
export function getDeductions(params) {
|
||||
return request({
|
||||
url: '/martial/deductionItem/list',
|
||||
url: '/blade-martial/deductionItem/list',
|
||||
method: 'GET',
|
||||
params: {
|
||||
...params,
|
||||
@@ -35,7 +35,7 @@ export function getDeductions(params) {
|
||||
*/
|
||||
export function submitScore(data) {
|
||||
return request({
|
||||
url: '/martial/score/submit',
|
||||
url: '/mini/score/submit',
|
||||
method: 'POST',
|
||||
data,
|
||||
showLoading: true,
|
||||
@@ -54,7 +54,7 @@ export function submitScore(data) {
|
||||
*/
|
||||
export function getScoreDetail(params) {
|
||||
return request({
|
||||
url: `/api/mini/score/detail/${params.athleteId}`,
|
||||
url: `/mini/score/detail/${params.athleteId}`,
|
||||
method: 'GET',
|
||||
showLoading: true
|
||||
})
|
||||
@@ -74,7 +74,7 @@ export function getScoreDetail(params) {
|
||||
*/
|
||||
export function modifyScore(data) {
|
||||
return request({
|
||||
url: '/api/mini/score/modify',
|
||||
url: '/mini/score/modify',
|
||||
method: 'PUT',
|
||||
data,
|
||||
showLoading: true,
|
||||
|
||||
@@ -17,11 +17,14 @@ const ENV_CONFIG = {
|
||||
dataMode: 'api',
|
||||
|
||||
// API基础路径(dataMode为'api'时使用)
|
||||
apiBaseURL: 'http://localhost:8123',
|
||||
// uni.request 不支持 devServer proxy,必须用完整地址
|
||||
apiBaseURL: 'http://142.91.105.230:8123',
|
||||
|
||||
// 请求超时时间(毫秒)
|
||||
timeout: 30000,
|
||||
|
||||
// 调试模式
|
||||
debug: true
|
||||
},
|
||||
|
||||
// 测试环境配置
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
"path": "pages/score-list/score-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -120,6 +120,7 @@ export default {
|
||||
|
||||
// 保存用户信息到全局数据
|
||||
getApp().globalData = {
|
||||
token, // Token(用于登录状态检查)
|
||||
userRole, // 'pub' 或 'admin'
|
||||
matchCode: this.matchCode, // 比赛编码
|
||||
inviteCode: this.inviteCode, // 邀请码(重要:用于后续API调用)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<view class="player-name">{{ athleteInfo.name }}</view>
|
||||
<view class="total-score-label">
|
||||
<text class="label-text">总分:</text>
|
||||
<text class="score-value">{{ athleteInfo.totalScore }}</text>
|
||||
<text class="score-value">{{ formatScore(athleteInfo.totalScore) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="player-details">
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- 修改总分区域 -->
|
||||
<view class="modify-section">
|
||||
<view class="modify-header">
|
||||
<text class="modify-label">修改总分(+-0.005分)</text>
|
||||
<text class="modify-label">修改总分(±0.050分)</text>
|
||||
</view>
|
||||
|
||||
<view class="score-control">
|
||||
@@ -65,10 +65,6 @@
|
||||
<text class="btn-value">+0.001</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="modify-tip">
|
||||
裁判长修改:保留3位小数点,超过上限或下限时,按钮置灰
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
@@ -110,10 +106,10 @@ export default {
|
||||
judgeScores: [],
|
||||
modification: null,
|
||||
modifierId: '',
|
||||
currentScore: 8.000,
|
||||
originalScore: 8.000,
|
||||
currentScore: 0,
|
||||
originalScore: 0,
|
||||
note: '',
|
||||
minScore: 5.0,
|
||||
minScore: 0,
|
||||
maxScore: 10.0
|
||||
}
|
||||
},
|
||||
@@ -123,27 +119,101 @@ export default {
|
||||
const app = getApp()
|
||||
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.userRole !== 'admin') {
|
||||
console.warn('非裁判长用户,无权修改评分')
|
||||
uni.showToast({
|
||||
title: '无权限',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有选手信息
|
||||
if (!globalData.currentAthlete || !globalData.currentAthlete.athleteId) {
|
||||
console.warn('没有选手信息,返回列表页')
|
||||
uni.showToast({
|
||||
title: '请选择选手',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前选手信息(从 score-list-multi 页面传递)
|
||||
const currentAthlete = globalData.currentAthlete || {}
|
||||
|
||||
// 获取裁判长ID
|
||||
this.modifierId = globalData.judgeId
|
||||
|
||||
// 🔥 关键修复:先用传递过来的选手数据初始化页面
|
||||
this.athleteInfo = {
|
||||
athleteId: currentAthlete.athleteId,
|
||||
name: currentAthlete.name || '',
|
||||
idCard: currentAthlete.idCard || '',
|
||||
team: currentAthlete.team || '',
|
||||
number: currentAthlete.number || '',
|
||||
totalScore: currentAthlete.totalScore || 0
|
||||
}
|
||||
|
||||
// 设置初始分数(使用传递过来的总分)
|
||||
const totalScore = parseFloat(currentAthlete.totalScore) || 0
|
||||
this.originalScore = totalScore
|
||||
this.currentScore = totalScore
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('修改评分页加载:', {
|
||||
currentAthlete: currentAthlete,
|
||||
athleteId: currentAthlete.athleteId,
|
||||
totalScore: totalScore,
|
||||
modifierId: this.modifierId
|
||||
})
|
||||
}
|
||||
|
||||
// 加载选手评分详情
|
||||
// 尝试加载选手评分详情(获取各评委的评分)
|
||||
if (currentAthlete.athleteId) {
|
||||
await this.loadScoreDetail(currentAthlete.athleteId)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
formatScore(score) {
|
||||
if (score === null || score === undefined || score === -1 || score === '-1') {
|
||||
return '--'
|
||||
}
|
||||
if (typeof score === 'string' && !isNaN(parseFloat(score))) {
|
||||
return parseFloat(score).toFixed(3)
|
||||
}
|
||||
if (typeof score === 'number') {
|
||||
return score.toFixed(3)
|
||||
}
|
||||
return score
|
||||
},
|
||||
|
||||
async loadScoreDetail(athleteId) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
@@ -151,27 +221,35 @@ export default {
|
||||
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', {
|
||||
athleteId: athleteId
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 保存选手信息和评分详情
|
||||
this.athleteInfo = response.data.athleteInfo || {}
|
||||
this.judgeScores = response.data.judgeScores || []
|
||||
this.modification = response.data.modification || null
|
||||
// 如果接口返回了数据,更新页面
|
||||
if (response && response.data) {
|
||||
// 更新评委评分列表
|
||||
this.judgeScores = response.data.judgeScores || []
|
||||
this.modification = response.data.modification || null
|
||||
|
||||
// 设置初始分数
|
||||
this.originalScore = this.athleteInfo.totalScore || 8.000
|
||||
this.currentScore = this.originalScore
|
||||
// 如果接口返回了选手信息,更新(但保留传递过来的数据作为备用)
|
||||
if (response.data.athleteInfo) {
|
||||
const apiAthleteInfo = response.data.athleteInfo
|
||||
this.athleteInfo = {
|
||||
athleteId: apiAthleteInfo.athleteId || this.athleteInfo.athleteId,
|
||||
name: apiAthleteInfo.name || this.athleteInfo.name,
|
||||
idCard: apiAthleteInfo.idCard || this.athleteInfo.idCard,
|
||||
team: apiAthleteInfo.team || this.athleteInfo.team,
|
||||
number: apiAthleteInfo.number || this.athleteInfo.number,
|
||||
totalScore: apiAthleteInfo.totalScore || this.athleteInfo.totalScore
|
||||
}
|
||||
|
||||
// 如果之前已修改过,加载修改后的分数
|
||||
if (this.modification && this.modification.modifiedScore) {
|
||||
this.currentScore = this.modification.modifiedScore
|
||||
// 更新分数
|
||||
const totalScore = parseFloat(apiAthleteInfo.totalScore) || this.originalScore
|
||||
this.originalScore = totalScore
|
||||
this.currentScore = totalScore
|
||||
}
|
||||
}
|
||||
|
||||
// 调试信息
|
||||
@@ -188,10 +266,10 @@ export default {
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('加载评分详情失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
// 不显示错误提示,因为已经有传递过来的数据可以使用
|
||||
if (config.debug) {
|
||||
console.log('使用传递过来的选手数据')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -200,22 +278,28 @@ export default {
|
||||
},
|
||||
|
||||
decreaseScore() {
|
||||
if (this.currentScore > this.minScore) {
|
||||
// 限制最小值为原始分数-0.050
|
||||
const minAllowed = parseFloat((this.originalScore - 0.050).toFixed(3))
|
||||
if (this.currentScore > minAllowed) {
|
||||
this.currentScore = parseFloat((this.currentScore - 0.001).toFixed(3))
|
||||
}
|
||||
},
|
||||
|
||||
increaseScore() {
|
||||
if (this.currentScore < this.maxScore) {
|
||||
// 限制最大值为原始分数+0.050
|
||||
const maxAllowed = parseFloat((this.originalScore + 0.050).toFixed(3))
|
||||
if (this.currentScore < maxAllowed) {
|
||||
this.currentScore = parseFloat((this.currentScore + 0.001).toFixed(3))
|
||||
}
|
||||
},
|
||||
|
||||
async handleModify() {
|
||||
// 验证评分范围
|
||||
if (this.currentScore < this.minScore || this.currentScore > this.maxScore) {
|
||||
// 验证评分范围(±0.050)
|
||||
const minAllowed = parseFloat((this.originalScore - 0.050).toFixed(3))
|
||||
const maxAllowed = parseFloat((this.originalScore + 0.050).toFixed(3))
|
||||
if (this.currentScore < minAllowed || this.currentScore > maxAllowed) {
|
||||
uni.showToast({
|
||||
title: `评分必须在${this.minScore}-${this.maxScore}分之间`,
|
||||
title: '评分只能在原始分数±0.050范围内',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
@@ -236,12 +320,9 @@ export default {
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 修改评分
|
||||
// Mock模式:调用 mock/score.js 的 modifyScore 函数
|
||||
// API模式:调用 api/score.js 的 modifyScore 函数(PUT /api/mini/score/modify)
|
||||
const response = await dataAdapter.getData('modifyScore', {
|
||||
athleteId: this.athleteInfo.athleteId,
|
||||
modifierId: this.modifierId,
|
||||
athleteId: this.athleteInfo.athleteId,
|
||||
modifiedScore: this.currentScore,
|
||||
note: this.note
|
||||
})
|
||||
@@ -420,15 +501,21 @@ export default {
|
||||
}
|
||||
|
||||
.judge-score-item {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 20rpx;
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 8rpx;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
}
|
||||
|
||||
.judge-name {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.judge-score {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,36 @@ export default {
|
||||
const app = getApp()
|
||||
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 || {}
|
||||
this.player = {
|
||||
@@ -161,14 +191,22 @@ export default {
|
||||
try {
|
||||
// 🔥 关键改动:使用 dataAdapter 获取扣分项列表
|
||||
// Mock模式:调用 mock/score.js 的 getDeductions 函数
|
||||
// API模式:调用 api/score.js 的 getDeductions 函数(GET /martial/deductionItem/list)
|
||||
// API模式:调用 api/score.js 的 getDeductions 函数(GET /blade-martial/deductionItem/list)
|
||||
const response = await dataAdapter.getData('getDeductions', {
|
||||
projectId: this.projectId
|
||||
})
|
||||
|
||||
// 为每个扣分项添加 checked 状态
|
||||
this.deductions = (response.data || []).map(item => ({
|
||||
...item,
|
||||
// 获取返回数据(兼容分页和非分页格式)
|
||||
const responseData = response.data || {}
|
||||
const records = responseData.records || response.data || []
|
||||
|
||||
// 为每个扣分项添加 checked 状态,并映射字段名
|
||||
// 后端字段: 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
|
||||
}))
|
||||
|
||||
@@ -216,10 +254,10 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 收集选中的扣分项ID
|
||||
// 收集选中的扣分项ID(转为数字类型,后端期望 List<Long>)
|
||||
const selectedDeductions = this.deductions
|
||||
.filter(item => item.checked)
|
||||
.map(item => item.deductionId)
|
||||
.map(item => String(item.deductionId))
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
@@ -229,13 +267,19 @@ export default {
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 提交评分
|
||||
// Mock模式:调用 mock/score.js 的 submitScore 函数
|
||||
// API模式:调用 api/score.js 的 submitScore 函数(POST /martial/score/submit)
|
||||
// API模式:调用 api/score.js 的 submitScore 函数(POST /mini/score/submit)
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
const response = await dataAdapter.getData('submitScore', {
|
||||
athleteId: this.player.athleteId,
|
||||
judgeId: this.judgeId,
|
||||
athleteId: String(this.player.athleteId),
|
||||
judgeId: String(this.judgeId),
|
||||
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,
|
||||
note: this.note
|
||||
note: this.note || ''
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
@@ -4,57 +4,45 @@
|
||||
<view class="nav-bar">
|
||||
<view class="nav-title">评分系统</view>
|
||||
<view class="nav-right">
|
||||
<view class="icon-menu">···</view>
|
||||
<view class="icon-close">⊗</view>
|
||||
<view class="nav-dots">···</view>
|
||||
<view class="nav-circle">◎</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 比赛信息 -->
|
||||
<view class="match-info">
|
||||
<view class="match-title">{{ matchInfo.name }}</view>
|
||||
<view class="match-time">比赛时间:{{ matchInfo.time }}</view>
|
||||
<view class="match-time">比赛时间:{{ formatDateTime(matchInfo.time) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 场地和项目选择 -->
|
||||
<view class="venue-section">
|
||||
<!-- 场地切换 - 横向滚动 -->
|
||||
<scroll-view class="venue-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<view class="venue-tabs">
|
||||
<view
|
||||
v-for="venue in venues"
|
||||
:key="venue.venueId"
|
||||
:class="['venue-tab', currentVenue === venue.venueId ? 'active' : '']"
|
||||
@click="switchVenue(venue.venueId)"
|
||||
>
|
||||
{{ venue.venueName }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="venue-tip">
|
||||
<!-- <text class="tip-bold">裁判长可看见所有场地和项目</text> -->
|
||||
<!-- <text class="tip-normal">(场地和项目可动态全部),可以点击切换</text> -->
|
||||
<!-- 场地和项目卡片 -->
|
||||
<view class="venue-card">
|
||||
<!-- 场地标题行 -->
|
||||
<view class="venue-header">
|
||||
<view class="venue-name">{{ venueInfo.name }}</view>
|
||||
<view class="refresh-link" @click="handleRefresh">刷新</view>
|
||||
</view>
|
||||
|
||||
<!-- 项目选择 - 横向滚动 -->
|
||||
<scroll-view class="project-scroll" scroll-x="true" show-scrollbar="false">
|
||||
<view class="project-list">
|
||||
<!-- 项目筛选 -->
|
||||
<view class="project-row">
|
||||
<view class="project-grid">
|
||||
<view
|
||||
class="project-chip"
|
||||
:class="{ active: index === currentProjectIndex }"
|
||||
v-for="(project, index) in projects"
|
||||
:key="project.projectId"
|
||||
:class="['project-btn', currentProject === project.projectId ? 'active' : '']"
|
||||
@click="switchProject(project.projectId)"
|
||||
@click="switchProject(index)"
|
||||
>
|
||||
{{ project.projectName }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已评分统计 -->
|
||||
<view class="score-stats">
|
||||
<text class="stats-text">已评分:</text>
|
||||
<text class="stats-number">{{ scoredCount }}/{{ totalCount }}</text>
|
||||
<text class="stats-label">已评分:</text>
|
||||
<text class="stats-value">{{ scoredCount }}/{{ totalCount }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 选手列表 -->
|
||||
@@ -65,25 +53,42 @@
|
||||
v-for="player in players"
|
||||
:key="player.athleteId"
|
||||
>
|
||||
<view class="player-header">
|
||||
<view class="card-header">
|
||||
<view class="player-name">{{ player.name }}</view>
|
||||
|
||||
<!-- 已评分:显示总分和修改按钮 -->
|
||||
<view class="action-area" v-if="player.totalScore">
|
||||
<text class="total-score">总分:{{ player.totalScore }}</text>
|
||||
<view class="chief-actions">
|
||||
<!-- <text class="chief-hint">裁判长功能:修改评分、修改按钮需等总分出来才出现</text> -->
|
||||
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
||||
<view class="score-tag">
|
||||
<text class="tag-label">总分:</text>
|
||||
<text class="tag-value">{{ formatScore(player.totalScore) }}</text>
|
||||
</view>
|
||||
<button class="modify-btn" @click="goToModify(player)">修改</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="player-info">
|
||||
<view class="info-item">身份证:{{ player.idCard }}</view>
|
||||
<view class="info-item">队伍:{{ player.team }}</view>
|
||||
<view class="info-item">编号:{{ player.number }}</view>
|
||||
<view class="player-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">身份证:{{ player.idCard }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">队伍:{{ player.team }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">编号:{{ player.number }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-status" v-if="players.length > 0">
|
||||
<view v-if="isLoading" class="loading-text">加载中...</view>
|
||||
<view v-else-if="!hasMore" class="no-more-text">— 没有更多了 —</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!isLoading && players.length === 0">
|
||||
<text class="empty-text">暂无选手数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -96,190 +101,253 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
matchInfo: {
|
||||
id: '',
|
||||
name: '',
|
||||
time: ''
|
||||
},
|
||||
competitionId: '',
|
||||
currentVenue: '',
|
||||
currentProject: '',
|
||||
venues: [],
|
||||
venueInfo: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
projectInfo: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
judgeId: '',
|
||||
projects: [],
|
||||
currentProjectIndex: 0,
|
||||
players: [],
|
||||
scoredCount: 0,
|
||||
totalCount: 0
|
||||
totalCount: 0,
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
isFirstLoad: true
|
||||
}
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
// 获取全局数据
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
|
||||
// 加载比赛信息
|
||||
this.matchInfo = {
|
||||
id: globalData.matchId,
|
||||
name: globalData.matchName || '比赛名称',
|
||||
time: globalData.matchTime || '比赛时间'
|
||||
// 检查登录状态
|
||||
if (!globalData.judgeId || !globalData.token) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 注意:裁判长没有固定场地和项目,需要查看所有
|
||||
this.competitionId = globalData.matchId
|
||||
// 检查是否是裁判长
|
||||
if (globalData.userRole !== 'admin') {
|
||||
console.warn('非裁判长用户,跳转到普通评分页')
|
||||
uni.reLaunch({
|
||||
url: '/pages/score-list/score-list'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 加载比赛信息
|
||||
this.matchInfo = {
|
||||
name: globalData.matchName || '比赛名称',
|
||||
time: globalData.matchTime || ''
|
||||
}
|
||||
|
||||
// 从 globalData 获取场地信息(与普通裁判相同)
|
||||
this.venueInfo = {
|
||||
id: globalData.venueId,
|
||||
name: globalData.venueName || '场地'
|
||||
}
|
||||
|
||||
// 从 globalData 获取项目列表
|
||||
this.projects = globalData.projects || []
|
||||
this.currentProjectIndex = globalData.currentProjectIndex || 0
|
||||
this.updateCurrentProject()
|
||||
this.judgeId = globalData.judgeId
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('裁判长列表页加载:', {
|
||||
userRole: globalData.userRole,
|
||||
competitionId: this.competitionId
|
||||
judgeId: this.judgeId,
|
||||
venueId: this.venueInfo.id,
|
||||
projectId: this.projectInfo.id
|
||||
})
|
||||
}
|
||||
|
||||
// 加载场地和项目列表
|
||||
await this.loadVenuesAndProjects()
|
||||
await this.loadPlayers(true)
|
||||
this.isFirstLoad = false
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
// 从修改评分页返回时刷新数据
|
||||
if (!this.isFirstLoad) {
|
||||
if (config.debug) {
|
||||
console.log('页面显示,刷新数据')
|
||||
}
|
||||
await this.loadPlayers(true)
|
||||
}
|
||||
},
|
||||
|
||||
async onPullDownRefresh() {
|
||||
await this.loadPlayers(true)
|
||||
uni.stopPullDownRefresh()
|
||||
},
|
||||
|
||||
async onReachBottom() {
|
||||
if (this.hasMore && !this.isLoading) {
|
||||
await this.loadMore()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadVenuesAndProjects() {
|
||||
formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return ''
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 获取场地列表
|
||||
// Mock模式:调用 mock/athlete.js 的 getVenues 函数
|
||||
// API模式:调用 api/athlete.js 的 getVenues 函数(GET /martial/venue/list)
|
||||
const venuesRes = await dataAdapter.getData('getVenues', {
|
||||
competitionId: this.competitionId
|
||||
})
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 获取项目列表
|
||||
// Mock模式:调用 mock/athlete.js 的 getProjects 函数
|
||||
// API模式:调用 api/athlete.js 的 getProjects 函数(GET /martial/project/list)
|
||||
const projectsRes = await dataAdapter.getData('getProjects', {
|
||||
competitionId: this.competitionId
|
||||
})
|
||||
|
||||
this.venues = venuesRes.data || []
|
||||
this.projects = projectsRes.data || []
|
||||
|
||||
// 默认选中第一个场地和项目
|
||||
if (this.venues.length > 0) {
|
||||
this.currentVenue = this.venues[0].venueId
|
||||
}
|
||||
if (this.projects.length > 0) {
|
||||
this.currentProject = this.projects[0].projectId
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('场地和项目加载成功:', {
|
||||
venues: this.venues.length,
|
||||
projects: this.projects.length,
|
||||
currentVenue: this.currentVenue,
|
||||
currentProject: this.currentProject
|
||||
})
|
||||
}
|
||||
|
||||
// 加载选手列表
|
||||
if (this.currentVenue && this.currentProject) {
|
||||
await this.loadPlayers()
|
||||
}
|
||||
|
||||
const date = new Date(dateTimeStr)
|
||||
if (isNaN(date.getTime())) return dateTimeStr
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const hours = date.getHours()
|
||||
const minutes = date.getMinutes()
|
||||
const paddedMinutes = minutes < 10 ? '0' + minutes : minutes
|
||||
return year + '年' + month + '月' + day + '日 ' + hours + ':' + paddedMinutes
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('加载场地和项目失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
return dateTimeStr
|
||||
}
|
||||
},
|
||||
|
||||
async loadPlayers() {
|
||||
formatScore(score) {
|
||||
// 处理 null、undefined、-1 等无效值
|
||||
if (score === null || score === undefined || score === -1 || score === '-1') {
|
||||
return '--'
|
||||
}
|
||||
// 如果是字符串类型的数字,直接返回
|
||||
if (typeof score === 'string' && !isNaN(parseFloat(score))) {
|
||||
return score
|
||||
}
|
||||
// 如果是数字类型,保留3位小数
|
||||
if (typeof score === 'number') {
|
||||
return score.toFixed(3)
|
||||
}
|
||||
return score
|
||||
},
|
||||
|
||||
async handleRefresh() {
|
||||
if (this.isLoading) return
|
||||
uni.showToast({ title: '刷新中...', icon: 'loading', duration: 1000 })
|
||||
await this.loadPlayers(true)
|
||||
uni.showToast({ title: '刷新成功', icon: 'success', duration: 1000 })
|
||||
},
|
||||
|
||||
async loadPlayers(refresh = false) {
|
||||
if (this.isLoading) return
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true
|
||||
this.isLoading = true
|
||||
if (refresh) {
|
||||
this.pagination.current = 1
|
||||
this.hasMore = true
|
||||
}
|
||||
if (refresh && this.isFirstLoad) {
|
||||
uni.showLoading({ title: '加载中...', mask: true })
|
||||
}
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
const params = {
|
||||
matchCode: globalData.matchCode,
|
||||
judgeId: this.judgeId,
|
||||
venueId: this.venueInfo.id,
|
||||
projectId: this.projectInfo.id,
|
||||
current: this.pagination.current,
|
||||
size: this.pagination.size
|
||||
}
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined || params[key] === null || params[key] === '') {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 获取选手列表(裁判长视图)
|
||||
// Mock模式:调用 mock/athlete.js 的 getAthletesForAdmin 函数
|
||||
// API模式:调用 api/athlete.js 的 getAthletesForAdmin 函数(GET /api/mini/athletes/admin)
|
||||
const response = await dataAdapter.getData('getAthletesForAdmin', {
|
||||
competitionId: this.competitionId,
|
||||
venueId: this.currentVenue,
|
||||
projectId: this.currentProject
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 保存选手列表
|
||||
this.players = response.data || []
|
||||
|
||||
// 计算评分统计(裁判长视图:统计有总分的选手)
|
||||
this.totalCount = this.players.length
|
||||
this.scoredCount = this.players.filter(p => p.totalScore).length
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('选手列表加载成功:', {
|
||||
venueId: this.currentVenue,
|
||||
projectId: this.currentProject,
|
||||
total: this.totalCount,
|
||||
console.log('请求选手列表参数:', params)
|
||||
}
|
||||
|
||||
// 裁判长使用 getAthletesForAdmin 接口
|
||||
const response = await dataAdapter.getData('getAthletesForAdmin', params)
|
||||
|
||||
if (config.debug) {
|
||||
console.log('选手列表响应:', response)
|
||||
}
|
||||
|
||||
if (refresh && this.isFirstLoad) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
const responseData = response.data || {}
|
||||
const records = responseData.records || response.data || []
|
||||
const total = responseData.total || records.length
|
||||
this.pagination.total = total
|
||||
this.totalCount = total
|
||||
if (refresh) {
|
||||
this.players = records
|
||||
} else {
|
||||
this.players = [...this.players, ...records]
|
||||
}
|
||||
// 裁判长视图:统计有总分的选手
|
||||
this.scoredCount = this.players.filter(p => p.totalScore).length
|
||||
this.hasMore = this.players.length < total
|
||||
|
||||
if (config.debug) {
|
||||
console.log('选手列表处理结果:', {
|
||||
total: total,
|
||||
loaded: this.players.length,
|
||||
scored: this.scoredCount,
|
||||
players: this.players
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('加载选手列表失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.showToast({ title: error.message || '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
async switchVenue(venueId) {
|
||||
if (this.currentVenue === venueId) return
|
||||
|
||||
this.currentVenue = venueId
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('切换场地:', venueId)
|
||||
}
|
||||
|
||||
// 重新加载选手列表
|
||||
await this.loadPlayers()
|
||||
},
|
||||
|
||||
async switchProject(projectId) {
|
||||
if (this.currentProject === projectId) return
|
||||
|
||||
this.currentProject = projectId
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('切换项目:', projectId)
|
||||
}
|
||||
|
||||
// 重新加载选手列表
|
||||
await this.loadPlayers()
|
||||
async loadMore() {
|
||||
if (!this.hasMore || this.isLoading) return
|
||||
this.pagination.current++
|
||||
await this.loadPlayers(false)
|
||||
},
|
||||
|
||||
goToModify(player) {
|
||||
// 保存当前选手信息到全局数据
|
||||
const app = getApp()
|
||||
app.globalData.currentAthlete = player
|
||||
uni.navigateTo({ url: '/pages/modify-score/modify-score' })
|
||||
},
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/modify-score/modify-score'
|
||||
})
|
||||
updateCurrentProject() {
|
||||
const currentProject = this.projects[this.currentProjectIndex] || {}
|
||||
this.projectInfo = {
|
||||
id: currentProject.projectId,
|
||||
name: currentProject.projectName || '项目'
|
||||
}
|
||||
},
|
||||
|
||||
async switchProject(index) {
|
||||
if (index === this.currentProjectIndex) return
|
||||
this.currentProjectIndex = index
|
||||
const app = getApp()
|
||||
app.globalData.currentProjectIndex = index
|
||||
this.updateCurrentProject()
|
||||
await this.loadPlayers(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +360,7 @@ export default {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
/* ==================== 导航栏 ==================== */
|
||||
.nav-bar {
|
||||
height: 90rpx;
|
||||
background: linear-gradient(135deg, #1B7C5E 0%, #2A9D7E 100%);
|
||||
@@ -307,7 +375,6 @@ export default {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
@@ -315,24 +382,15 @@ export default {
|
||||
right: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.icon-menu,
|
||||
.icon-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.nav-dots, .nav-circle {
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 比赛信息 */
|
||||
/* ==================== 比赛信息 ==================== */
|
||||
.match-info {
|
||||
padding: 30rpx;
|
||||
background-color: #F5F5F5;
|
||||
@@ -341,132 +399,94 @@ export default {
|
||||
.match-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
margin-bottom: 10rpx;
|
||||
color: #1B7C5E;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.match-time {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 场地和项目区域 */
|
||||
.venue-section {
|
||||
/* ==================== 场地卡片 ==================== */
|
||||
.venue-card {
|
||||
margin: 0 30rpx 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 场地滚动容器 */
|
||||
.venue-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 20rpx;
|
||||
.venue-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 4rpx solid #1B7C5E;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.venue-tabs {
|
||||
display: inline-flex;
|
||||
gap: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 4rpx solid #E0E0E0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.venue-tab {
|
||||
.venue-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #666666;
|
||||
padding: 0 20rpx;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.venue-tab.active {
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.venue-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -24rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4rpx;
|
||||
background-color: #1B7C5E;
|
||||
.refresh-link {
|
||||
font-size: 26rpx;
|
||||
color: #4A90D9;
|
||||
}
|
||||
|
||||
.venue-tip {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20rpx;
|
||||
.project-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tip-bold {
|
||||
color: #FF4D6A;
|
||||
font-weight: 500;
|
||||
.project-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tip-normal {
|
||||
color: #FF4D6A;
|
||||
}
|
||||
|
||||
/* 项目滚动容器 */
|
||||
.project-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-list {
|
||||
display: inline-flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.project-btn {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
border: 2rpx solid #CCCCCC;
|
||||
.project-chip {
|
||||
padding: 20rpx 12rpx;
|
||||
border: 2rpx solid #1B7C5E;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
color: #1B7C5E;
|
||||
background-color: #FFFFFF;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-btn.active {
|
||||
.project-chip.active {
|
||||
background-color: #1B7C5E;
|
||||
color: #FFFFFF;
|
||||
border-color: #1B7C5E;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 评分统计 */
|
||||
/* ==================== 评分统计 ==================== */
|
||||
.score-stats {
|
||||
padding: 20rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.stats-text {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
.stats-value {
|
||||
font-size: 32rpx;
|
||||
color: #1B7C5E;
|
||||
font-weight: 600;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 选手列表 */
|
||||
/* ==================== 选手卡片 ==================== */
|
||||
.player-list {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
@@ -474,12 +494,12 @@ export default {
|
||||
.player-card {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.player-header {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
@@ -492,32 +512,31 @@ export default {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* ==================== 操作区域 ==================== */
|
||||
.action-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10rpx;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.total-score {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chief-actions {
|
||||
.score-tag {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10rpx;
|
||||
align-items: center;
|
||||
padding: 12rpx 20rpx;
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 8rpx;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
}
|
||||
|
||||
.chief-hint {
|
||||
font-size: 22rpx;
|
||||
color: #FF4D6A;
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
max-width: 400rpx;
|
||||
.tag-label {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.tag-value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modify-btn {
|
||||
@@ -527,21 +546,50 @@ export default {
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.modify-btn:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
/* ==================== 选手详情 ==================== */
|
||||
.player-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
.detail-row {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ==================== 加载状态 ==================== */
|
||||
.loading-status {
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 26rpx;
|
||||
color: #1B7C5E;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* ==================== 空状态 ==================== */
|
||||
.empty-state {
|
||||
padding: 100rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,75 +4,111 @@
|
||||
<view class="nav-bar">
|
||||
<view class="nav-title">评分系统</view>
|
||||
<view class="nav-right">
|
||||
<view class="icon-menu">···</view>
|
||||
<view class="icon-close">⊗</view>
|
||||
<view class="nav-dots">···</view>
|
||||
<view class="nav-circle">◎</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 比赛信息 -->
|
||||
<view class="match-info">
|
||||
<view class="match-title">{{ matchInfo.name }}</view>
|
||||
<view class="match-time">比赛时间:{{ matchInfo.time }}</view>
|
||||
<view class="match-time">比赛时间:{{ formatDateTime(matchInfo.time) }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 场地和项目选择 -->
|
||||
<view class="venue-section">
|
||||
<!-- 场地和项目卡片 -->
|
||||
<view class="venue-card">
|
||||
<!-- 场地标题行 -->
|
||||
<view class="venue-header">
|
||||
<view class="venue-tab active">{{ venueInfo.name }}</view>
|
||||
<view class="venue-name">{{ venueInfo.name }}</view>
|
||||
<view class="refresh-link" @click="handleRefresh">刷新</view>
|
||||
</view>
|
||||
|
||||
<view class="project-section">
|
||||
<view
|
||||
class="project-btn"
|
||||
:class="{ active: index === currentProjectIndex }"
|
||||
v-for="(project, index) in projects"
|
||||
:key="project.projectId"
|
||||
@click="switchProject(index)"
|
||||
>
|
||||
{{ project.projectName }}
|
||||
<!-- 项目筛选 -->
|
||||
<view class="project-row">
|
||||
<view class="project-grid">
|
||||
<view
|
||||
class="project-chip"
|
||||
:class="{ active: index === currentProjectIndex }"
|
||||
v-for="(project, index) in projects"
|
||||
:key="project.projectId"
|
||||
@click="switchProject(index)"
|
||||
>
|
||||
{{ project.projectName }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已评分统计 -->
|
||||
<!-- 评分统计 -->
|
||||
<view class="score-stats">
|
||||
<text class="stats-text">已评分:</text>
|
||||
<text class="stats-number">{{ scoredCount }}/{{ totalCount }}</text>
|
||||
<text class="stats-label">已评分:</text>
|
||||
<text class="stats-value">{{ scoredCount }}/{{ totalCount }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 选手列表 -->
|
||||
<view class="player-list">
|
||||
<!-- 遍历选手列表 -->
|
||||
<!-- 选手卡片 -->
|
||||
<view
|
||||
class="player-card"
|
||||
v-for="player in players"
|
||||
:key="player.athleteId"
|
||||
>
|
||||
<view class="player-header">
|
||||
<view class="player-name">{{ player.name }}</view>
|
||||
|
||||
<!-- 已评分:显示我的评分和总分 -->
|
||||
<view class="player-scores" v-if="player.scored">
|
||||
<text class="my-score">我的评分:{{ player.myScore }}</text>
|
||||
<text class="total-score">总分:{{ player.totalScore }}</text>
|
||||
<!-- 已评分状态 -->
|
||||
<template v-if="player.scored">
|
||||
<view class="card-header">
|
||||
<view class="player-name">{{ player.name }}</view>
|
||||
<view class="score-tags">
|
||||
<view class="score-tag">
|
||||
<text class="tag-label">我的评分:</text>
|
||||
<text class="tag-value">{{ player.myScore }}</text>
|
||||
</view>
|
||||
<!-- 总分:只有所有裁判都评分完成后才显示 -->
|
||||
<view class="score-tag" v-if="player.scoringComplete">
|
||||
<text class="tag-label">总分:</text>
|
||||
<text class="tag-value">{{ formatScore(player.totalScore) }}</text>
|
||||
</view>
|
||||
<view class="score-tag waiting" v-else>
|
||||
<text class="tag-label">总分:</text>
|
||||
<text class="tag-value">评分中({{ player.scoredJudgeCount || 0 }}/{{ player.requiredJudgeCount || "?" }})</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 未评分:显示评分按钮 -->
|
||||
<button
|
||||
class="score-btn"
|
||||
v-else
|
||||
@click="goToScoreDetail(player)"
|
||||
>
|
||||
评分
|
||||
</button>
|
||||
</view>
|
||||
<!-- 未评分状态 -->
|
||||
<template v-else>
|
||||
<view class="card-header">
|
||||
<view class="player-name">{{ player.name }}</view>
|
||||
<view class="action-row">
|
||||
<button class="score-btn" @click="goToScoreDetail(player)">评分</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="player-info">
|
||||
<view class="info-item">身份证:{{ player.idCard }}</view>
|
||||
<view class="info-item">队伍:{{ player.team }}</view>
|
||||
<view class="info-item">编号:{{ player.number }}</view>
|
||||
<!-- 选手详细信息 -->
|
||||
<view class="player-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">身份证:{{ player.idCard }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">队伍:{{ player.team }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-text">编号:{{ player.number }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-status" v-if="players.length > 0">
|
||||
<view v-if="isLoading" class="loading-text">加载中...</view>
|
||||
<view v-else-if="!hasMore" class="no-more-text">— 没有更多了 —</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="!isLoading && players.length === 0">
|
||||
<text class="empty-text">暂无选手数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -97,137 +133,289 @@ export default {
|
||||
name: ''
|
||||
},
|
||||
judgeId: '',
|
||||
projects: [], // 所有分配的项目列表
|
||||
currentProjectIndex: 0, // 当前选中的项目索引
|
||||
projects: [],
|
||||
currentProjectIndex: 0,
|
||||
players: [],
|
||||
scoredCount: 0,
|
||||
totalCount: 0
|
||||
totalCount: 0,
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0
|
||||
},
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
isFirstLoad: true
|
||||
}
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
// 获取全局数据
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
|
||||
// 加载比赛信息
|
||||
if (!globalData.judgeId || !globalData.token) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
this.matchInfo = {
|
||||
name: globalData.matchName || '比赛名称',
|
||||
time: globalData.matchTime || '比赛时间'
|
||||
time: globalData.matchTime || ''
|
||||
}
|
||||
|
||||
// 加载场地信息
|
||||
this.venueInfo = {
|
||||
id: globalData.venueId,
|
||||
name: globalData.venueName || '场地'
|
||||
name: globalData.venueName || '第一场地'
|
||||
}
|
||||
|
||||
// 加载项目列表
|
||||
this.projects = globalData.projects || []
|
||||
this.currentProjectIndex = globalData.currentProjectIndex || 0
|
||||
|
||||
// 设置当前项目信息
|
||||
this.updateCurrentProject()
|
||||
|
||||
this.judgeId = globalData.judgeId
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('评分列表页加载:', {
|
||||
judgeId: this.judgeId,
|
||||
venueId: this.venueInfo.id,
|
||||
projectId: this.projectInfo.id,
|
||||
projectsCount: this.projects.length
|
||||
projectId: this.projectInfo.id
|
||||
})
|
||||
}
|
||||
|
||||
// 加载选手列表
|
||||
await this.loadPlayers()
|
||||
await this.loadPlayers(true)
|
||||
this.isFirstLoad = false
|
||||
},
|
||||
|
||||
async onShow() {
|
||||
// 从评分详情页返回时刷新数据
|
||||
if (!this.isFirstLoad) {
|
||||
if (config.debug) {
|
||||
console.log('页面显示,刷新数据')
|
||||
}
|
||||
await this.loadPlayers(true)
|
||||
}
|
||||
},
|
||||
|
||||
async onPullDownRefresh() {
|
||||
await this.loadPlayers(true)
|
||||
uni.stopPullDownRefresh()
|
||||
},
|
||||
|
||||
async onReachBottom() {
|
||||
if (this.hasMore && !this.isLoading) {
|
||||
await this.loadMore()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadPlayers() {
|
||||
formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return ''
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true
|
||||
})
|
||||
const date = new Date(dateTimeStr)
|
||||
if (isNaN(date.getTime())) return dateTimeStr
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const hours = date.getHours()
|
||||
const minutes = date.getMinutes()
|
||||
const paddedMinutes = minutes < 10 ? '0' + minutes : minutes
|
||||
return year + '年' + month + '月' + day + '日 ' + hours + ':' + paddedMinutes
|
||||
} catch (error) {
|
||||
return dateTimeStr
|
||||
}
|
||||
},
|
||||
|
||||
// 🔥 关键改动:使用 dataAdapter 获取选手列表
|
||||
// Mock模式:调用 mock/athlete.js 的 getMyAthletes 函数
|
||||
// API模式:调用 api/athlete.js 的 getMyAthletes 函数(GET /api/mini/athletes)
|
||||
formatScore(score) {
|
||||
// 处理 null、undefined、-1 等无效值
|
||||
if (score === null || score === undefined || score === -1 || score === '-1') {
|
||||
return '--'
|
||||
}
|
||||
// 如果是字符串类型的数字,直接返回
|
||||
if (typeof score === 'string' && !isNaN(parseFloat(score))) {
|
||||
return score
|
||||
}
|
||||
// 如果是数字类型,保留3位小数
|
||||
if (typeof score === 'number') {
|
||||
return score.toFixed(3)
|
||||
}
|
||||
return score
|
||||
},
|
||||
|
||||
// 构建请求参数
|
||||
// 优先使用 matchCode(比赛编码),这样后端可以根据邀请码关联查询
|
||||
/**
|
||||
* 计算选手总分
|
||||
* 规则:所有裁判评分完成后,去掉一个最高分和一个最低分,取剩余分数的平均值
|
||||
* @param {Object} player - 选手对象
|
||||
* @returns {Number|null} 计算后的总分,如果未完成评分返回 null
|
||||
*/
|
||||
calculateTotalScore(player) {
|
||||
// 检查是否有裁判评分数据
|
||||
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查是否所有裁判都已评分
|
||||
const totalJudges = player.totalJudges || 0
|
||||
const scoredCount = player.judgeScores.length
|
||||
|
||||
if (totalJudges === 0 || scoredCount < totalJudges) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 提取所有分数
|
||||
const scores = player.judgeScores.map(j => parseFloat(j.score)).filter(s => !isNaN(s))
|
||||
|
||||
if (scores.length < 3) {
|
||||
// 少于3个评分无法去掉最高最低,直接取平均
|
||||
if (scores.length === 0) return null
|
||||
const sum = scores.reduce((a, b) => a + b, 0)
|
||||
return sum / scores.length
|
||||
}
|
||||
|
||||
// 排序
|
||||
scores.sort((a, b) => a - b)
|
||||
|
||||
// 去掉最高分和最低分
|
||||
const trimmedScores = scores.slice(1, -1)
|
||||
|
||||
// 计算平均分
|
||||
const sum = trimmedScores.reduce((a, b) => a + b, 0)
|
||||
const average = sum / trimmedScores.length
|
||||
|
||||
return average
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查选手是否所有裁判都已评分
|
||||
* @param {Object} player - 选手对象
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isAllJudgesScored(player) {
|
||||
if (!player.judgeScores || !Array.isArray(player.judgeScores)) {
|
||||
return false
|
||||
}
|
||||
const totalJudges = player.totalJudges || 0
|
||||
return totalJudges > 0 && player.judgeScores.length >= totalJudges
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取选手的显示总分
|
||||
* @param {Object} player - 选手对象
|
||||
* @returns {String} 格式化后的总分或 '--'
|
||||
*/
|
||||
getDisplayTotalScore(player) {
|
||||
const score = this.calculateTotalScore(player)
|
||||
if (score === null) {
|
||||
return '--'
|
||||
}
|
||||
return score.toFixed(3)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取裁判评分进度
|
||||
* @param {Object} player - 选手对象
|
||||
* @returns {String} 进度字符串,如 "3/6"
|
||||
*/
|
||||
getJudgeProgress(player) {
|
||||
const scored = player.judgeScores ? player.judgeScores.length : 0
|
||||
const total = player.totalJudges || '?'
|
||||
return scored + '/' + total
|
||||
},
|
||||
|
||||
async handleRefresh() {
|
||||
if (this.isLoading) return
|
||||
uni.showToast({ title: '刷新中...', icon: 'loading', duration: 1000 })
|
||||
await this.loadPlayers(true)
|
||||
uni.showToast({ title: '刷新成功', icon: 'success', duration: 1000 })
|
||||
},
|
||||
|
||||
async loadPlayers(refresh = false) {
|
||||
if (this.isLoading) return
|
||||
try {
|
||||
this.isLoading = true
|
||||
if (refresh) {
|
||||
this.pagination.current = 1
|
||||
this.hasMore = true
|
||||
}
|
||||
if (refresh && this.isFirstLoad) {
|
||||
uni.showLoading({ title: '加载中...', mask: true })
|
||||
}
|
||||
const app = getApp()
|
||||
const globalData = app.globalData || {}
|
||||
|
||||
const params = {
|
||||
// 方案1:使用比赛编码(推荐,后端可以根据邀请码关联)
|
||||
matchCode: globalData.matchCode,
|
||||
|
||||
// 方案2:使用具体的ID(作为备选)
|
||||
judgeId: this.judgeId,
|
||||
venueId: this.venueInfo.id,
|
||||
projectId: this.projectInfo.id
|
||||
projectId: this.projectInfo.id,
|
||||
current: this.pagination.current,
|
||||
size: this.pagination.size
|
||||
}
|
||||
|
||||
// 移除无效参数
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === undefined || params[key] === null || params[key] === '') {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('请求运动员列表参数:', params)
|
||||
console.log('请求选手列表参数:', params)
|
||||
}
|
||||
|
||||
const response = await dataAdapter.getData('getMyAthletes', params)
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
// 保存选手列表
|
||||
this.players = response.data || []
|
||||
|
||||
// 计算评分统计
|
||||
this.totalCount = this.players.length
|
||||
this.scoredCount = this.players.filter(p => p.scored).length
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('选手列表加载成功:', {
|
||||
total: this.totalCount,
|
||||
console.log('选手列表响应:', response)
|
||||
}
|
||||
|
||||
if (refresh && this.isFirstLoad) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
const responseData = response.data || {}
|
||||
const records = responseData.records || response.data || []
|
||||
const total = responseData.total || records.length
|
||||
this.pagination.total = total
|
||||
this.totalCount = total
|
||||
if (refresh) {
|
||||
this.players = records
|
||||
} else {
|
||||
this.players = [...this.players, ...records]
|
||||
}
|
||||
this.scoredCount = this.players.filter(p => p.scored).length
|
||||
this.hasMore = this.players.length < total
|
||||
|
||||
if (config.debug) {
|
||||
console.log('选手列表处理结果:', {
|
||||
total: total,
|
||||
loaded: this.players.length,
|
||||
scored: this.scoredCount,
|
||||
players: this.players
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('加载选手列表失败:', error)
|
||||
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.showToast({ title: error.message || '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
goToScoreDetail(player) {
|
||||
// 保存当前选手信息到全局数据
|
||||
const app = getApp()
|
||||
app.globalData.currentAthlete = player
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/score-detail/score-detail'
|
||||
})
|
||||
async loadMore() {
|
||||
if (!this.hasMore || this.isLoading) return
|
||||
this.pagination.current++
|
||||
await this.loadPlayers(false)
|
||||
},
|
||||
|
||||
goToScoreDetail(player) {
|
||||
const app = getApp()
|
||||
app.globalData.currentAthlete = player
|
||||
uni.navigateTo({ url: '/pages/score-detail/score-detail' })
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新当前项目信息
|
||||
*/
|
||||
updateCurrentProject() {
|
||||
const currentProject = this.projects[this.currentProjectIndex] || {}
|
||||
this.projectInfo = {
|
||||
@@ -236,37 +424,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换项目
|
||||
* @param {Number} index - 项目索引
|
||||
*/
|
||||
async switchProject(index) {
|
||||
// 如果点击的是当前项目,不做处理
|
||||
if (index === this.currentProjectIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新当前项目索引
|
||||
if (index === this.currentProjectIndex) return
|
||||
this.currentProjectIndex = index
|
||||
|
||||
// 更新全局数据中的项目索引
|
||||
const app = getApp()
|
||||
app.globalData.currentProjectIndex = index
|
||||
|
||||
// 更新当前项目信息
|
||||
this.updateCurrentProject()
|
||||
|
||||
// 调试信息
|
||||
if (config.debug) {
|
||||
console.log('切换项目:', {
|
||||
index: index,
|
||||
projectId: this.projectInfo.id,
|
||||
projectName: this.projectInfo.name
|
||||
})
|
||||
}
|
||||
|
||||
// 重新加载选手列表
|
||||
await this.loadPlayers()
|
||||
await this.loadPlayers(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +443,7 @@ export default {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
/* ==================== 导航栏 ==================== */
|
||||
.nav-bar {
|
||||
height: 90rpx;
|
||||
background: linear-gradient(135deg, #1B7C5E 0%, #2A9D7E 100%);
|
||||
@@ -294,7 +458,6 @@ export default {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
@@ -302,24 +465,15 @@ export default {
|
||||
right: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.icon-menu,
|
||||
.icon-close {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.nav-dots, .nav-circle {
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 比赛信息 */
|
||||
/* ==================== 比赛信息 ==================== */
|
||||
.match-info {
|
||||
padding: 30rpx;
|
||||
background-color: #F5F5F5;
|
||||
@@ -328,121 +482,94 @@ export default {
|
||||
.match-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
margin-bottom: 10rpx;
|
||||
color: #1B7C5E;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.match-time {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 场地和项目区域 */
|
||||
.venue-section {
|
||||
/* ==================== 场地卡片 ==================== */
|
||||
.venue-card {
|
||||
margin: 0 30rpx 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
margin: 20rpx 30rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.venue-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 4rpx solid #1B7C5E;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.venue-tab {
|
||||
.venue-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.venue-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -24rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4rpx;
|
||||
background-color: #1B7C5E;
|
||||
.refresh-link {
|
||||
font-size: 26rpx;
|
||||
color: #4A90D9;
|
||||
}
|
||||
|
||||
.refresh-hint {
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
}
|
||||
|
||||
.project-section {
|
||||
.project-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.project-btn {
|
||||
padding: 20rpx 40rpx;
|
||||
background-color: #FFFFFF;
|
||||
.project-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.project-chip {
|
||||
padding: 20rpx 12rpx;
|
||||
border: 2rpx solid #1B7C5E;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #1B7C5E;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #FFFFFF;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-btn:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.project-btn.active {
|
||||
.project-chip.active {
|
||||
background-color: #1B7C5E;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.project-tip {
|
||||
font-size: 22rpx;
|
||||
color: #FF4D6A;
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 评分统计 */
|
||||
/* ==================== 评分统计 ==================== */
|
||||
.score-stats {
|
||||
padding: 20rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.stats-text {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
.stats-value {
|
||||
font-size: 32rpx;
|
||||
color: #1B7C5E;
|
||||
font-weight: 600;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.warning-tip {
|
||||
padding: 0 30rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
}
|
||||
|
||||
/* 选手列表 */
|
||||
/* ==================== 选手卡片 ==================== */
|
||||
.player-list {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
@@ -450,14 +577,14 @@ export default {
|
||||
.player-card {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.player-header {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
@@ -468,34 +595,48 @@ export default {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.player-scores {
|
||||
/* ==================== 已评分标签 ==================== */
|
||||
.score-tags {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.my-score {
|
||||
font-size: 26rpx;
|
||||
.score-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 20rpx;
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 8rpx;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
}
|
||||
|
||||
.tag-label {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.total-score {
|
||||
font-size: 26rpx;
|
||||
.tag-value {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
/* ==================== 等待中状态 ==================== */
|
||||
.score-tag.waiting {
|
||||
background-color: #FFF7E6;
|
||||
border-color: #FFD591;
|
||||
}
|
||||
|
||||
.chief-hint {
|
||||
.score-tag.waiting .tag-value {
|
||||
color: #FA8C16;
|
||||
font-size: 24rpx;
|
||||
color: #FF4D6A;
|
||||
}
|
||||
|
||||
/* ==================== 未评分操作 ==================== */
|
||||
.action-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.score-btn {
|
||||
@@ -505,21 +646,50 @@ export default {
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.score-btn:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.player-info {
|
||||
/* ==================== 选手详情 ==================== */
|
||||
.player-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
.detail-row {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ==================== 加载状态 ==================== */
|
||||
.loading-status {
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 26rpx;
|
||||
color: #1B7C5E;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* ==================== 空状态 ==================== */
|
||||
.empty-state {
|
||||
padding: 100rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,10 +20,21 @@ module.exports = {
|
||||
// 开发服务器配置
|
||||
devServer: {
|
||||
port: 8080,
|
||||
host: '0.0.0.0',
|
||||
open: true,
|
||||
overlay: {
|
||||
warnings: false,
|
||||
errors: true
|
||||
},
|
||||
proxy: {
|
||||
'/mini': {
|
||||
target: 'http://localhost:8123',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/martial': {
|
||||
target: 'http://localhost:8123',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user