浏览代码

修复盲注表

wengan01 1 天之前
父节点
当前提交
32b1a96503

+ 34 - 57
src/views/system/business/levels/index.vue

@@ -200,7 +200,6 @@ import {
   updateLevels,
   downloadImportTemplate,
   importData,
-  insertAfter,
   batchSave
 } from '@/api/system/business/levels';
 import { LevelsVO, LevelsQuery, LevelsForm } from '@/api/system/business/levels/types';
@@ -549,24 +548,14 @@ const validateRows = (): string[] => {
   return allErrors;
 };
 
-/** 在指定级别后插入(默认全0,本地插入不刷新列表) */
-const handleInsertAfter = async (row: LevelsVO) => {
+/** 在指定级别后插入(纯本地插入,点击"保存"后才提交后端) */
+const handleInsertAfter = (row: LevelsVO) => {
   if (insertLoading.value) return; // 防止重复点击
   insertLoading.value = true;
-  const newLevelNumber = (row.levelNumber || 0) + 1;
-  const newLevel: LevelsForm = {
-    id: undefined,
-    blindStructureId: currentId.value as string | number,
-    levelNumber: newLevelNumber,
-    smallBlind: 0,
-    bigBlind: 0,
-    ante: 0,
-    durationMinutes: 0
-  };
   try {
-    await insertAfter(newLevel, row.levelNumber);
+    const newLevelNumber = (row.levelNumber || 0) + 1;
 
-    // ✅ 关键修复:插入新级别后,将后续所有记录的levelNumber+1,避免编号重复
+    // 将后续所有记录的levelNumber+1,避免编号重复
     levelsList.value.forEach((item) => {
       if (item.levelNumber >= newLevelNumber && item.id !== row.id) {
         item.levelNumber += 1;
@@ -574,16 +563,24 @@ const handleInsertAfter = async (row: LevelsVO) => {
     });
 
     // 本地插入新行到正确位置
+    const newLevel: any = {
+      id: Date.now(), // 临时ID,保存时由后端全量替换生成新ID
+      _isNew: true,
+      blindStructureId: currentId.value as string | number,
+      levelNumber: newLevelNumber,
+      smallBlind: 0,
+      bigBlind: 0,
+      ante: 0,
+      durationMinutes: 0
+    };
     const idx = levelsList.value.findIndex((r) => r.id === row.id);
     if (idx !== -1) {
-      levelsList.value.splice(idx + 1, 0, { ...newLevel, id: Date.now(), _isNew: true } as any);
+      levelsList.value.splice(idx + 1, 0, newLevel);
+      total.value += 1; // 同步总条数(本地插入后分页信息正确)
       sortListByLevelNumber();
     }
 
-    proxy?.$modal.msgSuccess(`成功在第${row.levelNumber}级后插入第${newLevelNumber}级`);
-  } catch (error) {
-    proxy?.$modal.msgError('插入失败,请重试');
-    console.error('Insert after error:', error);
+    proxy?.$modal.msgSuccess(`成功在第${row.levelNumber}级后插入第${newLevelNumber}级(点击保存后生效)`);
   } finally {
     insertLoading.value = false;
   }
@@ -605,18 +602,6 @@ const handleBatchSave = async () => {
 
   saveLoading.value = true;
   try {
-    // ✅ 关键修复:只收集已存在记录的编辑内容(不包括新插入的临时记录)
-    // 原因:新插入的记录已经通过 insertAfter() API 在后端创建了,
-    //       如果再提交会导致重复数据!
-    const localEdits = new Map<string | number, LevelsVO>();
-
-    levelsList.value.forEach((row) => {
-      if (row.id && !(row as any)._isNew) {
-        // 只收集已存在的记录,忽略 _isNew 标记的临时记录
-        localEdits.set(row.id, row);
-      }
-    });
-
     // 拉取全部数据(不分页,强制按级别号排序)
     const allRes = await listLevels({
       ...queryParams.value,
@@ -625,40 +610,32 @@ const handleBatchSave = async () => {
       orderByColumn: 'levelNumber',
       isAsc: 'asc'
     });
-    let allRows: LevelsVO[] = allRes.rows || [];
-
-    // 用本地编辑覆盖全量数据中对应的行(包括对新插入记录的编辑)
-    allRows.forEach((row) => {
-      if (row.id && localEdits.has(row.id)) {
-        Object.assign(row, localEdits.get(row.id));
+    const allRows: any[] = allRes.rows || [];
+
+    // 用本地已存在记录的编辑覆盖全量数据中对应的行
+    levelsList.value.forEach((localRow: any) => {
+      if (localRow.id && !localRow._isNew) {
+        const idx = allRows.findIndex((r) => r.id === localRow.id);
+        if (idx !== -1) {
+          allRows[idx] = { ...allRows[idx], ...localRow };
+        }
       }
     });
 
-    // ✅ 特殊处理:将用户对新插入记录的编辑合并到后端返回的对应记录上
-    levelsList.value.forEach((localRow) => {
-      if ((localRow as any)._isNew && localRow.levelNumber) {
-        // 找到后端数据中相同 levelNumber 的记录(就是刚才 insertAfter 创建的)
-        const backendRow = allRows.find(r => r.levelNumber === localRow.levelNumber);
-        if (backendRow) {
-          // 将用户的编辑内容合并到后端记录上
-          Object.assign(backendRow, {
-            smallBlind: localRow.smallBlind,
-            bigBlind: localRow.bigBlind,
-            ante: localRow.ante,
-            durationMinutes: localRow.durationMinutes
-          });
-        }
+    // 将本地新插入的记录(_isNew)合并进全量数据,随保存一起提交
+    levelsList.value.forEach((localRow: any) => {
+      if (localRow._isNew) {
+        const { _isNew, id, ...rest } = localRow;
+        allRows.push({ ...rest, id: undefined });
       }
     });
 
-    // 保存前按级别号排序,然后重新计算级别:级别号连续递增
+    // 保存前按级别号排序,然后重新计算级别:级别号连续递增
     allRows.sort((a, b) => (a.levelNumber || 0) - (b.levelNumber || 0));
-    let expectedLevel = 1;
-    allRows.forEach((row) => {
-      row.levelNumber = expectedLevel++;
+    allRows.forEach((row, index) => {
+      row.levelNumber = index + 1;
     });
 
-    console.log('💾 准备保存的数据:', allRows); // 调试日志
     await batchSave(allRows as LevelsForm[]);
     proxy?.$modal.msgSuccess('保存成功');
     await getList();

+ 2 - 2
src/views/system/business/structures/index.vue

@@ -140,8 +140,8 @@
       </template>
     </el-dialog>
 
-    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%">
-      <!-- 使用 component 动态加载目标组件 -->
+    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%" destroy-on-close>
+      <!-- 使用 component 动态加载目标组件;destroy-on-close 保证每次打开弹窗都重新挂载并自动刷新数据 -->
       <levels-index ref="levelsIndexRef" :blind-structure-id="dialogParams.blindStructureId" :name="dialogParams.name" />
     </el-dialog>
 

+ 59 - 40
src/views/system/physical/blindLevels/index.vue

@@ -229,7 +229,6 @@ import {
   updateBlindLevels,
   downloadImportTemplate,
   importData,
-  insertAfter,
   batchSave
 } from '@/api/system/physical/blindLevels';
 import { BlindLevelsVO, BlindLevelsQuery, BlindLevelsForm } from '@/api/system/physical/blindLevels/types';
@@ -369,6 +368,8 @@ const applyPagination = () => {
   const pageSize = queryParams.value.pageSize || 10;
   const start = (pageNum - 1) * pageSize;
   blindLevelsList.value = allData.value.slice(start, start + pageSize);
+  // 同步总条数(本地插入/删除后保持分页信息正确)
+  total.value = allData.value.length;
 };
 
 /** 独立排序方法:按展示顺序字段排序(用于插入和保存后重新排序) */
@@ -454,11 +455,12 @@ const submitForm = () => {
 
 /** 删除按钮操作 */
 const handleDelete = async (row?: BlindLevelsVO) => {
-  // 行内删除:未保存的临时数据,直接移除不弹框
+  // 行内删除:未保存的临时数据,直接移除不弹框(需同步从 allData 移除)
   if (row && (row as any)._isNew) {
-    const idx = blindLevelsList.value.findIndex((r) => r.id === row.id);
+    const idx = allData.value.findIndex((r) => r.id === row.id);
     if (idx !== -1) {
-      blindLevelsList.value.splice(idx, 1);
+      allData.value.splice(idx, 1);
+      applyPagination();
     }
     proxy?.$modal.msgSuccess('删除成功');
     return;
@@ -589,24 +591,25 @@ const validateRows = (): string[] => {
 
   blindLevelsList.value.forEach((row) => {
     const errors: string[] = [];
-    // 休息行不占级别(级别为0),跳过级别编号校验
-    if (row.isBreak !== 1 && (!row.levelNumber || row.levelNumber <= 0)) {
-      errors.push(`级别${row.levelNumber || '?'}:级别编号不能为空`);
-    }
+    // 注:不校验级别编号——休息行 levelNumber 固定为 0;将休息行改为正式级别时 levelNumber 仍为 0,
+    // 但保存时(handleBatchSave)会自动按顺序重新编号,因此此处无需校验,避免误拦截。
+    // 错误提示中的行标识:正式级别显示“级别N”,休息行显示“休息(第N级后)”
+    const level = getDisplayLevel(row);
+    const rowLabel = typeof level === 'number' ? `级别${level}` : String(level);
     if (row.smallBlind === null || row.smallBlind === undefined || row.smallBlind < 0) {
-      errors.push(`级别${row.levelNumber || '?'}:小盲金额不能为空`);
+      errors.push(`${rowLabel}:小盲金额不能为空`);
     }
     if (row.bigBlind === null || row.bigBlind === undefined || row.bigBlind < 0) {
-      errors.push(`级别${row.levelNumber || '?'}:大盲金额不能为空`);
+      errors.push(`${rowLabel}:大盲金额不能为空`);
     }
     if (row.bigBlind !== null && row.bigBlind !== undefined && row.smallBlind !== null && row.smallBlind !== undefined && row.bigBlind < row.smallBlind) {
-      errors.push(`级别${row.levelNumber || '?'}:大盲金额不能小于小盲金额`);
+      errors.push(`${rowLabel}:大盲金额不能小于小盲金额`);
     }
     if (row.durationMinutes === null || row.durationMinutes === undefined || row.durationMinutes < 0) {
-      errors.push(`级别${row.levelNumber || '?'}:本级别持续时间(分钟)不能为空`);
+      errors.push(`${rowLabel}:本级别持续时间(分钟)不能为空`);
     }
-    if (row.isBreak === 1 && (!row.breakDurationMinutes || row.breakDurationMinutes <= 0)) {
-      errors.push(`级别${row.levelNumber || '?'}:休息级别为“是”时,休息分钟不能为0`);
+    if (row.isBreak === 1 && (row.breakDurationMinutes === null || row.breakDurationMinutes === undefined || row.breakDurationMinutes < 0)) {
+      errors.push(`${rowLabel}:休息级别为"是"时,休息分钟不能为空且不能小于0`);
     }
     if (errors.length > 0) {
       rowErrors.value.set(row.id, errors);
@@ -617,31 +620,42 @@ const validateRows = (): string[] => {
   return allErrors;
 };
 
-/** 在指定级别后插入(调用API后重新加载全量数据) */
-const handleInsertAfter = async (row: BlindLevelsVO) => {
+/** 在指定级别后插入(纯本地插入,点击"保存"后才提交后端) */
+const handleInsertAfter = (row: BlindLevelsVO) => {
   if (insertLoading.value) return;
   insertLoading.value = true;
-  const newLevelNumber = (row.levelNumber || 0) + 1;
-  const newLevel: BlindLevelsForm = {
-    id: undefined,
-    blindStructureId: currentId.value as string | number,
-    levelNumber: newLevelNumber,
-    smallBlind: 0,
-    bigBlind: 0,
-    ante: 0,
-    durationMinutes: 0,
-    isBreak: 0,
-    breakDurationMinutes: 0,
-    displayOrder: 0
-  };
   try {
-    await insertAfter(newLevel, row.levelNumber);
-    proxy?.$modal.msgSuccess(`成功在第${row.levelNumber}级后插入第${newLevelNumber}级`);
-    // 重新加载全量数据,保证 allData 与后端一致
-    await getList();
-  } catch (error) {
-    proxy?.$modal.msgError('插入失败,请重试');
-    console.error('Insert after error:', error);
+    const idx = allData.value.findIndex((r) => r.id === row.id);
+    if (idx === -1) return;
+
+    const newDisplayOrder = (row.displayOrder || 0) + 1;
+    // 将后续所有记录的 displayOrder+1,避免排序冲突
+    allData.value.forEach((item) => {
+      if ((item.displayOrder || 0) >= newDisplayOrder && item.id !== row.id) {
+        item.displayOrder = (item.displayOrder || 0) + 1;
+      }
+    });
+
+    // 本地插入新行到正确位置(默认非休息的正式级别,保存时后端会重排级别号)
+    const newLevel: any = {
+      id: Date.now(), // 临时ID,保存时由后端全量替换生成新ID
+      _isNew: true,
+      blindStructureId: currentId.value as string | number,
+      levelNumber: row.isBreak === 1 ? 0 : (row.levelNumber || 0) + 1,
+      smallBlind: 0,
+      bigBlind: 0,
+      ante: 0,
+      durationMinutes: 0,
+      isBreak: 0,
+      breakDurationMinutes: 0,
+      displayOrder: newDisplayOrder
+    };
+    allData.value.splice(idx + 1, 0, newLevel);
+
+    // 重新排序并按当前页展示
+    allData.value.sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));
+    applyPagination();
+    proxy?.$modal.msgSuccess('已插入新行(点击保存后生效)');
   } finally {
     insertLoading.value = false;
   }
@@ -664,10 +678,15 @@ const handleBatchSave = async () => {
   saveLoading.value = true;
   try {
     // 直接用全量数据(blindLevelsList 中的行是 allData 的引用,编辑已同步)
-    const allRows = allData.value.map((item: any) => ({
-      ...item,
-      isBreak: item.isBreak === true || item.isBreak === 1 ? 1 : 0
-    }));
+    const allRows = allData.value.map((item: any) => {
+      const { _isNew, ...rest } = item;
+      return {
+        ...rest,
+        // 新插入的临时记录:去掉临时ID,由后端全量替换生成新ID
+        id: _isNew ? undefined : rest.id,
+        isBreak: rest.isBreak === true || rest.isBreak === 1 ? 1 : 0
+      };
+    });
 
     // 保存前按展示顺序排序;自动修正级别:休息行为0不占级别,正式级别连续递增
     allRows.sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0));

+ 2 - 2
src/views/system/physical/blindStructures/index.vue

@@ -150,8 +150,8 @@
       </template>
     </el-dialog>
 
-    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%">
-      <!-- 使用 component 动态加载目标组件 -->
+    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%" destroy-on-close>
+      <!-- 使用 component 动态加载目标组件;destroy-on-close 保证每次打开弹窗都重新挂载并自动刷新数据 -->
       <levels-index ref="levelsIndexRef" :blind-structure-id="dialogParams.blindStructureId" :name="dialogParams.name" />
     </el-dialog>
   </div>

+ 9 - 3
src/views/system/physical/tournaments/index.vue

@@ -474,9 +474,9 @@
         </div>
       </template>
     </el-dialog>
-    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%">
-      <!-- 使用 component 动态加载目标组件 -->
-      <levels-index :blind-structure-id="dialogParams.blindStructureId" :name="dialogParams.name" />
+    <el-dialog v-model="levelsDialogVisible" title="盲注等级列表" width="80%" destroy-on-close>
+      <!-- 使用 component 动态加载目标组件;destroy-on-close 保证每次打开弹窗都重新挂载并自动刷新数据 -->
+      <levels-index ref="levelsIndexRef" :blind-structure-id="dialogParams.blindStructureId" :name="dialogParams.name" />
     </el-dialog>
     <!-- 图片预览弹窗 -->
     <el-dialog v-model="previewDialogVisible" title="图片预览" width="50%">
@@ -782,6 +782,8 @@ const data = reactive<PageData<TournamentsForm, TournamentsQuery>>({
 });
 // 控制 Dialog 是否显示
 const levelsDialogVisible = ref(false);
+// 盲注等级列表子组件引用(打开弹窗时自动刷新数据)
+const levelsIndexRef = ref();
 // 传递给子组件的参数
 const dialogParams = ref({
   blindStructureId: null,
@@ -1194,6 +1196,10 @@ const handleViewLevels = () => {
   dialogParams.value.blindStructureId = blindStructureId;
   /*  dialogParams.value.name = row.name;*/
   levelsDialogVisible.value = true;
+  // 打开弹窗后自动刷新盲注等级表格数据
+  nextTick(() => {
+    levelsIndexRef.value?.refreshList?.();
+  });
 };
 
 const formPrize = reactive({