edit.vue 31 KB

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