27 Commits df65e7fccd ... f48d535bf2

Author SHA1 Message Date
  wenkai f48d535bf2 feat:10:快捷报名 11:报名咨询 2 months ago
  zhou 708e9e207a refactor(GameNavigator): 更新日期时间格式和组件逻辑 2 months ago
  zhou e9db5f3406 refactor(system): 重构游戏分数模块 2 months ago
  zhou a831b3538b feat(game-team):修改刷新队伍bug 2 months ago
  zhou 731dbe2f2f 分组bug修改 3 months ago
  zhou 1810349f9f feat(game-event): 增加赛事项目库功能 3 months ago
  zhou 12e571b2d7 feat(api/system/gameScore): 添加加分数据相关API和界面 3 months ago
  zhou 3984be7948 Merge branch 'dev_zlt' into dev 3 months ago
  zhou 924c467e5a refactor(system): 优化运动员参与项目的数据处理 3 months ago
  zhou 6ad93784b6 refactor(system): 重构裁判相关功能 3 months ago
  zhou a37c033ecb feat(config): 新增 API 配置管理模块 3 months ago
  zhou c0be9933fb 修1 3 months ago
  zhou 606da4178c Merge branch 'dev_zlt' into dev 3 months ago
  zhou 51a7d2d159 清除赛事菜单默认打开修改的其他设置 3 months ago
  zhou cc9936827e refactor(layout): 优化侧边栏并添加测试组件 3 months ago
  zhou 9d81dccaf3 refactor(layout): 调整侧边栏和菜单默认打开状态 3 months ago
  zhou 35534b739d feat(layout): 添加菜单展开状态持久化功能 3 months ago
  zhou a86c6b9712 Merge branch 'dev_zlt' into dev 3 months ago
  zhou eef17ca7e0 fix(system): 修复添加裁判功能并优化项目显示 3 months ago
  zhou a073dba0ad fix(system): 修复新增赛事时出现的错误 3 months ago
  zhou 697eb9cdcf refactor(system): 优化游戏导航功能 3 months ago
  zhou cf83514dc8 Merge branch 'dev' 3 months ago
  wenkai d0abff6b5b Merge branch 'wk-dev' into dev 3 months ago
  zhou d8179afe53 refactor(system): 重构分组页面逻辑 3 months ago
  zhou c707c3b9e8 feat(system): 增加项目进度展示功能 3 months ago
  wenkai 1fc8034afe Merge branch 'dev' 3 months ago
  wenkai 1d3dc5ef7e 线上 3 months ago
53 changed files with 2261 additions and 843 deletions
  1. 1 1
      .env.production
  2. 3 3
      package.json
  3. 11 2
      src/App.vue
  4. 3 4
      src/api/system/common/nav/gameNavigator.ts
  5. 3 3
      src/api/system/gameAthlete/types.ts
  6. 10 0
      src/api/system/gameEvent/projectProgress.ts
  7. 42 0
      src/api/system/gameEvent/types.ts
  8. 22 0
      src/api/system/gameEventGroup/index.ts
  9. 1 1
      src/api/system/gameEventGroup/types.ts
  10. 25 0
      src/api/system/gameEventProject/index.ts
  11. 18 12
      src/api/system/gameEventProject/types.ts
  12. 13 1
      src/api/system/gameReferee/index.ts
  13. 2 2
      src/api/system/gameReferee/types.ts
  14. 41 0
      src/api/system/gameScore/index.ts
  15. 8 8
      src/api/system/gameScore/types.ts
  16. 2 1
      src/components/Editor/index.vue
  17. 12 2
      src/components/FileUpload/index.vue
  18. 2 1
      src/components/ImageOrUrlInput/index.vue
  19. 2 1
      src/components/ImageUpload/index.vue
  20. 2 1
      src/components/ImageUploadCropper/index.vue
  21. 127 0
      src/config/api.ts
  22. 14 10
      src/layout/components/Navbar.vue
  23. 6 0
      src/layout/components/Sidebar/index.vue
  24. 1 1
      src/router/index.ts
  25. 20 1
      src/store/modules/app.ts
  26. 2 1
      src/utils/request.ts
  27. 11 1
      src/utils/theme.ts
  28. 3 1
      src/views/system/advice/index.vue
  29. 1 1
      src/views/system/common/file/info/index.vue
  30. 80 120
      src/views/system/common/nav/components/GameNavigator.vue
  31. 48 37
      src/views/system/gameAthlete/index.vue
  32. 225 25
      src/views/system/gameEvent/RankingBoardPage.vue
  33. 40 17
      src/views/system/gameEvent/RefereeForm.vue
  34. 6 5
      src/views/system/gameEvent/athlete.vue
  35. 5 5
      src/views/system/gameEvent/detail.vue
  36. 105 30
      src/views/system/gameEvent/edit.vue
  37. 82 27
      src/views/system/gameEvent/index.vue
  38. 4 4
      src/views/system/gameEventConfig/index.vue
  39. 4 4
      src/views/system/gameEventConfigType/index.vue
  40. 105 225
      src/views/system/gameEventGroup/detail.vue
  41. 222 43
      src/views/system/gameEventGroup/index.vue
  42. 2 2
      src/views/system/gameEventMenu/index.vue
  43. 272 0
      src/views/system/gameEventProject/ProjectLibraryDialog.vue
  44. 2 2
      src/views/system/gameEventProject/RefereeGroupDialog.vue
  45. 115 70
      src/views/system/gameEventProject/index.vue
  46. 4 4
      src/views/system/gameEventSchedule/index.vue
  47. 32 113
      src/views/system/gameReferee/index.vue
  48. 261 0
      src/views/system/gameScore/gameScoreBonus.vue
  49. 2 4
      src/views/system/gameScore/gameScoreEdit.vue
  50. 218 31
      src/views/system/gameScore/index.vue
  51. 7 7
      src/views/system/gameScore/print.vue
  52. 6 7
      src/views/system/gameTeam/index.vue
  53. 6 2
      tsconfig.json

+ 1 - 1
.env.production

@@ -14,7 +14,7 @@ VITE_APP_MONITOR_ADMIN = '/admin/applications'
 VITE_APP_SNAILJOB_ADMIN = '/snail-job'
 
 # 生产环境
-VITE_APP_BASE_API = '/prod-api'
+VITE_APP_BASE_API = 'http://meet2.sportsrobo.club:8080'
 
 # 是否在打包时开启压缩,支持 gzip 和 brotli
 VITE_BUILD_COMPRESS = gzip

+ 3 - 3
package.json

@@ -22,7 +22,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "2.3.1",
     "@highlightjs/vue-plugin": "2.1.0",
-    "@types/qrcode": "^1.5.5",
+    
     "@vueup/vue-quill": "1.2.0",
     "@vueuse/core": "13.1.0",
     "animate.css": "4.1.1",
@@ -38,14 +38,14 @@
     "jsencrypt": "3.3.2",
     "nprogress": "0.2.0",
     "pinia": "3.0.2",
-    "qrcode": "^1.5.4",
+    
     "screenfull": "6.0.2",
     "vue": "3.5.13",
     "vue-cropper": "1.1.1",
     "vue-cropperjs": "^5.0.0",
     "vue-i18n": "11.1.3",
     "vue-json-pretty": "2.4.0",
-    "vue-qr": "^4.0.9",
+    
     "vue-router": "4.5.0",
     "vue-types": "6.0.0",
     "vxe-table": "4.13.7"

+ 11 - 2
src/App.vue

@@ -13,8 +13,17 @@ const appStore = useAppStore();
 
 onMounted(() => {
   nextTick(() => {
-    // 初始化主题样式
-    handleThemeStyle(useSettingsStore().theme);
+    try {
+      // 初始化主题样式,添加错误处理
+      const settingsStore = useSettingsStore();
+      const theme = settingsStore.theme;
+      console.log('初始化主题:', theme);
+      handleThemeStyle(theme);
+    } catch (error) {
+      console.warn('主题初始化失败,使用默认主题:', error);
+      // 使用默认主题色
+      handleThemeStyle('#409EFF');
+    }
   });
 });
 </script>

+ 3 - 4
src/api/system/common/nav/gameNavigator.ts

@@ -1,6 +1,5 @@
 import request from '@/utils/request';
 import type { ApiResult, PageQuery } from '../sys/types';
-import { GameEventMenuVO } from "@/api/system/gameEventMenu/types";
 
 // ============================ 主导航 ============================
 export interface GameNavigatorVo {
@@ -98,7 +97,7 @@ export function updateNavigator(data: GameNavigatorBo) {
     url: '/system/scenic/navigator',
     method: 'put',
     data: data
-  });
+  })
 }
 
 // 删除底部主导航
@@ -106,7 +105,7 @@ export function delNavigator(navId: number | Array<number>) {
   return request<ApiResult<void>>({
     url: `/system/scenic/navigator/${navId}`,
     method: 'delete'
-  });
+  })
 }
 
 // 导出底部主导航
@@ -115,7 +114,7 @@ export function exportNavigator(query: GameNavigatorBo) {
     url: '/system/scenic/navigator/export',
     method: 'post',
     data: query
-  });
+  })
 }
 
 // 金刚区(type=4)固定类型数据

+ 3 - 3
src/api/system/gameAthlete/types.ts

@@ -73,7 +73,7 @@ export interface GameAthleteVO {
    * 参与项目列表
    */
   projectValue: string;
-  projectList: String[];
+  projectList: number[];
 
   /**
    * 状态(0正常 1停用)
@@ -166,12 +166,12 @@ export interface GameAthleteForm extends BaseEntity {
    * 参与项目列表
    */
   projectValue?: string;
-  projectList: String[];
+  projectList: number[];
 
   /**
    * 选中的项目列表(用于穿梭框)
    */
-  selectedProjects?: string[];
+  selectedProjects?: number[];
 
   /**
    * 状态(0正常 1停用)

+ 10 - 0
src/api/system/gameEvent/projectProgress.ts

@@ -0,0 +1,10 @@
+import request from '@/utils/request';
+import { ProjectProgressVo } from './types';
+
+// 获取赛事项目进度信息
+export function getProjectProgress(eventId: string | number) {
+  return request({
+    url: `/system/gameEvent/projectProgress/${eventId}`,
+    method: 'get'
+  });
+} 

+ 42 - 0
src/api/system/gameEvent/types.ts

@@ -240,3 +240,45 @@ export interface GameEventQuery extends PageQuery {
    */
   params?: any;
 }
+
+// 项目进度视图对象
+export interface ProjectProgressVo {
+  /** 项目ID */
+  projectId: number;
+  /** 项目名称 */
+  projectName: string;
+  /** 项目类型 */
+  projectType: string;
+  /** 归类(0个人项目/1团体项目) */
+  classification: string;
+  /** 比赛场地 */
+  location: string;
+  /** 项目开始时间 */
+  startTime: string;
+  /** 项目结束时间 */
+  endTime: string;
+  /** 状态(0未开始 1进行中 2已完成) */
+  status: string;
+  /** 状态描述 */
+  statusText: string;
+  /** 组别信息列表 */
+  groups: GroupProgressVo[];
+  /** 项目进度百分比 */
+  progressPercentage: number;
+}
+
+// 组别进度视图对象
+export interface GroupProgressVo {
+  /** 组别ID */
+  groupId: number;
+  /** 组别名称 */
+  groupName: string;
+  /** 组别开始时间 */
+  beginTime: string;
+  /** 组别结束时间 */
+  endTime: string;
+  /** 状态(0未开始 1进行中 2已完成) */
+  status: string;
+  /** 状态描述 */
+  statusText: string;
+}

+ 22 - 0
src/api/system/gameEventGroup/index.ts

@@ -61,3 +61,25 @@ export const delGameEventGroup = (groupId: string | number | Array<string | numb
     method: 'delete'
   });
 };
+
+/**
+ * 生成分组结果
+ * @param groupId
+ */
+export const generateGroups = (groupId: string | number): AxiosPromise<any> => {
+  return request({
+    url: '/system/gameEventGroup/generateGroups/' + groupId,
+    method: 'get'
+  });
+};
+
+/**
+ * 从数据库获取分组结果
+ * @param groupId
+ */
+export const getGroupResultFromDB = (groupId: string | number): AxiosPromise<any> => {
+  return request({
+    url: '/system/gameEventGroup/getGroupResultFromDB/' + groupId,
+    method: 'get'
+  });
+};

+ 1 - 1
src/api/system/gameEventGroup/types.ts

@@ -111,7 +111,7 @@ export interface GameEventGroupForm extends BaseEntity {
   /**
    * 选中的项目列表(用于穿梭框)
    */
-  selectedProjects?: string[];
+  selectedProjects?: number[];
 
   /**
    * 成员性别(0不限1男2女)

+ 25 - 0
src/api/system/gameEventProject/index.ts

@@ -16,6 +16,19 @@ export const listGameEventProject = (query?: GameEventProjectQuery): AxiosPromis
   });
 };
 
+/**
+ * 查询所有赛事的项目列表(用于项目库)
+ * @param query
+ * @returns {*}
+ */
+export const listAllGameEventProject = (query?: GameEventProjectQuery): AxiosPromise<GameEventProjectVO[]> => {
+  return request({
+    url: '/system/gameEventProject/listAll',
+    method: 'get',
+    params: query
+  });
+};
+
 /**
  * 查询赛事项目详细
  * @param projectId
@@ -39,6 +52,18 @@ export const addGameEventProject = (data: GameEventProjectForm) => {
   });
 };
 
+/**
+ * 批量新增赛事项目
+ * @param data
+ */
+export const BatchAddProject = (data: GameEventProjectVO[]) => {
+  return request({
+    url: '/system/gameEventProject/BatchAdd',
+    method: 'post',
+    data: data
+  });
+};
+
 /**
  * 修改赛事项目
  * @param data

+ 18 - 12
src/api/system/gameEventProject/types.ts

@@ -4,6 +4,12 @@ export interface GameEventProjectVO {
    * 赛事ID
    */
   eventId: string | number;
+
+  /**
+   * 赛事名称
+   */
+  eventName: string;
+
   /**
    * 项目ID
    */
@@ -26,7 +32,7 @@ export interface GameEventProjectVO {
   /**
    * 裁判组员
    */
-  refereeGroups: String[];
+  refereeGroups: number[];
   /**
    * 比赛场地
    */
@@ -58,7 +64,7 @@ export interface GameEventProjectVO {
   participateNum: number;
 
   /**
-   * 
+   * 录取名
    */
   roundType: string;
 
@@ -83,14 +89,14 @@ export interface GameEventProjectVO {
   award: string;
 
   /**
-   * 计时点名称
+   * 比赛轮次
    */
-  timePoint: string;
+  gameRound: string;
 
   /**
-   * 控制盒编号
+   * 比赛阶段
    */
-  boxCode: string;
+  gameStage: string;
 
   /**
    * 状态(0正常 1停用)
@@ -132,7 +138,7 @@ export interface GameEventProjectForm extends BaseEntity {
   /**
    * 裁判组员
    */
-  refereeGroups?: String[];
+  refereeGroups?: number[];
 
   /**
    * 比赛场地
@@ -165,7 +171,7 @@ export interface GameEventProjectForm extends BaseEntity {
   participateNum?: number;
 
   /**
-   * 
+   * 录取名
    */
   roundType?: string;
 
@@ -190,14 +196,14 @@ export interface GameEventProjectForm extends BaseEntity {
   award?: string;
 
   /**
-   * 计时点名称
+   * 比赛轮次
    */
-  timePoint?: string;
+  gameRound?: string;
 
   /**
-   * 控制盒编号
+   * 比赛阶段
    */
-  boxCode?: string;
+  gameStage?: string;
 
   /**
    * 状态(0正常 1停用)

+ 13 - 1
src/api/system/gameReferee/index.ts

@@ -1,4 +1,4 @@
-import request from '@/utils/request';
+import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { GameRefereeVO, GameRefereeForm, GameRefereeQuery } from '@/api/system/gameReferee/types';
 
@@ -72,3 +72,15 @@ export const getRefereeCount = () => {
     method: 'get'
   });
 };
+
+/**
+ * 生成裁判二维码
+ * @param refereeId 裁判ID
+ * @returns {*}
+ */
+export const generateRefereeQRCode = (refereeId: string | number): AxiosPromise<string> => {
+  return request({
+    url: '/system/gameReferee/qrcode/' + refereeId,
+    method: 'get'
+  });
+};

+ 2 - 2
src/api/system/gameReferee/types.ts

@@ -33,7 +33,7 @@ export interface GameRefereeVO {
    * 负责的项目
    */
   projectList: string;
-  projectList2: string[];
+  projectList2: number[];
 
   /**
    * 裁判码
@@ -87,7 +87,7 @@ export interface GameRefereeForm extends BaseEntity {
    * 负责的项目
    */
   projectList?: string;
-  projectList2?: string[];
+  projectList2?: number[];
 
   /**
    * 裁判码

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

@@ -146,3 +146,44 @@ export const exportScoresSummary = (eventId: string | number) => {
     responseType: 'blob' // 确保以二进制流形式接收响应
   });
 };
+
+/**
+ * 获取加分数据
+ * @param params 查询参数
+ */
+export const getBonusData = (params: { eventId: string | number }) => {
+  return request({
+    url: '/system/gameScore/getBonusData',
+    method: 'get',
+    params
+  });
+};
+
+/**
+ * 更新加分数据
+ * @param data 加分数据
+ */
+export const updateBonusData = (data: { eventId: string | number; data: any[] }) => {
+  return request({
+    url: '/system/gameScore/updateBonusData',
+    method: 'put',
+    data
+  });
+};
+
+/**
+ * 导出加分Excel
+ * @param data 导出数据
+ */
+export const exportBonusExcel = (data: { 
+  eventId: string | number; 
+  data: any[]; 
+  projects: any[] 
+}) => {
+  return request({
+    url: '/system/gameScore/exportBonusExcel',
+    method: 'post',
+    data,
+    responseType: 'blob' // 确保以二进制流形式接收响应
+  });
+};

+ 8 - 8
src/api/system/gameScore/types.ts

@@ -54,14 +54,14 @@ export interface GameScoreVO {
   award: string;
 
   /**
-   * 附加成绩1
+   * 领导加分
    */
-  extraScore1: string;
+  leaderPoint: number;
 
   /**
-   * 附加成绩2
+   * 额外加分
    */
-  extraScore2: string;
+  extraPoint: number;
 
   /**
    * 成绩状态(0等待处理1处理完毕)
@@ -147,14 +147,14 @@ export interface GameScoreForm extends BaseEntity {
   award?: string;
 
   /**
-   * 附加成绩1
+   * 领导加分
    */
-  extraScore1?: string;
+  leaderPoint?: number;
 
   /**
-   * 附加成绩2
+   * 额外加分
    */
-  extraScore2?: string;
+  extraPoint?: number;
 
   /**
    * 成绩状态(0等待处理1处理完毕)

+ 2 - 1
src/components/Editor/index.vue

@@ -32,6 +32,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
 import { QuillEditor, Quill } from '@vueup/vue-quill';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
+import { getUploadUrl } from '@/config/api';
 
 defineEmits(['update:modelValue']);
 
@@ -54,7 +55,7 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const upload = reactive<UploadOption>({
   headers: globalHeaders(),
-  url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
+  url: getUploadUrl('oss')
 });
 const quillEditorRef = ref();
 const uploadRef = ref<HTMLDivElement>();

+ 12 - 2
src/components/FileUpload/index.vue

@@ -48,6 +48,7 @@
 import { propTypes } from '@/utils/propTypes';
 import { delOss, listByIds } from '@/api/system/oss';
 import { globalHeaders } from '@/utils/request';
+import { getUploadUrl } from '@/config/api';
 
 const props = defineProps({
   modelValue: {
@@ -72,7 +73,7 @@ const number = ref(0);
 const uploadList = ref<any[]>([]);
 
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // 上传文件服务器地址
+const uploadFileUrl = ref(getUploadUrl('oss')); // 上传文件服务器地址
 const headers = ref(globalHeaders());
 
 const fileList = ref<any[]>([]);
@@ -92,7 +93,16 @@ watch(
       let list: any[] = [];
       if (Array.isArray(val)) {
         list = val;
-      } else {
+      } else if (typeof val === 'string') {
+        const res = await listByIds(val);
+        list = res.data.map((oss) => {
+          return {
+            name: oss.originalName,
+            url: oss.url,
+            ossId: oss.ossId
+          };
+        });
+      } else if (typeof val === 'number') {
         const res = await listByIds(val);
         list = res.data.map((oss) => {
           return {

+ 2 - 1
src/components/ImageOrUrlInput/index.vue

@@ -61,6 +61,7 @@ import { listByIds, delOss } from '@/api/system/oss';
 import { OssVO } from '@/api/system/oss/types';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
+import { getUploadUrl } from '@/config/api';
 import { compressAccurately } from 'image-conversion';
 import { Plus } from '@element-plus/icons-vue';
 
@@ -99,7 +100,7 @@ const urlValue = ref('');
 const isValidUrl = ref(false);
 
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadImgUrl = ref(baseUrl + '/resource/oss/upload');
+const uploadImgUrl = ref(getUploadUrl('oss'));
 const headers = ref(globalHeaders());
 
 const fileList = ref<any[]>([]);

+ 2 - 1
src/components/ImageUpload/index.vue

@@ -45,6 +45,7 @@ import { listByIds, delOss } from '@/api/system/oss';
 import { OssVO } from '@/api/system/oss/types';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
+import { getUploadUrl } from '@/config/api';
 import { compressAccurately } from 'image-conversion';
 
 const props = defineProps({
@@ -80,7 +81,7 @@ const dialogImageUrl = ref('');
 const dialogVisible = ref(false);
 
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
+const uploadImgUrl = ref(getUploadUrl('oss')); // 上传的图片服务器地址
 const headers = ref(globalHeaders());
 
 const fileList = ref<any[]>([]);

+ 2 - 1
src/components/ImageUploadCropper/index.vue

@@ -138,6 +138,7 @@ import { listByIds, delOss } from '@/api/system/oss';
 import { OssVO } from '@/api/system/oss/types';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
+import { getUploadUrl } from '@/config/api';
 import { compressAccurately } from 'image-conversion';
 import { UploadRawFile } from 'element-plus';
 
@@ -200,7 +201,7 @@ const dialogVisible = ref(false);
 const cropperVisible = ref(false);
 
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // 上传的图片服务器地址
+const uploadImgUrl = ref(getUploadUrl('oss')); // 上传的图片服务器地址
 const headers = ref(globalHeaders());
 
 const fileList = ref<any[]>([]);

+ 127 - 0
src/config/api.ts

@@ -0,0 +1,127 @@
+/**
+ * API配置管理
+ * 统一管理所有API相关的URL配置,避免在多个地方重复修改
+ */
+
+// 环境配置
+const isDev = import.meta.env.DEV;
+const isProd = import.meta.env.PROD;
+
+// 基础API地址配置
+const API_CONFIG = {
+  // 开发环境
+  development: import.meta.env.VITE_APP_BASE_API,
+  // 生产环境
+  production: 'http://meet2.sportsrobo.club:8080',
+  // 测试环境
+  test: 'http://192.168.1.126:8080',
+  // 本地环境
+  local: 'http://localhost:8080'
+};
+
+// 获取当前环境的API基础地址
+export const getBaseURL = (): string => {
+  // 优先使用环境变量中的配置
+  if (import.meta.env.VITE_APP_BASE_API) {
+    return import.meta.env.VITE_APP_BASE_API;
+  }
+  
+  // 根据环境自动选择
+  if (isDev) {
+    return API_CONFIG.development;
+  } else if (isProd) {
+    return API_CONFIG.production;
+  }
+  
+  // 默认返回开发环境配置
+  return API_CONFIG.development;
+};
+
+// 导出基础URL
+export const BASE_URL = getBaseURL();
+
+// 常用API端点
+export const API_ENDPOINTS = {
+  // 文件上传相关
+  UPLOAD: '/resource/oss/upload',
+  UPLOAD_COMMON: '/common/upload',
+  DOWNLOAD: '/resource/oss/download',
+  
+  // 系统相关
+  USER_IMPORT: '/system/user/importData',
+  GAME_TEAM_IMPORT: '/system/gameTeam/import',
+  GAME_ATHLETE_IMPORT: '/system/gameAthlete/import',
+  ENROLL_IMPORT: '/system/enroll/importData',
+  
+  // WebSocket和SSE
+  WEBSOCKET: '/resource/websocket',
+  SSE: '/resource/sse',
+  
+  // 工作流
+  WORKFLOW_UI: '/warm-flow-ui/index.html'
+} as const;
+
+// 获取完整的API URL
+export const getApiUrl = (endpoint: string): string => {
+  return `${BASE_URL}${endpoint}`;
+};
+
+// 获取上传URL
+export const getUploadUrl = (type: 'oss' | 'common' = 'oss'): string => {
+  const endpoint = type === 'oss' ? API_ENDPOINTS.UPLOAD : API_ENDPOINTS.UPLOAD_COMMON;
+  return getApiUrl(endpoint);
+};
+
+// 获取下载URL
+export const getDownloadUrl = (filePath: string): string => {
+  return getApiUrl(`${API_ENDPOINTS.DOWNLOAD}/${filePath}`);
+};
+
+// 获取文件完整URL
+export const getFileUrl = (filePath: string): string => {
+  if (filePath.startsWith('http')) {
+    return filePath;
+  }
+  return `${BASE_URL}${filePath}`;
+};
+
+// 获取工作流URL
+export const getWorkflowUrl = (id: string, disabled: boolean = false, type: string = 'FlowChart'): string => {
+  const params = new URLSearchParams({
+    id,
+    disabled: disabled.toString(),
+    type,
+    t: Date.now().toString()
+  });
+  return getApiUrl(`${API_ENDPOINTS.WORKFLOW_UI}?${params.toString()}`);
+};
+
+// 导出配置对象,方便调试
+export const API_CONFIG_DEBUG = {
+  currentEnv: isDev ? 'development' : 'production',
+  baseURL: BASE_URL,
+  endpoints: API_ENDPOINTS,
+  isDev,
+  isProd,
+  envVars: {
+    VITE_APP_BASE_API: import.meta.env.VITE_APP_BASE_API,
+    VITE_APP_CLIENT_ID: import.meta.env.VITE_APP_CLIENT_ID
+  }
+};
+
+// 在开发环境下打印环境信息
+if (isDev) {
+  console.log(' API配置信息:', API_CONFIG_DEBUG);
+  console.log(' 当前使用的API地址:', BASE_URL);
+}
+
+// 手动切换环境的函数(仅用于调试)
+export const switchEnvironment = (env: 'development' | 'production' | 'test' | 'local') => {
+  if (isDev) {
+    console.warn(' 手动切换环境仅用于调试,实际部署时请使用环境变量');
+    const newBaseURL = API_CONFIG[env];
+    console.log(` 切换到 ${env} 环境:`, newBaseURL);
+    return newBaseURL;
+  }
+  return BASE_URL;
+};

+ 14 - 10
src/layout/components/Navbar.vue

@@ -32,11 +32,11 @@
         </el-select>
 
         <search-menu ref="searchMenuRef" />
-<!--        <el-tooltip content="搜索" effect="dark" placement="bottom">-->
-<!--          <div class="right-menu-item hover-effect" @click="openSearchMenu">-->
-<!--            <svg-icon class-name="search-icon" icon-class="search" />-->
-<!--          </div>-->
-<!--        </el-tooltip>-->
+        <el-tooltip content="搜索" effect="dark" placement="bottom">
+          <div class="right-menu-item hover-effect" @click="openSearchMenu">
+            <svg-icon class-name="search-icon" icon-class="search" />
+          </div>
+        </el-tooltip>
         <!-- 消息 -->
         <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
           <div>
@@ -52,9 +52,13 @@
             </el-popover>
           </div>
         </el-tooltip>
-<!--        <el-tooltip :content="proxy.$t('navbar.document')" effect="dark" placement="bottom">-->
-<!--          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />-->
-<!--        </el-tooltip>-->
+        <el-tooltip content="Github" effect="dark" placement="bottom">
+          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
+        </el-tooltip>
+
+        <el-tooltip :content="proxy.$t('navbar.document')" effect="dark" placement="bottom">
+          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
+        </el-tooltip>
 
         <el-tooltip :content="proxy.$t('navbar.full')" effect="dark" placement="bottom">
           <screenfull id="screenfull" class="right-menu-item hover-effect" />
@@ -270,9 +274,9 @@ watch(
     gap: 8px;
     padding: 0 16px;
     height: 100%;
-    background: rgba(64, 158, 255, 0.1);
+    background: rgba(248, 249, 250, 0.1);
     border-radius: 4px;
-    border: 1px solid rgba(64, 158, 255, 0.2);
+    border: 1px solid rgba(243, 245, 247, 0.2);
 
     .event-icon {
       font-size: 16px;

+ 6 - 0
src/layout/components/Sidebar/index.vue

@@ -5,6 +5,7 @@
       <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
         <el-menu
           :default-active="activeMenu"
+          :default-opened="defaultOpenedMenus"
           :collapse="isCollapse"
           :background-color="bgColor"
           :text-color="textColor"
@@ -50,6 +51,11 @@ const activeMenu = computed(() => {
   return path;
 });
 
+// 获取默认打开的菜单(从 store 中获取持久化状态)
+const defaultOpenedMenus = computed(() => {
+  return appStore.getDefaultOpenedMenus();
+});
+
 const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground));
 const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor));
 </script>

+ 1 - 1
src/router/index.ts

@@ -1,4 +1,4 @@
-import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router';
+import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router';
 /* Layout */
 import Layout from '@/layout/index.vue';
 

+ 20 - 1
src/store/modules/app.ts

@@ -6,6 +6,9 @@ import { ref, reactive, computed } from 'vue';
 
 export const useAppStore = defineStore('app', () => {
   const sidebarStatus = useStorage('sidebarStatus', '1');
+  // 添加菜单打开状态的持久化存储
+  const menuOpenedStatus = useStorage('menuOpenedStatus', JSON.stringify(['/game']));
+
   const sidebar = reactive({
     opened: sidebarStatus.value ? !!+sidebarStatus.value : true,
     withoutAnimation: false,
@@ -57,6 +60,20 @@ export const useAppStore = defineStore('app', () => {
     language.value = val;
   };
 
+  // 获取默认打开的菜单
+  const getDefaultOpenedMenus = (): string[] => {
+    try {
+      return JSON.parse(menuOpenedStatus.value);
+    } catch {
+      return ['/game']; // 默认打开系统菜单
+    }
+  };
+
+  // 设置菜单打开状态
+  const setMenuOpenedStatus = (openedMenus: string[]): void => {
+    menuOpenedStatus.value = JSON.stringify(openedMenus);
+  };
+
   return {
     device,
     sidebar,
@@ -68,6 +85,8 @@ export const useAppStore = defineStore('app', () => {
     closeSideBar,
     toggleDevice,
     setSize,
-    toggleSideBarHide
+    toggleSideBarHide,
+    getDefaultOpenedMenus,
+    setMenuOpenedStatus
   };
 });

+ 2 - 1
src/utils/request.ts

@@ -11,6 +11,7 @@ import { getLanguage } from '@/lang';
 import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
 import { encrypt, decrypt } from '@/utils/jsencrypt';
 import router from '@/router';
+import { BASE_URL } from '@/config/api';
 
 const encryptHeader = 'encrypt-key';
 let downloadLoadingInstance: LoadingInstance;
@@ -27,7 +28,7 @@ axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
 axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
 // 创建 axios 实例
 const service = axios.create({
-  baseURL: import.meta.env.VITE_APP_BASE_API,
+  baseURL: BASE_URL,
   // baseURL: 'http://192.168.1.126:8080',
   // baseURL: 'http://meet2.sportsrobo.club:8080',
   // baseURL: 'http://localhost:8080',

+ 11 - 1
src/utils/theme.ts

@@ -1,5 +1,10 @@
 // 处理主题样式
-export const handleThemeStyle = (theme: string) => {
+export const handleThemeStyle = (theme: string | null | undefined) => {
+  // 添加空值检查,使用默认主题色
+  if (!theme || typeof theme !== 'string') {
+    theme = '#409EFF'; // 使用默认主题色
+  }
+  
   document.documentElement.style.setProperty('--el-color-primary', theme);
   for (let i = 1; i <= 9; i++) {
     document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`);
@@ -11,6 +16,11 @@ export const handleThemeStyle = (theme: string) => {
 
 // hex颜色转rgb颜色
 export const hexToRgb = (str: string): string[] => {
+  // 添加空值检查
+  if (!str || typeof str !== 'string') {
+    return ['0', '0', '0']; // 返回默认黑色
+  }
+  
   str = str.replace('#', '');
   const hexs = str.match(/../g);
   for (let i = 0; i < 3; i++) {

+ 3 - 1
src/views/system/advice/index.vue

@@ -111,7 +111,9 @@ const data = reactive<PageData<AdviceForm, AdviceQuery>>({
     pageSize: 10,
     name: undefined,
     content: undefined,
-    params: {}
+    params: {},
+    orderByColumn: undefined,
+    isAsc: undefined,
   },
   rules: {
     name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],

+ 1 - 1
src/views/system/common/file/info/index.vue

@@ -2489,7 +2489,7 @@ onMounted(async () => {
 
 .file-error {
   display: flex;
-  justify-content: center;
+  justify-content: space-evenly;
   align-items: center;
   width: 100%;
   height: 100%;

+ 80 - 120
src/views/system/common/nav/components/GameNavigator.vue

@@ -37,29 +37,19 @@
         <el-col :span="1.5">
           <el-button type="primary" icon="Plus" @click="handleAdd"> 添加菜单</el-button>
         </el-col>
-        <!-- <el-col :span="1.5">
-          <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
-            >修改
-          </el-button>
-        </el-col>
-        <el-col :span="1.5">
-          <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
-            >删除
-          </el-button>
-        </el-col> -->
         <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="loadNavData"></right-toolbar>
       </el-row>
     </el-card>
 
     <!-- 菜单列表 -->
     <el-card class="list-card">
-      <template #header>
-        <div class="list-header">
-          <span>菜单列表</span>
-          <span class="result-count">共找到 {{ total }} 条记录</span>
-        </div>
-      </template>
       <el-table v-loading="loading" :data="navItems" @selection-change="handleSelectionChange" style="width: 100%">
+        <template #header>
+          <div class="list-header">
+            <span>菜单列表</span>
+            <span class="result-count">共找到 {{ total }} 条记录</span>
+          </div>
+        </template>
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="序号" align="center" width="80" v-if="columns[0].visible">
           <template #default="scope">
@@ -107,7 +97,7 @@
               </div>
               <div class="create-time">
                 <span class="label">创建时间:</span>
-                <span class="value">{{ formatDate(scope.row.createTime) }}</span>
+                <span class="value">{{ formatDateTime(scope.row.createTime) }}</span>
               </div>
             </div>
           </template>
@@ -155,7 +145,7 @@
           <el-col :span="18">
             <el-form-item label="活动类型" prop="activityType">
               <el-radio-group v-model="form.activityType">
-                <el-radio v-for="dict in game_activity_type" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
+                <el-radio v-for="dict in game_activity_type" :key="dict.value" :label="Number(dict.value)">{{ dict.label }} </el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -211,7 +201,7 @@
           <el-input v-model="form.appId" placeholder="请输入对方小程序的AppID" />
         </el-form-item>
 
-        <el-row :gutter="20">
+        <!-- <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="创建时间">
               <el-date-picker
@@ -236,7 +226,7 @@
               />
             </el-form-item>
           </el-col>
-        </el-row>
+        </el-row> -->
 
         <el-form-item label="状态" prop="status">
           <el-radio-group v-model="form.status">
@@ -327,9 +317,9 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, computed } from 'vue';
+import { ref, reactive, onMounted, computed, getCurrentInstance, toRefs } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
-import { HomeFilled, Plus } from '@element-plus/icons-vue';
+import { Plus, ArrowDown } from '@element-plus/icons-vue';
 import {
   getEnabledNavigator,
   getNavigator,
@@ -340,8 +330,16 @@ import {
   type GameNavigatorBo,
   listNavigator
 } from '@/api/system/common/nav/gameNavigator';
+import { globalHeaders } from '@/utils/request';
 
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+// 类型定义
+interface FieldOption {
+  key: number;
+  label: string;
+  visible: boolean;
+}
+
+const { proxy } = getCurrentInstance() as any;
 const { game_activity_type } = toRefs<any>(proxy?.useDict('game_activity_type'));
 
 // 计算下一个排序值
@@ -399,11 +397,11 @@ const form = reactive<
   jumpPath: '',
   activityType: 1,
   appId: '',
-  sortNum: 1, // 初始值,会在使用时动态更新
+  sortNum: 1,
   status: 0,
   remark: '',
   createTime: '',
-  updateTime: ''
+  updateTime: '',
 });
 
 // 响应式数据
@@ -424,11 +422,6 @@ const queryParams = reactive({
   createTime: ''
 });
 
-// 监听查询参数变化,自动搜索(可选)
-// watch(queryParams, () => {
-//   // 如果需要实时搜索,可以在这里调用 loadNavData()
-// }, { deep: true })
-
 // 表单验证规则
 const rules = {
   name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
@@ -438,18 +431,7 @@ const rules = {
   sortNum: [{ required: true, message: '请输入排序', trigger: 'blur' }]
 };
 
-// 添加跳转链接验证函数
-function validateJumpPath(rule: any, value: string, callback: Function) {
-  if (value && !/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(value)) {
-    callback(new Error('请输入有效的URL'));
-  } else {
-    callback();
-  }
-}
-
 // 上传相关
-import { globalHeaders } from '@/utils/request';
-
 const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
 const headers = globalHeaders();
 
@@ -466,14 +448,15 @@ function formatDateTime(date: string | Date | null | undefined): string {
   if (!date) return '-';
   const d = new Date(date);
   if (isNaN(d.getTime())) return '-';
-  return d.toLocaleString('zh-CN', {
-    year: 'numeric',
-    month: '2-digit',
-    day: '2-digit',
-    hour: '2-digit',
-    minute: '2-digit',
-    second: '2-digit'
-  });
+  // 使用自定义格式确保年月日之间以-分隔
+  const year = d.getFullYear();
+  const month = String(d.getMonth() + 1).padStart(2, '0');
+  const day = String(d.getDate()).padStart(2, '0');
+  const hours = String(d.getHours()).padStart(2, '0');
+  const minutes = String(d.getMinutes()).padStart(2, '0');
+  const seconds = String(d.getSeconds()).padStart(2, '0');
+  
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
 }
 
 // 格式化日期为查询格式 (YYYY-MM-DD)
@@ -486,7 +469,7 @@ function formatDateForQuery(date: Date): string {
 
 // 获取跳转类型文本
 function getJumpTypeText(jumpType: number | null | undefined): string {
-  if (!jumpType) return '-';
+  if (!jumpType) return '-'
   const typeMap: Record<number, string> = {
     1: '跳转链接',
     2: '不跳转',
@@ -496,22 +479,22 @@ function getJumpTypeText(jumpType: number | null | undefined): string {
     6: '公众号文章',
     7: '公众号',
     8: '电话拨号'
-  };
-  return typeMap[jumpType] || '-';
+  }
+  return typeMap[jumpType] || '-'
 }
 
 // 获取最大排序值
 function getMaxSortNum(): number {
   if (navItems.value.length === 0) {
-    return 0;
+    return 0
   }
-  const maxSort = Math.max(...navItems.value.map((item) => item.sortNum || 0));
-  return maxSort;
+  const maxSort = Math.max(...navItems.value.map(item => item.sortNum || 0))
+  return maxSort
 }
 
 // 加载菜单数据
 async function loadNavData() {
-  loading.value = true;
+  loading.value = true
   try {
     // 构建查询参数
     const query: any = {
@@ -539,10 +522,10 @@ async function loadNavData() {
     navItems.value = [];
     total.value = 0;
   } finally {
-    loading.value = false;
+    loading.value = false
     // 数据加载完成后,更新排序值
     if (!form.navId) {
-      form.sortNum = nextSortNum.value;
+      form.sortNum = nextSortNum.value
     }
   }
 }
@@ -580,7 +563,6 @@ async function handleSortChange(row: NavItem) {
     const response = await updateNavigator({
       navId: row.navId,
       sortNum: row.sortNum
-      // type: '4'
     });
     if (response.code === 200) {
       ElMessage.success('排序更新成功');
@@ -677,11 +659,11 @@ async function handleDelete(item: NavItem) {
       ElMessage.success('删除成功');
       loadNavData();
     } else {
-      ElMessage.error(response.msg || '删除失败');
+      ElMessage.error(response.msg || '删除失败')
     }
   } catch (error) {
     if (error !== 'cancel') {
-      ElMessage.error('删除失败');
+      ElMessage.error('删除失败')
     }
   }
 }
@@ -689,14 +671,14 @@ async function handleDelete(item: NavItem) {
 // 处理提交
 async function handleSubmit() {
   try {
-    await formRef.value.validate();
-    submitting.value = true;
+    await formRef.value.validate()
+    submitting.value = true
 
     const payload: any = {
       navId: form.navId,
       name: form.name,
       pic: form.pic,
-      color: normalizeColor(form.color), // 确保颜色值统一为16进制格式
+      color: form.color,
       jumpType: form.jumpType,
       jumpPath: form.jumpType === 2 ? form.jumpPath || '#' : form.jumpPath,
       activityType: form.activityType,
@@ -705,40 +687,38 @@ async function handleSubmit() {
       status: form.status,
       remark: form.remark,
       createTime: form.createTime,
-      updateTime: form.updateTime
-      // type: '4' // 菜单固定类型
-    };
+      updateTime: form.updateTime,
+    }
 
-    let response;
+    let response
     if (form.navId) {
-      response = await updateNavigator(payload);
+      response = await updateNavigator(payload)
     } else {
-      response = await addNavigator(payload);
+      response = await addNavigator(payload)
     }
 
     if (response.code === 200) {
-      ElMessage.success(form.navId ? '修改成功' : '添加成功');
-      dialogVisible.value = false;
-      loadNavData();
+      ElMessage.success(form.navId ? '修改成功' : '添加成功')
+      dialogVisible.value = false
+      loadNavData()
     } else {
-      ElMessage.error(response.msg || '操作失败');
+      ElMessage.error(response.msg || '操作失败')
     }
   } catch (error) {
-    console.error('提交失败:', error);
-    ElMessage.error('操作失败');
+    console.error('提交失败:', error)
+    ElMessage.error('操作失败')
   } finally {
-    submitting.value = false;
+    submitting.value = false
   }
 }
 
 // 重置表单
 function reset() {
-  const defaultColor = '#409EFF';
   Object.assign(form, {
     navId: undefined,
     name: '',
     pic: '',
-    color: defaultColor,
+    color: '#409EFF',
     jumpType: 1,
     jumpPath: '',
     activityType: 1,
@@ -749,67 +729,47 @@ function reset() {
     createTime: '',
     updateTime: ''
   });
-  colorInput.value = defaultColor;
+  colorInput.value = '#409EFF';
   formRef.value?.clearValidate();
 }
 
 // 跳转类型变更处理
 function handleJumpTypeChange() {
   if (form.jumpType === 2) {
-    form.jumpPath = '#';
+    form.jumpPath = '#'
   } else if (form.jumpType === 4) {
-    form.jumpPath = '';
+    form.jumpPath = ''
   }
 }
 
 // 图片上传前验证
 function beforeIconUpload(file: File) {
-  const isImage = file.type.startsWith('image/');
-  const isLt2M = file.size / 1024 / 1024 < 2;
+  const isImage = file.type.startsWith('image/')
+  const isLt2M = file.size / 1024 / 1024 < 2
 
   if (!isImage) {
-    ElMessage.error('只能上传图片文件!');
-    return false;
+    ElMessage.error('只能上传图片文件!')
+    return false
   }
   if (!isLt2M) {
-    ElMessage.error('图片大小不能超过 2MB!');
-    return false;
+    ElMessage.error('图片大小不能超过 2MB!')
+    return false
   }
-  return true;
+  return true
 }
 
 // 图片上传成功
 function handleIconSuccess(response: any) {
   if (response.code === 200) {
-    form.pic = response.data.url;
-    ElMessage.success('图片上传成功');
+    form.pic = response.data.url
+    ElMessage.success('图片上传成功')
   } else {
-    ElMessage.error(response.msg || '图片上传失败');
+    ElMessage.error(response.msg || '图片上传失败')
   }
 }
 
 // 颜色输入框的值
-const colorInput = ref('#409EFF');
-
-// 将RGB颜色转换为16进制
-const rgbToHex = (rgb: string): string => {
-  // 如果是16进制格式,直接返回
-  if (rgb.startsWith('#')) {
-    return rgb;
-  }
-
-  // 如果是RGB格式,转换为16进制
-  const rgbMatch = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
-  if (rgbMatch) {
-    const r = parseInt(rgbMatch[1]);
-    const g = parseInt(rgbMatch[2]);
-    const b = parseInt(rgbMatch[3]);
-    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
-  }
-
-  // 如果无法解析,返回默认值
-  return '#409EFF';
-};
+const colorInput = ref('#409EFF')
 
 // 将任意颜色格式统一转换为16进制
 const normalizeColor = (color: string): string => {
@@ -835,25 +795,25 @@ const normalizeColor = (color: string): string => {
 
 // 处理颜色输入框变化
 const handleColorInput = (value: string) => {
-  // 实时同步到颜色选择器和表单
+  // 实时同步到颜色选择器
   if (value.startsWith('#')) {
-    form.color = value;
+    form.color = value
   }
-};
+}
 
 // 处理颜色选择器变化
 const handleColorChange = (value: string) => {
-  // 当颜色选择器改变时,将颜色统一转换为16进制格式
+  // 当颜色选择器改变时,将16进制值填充到输入框
   if (value) {
     const hexValue = normalizeColor(value);
     form.color = hexValue;
     colorInput.value = hexValue;
   }
-};
+}
 
 // 验证颜色输入框
 const validateColorInput = () => {
-  const hexRegex = /^#[0-9A-Fa-f]{6}$/;
+  const hexRegex = /^#[0-9A-Fa-f]{6}$/
   if (!hexRegex.test(colorInput.value)) {
     ElMessage.warning('请输入正确的16进制颜色格式,如 #FF0000');
     colorInput.value = form.color;
@@ -861,7 +821,7 @@ const validateColorInput = () => {
     // 验证通过,确保表单中的颜色值也是16进制格式
     form.color = colorInput.value;
   }
-};
+}
 
 // 初始化数据
 onMounted(() => {

+ 48 - 37
src/views/system/gameAthlete/index.vue

@@ -7,8 +7,8 @@
             <el-form-item label="队伍名称" prop="teamName">
               <el-input v-model="queryParams.teamName" placeholder="请输入队伍名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="运动员编号" prop="athleteCode">
-              <el-input v-model="queryParams.athleteCode" placeholder="请输入运动员号" clearable @keyup.enter="handleQuery" />
+            <el-form-item label="号" prop="athleteCode">
+              <el-input v-model="queryParams.athleteCode" placeholder="请输入运动员号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="姓名" prop="name">
               <el-input v-model="queryParams.name" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" />
@@ -44,9 +44,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:gameAthlete:export']">导出 </el-button>
-          </el-col>
+          </el-col> -->
           <el-col :span="1.5">
             <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:gameAthlete:import']"> 导入 </el-button>
           </el-col>
@@ -56,9 +56,9 @@
 
       <el-table v-loading="loading" border :data="gameAthleteList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="主键" align="center" prop="athleteId" v-if="columns[0].visible" />
+        <el-table-column label="运动员id" align="center" prop="athleteId" v-if="columns[0].visible" />
         <el-table-column label="赛事名称" align="center" prop="eventName" width="120px" v-if="columns[1].visible" />
-        <el-table-column label="运动员编号" align="center" prop="athleteCode" width="100px" v-if="columns[2].visible" />
+        <el-table-column label="号" align="center" prop="athleteCode" width="100px" v-if="columns[2].visible" />
         <el-table-column label="姓名" align="center" prop="name" v-if="columns[3].visible" />
         <el-table-column label="性别" align="center" prop="gender" v-if="columns[4].visible">
           <template #default="scope">
@@ -77,14 +77,18 @@
             {{ getTeamNameById(scope.row.teamId) }}
           </template>
         </el-table-column>
-        <el-table-column label="芯片号" align="center" prop="chipCode" v-if="columns[9].visible" />
-        <el-table-column label="手机号" align="center" prop="phone" v-if="columns[10].visible" />
-        <el-table-column label="居住地址" align="center" prop="location" v-if="columns[11].visible" />
-        <el-table-column label="T恤尺码" align="center" prop="tshirtSize" v-if="columns[12].visible" />
-        <el-table-column label="组别" align="center" prop="groupType" v-if="columns[13].visible" />
-        <el-table-column label="号码" align="center" prop="number" v-if="columns[14].visible" />
-        <el-table-column label="状态" align="center" prop="status" v-if="columns[15].visible" />
-        <el-table-column label="备注" align="center" prop="remark" v-if="columns[16].visible" />
+        <!-- <el-table-column label="芯片号" align="center" prop="chipCode" v-if="columns[9].visible" /> -->
+        <!-- <el-table-column label="手机号" align="center" prop="phone" v-if="columns[10].visible" /> -->
+        <!-- <el-table-column label="居住地址" align="center" prop="location" v-if="columns[11].visible" /> -->
+        <!-- <el-table-column label="T恤尺码" align="center" prop="tshirtSize" v-if="columns[12].visible" /> -->
+        <!-- <el-table-column label="组别" align="center" prop="groupType" v-if="columns[13].visible" /> -->
+        <!-- <el-table-column label="号码" align="center" prop="number" v-if="columns[14].visible" /> -->
+        <el-table-column label="状态" align="center" prop="status" v-if="columns[9].visible" >
+          <template #default="scope">
+            <dict-tag :options="game_event_status" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="remark" v-if="columns[10].visible" />
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
@@ -102,15 +106,12 @@
     <!-- 添加或修改参赛队员对话框 -->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
       <el-form ref="gameAthleteFormRef" :model="form" :rules="rules" label-width="80px">
-        <!-- <el-form-item label="用户ID" prop="userId">
-          <el-input v-model="form.userId" placeholder="请输入用户ID" />
-        </el-form-item> -->
         <el-form-item label="队伍" prop="teamId">
           <el-select v-model="form.teamId" placeholder="请选择队伍">
             <el-option v-for="team in gameTeamList" :key="team.teamId" :label="team.teamName" :value="team.teamId" />
           </el-select>
         </el-form-item>
-        <el-form-item label="运动员编号" prop="athleteCode">
+        <el-form-item label="号" prop="athleteCode">
           <el-input v-model="form.athleteCode" placeholder="请输入运动员编号" />
         </el-form-item>
         <el-form-item label="姓名" prop="name">
@@ -124,9 +125,9 @@
         <el-form-item label="年龄" prop="age">
           <el-input v-model="form.age" placeholder="请输入年龄" />
         </el-form-item>
-        <el-form-item label="参与项目" prop="projectValue">
+        <el-form-item label="参与项目" prop="projectList">
           <el-transfer
-            v-model="form.selectedProjects"
+            v-model="projectListStr"
             :data="gameEventProjectList"
             :titles="['可选项目', '已选项目']"
             :button-texts="['移除', '添加']"
@@ -196,7 +197,7 @@ import { GameEventVO } from '@/api/system/gameEvent/types';
 import { globalHeaders } from '@/utils/request';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
+const { sys_user_sex, game_event_status } = toRefs<any>(proxy?.useDict('sys_user_sex','game_event_status'));
 const defaultEvent = ref<GameEventVO | null>(null); // 默认赛事信息
 
 const gameTeamList = ref<GameTeamVO[]>([]); // 队伍列表
@@ -212,23 +213,23 @@ const total = ref(0);
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '主键', visible: false },
+  { key: 0, label: '运动员id', visible: false },
   { key: 1, label: '赛事名称', visible: false },
-  { key: 2, 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 },
   { key: 7, label: '证件号', visible: true },
   { key: 8, label: '队伍', visible: true },
-  { key: 9, label: '芯片号', visible: true },
-  { key: 10, label: '手机号', visible: true },
-  { key: 11, label: '居住地址', visible: true },
-  { key: 12, label: 'T恤尺码', visible: true },
-  { key: 13, label: '组别', visible: true },
-  { key: 14, label: '号码', visible: true },
-  { key: 15, label: '状态', visible: true },
-  { key: 16, label: '备注', visible: true },
+  // { key: 9, label: '芯片号', visible: true },
+  // { key: 10, label: '手机号', visible: true },
+  // { key: 11, label: '居住地址', visible: true },
+  // { key: 12, label: 'T恤尺码', visible: true },
+  // { key: 13, label: '组别', visible: true },
+  // { key: 14, label: '号码', visible: true },
+  { key: 9, label: '状态', visible: true },
+  { key: 10, label: '备注', visible: true },
 ]);
 
 const queryFormRef = ref<ElFormInstance>();
@@ -300,7 +301,6 @@ const data = reactive<PageData<GameAthleteForm, GameAthleteQuery>>({
     teamId: [{ required: true, message: '队伍ID不能为空', trigger: 'blur' }],
     athleteCode: [{ required: true, message: '运动员编号不能为空', trigger: 'blur' }],
     gender: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
-    age: [{ required: true, message: '年龄不能为空', trigger: 'blur' }]
   }
 });
 
@@ -328,7 +328,8 @@ const { queryParams, form, rules } = toRefs(data);
 // };
 
 /** 获取队伍名称 */
-const getTeamNameById = (teamId: string | number) => {
+const getTeamNameById = (teamId: string | number | null | undefined) => {
+  if (!teamId) return '';
   const team = gameTeamList.value.find((team) => team.teamId === teamId);
   return team ? team.teamName : '';
 };
@@ -349,7 +350,7 @@ const getProjectList = async (eventId?: string) => {
 };
 
 // 格式化项目列表显示
-const formatProjectList = (projectList: string[]) => {
+const formatProjectList = (projectList: number[]) => {
   if (!projectList) return '';
   // 将逗号分隔的ID列表转换为项目名称列表
   // const projectIds = projectValue.split(',');
@@ -357,7 +358,7 @@ const formatProjectList = (projectList: string[]) => {
   //   const project = gameEventProjectList.value.find((p) => p.key === id);
   //   return project ? project.label : id;
   // });
-  const projectNames = gameEventProjectList.value.filter((p) => projectList.includes(p.key)).map((p) => p.label);
+  const projectNames = gameEventProjectList.value.filter((p) => projectList.includes(Number(p.key))).map((p) => p.label);
   return projectNames.join(',');
 };
 
@@ -435,8 +436,10 @@ const handleUpdate = async (row?: GameAthleteVO) => {
   Object.assign(form.value, res.data);
 
   // 处理项目列表,将逗号分隔的字符串转换为数组
-  if (res.data.projectValue) {
-    form.value.selectedProjects = res.data.projectValue.split(',');
+  if (res.data.projectList) {
+    form.value.selectedProjects = res.data.projectList;
+  } else if (res.data.projectValue) {
+    form.value.selectedProjects = res.data.projectValue.split(',').map(Number);
   } else {
     form.value.selectedProjects = [];
   }
@@ -451,6 +454,14 @@ const handleUpdate = async (row?: GameAthleteVO) => {
   // });
 };
 
+// 添加一个计算属性用于处理projectList2的类型转换
+const projectListStr = computed({
+  get: () => form.value.selectedProjects.map(id => String(id)),
+  set: (value) => {
+    form.value.selectedProjects = value.map(id => Number(id));
+  }
+});
+
 /** 提交按钮 */
 const submitForm = () => {
   gameAthleteFormRef.value?.validate(async (valid: boolean) => {

+ 225 - 25
src/views/system/gameEvent/RankingBoardPage.vue

@@ -16,6 +16,7 @@
           <div class="ranking-list ranking-list-full">
             <div v-for="(item, index) in athleteScoreList" :key="index" class="ranking-item">
               <div class="item-content">
+                <span class="item-rank">{{ getRankDisplay(item, index, athleteScoreList) }}</span>
                 <span class="item-name">{{ item.athleteName }}</span>
                 <span class="item-team">{{ item.teamName }}</span>
                 <span class="item-time">{{ item.totalScore }}</span>
@@ -34,7 +35,7 @@
           <div class="ranking-list ranking-list-full">
             <div v-for="(item, index) in teamScores" :key="index" class="ranking-item">
               <div class="item-content">
-                <span class="item-rank">第{{ item.rank }}名</span>
+                <span class="item-rank">{{ getRankDisplay(item, index, teamScores) }}</span>
                 <span class="item-team-name">{{ item.teamName }}</span>
                 <span class="item-score">{{ item.score }}分</span>
               </div>
@@ -56,9 +57,17 @@
           <div class="ranking-list ranking-list-full">
             <div v-for="(item, index) in projectProgress" :key="index" class="ranking-item">
               <div class="item-content">
-                <span class="item-name">{{ item.name }}</span>
-                <span class="item-type">{{ item.type }}</span>
-                <span class="item-time">{{ item.time }}</span>
+                <span class="item-name">{{ item.projectName }}</span>
+                <span class="item-type">{{ getProjectTypeText(item.projectType) }} {{ item.classification === '0' ? '个人' : '团体' }}</span>
+                <span class="item-time">{{ formatTime(item.startTime) }}</span>
+              </div>
+              <!-- 如果有组别信息,显示组别详情 -->
+              <div v-if="item.groups && item.groups.length > 0" class="group-details">
+                <div v-for="group in item.groups" :key="group.groupId" class="group-item">
+                  <span class="group-name">{{ group.groupName }}</span>
+                  <span class="group-time">{{ formatTimeOnly(group.beginTime) }}</span>
+                  <span class="group-status" :class="'status-' + group.status">{{ group.statusText }}</span>
+                </div>
               </div>
             </div>
           </div>
@@ -73,8 +82,10 @@ import { ref, computed, onMounted } from 'vue';
 import { listScoreRanking, listPersonalRanking, listTeamRanking } from '@/api/system/gameEvent/eventRank';
 import { listGameScore } from '@/api/system/gameScore';
 import { listGameTeam } from '@/api/system/gameTeam';
+import { getProjectProgress } from '@/api/system/gameEvent/projectProgress';
 import { useRouter,useRoute } from 'vue-router';
 import { GameTeamVO } from '@/api/system/gameTeam/types';
+import { ProjectProgressVo, GroupProgressVo } from '@/api/system/gameEvent/types';
 
 // 定义队伍积分排行榜的数据结构
 interface TeamScore {
@@ -84,27 +95,19 @@ interface TeamScore {
   rank: number;
 }
 
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { game_project_type } = toRefs<any>(proxy?.useDict('game_project_type'));
+
 const route = useRoute();
 const eventId = route.params.eventId as string;
 
 const athleteScoreList = ref([]);
 
-const completedTasks = ref(22);
-const totalTasks = ref(33);
-const progressPercentage = computed(() => (completedTasks.value / totalTasks.value) * 100);
-
-const projectProgress = ref([
-  { name: '全部', type: '', time: '' },
-  { name: '砥砺前行', type: '团体趣味u竞赛', time: '16:24' },
-  { name: '同舟共济', type: '团体趣味u竞赛', time: '16:26' },
-  { name: '跳绳', type: '个人趣味', time: '13:25' },
-  { name: '托球跑', type: '个人趣味', time: '15:08' },
-  { name: '100m', type: '田径丙组', time: '10:18' },
-  { name: '200m', type: '田径丙组', time: '10:24' },
-  { name: '400m', type: '田径丙组', time: '09:26' },
-  { name: '800m', type: '田径丙组', time: '10:28' },
-  { name: '1500m', type: '田径丙组', time: '10:17' }
-]);
+const completedTasks = ref(0);
+const totalTasks = ref(0);
+const progressPercentage = computed(() => totalTasks.value > 0 ? (completedTasks.value / totalTasks.value) * 100 : 0);
+
+const projectProgress = ref<ProjectProgressVo[]>([]);
 
 const teamScores = ref<TeamScore[]>([]);
 
@@ -156,14 +159,155 @@ const loadTeamScores = async () => {
   // 按积分从高到低排序
   teamScoreList.sort((a, b) => b.score - a.score);
   
-  // 添加排名
-  teamScoreList.forEach((team, index) => {
-    team.rank = index + 1;
-  });
+  // 添加排名(支持并列排名)
+  let currentRank = 1;
+  for (let i = 0; i < teamScoreList.length; i++) {
+    const team = teamScoreList[i];
+    const currentScore = team.score || 0;
+    
+    // 如果不是第一个,检查是否与前一个积分相同
+    if (i > 0) {
+      const previousScore = teamScoreList[i - 1].score || 0;
+      if (currentScore !== previousScore) {
+        currentRank = i + 1;
+      }
+    }
+    
+    team.rank = currentRank;
+  }
   
   teamScores.value = teamScoreList;
 };
 
+// 加载项目进度信息
+const loadProjectProgress = async () => {
+  try {
+    const res = await getProjectProgress(eventId);
+    projectProgress.value = res.data || [];
+    
+    // 计算已完成和总任务数
+    let completed = 0;
+    let total = 0;
+    
+    projectProgress.value.forEach(project => {
+      if (project.groups && project.groups.length > 0) {
+        // 有组别的项目,统计组别
+        project.groups.forEach(group => {
+          total++;
+          if (group.status === '2') { // 已完成
+            completed++;
+          }
+        });
+      } else {
+        // 没有组别的项目,直接统计项目
+        total++;
+        if (project.status === '2') { // 已完成
+          completed++;
+        }
+      }
+    });
+    
+    completedTasks.value = completed;
+    totalTasks.value = total;
+    
+    // 前端也可以做一次排序确保,虽然后端已经排序了
+    // 按完整时间排序(项目时间或最早组别时间)
+    projectProgress.value.sort((a, b) => {
+      const getEarliestTime = (project: ProjectProgressVo) => {
+        if (project.groups && project.groups.length > 0) {
+          // 有组别,找到最早的组别时间
+          const groupTimes = project.groups
+            .map(g => g.beginTime ? new Date(g.beginTime).getTime() : 0)
+            .filter(time => time > 0);
+          if (groupTimes.length > 0) {
+            return Math.min(...groupTimes);
+          }
+        }
+        // 没有组别或组别时间无效,使用项目时间
+        return project.startTime ? new Date(project.startTime).getTime() : 0;
+      };
+      
+      const aTime = getEarliestTime(a);
+      const bTime = getEarliestTime(b);
+      
+      return aTime - bTime;
+    });
+  } catch (error) {
+    console.error('加载项目进度失败:', error);
+  }
+};
+
+// 获取项目类型文本
+const getProjectTypeText = (projectType: string) => {
+  if (!game_project_type.value || !projectType) return '未知';
+  
+  const typeItem = game_project_type.value.find((item: any) => item.value === projectType);
+  return typeItem ? typeItem.label : '未知';
+};
+
+// 格式化时间显示(包含日期)
+const formatTime = (timeStr: string) => {
+  if (!timeStr) return '-';
+  try {
+    const date = new Date(timeStr);
+    // 检查是否是有效日期
+    if (isNaN(date.getTime())) {
+      return timeStr;
+    }
+    return date.toLocaleString('zh-CN', { 
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit', 
+      minute: '2-digit',
+      hour12: false 
+    });
+  } catch (error) {
+    return timeStr;
+  }
+};
+
+// 格式化时间显示(仅时间,用于组别详情)
+const formatTimeOnly = (timeStr: string) => {
+  if (!timeStr) return '-';
+  try {
+    const date = new Date(timeStr);
+    // 检查是否是有效日期
+    if (isNaN(date.getTime())) {
+      return timeStr;
+    }
+    return date.toLocaleTimeString('zh-CN', { 
+      hour: '2-digit', 
+      minute: '2-digit',
+      hour12: false 
+    });
+  } catch (error) {
+    return timeStr;
+  }
+};
+
+// 获取排名显示文本(支持并列排名,排名数值连续)
+const getRankDisplay = (item: any, index: number, list: any[]) => {
+  // 如果项目有rank字段(如团队排名),直接使用
+  if (item.rank !== undefined) {
+    return `第${item.rank}名`;
+  }
+  
+  // 个人排名逻辑(排名数值连续)
+  // 计算当前项目的实际排名
+  // 排名 = 比当前积分高的不同积分数量 + 1
+  const currentScore = item.totalScore || 0;
+  const higherScores = new Set();
+  
+  for (let i = 0; i < list.length; i++) {
+    if (list[i].totalScore > currentScore) {
+      higherScores.add(list[i].totalScore);
+    }
+  }
+  
+  const actualRank = higherScores.size + 1;
+  return `第${actualRank}名`;
+};
+
 onMounted(async () => {
   const res = await listScoreRanking(eventId);
   // 按照totalScore字段降序排序(分数高的在前面)
@@ -171,6 +315,9 @@ onMounted(async () => {
   
   // 加载队伍积分排行榜
   await loadTeamScores();
+  
+  // 加载项目进度信息
+  await loadProjectProgress();
 });
 </script>
 
@@ -242,7 +389,11 @@ onMounted(async () => {
   justify-content: space-between;
 }
 
-.item-name,
+.item-name {
+  flex: 2;
+  text-align: left;
+}
+
 .item-team,
 .item-time,
 .item-type,
@@ -252,4 +403,53 @@ onMounted(async () => {
   flex: 1;
   text-align: center;
 }
+
+/* 组别详情样式 */
+.group-details {
+  margin-top: 8px;
+  padding-left: 20px;
+  border-left: 2px solid #e0e0e0;
+}
+
+.group-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 4px 0;
+  font-size: 12px;
+  color: #666;
+}
+
+.group-name {
+  flex: 3;
+  text-align: left;
+}
+
+.group-time {
+  flex: 2;
+  text-align: center;
+}
+
+.group-status {
+  flex: 1;
+  text-align: center;
+  padding: 2px 6px;
+  border-radius: 10px;
+  font-size: 10px;
+}
+
+.status-0 {
+  background-color: #f0f0f0;
+  color: #999;
+}
+
+.status-1 {
+  background-color: #e6f7ff;
+  color: #1890ff;
+}
+
+.status-2 {
+  background-color: #f6ffed;
+  color: #52c41a;
+}
 </style>

+ 40 - 17
src/views/system/gameEvent/RefereeForm.vue

@@ -57,24 +57,47 @@ const allProjects = ref<{key: string, label: string }[]>([]);
 const eventId = ref<string>(''); // 新增:用于存储赛事ID
 
 const loadProjects = async () => {
-  const res = await listGameEventProject({
-    eventId: eventId.value, // 使用传递进来的赛事ID
-    pageNum: 1,
-    pageSize: 10,
-    orderByColumn: undefined,
-    isAsc: undefined,
-  });
-  allProjects.value = res.rows.map(item => ({
-    key: String(item.projectId),
-    label: `${item.projectName} - ${item.groupType}`
-  }));
-  console.log(allProjects.value);
+  try {
+    const res = await listGameEventProject({
+      eventId: eventId.value, // 使用传递进来的赛事ID
+      pageNum: 1,
+      pageSize: 1000, // 增加页面大小,确保获取所有项目
+      orderByColumn: undefined,
+      isAsc: undefined,
+    });
+    
+    allProjects.value = res.rows.map(item => ({
+      key: String(item.projectId),
+      label: item.projectName || '未命名项目' // 只显示项目名称,不拼接组别
+    }));
+    
+  } catch (error) {
+    console.error('加载项目失败:', error);
+    ElMessage.error('加载项目失败');
+  }
 };
 
 const openDialog = async (eventIdParam: string) => {
-  eventId.value = eventIdParam; // 接收并设置赛事ID
-  await loadProjects();
-  dialog.visible = true;
+  try {
+    
+    if (!eventIdParam) {
+      ElMessage.error('赛事ID不能为空');
+      return;
+    }
+    
+    eventId.value = eventIdParam; // 接收并设置赛事ID
+    
+    // 重置表单数据
+    form.account = '';
+    form.password = '';
+    form.projectIds = [];
+    
+    await loadProjects();
+    dialog.visible = true;
+  } catch (error) {
+    console.error('打开对话框失败:', error);
+    ElMessage.error('打开对话框失败');
+  }
 };
 
 const cancel = () => {
@@ -103,8 +126,8 @@ const submitForm = () => {
               // 直接使用 refereeGroups 字段(字符串数组)
               let currentRefereeGroups = project.refereeGroups || [];
               // 检查是否已存在该裁判ID,避免重复添加
-              if (!currentRefereeGroups.includes(String(newRefereeId))) {
-                currentRefereeGroups.push(String(newRefereeId));
+              if (!currentRefereeGroups.includes(newRefereeId)) {
+                currentRefereeGroups.push(newRefereeId);
               }
               
               // 更新项目表中的裁判组字段

+ 6 - 5
src/views/system/gameEvent/athlete.vue

@@ -29,7 +29,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="号" prop="athleteCode">
+            <el-form-item label="号" prop="athleteCode">
               <el-input v-model="athleteForm.athleteCode" placeholder="输入号码" />
             </el-form-item>
           </el-col>
@@ -93,7 +93,7 @@ const athleteForm = reactive({
   unit: '',
   age: 0,
   gender: '',
-  projectValue: [] as string[]  // 确保初始化为数组
+  projectValue: [] as number[]  // 确保初始化为数组
 });
 
 const athleteRules = {
@@ -103,8 +103,8 @@ const athleteRules = {
   athleteCode: [
     { required: true, message: '请输入号码', trigger: 'blur' }
   ],
-  age: [
-    { required: true, message: '请输入年龄', trigger: 'blur' }
+  gender: [
+    { required: true, message: '请选择性别', trigger: 'change' }
   ]
 };
 
@@ -152,7 +152,8 @@ const submitForm = () => {
         athleteCode: athleteForm.athleteCode,
         age: athleteForm.age,
         gender: athleteForm.gender,
-        projectValue: `${projectValue.join(',')}`,  // 安全地进行 join 操作
+        projectValue: `${projectValue.join(',')}`,
+        projectList: projectValue,
       };
       console.log(athleteForm.projectValue);
       try {

+ 5 - 5
src/views/system/gameEvent/detail.vue

@@ -57,13 +57,13 @@
         </template>
 
         <el-table :data="projectData" border>
-          <el-table-column label="项目名称" prop="projectName" align="center" />
           <el-table-column label="项目类型" prop="projectType" align="center">
             <template #default="scope">
-              {{ scope.row.projectType === '0' ? '个人' : '团体' }}
+              <dict-tag :options="game_project_type" :value="scope.row.projectType" />
             </template>
           </el-table-column>
-          <el-table-column label="项目组别" prop="groupType" align="center" />
+          <el-table-column label="项目名称" prop="projectName" align="center" />
+          <!-- <el-table-column label="项目组别" prop="groupType" align="center" /> -->
           <el-table-column label="比赛场地" prop="location" align="center" />
           <el-table-column label="开始时间" prop="startTime" align="center" width="180">
             <template #default="scope">
@@ -102,8 +102,8 @@ const router = useRouter();
 
 // 获取字典数据
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { game_event_type, game_event_status, sys_normal_disable } = toRefs<any>(
-  proxy?.useDict('game_event_type', 'game_event_status', 'sys_normal_disable')
+const { game_event_type, game_event_status, sys_normal_disable, game_project_type } = toRefs<any>(
+  proxy?.useDict('game_event_type', 'game_event_status', 'sys_normal_disable', 'game_project_type')
 );
 
 // 赛事数据

+ 105 - 30
src/views/system/gameEvent/edit.vue

@@ -319,7 +319,7 @@ const basicForm = ref<GameEventForm>({
   refereeUrl: '',
   registerUrl: '',
   unit: '',
-  isDefault: '0',
+  isDefault: '1',
   status: '0',
   remark: ''
 });
@@ -343,6 +343,26 @@ onMounted(async () => {
     currentEventId.value = Number(eventId);
     await loadEventData(eventId);
   } else {
+    // 新增模式,确保清除所有可能残留的数据
+    isEdit.value = false;
+    currentEventId.value = undefined;
+    // 重置表单数据,确保不包含eventId
+    basicForm.value = {
+      eventCode: '',
+      eventName: '',
+      eventType: '',
+      location: '',
+      purpose: '',
+      startTime: '',
+      endTime: '',
+      eventUrl: '',
+      refereeUrl: '',
+      registerUrl: '',
+      unit: '',
+      isDefault: '1',
+      status: '0',
+      remark: ''
+    };
     // 新增模式,加载图片配置模板
     // await loadImageConfigTemplates();
   }
@@ -378,7 +398,10 @@ const loadEventData = async (eventId: string | number) => {
 const handleStatusChange = async (row: GameEventVO) => {
   const text = row.isDefault === '0' ? '启用' : '停用';
   try {
-    await loadEventData(row.eventId);
+    // 只有在编辑模式下才加载数据
+    if (isEdit.value && row.eventId) {
+      await loadEventData(row.eventId);
+    }
     proxy?.$modal.msgSuccess(text + '成功');
   } catch {
     return;
@@ -449,19 +472,34 @@ const handleMenuSelectionChange = (selection: any[]) => {
 };
 
 // 确认添加菜单项
-const confirmAddMenuItems = () => {
+const confirmAddMenuItems = async () => {
   console.log('selectedMenus:', selectedMenus.value);
   if (selectedMenus.value.length === 0) {
     proxy?.$modal.msgWarning('请选择要添加的菜单');
     return;
   }
 
-  // 将选中的菜单添加到菜单列表中(无需重复检查,因为列表已经过滤掉了重复项)
-  menuItems.value.push(...selectedMenus.value);
-
-  console.log('menuItems:', menuItems.value);
-  menuSelectDialogVisible.value = false;
-  proxy?.$modal.msgSuccess(`成功添加 ${selectedMenus.value.length} 个菜单项`);
+  try {
+    // 将选中的菜单添加到菜单列表中(无需重复检查,因为列表已经过滤掉了重复项)
+    menuItems.value.push(...selectedMenus.value);
+
+    // 如果是编辑模式,立即保存菜单数据到数据库
+    // if (isEdit.value && currentEventId.value) {
+    //   await saveMenuData(currentEventId.value);
+    //   proxy?.$modal.msgSuccess(`成功添加 ${selectedMenus.value.length} 个菜单项并保存到数据库`);
+    // } else {
+    //   // 新增模式下,菜单数据会在最终保存赛事时一起保存
+    //   proxy?.$modal.msgSuccess(`成功添加 ${selectedMenus.value.length} 个菜单项(将在保存赛事时一起保存)`);
+    // }
+
+    console.log('menuItems:', menuItems.value);
+    menuSelectDialogVisible.value = false;
+  } catch (error) {
+    console.error('添加菜单项失败:', error);
+    // 如果保存失败,回滚本地数组的更改
+    menuItems.value.splice(-selectedMenus.value.length, selectedMenus.value.length);
+    proxy?.$modal.msgError('添加菜单项失败,请重试');
+  }
 };
 
 // 获取跳转类型标签
@@ -644,7 +682,7 @@ const loadConfigData = async (eventId: string | number) => {
     const res = await listGameEventConfig({
       eventId: eventId === '' ? '' : eventId,
       pageNum: 1,
-      pageSize: 10,
+      pageSize: 1000,
       orderByColumn: '',
       isAsc: ''
     });
@@ -700,6 +738,9 @@ const saveconfigData = async (eventId: string | number) => {
 
 // 保存赛事信息
 const saveEvent = async () => {
+  let formData: GameEventForm;
+  let savedEventId: string;
+  
   try {
     // 验证基本信息表单
     await basicFormRef.value?.validate();
@@ -707,28 +748,60 @@ const saveEvent = async () => {
     saveLoading.value = true;
 
     // 合并表单数据
-    const formData: GameEventForm = {
+    formData = {
       ...basicForm.value
     };
-
-    let savedEventId: string;
-    // 如果设置为默认赛事,则取消其他赛事的默认状态
-    if (basicForm.value.isDefault === '0') {
-      handleStatusChange({
-        eventId: basicForm.value.eventId,
-        isDefault: basicForm.value.isDefault,
-        eventName: basicForm.value.eventName
-      } as GameEventVO);
-    }
-
-    // 保存基本信息
+    
+    // 新增模式下,确保不包含eventId字段,避免主键冲突
     if (!isEdit.value) {
+      console.log('新增模式,开始保存赛事信息');
+      console.log('保存前的formData:', formData);
+      
+      // 三重保险:确保eventId被完全清除
+      delete formData.eventId;
+      delete basicForm.value.eventId;
+      
+      // 再次检查formData中是否还有eventId
+      if (formData.eventId !== undefined) {
+        console.warn('检测到formData中仍有eventId,强制清除:', formData.eventId);
+        delete formData.eventId;
+      }
+      
+      console.log('清除eventId后的formData:', formData);
+      
+      // 如果设置为默认赛事,则取消其他赛事的默认状态
+      if (basicForm.value.isDefault === '0') {
+        console.log('设置为默认赛事,调用handleStatusChange');
+        handleStatusChange({
+          eventId: '', // 新增时没有eventId
+          isDefault: basicForm.value.isDefault,
+          eventName: basicForm.value.eventName
+        } as GameEventVO);
+      }
+
+      // 保存基本信息
+      console.log('调用addGameEvent API');
       const addRes = await addGameEvent(formData);
-      // 假设返回的数据中包含新创建的赛事ID
+      console.log('addGameEvent 响应:', addRes);
+      // 获取新创建的赛事ID
       savedEventId = addRes?.data as string;
+      console.log('获取到的新赛事ID:', savedEventId);
     } else {
       savedEventId = route.params.id as string;
+      
+      // 编辑模式下,如果设置为默认赛事,则取消其他赛事的默认状态
+      if (basicForm.value.isDefault === '0') {
+        handleStatusChange({
+          eventId: basicForm.value.eventId,
+          isDefault: basicForm.value.isDefault,
+          eventName: basicForm.value.eventName
+        } as GameEventVO);
+      }
+      
+      // 更新基本信息
+      await updateGameEvent(formData);
     }
+    
     // 保存图片配置数据
     await saveImageConfigData(savedEventId);
     // 保存赛事配置项数据
@@ -736,11 +809,6 @@ const saveEvent = async () => {
     // 保存菜单数据
     await saveMenuData(savedEventId);
 
-    // 保存基本信息
-    if (isEdit.value) {
-      await updateGameEvent(formData);
-    }
-
     proxy?.$modal.msgSuccess('保存成功');
 
     // 保存成功后,设置一个标识,表示需要刷新列表数据
@@ -748,7 +816,14 @@ const saveEvent = async () => {
     goBack();
   } catch (error) {
     console.error('保存失败:', error);
-    proxy?.$modal.msgError('保存失败');
+    console.error('错误详情:', {
+      message: error instanceof Error ? error.message : String(error),
+      stack: error instanceof Error ? error.stack : undefined,
+      formData: formData,
+      isEdit: isEdit.value,
+      currentEventId: currentEventId.value
+    });
+    proxy?.$modal.msgError(`保存失败: ${error instanceof Error ? error.message : String(error)}`);
   } finally {
     saveLoading.value = false;
   }

+ 82 - 27
src/views/system/gameEvent/index.vue

@@ -38,21 +38,17 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:gameEvent:add']">新增 </el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:gameEvent:add']">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:gameEvent:edit']"
-              >修改
-            </el-button>
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:gameEvent:edit']">修改</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEvent:remove']"
-              >删除
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEvent:export']">导出 </el-button>
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEvent:remove']">删除</el-button>
           </el-col>
+          <!-- <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEvent:export']">导出</el-button>
+          </el-col> -->
           <!-- 新增的操作按钮,基于默认赛事 -->
           <el-col :span="1.5">
             <el-button type="warning" plain icon="Download" @click="handleDownloadTemplateDefault" v-hasPermi="['system:gameEvent:download']"
@@ -129,12 +125,12 @@
         </el-table-column>
         <el-table-column label="开始时间" align="center" prop="startTime" width="180" v-if="columns[6].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="结束时间" align="center" prop="endTime" width="180" v-if="columns[7].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="赛事链接" align="center" prop="eventUrlUrl" width="100" v-if="columns[8].visible">
@@ -155,12 +151,12 @@
         </el-table-column>
         <el-table-column label="创建时间" align="center" prop="createTime" width="180" v-if="columns[12].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="更新时间" align="center" prop="updateTime" width="180" v-if="columns[13].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="状态" align="center" prop="status" v-if="columns[14].visible">
@@ -301,6 +297,32 @@
             </el-form-item>
           </div>
         </el-tab-pane>
+        <el-tab-pane label="快捷报名" name="quick-registration">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.quickRegistration.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.quickRegistration.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.quickRegistration.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="报名咨询" name="registration-consultation">
+          <div class="article-form">
+            <el-form-item label="标题">
+              <el-input v-model="articleData.registrationConsultation.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="内容">
+              <Editor v-model="articleData.registrationConsultation.content" :min-height="300" />
+            </el-form-item>
+            <el-form-item label="备注">
+              <el-input v-model="articleData.registrationConsultation.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+            </el-form-item>
+          </div>
+        </el-tab-pane>
       </el-tabs>
       <template #footer>
         <div class="dialog-footer">
@@ -664,10 +686,25 @@ const handleAddParticipant = (row: GameEventVO) => {
   });
 };
 
-// 添加裁判按钮操作                                                                                                         1
+// 添加裁判按钮操作
 const handleAddReferee = async (row: GameEventVO) => {
-  // 打开裁判表单对话框并传递 eventId
-  refereeFormRef.value?.openDialog(String(row.eventId));
+  try {
+    if (!row.eventId) {
+      proxy?.$modal.msgError('赛事ID不能为空');
+      return;
+    }
+    
+    if (!refereeFormRef.value) {
+      proxy?.$modal.msgError('裁判表单组件未初始化');
+      return;
+    }
+    
+    // 打开裁判表单对话框并传递 eventId
+    refereeFormRef.value.openDialog(String(row.eventId));
+  } catch (error) {
+    console.error('添加裁判失败:', error);
+    proxy?.$modal.msgError('添加裁判失败');
+  }
 };
 
 // 预览按钮点击事件
@@ -706,9 +743,10 @@ const tabTypeMapping: Record<string, number> = {
   'event-grouping': 6, // 赛事分组
   'athlete-handbook': 7, // 运动员号码簿
   'project-venue': 8, // 项目场地
-  'traffic-guide': 9 // 交通指示
+  'traffic-guide': 9, // 交通指示
+  'quick-registration': 10, // 快捷报名
+  'registration-consultation': 11 // 报名咨询
 };
-
 const articleData = reactive({
   competitionProcess: { id: undefined, title: '', content: '', remark: '' },
   competitionItems: { id: undefined, title: '', content: '', remark: '' },
@@ -718,9 +756,10 @@ const articleData = reactive({
   eventGrouping: { id: undefined, title: '', content: '', remark: '' },
   athleteHandbook: { id: undefined, title: '', content: '', remark: '' },
   projectVenue: { id: undefined, title: '', content: '', remark: '' },
-  trafficGuide: { id: undefined, title: '', content: '', remark: '' }
+  trafficGuide: { id: undefined, title: '', content: '', remark: '' },
+  quickRegistration: { id: undefined, title: '', content: '', remark: '' },
+  registrationConsultation: { id: undefined, title: '', content: '', remark: '' }
 });
-
 // 打开排行榜组件并传递赛事ID
 // const openRankingBoard = (eventId: string) => {
 //   currentEventId.value = eventId;
@@ -810,7 +849,9 @@ const getDataKeyByTabName = (tabName: string): keyof typeof articleData | null =
     'event-grouping': 'eventGrouping',
     'athlete-handbook': 'athleteHandbook',
     'project-venue': 'projectVenue',
-    'traffic-guide': 'trafficGuide'
+    'traffic-guide': 'trafficGuide',
+    'quick-registration': 'quickRegistration',
+    'registration-consultation': 'registrationConsultation'
   };
   return mapping[tabName] || null;
 };
@@ -909,12 +950,19 @@ const handleAddParticipantDefault = async () => {
 
 /** 添加裁判(默认赛事) */
 const handleAddRefereeDefault = async () => {
-  const defaultEvent = gameEventStore.defaultEventInfo;
-  if (!defaultEvent) {
-    proxy?.$modal.msgError('请先设置默认赛事');
-    return;
+  try {
+    const defaultEvent = gameEventStore.defaultEventInfo;
+    
+    if (!defaultEvent) {
+      proxy?.$modal.msgError('请先设置默认赛事');
+      return;
+    }
+    
+    await handleAddReferee(defaultEvent);
+  } catch (error) {
+    console.error('默认赛事添加裁判失败:', error);
+    proxy?.$modal.msgError('添加裁判失败');
   }
-  handleAddReferee(defaultEvent);
 };
 
 /** 预览(默认赛事) */
@@ -964,6 +1012,13 @@ onMounted(() => {
   // 获取默认赛事信息
   gameEventStore.fetchDefaultEvent();
   getList();
+  
+  // 检查组件引用是否正确初始化
+  nextTick(() => {
+    console.log('组件引用检查:');
+    console.log('refereeFormRef.value:', refereeFormRef.value);
+    console.log('bibViewerDialogRef.value:', bibViewerDialogRef.value);
+  });
 });
 
 // 监听路由变化,当从编辑页返回时检查是否需要刷新列表

+ 4 - 4
src/views/system/gameEventConfig/index.vue

@@ -41,16 +41,16 @@
           <el-col :span="1.5">
             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEventConfig:remove']">删除</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:gameEventConfig:export']">导出</el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
       <el-table v-loading="loading" border :data="gameEventConfigList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="主键" align="center" prop="configId" v-if="columns[0].visible" />
+        <el-table-column label="配置id" align="center" prop="configId" v-if="columns[0].visible" />
         <!-- <el-table-column label="赛事ID" align="center" prop="eventId" /> -->
         <el-table-column label="配置类型" align="center" prop="configType" v-if="columns[1].visible" />
         <el-table-column label="配置描述" align="center" prop="configDesc" v-if="columns[2].visible" />
@@ -146,7 +146,7 @@ const total = ref(0);
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '主键', visible: true },
+  { key: 0, label: '配置id', visible: true },
   { key: 1, label: '配置类型', visible: true },
   { key: 2, label: '配置描述', visible: true },
   { key: 3, label: '配置键', visible: true },

+ 4 - 4
src/views/system/gameEventConfigType/index.vue

@@ -42,16 +42,16 @@
           <el-col :span="1.5">
             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEventConfigType:remove']">删除</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:gameEventConfigType:export']">导出</el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
       <el-table v-loading="loading" border :data="gameEventConfigTypeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="主键" align="center" prop="typeId" v-if="columns[0].visible" />
+        <el-table-column label="类型id" align="center" prop="typeId" v-if="columns[0].visible" />
         <el-table-column label="类型编码" align="center" prop="typeCode" v-if="columns[1].visible" />
         <el-table-column label="类型名称" align="center" prop="typeName" v-if="columns[2].visible" />
         <el-table-column label="类型描述" align="center" prop="typeDesc" v-if="columns[3].visible" />
@@ -144,7 +144,7 @@ const total = ref(0);
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '主键', visible: false },
+  { key: 0, label: '类型id', visible: false },
   { key: 1, label: '类型编码', visible: true },
   { key: 2, label: '类型名称', visible: true },
   { key: 3, label: '类型描述', visible: true },

+ 105 - 225
src/views/system/gameEventGroup/detail.vue

@@ -15,7 +15,7 @@
           </div>
         </div>
         <div class="text-right">
-          <el-button type="primary" @click="generateGroups" :loading="generating">重新生成分组</el-button>
+          <el-button type="primary" @click="regenerateGroups" :loading="generating">重新生成分组</el-button>
           <el-button @click="goBack">返回</el-button>
         </div>
       </div>
@@ -95,43 +95,27 @@
           <div class="text-2xl font-bold text-purple-600">{{ groupInfo.trackNum }}</div>
           <div class="text-sm text-gray-600">道数</div>
         </div>
-        <div class="text-center p-3 bg-orange-50 rounded-lg">
+        <!-- <div class="text-center p-3 bg-orange-50 rounded-lg">
           <div class="text-2xl font-bold text-orange-600">{{ groupInfo.personNum }}</div>
           <div class="text-sm text-gray-600">{{ groupInfo.groupName }}人数</div>
+        </div> -->
+        <div class="text-center p-3 bg-orange-50 rounded-lg">
+          <div class="text-2xl font-bold text-orange-600">{{ roundType }}</div>
+          <div class="text-sm text-gray-600">录取人数</div>
         </div>
       </div>
     </el-card>
 
-    <!-- 调试信息 -->
-    <!-- <el-card shadow="hover" class="mt-4">
-      <template #header>
-        <span class="font-medium">调试信息</span>
-      </template>
-      
-      <div class="space-y-2 text-sm">
-        <div><strong>项目ID:</strong> {{ groupInfo.projectId }}</div>
-        <div><strong>性别要求:</strong> {{ groupInfo.memberGender === '0' ? '不分男女' : groupInfo.memberGender === '1' ? '男' : '女' }}</div>
-        <div><strong>运动员总数:</strong> {{ athletes.length }}</div>
-        <div><strong>符合条件的运动员数:</strong> {{ totalAthletes }}</div>
-        <div><strong>分组结果大小:</strong> {{ groupResult.size }}</div>
-        <div><strong>分组结果:</strong></div>
-        <pre class="bg-gray-100 p-2 rounded text-xs overflow-auto max-h-40">{{ JSON.stringify(Array.from(groupResult.entries()), null, 2) }}</pre>
-      </div>
-    </el-card> -->
+    
   </div>
 </template>
 
 <script setup name="GameEventGroupDetail" lang="ts">
-import { ref, onMounted, computed, getCurrentInstance } from 'vue';
+import { ref, onMounted, getCurrentInstance } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-import { getGameEventGroup } from '@/api/system/gameEventGroup';
-import { listGameAthlete } from '@/api/system/gameAthlete';
-import { listGameTeam } from '@/api/system/gameTeam';
-import { listGameEventProject } from '@/api/system/gameEventProject';
+import { getGameEventGroup, generateGroups, getGroupResultFromDB } from '@/api/system/gameEventGroup';
+import { getGameEventProject } from '@/api/system/gameEventProject';
 import { GameEventGroupVO } from '@/api/system/gameEventGroup/types';
-import { GameAthleteVO } from '@/api/system/gameAthlete/types';
-import { GameTeamVO } from '@/api/system/gameTeam/types';
-import { GameEventProjectVO } from '@/api/system/gameEventProject/types';
 import type { ComponentInternalInstance } from 'vue';
 
 const route = useRoute();
@@ -140,73 +124,18 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 // 分组信息
 const groupInfo = ref<GameEventGroupVO>({} as GameEventGroupVO);
-// 运动员列表
-const athletes = ref<GameAthleteVO[]>([]);
-// 队伍列表
-const teams = ref<GameTeamVO[]>([]);
-// 项目列表
-const projects = ref<GameEventProjectVO[]>([]);
 // 分组结果
-const groupResult = ref<Map<string, GameAthleteVO>>(new Map());
+const groupResult = ref<Map<string, any>>(new Map());
 // 加载状态
 const loading = ref(false);
 // 生成分组状态
 const generating = ref(false);
-// 项目名称(需要从项目信息中获取)
-const projectName = computed(() => {
-  if (!groupInfo.value.projectId) return '';
-  const project = projects.value.find(p => p.projectId === groupInfo.value.projectId);
-  return project?.projectName || '';
-});
-
-// 计算总运动员数
-const totalAthletes = computed(() => {
-  return athletes.value.filter(athlete => {
-    // 检查运动员是否参与该项目
-    if (!athlete.projectList) return false;
-    
-    // 处理项目列表
-    let projectIds: string[] = [];
-    if (Array.isArray(athlete.projectList)) {
-      projectIds = athlete.projectList.map(p => p.toString());
-    } else if (typeof athlete.projectList === 'string') {
-      try {
-        projectIds = JSON.parse(athlete.projectList);
-      } catch (e) {
-        // 如果不是JSON格式,可能是逗号分隔的字符串
-        projectIds = (athlete.projectList as string).split(',').map(p => p.trim());
-      }
-    }
-    
-    const targetProjectId = groupInfo.value.projectId?.toString();
-    const hasProject = projectIds.includes(targetProjectId);
-    
-    if (!hasProject) {
-      return false;
-    }
-    
-    // 检查性别是否匹配
-    if (groupInfo.value.memberGender && groupInfo.value.memberGender !== '0') {
-      // 使用字典来匹配性别,而不是硬编码的字符串
-      if (athlete.gender?.toString() !== groupInfo.value.memberGender?.toString()) {
-        return false;
-      }
-    }
-    
-    return true;
-  }).length;
-});
 
-// 调试:打印运动员数据
-const debugAthletes = computed(() => {
-  return athletes.value.map(athlete => ({
-    id: athlete.athleteId,
-    name: athlete.name,
-    teamId: athlete.teamId,
-    projectList: athlete.projectList,
-    gender: athlete.gender
-  }));
-});
+// 项目名称和录取人数
+const projectName = ref('');
+const roundType = ref(0);
+// 总运动员数
+const totalAthletes = ref(0);
 
 // 获取道次名称
 const getTrackName = (track: number) => {
@@ -222,8 +151,9 @@ const getAthleteByGroupAndTrack = (groupIndex: number, track: number) => {
 
 // 根据队伍ID获取队伍名称
 const getTeamName = (teamId: string | number | undefined) => {
-  if (!teamId) return '';
-  const team = teams.value.find(t => t.teamId === teamId);
+  if (!groupResult.value.has('teams')) return '';
+  const teams = groupResult.value.get('teams') || [];
+  const team = teams.find((t: any) => t.teamId === teamId);
   return team?.teamName || '';
 };
 
@@ -240,15 +170,8 @@ const getGroupInfo = async () => {
     const res = await getGameEventGroup(groupId);
     groupInfo.value = res.data;
     
-    // 获取运动员、队伍和项目信息
-    await Promise.all([
-      getAthletes(),
-      getTeams(),
-      getProjects()
-    ]);
-    
-    // 生成分组
-    generateGroups();
+    // 优先从数据库读取分组数据
+    await loadGroupResultFromDB();
   } catch (error) {
     console.error('获取分组信息失败:', error);
     proxy?.$modal.msgError('获取分组信息失败');
@@ -257,153 +180,105 @@ const getGroupInfo = async () => {
   }
 };
 
-// 获取运动员列表
-const getAthletes = async () => {
-  try {
-    const res = await listGameAthlete({
-      pageNum: 1,
-      pageSize: 1000,
-      eventId: groupInfo.value.eventId,
-      orderByColumn: '',
-      isAsc: ''
-    });
-    athletes.value = res.rows;
-  } catch (error) {
-    console.error('获取运动员列表失败:', error);
-  }
-};
-
-// 获取队伍列表
-const getTeams = async () => {
+// 从数据库加载分组结果
+const loadGroupResultFromDB = async () => {
   try {
-    const res = await listGameTeam({
-      pageNum: 1,
-      pageSize: 1000,
-      eventId: groupInfo.value.eventId,
-      orderByColumn: '',
-      isAsc: ''
-    });
-    teams.value = res.rows;
-  } catch (error) {
-    console.error('获取队伍列表失败:', error);
-  }
-};
-
-// 获取项目列表
-const getProjects = async () => {
-  try {
-    const res = await listGameEventProject({
-      pageNum: 1,
-      pageSize: 1000,
-      orderByColumn: '',
-      isAsc: ''
-    });
-    projects.value = res.rows;
+    const groupId = route.query.id;
+    if (!groupId || Array.isArray(groupId)) {
+      proxy?.$modal.msgError('分组ID不能为空');
+      return;
+    }
+    
+    const res = await getGroupResultFromDB(groupId);
+    const data = res.data;
+    
+    if (data.success) {
+      // 更新分组结果
+      groupResult.value.clear();
+      
+      // 设置分组结果
+      if (data.groupResult) {
+        Object.entries(data.groupResult).forEach(([key, athlete]) => {
+          groupResult.value.set(key, athlete);
+        });
+      }
+      
+      if (data.totalAthletes !== undefined) {
+        totalAthletes.value = data.totalAthletes;
+      }
+      
+      // 设置项目信息(从分组信息中获取)
+      if (groupInfo.value.projectId) {
+        // 获取项目详细信息
+        try {
+          const projectRes = await getGameEventProject(groupInfo.value.projectId);
+          const projectInfo = projectRes.data;
+          projectName.value = projectInfo.projectName; 
+          roundType.value = projectInfo.roundType ? parseInt(projectInfo.roundType) : 0; 
+        } catch (error) {
+          console.error('获取项目信息失败:', error);
+          projectName.value = '项目名称'; // 默认值
+          roundType.value = 0; // 默认值
+        }
+      }
+      
+      console.log('从数据库加载分组结果成功');
+    } else {
+      // 数据库中没有数据,自动生成分组
+      console.log('数据库中没有分组数据,自动生成分组');
+      await generateGroupsData();
+    }
   } catch (error) {
-    console.error('获取项目列表失败:', error);
+    console.error('从数据库加载分组结果失败:', error);
+    // 加载失败时,自动生成分组
+    await generateGroupsData();
   }
 };
 
 // 生成分组
-const generateGroups = async () => {
+const generateGroupsData = async () => {
   try {
     generating.value = true;
     
-    // 清空之前的分组结果
-    groupResult.value.clear();
+    const groupId = route.query.id;
+    if (!groupId || Array.isArray(groupId)) {
+      proxy?.$modal.msgError('分组ID不能为空');
+      return;
+    }
+    
+    const res = await generateGroups(groupId);
+    const data = res.data;
     
-    // 筛选符合条件的运动员
-    const eligibleAthletes = athletes.value.filter(athlete => {
+    if (data.success) {
+      // 更新分组结果
+      groupResult.value.clear();
       
-      // 检查是否参与该项目
-      if (!athlete.projectList) {
-        return false;
+      // 设置分组结果
+      if (data.groupResult) {
+        Object.entries(data.groupResult).forEach(([key, athlete]) => {
+          groupResult.value.set(key, athlete);
+        });
       }
       
-      // 处理项目列表
-      let projectIds: string[] = [];
-      if (Array.isArray(athlete.projectList)) {
-        projectIds = athlete.projectList.map(p => p.toString());
-      } else if (typeof athlete.projectList === 'string') {
-        try {
-          projectIds = JSON.parse(athlete.projectList);
-        } catch (e) {
-          // 如果不是JSON格式,可能是逗号分隔的字符串
-          projectIds = (athlete.projectList as string).split(',').map(p => p.trim());
-        }
+      // 设置其他数据
+      if (data.project) {
+        projectName.value = data.project.projectName || '';
+        roundType.value = data.project.roundType || 0;
       }
       
-      const targetProjectId = groupInfo.value.projectId?.toString();
-      const hasProject = projectIds.includes(targetProjectId);
-      
-      if (!hasProject) {
-        return false;
+      if (data.totalAthletes !== undefined) {
+        totalAthletes.value = data.totalAthletes;
       }
       
-      // 检查性别是否匹配
-      if (groupInfo.value.memberGender && groupInfo.value.memberGender !== '0') {
-        // 使用字典来匹配性别,而不是硬编码的字符串
-        if (athlete.gender?.toString() !== groupInfo.value.memberGender?.toString()) {
-          return false;
-        }
+      // 设置队伍信息
+      if (data.teams) {
+        groupResult.value.set('teams', data.teams);
       }
       
-      return true;
-    });
-    
-    if (eligibleAthletes.length === 0) {
-      proxy?.$modal.msgWarning('没有找到符合条件的运动员');
-      return;
-    }
-    
-    // 随机打乱运动员顺序
-    const shuffledAthletes = [...eligibleAthletes].sort(() => Math.random() - 0.5);
-    
-    // 记录已分配的运动员ID,避免重复分配
-    const assignedAthleteIds = new Set();
-    
-    // 按组别和道次分配运动员
-    for (let groupIndex = 1; groupIndex <= groupInfo.value.includeGroupNum; groupIndex++) {
-      for (let track = 1; track <= groupInfo.value.trackNum; track++) {
-        // 寻找可用的运动员
-        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);
-        }
-      }
+      proxy?.$modal.msgSuccess('分组生成成功');
+    } else {
+      proxy?.$modal.msgWarning(data.message || '生成分组失败');
     }
-    
-    proxy?.$modal.msgSuccess('分组生成成功');
   } catch (error) {
     console.error('生成分组失败:', error);
     proxy?.$modal.msgError('生成分组失败');
@@ -412,6 +287,11 @@ const generateGroups = async () => {
   }
 };
 
+// 重新生成分组
+const regenerateGroups = async () => {
+  await generateGroupsData();
+};
+
 // 返回上一页
 const goBack = () => {
   router.go(-1);

+ 222 - 43
src/views/system/gameEventGroup/index.vue

@@ -6,12 +6,22 @@
           <el-form ref="queryFormRef" :model="queryParams" :inline="true">
             <el-form-item label="项目类型" prop="projectTypeFilter">
               <el-select v-model="projectTypeFilter" placeholder="请选择项目类型" clearable @change="handleProjectTypeFilterChange">
-                <el-option v-for="dict in game_project_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+                <el-option
+                  v-for="dict in game_project_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
               </el-select>
             </el-form-item>
             <el-form-item label="项目" prop="projectId">
               <el-select v-model="queryParams.projectId" placeholder="请选择项目" clearable>
-                <el-option v-for="project in filteredProjectList" :key="project.projectId" :label="project.projectName" :value="project.projectId" />
+                <el-option
+                  v-for="project in filteredProjectList"
+                  :key="project.projectId"
+                  :label="project.projectName"
+                  :value="project.projectId"
+                />
               </el-select>
             </el-form-item>
             <el-form-item label="组别" prop="groupName">
@@ -69,7 +79,10 @@
         <el-table-column label="场地数量" align="center" prop="fieldNum" v-if="columns[7].visible" />
         <el-table-column label="每组用时(分钟)" align="center" prop="duration" v-if="columns[8].visible" />
         <el-table-column label="比赛时间" align="center" v-if="columns[9].visible">
-          <template #default="scope"> {{ scope.row.beginTime }} - {{ scope.row.endTime }} </template>
+          <template #default="scope">
+            {{ formatFullDateTime(scope.row.beginTime) }} ~ {{ formatFullDateTime(scope.row.endTime) }}
+            <!-- {{ scope.row.beginTime }} ~ {{ scope.row.endTime }} -->
+          </template>
         </el-table-column>
         <el-table-column label="成员性别" align="center" prop="memberGender" v-if="columns[10].visible">
           <template #default="scope">
@@ -107,7 +120,12 @@
                 @change="handleFormProjectTypeFilterChange"
                 :disabled="!!form.groupId"
               >
-                <el-option v-for="dict in game_project_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+                <el-option
+                  v-for="dict in game_project_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
               </el-select>
             </el-form-item>
           </el-col>
@@ -140,7 +158,12 @@
           <el-col :span="12">
             <el-form-item label="成员性别" prop="memberGender">
               <el-select v-model="form.memberGender" placeholder="请选择性别" style="width: 100%">
-                <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value" />
+                <el-option
+                  v-for="dict in sys_user_sex"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
               </el-select>
             </el-form-item>
           </el-col>
@@ -180,22 +203,29 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="组别开始时间" prop="beginTime">
-              <el-time-picker
+              <el-date-picker
                 v-model="form.beginTime"
-                placeholder="选择开始时间"
-                format="HH:mm"
-                value-format="HH:mm"
+                type="datetime"
+                placeholder="请选择开始时间"
+                format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss"
                 style="width: 100%"
                 :disabled="!form.projectId"
+                :disabled-date="disabledDate"
               />
             </el-form-item>
-          </el-col>
+        </el-col>
         </el-row>
 
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="预计结束时间" prop="endTime">
-              <el-input v-model="calculatedEndTime" placeholder="自动计算" disabled style="width: 100%" />
+            <el-form-item label="组别结束时间" prop="endTime">
+              <el-input 
+                v-model="calculatedEndTime" 
+                placeholder="自动计算" 
+                disabled 
+                style="width: 100%" 
+              />
             </el-form-item>
           </el-col>
         </el-row>
@@ -254,7 +284,7 @@ const columns = ref<FieldOption[]>([
   { key: 7, label: '场地数量', visible: true },
   { key: 8, label: '每组用时(分钟)', visible: true },
   { key: 9, label: '比赛时间', visible: true },
-  { key: 10, label: '成员性别', visible: true }
+  { key: 10, label: '成员性别', visible: true },
 ]);
 
 const queryFormRef = ref<ElFormInstance>();
@@ -314,42 +344,116 @@ const { queryParams, form, rules } = toRefs(data);
 // 过滤后的项目列表(用于查询)
 const filteredProjectList = computed(() => {
   if (!projectTypeFilter.value) return projectList.value;
-  return projectList.value.filter((project) => project.projectType === projectTypeFilter.value);
+  return projectList.value.filter(project => project.projectType === projectTypeFilter.value);
 });
 
 // 过滤后的项目列表(用于表单)
 const filteredFormProjectList = computed(() => {
   if (!formProjectTypeFilter.value) return [];
-  return projectList.value.filter((project) => project.projectType === formProjectTypeFilter.value);
+  return projectList.value.filter(project => project.projectType === formProjectTypeFilter.value);
 });
 
 // 根据项目ID获取项目类型
 const getProjectTypeByProjectId = (projectId: string | number) => {
   if (!projectId) return '';
-  const project = projectList.value.find((p) => p.projectId === projectId);
+  const project = projectList.value.find(p => p.projectId === projectId);
   return project?.projectType || '';
 };
 
 // 根据项目ID获取项目名称
 const getProjectNameByProjectId = (projectId: string | number) => {
   if (!projectId) return '';
-  const project = projectList.value.find((p) => p.projectId === projectId);
+  const project = projectList.value.find(p => p.projectId === projectId);
   return project?.projectName || '';
 };
 
+// 根据项目ID获取项目开始时间
+const getProjectStartTimeByProjectId = (projectId: string | number) => {
+  if (!projectId) return '';
+  const project = projectList.value.find(p => p.projectId === projectId);
+  if (project && project.startTime) {
+    try {
+      const startDate = new Date(project.startTime);
+      if (!isNaN(startDate.getTime())) {
+        const year = startDate.getFullYear();
+        const month = String(startDate.getMonth() + 1).padStart(2, '0');
+        const day = String(startDate.getDate()).padStart(2, '0');
+        const hours = String(startDate.getHours()).padStart(2, '0');
+        const minutes = String(startDate.getMinutes()).padStart(2, '0');
+        const seconds = String(startDate.getSeconds()).padStart(2, '0');
+        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+      }
+    } catch (error) {
+      console.warn('项目开始时间解析失败:', error);
+    }
+  }
+  return '';
+};
+
 // 计算预计结束时间
 const calculatedEndTime = computed(() => {
   if (!form.value.beginTime || !form.value.duration || !form.value.includeGroupNum) {
     return '';
   }
 
-  const beginTime = new Date(`2000-01-01 ${form.value.beginTime}`);
+  const beginTime = new Date(form.value.beginTime);
+  if (isNaN(beginTime.getTime())) return '';
+  
   const totalMinutes = form.value.duration * form.value.includeGroupNum;
   const endTime = new Date(beginTime.getTime() + totalMinutes * 60 * 1000);
 
-  return endTime.toTimeString().slice(0, 5); // 返回 HH:mm 格式
+  const year = endTime.getFullYear();
+  const month = String(endTime.getMonth() + 1).padStart(2, '0');
+  const day = String(endTime.getDate()).padStart(2, '0');
+  const hours = String(endTime.getHours()).padStart(2, '0');
+  const minutes = String(endTime.getMinutes()).padStart(2, '0');
+  const seconds = String(endTime.getSeconds()).padStart(2, '0');
+  
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
 });
 
+// 禁用日期(限制在项目时间范围内)
+const disabledDate = (time: Date) => {
+  if (!form.value.projectId) return false;
+  const project = projectList.value.find(p => p.projectId === form.value.projectId);
+  if (!project || !project.startTime || !project.endTime) return false;
+
+  const projectStart = new Date(project.startTime);
+  const projectEnd = new Date(project.endTime);
+
+  // 禁用项目时间范围外的日期
+  return time <= projectStart || time >= projectEnd;
+};
+
+// 格式化完整日期时间
+const formatFullDateTime = (dateTime: string | null | undefined): string => {
+  if (!dateTime) return '-';
+  
+  // 增加对空字符串的检查
+  if (dateTime === '') return '-';
+  
+  let date: Date;
+  try {
+    // 尝试解析时间字符串
+    date = new Date(dateTime);
+    if (isNaN(date.getTime())) {
+      return '-';
+    }
+  } catch (error) {
+    console.warn('时间格式化失败:', error);
+    return '-';
+  }
+  
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0');
+  const day = String(date.getDate()).padStart(2, '0');
+  const hours = String(date.getHours()).padStart(2, '0');
+  const minutes = String(date.getMinutes()).padStart(2, '0');
+  const seconds = String(date.getSeconds()).padStart(2, '0');
+  
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+};
+
 /** 查询赛事分组列表 */
 const getList = async () => {
   loading.value = true;
@@ -383,12 +487,15 @@ const handleFormProjectTypeFilterChange = () => {
   form.value.trackNum = undefined;
   form.value.fieldNum = undefined;
   form.value.duration = undefined;
+
+  // 重新获取项目列表以更新过滤
+  getProjectList();
 };
 
 // 表单中项目变化
 const handleFormProjectChange = () => {
   if (form.value.projectId) {
-    const selectedProject = projectList.value.find((p) => p.projectId === form.value.projectId);
+    const selectedProject = projectList.value.find(p => p.projectId === form.value.projectId);
     if (selectedProject) {
       // 可以在这里设置一些默认值或者进行其他处理
       console.log('选中的项目:', selectedProject);
@@ -437,6 +544,12 @@ const handleAdd = () => {
   // 获取项目列表
   nextTick(() => {
     getProjectList();
+    // 监听项目选择变化,设置默认开始时间
+    watch(() => form.value.projectId, (newProjectId) => {
+      if (newProjectId && !form.value.beginTime) {
+        form.value.beginTime = getProjectStartTimeByProjectId(newProjectId);
+      }
+    });
   });
 };
 
@@ -449,12 +562,50 @@ const handleUpdate = async (row?: GameEventGroupVO) => {
 
   // 根据项目ID设置项目类型过滤器
   if (res.data.projectId) {
-    const project = projectList.value.find((p) => p.projectId === res.data.projectId);
+    const project = projectList.value.find(p => p.projectId === res.data.projectId);
     if (project) {
       formProjectTypeFilter.value = project.projectType;
     }
   }
 
+  // 处理时间字段的回显,增加安全检查
+  if (res.data.beginTime) {
+    try {
+      // 尝试解析时间字符串
+      const date = new Date(res.data.beginTime);
+      if (!isNaN(date.getTime())) {
+        // 如果解析成功,转换为正确的格式
+        form.value.beginTime = formatFullDateTime(date.toString());
+        // form.value.beginTime = date.toISOString().replace('T', ' ');
+      } else {
+        // 如果解析失败,设置为默认值
+        console.warn('开始时间格式无效:', res.data.beginTime);
+        form.value.beginTime = '';
+      }
+    } catch (error) {
+      console.warn('开始时间解析失败:', error);
+      form.value.beginTime = '';
+    }
+  }
+  
+  if (res.data.endTime) {
+    try {
+      // 尝试解析时间字符串
+      const date = new Date(res.data.endTime);
+      if (!isNaN(date.getTime())) {
+        // 如果解析成功,转换为正确的格式
+        form.value.endTime = formatFullDateTime(date.toString());
+      } else {
+        // 如果解析失败,设置为默认值
+        console.warn('结束时间格式无效:', res.data.endTime);
+        form.value.endTime = '';
+      }
+    } catch (error) {
+      console.warn('结束时间解析失败:', error);
+      form.value.endTime = '';
+    }
+  }
+
   dialog.visible = true;
   dialog.title = '修改赛事分组';
   // 获取项目列表
@@ -477,36 +628,64 @@ const submitForm = () => {
         }
       }
 
-      // 自动计算结束时间
+      // 自动计算结束时间并赋值
       if (form.value.beginTime && form.value.duration && form.value.includeGroupNum) {
-        const beginTime = new Date(`2000-01-01 ${form.value.beginTime}`);
-        const totalMinutes = form.value.duration * form.value.includeGroupNum;
-        const endTime = new Date(beginTime.getTime() + totalMinutes * 60 * 1000);
-        form.value.endTime = endTime.toTimeString().slice(0, 5);
+        try {
+          const beginTime = new Date(form.value.beginTime);
+          if (isNaN(beginTime.getTime())) {
+            proxy?.$modal.msgError('开始时间格式无效');
+            return;
+          }
+          
+          const totalMinutes = form.value.duration * form.value.includeGroupNum;
+          const endTime = new Date(beginTime.getTime() + totalMinutes * 60 * 1000);
+          
+          const year = endTime.getFullYear();
+          const month = String(endTime.getMonth() + 1).padStart(2, '0');
+          const day = String(endTime.getDate()).padStart(2, '0');
+          const hours = String(endTime.getHours()).padStart(2, '0');
+          const minutes = String(endTime.getMinutes()).padStart(2, '0');
+          const seconds = String(endTime.getSeconds()).padStart(2, '0');
+          
+          form.value.endTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+        } catch (error) {
+          proxy?.$modal.msgError('计算结束时间失败');
+          return;
+        }
       }
 
       // 验证时间范围
       if (form.value.beginTime && form.value.endTime) {
-        const beginTime = new Date(`2000-01-01 ${form.value.beginTime}`);
-        const endTime = new Date(`2000-01-01 ${form.value.endTime}`);
-
-        if (beginTime >= endTime) {
-          proxy?.$modal.msgError('组别结束时间必须晚于开始时间');
-          return;
-        }
-
-        // 验证组别时间是否在项目时间范围内
-        if (form.value.projectId) {
-          const selectedProject = projectList.value.find((p) => p.projectId === form.value.projectId);
-          if (selectedProject && selectedProject.startTime && selectedProject.endTime) {
-            const projectStart = new Date(`2000-01-01 ${selectedProject.startTime}`);
-            const projectEnd = new Date(`2000-01-01 ${selectedProject.endTime}`);
+        try {
+          const beginTime = new Date(form.value.beginTime);
+          const endTime = new Date(form.value.endTime);
+          
+          if (isNaN(beginTime.getTime()) || isNaN(endTime.getTime())) {
+            proxy?.$modal.msgError('时间格式无效');
+            return;
+          }
+          
+          if (beginTime >= endTime) {
+            proxy?.$modal.msgError('组别结束时间必须晚于开始时间');
+            return;
+          }
 
-            if (beginTime < projectStart || endTime > projectEnd) {
-              proxy?.$modal.msgError('组别比赛时间必须在项目比赛时间范围内');
-              return;
+          // 验证组别时间是否在项目时间范围内
+          if (form.value.projectId) {
+            const selectedProject = projectList.value.find(p => p.projectId === form.value.projectId);
+            if (selectedProject && selectedProject.startTime && selectedProject.endTime) {
+              const projectStart = new Date(selectedProject.startTime);
+              const projectEnd = new Date(selectedProject.endTime);
+
+              if (beginTime <= projectStart || endTime >= projectEnd) {
+                proxy?.$modal.msgError('组别比赛时间必须在项目比赛时间范围内');
+                return;
+              }
             }
           }
+        } catch (error) {
+          proxy?.$modal.msgError('时间验证失败');
+          return;
         }
       }
 

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

@@ -33,9 +33,9 @@
           <el-col :span="1.5">
             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:menu:remove']">删除</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:menu:export']">导出</el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>

+ 272 - 0
src/views/system/gameEventProject/ProjectLibraryDialog.vue

@@ -0,0 +1,272 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="项目库"
+    width="80%"
+    :before-close="handleClose"
+    append-to-body
+  >
+    <div class="project-library">
+      <!-- 搜索区域 -->
+      <div class="search-area mb-4">
+        <el-form :model="searchForm" :inline="true">
+          <el-form-item label="来源赛事">
+            <el-select
+              v-model="searchForm.eventId"
+              placeholder="请选择来源赛事"
+              clearable
+              @change="handleSearch"
+            >
+              <el-option
+                v-for="event in eventList"
+                :key="event.eventId"
+                :label="event.eventName"
+                :value="event.eventId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="项目名称">
+            <el-input
+              v-model="searchForm.projectName"
+              placeholder="请输入项目名称"
+              clearable
+              @keyup.enter="handleSearch"
+            />
+          </el-form-item>
+          <el-form-item label="项目类型">
+            <el-select
+              v-model="searchForm.projectType"
+              placeholder="请选择项目类型"
+              clearable
+              @change="handleSearch"
+            >
+              <el-option
+                v-for="dict in game_project_type"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="归类">
+            <el-select
+              v-model="searchForm.classification"
+              placeholder="请选择归类"
+              clearable
+              @change="handleSearch"
+            >
+              <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-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
+            <el-button icon="Refresh" @click="resetSearch">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <!-- 项目列表 -->
+      <el-table
+        v-loading="loading"
+        :data="projectList"
+        @selection-change="handleSelectionChange"
+        height="400"
+        border
+      >
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="项目名称" prop="projectName" min-width="120" />
+        <el-table-column label="项目类型" prop="projectType" width="100">
+          <template #default="scope">
+            <dict-tag :options="game_project_type" :value="scope.row.projectType || ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="归类" prop="classification" width="100">
+          <template #default="scope">
+            <dict-tag :options="game_project_classification" :value="scope.row.classification || '未知'" />
+          </template>
+        </el-table-column>
+        <el-table-column label="比赛场地" prop="location" min-width="120" />
+        <el-table-column label="计算规则" prop="scoreRule" width="100">
+          <template #default="scope">
+            <dict-tag :options="game_score_type" :value="scope.row.scoreRule || ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="比赛轮次" prop="gameRound" width="100">
+          <template #default="scope">
+            <dict-tag :options="game_round" :value="scope.row.gameRound || ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="比赛阶段" prop="gameStage" width="100">
+          <template #default="scope">
+            <dict-tag :options="game_stage" :value="scope.row.gameStage || ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="排序方式" prop="orderType" width="100">
+          <template #default="scope">
+            {{ scope.row.orderType === '0' ? '升序' : '降序' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="录取名次" prop="roundType" width="100" />
+        <el-table-column label="积分分值" prop="scoreValue" width="120" />
+        <el-table-column label="来源赛事" prop="eventName" min-width="120" />
+      </el-table>
+
+      <!-- 分页 -->
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="searchForm.pageNum"
+        v-model:limit="searchForm.pageSize"
+        @pagination="getProjectList"
+      />
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button
+          type="primary"
+          :disabled="selectedProjects.length === 0"
+          @click="handleConfirm"
+        >
+          确定选择 ({{ selectedProjects.length }})
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup name="ProjectLibraryDialog" lang="ts">
+import { ref, reactive, defineExpose, onMounted } from 'vue';
+import { listAllGameEventProject } from '@/api/system/gameEventProject';
+import { listGameEvent } from '@/api/system/gameEvent';
+import { GameEventProjectVO, GameEventProjectQuery } from '@/api/system/gameEventProject/types';
+import { GameEventVO } from '@/api/system/gameEvent/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { game_score_type, game_project_type, game_project_classification, game_round, game_stage } = toRefs<any>(
+  proxy?.useDict('game_score_type', 'game_project_type', 'game_project_classification', 'game_round', 'game_stage')
+);
+
+const visible = ref(false);
+const loading = ref(false);
+const projectList = ref<GameEventProjectVO[]>([]);
+const selectedProjects = ref<GameEventProjectVO[]>([]);
+const total = ref(0);
+const eventList = ref<GameEventVO[]>([]);
+
+const searchForm = reactive<GameEventProjectQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  eventId: undefined,
+  projectName: undefined,
+  projectType: undefined,
+  classification: undefined,
+  orderByColumn: undefined,
+  isAsc: undefined,
+});
+
+// 打开对话框
+const openDialog = () => {
+  visible.value = true;
+  resetSearch();
+  getProjectList();
+};
+
+// 关闭对话框
+const handleClose = () => {
+  visible.value = false;
+  selectedProjects.value = [];
+};
+
+// 搜索
+const handleSearch = () => {
+  searchForm.pageNum = 1;
+  getProjectList();
+};
+
+// 重置搜索
+const resetSearch = () => {
+  searchForm.eventId = undefined;
+  searchForm.projectName = undefined;
+  searchForm.projectType = undefined;
+  searchForm.classification = undefined;
+  searchForm.pageNum = 1;
+  handleSearch();
+};
+
+// 获取项目列表
+const getProjectList = async () => {
+  loading.value = true;
+  try {
+    const res = await listAllGameEventProject(searchForm);
+    projectList.value = res.rows || [];
+    total.value = res.total || 0;
+  } catch (error) {
+    proxy?.$modal.msgError('获取项目列表失败');
+  } finally {
+    loading.value = false;
+  }
+};
+// 获取赛事列表
+const getEventList = async () => {
+  try {
+    const res = await listGameEvent({
+      pageNum: 1,
+      pageSize: 10,
+      orderByColumn: undefined,
+      isAsc: undefined,
+    });
+    eventList.value = res.rows || [];
+  } catch (error) {
+    proxy?.$modal.msgError('获取赛事列表失败');
+  }
+};
+// 选择变化
+const handleSelectionChange = (selection: GameEventProjectVO[]) => {
+  selectedProjects.value = selection;
+};
+
+// 确认选择
+const handleConfirm = () => {
+  if (selectedProjects.value.length === 0) {
+    proxy?.$modal.msgWarning('请选择至少一个项目');
+    return;
+  }
+  
+  // 触发父组件的确认事件
+  emit('confirm', selectedProjects.value);
+  handleClose();
+};
+
+// 定义事件
+const emit = defineEmits<{
+  confirm: [projects: GameEventProjectVO[]]
+}>();
+
+// 暴露方法给父组件
+defineExpose({
+  openDialog
+});
+
+// 组件挂载时获取赛事列表
+onMounted(() => {
+  getEventList();
+});
+
+</script>
+
+<style scoped lang="scss">
+.project-library {
+  .search-area {
+    background: #f5f5f5;
+    padding: 16px;
+    border-radius: 4px;
+  }
+}
+</style>

+ 2 - 2
src/views/system/gameEventProject/RefereeGroupDialog.vue

@@ -44,7 +44,7 @@ interface GameRefereeVOExt extends GameRefereeVO {
   projectNameList?: string;
 }
 
-const openDialog = async (refereeGroups: string[], projectNameParam: string) => {
+const openDialog = async (refereeGroups: number[], projectNameParam: string) => {
   projectName.value = projectNameParam;
   dialog.visible = true;
   loading.value = true;
@@ -52,7 +52,7 @@ const openDialog = async (refereeGroups: string[], projectNameParam: string) =>
   try {
     if (refereeGroups) {
       // 解析裁判组ID列表
-      const refereeIds = refereeGroups.filter(id => id.trim());
+      const refereeIds = refereeGroups.filter(id => id);
       
       if (refereeIds.length > 0) {
         // 获取裁判详细信息

+ 115 - 70
src/views/system/gameEventProject/index.vue

@@ -27,6 +27,9 @@
           <el-col :span="1.5">
             <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:gameEventProject:add']"> 新增 </el-button>
           </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Collection" @click="handleViewProjectLibrary()"> 项目库 </el-button>
+          </el-col>
           <el-col :span="1.5">
             <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:gameEventProject:edit']"
               >修改
@@ -46,14 +49,14 @@
 
       <el-table v-loading="loading" border :data="gameEventProjectList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="主键" align="center" prop="projectId" v-if="columns[0].visible" />
+        <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="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="classification" 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>
@@ -66,12 +69,12 @@
         </el-table-column>
         <el-table-column label="开始时间" align="center" prop="startTime" width="180" v-if="columns[6].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="结束时间" align="center" prop="endTime" width="180" v-if="columns[7].visible">
           <template #default="scope">
-            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
           </template>
         </el-table-column>
         <el-table-column label="裁判组" align="center" prop="refereeGroup" v-if="columns[8].visible">
@@ -84,23 +87,31 @@
             >
               查看裁判组 ({{ scope.row.refereeGroups.length }}人)
             </el-button>
-            <span v-else style="color: #999">暂无裁判</span>
+            <span v-else style="color: #999;">暂无裁判</span>
           </template>
         </el-table-column>
         <!-- <el-table-column label="参赛组数" align="center" prop="groupNum" />
         <el-table-column label="参赛人数" align="center" prop="participateNum" /> -->
-        <el-table-column label="次" align="center" prop="roundType" v-if="columns[9].visible" />
+        <el-table-column label="录取名次" align="center" prop="roundType" v-if="columns[9].visible" />
         <el-table-column label="排序方式" align="center" prop="orderType" v-if="columns[10].visible">
           <template #default="scope">
             {{ scope.row.orderType === '0' ? '升序' : '降序' }}
           </template>
         </el-table-column>
         <el-table-column label="积分分值" align="center" prop="scoreValue" v-if="columns[11].visible" />
-        <!-- <el-table-column label="奖项" align="center" prop="award" />
-        <el-table-column label="计时点名称" align="center" prop="timePoint" />
-        <el-table-column label="控制盒编号" align="center" prop="boxCode" />
-        <el-table-column label="状态" align="center" prop="status" />
-        <el-table-column label="备注" align="center" prop="remark" /> -->
+        <!-- <el-table-column label="奖项" align="center" prop="award" /> -->
+        <el-table-column label="比赛轮次" align="center" prop="gameRound" v-if="columns[12].visible">
+          <template #default="scope">
+            <dict-tag :options="game_round" :value="scope.row.gameRound || ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="比赛阶段" align="center" prop="gameStage" v-if="columns[13].visible">
+          <template #default="scope">
+            <dict-tag :options="game_stage" :value="scope.row.gameStage || ''" />
+          </template>
+        </el-table-column>
+        <!-- <el-table-column label="状态" align="center" prop="status" /> -->
+        <!-- <el-table-column label="备注" align="center" prop="remark" /> -->
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
@@ -146,6 +157,16 @@
         <el-form-item label="参赛人数" prop="participateNum">
           <el-input v-model="form.participateNum" placeholder="请输入参赛人数" />
         </el-form-item> -->
+        <el-form-item label="比赛阶段" prop="gameStage">
+          <el-radio-group v-model="form.gameStage" style="width: 100%">
+            <el-radio v-for="dict in game_stage" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="比赛轮次" prop="gameRound">
+          <el-radio-group v-model="form.gameRound" style="width: 100%">
+            <el-radio v-for="dict in game_round" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="计算规则" prop="scoreRule">
           <el-select v-model="form.scoreRule" placeholder="请选择计算规则" style="width: 100%">
             <el-option v-for="dict in game_score_type" :key="dict.value" :label="dict.label" :value="dict.value" />
@@ -157,11 +178,20 @@
             <el-radio value="1">降序</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="积分分值" prop="scoreValue">
-          <el-input v-model="form.scoreValue" placeholder="请输入积分分值" />
+        <el-form-item label="录取名次" prop="roundType">
+          <el-input v-model="form.roundType" placeholder="请输入录取名次" />
         </el-form-item>
-        <el-form-item label="轮次" prop="roundType">
-          <el-input v-model="form.roundType" placeholder="请输入轮次" />
+        <el-form-item label="积分分值" prop="scoreValue">
+          <template #label>
+            <span>
+              <el-tooltip content="各值之间以英文逗号分隔" placement="top">
+                <el-icon>
+                  <question-filled />
+                </el-icon>
+              </el-tooltip>
+            积分</span>
+          </template>
+          <el-input v-model="form.scoreValue" placeholder="请输入名次对应的积分" />
         </el-form-item>
         <el-form-item label="开始时间" prop="startTime">
           <el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间">
@@ -173,14 +203,9 @@
         </el-form-item>
         <!-- <el-form-item label="奖项" prop="award">
           <el-input v-model="form.award" placeholder="请输入奖项" />
-        </el-form-item>
-        <el-form-item label="计时点名称" prop="timePoint">
-          <el-input v-model="form.timePoint" placeholder="请输入计时点名称" />
-        </el-form-item>
-        <el-form-item label="控制盒编号" prop="boxCode">
-          <el-input v-model="form.boxCode" placeholder="请输入控制盒编号" />
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
+        </el-form-item> -->
+        
+        <!-- <el-form-item label="备注" prop="remark">
             <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
         </el-form-item> -->
       </el-form>
@@ -194,6 +219,9 @@
 
     <!-- 裁判组查看对话框 -->
     <RefereeGroupDialog ref="refereeGroupDialogRef" />
+
+    <!-- 项目库查看对话框 -->
+    <ProjectLibraryDialog ref="projectLibraryDialog" @confirm="handleLibraryConfirm" />
   </div>
 </template>
 
@@ -203,18 +231,18 @@ import {
   getGameEventProject,
   delGameEventProject,
   addGameEventProject,
-  updateGameEventProject
+  updateGameEventProject,
+  BatchAddProject
 } from '@/api/system/gameEventProject';
 import { listGameEventGroup } from '@/api/system/gameEventGroup';
 import { GameEventProjectVO, GameEventProjectQuery, GameEventProjectForm } from '@/api/system/gameEventProject/types';
 import RefereeGroupDialog from './RefereeGroupDialog.vue';
+import ProjectLibraryDialog from './ProjectLibraryDialog.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { game_score_type, game_project_type, game_project_classification } = toRefs<any>(
-  proxy?.useDict('game_score_type', 'game_project_type', 'game_project_classification')
-);
+const { game_score_type, game_project_type, game_project_classification, game_round, game_stage } = toRefs<any>(proxy?.useDict('game_score_type', 'game_project_type', 'game_project_classification', 'game_round', 'game_stage'));
 
-const gameEventGroupList = ref<any[]>([]); // 赛事分组列表
+const gameEventGroupList = ref<any[]>([]); // ���·����б�
 
 const gameEventProjectList = ref<GameEventProjectVO[]>([]);
 const buttonLoading = ref(false);
@@ -232,8 +260,13 @@ const gameEventProjectFormRef = ref<ElFormInstance>();
 interface RefereeGroupDialogInstance {
   openDialog: (refereeGroups: string[], projectName: string) => void;
 }
+// 定义 ProjectLibraryDialog 组件的类型
+interface ProjectLibraryDialogInstance {
+  openDialog: () => void;
+}
 
 const refereeGroupDialogRef = ref<(InstanceType<typeof RefereeGroupDialog> & RefereeGroupDialogInstance) | null>(null);
+const projectLibraryDialog = ref<(InstanceType<typeof ProjectLibraryDialog> & ProjectLibraryDialogInstance) | null>(null);
 
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -242,7 +275,7 @@ const dialog = reactive<DialogOption>({
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '主键', visible: false },
+  { key: 0, label: '项目id', visible: false },
   { key: 1, label: '项目名称', visible: true },
   { key: 2, label: '项目类型', visible: true },
   { key: 3, label: '归类', visible: true },
@@ -251,9 +284,11 @@ const columns = ref<FieldOption[]>([
   { key: 6, label: '开始时间', visible: true },
   { key: 7, label: '结束时间', visible: true },
   { key: 8, label: '裁判组', visible: true },
-  { key: 9, label: '轮次', visible: true },
-  { key: 10, label: '排序方式', visible: true },
-  { key: 11, label: '积分分值', visible: true }
+  { key: 9, label: '录取名次', visible: true },
+  { key: 10, label: '积分分值', visible: true },
+  { key: 11, label: '排序方式', visible: true },
+  { key: 12, label: '比赛轮次', visible: true },
+  { key: 13, label: '比赛阶段', visible: true },
 ]);
 
 const initFormData: GameEventProjectForm = {
@@ -273,8 +308,8 @@ const initFormData: GameEventProjectForm = {
   scoreRule: undefined,
   scoreValue: undefined,
   award: undefined,
-  timePoint: undefined,
-  boxCode: undefined,
+  gameRound: undefined,
+  gameStage: undefined,
   status: undefined,
   remark: undefined
 };
@@ -285,13 +320,13 @@ const data = reactive<PageData<GameEventProjectForm, GameEventProjectQuery>>({
     pageSize: 10,
     classification: undefined,
     orderByColumn: undefined,
-    isAsc: undefined
+    isAsc: undefined,
   },
   rules: {
     projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
     projectType: [{ required: true, message: '项目类型不能为空', trigger: 'change' }],
     classification: [{ required: true, message: '归类不能为空', trigger: 'change' }],
-    orderType: [{ required: true, message: '排序方式不能为空', trigger: 'change' }]
+    orderType: [{ required: true, message: '排序方式不能为空', trigger: 'change' }],
   }
 });
 
@@ -312,7 +347,7 @@ const getGameEventGroupList = async () => {
     pageNum: 1,
     pageSize: 10,
     orderByColumn: undefined,
-    isAsc: undefined
+    isAsc: undefined,
   });
   gameEventGroupList.value = res.data;
 };
@@ -343,7 +378,7 @@ const resetQuery = () => {
 
 /** 多选框选中数据 */
 const handleSelectionChange = (selection: any[]) => {
-  ids.value = selection.map((item) => item.projectId);
+  ids.value = selection.map(item => item.projectId);
   single.value = selection.length !== 1;
   multiple.value = !selection.length;
 };
@@ -359,7 +394,7 @@ const handleAdd = () => {
 const handleUpdate = (row?: GameEventProjectVO) => {
   reset();
   const projectId = row?.projectId || ids.value.at(0);
-  getGameEventProject(projectId).then((response) => {
+  getGameEventProject(projectId).then(response => {
     Object.assign(form.value, response.data);
     dialog.visible = true;
     dialog.title = '修改赛事项目';
@@ -368,29 +403,25 @@ const handleUpdate = (row?: GameEventProjectVO) => {
 
 /** 提交按钮 */
 const submitForm = () => {
-  gameEventProjectFormRef.value?.validate((valid) => {
+  gameEventProjectFormRef.value?.validate(valid => {
     if (valid) {
       buttonLoading.value = true;
       if (form.value.projectId !== undefined) {
-        updateGameEventProject(form.value)
-          .then((response) => {
-            proxy?.$modal.msgSuccess('修改成功');
-            dialog.visible = false;
-            getList();
-          })
-          .finally(() => {
-            buttonLoading.value = false;
-          });
+        updateGameEventProject(form.value).then(response => {
+          proxy?.$modal.msgSuccess('修改成功');
+          dialog.visible = false;
+          getList();
+        }).finally(() => {
+          buttonLoading.value = false;
+        });
       } else {
-        addGameEventProject(form.value)
-          .then((response) => {
-            proxy?.$modal.msgSuccess('新增成功');
-            dialog.visible = false;
-            getList();
-          })
-          .finally(() => {
-            buttonLoading.value = false;
-          });
+        addGameEventProject(form.value).then(response => {
+          proxy?.$modal.msgSuccess('新增成功');
+          dialog.visible = false;
+          getList();
+        }).finally(() => {
+          buttonLoading.value = false;
+        });
       }
     }
   });
@@ -399,18 +430,12 @@ const submitForm = () => {
 /** 删除按钮操作 */
 const handleDelete = (row?: GameEventProjectVO) => {
   const projectIds = row?.projectId || ids.value;
-  proxy?.$modal
-    .confirm('是否确认删除赛事项目编号为"' + projectIds + '"的数据项?')
-    .then(function () {
-      return delGameEventProject(projectIds);
-    })
-    .then(() => {
-      getList();
-      proxy?.$modal.msgSuccess('删除成功');
-    })
-    .catch(() => {
-      proxy?.$modal.msgSuccess('删除失败');
-    });
+  proxy?.$modal.confirm('是否确认删除赛事项目编号为"' + projectIds + '"的数据项?').then(function () {
+    return delGameEventProject(projectIds);
+  }).then(() => {
+    getList();
+    proxy?.$modal.msgSuccess('删除成功');
+  }).catch(() => {proxy?.$modal.msgSuccess('删除失败');});
 };
 
 /** 导出按钮操作 */
@@ -429,8 +454,28 @@ const handleViewRefereeGroup = (refereeGroups: string[], projectName: string) =>
   refereeGroupDialogRef.value?.openDialog(refereeGroups, projectName);
 };
 
+/** 查看项目库 */
+const handleViewProjectLibrary = () => {
+  projectLibraryDialog.value?.openDialog();
+};
+
+/** 处理项目库选择确认 */
+const handleLibraryConfirm = async (projects: GameEventProjectVO[]) => {
+  try {
+      // 调用添加接口
+      await BatchAddProject(projects);
+    } catch (error) {
+      console.error('添加项目失败:', error);
+      proxy?.$modal.msgError(`添加项目失败`);
+    }
+  
+  proxy?.$modal.msgSuccess(`成功添加${projects.length}个项目`);
+  getList(); // 刷新列表
+};
+
 onMounted(() => {
   getList();
   // getGameEventGroupList();
 });
+
 </script>

+ 4 - 4
src/views/system/gameEventSchedule/index.vue

@@ -265,7 +265,7 @@ const saveSchedule = async () => {
         eventId: defaultEvent.value.eventId,
         projectName: project.projectName,
         projectType: project.projectType,
-        groupType: project.groupType
+        // groupType: project.groupType
       } as any);
     }
 
@@ -297,7 +297,7 @@ const deleteSchedule = async (project: GameEventProjectVO) => {
       eventId: defaultEvent.value.eventId,
       projectName: project.projectName,
       projectType: project.projectType,
-      groupType: project.groupType
+      // groupType: project.groupType
     } as any);
 
     // 更新本地数据
@@ -332,7 +332,7 @@ const exportSchedule = () => {
   }
 
   // 创建表格数据
-  const headers = ['日期', '时间', '项目名称', '项目类型', '项目组别', '比赛场地', '分组数', '每组人数'];
+  const headers = ['日期', '时间', '项目名称', '项目类型', '比赛场地', '分组数', '每组人数'];
   let csvContent = headers.join(',') + '\n';
 
   scheduleData.value.forEach((item) => {
@@ -341,7 +341,7 @@ const exportSchedule = () => {
       `${item.startTime}-${item.endTime}`,
       item.projectName,
       item.projectType === '0' ? '个人' : '团体',
-      item.groupType,
+      // item.groupType,
       item.location,
       item.groupNum,
       item.participateNum

+ 32 - 113
src/views/system/gameReferee/index.vue

@@ -28,9 +28,9 @@
           <el-col :span="1.5">
             <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameReferee:remove']">删除</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:gameReferee:export']">导出</el-button>
-          </el-col>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
         </el-row>
       </template>
@@ -80,7 +80,7 @@
         <el-form-item label="赛事项目" prop="projectList2">
           <!-- 赛事项目穿梭框 -->
           <el-transfer
-            v-model="form.projectList2"
+            v-model="projectList2Str"
             :data="allProjects"
             :titles="['可选项目', '已选项目']"
             :button-texts="['移除', '添加']"
@@ -114,10 +114,9 @@
 </template>
 
 <script setup name="GameReferee" lang="ts">
-import { listGameReferee, getGameReferee, delGameReferee, addGameReferee, updateGameReferee } from '@/api/system/gameReferee';
+import { listGameReferee, getGameReferee, delGameReferee, addGameReferee, updateGameReferee, generateRefereeQRCode } from '@/api/system/gameReferee';
 import { getGameEventProject, listGameEventProject, updateGameEventProject } from '@/api/system/gameEventProject';
 import { GameRefereeVO, GameRefereeQuery, GameRefereeForm } from '@/api/system/gameReferee/types';
-import { toDataURL } from 'qrcode';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -133,7 +132,7 @@ const allProjects = ref<{key: string, label: string }[]>([]); // 添加项目数
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '裁判ID', visible: true },
+  { key: 0, label: '裁判ID', visible: false },
   { key: 1, label: '裁判姓名', visible: true },
   { key: 2, label: '账号', visible: true },
   { key: 3, label: '密码', visible: true },
@@ -176,7 +175,7 @@ const form = reactive({
   account: undefined,
   password: undefined,
   // projectList: [] as string[], // 存储所负责的项目
-  projectList2: [] as string[], // 存储所负责的项目
+  projectList2: [] as number[], // 存储所负责的项目
   refereeCode: undefined,
   createTime: undefined,
   updateTime: undefined,
@@ -223,7 +222,7 @@ const getList = async () => {
       
       // 从所有项目中筛选出在projectIds中存在的项目,并提取它们的label值(项目名称)
       const projectNames = projectRes.rows
-        .filter(project => projectIds.includes(String(project.projectId)))
+        .filter(project => projectIds.map(id => Number(id)).includes(Number(project.projectId)))
         .map(project => project.projectName);
       
       // 添加projectNameList属性到裁判对象中用于展示
@@ -260,6 +259,14 @@ const reset = () => {
   gameRefereeFormRef.value?.resetFields();
 }
 
+// 添加一个计算属性用于处理projectList2的类型转换
+const projectList2Str = computed({
+  get: () => form.projectList2.map(id => String(id)),
+  set: (value) => {
+    form.projectList2 = value.map(id => Number(id));
+  }
+});
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNum = 1;
@@ -290,7 +297,7 @@ const loadProjects = async () => {
   // 赛事项目穿梭框可选数据拼接
   allProjects.value = res.rows.map(item => ({
     key: String(item.projectId),
-    label: `${item.projectName}-${item.groupType}` 
+    label: `${item.projectName}` 
   }));
 };
 
@@ -308,20 +315,18 @@ const handleUpdate = async (row?: GameRefereeVO) => {
   const _refereeId = row?.refereeId || ids.value[0]
   const res = await getGameReferee(_refereeId);
   Object.assign(form, res.data);
-  
-  // 如果是修改操作,先加载项目列表
+
+  // 等待项目列表加载完成
   await loadProjects();
-  
+
   // 处理项目列表数据格式
-  // if (res.data.projectList && typeof res.data.projectList === 'string') {
-  //   form.projectList = res.data.projectList.split(',').filter(id => id);
-  // } else 
   if (Array.isArray(res.data.projectList2)) {
-    form.projectList2 = res.data.projectList2;
+    // 将数字数组转换为字符串数组,以匹配 allProjects 的 key 类型
+    form.projectList2 = res.data.projectList2.map(id => id);
   } else {
     form.projectList2 = [];
   }
-  
+
   dialog.visible = true;
   dialog.title = "修改裁判";
 }
@@ -331,103 +336,23 @@ const submitForm = async () => {
   const valid = await gameRefereeFormRef.value?.validate();
   if (valid) {
     buttonLoading.value = true;
-    // 提交前处理项目列表数据格式
     const submitForm: any = { ...form };
-    // if (Array.isArray(form.projectList)) {
-    //   submitForm.projectList = form.projectList.join(',');
-    // }
     if (form.projectList2) {
       submitForm.projectList2 = form.projectList2;
     }
 
     try {
       if (form.refereeId) {
-        // 如果是更新操作,需要先获取原裁判信息,然后更新项目表中的裁判组
-        const originalReferee = await getGameReferee(form.refereeId);
-        const originalProjectList = originalReferee.data.projectList2 || [];
-        const newProjectList = submitForm.projectList2 || [];
-        
-        // 获取当前项目列表中的项目ID集合
-        // const currentProjectIds = new Set<string>(newProjectList.filter(id => id.trim()));
-        
-        // 获取原项目列表中的项目ID集合
-        // const originalProjectIds = new Set<string>(originalProjectList.filter(id => id.trim()));
-        
-        // 筛选出在原项目列表中但不在当前项目列表中的项目(即被删除的项目)
-        // const removedProjectIds = Array.from(currentProjectIds).filter(id => !originalProjectIds.has(id));
-        const removedProjectIds = newProjectList.filter(id => !originalProjectList.includes(id));
-        
-        // 从被删除的项目中移除该裁判
-        for (const projectId of removedProjectIds) {
-          const projectRes = await getGameEventProject(projectId);
-          
-          if (projectRes) {
-            const project = projectRes.data;
-            let currentRefereeGroups = project.refereeGroups || [];
-            
-            // 从裁判组中移除该裁判ID
-            if (currentRefereeGroups) {
-              const refereeIds = currentRefereeGroups.filter(id => id.trim() !== String(form.refereeId));
-              // 更新项目表中的裁判组字段
-              await updateGameEventProject({
-                ...project,
-                refereeGroups: refereeIds
-              });
-            }
-          }
-        }
-        
-        // 向新项目中添加该裁判
-        // const newProjectIds = Array.from<string>(currentProjectIds).filter(id => !originalProjectIds.has(
-        const newProjectIds = newProjectList.filter(id => !originalProjectList.includes(id));
-        for (const projectId of newProjectIds) {
-          const projectRes = await getGameEventProject(projectId);
-          
-          if (projectRes) {
-            const project = projectRes.data;
-            let currentRefereeGroups = project.refereeGroups || [];
-            // 如果该项目的裁判组中没有该裁判,则添加
-            if(!project.refereeGroups.includes(String(form.refereeId))){
-              currentRefereeGroups.push(String(form.refereeId)) ;
-              await updateGameEventProject({
-                ...project,
-                refereeGroups: currentRefereeGroups
-              });
-            }
-          }
-        }
-        
         await updateGameReferee(submitForm as GameRefereeForm);
       } else {
-        // 新增裁判
-        const refereeResult = await addGameReferee(submitForm as GameRefereeForm);
-        
-        // 获取新添加的裁判ID
-        const newRefereeId = refereeResult.data?.refereeId || refereeResult.data;
-        
-        // 更新所选项目的裁判组字段
-        if (newRefereeId && form.projectList2.length > 0) {
-          for (const projectId of form.projectList2) {
-            const projectRes = await getGameEventProject(projectId);
-            
-            if (projectRes) {
-              const project = projectRes.data;
-              let currentRefereeGroups = project.refereeGroups || [];
-              currentRefereeGroups.push(String(newRefereeId));
-              await updateGameEventProject({
-                ...project,
-                refereeGroups: currentRefereeGroups
-              });
-            }
-          }
-        }
+        await addGameReferee(submitForm as GameRefereeForm);
       }
       
       proxy?.$modal.msgSuccess("操作成功");
       dialog.visible = false;
       await getList();
     } catch (error) {
-      console.error('操作失败:', error);
+      console.error("操作失败:", error);
       proxy?.$modal.msgError("操作失败");
     } finally {
       buttonLoading.value = false;
@@ -447,11 +372,11 @@ const handleDelete = async (row?: GameRefereeVO) => {
     if (row) {
       const refereeId = row.refereeId;
       if (row.projectList2) {
-        const projectIds = row.projectList2.filter(id => id.trim());
+        const projectIds = row.projectList2.filter(id => id);
         
         for (const projectId of projectIds) {
           // 获取当前项目信息
-          const getProject = await getGameEventProject(projectId.trim());
+          const getProject = await getGameEventProject(projectId);
           const project = getProject.data;
           if (project) {
             
@@ -459,7 +384,7 @@ const handleDelete = async (row?: GameRefereeVO) => {
             
             // 从裁判组中移除该裁判ID
             if (currentRefereeGroups) {
-              const refereeIds = currentRefereeGroups.filter(id => id.trim() !== String(refereeId));
+              const refereeIds = currentRefereeGroups.filter(id => id !== refereeId);
               // 更新项目表中的裁判组字段
               await updateGameEventProject({
                 ...project,
@@ -482,18 +407,12 @@ const handleDelete = async (row?: GameRefereeVO) => {
 /** 生成裁判二维码 */
 const handleGenerateQRCode = async (row: GameRefereeVO) => {
   currentReferee.value = row;
-  // 生成二维码数据,可以包含裁判的基本信息
-  const qrData = {
-    eventId: row.eventId,
-    refereeId: row.refereeId,
-    name: row.name,
-    account: row.account,
-    password: row.password,
-    projectList: row.projectList2,
-  };
   
   try {
-    qrCodeData.value = await toDataURL(JSON.stringify(qrData), { width: 200 });
+    // 生成二维码数据
+    const res = await generateRefereeQRCode(row.refereeId);
+    qrCodeData.value = res.data;
+    console.log('二维码数据:', res);
     qrCodeDialog.visible = true;
   } catch (error) {
     proxy?.$modal.msgError("生成二维码失败");
@@ -522,4 +441,4 @@ onMounted(() => {
   getList();
 });
 
-</script>
+</script>

+ 261 - 0
src/views/system/gameScore/gameScoreBonus.vue

@@ -0,0 +1,261 @@
+<template>
+  <div class="p-2">
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" @click="saveBonusChanges" :loading="saveLoading">
+              <el-icon><Check /></el-icon> 保存修改
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" @click="exportBonusData" :loading="exportLoading">
+              <el-icon><Download /></el-icon> 导出Excel
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button @click="refreshBonusData">
+              <el-icon><Refresh /></el-icon> 刷新数据
+            </el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="loadBonusData" :columns="columns"></right-toolbar>
+        </el-row>
+      </template>
+      
+      <el-table 
+        v-loading="bonusLoading" 
+        border 
+        :data="bonusDataList" 
+        @selection-change="handleBonusSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="序号" align="center" width="80">
+          <template #default="scope">
+            {{ scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="代表队名称" align="center" prop="teamName" width="150" />
+        <el-table-column label="排名" align="center" prop="rank" width="80" />
+        <el-table-column label="总分" align="center" prop="totalScore" width="100" />
+        
+        <!-- 动态项目列 -->
+        <el-table-column 
+          v-for="project in bonusProjectList" 
+          :key="project.projectId" 
+          :label="project.projectName" 
+          align="center" 
+          width="120">
+          <template #default="scope">
+            {{ scope.row.projectScores[project.projectName] || 0 }}
+          </template>
+        </el-table-column>
+        
+        <!-- 领导加分列 - 可编辑 -->
+        <el-table-column label="领导加分" align="center" width="120">
+          <template #default="scope">
+            <el-input 
+              v-model="scope.row.leaderPoint" 
+              type="number" 
+              step="0.01"
+              size="small"
+              @input="calculateTotalScore(scope.row)"
+              @blur="calculateTotalScore(scope.row)" />
+          </template>
+        </el-table-column>
+        
+        <!-- 额外加分列 - 可编辑 -->
+        <el-table-column label="额外加分" align="center" width="120">
+          <template #default="scope">
+            <el-input 
+              v-model="scope.row.extraPoint" 
+              type="number" 
+              step="0.01"
+              size="small"
+              @input="calculateTotalScore(scope.row)"
+              @blur="calculateTotalScore(scope.row)" />
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script setup name="GameScoreBonus" lang="ts">
+import { getBonusData, updateBonusData, exportBonusExcel } from '@/api/system/gameScore';
+import { useGameEventStore } from '@/store/modules/gameEvent';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+// 默认赛事信息
+const gameEventStore = useGameEventStore();
+
+const bonusLoading = ref(false);
+const saveLoading = ref(false);
+const exportLoading = ref(false);
+const showSearch = ref(true);
+
+const bonusDataList = ref<any[]>([]);
+const bonusProjectList = ref<any[]>([]);
+const selectedBonusRows = ref<any[]>([]);
+
+// 列显隐数据
+const columns = ref<FieldOption[]>([
+  { key: 0, label: '序号', visible: true },
+  { 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 },
+]);
+
+// 加载加分数据
+const loadBonusData = async () => {
+  bonusLoading.value = true;
+  try {
+    const event = gameEventStore.defaultEventInfo;
+    const eventId = event?.eventId;
+    
+    if (!eventId) {
+      proxy?.$modal.msgWarning('未指定赛事,无法加载加分数据');
+      return;
+    }
+    
+    const response = await getBonusData({
+      eventId: eventId
+    });
+    
+    bonusDataList.value = response.data.rows || [];
+    bonusProjectList.value = response.data.projects || [];
+    
+    // 初始化加分字段
+    bonusDataList.value.forEach((row: any) => {
+      if (!row.leaderPoint) row.leaderPoint = 0;
+      if (!row.extraPoint) row.extraPoint = 0;
+      calculateTotalScore(row);
+    });
+  } catch (error) {
+    console.error("加载加分数据失败:", error);
+    proxy?.$modal.msgError("加载加分数据失败");
+  } finally {
+    bonusLoading.value = false;
+  }
+};
+
+// 计算总分
+const calculateTotalScore = (row: any) => {
+  let totalScore = 0;
+  
+  // 累加各项目积分
+  Object.values(row.projectScores || {}).forEach((score: any) => {
+    totalScore += Number(score) || 0;
+  });
+  
+  // 加上领导加分和额外加分
+  totalScore += Number(row.leaderPoint) || 0;
+  totalScore += Number(row.extraPoint) || 0;
+  
+  row.totalScore = totalScore;
+  
+  // 重新计算排名
+  calculateBonusRankings();
+};
+
+// 计算excel表排名
+const calculateBonusRankings = () => {
+  // 按总分排序
+  const sortedData = [...bonusDataList.value].sort((a, b) => b.totalScore - a.totalScore);
+  
+  // 更新排名
+  sortedData.forEach((row, index) => {
+    row.rank = index + 1;
+  });
+  
+  // 更新原数组
+  bonusDataList.value = sortedData;
+};
+
+// 选择变化处理
+const handleBonusSelectionChange = (selection: any[]) => {
+  selectedBonusRows.value = selection;
+};
+
+// 保存加分修改
+const saveBonusChanges = async () => {
+  try {
+    saveLoading.value = true;
+    
+    const event = gameEventStore.defaultEventInfo;
+    const eventId = event?.eventId;
+    
+    if (!eventId) {
+      proxy?.$modal.msgWarning('未指定赛事,无法保存');
+      return;
+    }
+    
+    const updateData = bonusDataList.value.map(row => ({
+      teamId: row.teamId,
+      leaderPoint: Number(row.leaderPoint) || 0,
+      extraPoint: Number(row.extraPoint) || 0,
+      totalScore: row.totalScore,
+      rank: row.rank
+    }));
+    
+    await updateBonusData({
+      eventId: eventId,
+      data: updateData
+    });
+    
+    proxy?.$modal.msgSuccess("保存成功");
+  } catch (error) {
+    console.error("保存失败:", error);
+    proxy?.$modal.msgError("保存失败,请稍后再试");
+  } finally {
+    saveLoading.value = false;
+  }
+};
+
+// 导出加分数据方法
+const exportBonusData = async () => {
+  try {
+    exportLoading.value = true;
+    
+    const event = gameEventStore.defaultEventInfo;
+    const eventId = event?.eventId;
+    
+    if (!eventId) {
+      proxy?.$modal.msgWarning('未指定默认赛事,无法导出');
+      return;
+    }
+    
+    // 使用 proxy?.download 方法,传递JSON字符串参数
+    proxy?.download(
+      '/system/gameScore/exportBonusExcel',
+      {
+        eventId: eventId,
+        data: JSON.stringify(bonusDataList.value),
+        projects: JSON.stringify(bonusProjectList.value)
+      },
+      `总成绩排名表_${new Date().toLocaleDateString()}.xlsx`
+    );
+    
+    proxy?.$modal.msgSuccess("导出成功");
+  } catch (error) {
+    console.error("导出失败:", error);
+    proxy?.$modal.msgError("导出失败,请稍后再试");
+  } finally {
+    exportLoading.value = false;
+  }
+};
+
+// 刷新加分数据
+const refreshBonusData = async () => {
+  await loadBonusData();
+};
+
+onMounted(async () => {
+  // 先加载默认赛事信息
+  await gameEventStore.fetchDefaultEvent();
+  // 然后加载加分数据
+  await loadBonusData();
+});
+</script>
+

+ 2 - 4
src/views/system/gameScore/gameScoreEdit.vue

@@ -45,8 +45,6 @@
         
         <!-- 团体项目显示队伍信息 -->
         <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>
         
@@ -143,7 +141,7 @@ const queryParams = reactive({
 //刷新数据方法
 const refreshData = () => {
   searchValue.value = '';
-  loadData(true); // 刷新时自动计算排名
+  loadData(false); // 刷新时不自动计算排名,需要手动点击计算排名按钮
 };
 
 // 计算排名方法
@@ -412,6 +410,6 @@ const handlePagination = (paginationData: { page: number, limit: number }) => {
 };
 
 onMounted(() => {
-  loadData(true); // 页面初始化时自动计算排名
+  loadData(false); // 页面初始化时不自动计算排名,需要手动点击计算排名按钮
 });
 </script>

+ 218 - 31
src/views/system/gameScore/index.vue

@@ -35,9 +35,14 @@
           <el-col :span="1.5">
             <el-button type="primary" @click="printScores">打印成绩(前3名)</el-button>
           </el-col>
-          <el-col :span="1.5">
+          <!-- <el-col :span="1.5">
             <el-button type="primary" @click="exportScoresNames">导出成绩(全部)</el-button>
-          </el-col>
+          </el-col> -->
+          <!-- <el-col :span="1.5">
+            <el-button type="warning" @click="openBonusDialog" :loading="bonusLoading">
+              <el-icon><Edit /></el-icon> 加分
+            </el-button>
+          </el-col> -->
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
         </el-row>
       </template>
@@ -69,7 +74,6 @@
           </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">
@@ -92,21 +96,17 @@
 
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
+
   </div>
 </template>
 
 <script setup name="GameScore" lang="ts">
-import { listGameScore, getGameScore, delGameScore, addGameScore, updateGameScore, getProjectScoreData } from '@/api/system/gameScore';
-import { getDefaultEvent } from '@/api/system/gameEvent'
+import { listGameScore, getGameScore, delGameScore, addGameScore, updateGameScore, getProjectScoreData, getBonusData, updateBonusData, exportBonusExcel } from '@/api/system/gameScore';
 import { listGameEventProject } from '@/api/system/gameEventProject';
 import { getGameTeam } from '@/api/system/gameTeam';
 import { getGameAthlete } from '@/api/system/gameAthlete';
 import { GameScoreVO, GameScoreQuery, GameScoreForm } from '@/api/system/gameScore/types';
-import { GameEventVO, GameEventQuery } from '@/api/system/gameEvent/types';
 import { GameEventProjectVO, GameEventProjectQuery } from '@/api/system/gameEventProject/types';
-import { GameTeamVO } from '@/api/system/gameTeam/types';
-import { GameAthleteVO } from '@/api/system/gameAthlete/types';
-import { ElMessage } from 'element-plus';
 import { useGameEventStore } from '@/store/modules/gameEvent';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -126,6 +126,19 @@ const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
 
+// // 加分弹窗相关数据
+// const bonusLoading = ref(false);
+// const saveLoading = ref(false);
+// const exportLoading = ref(false);
+// const bonusDialog = reactive({
+//   visible: false,
+//   title: '加分编辑'
+// });
+
+const bonusDataList = ref<any[]>([]);
+const bonusProjectList = ref<any[]>([]);
+const selectedBonusRows = ref<any[]>([]);
+
 // 列显隐数据
 const columns = ref<FieldOption[]>([
   { key: 0, label: 'ID', visible: false },
@@ -154,8 +167,8 @@ const initFormData: GameScoreForm = {
   scoreRank: undefined,
   scorePoint: undefined,
   award: undefined,
-  extraScore1: undefined,
-  extraScore2: undefined,
+  leaderPoint: undefined,
+  extraPoint: undefined,
   statusFlag: undefined,
   status: undefined,
   remark: undefined
@@ -174,18 +187,188 @@ const data = reactive<PageData<GameScoreForm, GameScoreQuery>>({
 
 const { queryParams, form, rules } = toRefs(data);
 
+// 加分相关方法
+// 打开加分弹窗
+// const openBonusDialog = async () => {
+//   try {
+//     bonusLoading.value = true;
+//     bonusDialog.visible = true;
+    
+//     // 获取默认赛事ID
+//     const event = gameEventStore.defaultEventInfo;
+//     const eventId = event?.eventId;
+
+//     if (!eventId) {
+//       proxy?.$modal.msgWarning('未指定赛事,无法进行加分操作');
+//       bonusDialog.visible = false;
+//       return;
+//     }
+    
+//     // 获取加分数据
+//     await loadBonusData(eventId);
+//   } catch (error) {
+//     console.error("打开加分弹窗失败:", error);
+//     proxy?.$modal.msgError("加载加分数据失败");
+//   } finally {
+//     bonusLoading.value = false;
+//   }
+// };
+
+// 加载加分数据
+const loadBonusData = async (eventId: string | number) => {
+  try {
+    const response = await getBonusData({
+      eventId: eventId
+    });
+    
+    bonusDataList.value = response.data.rows || [];
+    bonusProjectList.value = response.data.projects || [];
+    
+    // 初始化加分字段
+    bonusDataList.value.forEach((row: any) => {
+      if (!row.leaderPoint) row.leaderPoint = 0;
+      if (!row.extraPoint) row.extraPoint = 0;
+      calculateTotalScore(row);
+    });
+  } catch (error) {
+    console.error("加载加分数据失败:", error);
+    proxy?.$modal.msgError("加载加分数据失败");
+  }
+};
+
+// 计算总分
+const calculateTotalScore = (row: any) => {
+  let totalScore = 0;
+  
+  // 累加各项目积分
+  Object.values(row.projectScores || {}).forEach((score: any) => {
+    totalScore += Number(score) || 0;
+  });
+  
+  // 加上领导加分和额外加分
+  totalScore += Number(row.leaderPoint) || 0;
+  totalScore += Number(row.extraPoint) || 0;
+  
+  row.totalScore = totalScore;
+  
+  // 重新计算排名
+  calculateBonusRankings();
+};
+
+// 计算excel表排名
+const calculateBonusRankings = () => {
+  // 按总分排序
+  const sortedData = [...bonusDataList.value].sort((a, b) => b.totalScore - a.totalScore);
+  
+  // 更新排名
+  sortedData.forEach((row, index) => {
+    row.rank = index + 1;
+  });
+  
+  // 更新原数组
+  bonusDataList.value = sortedData;
+};
+
+// 选择变化处理
+const handleBonusSelectionChange = (selection: any[]) => {
+  selectedBonusRows.value = selection;
+};
+
+// 保存加分修改
+// const saveBonusChanges = async () => {
+//   try {
+//     saveLoading.value = true;
+    
+//     const event = gameEventStore.defaultEventInfo;
+//     const eventId = event?.eventId;
+    
+//     if (!eventId) {
+//       proxy?.$modal.msgWarning('未指定赛事,无法保存');
+//       return;
+//     }
+    
+//     const updateData = bonusDataList.value.map(row => ({
+//       teamId: row.teamId,
+//       leaderPoint: Number(row.leaderPoint) || 0,
+//       extraPoint: Number(row.extraPoint) || 0,
+//       totalScore: row.totalScore,
+//       rank: row.rank
+//     }));
+    
+//     await updateBonusData({
+//       eventId: eventId,
+//       data: updateData
+//     });
+    
+//     proxy?.$modal.msgSuccess("保存成功");
+    
+//     // 刷新主页面数据
+//     // await refreshData();
+//   } catch (error) {
+//     console.error("保存失败:", error);
+//     proxy?.$modal.msgError("保存失败,请稍后再试");
+//   } finally {
+//     saveLoading.value = false;
+//   }
+// };
+
+// 导出加分数据方法
+// const exportBonusData = async () => {
+//   try {
+//     exportLoading.value = true;
+    
+//     const event = gameEventStore.defaultEventInfo;
+//     const eventId = event?.eventId;
+    
+//     if (!eventId) {
+//       proxy?.$modal.msgWarning('未指定默认赛事,无法导出');
+//       return;
+//     }
+    
+//     // 使用 proxy?.download 方法,传递JSON字符串参数
+//     proxy?.download(
+//       '/system/gameScore/exportBonusExcel',
+//       {
+//         eventId: eventId,
+//         data: JSON.stringify(bonusDataList.value),
+//         projects: JSON.stringify(bonusProjectList.value)
+//       },
+//       `总成绩排名表_${new Date().toLocaleDateString()}.xlsx`
+//     );
+    
+//     proxy?.$modal.msgSuccess("导出成功");
+//   } catch (error) {
+//     console.error("导出失败:", error);
+//     proxy?.$modal.msgError("导出失败,请稍后再试");
+//   } finally {
+//     exportLoading.value = false;
+//   }
+// };
+
+// 刷新加分数据
+// const refreshBonusData = async () => {
+//   const event = gameEventStore.defaultEventInfo;
+//   const eventId = event?.eventId;
+  
+//   if (eventId) {
+//     await loadBonusData(eventId);
+//   }
+// };
+
 /** 查询成绩列表 */
 const getList = async () => {
-  // if (!queryParams.value.eventId) {
-  //   proxy?.$modal.msgWarning('未指定默认赛事');
-  //   loading.value = false;
-  //   return;
-  // }
   loading.value = true;
-  const res = await listGameScore(queryParams.value);
-  gameScoreList.value = res.rows;
-  total.value = res.total;
-  loading.value = false;
+  try{
+    const res = await listGameScore(queryParams.value);
+    gameScoreList.value = res.rows;
+    total.value = res.total;
+    loading.value = false;
+  }catch(error){
+    console.error("查询成绩列表失败:", error);
+    proxy?.$modal.msgError("查询成绩列表失败");
+  } finally{
+    loading.value = false;
+  }
 }
 
 /**
@@ -467,13 +650,10 @@ const formatScore = (score: number | string) => {
  * 获取项目类型名称
  */
 const getProjectTypeName = (type: string) => {
-  const typeMap: Record<string, string> = {
-    '1': '田径',
-    '2': '游泳',
-    '3': '球类',
-    '4': '其他'
-  };
-  return typeMap[type] || '未知';
+  if (!game_project_type.value || !type) return '未知';
+
+  const typeItem = game_project_type.value.find((item: any) => item.value === type);
+  return typeItem ? typeItem.label : '未知';
 };
 
 const exportScoresNames = () => {
@@ -501,10 +681,17 @@ const exportScoresNames = () => {
  */
 const loadProjects = async () => {
   loading.value = true;
-  console.log('加载项目列表: ',queryParams.value);
-  const res = await listGameEventProject(queryParams.value);
-  projectList.value = res.rows;
-  total.value = res.total;
+  try {
+    console.log('加载项目列表: ', queryParams.value);
+    const res = await listGameEventProject(queryParams.value);
+    projectList.value = res.rows;
+    total.value = res.total;
+  } catch (error) {
+    console.error('加载项目列表失败:', error);
+    proxy?.$modal.msgError('加载项目列表失败');
+  } finally {
+    loading.value = false;
+  }
 };
 
 /** 搜索按钮操作 */

+ 7 - 7
src/views/system/gameScore/print.vue

@@ -58,6 +58,9 @@ import { getGameEventProject } from '@/api/system/gameEventProject'
 import { GameScoreVO } from '@/api/system/gameScore/types'
 import { GameEventProjectVO } from '@/api/system/gameEventProject/types'
 
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { game_project_type } = toRefs<any>(proxy?.useDict('game_project_type'));
+
 const route = useRoute()
 const router = useRouter()
 
@@ -133,13 +136,10 @@ const loadProjectScores = async () => {
 
 // 获取项目类型名称
 const getProjectTypeName = (type: string) => {
-  const typeMap: Record<string, string> = {
-    '1': '田径',
-    '2': '游泳',
-    '3': '球类',
-    '4': '其他'
-  }
-  return typeMap[type] || '未知'
+  if (!game_project_type.value || !type) return '未知';
+  
+  const typeItem = game_project_type.value.find((item: any) => item.value === type);
+  return typeItem ? typeItem.label : '未知';
 }
 
 // 获取分组类型名称

+ 6 - 7
src/views/system/gameTeam/index.vue

@@ -35,9 +35,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:gameTeam:export']">导出 </el-button>
-          </el-col>
+          </el-col> -->
           <el-col :span="1.5">
             <el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:gameTeam:import']"> 导入 </el-button>
           </el-col>
@@ -47,8 +47,8 @@
 
       <el-table v-loading="loading" border :data="gameTeamList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
-        <!--        <el-table-column label="主键" align="center" prop="teamId" v-if="columns[0].visible" />-->
-        <!--        <el-table-column label="队伍编号" align="center" prop="teamCode" v-if="columns[1].visible" />-->
+        <el-table-column label="队伍id" align="center" prop="teamId" v-if="columns[0].visible" />
+        <el-table-column label="队伍编号" align="center" prop="teamCode" v-if="columns[1].visible" />
         <el-table-column label="赛事名称" align="center" prop="eventName" v-if="columns[2].visible" />
         <el-table-column label="队伍名称" align="center" prop="teamName" v-if="columns[3].visible" />
         <el-table-column label="团队描述" align="center" prop="teamDescribe" v-if="columns[4].visible">
@@ -149,7 +149,6 @@
 import { listGameTeam, getGameTeam, delGameTeam, addGameTeam, updateGameTeam } from '@/api/system/gameTeam';
 // import { getDefaultEvent } from '@/api/system/gameEvent';
 import { GameTeamVO, GameTeamQuery, GameTeamForm } from '@/api/system/gameTeam/types';
-import { getEventIdNameMap } from '@/api/system/gameEvent';
 import { globalHeaders } from '@/utils/request';
 import { GameEventVO } from '@/api/system/gameEvent/types';
 
@@ -169,8 +168,8 @@ const eventOptions = ref<Array<{ label: string; value: string | number }>>([]);
 
 // 列显隐数据
 const columns = ref<FieldOption[]>([
-  { key: 0, label: '主键', visible: false },
-  { key: 1, label: '队伍编号', visible: true },
+  { key: 0, label: '队伍id', visible: false },
+  { key: 1, label: '队伍编号', visible: false },
   { key: 2, label: '赛事名称', visible: false },
   { key: 3, label: '队伍名称', visible: true },
   { key: 4, label: '团队描述', visible: true },

+ 6 - 2
tsconfig.json

@@ -38,5 +38,9 @@
     "eslint.config.ts",
     "src/**/*.d.ts"
   ],
-  "exclude": ["node_modules", "dist", "src/**/__tests__/*"]
-}
+  "exclude": [
+    "node_modules", 
+    "dist", 
+    "src/**/__tests__/*",
+  ]
+}