Browse Source

feat(tournaments): 新增比赛项目和类目标签功能

- 添加比赛项目下拉选择框,支持德州扑克、奥马哈等类型
- 新增比赛类目和标签的多选功能,支持标签管理跳转
- 增加是否冠名选项,控制比赛背景字段显示
- 添加延迟卡和重复买入相关配置项
- 实现比赛标签和类目的数据加载与提交处理
- 优化表单字段顺序和逻辑判断条件
fugui001 1 tuần trước cách đây
mục cha
commit
ba01c76102

+ 15 - 0
src/api/system/business/tournamentsTemplate/types.ts

@@ -84,6 +84,14 @@ export interface TournamentsVO {
   itemsPrizeList?: ItemsPrize[];
   tournamentsBiId?: string;
   signNum?: number;
+
+  delayCardNum?: number;
+  rebuyLimit?: number;
+  delayCardTime?: number;
+  isSponsor?: number;
+  isDelay?: number;
+  rebuy?: number;
+  delayShow?: number;
 }
 
 export interface TournamentsForm extends BaseEntity {
@@ -170,6 +178,13 @@ export interface TournamentsForm extends BaseEntity {
   delayCardNum?: number;
   delayCardTime?: number;
   minPlayers?: number;
+  isSponsor?: number;
+  isDelay?: number;
+  rebuy?: number;
+  delayShow?: number;
+  rebuyLimit?: number;
+  tagId?: number[];
+  categoryId?: number[];
 }
 
 export interface ItemsPrize {

+ 287 - 78
src/views/system/business/tournamentsTemplate/index.vue

@@ -47,6 +47,11 @@
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="模版比赛ID" align="center" prop="id" />
         <el-table-column label="比赛名" align="center" prop="name" />
+        <el-table-column label="比赛项目" align="center" prop="gameVariant">
+          <template #default="scope">
+            {{ getGameVariantText(scope.row.gameVariant) }}
+          </template>
+        </el-table-column>
         <el-table-column label="比赛Logo" align="center">
           <template #default="scope">
             <el-image
@@ -84,10 +89,28 @@
             }}
           </template>
         </el-table-column>
-
-        <el-table-column label="报名截止盲注等级" align="center" prop="lateRegistrationLevel" />
+        <el-table-column label="比赛类目" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.categoryName && scope.row.categoryName.length > 0">
+              <el-tag v-for="(category, index) in scope.row.categoryName" :key="index" type="primary" style="margin-right: 4px">
+                {{ category }}
+              </el-tag>
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="比赛标签" align="center">
+          <template #default="scope">
+            <span v-if="scope.row.tagName && scope.row.tagName.length > 0">
+              <el-tag v-for="(tag, index) in scope.row.tagName" :key="index" type="success" style="margin-right: 4px">
+                {{ tag }}
+              </el-tag>
+            </span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="截止盲注等级" align="center" prop="lateRegistrationLevel" />
         <el-table-column label="盲注表" align="center" prop="blindStructuresName" />
-
         <el-table-column label="奖励" align="center">
           <template #default="scope">
             <div v-if="scope.row.itemsPrizeList && scope.row.itemsPrizeList.length > 0">
@@ -134,6 +157,22 @@
     </el-card>
     <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'" />
@@ -180,9 +219,14 @@
             </el-upload>
           </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"
@@ -222,8 +266,39 @@
             </el-upload>
           </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'"
+              multiple
+              collapse-tags
+              collapse-tags-tooltip
+            >
+              <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'"
+              multiple
+              collapse-tags
+              collapse-tags-tooltip
+            >
+              <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
             clearable
@@ -234,22 +309,25 @@
             :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
@@ -279,87 +357,80 @@
         <el-form-item label="晋级条件值" prop="qualifierValue" v-if="form.qualifierType === '2'">
           <el-input v-model="form.qualifierValue" placeholder="请输入晋级条件值" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-        <el-form-item label="报名时" prop="signTime">
+        <el-form-item label="报名时" prop="signTime">
           <el-select v-model="form.signTime" placeholder="请选择" :disabled="dialog.mode === 'view'">
             <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="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" />
           </el-select>
         </el-form-item>
-
         <el-form-item label="起始级别" prop="startBlindLevel">
           <el-select v-model="form.startBlindLevel" placeholder="选项" style="width: 200px" :disabled="dialog.mode === 'view'">
             <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">
           <el-input v-model="form.levelDuration" placeholder="请输入级别持续时间" :disabled="dialog.mode === 'view'" />
         </el-form-item>
-
         <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="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 label="最小参赛人数" prop="minPlayers">
-          <el-input v-model="form.minPlayers" placeholder="请输入最小参赛人数" :disabled="dialog.mode === 'view'" />
+            <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="奖励内容">
           <div v-for="(reward, index) in formPrize.rewards" :key="index" style="display: flex; align-items: center; margin-bottom: 8px">
             <span>第{{ reward.ranking }}名</span>
@@ -467,6 +538,7 @@ import { ref } from 'vue';
 import LevelsIndex from '@/views/system/business/levels/index.vue';
 import { ElSelect } from 'element-plus';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { selectTagSelList, selectCategorySelList } from '@/api/system/business/tag';
 const { tournaments_type, tournaments_time, tournaments_status, game_variant_type, qualifier_type } = toRefs<any>(
   proxy?.useDict('tournaments_type', 'tournaments_time', 'tournaments_status', 'game_variant_type', 'qualifier_type')
 );
@@ -705,7 +777,12 @@ const initFormData: TournamentsForm = {
   itemsPrizeList: [],
   delayCardNum: 4,
   delayCardTime: 15,
-  minPlayers: 5
+  minPlayers: 5,
+  isSponsor: 1, // 添加赞助商标识,默认为否
+  isDelay: 1,
+  rebuyLimit: null,
+  tagId: [], // 修改为数组形式以支持多选
+  categoryId: []
 };
 const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
   form: { ...initFormData },
@@ -927,6 +1004,31 @@ const handleUpdate = async (row?: TournamentsVO, mode: 'edit' | 'view' = 'edit')
   form.value.startBlindLevel = Number(res.data.startBlindLevel);
   competitionBg.value = res.data.competitionBg;
   competitionIcon.value = res.data.competitionIcon;
+  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;
+  }
+  // 确保tagId是数组格式
+  if (!Array.isArray(form.value.tagId)) {
+    form.value.tagId = form.value.tagId ? [form.value.tagId] : [];
+  }
+
+  // 确保categoryId是数组格式
+  if (!Array.isArray(form.value.categoryId)) {
+    form.value.categoryId = form.value.categoryId ? [form.value.categoryId] : [];
+  }
   // 处理奖励表单数据
   const prizeItems = res.data.itemsPrizeList || [];
   if (prizeItems.length > 0) {
@@ -965,10 +1067,25 @@ 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;
+      }
+      // 确保tagId始终是数组格式
+      let submitTagId = data.form.tagId;
+      if (!Array.isArray(submitTagId)) {
+        submitTagId = submitTagId ? [submitTagId] : [];
+      }
+      // 确保categoryId是数组格式
+      if (!Array.isArray(form.value.categoryId)) {
+        form.value.categoryId = form.value.categoryId ? [form.value.categoryId] : [];
+      }
       // 构造最终要提交的数据对象
       const formData: TournamentsForm = {
         ...data.form,
+        tagId: submitTagId, // 使用处理后的tagId值
+        rebuy: submitRebuyValue, // 使用处理后的rebuy值
         competitionIcon: data.form.competitionIcon, // 确保 iconUrl 存在
         itemsPrizeList: formPrize.rewards.map((reward) => ({
           ranking: Number(reward.ranking),
@@ -1023,6 +1140,8 @@ onMounted(() => {
   loadItemStructuresOptions();
   // 直接调用 loadSelectData 来加载初始数据
   loadSelectData('');
+  loadTagOptions();
+  loadCategoryOptions();
 });
 
 async function getAssignList() {
@@ -1250,6 +1369,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');
@@ -1314,6 +1447,82 @@ 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;
+  }
+};
+const getGameVariantText = (value: string | number | null | undefined): string => {
+  const numValue = Number(value);
+  switch (numValue) {
+    case 0:
+      return '德州扑克';
+    case 1:
+      return '奥马哈';
+    case 2:
+      return '短牌';
+    default:
+      return '未知';
+  }
+};
 </script>
 <style scoped>
 .more-rewards {