Эх сурвалжийг харах

feat(game): 添加富文本编辑器并优化成绩打印功能

- 添加文章编辑器路由和页面组件
- 重构编写文章功能,使用独立页面替代对话框
- 在成绩打印中增加运动员编号显示
- 优化成绩数据处理逻辑,提取运动员编号和姓名
- 注释掉旧的队伍和运动员名称获取方法
- 调整打印模板中的运动员信息展示字段
zhou 2 долоо хоног өмнө
parent
commit
e1831c743b

+ 6 - 0
src/router/index.ts

@@ -129,6 +129,12 @@ export const constantRoutes: RouteRecordRaw[] = [
         name: 'RankingBoardPage',
         component: () => import('@/views/system/gameEvent/RankingBoardPage.vue'),
         meta: { title: '排行榜' }
+      },
+      {
+        path: '/system/gameEvent/components/articleEditor/:eventId',
+        name: 'ArticleEditor',
+        component: () => import('@/views/system/gameEvent/components/ArticleEditor.vue'),
+        meta: { title: '富文本编辑' }
       }
     ]
   },

+ 347 - 0
src/views/system/gameEvent/components/ArticleEditor.vue

@@ -0,0 +1,347 @@
+<template>
+    <div class="p-2">
+      <el-card shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <div class="text-lg font-600">
+              赛事文章编辑 
+              <!-- - {{ pageTitle }} -->
+            </div>
+            <div>
+              <el-button @click="handleBack">返回</el-button>
+              <el-button type="primary" @click="handleSaveArticle" :loading="saving">保存</el-button>
+            </div>
+          </div>
+        </template>
+  
+        <div>
+          <el-alert
+            v-if="!articleDialog.currentEventId"
+            title="缺少赛事ID,无法编辑文章"
+            type="error"
+            show-icon
+            class="mb-2"
+          />
+          <el-tabs v-model="activeTab" @tab-click="handleTabClick">
+            <el-tab-pane label="竞赛流程" name="competition-process">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.competitionProcess.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.competitionProcess.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.competitionProcess.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="竞赛项目" name="competition-items">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.competitionItems.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.competitionItems.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.competitionItems.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="活动议程" name="activity-agenda">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.activityAgenda.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.activityAgenda.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.activityAgenda.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="项目介绍" name="project-introduction">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.projectIntroduction.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.projectIntroduction.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.projectIntroduction.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="竞赛流程" name="competition-flow">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.competitionFlow.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.competitionFlow.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.competitionFlow.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="赛事分组" name="event-grouping">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.eventGrouping.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.eventGrouping.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.eventGrouping.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="运动员号码簿" name="athlete-handbook">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.athleteHandbook.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.athleteHandbook.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.athleteHandbook.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="项目场地" name="project-venue">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.projectVenue.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.projectVenue.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.projectVenue.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </el-form-item>
+              </div>
+            </el-tab-pane>
+  
+            <el-tab-pane label="交通指示" name="traffic-guide">
+              <div class="article-form">
+                <el-form-item label="标题">
+                  <el-input v-model="articleData.trafficGuide.title" placeholder="请输入标题" />
+                </el-form-item>
+                <el-form-item label="内容">
+                  <Editor v-model="articleData.trafficGuide.content" :min-height="300" />
+                </el-form-item>
+                <el-form-item label="备注">
+                  <el-input v-model="articleData.trafficGuide.remark" placeholder="请输入备注" type="textarea" :rows="3" />
+                </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>
+        </div>
+      </el-card>
+    </div>
+  </template>
+  
+  <script setup lang="ts" name="GameEventArticleEditor">
+  import { useRouter, useRoute } from 'vue-router';
+  import { ref, reactive, onMounted } from 'vue';
+  import Editor from '@/components/Editor/index.vue';
+  import { getEventMdByEventAndType, editEventMd } from '@/api/system/eventMd';
+  import type { EventMdForm } from '@/api/system/eventMd/types';
+  import { useGameEventStore } from '@/store/modules/gameEvent';
+  import { storeToRefs } from 'pinia';
+  
+  const router = useRouter();
+  const route = useRoute();
+
+  //从pinia中获取默认赛事信息
+  const gameEventStore = useGameEventStore();
+  const { defaultEventInfo } = storeToRefs(gameEventStore);
+  
+  const pageTitle = ref<string>('');
+  const saving = ref<boolean>(false);
+  
+  const articleDialog = reactive({
+    currentEventId: defaultEventInfo.value.eventId,
+  });
+  
+  const activeTab = ref('competition-process');
+  
+  const tabTypeMapping: Record<string, number> = {
+    'competition-process': 1,
+    'competition-items': 2,
+    'activity-agenda': 3,
+    'project-introduction': 4,
+    'competition-flow': 5,
+    'event-grouping': 6,
+    'athlete-handbook': 7,
+    'project-venue': 8,
+    'traffic-guide': 9,
+    'quick-registration': 10,
+    'registration-consultation': 11
+  };
+  
+  const articleData = reactive({
+    competitionProcess: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    competitionItems: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    activityAgenda: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    projectIntroduction: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    competitionFlow: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    eventGrouping: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    athleteHandbook: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    projectVenue: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    trafficGuide: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    quickRegistration: { id: undefined as number | undefined, title: '', content: '', remark: '' },
+    registrationConsultation: { id: undefined as number | undefined, title: '', content: '', remark: '' }
+  });
+  
+  const getDataKeyByTabName = (tabName: string): keyof typeof articleData | null => {
+    const mapping: Record<string, keyof typeof articleData> = {
+      'competition-process': 'competitionProcess',
+      'competition-items': 'competitionItems',
+      'activity-agenda': 'activityAgenda',
+      'project-introduction': 'projectIntroduction',
+      'competition-flow': 'competitionFlow',
+      'event-grouping': 'eventGrouping',
+      'athlete-handbook': 'athleteHandbook',
+      'project-venue': 'projectVenue',
+      'traffic-guide': 'trafficGuide',
+      'quick-registration': 'quickRegistration',
+      'registration-consultation': 'registrationConsultation'
+    };
+    return mapping[tabName] || null;
+  };
+  
+  const loadTabData = async (tabName: string) => {
+    const type = tabTypeMapping[tabName];
+    if (articleDialog.currentEventId && type) {
+      try {
+        const response = await getEventMdByEventAndType(articleDialog.currentEventId, type);
+        const eventMd = response.data;
+        const dataKey = getDataKeyByTabName(tabName);
+        if (dataKey && articleData[dataKey]) {
+          if (eventMd) {
+            articleData[dataKey].id = typeof eventMd.id === 'number' ? eventMd.id : Number(eventMd.id);
+            articleData[dataKey].title = eventMd.title || '';
+            articleData[dataKey].content = eventMd.content || '';
+            articleData[dataKey].remark = eventMd.remark || '';
+          } else {
+            articleData[dataKey].id = undefined;
+            articleData[dataKey].title = '';
+            articleData[dataKey].content = '';
+            articleData[dataKey].remark = '';
+          }
+        }
+      } catch {
+        const dataKey = getDataKeyByTabName(tabName);
+        if (dataKey && articleData[dataKey]) {
+          articleData[dataKey].id = undefined;
+          articleData[dataKey].title = '';
+          articleData[dataKey].content = '';
+          articleData[dataKey].remark = '';
+        }
+      }
+    }
+  };
+  
+  const handleTabClick = async (tab: any) => {
+    const tabName = tab.props.name;
+    await loadTabData(tabName);
+  };
+  
+  const handleSaveArticle = async () => {
+    if (!articleDialog.currentEventId) {
+      ElMessage.error('赛事ID不能为空');
+      return;
+    }
+    const currentTabName = activeTab.value;
+    const type = tabTypeMapping[currentTabName];
+    const dataKey = getDataKeyByTabName(currentTabName);
+    if (!dataKey || !articleData[dataKey]) {
+      ElMessage.error('获取当前标签页数据失败');
+      return;
+    }
+    const currentData = articleData[dataKey];
+    if (!currentData.title?.trim()) {
+      ElMessage.error('标题不能为空');
+      return;
+    }
+    try {
+      saving.value = true;
+      const formData: EventMdForm = {
+        id: currentData.id,
+        eventId: articleDialog.currentEventId,
+        title: currentData.title,
+        content: currentData.content || '',
+        type: type,
+        remark: currentData.remark || ''
+      };
+      await editEventMd(formData);
+      ElMessage.success('文章保存成功');
+      await loadTabData(currentTabName);
+    } catch {
+      ElMessage.error('保存文章失败');
+    } finally {
+      saving.value = false;
+    }
+  };
+  
+  const handleBack = () => {
+    router.back();
+  };
+  
+  onMounted(async () => {
+    const eventId = defaultEventInfo.value.eventId;
+    // const eventIdParam = route.params.eventId as string | undefined;
+    articleDialog.currentEventId = eventId;
+    // pageTitle.value = eventIdParam ? `赛事ID: ${eventIdParam}` : '未指定赛事';
+    // 初次进入加载默认标签页
+    await loadTabData(activeTab.value);
+  });
+  </script>
+  
+  <style scoped>
+  .article-form {
+    padding: 10px 6px;
+  }
+  </style>

+ 9 - 5
src/views/system/gameEvent/index.vue

@@ -78,11 +78,11 @@
               >排行榜
             </el-button>
           </el-col> -->
-          <el-col :span="1.5">
+          <!-- <el-col :span="1.5">
             <el-button type="primary" plain icon="EditPen" @click="handleWriteArticleDefault" v-hasPermi="['system:gameEvent:writeArticle']"
               >编写文章
             </el-button>
-          </el-col>
+          </el-col> -->
           <el-col :span="1.5">
             <el-button type="primary" plain icon="Download" @click="handleExportNumberTableDefault" v-hasPermi="['system:gameEvent:numberExport']"
               >导出号码对照表
@@ -178,7 +178,7 @@
       <RankingBoard :eventId="currentEventId" v-if="rankingBoardVisible" />
     </el-dialog> -->
     <!-- 文章编写对话框 -->
-    <el-dialog v-model="articleDialog.visible" :title="articleDialog.title" width="1200px" append-to-body>
+    <!-- <el-dialog v-model="articleDialog.visible" :title="articleDialog.title" width="1200px" append-to-body>
       <el-tabs v-model="activeTab" @tab-click="handleTabClick">
         <el-tab-pane label="竞赛流程" name="competition-process">
           <div class="article-form">
@@ -330,7 +330,7 @@
           <el-button type="primary" @click="handleSaveArticle">保 存</el-button>
         </div>
       </template>
-    </el-dialog>
+    </el-dialog> -->
 
     <!-- 用户导入对话框 -->
     <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
@@ -992,7 +992,11 @@ const handleWriteArticleDefault = async () => {
     proxy?.$modal.msgError('请先设置默认赛事');
     return;
   }
-  handleWriteArticle(defaultEvent);
+  router.push({
+    name: 'ArticleEditor',
+    params: { eventId: defaultEvent.eventId }
+  });
+  // handleWriteArticle(defaultEvent);
 };
 
 const handleExportNumberTableDefault = async () => {

+ 5 - 2
src/views/system/gameScore/index.vue

@@ -483,6 +483,7 @@ const printScores = async () => {
             sortedScores.map(async (score: any) => {
               let teamName = '-';
               let athleteName = '-';
+              let athleteCode = '-';
 
               try {
                 // 获取队伍信息
@@ -495,6 +496,7 @@ const printScores = async () => {
                 if (score.athleteId) {
                   const athleteRes = await getGameAthlete(score.athleteId);
                   athleteName = athleteRes.data.name || `运动员${score.athleteId}`;
+                  athleteCode = athleteRes.data.athleteCode || `无`;
                 }
               } catch (error) {
                 console.warn('获取队伍或运动员信息失败:', error);
@@ -503,7 +505,8 @@ const printScores = async () => {
               return {
                 ...score,
                 teamName,
-                athleteName
+                athleteName,
+                athleteCode
               };
             })
           );
@@ -659,7 +662,7 @@ const buildPrintHtml = (projects: any[], topCount: number = 3) => {
             <td>${score.classification === '0' ? '个人项目' : '团体项目'}</td>
             <td>第${index + 1}名</td>
             <td>${score.teamName || '-'}</td>
-            <td>${score.athleteId || '-'}</td>
+            <td>${score.athleteCode || '-'}</td>
             <td>${score.athleteName || '-'}</td>
             <td>${formatScore(score.individualPerformance || score.teamPerformance)}</td>
             <td>${score.scorePoint || 0}</td>

+ 13 - 12
src/views/system/gameScore/print.vue

@@ -38,7 +38,7 @@
               <td>{{ score.classification === '0' ? '个人项目' : '团体项目' }}</td>
               <td>第{{ index + 1 }}名</td>
               <td>{{ score.teamName || '-' }}</td>
-              <td>{{ score.athleteId || '-' }}</td>
+              <td>{{ score.athleteCode || '-' }}</td>
               <td>{{ score.athleteName || '-' }}</td>
               <td>{{ formatScore(score.individualPerformance || score.teamPerformance) }}</td>
               <td>{{ score.scorePoint || 0 }}</td>
@@ -121,8 +121,9 @@ const loadProjectScores = async () => {
       classification: project.classification,
       scores: sortedScores.map(score => ({
         ...score,
-        teamName: getTeamName(score.teamId),
-        athleteName: getAthleteName(score.athleteId)
+        teamName: score.teamId,
+        athleteName: score.name,
+        athleteCode: score.athleteCode,
       }))
     }]
 
@@ -154,16 +155,16 @@ const getProjectTypeName = (type: string) => {
 // }
 
 // 获取队伍名称(这里需要根据实际API调整)
-const getTeamName = (teamId: string | number) => {
-  // 实际项目中应该调用队伍API获取名称
-  return `队伍${teamId}`
-}
+// const getTeamName = (teamId: string | number) => {
+//   // 实际项目中应该调用队伍API获取名称
+//   return `队伍${teamId}`
+// }
 
-// 获取运动员姓名(这里需要根据实际API调整)
-const getAthleteName = (athleteId: string | number) => {
-  // 实际项目中应该调用运动员API获取姓名
-  return `运动员${athleteId}`
-}
+// // 获取运动员姓名(这里需要根据实际API调整)
+// const getAthleteName = (athleteId: string | number) => {
+//   // 实际项目中应该调用运动员API获取姓名
+//   return `运动员${athleteId}`
+// }
 
 // 打印页面
 const printPage = () => {