Преглед изворни кода

feat(tournaments): 新增比赛标签与类目管理功能

- 添加查询标签与类目下拉列表接口
- 表单新增比赛项目、比赛类型、是否冠名等字段
- 实现比赛标签与类目的选择及跳转管理页面功能
- 调整延迟卡与重复买入相关字段及逻辑处理
- 更新比赛奖励显示标题并优化表单项结构
- 修复并完善赛事详情数据回显与提交逻辑
- 引入标签与类目选项加载方法并初始化调用
fugui001 пре 1 недеља
родитељ
комит
c9cf5ffb29

+ 19 - 0
src/api/system/business/tag/index.ts

@@ -61,3 +61,22 @@ export const delTag = (id: string | number | Array<string | number>) => {
     method: 'delete'
   });
 };
+
+/**
+ * 查询标签列表
+ */
+export const selectTagSelList = (): AxiosPromise<TagVO[]> => {
+  return request({
+    url: '/business/tag/selectTagSelList',
+    method: 'get'
+  });
+};
+/**
+ * 查询类别列表
+ */
+export const selectCategorySelList = (): AxiosPromise<TagVO[]> => {
+  return request({
+    url: '/business/tag/selectCategorySelList',
+    method: 'get'
+  });
+};

+ 12 - 5
src/api/system/business/tournaments/types.ts

@@ -85,9 +85,9 @@ export interface TournamentsVO {
   tournamentsBiId?: string;
   signNum?: number;
   isComplaints?: boolean;
-
-  delayCardTime?: string;
-  delayCardNum?: string;
+  rebuy?: number;
+  delayCardTime?: number;
+  delayCardNum?: number;
 }
 
 export interface TournamentsForm extends BaseEntity {
@@ -161,11 +161,14 @@ export interface TournamentsForm extends BaseEntity {
    * 盲注表ID
    */
   blindStructureId?: number;
+  tagId?: number;
+  categoryId?: number;
   competitionIcon?: string;
   itemsPrizeList?: ItemsPrize[];
   robotCount?: number;
-  delayCardNum?: string;
-  delayCardTime?: string;
+  delayCardNum?: number;
+  rebuyLimit?: number;
+  delayCardTime?: number;
   competitionBg?: string;
   minPlayers?: number;
   gameVariant?: string;
@@ -174,6 +177,10 @@ export interface TournamentsForm extends BaseEntity {
   startBlindLevel?: number;
   targetTournamentId?: number;
   introduction?: string;
+  isSponsor?: number;
+  isDelay?: number;
+  rebuy?: number;
+  delayShow?: number;
 }
 
 export interface ItemsPrize {

+ 215 - 65
src/views/system/business/tournaments/index.vue

@@ -63,7 +63,7 @@
             </el-button>
           </el-col>
           <!-- 新增的 “盲注管理” 按钮 -->
-<!--          <el-col :span="1.5">
+          <!--          <el-col :span="1.5">
             <el-button type="warning" plain icon="Operation" v-hasPermi="['business:tournaments:manager']">
               <router-link to="/tournament/structures">盲注表管理</router-link>
             </el-button>
@@ -267,6 +267,22 @@
     </el-dialog>-->
     <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body @close="cancel">
       <el-form ref="tournamentsFormRef" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="比赛项目" prop="gameVariant">
+          <el-select aria-required="true" v-model="form.gameVariant" placeholder="请选择" :disabled="dialog.mode === 'view'">
+            <el-option v-for="dict in game_variant_type" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="比赛类型" prop="gameType">
+          <el-select aria-required="true" v-model="form.gameType" placeholder="请选择" :disabled="dialog.mode === 'view'">
+            <el-option
+              v-for="dict in tournaments_type.filter((item) => item.value !== '1')"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
         <!-- 赛事名称 -->
         <el-form-item label="比赛名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入比赛名称" :disabled="dialog.mode === 'view'" />
@@ -314,8 +330,15 @@
           </div>
         </el-form-item>
 
+        <el-form-item label="是否冠名" prop="isSponsor">
+          <el-select v-model="form.isSponsor" placeholder="请选择" :disabled="dialog.mode === 'view'">
+            <el-option label="是" :value="1"></el-option>
+            <el-option label="否" :value="0"></el-option>
+          </el-select>
+        </el-form-item>
+
         <!-- 比赛图标 -->
-        <el-form-item label="比赛背景" prop="icon">
+        <el-form-item label="比赛背景" prop="icon" v-if="form.isSponsor === 1">
           <div class="upload-container">
             <el-upload
               class="upload-icon"
@@ -356,6 +379,22 @@
           </div>
         </el-form-item>
 
+        <el-form-item label="比赛标签" prop="tagId">
+          <div style="display: flex; align-items: center">
+            <el-select v-model="form.tagId" placeholder="选项" style="width: 200px" :disabled="dialog.mode === 'view'">
+              <el-option v-for="item in itemOptionsTagList" :key="item.id" :label="item.label" :value="item.id" />
+            </el-select>
+            <el-button type="primary" :disabled="dialog.mode === 'view'" @click="handleGoToTag"> 标签管理 </el-button>
+          </div>
+        </el-form-item>
+        <el-form-item label="比赛类目" prop="categoryId">
+          <div style="display: flex; align-items: center">
+            <el-select v-model="form.categoryId" placeholder="选项" style="width: 200px" :disabled="dialog.mode === 'view'">
+              <el-option v-for="item in itemOptionsCategoryList" :key="item.id" :label="item.label" :value="item.id" />
+            </el-select>
+            <el-button type="primary" :disabled="dialog.mode === 'view'" @click="handleGoToCategory"> 类目管理 </el-button>
+          </div>
+        </el-form-item>
         <!-- 开始时间 -->
         <el-form-item label="开始时间" prop="startTime">
           <el-date-picker
@@ -367,23 +406,27 @@
             :disabled="dialog.mode === 'view'"
           />
         </el-form-item>
-
-        <el-form-item label="比赛类型" prop="gameType">
-          <el-select aria-required="true" v-model="form.gameType" placeholder="请选择" :disabled="dialog.mode === 'view'">
-            <el-option
-              v-for="dict in tournaments_type.filter((item) => item.value !== '1')"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-            >
-            </el-option>
-          </el-select>
+        <el-form-item label="开赛最低人数" prop="minPlayers">
+          <el-input v-model="form.minPlayers" placeholder="请输入开赛最低人数" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-        <el-form-item label="玩法类型" prop="gameVariant">
-          <el-select aria-required="true" v-model="form.gameVariant" placeholder="请选择" :disabled="dialog.mode === 'view'">
-            <el-option v-for="dict in game_variant_type" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
-          </el-select>
+        <!-- 盲注表 -->
+        <el-form-item label="盲注等级" prop="blindStructureId">
+          <div style="display: flex; align-items: center">
+            <el-select
+              v-model="form.blindStructureId"
+              placeholder="选项"
+              style="width: 200px"
+              @change="handleBlindStructureChange"
+              :disabled="dialog.mode === 'view'"
+            >
+              <el-option v-for="item in itemOptionsStructures" :key="item.id" :label="item.label" :value="item.id" />
+            </el-select>
+
+            <el-button type="primary" :disabled="dialog.mode === 'view'" @click="handleGoToStructures"> 上传新盲注 </el-button>
+            <el-button type="primary" @click="handleViewLevels" :disabled="dialog.mode === 'view'">预览</el-button>
+          </div>
         </el-form-item>
+
         <el-form-item label="目标锦标赛" prop="targetTournamentId">
           <el-select
             v-model="form.targetTournamentId"
@@ -418,17 +461,7 @@
             <el-option v-for="dict in tournaments_time" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="报名条件" prop="itemsId">
-          <div style="display: flex; align-items: center; gap: 10px; width: 100%">
-            <el-select v-model="form.itemsId" placeholder="请选择道具类型" :disabled="dialog.mode === 'view'" style="flex: 1">
-              <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
-            </el-select>
 
-            <el-form-item prop="itemsNum" style="margin-bottom: 0; flex: 1">
-              <el-input v-model.number="form.itemsNum" placeholder="数量" :disabled="dialog.mode === 'view'" style="width: 100%" />
-            </el-form-item>
-          </div>
-        </el-form-item>
         <!-- 报名条件 -->
         <!--        <el-form-item label="报名条件" prop="itemsId">
           <div style="display: flex; align-items: center; gap: 10px">
@@ -450,31 +483,6 @@
             </el-form-item>
           </div>
         </el-form-item>-->
-        <!-- 盲注表 -->
-        <el-form-item label="盲注等级" prop="blindStructureId">
-          <div style="display: flex; align-items: center">
-            <el-select
-              v-model="form.blindStructureId"
-              placeholder="选项"
-              style="width: 200px"
-              @change="handleBlindStructureChange"
-              :disabled="dialog.mode === 'view'"
-            >
-              <el-option v-for="item in itemOptionsStructures" :key="item.id" :label="item.label" :value="item.id" />
-            </el-select>
-
-            <el-button type="primary" :disabled="dialog.mode === 'view'" @click="handleGoToStructures"> 上传新盲注 </el-button>
-            <el-button type="primary" @click="handleViewLevels" :disabled="dialog.mode === 'view'">预览</el-button>
-          </div>
-        </el-form-item>
-
-        <!-- 报名截止至 -->
-        <el-form-item label="报名截止至" prop="lateRegistrationLevel">
-          <el-select v-model="form.lateRegistrationLevel" placeholder="选项" style="width: 200px" :disabled="dialog.mode === 'view'">
-            <el-option v-for="item in itemOptionsStructuresLevel" :key="item.id" :label="item.label" :value="item.id" />
-          </el-select>
-        </el-form-item>
-
         <el-form-item label="晋级级别" prop="qualifierValue" v-if="form.qualifierType === '1'">
           <el-select v-model="form.qualifierValue" placeholder="选项" style="width: 200px" :disabled="dialog.mode === 'view'">
             <el-option v-for="item in itemOptionsStructuresLevel2" :key="item.id" :label="item.label" :value="item.id" />
@@ -485,10 +493,6 @@
             <el-option v-for="item in itemOptionsStructuresLevel3" :key="item.id" :label="item.label" :value="item.id" />
           </el-select>
         </el-form-item>
-        <el-form-item label="起始记分牌数量" prop="startingChips">
-          <el-input v-model="form.startingChips" placeholder="请输入起始记分牌数量" :disabled="dialog.mode === 'view'" />
-        </el-form-item>
-
         <el-form-item label="级别持续时间" prop="levelDuration" v-if="dialog.mode === 'edit'">
           <el-input v-model="form.levelDuration" placeholder="请输入级别持续时间" />
         </el-form-item>
@@ -496,19 +500,59 @@
         <el-form-item label="机器人数" prop="robotCount" v-if="!isProdEnvironment">
           <el-input v-model="form.robotCount" placeholder="请输入机器人数" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-
-        <el-form-item label="延迟卡时间" prop="delayCardTime">
+        <el-form-item label="延迟看牌" prop="delayShow">
+          <el-select v-model="form.delayShow" placeholder="请选择" :disabled="dialog.mode === 'view'">
+            <el-option label="开启" :value="1"></el-option>
+            <el-option label="关闭" :value="0"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="延迟卡" prop="isDelay">
+          <el-select v-model="form.isDelay" placeholder="请选择" :disabled="dialog.mode === 'view'" @change="handleIsDelayChange">
+            <el-option label="是" :value="1"></el-option>
+            <el-option label="否" :value="0"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="延迟卡时间" prop="delayCardTime" v-if="form.isDelay === 1">
           <el-input v-model="form.delayCardTime" placeholder="请输入延迟卡时间" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-
-        <el-form-item label="延迟卡数量" prop="delayCardNum">
+        <el-form-item label="延迟卡数量" prop="delayCardNum" v-if="form.isDelay === 1">
           <el-input v-model="form.delayCardNum" placeholder="请输入延迟卡数量" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-        <el-form-item label="最小参赛人数" prop="minPlayers">
-          <el-input v-model="form.minPlayers" placeholder="请输入最小参赛人数" :disabled="dialog.mode === 'view'" />
+        <el-form-item label="重复买入" prop="rebuy">
+          <div style="display: flex; align-items: center; gap: 10px; width: 100%">
+            <el-select v-model="form.rebuy" placeholder="请选择" :disabled="dialog.mode === 'view'" style="flex: 1">
+              <el-option label="不可重复" :value="-1"></el-option>
+              <el-option label="不限制" :value="0"></el-option>
+              <el-option label="限制次数" :value="1"></el-option>
+            </el-select>
+
+            <el-form-item prop="rebuyLimit" style="margin-bottom: 0; flex: 1" v-if="form.rebuy === 1">
+              <el-input v-model.number="form.rebuyLimit" placeholder="请输入次数" :disabled="dialog.mode === 'view'" style="width: 100%" />
+            </el-form-item>
+          </div>
+        </el-form-item>
+        <!-- 报名截止至 -->
+        <el-form-item label="报名截止至" prop="lateRegistrationLevel">
+          <el-select v-model="form.lateRegistrationLevel" placeholder="选项" :disabled="dialog.mode === 'view'">
+            <el-option v-for="item in itemOptionsStructuresLevel" :key="item.id" :label="item.label" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="起始记分牌数量" prop="startingChips">
+          <el-input v-model="form.startingChips" placeholder="请输入起始记分牌数量" :disabled="dialog.mode === 'view'" />
+        </el-form-item>
+        <el-form-item label="报名条件" prop="itemsId">
+          <div style="display: flex; align-items: center; gap: 10px; width: 100%">
+            <el-select v-model="form.itemsId" placeholder="请选择道具类型" :disabled="dialog.mode === 'view'" style="flex: 1">
+              <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
+            </el-select>
+
+            <el-form-item prop="itemsNum" style="margin-bottom: 0; flex: 1">
+              <el-input v-model.number="form.itemsNum" placeholder="数量" :disabled="dialog.mode === 'view'" style="width: 100%" />
+            </el-form-item>
+          </div>
         </el-form-item>
         <!-- 奖励内容 -->
-        <el-form-item label="奖励内容">
+        <el-form-item label="比赛奖励">
           <div v-for="(reward, index) in formPrize.rewards" :key="index" style="display: flex; align-items: center; margin-bottom: 8px">
             <span>第{{ reward.ranking }}名</span>
 
@@ -700,6 +744,7 @@ import {
   recoverTournament,
   deleteRelationTournament
 } from '@/api/system/business/tournaments';
+import { selectTagSelList, selectCategorySelList } from '@/api/system/business/tag';
 import { selectItemsSelList } from '@/api/system/business/items';
 import { selectBlindLevelsById } from '@/api/system/business/levels';
 import { selectBlingStructuresInfo } from '@/api/system/business/structures';
@@ -952,8 +997,11 @@ const initFormData: TournamentsForm = {
   itemsNum: null,
   blindStructureId: null,
   itemsPrizeList: [],
-  delayCardNum: null,
-  delayCardTime: null
+  delayCardNum: 4,
+  delayCardTime: 15,
+  isSponsor: 1, // 添加赞助商标识,默认为否
+  isDelay: 1,
+  rebuyLimit: null
 };
 const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
   form: { ...initFormData },
@@ -1181,6 +1229,22 @@ const handleUpdate = async (row?: TournamentsVO, mode: 'edit' | 'view' = 'edit')
   form.value.gameType = String(res.data.gameType); // 转为字符串
   competitionIcon.value = res.data.competitionIcon;
   competitionBg.value = res.data.competitionBg;
+  if (res.data.delayCardTime == 0 && res.data.delayCardNum == 0) {
+    form.value.isDelay = 0;
+  }
+  if (res.data.competitionBg == null) {
+    form.value.isSponsor = 0;
+  }
+  // 处理rebuy和rebuyLimit的显示逻辑
+  if (res.data.rebuy > 0) {
+    // 如果rebuy值>=0,说明是限制次数的情况
+    form.value.rebuy = 1; // 设置为"限制次数"选项
+    form.value.rebuyLimit = res.data.rebuy; // 将原值保存到rebuyLimit
+  } else {
+    // 否则保持原来的值(-1表示不可重复,0表示不限制)
+    form.value.rebuy = res.data.rebuy;
+    form.value.rebuyLimit = null;
+  }
   // 处理奖励表单数据
   const prizeItems = res.data.itemsPrizeList || [];
   if (prizeItems.length > 0) {
@@ -1218,10 +1282,16 @@ const submitForm = () => {
     if (!valid) return;
     try {
       buttonLoading.value = true;
+      // 当rebuy为1(限制次数)时,将rebuyLimit的值赋给rebuy
+      let submitRebuyValue = data.form.rebuy;
+      if (data.form.rebuy === 1) {
+        submitRebuyValue = data.form.rebuyLimit;
+      }
 
       // 构造最终要提交的数据对象
       const formData: TournamentsForm = {
         ...data.form,
+        rebuy: submitRebuyValue, // 使用处理后的rebuy值
         competitionIcon: data.form.competitionIcon, // 确保 iconUrl 存在
         itemsPrizeList: formPrize.rewards.map((reward) => ({
           ranking: Number(reward.ranking),
@@ -1347,6 +1417,9 @@ onMounted(() => {
 
   // 直接调用 loadSelectData 来加载初始数据
   loadSelectData('');
+
+  loadTagOptions();
+  loadCategoryOptions();
 });
 
 async function getAssignList() {
@@ -1795,6 +1868,20 @@ const handleGoToStructures = () => {
     proxy?.$router.push('/tournament/structures');
   });
 };
+const handleGoToTag = () => {
+  dialog.visible = false; // 关闭弹窗
+  // 使用 nextTick 确保关闭动画完成后跳转(可选)
+  nextTick(() => {
+    proxy?.$router.push('/service/tag');
+  });
+};
+const handleGoToCategory = () => {
+  dialog.visible = false; // 关闭弹窗
+  // 使用 nextTick 确保关闭动画完成后跳转(可选)
+  nextTick(() => {
+    proxy?.$router.push('/service/catory');
+  });
+};
 // 获取奖励提示内容
 const getRewardTooltipContent = (rewards: any[]) => {
   return rewards.map((prize) => `第${prize.ranking}名:${prize.quantity} ${prize.itemsName}`).join('\n');
@@ -1873,6 +1960,69 @@ const handleQualifierTypeChange = (value: string) => {
   // Clear the qualifierValue when qualifierType changes
   form.value.qualifierValue = null;
 };
+
+// 下拉选项数据 selectBlingStructuresInfo
+const itemOptionsTagList = ref<{ id: number; label: string }[]>([]);
+
+// 加载报名条件选项
+const loadTagOptions = async () => {
+  try {
+    const res = await selectTagSelList();
+    if (res.code === 200) {
+      // 使用 unknown 中间类型进行类型转换
+      const data = res.data as unknown as { id: number; name: string }[];
+      const list = [];
+      for (let i = 0; i < data.length; i++) {
+        const item = data[i];
+        list.push({
+          id: item.id,
+          label: item.name
+        });
+      }
+      itemOptionsTagList.value = list;
+    } else {
+      alert('加载失败:' + res.msg);
+    }
+  } catch (error) {
+    console.error('请求出错:', error);
+  }
+};
+// 下拉选项数据 selectBlingStructuresInfo
+const itemOptionsCategoryList = ref<{ id: number; label: string }[]>([]);
+
+// 加载报名条件选项
+const loadCategoryOptions = async () => {
+  try {
+    const res = await selectCategorySelList();
+    if (res.code === 200) {
+      // 使用 unknown 中间类型进行类型转换
+      const data = res.data as unknown as { id: number; name: string }[];
+      const list = [];
+      for (let i = 0; i < data.length; i++) {
+        const item = data[i];
+        list.push({
+          id: item.id,
+          label: item.name
+        });
+      }
+      itemOptionsCategoryList.value = list;
+    } else {
+      alert('加载失败:' + res.msg);
+    }
+  } catch (error) {
+    console.error('请求出错:', error);
+  }
+};
+const handleIsDelayChange = (value: number) => {
+  if (value === 0) {
+    // 当选择"否"时,将延迟卡时间和数量设置为0
+    form.value.delayCardTime = 0;
+    form.value.delayCardNum = 0;
+  } else {
+    form.value.delayCardTime = 15;
+    form.value.delayCardNum = 4;
+  }
+};
 </script>
 <style scoped>
 .more-rewards {