Переглянути джерело

feat(tournaments): 新增比赛管理功能

- 添加比赛管理相关的 API 接口和类型定义
- 实现比赛列表、新增比赛、编辑比赛和查看比赛功能
- 优化比赛表单,支持上传比赛图标和设置奖励
-调整比赛列表的操作按钮布局
fugui001 5 місяців тому
батько
коміт
d200e48afc

+ 6 - 9
src/api/system/business/tournaments/index.ts

@@ -37,26 +37,23 @@ export const getTournaments = (id: string | number): AxiosPromise<TournamentsVO>
  * 新增【请填写功能名称】
  * @param data
  */
-export const addTournaments = (data: TournamentsForm) => {
+export const addTournaments = (data: FormData) => {
   return request({
     url: '/business/tournaments',
     method: 'post',
-    data: data
+    data: data,
+    headers: { 'Content-Type': 'multipart/form-data' }
   });
 };
 
-/**
- * 修改【请填写功能名称】
- * @param data
- */
-export const updateTournaments = (data: TournamentsForm) => {
+export const updateTournaments = (data: FormData) => {
   return request({
     url: '/business/tournaments',
     method: 'put',
-    data: data
+    data: data,
+    headers: { 'Content-Type': 'multipart/form-data' }
   });
 };
-
 /**
  * 删除【请填写功能名称】
  * @param id

+ 22 - 0
src/api/system/business/tournaments/types.ts

@@ -50,6 +50,28 @@ export interface TournamentsVO {
   /**
    */
   updatedAt: string;
+
+  /**
+   * 报名时间
+   */
+  signTime?: number;
+
+  /**
+   * 道具ID
+   */
+  itemsId?: number;
+
+  /**
+   * 道具数量
+   */
+  itemsNum?: number;
+
+  /**
+   * 盲注表ID
+   */
+  blindStructureId?: number;
+  competitionIcon?: string;
+  itemsPrizeList?: ItemsPrize[];
 }
 
 export interface TournamentsForm extends BaseEntity {

+ 124 - 57
src/views/system/business/tournaments/index.vue

@@ -81,12 +81,27 @@
 
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['business:tournaments:edit']"></el-button>
+            <el-tooltip content="查看" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="View"
+                @click="handleUpdate(scope.row, 'view')"
+                v-hasPermi="['business:tournaments:edit']"
+              ></el-button>
             </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:tournaments:remove']"></el-button>
+            <el-tooltip content="编辑" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="Edit"
+                @click="handleUpdate(scope.row, 'edit')"
+                v-hasPermi="['business:tournaments:edit']"
+              ></el-button>
             </el-tooltip>
+            <!--            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:tournaments:remove']"></el-button>
+            </el-tooltip>-->
 
             <!-- 新增:分配按钮 -->
             <el-tooltip content="分配盲注" placement="top">
@@ -134,77 +149,79 @@
       <el-form ref="tournamentsFormRef" :model="form" :rules="rules" label-width="120px">
         <!-- 赛事名称 -->
         <el-form-item label="比赛名称" prop="name">
-          <el-input v-model="form.name" placeholder="请输入比赛名称" />
+          <el-input v-model="form.name" placeholder="请输入比赛名称" :disabled="dialog.mode === 'view'" />
         </el-form-item>
 
         <!-- 比赛图标 -->
-        <!--        <el-form-item label="比赛图标">
-          <el-button type="primary">点击选择图标</el-button>
-        </el-form-item>-->
         <el-form-item label="比赛图标" prop="icon">
           <div class="upload-container">
             <el-upload
               class="upload-icon"
               action="#"
               :on-change="handleIconChange"
-              :file-list="fileList"
               :auto-upload="false"
               :limit="1"
               accept="image/*"
+              :disabled="dialog.mode === 'view'"
             >
               <template #trigger>
-                <el-button type="primary">点击选择图标</el-button>
-              </template>
-
-              <template #tip>
-                <div class="el-upload__tip">
-                  <!--                  请选择 PNG/JPG/JPEG 格式图片-->
-                  <span v-if="fileList.length > 0">当前已选文件:{{ fileList[0].name }} </span>
-                </div>
+                <el-button type="primary" :disabled="dialog.mode === 'view'">点击选择图标</el-button>
               </template>
             </el-upload>
           </div>
         </el-form-item>
 
-        <!-- 比赛开始时间 -->
+        <!-- 开始时间 -->
         <el-form-item label="开始时间" prop="startTime">
-          <el-date-picker clearable v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择开始时间">
-          </el-date-picker>
+          <el-date-picker
+            clearable
+            v-model="form.startTime"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择开始时间"
+            :disabled="dialog.mode === 'view'"
+          />
         </el-form-item>
 
         <!-- 报名时间 -->
         <el-form-item label="报名时间">
-          <el-select v-model="form.signTime" placeholder="请选择">
+          <el-select v-model="form.signTime" placeholder="请选择" :disabled="dialog.mode === 'view'">
             <el-option label="比赛前5分钟" :value="5" />
             <el-option label="比赛前10分钟" :value="10" />
           </el-select>
         </el-form-item>
-        <!-- 报名条件 -->
 
+        <!-- 报名条件 -->
         <el-form-item label="报名条件">
           <div style="display: flex; align-items: center; gap: 10px">
-            <el-select v-model="form.itemsId" placeholder="请选择道具类型">
+            <el-select v-model="form.itemsId" placeholder="请选择道具类型" :disabled="dialog.mode === 'view'">
               <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
             </el-select>
-            <el-input v-model="form.itemsNum" :min="1" placeholder="数量" />
+            <el-input v-model="form.itemsNum" :min="1" placeholder="数量" :disabled="dialog.mode === 'view'" />
           </div>
         </el-form-item>
 
         <!-- 盲注表 -->
         <el-form-item label="盲注表">
           <div style="display: flex; align-items: center">
-            <el-select v-model="form.blindStructureId" placeholder="选项" style="width: 200px" @change="handleBlindStructureChange">
+            <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">上传新盲注</el-button>
-            <el-button type="primary" @click="handleViewLevels">预览</el-button>
+            <el-button type="primary" :disabled="dialog.mode === 'view'">上传新盲注</el-button>
+            <el-button type="primary" @click="handleViewLevels" :disabled="dialog.mode === 'view'">预览</el-button>
           </div>
         </el-form-item>
 
-        <!-- 报名截止等级 itemOptionsStructuresLevel-->
+        <!-- 报名截止等级 -->
         <el-form-item label="报名截止等级">
-          <el-select v-model="form.lateRegistrationLevel" placeholder="选项" style="width: 200px">
+          <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>
@@ -215,22 +232,23 @@
             <span>第{{ reward.ranking }}名</span>
 
             <!-- 道具选择 -->
-            <el-select v-model="reward.itemId" placeholder="选项" style="width: 120px; margin-right: 8px">
+            <el-select v-model="reward.itemId" placeholder="选项" style="width: 120px; margin-right: 8px" :disabled="dialog.mode === 'view'">
               <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
             </el-select>
 
             <!-- 数量输入 -->
-            <el-input v-model="reward.quantity" placeholder="请输入数量" style="width: 100px; margin-right: 8px"></el-input>
+            <el-input v-model="reward.quantity" placeholder="请输入数量" style="width: 100px; margin-right: 8px" :disabled="dialog.mode === 'view'" />
 
             <!-- 操作按钮 -->
-            <el-button type="primary" @click="addReward" v-if="index === 0">+</el-button>
-            <el-button type="primary" @click="removeReward(index)" v-if="index !== 0">-</el-button>
+            <el-button type="primary" @click="addReward" v-if="index === 0 && dialog.mode !== 'view'">+</el-button>
+            <el-button type="primary" @click="removeReward(index)" v-if="index !== 0 && dialog.mode !== 'view'">-</el-button>
           </div>
         </el-form-item>
       </el-form>
+
       <template #footer>
         <div class="dialog-footer">
-          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm" v-if="dialog.mode !== 'view'">确 定</el-button>
           <el-button @click="cancel">取 消</el-button>
         </div>
       </template>
@@ -318,10 +336,15 @@ const total = ref(0);
 
 const queryFormRef = ref<ElFormInstance>();
 const tournamentsFormRef = ref<ElFormInstance>();
-
-const dialog = reactive<DialogOption>({
+const iconFile = ref<File | null>(null); // 存储选中的图标文件
+const dialog = reactive<{
+  visible: boolean;
+  title: string;
+  mode: 'edit' | 'view'; // 新增字段
+}>({
   visible: false,
-  title: ''
+  title: '',
+  mode: 'edit'
 });
 
 const assignDialog = reactive({
@@ -427,8 +450,8 @@ const formPrize = reactive({
   rewards: [
     {
       ranking: 1,
-      itemId: '',
-      quantity: ''
+      itemId: null,
+      quantity: null
     }
   ]
 });
@@ -439,9 +462,9 @@ const addReward = () => {
   // 判断是否超过 itemOptions 的数量限制
   if (currentLength < itemOptions.value.length) {
     formPrize.rewards.push({
-      ranking: currentLength + 1,  // 自动生成排名,从 1 开始
+      ranking: currentLength + 1, // 自动生成排名,从 1 开始
       itemId: null,
-      quantity: ''
+      quantity: null
     });
   } else {
     ElMessage.warning(`最多只能添加 ${itemOptions.value.length} 个奖励项`);
@@ -538,7 +561,6 @@ const getList = async () => {
 const cancel = () => {
   reset();
   dialog.visible = false;
-  fileList.value = [];
 };
 
 /** 表单重置 */
@@ -574,13 +596,34 @@ const handleAdd = () => {
 };
 
 /** 修改按钮操作 */
-const handleUpdate = async (row?: TournamentsVO) => {
-  reset();
+const handleUpdate = async (row?: TournamentsVO, mode: 'edit' | 'view' = 'edit') => {
+  reset(); // 重置表单
   const _id = row?.id || ids.value[0];
   const res = await getTournaments(_id);
+
   Object.assign(form.value, res.data);
+
+  // 处理奖励表单数据
+  const prizeItems = res.data.itemsPrizeList || [];
+  if (prizeItems.length > 0) {
+    formPrize.rewards = prizeItems.map((item, index) => ({
+      ranking: item.ranking || index + 1,
+      itemId: Number(item.itemId),
+      quantity: Number(item.quantity)
+    }));
+  } else {
+    formPrize.rewards = [
+      {
+        ranking: 1,
+        itemId: null,
+        quantity: null
+      }
+    ];
+  }
+
   dialog.visible = true;
-  dialog.title = '编辑比赛';
+  dialog.title = mode === 'view' ? '查看比赛' : '编辑比赛';
+  dialog.mode = mode; // 设置模式
 };
 
 /** 提交按钮 */
@@ -593,23 +636,39 @@ const submitForm = () => {
     }
 
     if (!valid) return;
+
     try {
       buttonLoading.value = true;
 
-      // 构造最终要提交的数据对象
-      const formData: TournamentsForm = {
-        ...data.form,
-        competitionIcon: data.form.competitionIcon, // 确保 iconUrl 存在
-        itemsPrizeList: formPrize.rewards.map((reward) => ({
-          ranking: Number(reward.ranking),
-          itemId: Number(reward.itemId),
-          quantity: Number(reward.quantity)
+      // 1. 创建 FormData 对象
+      const formData = new FormData();
+
+      // 2. 添加普通表单字段
+      for (const key in data.form) {
+        const value = data.form[key];
+        if (value !== undefined && value !== null) {
+          formData.append(key, String(value));
+        }
+      }
+
+      // 3. 添加奖励列表(itemsPrizeList)
+      const itemsPrizeListJson = JSON.stringify(
+        formPrize.rewards.map((reward) => ({
+          ranking: reward.ranking,
+          itemId: reward.itemId,
+          quantity: reward.quantity
         }))
-      };
+      );
+      formData.append('itemsPrizeList', itemsPrizeListJson);
 
-      // 提交数据(区分新增/编辑)
+      // 4. 添加图标文件(如果存在)
+      if (iconFile.value) {
+        formData.append('competitionIcon', iconFile.value);
+      }
+      debugger;
+      // 5. 判断新增或编辑
       let response;
-      if (formData.id) {
+      if (data.form.id) {
         response = await updateTournaments(formData);
       } else {
         response = await addTournaments(formData);
@@ -619,7 +678,6 @@ const submitForm = () => {
       dialog.visible = false;
       await getList();
     } catch (error) {
-      console.error('提交失败:', error);
       proxy?.$modal.msgError('提交失败,请重试');
     } finally {
       buttonLoading.value = false;
@@ -733,6 +791,14 @@ watch(
   }
 );
 
+const handleIconChange = (file: any) => {
+  if (file.raw) {
+    iconFile.value = file.raw; // 缓存原始文件对象
+  } else {
+    iconFile.value = null;
+  }
+};
+/*
 const fileList = ref([]);
 const handleIconChange = async (file) => {
   const index = fileList.value.findIndex((f) => f.uid === file.uid);
@@ -767,6 +833,7 @@ const handleIconChange = async (file) => {
     ElMessage.error('上传失败,请重试');
   }
 };
+*/
 
 const handleViewLevels = () => {
   const blindStructureId = data.form.blindStructureId;