Browse Source

feat(gameScore): 重构成绩管理功能

- 新增个人项目和团体项目的成绩管理支持
- 优化成绩数据结构,区分个人成绩和团队成绩
- 添加成绩计算和排名功能
- 重构成绩列表展示,根据项目分类显示不同信息
- 优化成绩编辑界面,适配个人和团体项目
zhou 6 days ago
parent
commit
d6383cc347

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@ tests/e2e/reports
 selenium-debug.log
 
 # Editor directories and files
+.md
 .idea
 .vscode
 *.suo

+ 30 - 0
src/api/system/backup/index.ts

@@ -0,0 +1,30 @@
+import request from '@/utils/request';
+
+/**
+ * 备份数据库 - 下载SQL文件
+ */
+export function backupDatabase() {
+  return request({
+    url: '/system/backup/backup',
+    method: 'get',
+    responseType: 'blob'
+  });
+}
+
+/**
+ * 恢复数据库 - 上传SQL文件
+ * @param file SQL文件
+ */
+export function restoreDatabase(file: File) {
+  const formData = new FormData();
+  formData.append('file', file);
+
+  return request({
+    url: '/system/backup/restore',
+    method: 'post',
+    data: formData,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  });
+}

+ 6 - 6
src/api/system/gameEventProject/types.ts

@@ -19,9 +19,9 @@ export interface GameEventProjectVO {
   projectType: string;
 
   /**
-   * 项目组别
+   * 归类(0个人项目/1团体项目)
    */
-  groupType: string;
+  classification: string;
 
   /**
    * 裁判组员
@@ -125,9 +125,9 @@ export interface GameEventProjectForm extends BaseEntity {
   projectType?: string;
 
   /**
-   * 项目组别
+   * 归类(0个人项目/1团体项目)
    */
-  groupType?: string;
+  classification?: string;
 
   /**
    * 裁判组员
@@ -237,9 +237,9 @@ export interface GameEventProjectQuery extends PageQuery {
   projectType?: string;
 
   /**
-   * 项目组别
+   * 归类(0个人项目/1团体项目)
    */
-  groupType?: string;
+  classification?: string;
 
   /**
    * 开始时间

+ 61 - 0
src/api/system/gameScore/index.ts

@@ -27,6 +27,19 @@ export const getGameScore = (scoreId: string | number): AxiosPromise<GameScoreVO
   });
 };
 
+/**
+ * 根据运动员ID和项目ID查询成绩
+ * @param athleteId
+ * @param projectId
+ */
+export const getScoreByAthleteIdAndProjectId = (athleteId: string | number, projectId: string | number): AxiosPromise<GameScoreVO> => {
+  return request({
+    url: '/system/gameScore/getScoreByAthleteIdAndProjectId',
+    method: 'get',
+    params: { athleteId, projectId }
+  });
+};
+
 /**
  * 查询项目成绩详细
  * @param projectId
@@ -72,3 +85,51 @@ export const delGameScore = (scoreId: string | number | Array<string | number>)
     method: 'delete'
   });
 };
+
+/**
+ * 获取项目成绩综合数据(包含运动员/队伍信息)
+ * @param eventId 赛事ID
+ * @param projectId 项目ID
+ * @param classification 项目分类
+ * @param searchValue 搜索值
+ * @param pageQuery 分页参数
+ */
+export const getProjectScoreData = (params: {
+  eventId: string | number;
+  projectId: string | number;
+  classification: string;
+  searchValue?: string;
+  pageNum?: number;
+  pageSize?: number;
+}) => {
+  return request({
+    url: '/system/gameScore/getProjectScoreData',
+    method: 'get',
+    params
+  });
+};
+
+/**
+ * 更新成绩并重新计算排名积分
+ * @param data 成绩数据
+ */
+export const updateScoreAndRecalculate = (data: GameScoreForm) => {
+  return request({
+    url: '/system/gameScore/updateScoreAndRecalculate',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 重新计算项目排名和积分
+ * @param eventId 赛事ID
+ * @param projectId 项目ID
+ */
+export const recalculateRankingsAndPoints = (eventId: string | number, projectId: string | number) => {
+  return request({
+    url: '/system/gameScore/recalculateRankingsAndPoints',
+    method: 'post',
+    params: { eventId, projectId }
+  });
+};

+ 14 - 4
src/api/system/gameScore/types.ts

@@ -24,9 +24,14 @@ export interface GameScoreVO {
   teamId: string | number;
 
   /**
-   * 成绩
+   * 个人成绩
    */
-  scoreValue: number;
+  individualPerformance: number;
+
+  /**
+   * 团队成绩
+   */
+  teamPerformance: number;
 
   /**
    * 成绩类型
@@ -107,9 +112,14 @@ export interface GameScoreForm extends BaseEntity {
   teamId?: string | number;
 
   /**
-   * 成绩值
+   * 个人成绩
+   */
+  individualPerformance?: number;
+
+  /**
+   * 团队成绩
    */
-  scoreValue?: number;
+  teamPerformance?: number;
 
   /**
    * 成绩类型

+ 1 - 1
src/router/index.ts

@@ -138,7 +138,7 @@ export const constantRoutes: RouteRecordRaw[] = [
     hidden: true,
     children: [
       {
-        path: 'edit/:projectId/:projectName/:projectType/:eventId',
+        path: 'edit/:projectId/:projectName/:projectType/:eventId/:classification',
         component: () => import('@/views/system/gameScore/gameScoreEdit.vue'),
         name: 'GameScoreEdit',
         meta: { title: '修改成绩', icon: 'form' }

+ 38 - 39
src/views/system/gameEventGroup/detail.vue

@@ -359,48 +359,47 @@ const generateGroups = async () => {
     // 随机打乱运动员顺序
     const shuffledAthletes = [...eligibleAthletes].sort(() => Math.random() - 0.5);
     
-    // 按组别和道次分配运动员
-    let athleteIndex = 0;
+    // 记录已分配的运动员ID,避免重复分配
+    const assignedAthleteIds = new Set();
     
+    // 按组别和道次分配运动员
     for (let groupIndex = 1; groupIndex <= groupInfo.value.includeGroupNum; groupIndex++) {
       for (let track = 1; track <= groupInfo.value.trackNum; track++) {
-        if (athleteIndex < shuffledAthletes.length) {
-           let selectedAthlete = shuffledAthletes[athleteIndex];
-           
-           // 检查同一组中是否已有同一队伍的运动员
-           const hasSameTeamInGroup = Array.from(groupResult.value.entries())
-             .some(([key, existingAthlete]) => {
-               const [existingGroup] = key.split('-');
-               return existingGroup === groupIndex.toString() && 
-                      existingAthlete.teamId === selectedAthlete.teamId;
-             });
-           
-           // 如果同一组中已有同一队伍的运动员,尝试找下一个不同队伍的运动员
-           if (hasSameTeamInGroup) {
-             let nextAthleteIndex = athleteIndex + 1;
-             let found = false;
-             
-             while (nextAthleteIndex < shuffledAthletes.length && !found) {
-               const nextAthlete = shuffledAthletes[nextAthleteIndex];
-               const hasSameTeam = Array.from(groupResult.value.entries())
-                 .some(([key, existingAthlete]) => {
-                   const [existingGroup] = key.split('-');
-                   return existingGroup === groupIndex.toString() && 
-                          existingAthlete.teamId === nextAthlete.teamId;
-                 });
-               
-               if (!hasSameTeam) {
-                 selectedAthlete = nextAthlete;
-                 found = true;
-               }
-               nextAthleteIndex++;
-             }
-           }
-           
-           const key = `${groupIndex}-${track}`;
-           groupResult.value.set(key, selectedAthlete);
-           athleteIndex++;
-         }
+        // 寻找可用的运动员
+        let selectedAthlete = null;
+        let athleteIndex = 0;
+        
+        while (athleteIndex < shuffledAthletes.length && !selectedAthlete) {
+          const candidateAthlete = shuffledAthletes[athleteIndex];
+          
+          // 检查运动员是否已经被分配
+          if (assignedAthleteIds.has(candidateAthlete.athleteId)) {
+            athleteIndex++;
+            continue;
+          }
+          
+          // 检查同一组中是否已有同一队伍的运动员
+          const hasSameTeamInGroup = Array.from(groupResult.value.entries())
+            .some(([key, existingAthlete]) => {
+              const [existingGroup] = key.split('-');
+              return existingGroup === groupIndex.toString() && 
+                     existingAthlete.teamId === candidateAthlete.teamId;
+            });
+          
+          if (!hasSameTeamInGroup) {
+            selectedAthlete = candidateAthlete;
+            // 标记运动员为已分配
+            assignedAthleteIds.add(candidateAthlete.athleteId);
+          }
+          
+          athleteIndex++;
+        }
+        
+        // 如果找到了合适的运动员,分配到当前组和道次
+        if (selectedAthlete) {
+          const key = `${groupIndex}-${track}`;
+          groupResult.value.set(key, selectedAthlete);
+        }
       }
     }
     

+ 2 - 2
src/views/system/gameEventGroup/index.vue

@@ -52,9 +52,9 @@
               >删除
             </el-button>
           </el-col>
-          <el-col :span="1.5">
+          <!-- <el-col :span="1.5">
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEventGroup:export']">导出 </el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>

+ 25 - 15
src/views/system/gameEventProject/index.vue

@@ -7,11 +7,11 @@
             <el-form-item label="项目名称" prop="projectName">
               <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <!-- <el-form-item label="项目组别" prop="groupType">
-              <el-select v-model="queryParams.groupType" placeholder="请选择项目组别" clearable @change="handleQuery">
-                <el-option v-for="group in gameEventGroupList" :key="group.groupName" :label="group.groupName" :value="group.groupName" />
+            <el-form-item label="项目归类" prop="classification">
+              <el-select v-model="queryParams.classification" placeholder="请选择项目归类" clearable @change="handleQuery">
+                <el-option v-for="dict in game_project_classification" :key="dict.value" :label="dict.label" :value="dict.value" />
               </el-select>
-            </el-form-item> -->
+            </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -37,9 +37,9 @@
               >删除
             </el-button>
           </el-col>
-          <el-col :span="1.5">
+          <!-- <el-col :span="1.5">
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEventProject:export']">导出 </el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
@@ -53,7 +53,11 @@
             <dict-tag :options="game_project_type" :value="scope.row.projectType || ''" />
           </template>
         </el-table-column>
-        <!-- <el-table-column label="项目组别" align="center" prop="groupType" v-if="columns[3].visible" /> -->
+        <el-table-column label="项目归类" align="center" prop="classification" v-if="columns[3].visible" >
+          <template #default="scope">
+            <dict-tag :options="game_project_classification" :value="scope.row.classification || '未知'" />
+          </template>
+        </el-table-column>
         <el-table-column label="比赛场地" align="center" prop="location" v-if="columns[4].visible" />
         <el-table-column label="计算规则" align="center" prop="scoreRule" v-if="columns[5].visible">
           <template #default="scope">
@@ -131,7 +135,11 @@
         <el-form-item label="比赛场地" prop="location">
           <el-input v-model="form.location" placeholder="请输入比赛场地" />
         </el-form-item>
-
+        <el-form-item label="归类" prop="classification">
+          <el-radio-group v-model="form.classification" style="width: 100%">
+            <el-radio v-for="dict in game_project_classification" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-radio-group>
+        </el-form-item>
         <!-- <el-form-item label="参赛组数" prop="groupNum">
           <el-input v-model="form.groupNum" placeholder="请输入参赛组数" />
         </el-form-item>
@@ -200,9 +208,10 @@ import {
 import { listGameEventGroup } from '@/api/system/gameEventGroup';
 import { GameEventProjectVO, GameEventProjectQuery, GameEventProjectForm } from '@/api/system/gameEventProject/types';
 import RefereeGroupDialog from './RefereeGroupDialog.vue';
+import { orderBy } from 'element-plus/es/components/table/src/util';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { game_score_type, game_project_type } = toRefs<any>(proxy?.useDict('game_score_type', 'game_project_type'));
+const { game_score_type, game_project_type, game_project_classification } = toRefs<any>(proxy?.useDict('game_score_type', 'game_project_type','game_project_classification'));
 
 const gameEventGroupList = ref<any[]>([]); // 赛事分组列表
 
@@ -235,7 +244,7 @@ const columns = ref<FieldOption[]>([
   { key: 0, label: '主键', visible: false },
   { key: 1, label: '项目名称', visible: true },
   { key: 2, label: '项目类型', visible: true },
-  { key: 3, label: '项目组别', visible: true },
+  { key: 3, label: '归类', visible: true },
   { key: 4, label: '比赛场地', visible: true },
   { key: 5, label: '计算规则', visible: true },
   { key: 6, label: '开始时间', visible: true },
@@ -251,7 +260,7 @@ const initFormData: GameEventProjectForm = {
   eventId: undefined,
   projectName: undefined,
   projectType: undefined,
-  groupType: undefined,
+  classification: '0',
   refereeGroups: undefined,
   location: undefined,
   startTime: undefined,
@@ -259,7 +268,7 @@ const initFormData: GameEventProjectForm = {
   groupNum: undefined,
   participateNum: undefined,
   roundType: undefined,
-  orderType: undefined,
+  orderType: '0',
   scoreRule: undefined,
   scoreValue: undefined,
   award: undefined,
@@ -273,14 +282,15 @@ const data = reactive<PageData<GameEventProjectForm, GameEventProjectQuery>>({
   queryParams: {
     pageNum: 1,
     pageSize: 10,
+    classification: undefined,
     orderByColumn: undefined,
     isAsc: undefined,
   },
   rules: {
     projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
     projectType: [{ required: true, message: '项目类型不能为空', trigger: 'change' }],
-    groupType: [{ required: true, message: '项目组别不能为空', trigger: 'change' }],
-    location: [{ required: true, message: '比赛场地不能为空', trigger: 'blur' }]
+    classification: [{ required: true, message: '归类不能为空', trigger: 'change' }],
+    orderType: [{ required: true, message: '排序方式不能为空', trigger: 'change' }],
   }
 });
 
@@ -410,7 +420,7 @@ const handleViewRefereeGroup = (refereeGroups: string[], projectName: string) =>
 
 onMounted(() => {
   getList();
-  getGameEventGroupList();
+  // getGameEventGroupList();
 });
 
 </script>

+ 277 - 144
src/views/system/gameScore/gameScoreEdit.vue

@@ -3,29 +3,54 @@
     <!-- 新增:顶部搜索框 -->
     <div class="flex items-center mb-4">
       <el-button type="primary" @click="refreshData"><el-icon><Refresh /></el-icon> 刷新</el-button>
-      <el-input v-model="searchValue" placeholder="输入姓名搜索" style="margin-left: 20px; width: 200px;" clearable @keyup.enter="loadAthleteScores">
+      <el-button type="success" @click="calculateRankings" :loading="rankingLoading">
+        <el-icon><Trophy /></el-icon> 计算排名
+      </el-button>
+      <el-input 
+        v-model="searchValue" 
+        :placeholder="projectClassification === '0' ? '输入运动员姓名搜索' : '输入队伍名称搜索'" 
+        style="margin-left: 20px; width: 200px;" 
+        clearable 
+        @keyup.enter="loadData">
         <template #prefix>
           <el-icon><Search /></el-icon>
         </template>
       </el-input>
+      <div class="ml-4 text-gray-600">
+        <el-tag :type="projectClassification === '0' ? 'success' : 'warning'" class="mr-2">
+          {{ projectClassification === '0' ? '个人项目' : '团体项目' }}
+        </el-tag>
+        <span>{{ projectClassification === '0' ? '管理运动员个人成绩' : '管理队伍团队成绩' }}</span>
+      </div>
     </div>
 
     <el-card shadow="never">
-      <el-table v-loading="loading" border :data="athleteScores">
+      <el-table v-loading="loading" border :data="dataList">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="ID" align="center" prop="projectId" />
+        <el-table-column label="队伍ID" align="center" prop="teamId" />
         <el-table-column label="项目" align="center" prop="projectName" />
-        <!-- <el-table-column label="分组" align="center" prop="groupType" /> -->
         <el-table-column label="项目类型" align="center" prop="projectType" >
           <template #default="scope">
             <dict-tag :options="game_project_type" :value="scope.row.projectType" />
           </template>
         </el-table-column>
-        <el-table-column label="单位" align="center" prop="unit" />
-        <el-table-column label="姓名" align="center" prop="name" />
-        <el-table-column label="号码" align="center" prop="athleteCode" />
+        <el-table-column label="队伍名称" align="center" prop="teamName" />
+        
+        <!-- 个人项目显示运动员信息 -->
+        <template v-if="projectClassification === '0'">
+          <el-table-column label="姓名" align="center" prop="name" />
+          <el-table-column label="号码" align="center" prop="athleteCode" />
+          <el-table-column label="个人成绩" align="center" prop="individualPerformance" />
+        </template>
+        
+        <!-- 团体项目显示队伍信息 -->
+        <template v-else>
+          <!-- <el-table-column label="队伍名称" align="center" prop="teamName" /> -->
+          <!-- <el-table-column label="队伍编号" align="center" prop="teamCode" /> -->
+          <el-table-column label="团队成绩" align="center" prop="teamPerformance" />
+        </template>
+        
         <el-table-column label="积分" align="center" prop="scorePoint" />
-        <el-table-column label="成绩" align="center" prop="scoreValue" />
         <el-table-column label="排名" align="center" prop="scoreRank" />
         <el-table-column label="更新时间" align="center" prop="updateTime" />
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@@ -39,17 +64,33 @@
     </el-card>
 
     <!-- 添加或修改裁判对话框 -->
-    <el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="400px" append-to-body>
       <el-form ref="scoreFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="积分" prop="scorePoint">
-          <el-input v-model="form.scorePoint" placeholder="输入积分" />
-        </el-form-item>
-        <el-form-item label="成绩" prop="scoreValue">
-          <el-input v-model="form.scoreValue" placeholder="输入成绩" />
-        </el-form-item>
-        <el-form-item label="排名" prop="scoreRank">
-          <el-input v-model="form.scoreRank" placeholder="输入排名" />
-        </el-form-item>
+        <!-- 个人项目显示个人成绩字段 -->
+        <template v-if="projectClassification === '0'">
+          <el-form-item label="个人成绩" prop="individualPerformance">
+            <el-input 
+              v-model="form.individualPerformance" 
+              placeholder="输入个人成绩" 
+              type="number"
+              step="0.01"
+              @input="handleIndividualPerformanceInput"
+              @blur="handleIndividualPerformanceBlur" />
+          </el-form-item>
+        </template>
+        
+        <!-- 团体项目显示团队成绩字段 -->
+        <template v-else>
+          <el-form-item label="团队成绩" prop="teamPerformance">
+            <el-input 
+              v-model="form.teamPerformance" 
+              placeholder="输入团队成绩" 
+              type="number"
+              step="0.01"
+              @input="handleTeamPerformanceInput"
+              @blur="handleTeamPerformanceBlur" />
+          </el-form-item>
+        </template>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -59,15 +100,14 @@
       </template>
     </el-dialog>
 
-    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="loadAthleteScores" />
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="handlePagination({page: queryParams.pageNum, limit: queryParams.pageSize})" />
   </div>
 </template>
 
 <script setup name="GameScoreEdit" lang="ts">
 import { onMounted, ref } from 'vue';
 import { useRoute } from 'vue-router';
-import { listGameAthlete } from '@/api/system/gameAthlete/index'; 
-import { listGameScore, delGameScore, getGameScore, addGameScore, updateGameScore } from '@/api/system/gameScore/index';
+import { getProjectScoreData, updateScoreAndRecalculate } from '@/api/system/gameScore/index';
 import { GameScoreForm } from '@/api/system/gameScore/types';
 import Pagination from '@/components/Pagination/index.vue';
 
@@ -76,43 +116,19 @@ const { game_project_type } = toRefs<any>(proxy?.useDict('game_project_type'));
 
 const route = useRoute();
 const buttonLoading = ref(false);
-const athleteScores = ref<AthleteScore[]>([]);
+const rankingLoading = ref(false);
+const dataList = ref<any[]>([]);
 const loading = ref(true);
 const total = ref(0);
 
 // 定义搜索框状态变量
 const searchValue = ref('');
 
-// 定义一个简化版的类型,只包含需要的属性
-interface AthleteScore {
-  // GameAthleteVO 属性
-  athleteId?: string | number;
-  userId?: string | number;
-  eventId?: string | number;
-  teamId?: string | number;
-  athleteCode?: string;
-  name?: string;
-  unit?: string;
-  groupType?: string;
-  number?: string;
-  // GameEventProjectVO 属性 (排除scoreValue)
-  projectId?: string | number;
-  projectName?: string;
-  projectType?: string;
-  startTime?: string;
-  endTime?: string;
-  // GameScoreVO 属性
-  scoreId?: string | number;
-  scoreValue?: number; 
-  scoreRank?: number;
-  scorePoint?: number;
-  updateTime?: string;
-}
-
 const eventId = route.params.eventId as string;
 const projectId = route.params.projectId as string;
 const projectName = route.params.projectName as string;
 const projectType = route.params.projectType as string;
+const projectClassification = route.params.classification as string || '0'; // 默认为个人项目
 
 const queryParams = reactive({
   pageNum: 1,
@@ -121,13 +137,42 @@ const queryParams = reactive({
   projectId: projectId,
   projectName: projectName,
   projectType: projectType,
+  classification: projectClassification,
 });
 
 //刷新数据方法
 const refreshData = () => {
   searchValue.value = '';
-  loadAthleteScores();
+  loadData(true); // 刷新时自动计算排名
 };
+
+// 计算排名方法
+const calculateRankings = async () => {
+  try {
+    rankingLoading.value = true;
+    
+    // 调用后端接口重新计算排名和积分
+    await updateScoreAndRecalculate({
+      eventId: eventId,
+      projectId: projectId,
+      scoreType: projectClassification === '0' ? 'individual' : 'team',
+      statusFlag: '0',
+      status: '0',
+      remark: '手动触发排名计算'
+    } as GameScoreForm);
+    
+    proxy?.$modal.msgSuccess("排名计算完成");
+    
+    // 重新加载数据以显示新的排名和积分
+    await loadData(false); // 不自动计算排名,避免循环
+  } catch (error) {
+    console.error("排名计算失败:", error);
+    proxy?.$modal.msgError("排名计算失败,请稍后再试");
+  } finally {
+    rankingLoading.value = false;
+  }
+};
+
 const scoreFormRef = ref<ElFormInstance>();
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -135,150 +180,238 @@ const dialog = reactive<DialogOption>({
 });
 
 const form = reactive({
-  scorePoint: 0,
-  scoreValue: 0.0,
-  scoreRank: 0,
+  scorePoint: null,
+  individualPerformance: 0,
+  teamPerformance: 0,
+  scoreRank: null,
   scoreId: 0,
+  athleteId: 0,
+  teamId: 0,
+  eventId: '',
+  projectId: '',
+  athleteCode: '',
+  userId: 0,
+  teamName: '', // 个人项目和团体项目都使用
+  updateTime: '',
 });
 
 const rules = {
-  scorePoint: [
-    { required: true, message: "积分不能为空", trigger: "blur" }
-  ],
-  scoreValue: [
-    { required: true, message: "成绩不能为空", trigger: "blur" }
+  individualPerformance: [
+    { required: true, message: "个人成绩不能为空", trigger: "blur" },
+    { 
+      validator: (rule: any, value: any, callback: any) => {
+        if (value !== '' && value !== null && value !== undefined) {
+          const numValue = parseFloat(value);
+          if (isNaN(numValue)) {
+            callback(new Error('请输入有效的数字'));
+          } else if (numValue < 0) {
+            callback(new Error('成绩不能为负数'));
+          } else {
+            callback();
+          }
+        } else {
+          callback();
+        }
+      }, 
+      trigger: "blur" 
+    }
   ],
-  scoreRank: [
-    { required: true, message: "排名不能为空", trigger: "change" }
+  teamPerformance: [
+    { required: true, message: "团队成绩不能为空", trigger: "blur" },
+    { 
+      validator: (rule: any, value: any, callback: any) => {
+        if (value !== '' && value !== null && value !== undefined) {
+          const numValue = parseFloat(value);
+          if (isNaN(numValue)) {
+            callback(new Error('请输入有效的数字'));
+          } else if (numValue < 0) {
+            callback(new Error('成绩不能为负数'));
+          } else {
+            callback();
+          }
+        } else {
+          callback();
+        }
+      }, 
+      trigger: "blur" 
+    }
   ],
 }
 
+// 成绩输入处理函数
+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 = () => {
-  // reset();
   dialog.visible = false;
 }
+
 /** 编辑按钮 */
-const editScore = (row: AthleteScore) => {
+const editScore = (row: any) => {
   dialog.visible = true;
-  // 将当前行数据复制到编辑表单中
+  // 将当前行数据复制到编辑表单中,确保成绩数据类型正确
   Object.assign(form, {
     scoreId: row.scoreId,
-    scorePoint: row.scorePoint,
-    scoreValue: row.scoreValue,
-    scoreRank: row.scoreRank,
+    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,
-    teamId: row.teamId,
     projectId: row.projectId,
-    unit: row.unit,
+    teamName: row.teamName || '', // 统一使用teamName字段
     updateTime: row.updateTime,
   });
 };
 
 /** 提交按钮 */
-const submitForm = () => {
+const submitForm = async () => {
   scoreFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       try {
         buttonLoading.value = true;
         
-        // 根据 form 中的数据创建提交对象
-        const submitForm: GameScoreForm = {
-          ...form,
-          // updateTime: new Date().toISOString() // 在提交时设置updateTime
-        };
-
-        // 调用更新或添加接口
-        if (form.scoreId) { 
-          await updateGameScore(submitForm);
+        // 确保成绩数据类型正确
+        let individualPerformance = 0;
+        let teamPerformance = 0;
+        
+        if (projectClassification === '0') {
+          // 个人项目
+          individualPerformance = parseFloat(String(form.individualPerformance)) || 0;
+          if (isNaN(individualPerformance)) {
+            individualPerformance = 0;
+          }
         } else {
-          await addGameScore(submitForm);
+          // 团体项目
+          teamPerformance = parseFloat(String(form.teamPerformance)) || 0;
+          if (isNaN(teamPerformance)) {
+            teamPerformance = 0;
+          }
         }
-
+        
+        // 构建提交数据,确保使用正确的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);
+        
         proxy?.$modal.msgSuccess("操作成功");
         dialog.visible = false;
-        await loadAthleteScores(); // 重新加载数据以反映更改
+        loadData(false); // 提交成功后不自动计算排名,用户需要手动点击排名按钮
       } catch (error) {
+        console.error("提交失败:", error);
         proxy?.$modal.msgError("操作失败,请稍后再试");
       } finally {
         buttonLoading.value = false;
       }
     }
   });
-}
+};
 
-// 加载运动员成绩列表方法,支持搜索
-const loadAthleteScores = async () => {
+// 加载数据方法,支持搜索
+const loadData = async (autoCalculateRanking = false) => {
   loading.value = true;
-  const resAthletes = await listGameAthlete({
-    eventId: eventId,
-    pageNum: queryParams.pageNum,
-    pageSize: queryParams.pageSize,
-    // 使用name属性替代keyword进行搜索
-    name: searchValue.value,
-    orderByColumn: '',
-    isAsc: ''
-  });
-  const athletes = resAthletes.rows;
-  total.value = resAthletes.total;
-
-  const athleteScoresData: AthleteScore[] = [];
-
-  for (const athlete of athletes) {
-    try {
-      // 使用 listGameScore 替换 getGameScore
-      const resScores = await listGameScore({
-        athleteId: athlete.athleteId,
-        pageNum: queryParams.pageNum,
-        pageSize: queryParams.pageSize,
-        orderByColumn: '',
-        isAsc: ''
-      });
-      const scores = resScores.rows;
-
-      // 找到与当前 athleteId 匹配的成绩记录
-      const score = scores.find(scoreItem => scoreItem.athleteId === athlete.athleteId);
-      //如果运动员的参与项目列表中没有项目,则跳过
-      if (athlete.projectValue === null || athlete.projectValue === '') {
-        continue;
-      }
-      athleteScoresData.push({
-        ...athlete,
-        scoreId: score?.scoreId ?? 0,
-        scorePoint: score?.scorePoint ?? 0,
-        scoreValue: score?.scoreValue ?? 0.0,
-        scoreRank: score?.scoreRank ?? 0,
-        updateTime: score?.updateTime ?? '',
-        eventId: eventId,
-        projectId: projectId, // 直接使用路由传来的 projectId
-        projectName: projectName,
-        projectType: projectType,
-      });
-    } catch (error) {
-      // 如果获取成绩失败,使用默认值
-      athleteScoresData.push({
-        ...athlete,
-        scoreId: 0,
-        scorePoint: 0,
-        scoreValue: 0.0,
-        scoreRank: 0,
-        updateTime: '',
-        eventId: eventId,
-        projectId: projectId, // 直接使用路由传来的 projectId
-        projectName: projectName,
-        projectType: projectType,
-      });
+  
+  try {
+    // 调用后端接口获取综合数据
+    const response = await getProjectScoreData({
+      eventId: eventId,
+      projectId: projectId,
+      classification: projectClassification,
+      searchValue: searchValue.value,
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize
+    });
+    
+    // 处理返回的数据
+    const rows = response.rows || [];
+    
+    // 为每行数据添加项目相关信息
+    dataList.value = rows.map((row: any) => ({
+      ...row,
+      projectName: projectName,
+      projectType: projectType,
+    }));
+    
+    total.value = response.total || 0;
+    
+    // 只有在自动计算排名的情况下才进行排名计算
+    if (autoCalculateRanking) {
+      await calculateRankings();
     }
+  } catch (error) {
+    console.error("加载数据失败:", error);
+    proxy?.$modal.msgError("数据加载失败");
+    dataList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
   }
+};
 
-  athleteScores.value = athleteScoresData;
-  loading.value = false;
+// 分页组件事件处理
+const handlePagination = (paginationData: { page: number, limit: number }) => {
+  console.log('分页事件数据:', paginationData); // 添加调试日志
+  queryParams.pageNum = paginationData.page;
+  queryParams.pageSize = paginationData.limit;
+  console.log('更新后的查询参数:', queryParams); // 添加调试日志
+  loadData(false); // 分页加载不自动计算排名
 };
 
 onMounted(() => {
-  loadAthleteScores();
+  loadData(true); // 页面初始化时自动计算排名
 });
 </script>

+ 22 - 11
src/views/system/gameScore/index.vue

@@ -69,14 +69,22 @@
       <el-table v-loading="loading" border :data="projectList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="ID" align="center" prop="projectId" v-if="columns[0].visible" />
-        <el-table-column label="项目" align="center" prop="projectName" v-if="columns[1].visible" />
-        <!-- <el-table-column label="分组" align="center" prop="groupType" v-if="columns[2].visible" /> -->
         <el-table-column label="项目类型" align="center" prop="projectType" v-if="columns[2].visible" >
           <template #default="scope">
             <dict-tag :options="game_project_type" :value="scope.row.projectType" />
           </template>
         </el-table-column>
-        <el-table-column label="状态" align="center" prop="status" v-if="columns[3].visible">
+        <el-table-column label="归类" align="center" prop="classification" v-if="columns[3].visible" >
+          <template #default="scope">
+            <el-tag :type="scope.row.classification === '0' ? 'success' : 'warning'">
+              {{ scope.row.classification === '0' ? '个人项目' : '团体项目' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="项目" align="center" prop="projectName" v-if="columns[1].visible" />
+        <!-- <el-table-column label="分组" align="center" prop="groupType" v-if="columns[2].visible" /> -->
+        
+        <el-table-column label="状态" align="center" prop="status" v-if="columns[4].visible">
           <template #default="scope">
             <el-select v-model="scope.row.status" placeholder="请选择状态">
               <el-option label="等待处理" value="0"></el-option>
@@ -84,8 +92,8 @@
             </el-select>
           </template>
         </el-table-column>
-        <el-table-column label="比赛时间" align="center" prop="startTime" v-if="columns[4].visible" />
-        <el-table-column label="更新时间" align="center" prop="updateTime" v-if="columns[5].visible" />
+        <el-table-column label="比赛时间" align="center" prop="startTime" v-if="columns[5].visible" />
+        <el-table-column label="更新时间" align="center" prop="updateTime" v-if="columns[6].visible" />
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="查看成绩详情" placement="top">
@@ -128,10 +136,11 @@ const total = ref(0);
 const columns = ref<FieldOption[]>([
   { key: 0, label: 'ID', visible: false },
   { key: 1, label: '项目', visible: true },
-  { key: 2, label: '分组', visible: true },
-  { key: 3, label: '状态', visible: true },
-  { key: 4, label: '比赛时间', visible: true },
-  { key: 5, label: '更新时间', visible: true },
+  { key: 2, label: '项目类型', visible: true },
+  { key: 3, label: '归类', visible: true },
+  { key: 4, label: '状态', visible: true },
+  { key: 5, label: '比赛时间', visible: true },
+  { key: 6, label: '更新时间', visible: true },
 ]);
 
 // 下拉框数据
@@ -145,7 +154,8 @@ const initFormData: GameScoreForm = {
   projectId: undefined,
   athleteId: undefined,
   teamId: undefined,
-  scoreValue: undefined,
+  individualPerformance: undefined,
+  teamPerformance: undefined,
   scoreType: undefined,
   scoreRank: undefined,
   scorePoint: undefined,
@@ -314,9 +324,10 @@ const navigateToEditPage = (row: GameEventProjectVO) => {
   const projectName = row.projectName;
   const projectType = row.projectType;
   const eventId = row.eventId;
+  const classification = row.classification;
   router.push({
     name: 'GameScoreEdit',
-    params: { projectId,projectName,projectType,eventId}
+    params: { projectId, projectName, projectType, eventId, classification }
   });
 };