edit.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856
  1. <template>
  2. <div class="game-event-edit">
  3. <el-card shadow="never">
  4. <template #header>
  5. <div class="page-header">
  6. <el-button @click="goBack" icon="ArrowLeft">返回</el-button>
  7. <span class="page-title">{{ isEdit ? '编辑赛事' : '新增赛事' }}</span>
  8. </div>
  9. </template>
  10. <el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
  11. <!-- 基本信息标签页 -->
  12. <el-tab-pane label="基本信息" name="basic">
  13. <el-form ref="basicFormRef" :model="basicForm" :rules="basicRules" label-width="120px">
  14. <el-row :gutter="20">
  15. <el-col :span="12">
  16. <el-form-item label="赛事编号" prop="eventCode">
  17. <el-input v-model="basicForm.eventCode" placeholder="请输入赛事编号" />
  18. </el-form-item>
  19. </el-col>
  20. <el-col :span="12">
  21. <el-form-item label="赛事名称" prop="eventName">
  22. <el-input v-model="basicForm.eventName" placeholder="请输入赛事名称" />
  23. </el-form-item>
  24. </el-col>
  25. </el-row>
  26. <el-row :gutter="20">
  27. <el-col :span="12">
  28. <el-form-item label="赛事类型" prop="eventType">
  29. <el-select v-model="basicForm.eventType" placeholder="请选择赛事类型" style="width: 100%">
  30. <el-option v-for="dict in game_event_type" :key="dict.value" :label="dict.label" :value="dict.value" />
  31. </el-select>
  32. </el-form-item>
  33. </el-col>
  34. <el-col :span="12">
  35. <el-form-item label="用途" prop="purpose">
  36. <el-select v-model="basicForm.purpose" placeholder="请选择用途" style="width: 100%">
  37. <el-option v-for="dict in game_event_purpose" :key="dict.value" :label="dict.label" :value="dict.value" />
  38. </el-select>
  39. </el-form-item>
  40. </el-col>
  41. </el-row>
  42. <el-row :gutter="20">
  43. <el-col :span="12">
  44. <el-form-item label="开始时间" prop="startTime">
  45. <el-date-picker
  46. v-model="basicForm.startTime"
  47. type="datetime"
  48. value-format="YYYY-MM-DD HH:mm:ss"
  49. placeholder="请选择开始时间"
  50. style="width: 100%"
  51. />
  52. </el-form-item>
  53. </el-col>
  54. <el-col :span="12">
  55. <el-form-item label="结束时间" prop="endTime">
  56. <el-date-picker
  57. v-model="basicForm.endTime"
  58. type="datetime"
  59. value-format="YYYY-MM-DD HH:mm:ss"
  60. placeholder="请选择结束时间"
  61. style="width: 100%"
  62. />
  63. </el-form-item>
  64. </el-col>
  65. </el-row>
  66. <el-row :gutter="20">
  67. <el-col :span="12">
  68. <el-form-item label="举办地点" prop="location">
  69. <el-input v-model="basicForm.location" placeholder="请输入举办地点" />
  70. </el-form-item>
  71. </el-col>
  72. <el-col :span="12">
  73. <el-form-item label="举办单位" prop="unit">
  74. <el-input v-model="basicForm.unit" placeholder="请输入举办单位" />
  75. </el-form-item>
  76. </el-col>
  77. </el-row>
  78. <el-row :gutter="20">
  79. <el-col :span="12">
  80. <el-form-item label="是否默认赛事" prop="isDefault">
  81. <el-radio-group v-model="basicForm.isDefault">
  82. <el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
  83. </el-radio-group>
  84. </el-form-item>
  85. </el-col>
  86. <el-col :span="12">
  87. <el-form-item label="状态" prop="status">
  88. <el-radio-group v-model="basicForm.status">
  89. <el-radio v-for="dict in game_event_status" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
  90. </el-radio-group>
  91. </el-form-item>
  92. </el-col>
  93. </el-row>
  94. <el-row :gutter="20">
  95. <el-col :span="12">
  96. <el-form-item label="赛事链接" prop="eventUrl">
  97. <image-upload-cropper v-model="basicForm.eventUrl" />
  98. </el-form-item>
  99. </el-col>
  100. <el-col :span="12">
  101. <el-form-item label="裁判码" prop="refereeUrl">
  102. <image-upload-cropper v-model="basicForm.refereeUrl" />
  103. </el-form-item>
  104. </el-col>
  105. </el-row>
  106. <el-row :gutter="20">
  107. <el-col :span="12" v-for="item in imageConfigItems" :key="item.configKey">
  108. <el-form-item :label="item.configDesc">
  109. <image-upload-cropper v-model="item.configValue" />
  110. </el-form-item>
  111. </el-col>
  112. </el-row>
  113. <el-form-item label="备注" prop="remark">
  114. <el-input v-model="basicForm.remark" type="textarea" placeholder="请输入备注" :rows="3" />
  115. </el-form-item>
  116. </el-form>
  117. </el-tab-pane>
  118. <!-- 赛事菜单标签页 -->
  119. <el-tab-pane label="赛事菜单" name="menu">
  120. <div class="menu-config">
  121. <div class="menu-header">
  122. <h3>赛事菜单</h3>
  123. <el-button type="primary" @click="showMenuSelectDialog" icon="Plus">添加菜单项</el-button>
  124. </div>
  125. <el-table :data="menuItems" border style="width: 100%">
  126. <el-table-column label="菜单名称" prop="name" width="200">
  127. <template #default="scope">
  128. <span>{{ scope.row.name }}</span>
  129. </template>
  130. </el-table-column>
  131. <el-table-column label="图标" prop="pic" width="80">
  132. <template #default="scope">
  133. <el-image v-if="scope.row.pic" :src="scope.row.pic" style="width: 40px; height: 40px" fit="cover" />
  134. <span v-else>-</span>
  135. </template>
  136. </el-table-column>
  137. <el-table-column label="跳转类型" prop="jumpType" width="100">
  138. <template #default="scope">
  139. <span>{{ getJumpTypeLabel(scope.row.jumpType) }}</span>
  140. </template>
  141. </el-table-column>
  142. <el-table-column label="颜色" prop="color" width="100">
  143. <template #default="scope">
  144. <div v-if="scope.row.color" :style="{ backgroundColor: scope.row.color, width: '30px', height: '20px', borderRadius: '4px' }"></div>
  145. </template>
  146. </el-table-column>
  147. <el-table-column label="跳转路径" prop="jumpPath">
  148. <template #default="scope">
  149. <span>{{ scope.row.jumpPath }}</span>
  150. </template>
  151. </el-table-column>
  152. <el-table-column label="排序" prop="sortNum" width="80">
  153. <template #default="scope">
  154. <span>{{ scope.row.sortNum }}</span>
  155. </template>
  156. </el-table-column>
  157. <el-table-column label="活动类型" prop="activityType" width="100">
  158. <template #default="scope">
  159. <span>{{ scope.row.activityType === 0 ? '赛前' : scope.row.activityType === 1 ? '赛中' : '赛后' }}</span>
  160. </template>
  161. </el-table-column>
  162. <el-table-column label="操作" width="100">
  163. <template #default="scope">
  164. <el-button type="danger" size="small" @click="removeMenuItem(scope.$index)" icon="Delete">删除 </el-button>
  165. </template>
  166. </el-table-column>
  167. </el-table>
  168. </div>
  169. <!-- 菜单选择弹框 -->
  170. <el-dialog v-model="menuSelectDialogVisible" title="选择菜单" width="60%" :close-on-click-modal="false">
  171. <div class="menu-select-content">
  172. <div class="search-bar" style="margin-bottom: 16px">
  173. <el-input v-model="menuSearchKeyword" placeholder="搜索菜单名称" clearable style="width: 300px" />
  174. </div>
  175. <el-table :data="filteredAvailableMenus" border style="width: 100%" @selection-change="handleMenuSelectionChange" max-height="400">
  176. <el-table-column type="selection" width="55" />
  177. <el-table-column label="菜单名称" prop="name" width="200" />
  178. <el-table-column label="图标" prop="pic" width="80">
  179. <template #default="scope">
  180. <el-image v-if="scope.row.pic" :src="scope.row.pic" style="width: 40px; height: 40px" fit="cover" />
  181. </template>
  182. </el-table-column>
  183. <el-table-column label="颜色" prop="color" width="100">
  184. <template #default="scope">
  185. <div
  186. v-if="scope.row.color"
  187. :style="{ backgroundColor: scope.row.color, width: '30px', height: '20px', borderRadius: '4px' }"
  188. ></div>
  189. </template>
  190. </el-table-column>
  191. <el-table-column label="跳转类型" prop="jumpType" width="120">
  192. <template #default="scope">
  193. <span>{{ getJumpTypeLabel(scope.row.jumpType) }}</span>
  194. </template>
  195. </el-table-column>
  196. <el-table-column label="跳转路径" prop="jumpPath" />
  197. <el-table-column label="排序" prop="sortNum" width="80" />
  198. </el-table>
  199. </div>
  200. <template #footer>
  201. <span class="dialog-footer">
  202. <el-button @click="menuSelectDialogVisible = false">取消</el-button>
  203. <el-button type="primary" @click="confirmAddMenuItems">确认添加</el-button>
  204. </span>
  205. </template>
  206. </el-dialog>
  207. </el-tab-pane>
  208. <!-- 配置信息标签页 -->
  209. <el-tab-pane label="配置信息" name="config">
  210. <div class="config-info">
  211. <div class="config-header">
  212. <h3>赛事配置信息</h3>
  213. <el-button type="primary" @click="addConfigItem" icon="Plus">添加配置</el-button>
  214. </div>
  215. <el-table :data="configItems" border style="width: 100%">
  216. <el-table-column label="配置类型" prop="configType" width="150">
  217. <template #default="scope">
  218. <el-select v-model="scope.row.configType" placeholder="请选择配置类型" style="width: 100%">
  219. <el-option v-for="type in configTypes" :key="type.typeCode" :label="type.typeName" :value="type.typeCode" />
  220. </el-select>
  221. </template>
  222. </el-table-column>
  223. <el-table-column label="配置键" prop="configKey" width="200">
  224. <template #default="scope">
  225. <el-input v-model="scope.row.configKey" placeholder="请输入配置键" />
  226. </template>
  227. </el-table-column>
  228. <el-table-column label="配置值" prop="configValue">
  229. <template #default="scope">
  230. <image-or-url-input v-model="scope.row.configValue" />
  231. </template>
  232. </el-table-column>
  233. <el-table-column label="配置描述" prop="configDesc">
  234. <template #default="scope">
  235. <el-input v-model="scope.row.configDesc" placeholder="请输入配置描述" />
  236. </template>
  237. </el-table-column>
  238. <!-- <el-table-column label="是否启用" prop="isEnabled">
  239. <template #default="scope">
  240. <el-switch v-model="scope.row.isEnabled" />
  241. </template>
  242. </el-table-column> -->
  243. <el-table-column label="操作">
  244. <template #default="scope">
  245. <el-button type="danger" size="small" @click="removeConfigItem(scope.$index)" icon="Delete">删除 </el-button>
  246. </template>
  247. </el-table-column>
  248. </el-table>
  249. </div>
  250. </el-tab-pane>
  251. </el-tabs>
  252. <!-- 底部操作按钮 -->
  253. <div class="form-actions">
  254. <el-button @click="goBack">取消</el-button>
  255. <el-button type="primary" @click="saveEvent" :loading="saveLoading">保存</el-button>
  256. </div>
  257. </el-card>
  258. </div>
  259. </template>
  260. <script setup name="GameEventEdit" lang="ts">
  261. import { getGameEvent, addGameEvent, updateGameEvent, changeEventDefault } from '@/api/system/gameEvent';
  262. import { GameEventVO, GameEventForm } from '@/api/system/gameEvent/types';
  263. import { listGameEventConfig, addGameEventConfig, updateGameEventConfig, delGameEventConfig } from '@/api/system/gameEventConfig';
  264. import { GameEventConfigForm } from '@/api/system/gameEventConfig/types';
  265. import { listGameEventConfigType } from '@/api/system/gameEventConfigType'; // 添加导入
  266. import { GameEventConfigTypeVO } from '@/api/system/gameEventConfigType/types'; // 添加导入
  267. import { useRoute, useRouter } from 'vue-router';
  268. import { listRelateMenu, getEnabledNavigator, editRelate, type EditRelateMenuParams, GameNavigatorVo } from '@/api/system/common/nav/gameNavigator';
  269. const route = useRoute();
  270. const router = useRouter();
  271. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  272. const { game_event_type, game_event_purpose, game_event_status, sys_yes_no, game_project_type } = toRefs<any>(
  273. proxy?.useDict('game_event_type', 'game_event_purpose', 'game_event_status', 'sys_yes_no', 'game_project_type')
  274. );
  275. // 页面状态
  276. const isEdit = ref(false);
  277. const activeTab = ref('basic');
  278. const saveLoading = ref(false);
  279. // 判断是否为默认赛事操作
  280. const isDefaultEventOperation = () => {
  281. return basicForm.value.isDefault === '0';
  282. };
  283. // 获取有效的赛事ID(默认赛事返回空字符串,否则返回实际ID)
  284. const getEffectiveEventId = (eventId?: string | number): string => {
  285. if (isDefaultEventOperation()) {
  286. return '';
  287. }
  288. return String(eventId || (route.params.id as string) || '');
  289. };
  290. // 表单引用
  291. const basicFormRef = ref<ElFormInstance>();
  292. // 基本信息表单
  293. const basicForm = ref<GameEventForm>({
  294. eventCode: '',
  295. eventName: '',
  296. eventType: '',
  297. location: '',
  298. purpose: '',
  299. startTime: '',
  300. endTime: '',
  301. eventUrl: '',
  302. refereeUrl: '',
  303. registerUrl: '',
  304. unit: '',
  305. isDefault: '0',
  306. status: '0',
  307. remark: ''
  308. });
  309. // 表单验证规则
  310. const basicRules = {
  311. eventCode: [{ required: true, message: '请输入赛事编号', trigger: 'blur' }],
  312. eventName: [{ required: true, message: '请输入赛事名称', trigger: 'blur' }],
  313. eventType: [{ required: true, message: '请选择赛事类型', trigger: 'change' }],
  314. startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
  315. endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
  316. };
  317. const currentEventId = ref<number>();
  318. // 初始化页面
  319. onMounted(async () => {
  320. const eventId = route.params.id as string;
  321. if (eventId) {
  322. isEdit.value = true;
  323. currentEventId.value = Number(eventId);
  324. await loadEventData(eventId);
  325. } else {
  326. // 新增模式,加载图片配置模板
  327. // await loadImageConfigTemplates();
  328. }
  329. // 加载配置类型选项
  330. await loadConfigTypes();
  331. });
  332. // 加载赛事数据
  333. const loadEventData = async (eventId: string | number) => {
  334. try {
  335. const res = await getGameEvent(eventId);
  336. const data = res.data;
  337. // 填充基本信息
  338. Object.assign(basicForm.value, data);
  339. // 加载图片配置数据
  340. try {
  341. await loadImageConfigData(eventId);
  342. } catch (error) {
  343. console.warn('加载图片配置数据失败,继续加载其他数据:', error);
  344. // 不中断其他数据的加载
  345. }
  346. await loadMenuData(eventId); // 加载菜单数据
  347. await loadConfigData(eventId); // 加载配置数据
  348. } catch (error) {
  349. proxy?.$modal.msgError('加载赛事数据失败');
  350. }
  351. };
  352. /** 状态修改 */
  353. const handleStatusChange = async (row: GameEventVO) => {
  354. const text = row.isDefault === '0' ? '启用' : '停用';
  355. try {
  356. await proxy?.$modal.confirm('确认要"' + text + '""' + row.eventName + '"为默认赛事吗?');
  357. await changeEventDefault(row.eventId, row.isDefault);
  358. await loadEventData(row.eventId);
  359. proxy?.$modal.msgSuccess(text + '成功');
  360. } catch {
  361. return;
  362. } finally {
  363. row.isDefault = row.isDefault === '0' ? '1' : '0';
  364. }
  365. };
  366. // 菜单列表数据
  367. const menuItems = ref<GameNavigatorVo[]>([]);
  368. // 菜单选择弹框相关数据
  369. const menuSelectDialogVisible = ref(false);
  370. const availableMenus = ref<any[]>([]);
  371. const selectedMenus = ref<any[]>([]);
  372. const menuSearchKeyword = ref('');
  373. // 过滤可用菜单的计算属性
  374. const filteredAvailableMenus = computed(() => {
  375. if (!menuSearchKeyword.value) {
  376. return availableMenus.value;
  377. }
  378. return availableMenus.value.filter((menu) => menu.name?.toLowerCase().includes(menuSearchKeyword.value.toLowerCase()));
  379. });
  380. // 加载菜单数据
  381. const loadMenuData = async (eventId: string | number) => {
  382. try {
  383. const res = await listRelateMenu({
  384. eventId: eventId,
  385. pageNum: 1,
  386. pageSize: 1000,
  387. orderByColumn: '',
  388. isAsc: ''
  389. });
  390. menuItems.value = Array.isArray(res.rows) ? res.rows : [];
  391. } catch (error) {
  392. proxy?.$modal.msgError('加载菜单数据失败');
  393. }
  394. };
  395. // 显示菜单选择弹框
  396. const showMenuSelectDialog = async () => {
  397. try {
  398. // 加载可用的导航菜单
  399. const res = await getEnabledNavigator();
  400. availableMenus.value = Array.isArray(res.data) ? res.data : [];
  401. selectedMenus.value = [];
  402. menuSearchKeyword.value = '';
  403. menuSelectDialogVisible.value = true;
  404. } catch (error) {
  405. proxy?.$modal.msgError('加载可用菜单失败');
  406. }
  407. };
  408. // 处理菜单选择变化
  409. const handleMenuSelectionChange = (selection: any[]) => {
  410. selectedMenus.value = selection;
  411. };
  412. // 确认添加菜单项
  413. const confirmAddMenuItems = () => {
  414. console.log('selectedMenus:', selectedMenus.value);
  415. if (selectedMenus.value.length === 0) {
  416. proxy?.$modal.msgWarning('请选择要添加的菜单');
  417. return;
  418. }
  419. // 将选中的菜单添加到菜单列表中
  420. selectedMenus.value.forEach((menu) => {
  421. // 检查是否已存在相同的菜单(根据menu的navId和当前menuItems的icon字段比较)
  422. const exists = menuItems.value.some((item) => item.navId === Number(menu.navId));
  423. if (!exists) {
  424. menuItems.value.push(menu);
  425. }
  426. });
  427. console.log('menuItems:', menuItems.value);
  428. menuSelectDialogVisible.value = false;
  429. proxy?.$modal.msgSuccess(`成功添加 ${selectedMenus.value.length} 个菜单项`);
  430. };
  431. // 获取跳转类型标签
  432. const getJumpTypeLabel = (jumpType: number) => {
  433. const typeMap = {
  434. 1: '跳转链接',
  435. 2: '不跳转',
  436. 3: '小程序内链',
  437. 4: '小程序外链',
  438. 5: 'H5外链',
  439. 6: '公众号文章',
  440. 7: '公众号',
  441. 8: '电话拨号'
  442. };
  443. return typeMap[jumpType as keyof typeof typeMap] || '未知类型';
  444. };
  445. // 删除菜单项
  446. const removeMenuItem = async (index: number) => {
  447. try {
  448. // 从本地数组中删除元素
  449. menuItems.value.splice(index, 1);
  450. // 获取当前赛事ID
  451. const eventId = (route.params.id as string) || '';
  452. // 调用saveMenuData方法同步到后端
  453. await saveMenuData(eventId);
  454. proxy?.$modal.msgSuccess('删除菜单项成功');
  455. } catch (error) {
  456. console.error('删除菜单项失败:', error);
  457. proxy?.$modal.msgError('删除菜单项失败');
  458. }
  459. };
  460. // 保存菜单数据
  461. const saveMenuData = async (eventId: string | number) => {
  462. try {
  463. // 提取所有菜单的navId
  464. const menus = menuItems.value.map((item) => Number(item.navId)); // 转换为数字类型
  465. // 调用editRelate接口保存赛事关联菜单
  466. const relateData: EditRelateMenuParams = {
  467. eventId: eventId,
  468. menus: menus
  469. };
  470. await editRelate(relateData);
  471. } catch (error) {
  472. console.error('保存菜单数据失败:', error);
  473. throw new Error('保存菜单失败');
  474. }
  475. };
  476. // 图片配置数据
  477. const imageConfigItems = ref<GameEventConfigForm[]>([]);
  478. // 加载图片配置数据(用于编辑模式)
  479. const loadImageConfigData = async (eventId: string | number) => {
  480. try {
  481. // 先查询所有image类型的配置模板(不限制eventId,获取所有可用的图片配置项)
  482. const templateRes = await listGameEventConfig({
  483. configType: 'IMAGE',
  484. pageNum: 1,
  485. pageSize: 100,
  486. orderByColumn: '',
  487. isAsc: ''
  488. });
  489. // 获取所有图片配置模板
  490. const allImageConfigs = Array.isArray(templateRes.rows) ? templateRes.rows : [];
  491. // 查询当前赛事的图片配置数据(如果是默认赛事,eventId为空字符串)
  492. const eventRes = await listGameEventConfig({
  493. eventId: eventId === '' ? '' : eventId,
  494. configType: 'IMAGE',
  495. pageNum: 1,
  496. pageSize: 100,
  497. orderByColumn: '',
  498. isAsc: ''
  499. });
  500. const eventImageConfigs = Array.isArray(eventRes.rows) ? eventRes.rows : [];
  501. // 合并配置:以模板为基础,用当前赛事的数据覆盖
  502. imageConfigItems.value = allImageConfigs.map((template) => {
  503. const existingConfig = eventImageConfigs.find((item) => item.configKey === template.configKey);
  504. return {
  505. ...template,
  506. eventId: eventId,
  507. configValue: existingConfig?.configValue || '',
  508. isEnabled: existingConfig?.isEnabled || template.isEnabled || '0',
  509. status: existingConfig?.status || template.status || '0',
  510. configId: existingConfig?.configId
  511. };
  512. });
  513. } catch (error) {
  514. console.error('加载图片配置数据失败:', error);
  515. proxy?.$modal.msgError('加载图片配置数据失败: ' + (error as Error).message);
  516. imageConfigItems.value = [];
  517. }
  518. };
  519. // 刷新图片配置
  520. const refreshImageConfigs = async () => {
  521. const eventId = route.params.id as string;
  522. if (eventId && eventId !== 'add') {
  523. await loadImageConfigData(getEffectiveEventId(eventId));
  524. } else {
  525. // 新增模式,只加载配置模板
  526. // await loadImageConfigTemplates();
  527. }
  528. };
  529. // 保存图片配置数据
  530. const saveImageConfigData = async (eventId?: string | number) => {
  531. try {
  532. // 如果是默认赛事操作,使用空字符串作为eventId
  533. const targetEventId = eventId === '' ? '' : eventId || (route.params.id as string);
  534. const updates: GameEventConfigForm[] = [];
  535. const adds: GameEventConfigForm[] = [];
  536. // 收集需要更新和添加的数据
  537. for (const item of imageConfigItems.value) {
  538. if (item.configKey) {
  539. const configData: GameEventConfigForm = {
  540. ...item,
  541. eventId: targetEventId,
  542. configType: 'IMAGE'
  543. };
  544. if (item.configId && item.configValue) {
  545. updates.push(configData);
  546. } else if (!item.configId && item.configValue) {
  547. adds.push(configData);
  548. }
  549. }
  550. }
  551. // 批量更新
  552. if (updates.length > 0) {
  553. await Promise.all(updates.map(updateGameEventConfig));
  554. }
  555. // 批量添加
  556. if (adds.length > 0) {
  557. await Promise.all(adds.map(addGameEventConfig));
  558. }
  559. } catch (error) {
  560. console.error('保存图片配置数据失败:', error);
  561. throw new Error('保存图片配置失败');
  562. }
  563. };
  564. // 处理标签页切换
  565. const handleTabClick = () => {
  566. // 可以在这里添加标签页切换的逻辑
  567. };
  568. // 配置信息数据
  569. const configItems = ref<GameEventConfigForm[]>([]);
  570. // 配置类型选项
  571. const configTypes = ref<GameEventConfigTypeVO[]>([]);
  572. // 加载配置类型数据
  573. const loadConfigTypes = async () => {
  574. try {
  575. const res = await listGameEventConfigType({
  576. pageNum: 1,
  577. pageSize: 1000,
  578. orderByColumn: '',
  579. isAsc: ''
  580. });
  581. configTypes.value = Array.isArray(res.rows) ? res.rows : [];
  582. } catch (error) {
  583. proxy?.$modal.msgError('加载配置类型数据失败');
  584. }
  585. };
  586. // 加载赛事配置数据
  587. const loadConfigData = async (eventId: string | number) => {
  588. try {
  589. const res = await listGameEventConfig({
  590. eventId: eventId === '' ? '' : eventId,
  591. pageNum: 1,
  592. pageSize: 10,
  593. orderByColumn: '',
  594. isAsc: ''
  595. });
  596. configItems.value = Array.isArray(res.rows) ? res.rows : [];
  597. } catch (error) {
  598. console.error('加载赛事配置数据失败:', error);
  599. proxy?.$modal.msgError('加载赛事配置数据失败: ' + (error as Error).message);
  600. }
  601. };
  602. // 添加配置项
  603. const addConfigItem = () => {
  604. configItems.value.push({
  605. configKey: '',
  606. configValue: '',
  607. configDesc: '',
  608. configType: '' // 添加配置类型字段
  609. });
  610. };
  611. // 删除配置项
  612. const removeConfigItem = (index: number) => {
  613. const configItem = configItems.value[index];
  614. if (configItem.configId) {
  615. delGameEventConfig(configItem.configId).then(() => {
  616. configItems.value.splice(index, 1);
  617. });
  618. } else {
  619. configItems.value.splice(index, 1);
  620. }
  621. };
  622. // 保存赛事配置数据
  623. const saveconfigData = async (eventId: string | number) => {
  624. try {
  625. for (const item of configItems.value) {
  626. // 确保eventId被正确设置
  627. const configData = { ...item, eventId };
  628. if (item.configId) {
  629. // 更新现有队伍
  630. await updateGameEventConfig(configData);
  631. } else if (item.configDesc) {
  632. // 只有有名称的新队伍才保存
  633. await addGameEventConfig(configData);
  634. }
  635. }
  636. } catch (error) {
  637. console.error('保存赛事配置信息失败:', error);
  638. throw new Error('保存赛事配置信息失败');
  639. }
  640. };
  641. // 保存赛事信息
  642. const saveEvent = async () => {
  643. try {
  644. // 验证基本信息表单
  645. await basicFormRef.value?.validate();
  646. saveLoading.value = true;
  647. // 合并表单数据
  648. const formData: GameEventForm = {
  649. ...basicForm.value
  650. };
  651. let savedEventId: string;
  652. // 如果设置为默认赛事,则取消其他赛事的默认状态
  653. if (basicForm.value.isDefault === '0') {
  654. handleStatusChange({
  655. eventId: basicForm.value.eventId,
  656. isDefault: basicForm.value.isDefault,
  657. eventName: basicForm.value.eventName
  658. } as GameEventVO);
  659. }
  660. // 保存基本信息
  661. if (!isEdit.value) {
  662. const addRes = await addGameEvent(formData);
  663. // 假设返回的数据中包含新创建的赛事ID
  664. savedEventId = addRes?.data as string;
  665. } else {
  666. savedEventId = route.params.id as string;
  667. }
  668. // 保存图片配置数据
  669. await saveImageConfigData(savedEventId);
  670. // 保存赛事配置项数据
  671. await saveconfigData(savedEventId);
  672. // 保存菜单数据
  673. await saveMenuData(savedEventId);
  674. // 保存基本信息
  675. if (isEdit.value) {
  676. await updateGameEvent(formData);
  677. }
  678. proxy?.$modal.msgSuccess('保存成功');
  679. // 保存成功后,设置一个标识,表示需要刷新列表数据
  680. sessionStorage.setItem('needRefreshGameEventList', 'true');
  681. goBack();
  682. } catch (error) {
  683. console.error('保存失败:', error);
  684. proxy?.$modal.msgError('保存失败');
  685. } finally {
  686. saveLoading.value = false;
  687. }
  688. };
  689. // 返回上一页
  690. const goBack = () => {
  691. router.go(-1);
  692. };
  693. </script>
  694. <style lang="scss" scoped>
  695. .game-event-edit {
  696. .page-header {
  697. display: flex;
  698. align-items: center;
  699. gap: 16px;
  700. .page-title {
  701. font-size: 18px;
  702. font-weight: 600;
  703. }
  704. }
  705. .menu-config,
  706. .project-config,
  707. .config-info,
  708. .image-config {
  709. .menu-header,
  710. .project-header,
  711. .config-header,
  712. .image-header {
  713. display: flex;
  714. justify-content: space-between;
  715. align-items: center;
  716. margin-bottom: 16px;
  717. h3 {
  718. margin: 0;
  719. font-size: 16px;
  720. font-weight: 600;
  721. }
  722. }
  723. }
  724. .image-config {
  725. .config-info {
  726. margin-top: 8px;
  727. display: flex;
  728. align-items: center;
  729. justify-content: space-between;
  730. }
  731. }
  732. .team-config {
  733. .team-header {
  734. display: flex;
  735. justify-content: space-between;
  736. align-items: center;
  737. margin-bottom: 16px;
  738. h3 {
  739. margin: 0;
  740. font-size: 16px;
  741. font-weight: 600;
  742. }
  743. }
  744. }
  745. .group-config {
  746. .group-header {
  747. display: flex;
  748. justify-content: space-between;
  749. align-items: center;
  750. margin-bottom: 16px;
  751. h3 {
  752. margin: 0;
  753. font-size: 16px;
  754. font-weight: 600;
  755. }
  756. }
  757. }
  758. .form-actions {
  759. display: flex;
  760. justify-content: center;
  761. gap: 16px;
  762. margin-top: 32px;
  763. padding-top: 24px;
  764. border-top: 1px solid #ebeef5;
  765. }
  766. }
  767. :deep(.el-tabs__content) {
  768. padding: 20px 0;
  769. }
  770. :deep(.el-form-item) {
  771. margin-bottom: 20px;
  772. }
  773. </style>