index.vue 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. <template>
  2. <div class="p-2">
  3. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  4. <div v-show="showSearch" class="mb-[10px]">
  5. <el-card shadow="hover">
  6. <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
  7. <el-form-item label="赛事编号" prop="eventCode">
  8. <el-input v-model="queryParams.eventCode" placeholder="请输入赛事编号" clearable @keyup.enter="handleQuery" />
  9. </el-form-item>
  10. <el-form-item label="赛事名称" prop="eventName">
  11. <el-input v-model="queryParams.eventName" placeholder="请输入赛事名称" clearable @keyup.enter="handleQuery" />
  12. </el-form-item>
  13. <el-form-item label="赛事类型" prop="eventType">
  14. <el-select v-model="queryParams.eventType" placeholder="请选择赛事类型" clearable>
  15. <el-option v-for="dict in game_event_type" :key="dict.value" :label="dict.label" :value="dict.value" />
  16. </el-select>
  17. </el-form-item>
  18. <el-form-item label="开始时间" prop="startTime">
  19. <el-date-picker clearable v-model="queryParams.startTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择开始时间" />
  20. </el-form-item>
  21. <el-form-item label="是否默认赛事" prop="isDefault">
  22. <el-select v-model="queryParams.isDefault" placeholder="请选择是否默认赛事" clearable>
  23. <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="状态" prop="status">
  27. <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
  28. <el-option v-for="dict in game_event_status" :key="dict.value" :label="dict.label" :value="dict.value" />
  29. </el-select>
  30. </el-form-item>
  31. <el-form-item>
  32. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  33. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  34. </el-form-item>
  35. </el-form>
  36. </el-card>
  37. </div>
  38. </transition>
  39. <el-card shadow="never">
  40. <template #header>
  41. <el-row :gutter="10" class="mb8">
  42. <el-col :span="1.5">
  43. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:gameEvent:add']">新增 </el-button>
  44. </el-col>
  45. <el-col :span="1.5">
  46. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:gameEvent:edit']"
  47. >修改
  48. </el-button>
  49. </el-col>
  50. <el-col :span="1.5">
  51. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:gameEvent:remove']"
  52. >删除
  53. </el-button>
  54. </el-col>
  55. <el-col :span="1.5">
  56. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:gameEvent:export']">导出 </el-button>
  57. </el-col>
  58. <right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
  59. </el-row>
  60. </template>
  61. <el-table v-loading="loading" border :data="gameEventList" @selection-change="handleSelectionChange">
  62. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
  63. <template #default="scope">
  64. <div class="operation-buttons">
  65. <el-tooltip content="修改" placement="top">
  66. <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:gameEvent:edit']">
  67. <span class="button-text">修改</span>
  68. </el-button>
  69. </el-tooltip>
  70. <!-- 删除操作 -->
  71. <el-tooltip :content="getDeleteTooltip(scope.row)" placement="top">
  72. <el-button
  73. link
  74. type="danger"
  75. icon="Delete"
  76. :disabled="!canDelete(scope.row)"
  77. @click="handleDelete(scope.row)"
  78. v-hasPermi="['system:event:remove']"
  79. >
  80. <span class="button-text">删除</span>
  81. </el-button>
  82. </el-tooltip>
  83. <!-- 下载模板 -->
  84. <el-tooltip content="下载报名信息模板" placement="top">
  85. <el-button link type="warning" icon="Download" @click="handleDownloadTemplate(scope.row)">
  86. <span class="button-text">下载</span>
  87. </el-button>
  88. </el-tooltip>
  89. <!-- 导入报名信息 -->
  90. <el-tooltip content="导入报名信息" placement="top">
  91. <el-button
  92. link
  93. type="info"
  94. icon="FolderOpened"
  95. @click="handleImportRegistration(scope.row)"
  96. v-hasPermi="['system:gameEvent:import']"
  97. >
  98. <span class="button-text">导入</span>
  99. </el-button>
  100. </el-tooltip>
  101. <!-- 添加参赛者 -->
  102. <el-tooltip content="添加参赛者" placement="top">
  103. <el-button
  104. link
  105. type="success"
  106. icon="User"
  107. @click="handleAddParticipant(scope.row)"
  108. v-hasPermi="['system:gameEvent:addParticipant']"
  109. >
  110. <span class="button-text">参赛者</span>
  111. </el-button>
  112. </el-tooltip>
  113. <!-- 添加裁判 -->
  114. <el-tooltip content="添加裁判" placement="top">
  115. <el-button
  116. link
  117. type="primary"
  118. icon="Avatar"
  119. @click="handleAddReferee(scope.row)"
  120. v-hasPermi="['system:gameEvent:addReferee']"
  121. >
  122. <span class="button-text">裁判</span>
  123. </el-button>
  124. </el-tooltip>
  125. <!-- 预览按钮 -->
  126. <el-tooltip content="预览" placement="top">
  127. <el-button link type="info" icon="View" @click="handlePreview(scope.row)" v-hasPermi="['system:gameEvent:view']">
  128. <span class="button-text">预览</span>
  129. </el-button>
  130. </el-tooltip>
  131. <!-- 比赛数据按钮 -->
  132. <el-tooltip content="比赛排行榜" placement="top">
  133. <el-button
  134. link
  135. type="warning"
  136. icon="DataAnalysis"
  137. @click="handleGameData(scope.row)"
  138. v-hasPermi="['system:gameEvent:gameData']"
  139. >
  140. <span class="button-text">排行榜</span>
  141. </el-button>
  142. </el-tooltip>
  143. <!-- 编写文章按钮 -->
  144. <el-tooltip content="编写文章" placement="top">
  145. <el-button
  146. link
  147. type="primary"
  148. icon="EditPen"
  149. @click="handleWriteArticle(scope.row)"
  150. v-hasPermi="['system:gameEvent:writeArticle']"
  151. ></el-button>
  152. </el-tooltip>
  153. </div>
  154. </template>
  155. </el-table-column>
  156. <el-table-column type="selection" width="55" align="center" />
  157. <el-table-column label="主键" align="center" prop="eventId" v-if="columns[0].visible" />
  158. <el-table-column label="赛事编号" align="center" prop="eventCode" v-if="columns[1].visible" />
  159. <el-table-column label="赛事名称" align="center" prop="eventName" v-if="columns[2].visible" />
  160. <el-table-column label="赛事类型" align="center" prop="eventType" v-if="columns[3].visible">
  161. <template #default="scope">
  162. <dict-tag :options="game_event_type" :value="scope.row.eventType" />
  163. </template>
  164. </el-table-column>
  165. <el-table-column label="举办地点" align="center" prop="location" v-if="columns[4].visible" />
  166. <el-table-column label="用途" align="center" prop="purpose" v-if="columns[5].visible">
  167. <template #default="scope">
  168. <dict-tag :options="game_event_purpose" :value="scope.row.purpose" />
  169. </template>
  170. </el-table-column>
  171. <el-table-column label="开始时间" align="center" prop="startTime" width="180" v-if="columns[6].visible">
  172. <template #default="scope">
  173. <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
  174. </template>
  175. </el-table-column>
  176. <el-table-column label="结束时间" align="center" prop="endTime" width="180" v-if="columns[7].visible">
  177. <template #default="scope">
  178. <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
  179. </template>
  180. </el-table-column>
  181. <el-table-column label="赛事链接" align="center" prop="eventUrlUrl" width="100" v-if="columns[8].visible">
  182. <template #default="scope">
  183. <image-preview :src="scope.row.eventUrlUrl" :width="50" :height="50" />
  184. </template>
  185. </el-table-column>
  186. <el-table-column label="裁判码" align="center" prop="refereeUrlUrl" width="100" v-if="columns[9].visible">
  187. <template #default="scope">
  188. <image-preview :src="scope.row.refereeUrlUrl" :width="50" :height="50" />
  189. </template>
  190. </el-table-column>
  191. <el-table-column label="举办单位" align="center" prop="unit" v-if="columns[10].visible" />
  192. <el-table-column label="是否默认赛事" align="center" prop="isDefault" v-if="columns[11].visible">
  193. <template #default="scope">
  194. <el-switch v-model="scope.row.isDefault" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
  195. </template>
  196. </el-table-column>
  197. <el-table-column label="创建时间" align="center" prop="createTime" width="180" v-if="columns[12].visible">
  198. <template #default="scope">
  199. <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
  200. </template>
  201. </el-table-column>
  202. <el-table-column label="更新时间" align="center" prop="updateTime" width="180" v-if="columns[13].visible">
  203. <template #default="scope">
  204. <span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d}') }}</span>
  205. </template>
  206. </el-table-column>
  207. <el-table-column label="状态" align="center" prop="status" v-if="columns[14].visible">
  208. <template #default="scope">
  209. <dict-tag :options="game_event_status" :value="scope.row.status" />
  210. </template>
  211. </el-table-column>
  212. <el-table-column label="备注" align="center" prop="remark" v-if="columns[15].visible" />
  213. </el-table>
  214. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  215. </el-card>
  216. <!-- 注册 RefereeForm 组件 -->
  217. <RefereeForm ref="refereeFormRef" />
  218. <!-- 排行榜对话框 -->
  219. <!-- <el-dialog :title="`赛事 ${currentEventId} 排行榜`" v-model="rankingBoardVisible" width="800px" append-to-body>
  220. <RankingBoard :eventId="currentEventId" v-if="rankingBoardVisible" />
  221. </el-dialog> -->
  222. <!-- 文章编写对话框 -->
  223. <el-dialog v-model="articleDialog.visible" :title="articleDialog.title" width="1200px" append-to-body>
  224. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  225. <el-tab-pane label="竞赛流程" name="competition-process">
  226. <div class="article-form">
  227. <el-form-item label="标题">
  228. <el-input v-model="articleData.competitionProcess.title" placeholder="请输入标题" />
  229. </el-form-item>
  230. <el-form-item label="内容">
  231. <Editor v-model="articleData.competitionProcess.content" :min-height="300" />
  232. </el-form-item>
  233. <el-form-item label="备注">
  234. <el-input v-model="articleData.competitionProcess.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  235. </el-form-item>
  236. </div>
  237. </el-tab-pane>
  238. <el-tab-pane label="竞赛项目" name="competition-items">
  239. <div class="article-form">
  240. <el-form-item label="标题">
  241. <el-input v-model="articleData.competitionItems.title" placeholder="请输入标题" />
  242. </el-form-item>
  243. <el-form-item label="内容">
  244. <Editor v-model="articleData.competitionItems.content" :min-height="300" />
  245. </el-form-item>
  246. <el-form-item label="备注">
  247. <el-input v-model="articleData.competitionItems.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  248. </el-form-item>
  249. </div>
  250. </el-tab-pane>
  251. <el-tab-pane label="活动议程" name="activity-agenda">
  252. <div class="article-form">
  253. <el-form-item label="标题">
  254. <el-input v-model="articleData.activityAgenda.title" placeholder="请输入标题" />
  255. </el-form-item>
  256. <el-form-item label="内容">
  257. <Editor v-model="articleData.activityAgenda.content" :min-height="300" />
  258. </el-form-item>
  259. <el-form-item label="备注">
  260. <el-input v-model="articleData.activityAgenda.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  261. </el-form-item>
  262. </div>
  263. </el-tab-pane>
  264. <el-tab-pane label="项目介绍" name="project-introduction">
  265. <div class="article-form">
  266. <el-form-item label="标题">
  267. <el-input v-model="articleData.projectIntroduction.title" placeholder="请输入标题" />
  268. </el-form-item>
  269. <el-form-item label="内容">
  270. <Editor v-model="articleData.projectIntroduction.content" :min-height="300" />
  271. </el-form-item>
  272. <el-form-item label="备注">
  273. <el-input v-model="articleData.projectIntroduction.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  274. </el-form-item>
  275. </div>
  276. </el-tab-pane>
  277. <el-tab-pane label="竞赛流程" name="competition-flow">
  278. <div class="article-form">
  279. <el-form-item label="标题">
  280. <el-input v-model="articleData.competitionFlow.title" placeholder="请输入标题" />
  281. </el-form-item>
  282. <el-form-item label="内容">
  283. <Editor v-model="articleData.competitionFlow.content" :min-height="300" />
  284. </el-form-item>
  285. <el-form-item label="备注">
  286. <el-input v-model="articleData.competitionFlow.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  287. </el-form-item>
  288. </div>
  289. </el-tab-pane>
  290. <el-tab-pane label="赛事分组" name="event-grouping">
  291. <div class="article-form">
  292. <el-form-item label="标题">
  293. <el-input v-model="articleData.eventGrouping.title" placeholder="请输入标题" />
  294. </el-form-item>
  295. <el-form-item label="内容">
  296. <Editor v-model="articleData.eventGrouping.content" :min-height="300" />
  297. </el-form-item>
  298. <el-form-item label="备注">
  299. <el-input v-model="articleData.eventGrouping.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  300. </el-form-item>
  301. </div>
  302. </el-tab-pane>
  303. <el-tab-pane label="运动员号码簿" name="athlete-handbook">
  304. <div class="article-form">
  305. <el-form-item label="标题">
  306. <el-input v-model="articleData.athleteHandbook.title" placeholder="请输入标题" />
  307. </el-form-item>
  308. <el-form-item label="内容">
  309. <Editor v-model="articleData.athleteHandbook.content" :min-height="300" />
  310. </el-form-item>
  311. <el-form-item label="备注">
  312. <el-input v-model="articleData.athleteHandbook.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  313. </el-form-item>
  314. </div>
  315. </el-tab-pane>
  316. <el-tab-pane label="项目场地" name="project-venue">
  317. <div class="article-form">
  318. <el-form-item label="标题">
  319. <el-input v-model="articleData.projectVenue.title" placeholder="请输入标题" />
  320. </el-form-item>
  321. <el-form-item label="内容">
  322. <Editor v-model="articleData.projectVenue.content" :min-height="300" />
  323. </el-form-item>
  324. <el-form-item label="备注">
  325. <el-input v-model="articleData.projectVenue.remark" placeholder="请输入备注" type="textarea" :rows="3" />
  326. </el-form-item>
  327. </div>
  328. </el-tab-pane>
  329. </el-tabs>
  330. <template #footer>
  331. <div class="dialog-footer">
  332. <el-button @click="handleCloseArticleDialog">取 消</el-button>
  333. <el-button type="primary" @click="handleSaveArticle">保 存</el-button>
  334. </div>
  335. </template>
  336. </el-dialog>
  337. <!-- 用户导入对话框 -->
  338. <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
  339. <el-upload
  340. ref="uploadRef"
  341. :limit="1"
  342. accept=".xlsx"
  343. :headers="upload.headers"
  344. :action="upload.url + '?updateSupport=' + upload.updateSupport"
  345. :disabled="upload.isUploading"
  346. :on-progress="handleFileUploadProgress"
  347. :on-success="handleFileSuccess"
  348. :auto-upload="false"
  349. drag
  350. >
  351. <el-icon class="el-icon--upload">
  352. <i-ep-upload-filled />
  353. </el-icon>
  354. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  355. <template #tip>
  356. <div class="text-center el-upload__tip">
  357. <!-- <div class="el-upload__tip">-->
  358. <!-- <el-checkbox v-model="upload.updateSupport" />-->
  359. <!-- 是否更新已经存在的用户数据-->
  360. <!-- </div>-->
  361. <span>仅允许导入.xlsx格式文件。</span>
  362. </div>
  363. </template>
  364. </el-upload>
  365. <template #footer>
  366. <div class="dialog-footer">
  367. <el-button type="primary" @click="submitFileForm">确 定</el-button>
  368. <el-button @click="upload.open = false">取 消</el-button>
  369. </div>
  370. </template>
  371. </el-dialog>
  372. </div>
  373. </template>
  374. <script setup name="GameEvent" lang="ts">
  375. import { listGameEvent, changeEventDefault, delGameEvent, addGameEvent, updateGameEvent } from '@/api/system/gameEvent';
  376. import { GameEventVO, GameEventQuery, GameEventForm } from '@/api/system/gameEvent/types';
  377. import { getEventMdByEventAndType, editEventMd } from '@/api/system/eventMd';
  378. import { EventMdVO, EventMdForm } from '@/api/system/eventMd/types';
  379. import { useRouter } from 'vue-router';
  380. import { ref } from 'vue';
  381. import RefereeForm from '@/views/system/gameEvent/RefereeForm.vue';
  382. import RankingBoard from './RankingBoard.vue';
  383. import Editor from '@/components/Editor/index.vue';
  384. import { useTagsViewStore } from '@/store/modules/tagsView';
  385. import { globalHeaders } from '@/utils/request';
  386. import { useGameEventStore } from '@/store/modules/gameEvent';
  387. const router = useRouter();
  388. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  389. const { game_event_type, game_event_status, sys_yes_no, game_event_purpose } = toRefs<any>(
  390. proxy?.useDict('game_event_type', 'game_event_status', 'sys_yes_no', 'game_event_purpose')
  391. );
  392. // 定义 RefereeForm 组件的类型
  393. interface RefereeFormInstance {
  394. openDialog: (eventId: string) => void;
  395. }
  396. const refereeFormRef = ref<(InstanceType<typeof RefereeForm> & RefereeFormInstance) | null>(null);
  397. const gameEventList = ref<GameEventVO[]>([]);
  398. const buttonLoading = ref(false);
  399. const loading = ref(true);
  400. const showSearch = ref(true);
  401. const ids = ref<Array<string | number>>([]);
  402. const single = ref(true);
  403. const multiple = ref(true);
  404. const total = ref(0);
  405. // 列显隐数据
  406. const columns = ref<FieldOption[]>([
  407. { key: 0, label: '主键', visible: false },
  408. { key: 1, label: '赛事编号', visible: true },
  409. { key: 2, label: '赛事名称', visible: true },
  410. { key: 3, label: '赛事类型', visible: true },
  411. { key: 4, label: '举办地点', visible: true },
  412. { key: 5, label: '用途', visible: true },
  413. { key: 6, label: '开始时间', visible: true },
  414. { key: 7, label: '结束时间', visible: true },
  415. { key: 8, label: '赛事链接', visible: true },
  416. { key: 9, label: '裁判码', visible: true },
  417. { key: 10, label: '举办单位', visible: true },
  418. { key: 11, label: '是否默认赛事', visible: true },
  419. { key: 12, label: '创建时间', visible: true },
  420. { key: 13, label: '更新时间', visible: true },
  421. { key: 14, label: '状态', visible: true },
  422. { key: 15, label: '备注', visible: true }
  423. ]);
  424. const queryFormRef = ref<ElFormInstance>();
  425. const gameEventFormRef = ref<ElFormInstance>();
  426. const dialog = reactive<DialogOption>({
  427. visible: false,
  428. title: ''
  429. });
  430. const uploadRef = ref<ElUploadInstance>();
  431. /*** 用户导入参数 */
  432. const upload = reactive<ImportOption>({
  433. // 是否显示弹出层(用户导入)
  434. open: false,
  435. // 弹出层标题(用户导入)
  436. title: '',
  437. // 是否禁用上传
  438. isUploading: false,
  439. // 是否更新已经存在的用户数据
  440. updateSupport: 0,
  441. // 设置上传的请求头部
  442. headers: globalHeaders(),
  443. // 上传的地址
  444. url: import.meta.env.VITE_APP_BASE_API + '/system/enroll/importData'
  445. });
  446. const initFormData: GameEventForm = {
  447. eventId: undefined,
  448. eventCode: undefined,
  449. eventName: undefined,
  450. eventType: undefined,
  451. location: undefined,
  452. purpose: undefined,
  453. startTime: undefined,
  454. endTime: undefined,
  455. eventUrl: undefined,
  456. refereeUrl: undefined,
  457. registerUrl: undefined,
  458. unit: undefined,
  459. isDefault: undefined,
  460. status: undefined,
  461. remark: undefined
  462. };
  463. const data = reactive<PageData<GameEventForm, GameEventQuery>>({
  464. form: { ...initFormData },
  465. queryParams: {
  466. pageNum: 1,
  467. pageSize: 10,
  468. eventCode: undefined,
  469. eventName: undefined,
  470. eventType: undefined,
  471. purpose: undefined,
  472. startTime: undefined,
  473. isDefault: undefined,
  474. status: undefined,
  475. params: {},
  476. orderByColumn: '',
  477. isAsc: ''
  478. },
  479. rules: {
  480. eventCode: [{ required: true, message: '赛事编号不能为空', trigger: 'blur' }],
  481. eventName: [{ required: true, message: '赛事名称不能为空', trigger: 'blur' }],
  482. eventType: [{ required: true, message: '赛事类型不能为空', trigger: 'change' }]
  483. }
  484. });
  485. const { queryParams, form, rules } = toRefs(data);
  486. // 使用gameEvent store
  487. const gameEventStore = useGameEventStore();
  488. /** 查询赛事基本信息列表 */
  489. const getList = async () => {
  490. loading.value = true;
  491. const res = await listGameEvent(queryParams.value);
  492. gameEventList.value = res.rows;
  493. total.value = res.total;
  494. loading.value = false;
  495. };
  496. /** 取消按钮 */
  497. const cancel = () => {
  498. reset();
  499. dialog.visible = false;
  500. };
  501. /** 表单重置 */
  502. const reset = () => {
  503. form.value = { ...initFormData };
  504. gameEventFormRef.value?.resetFields();
  505. };
  506. /** 搜索按钮操作 */
  507. const handleQuery = () => {
  508. queryParams.value.pageNum = 1;
  509. getList();
  510. };
  511. /** 重置按钮操作 */
  512. const resetQuery = () => {
  513. queryFormRef.value?.resetFields();
  514. handleQuery();
  515. };
  516. /** 多选框选中数据 */
  517. const handleSelectionChange = (selection: GameEventVO[]) => {
  518. ids.value = selection.map((item) => item.eventId);
  519. single.value = selection.length != 1;
  520. multiple.value = !selection.length;
  521. };
  522. /** 新增按钮操作 */
  523. const handleAdd = () => {
  524. router.push('/system/gameEvent/add');
  525. };
  526. /** 修改按钮操作 */
  527. const handleUpdate = async (row?: GameEventVO) => {
  528. const _eventId = row?.eventId || ids.value[0];
  529. router.push(`/system/gameEvent/edit/${_eventId}`);
  530. };
  531. /** 提交按钮 */
  532. const submitForm = () => {
  533. gameEventFormRef.value?.validate(async (valid: boolean) => {
  534. if (valid) {
  535. buttonLoading.value = true;
  536. if (form.value.eventId) {
  537. await updateGameEvent(form.value).finally(() => (buttonLoading.value = false));
  538. } else {
  539. await addGameEvent(form.value).finally(() => (buttonLoading.value = false));
  540. }
  541. proxy?.$modal.msgSuccess('操作成功');
  542. dialog.visible = false;
  543. await getList();
  544. }
  545. });
  546. };
  547. /** 删除按钮操作 */
  548. const handleDelete = async (row?: GameEventVO) => {
  549. const _ids = row?.eventId || ids.value;
  550. if (!_ids || (Array.isArray(_ids) && _ids.length === 0)) {
  551. proxy?.$modal.msgError('请选择要删除的赛事');
  552. return;
  553. }
  554. // 如果是单个删除,需要检查时间限制
  555. if (row) {
  556. const endTime = new Date(row.endTime);
  557. const currentTime = new Date();
  558. const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
  559. if (daysDiff < 100) {
  560. proxy?.$modal.msgError(`该赛事结束时间不足100天,无法删除。距离可删除时间还有 ${100 - daysDiff} 天。`);
  561. return;
  562. }
  563. await proxy?.$modal.confirm('是否确认删除赛事编号为"' + row.eventCode + '"的数据项?').finally(() => (loading.value = false));
  564. await delGameEvent(row.eventId);
  565. proxy?.$modal.msgSuccess('删除成功');
  566. } else {
  567. // 批量删除
  568. const selectedEvents = gameEventList.value.filter((event) => ids.value.includes(event.eventId));
  569. const invalidEvents = selectedEvents.filter((event) => {
  570. const endTime = new Date(event.endTime);
  571. const currentTime = new Date();
  572. const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
  573. return daysDiff < 100;
  574. });
  575. if (invalidEvents.length > 0) {
  576. const eventNames = invalidEvents.map((event) => event.eventName).join('、');
  577. proxy?.$modal.msgError(`以下赛事结束时间不足100天,无法删除:${eventNames}`);
  578. return;
  579. }
  580. const eventCodes = selectedEvents.map((event) => event.eventCode).join('、');
  581. await proxy?.$modal.confirm(`是否确认删除以下赛事:${eventCodes}?`).finally(() => (loading.value = false));
  582. await delGameEvent(ids.value);
  583. proxy?.$modal.msgSuccess(`成功删除 ${selectedEvents.length} 个赛事`);
  584. }
  585. await getList();
  586. };
  587. /** 判断是否可以删除赛事 */
  588. const canDelete = (row: GameEventVO): boolean => {
  589. const endTime = new Date(row.endTime);
  590. const currentTime = new Date();
  591. const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
  592. return daysDiff >= 100;
  593. };
  594. /** 获取删除按钮的提示信息 */
  595. const getDeleteTooltip = (row: GameEventVO): string => {
  596. if (canDelete(row)) {
  597. return '删除';
  598. } else {
  599. const endTime = new Date(row.endTime);
  600. const currentTime = new Date();
  601. const daysDiff = Math.floor((currentTime.getTime() - endTime.getTime()) / (1000 * 60 * 60 * 24));
  602. return `赛事结束时间不足100天,无法删除。距离可删除时间还有 ${100 - daysDiff} 天。`;
  603. }
  604. };
  605. /** 导出按钮操作 */
  606. const handleExport = () => {
  607. proxy?.download(
  608. 'system/gameEvent/export',
  609. {
  610. ...queryParams.value
  611. },
  612. `gameEvent_${new Date().getTime()}.xlsx`
  613. );
  614. };
  615. /* 下载模板 */
  616. const handleDownloadTemplate = (row: GameEventVO) => {
  617. proxy?.download(
  618. 'system/enroll/importTemplate',
  619. {
  620. eventId: row.eventId
  621. },
  622. `event_enroll_template_${new Date().getTime()}.xlsx`
  623. );
  624. };
  625. // 导入报名表逻辑
  626. const handleImportRegistration = (row) => {
  627. upload.url = import.meta.env.VITE_APP_BASE_API + `/system/enroll/importData/${row.eventId}`;
  628. upload.title = '报名表导入';
  629. upload.open = true;
  630. };
  631. /**文件上传中处理 */
  632. const handleFileUploadProgress = () => {
  633. upload.isUploading = true;
  634. };
  635. /** 文件上传成功处理 */
  636. const handleFileSuccess = (response: any, file: UploadFile) => {
  637. upload.open = false;
  638. upload.isUploading = false;
  639. uploadRef.value?.handleRemove(file);
  640. ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
  641. dangerouslyUseHTMLString: true
  642. });
  643. getList();
  644. };
  645. /** 提交上传文件 */
  646. function submitFileForm() {
  647. uploadRef.value?.submit();
  648. }
  649. // 添加参赛者操作
  650. const handleAddParticipant = (row: GameEventVO) => {
  651. // 跳转到新增或编辑参赛者信息页面,并传递 eventName 参数
  652. router.push({
  653. name: 'GameEventAthlete',
  654. params: { eventId: row.eventId, eventName: row.eventName }
  655. });
  656. };
  657. // 添加裁判按钮操作 1
  658. const handleAddReferee = async (row: GameEventVO) => {
  659. // 打开裁判表单对话框并传递 eventId
  660. refereeFormRef.value?.openDialog(String(row.eventId));
  661. };
  662. // 预览按钮点击事件
  663. const handlePreview = (row: GameEventVO) => {
  664. // 跳转到赛事详情页面
  665. router.push({
  666. name: 'GameEventDetail',
  667. params: { eventId: row.eventId }
  668. });
  669. };
  670. // 比赛成绩按钮点击事件
  671. const handleGameData = (row: GameEventVO) => {
  672. router.push({
  673. name: 'RankingBoardPage',
  674. params: { eventId: row.eventId }
  675. });
  676. };
  677. const rankingBoardVisible = ref(false);
  678. const currentEventId = ref<string | undefined>();
  679. // 文章编写相关数据
  680. const articleDialog = reactive({
  681. visible: false,
  682. title: '',
  683. currentEventId: undefined as string | number | undefined
  684. });
  685. const activeTab = ref('competition-process');
  686. // 标签页与类型值的映射关系
  687. const tabTypeMapping: Record<string, number> = {
  688. 'competition-process': 1, // 竞赛流程
  689. 'competition-items': 2, // 竞赛项目
  690. 'activity-agenda': 3, // 活动议程
  691. 'project-introduction': 4, // 项目介绍
  692. 'competition-flow': 5, // 竞赛流程
  693. 'event-grouping': 6, // 赛事分组
  694. 'athlete-handbook': 7, // 运动员号码簿
  695. 'project-venue': 8 // 项目场地
  696. };
  697. const articleData = reactive({
  698. competitionProcess: { id: undefined, title: '', content: '', remark: '' },
  699. competitionItems: { id: undefined, title: '', content: '', remark: '' },
  700. activityAgenda: { id: undefined, title: '', content: '', remark: '' },
  701. projectIntroduction: { id: undefined, title: '', content: '', remark: '' },
  702. competitionFlow: { id: undefined, title: '', content: '', remark: '' },
  703. eventGrouping: { id: undefined, title: '', content: '', remark: '' },
  704. athleteHandbook: { id: undefined, title: '', content: '', remark: '' },
  705. projectVenue: { id: undefined, title: '', content: '', remark: '' }
  706. });
  707. // 打开排行榜组件并传递赛事ID
  708. // const openRankingBoard = (eventId: string) => {
  709. // currentEventId.value = eventId;
  710. // rankingBoardVisible.value = true;
  711. // };
  712. /** 状态修改 */
  713. const handleStatusChange = async (row: GameEventVO) => {
  714. const text = row.isDefault === '0' ? '启用' : '停用';
  715. try {
  716. await proxy?.$modal.confirm('确认要"' + text + '""' + row.eventName + '"为默认赛事吗?');
  717. await changeEventDefault(row.eventId, row.isDefault);
  718. await getList();
  719. // 更新全局默认赛事信息
  720. await gameEventStore.fetchDefaultEvent();
  721. // localStorage.setItem('defaultEventId', row.eventId);
  722. proxy?.$modal.msgSuccess(text + '成功');
  723. // 刷新当前标签页
  724. await useTagsViewStore().delOthersViews(router.currentRoute.value);
  725. } catch {
  726. return;
  727. } finally {
  728. row.isDefault = row.isDefault === '0' ? '1' : '0';
  729. }
  730. };
  731. /** 编写文章按钮操作 */
  732. const handleWriteArticle = async (row: GameEventVO) => {
  733. articleDialog.title = `编写文章 - ${row.eventName}`;
  734. articleDialog.currentEventId = row.eventId;
  735. articleDialog.visible = true;
  736. activeTab.value = 'competition-process';
  737. // 加载默认标签页(竞赛流程)的数据
  738. await loadTabData('competition-process');
  739. };
  740. /** 加载指定标签页的数据 */
  741. const loadTabData = async (tabName: string) => {
  742. const type = tabTypeMapping[tabName];
  743. if (articleDialog.currentEventId && type) {
  744. try {
  745. const response = await getEventMdByEventAndType(articleDialog.currentEventId, type);
  746. const eventMd = response.data;
  747. const dataKey = getDataKeyByTabName(tabName);
  748. if (dataKey && articleData[dataKey]) {
  749. if (eventMd) {
  750. articleData[dataKey].id = eventMd.id;
  751. articleData[dataKey].title = eventMd.title || '';
  752. articleData[dataKey].content = eventMd.content || '';
  753. articleData[dataKey].remark = eventMd.remark || '';
  754. } else {
  755. articleData[dataKey].id = undefined;
  756. articleData[dataKey].title = '';
  757. articleData[dataKey].content = '';
  758. articleData[dataKey].remark = '';
  759. }
  760. }
  761. } catch (error) {
  762. console.log('查询文章数据失败:', error);
  763. const dataKey = getDataKeyByTabName(tabName);
  764. if (dataKey && articleData[dataKey]) {
  765. articleData[dataKey].id = undefined;
  766. articleData[dataKey].title = '';
  767. articleData[dataKey].content = '';
  768. articleData[dataKey].remark = '';
  769. }
  770. }
  771. }
  772. };
  773. /** 标签页点击事件 */
  774. const handleTabClick = async (tab: any) => {
  775. const tabName = tab.props.name;
  776. await loadTabData(tabName);
  777. };
  778. /** 根据标签页名称获取数据键名 */
  779. const getDataKeyByTabName = (tabName: string): keyof typeof articleData | null => {
  780. const mapping: Record<string, keyof typeof articleData> = {
  781. 'competition-process': 'competitionProcess',
  782. 'competition-items': 'competitionItems',
  783. 'activity-agenda': 'activityAgenda',
  784. 'project-introduction': 'projectIntroduction',
  785. 'competition-flow': 'competitionFlow',
  786. 'event-grouping': 'eventGrouping',
  787. 'athlete-handbook': 'athleteHandbook',
  788. 'project-venue': 'projectVenue'
  789. };
  790. return mapping[tabName] || null;
  791. };
  792. /** 关闭文章编写对话框 */
  793. const handleCloseArticleDialog = () => {
  794. // 清空所有文章数据
  795. Object.keys(articleData).forEach(key => {
  796. const dataKey = key as keyof typeof articleData;
  797. articleData[dataKey].id = undefined;
  798. articleData[dataKey].title = '';
  799. articleData[dataKey].content = '';
  800. articleData[dataKey].remark = '';
  801. });
  802. // 重置对话框状态
  803. articleDialog.visible = false;
  804. articleDialog.currentEventId = undefined;
  805. activeTab.value = 'competition-process';
  806. };
  807. /** 保存文章 */
  808. const handleSaveArticle = async () => {
  809. if (!articleDialog.currentEventId) {
  810. proxy?.$modal.msgError('赛事ID不能为空');
  811. return;
  812. }
  813. const currentTabName = activeTab.value;
  814. const type = tabTypeMapping[currentTabName];
  815. const dataKey = getDataKeyByTabName(currentTabName);
  816. if (!dataKey || !articleData[dataKey]) {
  817. proxy?.$modal.msgError('获取当前标签页数据失败');
  818. return;
  819. }
  820. const currentData = articleData[dataKey];
  821. if (!currentData.title?.trim()) {
  822. proxy?.$modal.msgError('标题不能为空');
  823. return;
  824. }
  825. try {
  826. const formData: EventMdForm = {
  827. id: currentData.id,
  828. eventId: articleDialog.currentEventId,
  829. title: currentData.title,
  830. content: currentData.content || '',
  831. type: type,
  832. remark: currentData.remark || ''
  833. };
  834. await editEventMd(formData);
  835. proxy?.$modal.msgSuccess('文章保存成功');
  836. // 重新加载当前标签页数据以获取最新的ID
  837. await loadTabData(currentTabName);
  838. } catch (error) {
  839. console.error('保存文章失败:', error);
  840. proxy?.$modal.msgError('保存文章失败');
  841. }
  842. };
  843. onMounted(() => {
  844. // 获取默认赛事信息
  845. gameEventStore.fetchDefaultEvent();
  846. getList();
  847. });
  848. // 监听路由变化,当从编辑页返回时检查是否需要刷新列表
  849. onActivated(() => {
  850. // 检查是否有需要刷新的标识
  851. const needRefresh = sessionStorage.getItem('needRefreshGameEventList');
  852. if (needRefresh === 'true') {
  853. // 清除标识
  854. sessionStorage.removeItem('needRefreshGameEventList');
  855. // 刷新列表数据
  856. getList();
  857. }
  858. });
  859. </script>
  860. <style scoped>
  861. .operation-buttons {
  862. display: flex;
  863. flex-wrap: wrap;
  864. gap: 8px;
  865. justify-content: center;
  866. align-items: center;
  867. }
  868. .operation-buttons .el-button {
  869. display: flex;
  870. align-items: center;
  871. gap: 4px;
  872. padding: 6px 12px;
  873. border-radius: 6px;
  874. transition: all 0.3s ease;
  875. }
  876. .operation-buttons .el-button:hover {
  877. transform: translateY(-1px);
  878. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  879. }
  880. .button-text {
  881. font-size: 12px;
  882. font-weight: 500;
  883. white-space: nowrap;
  884. }
  885. /* 为不同类型的按钮设置不同的颜色主题 */
  886. .operation-buttons .el-button--primary {
  887. color: #409eff;
  888. }
  889. .operation-buttons .el-button--danger {
  890. color: #f56c6c;
  891. }
  892. .operation-buttons .el-button--warning {
  893. color: #e6a23c;
  894. }
  895. .operation-buttons .el-button--info {
  896. color: #909399;
  897. }
  898. .operation-buttons .el-button--success {
  899. color: #67c23a;
  900. }
  901. /* 响应式设计 */
  902. @media (max-width: 1200px) {
  903. .operation-buttons {
  904. gap: 4px;
  905. }
  906. .operation-buttons .el-button {
  907. padding: 4px 8px;
  908. }
  909. .button-text {
  910. font-size: 11px;
  911. }
  912. }
  913. </style>