|
|
@@ -103,44 +103,80 @@
|
|
|
<!-- 添加或修改裁判对话框 -->
|
|
|
<el-dialog :title="dialog.title" v-model="dialog.visible" width="400px" append-to-body>
|
|
|
<el-form ref="scoreFormRef" :model="form" :rules="rules" label-width="100px">
|
|
|
- <!-- 个人项目显示个人成绩字段 -->
|
|
|
+ <!-- 个人项目录入 -->
|
|
|
<template v-if="projectClassification === '0'">
|
|
|
- <el-form-item label="运动员姓名:">
|
|
|
- {{ pickAthletes || '--' }}
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="队伍名称:">
|
|
|
- {{ form.teamName || '--' }}
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="个人成绩:" prop="individualPerformance">
|
|
|
+ <div style="margin-bottom: 15px; background: #f8f9fb; padding: 10px; border-radius: 4px;">
|
|
|
+ <p><strong>运动员:</strong>{{ pickAthletes || '--' }}</p>
|
|
|
+ <p><strong>队伍:</strong>{{ form.teamName || '--' }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 动态明细录入区域 -->
|
|
|
+ <div v-if="['2', '4', '6', '7'].includes(projectInfo.scoreRule)" class="detail-section">
|
|
|
+ <div v-for="(item, index) in scoreDetails" :key="index" style="margin-bottom: 10px; display: flex; align-items: center;">
|
|
|
+ <span style="width: 80px;">第 {{ index + 1 }} 轮:</span>
|
|
|
+ <el-input-number v-model="item.performanceValue" :precision="3" :step="0.1" style="flex: 1" />
|
|
|
+ <span style="margin-left: 8px; color: #666; width: 30px;">{{ projectInfo.countUnit || (projectInfo.scoreRule === '1' ? '秒' : '') }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 单次计数类额外录入失误 -->
|
|
|
+ <template v-else-if="projectInfo.scoreRule === '3'">
|
|
|
+ <el-form-item label="单次成绩" prop="individualPerformance">
|
|
|
+ <el-input-number v-model="form.individualPerformance" :precision="3" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="projectInfo.orderType && projectInfo.orderType.includes('2')" label="失误次数A">
|
|
|
+ <el-input-number v-model="form.faultA" :min="0" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="projectInfo.orderType && projectInfo.orderType.includes('3')" label="失误次数B">
|
|
|
+ <el-input-number v-model="form.faultB" :min="0" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 基础成绩录入(计时/排名/默认) -->
|
|
|
+ <el-form-item v-else label="个人成绩" prop="individualPerformance">
|
|
|
<el-input
|
|
|
v-model="form.individualPerformance"
|
|
|
- placeholder="输入个人成绩"
|
|
|
- type="number"
|
|
|
- step="0.01"
|
|
|
- @input="handleIndividualPerformanceInput"
|
|
|
- @blur="handleIndividualPerformanceBlur" />
|
|
|
+ :placeholder="projectInfo.scoreRule === '1' ? '支持输入秒数或 00:00.00 格式' : '输入成绩'"
|
|
|
+ >
|
|
|
+ <template #append v-if="projectInfo.scoreRule === '1'">秒</template>
|
|
|
+ <template #append v-else-if="projectInfo.countUnit">{{ projectInfo.countUnit }}</template>
|
|
|
+ </el-input>
|
|
|
</el-form-item>
|
|
|
</template>
|
|
|
|
|
|
- <!-- 团体项目显示团队成绩字段 -->
|
|
|
+ <!-- 团体项目录入 -->
|
|
|
<template v-else>
|
|
|
- <el-form-item label="队伍名称:">
|
|
|
- {{ form.teamName || '--' }}
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="团队成绩" prop="teamPerformance">
|
|
|
+ <div style="margin-bottom: 15px; background: #f8f9fb; padding: 10px; border-radius: 4px;">
|
|
|
+ <p><strong>队伍:</strong>{{ form.teamName || '--' }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 动态明细录入区域 (如团体项目也有多轮) -->
|
|
|
+ <div v-if="['2', '4', '6', '7'].includes(projectInfo.scoreRule)" class="detail-section">
|
|
|
+ <div v-for="(item, index) in scoreDetails" :key="index" style="margin-bottom: 10px; display: flex; align-items: center;">
|
|
|
+ <span style="width: 80px;">第 {{ index + 1 }} 轮:</span>
|
|
|
+ <el-input-number v-model="item.performanceValue" :precision="3" :step="0.1" style="flex: 1" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基础成绩录入(计时类支持字符串,其他数字) -->
|
|
|
+ <el-form-item v-else label="团队成绩" prop="teamPerformance">
|
|
|
<el-input
|
|
|
+ v-if="projectInfo.scoreRule === '1'"
|
|
|
v-model="form.teamPerformance"
|
|
|
- placeholder="输入团队成绩"
|
|
|
- type="number"
|
|
|
- step="0.01"
|
|
|
- @input="handleTeamPerformanceInput"
|
|
|
- @blur="handleTeamPerformanceBlur" />
|
|
|
+ placeholder="支持输入 00:00.00 格式"
|
|
|
+ >
|
|
|
+ <template #append>秒</template>
|
|
|
+ </el-input>
|
|
|
+ <div v-else style="display: flex; align-items: center;">
|
|
|
+ <el-input-number v-model="form.teamPerformance" :precision="3" style="flex: 1" />
|
|
|
+ <span style="margin-left: 8px; color: #666;">{{ projectInfo.countUnit }}</span>
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
</template>
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
|
+ <el-button :loading="buttonLoading" type="primary" @click="submitForm">保存成绩</el-button>
|
|
|
<el-button @click="cancel">取 消</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -170,6 +206,8 @@
|
|
|
import { onMounted, onUnmounted, ref, reactive, getCurrentInstance, toRefs } from 'vue';
|
|
|
import { useRoute } from 'vue-router';
|
|
|
import { getProjectScoreData, updateScoreAndRecalculate, exportProjectScore } from '@/api/system/gameScore/index';
|
|
|
+import { getGameEventProject } from '@/api/system/gameEventProject';
|
|
|
+import { listScoreDetail } from '@/api/system/scoreDetail';
|
|
|
import { GameScoreForm } from '@/api/system/gameScore/types';
|
|
|
import Pagination from '@/components/Pagination/index.vue';
|
|
|
import type { ComponentInternalInstance } from 'vue';
|
|
|
@@ -180,6 +218,11 @@ const { game_project_type, sys_user_sex } = toRefs<any>(proxy?.useDict('game_pro
|
|
|
const route = useRoute();
|
|
|
const buttonLoading = ref(false);
|
|
|
const rankingLoading = ref(false);
|
|
|
+const projectInfo = ref<any>({}); // 存储项目规则信息
|
|
|
+
|
|
|
+// 成绩明细列表
|
|
|
+const scoreDetails = ref<any[]>([]);
|
|
|
+
|
|
|
const dataList = ref<any[]>([]);
|
|
|
const loading = ref(true);
|
|
|
const total = ref(0);
|
|
|
@@ -319,8 +362,19 @@ const form = reactive({
|
|
|
userId: 0,
|
|
|
teamName: '',
|
|
|
updateTime: '',
|
|
|
+ faultA: 0,
|
|
|
+ faultB: 0
|
|
|
});
|
|
|
|
|
|
+const reset = () => {
|
|
|
+ Object.assign(form, {
|
|
|
+ scorePoint: null, individualPerformance: 0, teamPerformance: 0, scoreRank: null,
|
|
|
+ scoreId: 0, athleteId: 0, teamId: 0, eventId: '', projectId: '',
|
|
|
+ athleteCode: '', userId: 0, teamName: '', updateTime: '', faultA: 0, faultB: 0
|
|
|
+ });
|
|
|
+ scoreDetails.value = [];
|
|
|
+};
|
|
|
+
|
|
|
const rules = {
|
|
|
individualPerformance: [
|
|
|
{ required: true, message: "个人成绩不能为空", trigger: "blur" },
|
|
|
@@ -364,123 +418,89 @@ const rules = {
|
|
|
],
|
|
|
}
|
|
|
|
|
|
-// 成绩输入处理函数
|
|
|
-const handleIndividualPerformanceInput = (value: string) => {
|
|
|
- // 确保输入的是有效数字
|
|
|
- if (value && !isNaN(parseFloat(value))) {
|
|
|
- form.individualPerformance = parseFloat(value);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const handleIndividualPerformanceBlur = () => {
|
|
|
- // 失焦时确保数据类型正确
|
|
|
- if (form.individualPerformance !== null && form.individualPerformance !== undefined) {
|
|
|
- const numValue = parseFloat(String(form.individualPerformance));
|
|
|
- if (!isNaN(numValue)) {
|
|
|
- form.individualPerformance = numValue;
|
|
|
- } else {
|
|
|
- form.individualPerformance = 0;
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const handleTeamPerformanceInput = (value: string) => {
|
|
|
- // 确保输入的是有效数字
|
|
|
- if (value && !isNaN(parseFloat(value))) {
|
|
|
- form.teamPerformance = parseFloat(value);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const handleTeamPerformanceBlur = () => {
|
|
|
- // 失焦时确保数据类型正确
|
|
|
- if (form.teamPerformance !== null && form.teamPerformance !== undefined) {
|
|
|
- const numValue = parseFloat(String(form.teamPerformance));
|
|
|
- if (!isNaN(numValue)) {
|
|
|
- form.teamPerformance = numValue;
|
|
|
- } else {
|
|
|
- form.teamPerformance = 0;
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
/** 取消按钮 */
|
|
|
const cancel = () => {
|
|
|
dialog.visible = false;
|
|
|
}
|
|
|
|
|
|
-/** 编辑按钮 */
|
|
|
-const editScore = (row: any) => {
|
|
|
- dialog.visible = true;
|
|
|
- // 将当前行数据复制到编辑表单中,确保成绩数据类型正确
|
|
|
- Object.assign(form, {
|
|
|
- scoreId: row.scoreId,
|
|
|
- scorePoint: row.scorePoint || null,
|
|
|
- individualPerformance: parseFloat(String(row.individualPerformance)) || 0,
|
|
|
- teamPerformance: parseFloat(String(row.teamPerformance)) || 0,
|
|
|
- scoreRank: row.scoreRank || null,
|
|
|
- athleteId: row.athleteId,
|
|
|
- teamId: row.teamId,
|
|
|
- athleteCode: row.athleteCode,
|
|
|
- userId: row.userId,
|
|
|
- eventId: row.eventId,
|
|
|
- projectId: row.projectId,
|
|
|
- teamName: row.teamName || '',
|
|
|
- updateTime: row.updateTime,
|
|
|
- });
|
|
|
+/** 修改成绩 */
|
|
|
+const editScore = async (row: any) => {
|
|
|
+ reset();
|
|
|
+
|
|
|
+ // 强制重新加载项目规则信息,确保获取到最新的 scoreCount 和 countUnit
|
|
|
+ await loadProjectInfo();
|
|
|
+
|
|
|
+ Object.assign(form, { ...row });
|
|
|
pickAthletes.value = row.name;
|
|
|
+
|
|
|
+ // 如果是需要明细的项目(远度、多次计数等),加载明细
|
|
|
+ if (['2', '4', '6', '7'].includes(projectInfo.value.scoreRule)) {
|
|
|
+ // 改为按维度查询,不再单纯依赖 scoreId
|
|
|
+ const queryParams: any = {
|
|
|
+ projectId: row.projectId
|
|
|
+ };
|
|
|
+ if (projectClassification === '0') {
|
|
|
+ queryParams.athleteId = row.athleteId;
|
|
|
+ } else {
|
|
|
+ queryParams.teamId = row.teamId;
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await listScoreDetail(queryParams);
|
|
|
+ const existingDetails = res.rows || [];
|
|
|
+ // 根据项目配置的次数初始化明细数组
|
|
|
+ const count = projectInfo.value.scoreCount || 1;
|
|
|
+ scoreDetails.value = [];
|
|
|
+ for (let i = 1; i <= count; i++) {
|
|
|
+ const detail = existingDetails.find((d: any) => d.attemptIndex === i);
|
|
|
+ scoreDetails.value.push({
|
|
|
+ scoreId: row.scoreId,
|
|
|
+ attemptIndex: i,
|
|
|
+ performanceValue: detail ? detail.performanceValue : 0,
|
|
|
+ faultA: detail ? detail.faultA : 0,
|
|
|
+ faultB: detail ? detail.faultB : 0
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dialog.visible = true;
|
|
|
+ dialog.title = "录入成绩";
|
|
|
};
|
|
|
|
|
|
-/** 提交按钮 */
|
|
|
-const submitForm = async () => {
|
|
|
+/** 提交表单 */
|
|
|
+const submitForm = () => {
|
|
|
scoreFormRef.value?.validate(async (valid: boolean) => {
|
|
|
if (valid) {
|
|
|
+ buttonLoading.value = true;
|
|
|
try {
|
|
|
- buttonLoading.value = true;
|
|
|
-
|
|
|
- // 确保成绩数据类型正确
|
|
|
- let individualPerformance = 0;
|
|
|
- let teamPerformance = 0;
|
|
|
+ // 使用 any 避开 TS 对 details 的类型检查
|
|
|
+ const postData: any = { ...form };
|
|
|
|
|
|
- if (projectClassification === '0') {
|
|
|
- // 个人项目
|
|
|
- individualPerformance = parseFloat(String(form.individualPerformance)) || 0;
|
|
|
- if (isNaN(individualPerformance)) {
|
|
|
- individualPerformance = 0;
|
|
|
+ // 计时类单位转换 (统一处理两个字段,确保不携带时间格式字符串)
|
|
|
+ if (projectInfo.value.scoreRule === '1') {
|
|
|
+ if (typeof postData.individualPerformance === 'string') {
|
|
|
+ postData.individualPerformance = timeToSeconds(postData.individualPerformance);
|
|
|
}
|
|
|
- } else {
|
|
|
- // 团体项目
|
|
|
- teamPerformance = parseFloat(String(form.teamPerformance)) || 0;
|
|
|
- if (isNaN(teamPerformance)) {
|
|
|
- teamPerformance = 0;
|
|
|
+ if (typeof postData.teamPerformance === 'string') {
|
|
|
+ postData.teamPerformance = timeToSeconds(postData.teamPerformance);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ // 非计时类,也确保是数值,防止占位符干扰
|
|
|
+ if (isNaN(parseFloat(postData.individualPerformance))) postData.individualPerformance = 0;
|
|
|
+ if (isNaN(parseFloat(postData.teamPerformance))) postData.teamPerformance = 0;
|
|
|
}
|
|
|
+
|
|
|
+ // 挂载成绩明细
|
|
|
+ postData.details = scoreDetails.value;
|
|
|
|
|
|
- // 构建提交数据,确保使用正确的scoreId
|
|
|
- const submitForm: GameScoreForm = {
|
|
|
- scoreId: form.scoreId, // 确保使用正确的scoreId
|
|
|
- scorePoint: form.scorePoint || 0,
|
|
|
- individualPerformance: individualPerformance,
|
|
|
- teamPerformance: teamPerformance,
|
|
|
- scoreRank: form.scoreRank || 0,
|
|
|
- athleteId: form.athleteId,
|
|
|
- teamId: form.teamId,
|
|
|
- eventId: eventId,
|
|
|
- projectId: projectId,
|
|
|
- scoreType: projectClassification === '0' ? 'individual' : 'team',
|
|
|
- statusFlag: '0',
|
|
|
- status: '0',
|
|
|
- remark: projectClassification === '0' ? '个人项目成绩' : '团体项目成绩'
|
|
|
- };
|
|
|
-
|
|
|
- // 调用后端接口更新成绩并重新计算排名积分
|
|
|
- await updateScoreAndRecalculate(submitForm);
|
|
|
+ // 调用后端接口更新成绩(后端已支持 recalculate)
|
|
|
+ await updateScoreAndRecalculate(postData);
|
|
|
|
|
|
- proxy?.$modal.msgSuccess("操作成功");
|
|
|
+ proxy?.$modal.msgSuccess("保存成功,排名已重新计算");
|
|
|
dialog.visible = false;
|
|
|
- loadData(false); // 提交成功后不自动计算排名,用户需要手动点击排名按钮
|
|
|
- } catch (error) {
|
|
|
- // console.error("提交失败:", error);
|
|
|
- proxy?.$modal.msgError("操作失败,请稍后再试");
|
|
|
+ loadData(false);
|
|
|
+ } catch (err) {
|
|
|
+ console.error("提交失败:", err);
|
|
|
+ proxy?.$modal.msgError("保存失败,请检查网络或数据格式");
|
|
|
} finally {
|
|
|
buttonLoading.value = false;
|
|
|
}
|
|
|
@@ -579,6 +599,25 @@ const handlePagination = (paginationData: { page: number, limit: number }) => {
|
|
|
loadData(false); // 分页加载不自动计算排名
|
|
|
};
|
|
|
|
|
|
+/** 时间字符串转秒 (00:00.00 -> 60.00) */
|
|
|
+const timeToSeconds = (timeStr: string) => {
|
|
|
+ if (!timeStr || !timeStr.includes(':')) return parseFloat(timeStr) || 0;
|
|
|
+ const parts = timeStr.split(':');
|
|
|
+ let seconds = 0;
|
|
|
+ if (parts.length === 3) { // 00:00:00.000
|
|
|
+ seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseFloat(parts[2]);
|
|
|
+ } else if (parts.length === 2) { // 00:00.00
|
|
|
+ seconds = parseInt(parts[0]) * 60 + parseFloat(parts[1]);
|
|
|
+ }
|
|
|
+ return seconds;
|
|
|
+};
|
|
|
+
|
|
|
+/** 加载项目规则信息 */
|
|
|
+const loadProjectInfo = async () => {
|
|
|
+ const res = await getGameEventProject(projectId);
|
|
|
+ projectInfo.value = res.data;
|
|
|
+};
|
|
|
+
|
|
|
// 组件卸载时清理定时器
|
|
|
onUnmounted(() => {
|
|
|
if (autoRefreshInterval.value) {
|
|
|
@@ -588,6 +627,7 @@ onUnmounted(() => {
|
|
|
});
|
|
|
|
|
|
onMounted(() => {
|
|
|
+ loadProjectInfo(); // 加载规则
|
|
|
loadData(false); // 页面初始化时不自动计算排名,需要手动点击计算排名按钮
|
|
|
// 开启自动刷新
|
|
|
startAutoRefresh();
|