| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807 |
- <template>
- <div class="p-2">
- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
- <div v-show="showSearch" class="mb-[10px]">
- <el-card shadow="hover">
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
- <el-form-item label="等级" prop="levelNumber">
- <el-input v-model="queryParams.levelNumber" placeholder="请输入等级" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="小盲预置分" prop="smallBlind">
- <el-input v-model="queryParams.smallBlind" placeholder="请输入小盲预置分" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="大盲预置分" prop="bigBlind">
- <el-input v-model="queryParams.bigBlind" placeholder="请输入大盲预置分" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="前置预置分" prop="ante">
- <el-input v-model="queryParams.ante" placeholder="请输入前置预置分" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item label="升级时间" prop="durationMinutes">
- <el-input v-model="queryParams.durationMinutes" placeholder="请输入升级时间" clearable @keyup.enter="handleQuery" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </div>
- </transition>
- <div class="blind-levels-container">
- <el-card shadow="never" class="blind-levels-card">
- <template #header>
- <el-row :gutter="10" class="mb8">
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['business:levels:add']">新增</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['business:levels:edit']">修改</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['business:levels:remove']">删除</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['business:levels:export']">导出</el-button>
- </el-col>
- <el-col :span="1.5">
- <el-button type="primary" plain icon="Upload" @click="handleImport" v-hasPermi="['business:levels:import']">导入</el-button>
- </el-col>
- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
- </el-row>
- </template>
- <el-table v-loading="loading" border :data="levelsList" @selection-change="handleSelectionChange" row-key="id" :row-class-name="tableRowClassName">
- <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
- <el-table-column label="等级" align="center" width="120">
- <template #default="scope">
- <span>{{ scope.row.levelNumber }}</span>
- </template>
- </el-table-column>
- <el-table-column label="小盲预置分" align="center">
- <template #default="scope">
- <div class="stepper-cell">
- <el-button size="small" text @click="stepValue(scope.row, 'smallBlind', -1)">−</el-button>
- <span class="stepper-value">{{ scope.row.smallBlind ?? 0 }}</span>
- <el-button size="small" text @click="stepValue(scope.row, 'smallBlind', 1)">+</el-button>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="大盲预置分" align="center">
- <template #default="scope">
- <div class="stepper-cell">
- <el-button size="small" text @click="stepValue(scope.row, 'bigBlind', -1)">−</el-button>
- <span class="stepper-value">{{ scope.row.bigBlind ?? 0 }}</span>
- <el-button size="small" text @click="stepValue(scope.row, 'bigBlind', 1)">+</el-button>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="前置预置分" align="center">
- <template #default="scope">
- <div class="stepper-cell">
- <el-button size="small" text @click="stepValue(scope.row, 'ante', -1)">−</el-button>
- <span class="stepper-value">{{ scope.row.ante ?? 0 }}</span>
- <el-button size="small" text @click="stepValue(scope.row, 'ante', 1)">+</el-button>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="升级时间" align="center" width="140">
- <template #default="scope">
- <div class="stepper-cell">
- <el-button size="small" text @click="stepValue(scope.row, 'durationMinutes', -1)">−</el-button>
- <span class="stepper-value">{{ scope.row.durationMinutes ?? 0 }}</span>
- <el-button size="small" text @click="stepValue(scope.row, 'durationMinutes', 1)">+</el-button>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="创建时间" align="center" prop="createdAt" width="180">
- <template #default="scope">
- <span>{{ parseTime(scope.row.createdAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="更新时间" align="center" prop="updatedAt" width="180">
- <template #default="scope">
- <span>{{ parseTime(scope.row.updatedAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" width="140">
- <template #default="scope">
- <el-button link type="primary" size="small" :loading="insertLoading" @click="handleInsertAfter(scope.row)">插入</el-button>
- <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
- <div class="blind-levels-footer">
- <el-button type="primary" :loading="saveLoading" @click="handleBatchSave">保存</el-button>
- <el-button @click="handleClose">关闭</el-button>
- </div>
- </el-card>
- </div>
- <!-- 添加或修改盲注等级对话框 -->
- <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
- <el-form ref="levelsFormRef" :model="form" :rules="rules" label-width="80px">
- <el-form-item label="所属盲注" prop="name">
- <el-input v-model="form.name" disabled />
- </el-form-item>
- <el-form-item label="等级" prop="levelNumber">
- <el-input v-model="form.levelNumber" placeholder="请输入等级" />
- </el-form-item>
- <el-form-item label="小盲" prop="smallBlind">
- <el-input v-model="form.smallBlind" placeholder="请输入小盲预置分" />
- </el-form-item>
- <el-form-item label="大盲" prop="bigBlind">
- <el-input v-model="form.bigBlind" placeholder="请输入大盲预置分" />
- </el-form-item>
- <el-form-item label="前置" prop="ante">
- <el-input v-model="form.ante" placeholder="请输入前置预置分" />
- </el-form-item>
- <el-form-item label="升级时间" prop="durationMinutes">
- <el-input v-model="form.durationMinutes" placeholder="请输入升级时间" />
- </el-form-item>
- <el-form-item label="备注" prop="remark">
- <el-input v-model="form.remark" placeholder="请输入备注" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
- <el-button @click="cancel">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 导入对话框 -->
- <el-dialog :title="importDialog.title" v-model="importDialog.visible" width="500px" append-to-body>
- <el-upload
- ref="uploadRef"
- :limit="1"
- accept=".xlsx, .xls"
- :headers="importDialog.headers"
- :action="importDialog.url"
- :disabled="importDialog.isUploading"
- :on-change="handleFileChange"
- :on-progress="handleFileUploadProgress"
- :on-success="handleFileSuccess"
- :auto-upload="false"
- drag
- >
- <el-icon class="el-icon--upload"><upload-filled /></el-icon>
- <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
- <template #tip>
- <div class="el-upload__tip text-center">
- <div class="el-upload__tip">
- <el-checkbox v-model="importDialog.updateSupport" />是否更新已经存在的数据
- </div>
- <span>仅允许导入xls、xlsx格式文件。</span>
- <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
- </div>
- </template>
- </el-upload>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="submitFileForm">确 定</el-button>
- <el-button @click="importDialog.visible = false; uploadFile.value = null; uploadRef.value?.clearFiles()">取 消</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup name="Levels" lang="ts">
- import {
- listLevels,
- getLevels,
- delLevels,
- addLevels,
- updateLevels,
- downloadImportTemplate,
- importData,
- insertAfter,
- batchSave
- } from '@/api/system/business/levels';
- import { LevelsVO, LevelsQuery, LevelsForm } from '@/api/system/business/levels/types';
- import { parseTime } from '@/utils/dateUtils';
- import { useBlindStructureInfo } from '@/api/system/business/levels/useBlindStructureId';
- import { getToken } from '@/utils/auth';
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const levelsList = ref<LevelsVO[]>([]);
- const buttonLoading = ref(false);
- const saveLoading = ref(false);
- const insertLoading = ref(false);
- const loading = ref(true);
- const showSearch = ref(true);
- const ids = ref<Array<string | number>>([]);
- const single = ref(true);
- const multiple = ref(true);
- const total = ref(0);
- const queryFormRef = ref<ElFormInstance>();
- const levelsFormRef = ref<ElFormInstance>();
- const uploadRef = ref<ElUploadInstance>();
- const uploadFile = ref<File | null>(null);
- const rowErrors = ref<Map<string | number, string[]>>(new Map());
- const props = defineProps({
- blindStructureId: {
- type: [Number, String],
- required: false,
- default: null
- },
- name: {
- type: String,
- required: false,
- default: ''
- }
- });
- const { info } = useBlindStructureInfo(props);
- const currentId = ref<number | string | null>(null);
- const currentName = ref('');
- const dialog = reactive<DialogOption>({
- visible: false,
- title: ''
- });
- const importDialog = reactive({
- visible: false,
- title: '导入盲注等级数据',
- isUploading: false,
- updateSupport: false,
- headers: { Authorization: 'Bearer ' + getToken() },
- url: import.meta.env.VITE_APP_BASE_API + '/business/levels/importData'
- });
- const initFormData: LevelsForm = {
- id: undefined,
- name: undefined,
- blindStructureId: undefined,
- levelNumber: undefined,
- smallBlind: undefined,
- bigBlind: undefined,
- ante: undefined,
- durationMinutes: undefined,
- createdAt: undefined,
- updatedAt: undefined,
- createUserId: undefined,
- updateUserId: undefined,
- remark: undefined
- };
- const data = reactive<PageData<LevelsForm, LevelsQuery>>({
- form: { ...initFormData },
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- blindStructureId: undefined,
- levelNumber: undefined,
- smallBlind: undefined,
- bigBlind: undefined,
- ante: undefined,
- durationMinutes: undefined,
- createdAt: undefined,
- updatedAt: undefined,
- createUserId: undefined,
- updateUserId: undefined,
- params: {},
- // 按级别号排序
- orderByColumn: 'levelNumber',
- isAsc: 'asc'
- },
- rules: {
- id: [{ required: true, message: '不能为空', trigger: 'blur' }],
- levelNumber: [{ required: true, message: '级别编号不能为空', trigger: 'blur' }],
- smallBlind: [{ required: true, message: '小盲金额不能为空', trigger: 'blur' }],
- bigBlind: [{ required: true, message: '大盲金额不能为空', trigger: 'blur' }],
- durationMinutes: [{ required: true, message: '持续时间不能为空', trigger: 'blur' }]
- }
- });
- const { queryParams, form, rules } = toRefs(data);
- /** 查询盲注等级列表 */
- const getList = async () => {
- loading.value = true;
- const res = await listLevels(queryParams.value);
- levelsList.value = (res.rows || []).sort((a, b) => (a.levelNumber || 0) - (b.levelNumber || 0));
- total.value = res.total;
- loading.value = false;
- };
- /** 独立排序方法:按级别号排序(用于插入和保存后重新排序) */
- const sortListByLevelNumber = () => {
- levelsList.value.sort((a, b) => (a.levelNumber || 0) - (b.levelNumber || 0));
- };
- /** 计算显示级别:直接返回级别号 */
- const getDisplayLevel = (row: LevelsVO): number => {
- return row.levelNumber || 0;
- };
- /** 取消按钮 */
- const cancel = () => {
- reset();
- dialog.visible = false;
- };
- /** 表单重置 */
- const reset = () => {
- form.value = { ...initFormData };
- levelsFormRef.value?.resetFields();
- };
- /** 搜索按钮操作 */
- const handleQuery = () => {
- queryParams.value.pageNum = 1;
- getList();
- };
- /** 重置按钮操作 */
- const resetQuery = () => {
- queryFormRef.value?.resetFields();
- handleQuery();
- };
- /** 多选框选中数据 */
- const handleSelectionChange = (selection: LevelsVO[]) => {
- ids.value = selection.map((item) => item.id);
- single.value = selection.length != 1;
- multiple.value = !selection.length;
- };
- /** 新增按钮操作 */
- const handleAdd = () => {
- reset();
- dialog.visible = true;
- dialog.title = '添加';
- };
- /** 修改按钮操作 */
- const handleUpdate = async (row?: LevelsVO) => {
- reset();
- const _id = row?.id || ids.value[0];
- const res = await getLevels(_id);
- Object.assign(form.value, res.data);
- dialog.visible = true;
- dialog.title = '修改';
- };
- /** 提交按钮 */
- const submitForm = () => {
- levelsFormRef.value?.validate(async (valid: boolean) => {
- if (valid) {
- buttonLoading.value = true;
- if (form.value.id) {
- await updateLevels(form.value).finally(() => (buttonLoading.value = false));
- } else {
- await addLevels(form.value).finally(() => (buttonLoading.value = false));
- }
- proxy?.$modal.msgSuccess('操作成功');
- dialog.visible = false;
- await getList();
- }
- });
- };
- /** 删除按钮操作 */
- const handleDelete = async (row?: LevelsVO) => {
- // 行内删除:未保存的临时数据,直接移除不弹框
- if (row && (row as any)._isNew) {
- const idx = levelsList.value.findIndex((r) => r.id === row.id);
- if (idx !== -1) {
- levelsList.value.splice(idx, 1);
- }
- proxy?.$modal.msgSuccess('删除成功');
- return;
- }
- // 已保存数据或批量删除:弹确认后调API
- const _ids = row?.id || ids.value;
- await proxy?.$modal.confirm('是否确认删除编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
- await delLevels(_ids);
- proxy?.$modal.msgSuccess('删除成功');
- await getList();
- };
- /** 导出按钮操作 */
- const handleExport = () => {
- proxy?.download(
- 'business/levels/export',
- {
- ...queryParams.value
- },
- `盲注等级详情${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
- );
- };
- /** 导入按钮操作 */
- const handleImport = () => {
- importDialog.visible = true;
- };
- /** 下载模板 */
- const handleDownloadTemplate = async () => {
- try {
- const response = await downloadImportTemplate();
- const blobData = new Blob([response.data || response], {
- type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
- });
- const urlBlob = window.URL.createObjectURL(blobData);
- const link = document.createElement('a');
- link.href = urlBlob;
- link.setAttribute('download', '盲注等级模板.xlsx');
- document.body.appendChild(link);
- link.click();
- setTimeout(() => {
- link.remove();
- window.URL.revokeObjectURL(urlBlob);
- }, 0);
- } catch (error) {
- console.error('Download error:', error);
- ElMessage.error('下载失败,请重试');
- }
- };
- /** 文件上传中处理 */
- const handleFileUploadProgress = () => {
- importDialog.isUploading = true;
- };
- /** 文件上传成功处理 */
- const handleFileSuccess = async (response: any, file: any) => {
- importDialog.isUploading = false;
- uploadRef.value?.clearFiles();
- importDialog.visible = false;
- if (response.code === 200) {
- proxy?.$modal.msgSuccess('导入成功');
- await getList();
- } else {
- proxy?.$modal.msgError(response.msg || '导入失败');
- }
- };
- /** 文件选择变化 */
- const handleFileChange = (file: any) => {
- uploadFile.value = file.raw;
- };
- /** 提交上传文件 */
- const submitFileForm = () => {
- if (!uploadFile.value) {
- proxy?.$modal.msgError('请先选择文件');
- return;
- }
- if (!currentId.value) {
- proxy?.$modal.msgError('盲注结构ID不能为空');
- return;
- }
- importDialog.isUploading = true;
- importData(uploadFile.value, currentId.value)
- .then((response: any) => {
- importDialog.isUploading = false;
- uploadFile.value = null;
- uploadRef.value?.clearFiles();
- importDialog.visible = false;
- if (response.code === 200) {
- proxy?.$modal.msgSuccess('导入成功');
- getList();
- } else {
- proxy?.$modal.msgError(response.msg || '导入失败');
- }
- })
- .catch((error) => {
- importDialog.isUploading = false;
- proxy?.$modal.msgError('导入失败,请重试');
- console.error('Import error:', error);
- });
- };
- /** 行内步进器增减(步长=1) */
- const stepValue = (row: LevelsVO, field: 'smallBlind' | 'bigBlind' | 'ante' | 'durationMinutes', delta: number) => {
- const val = (row[field] as number) || 0;
- (row as any)[field] = Math.max(0, val + delta);
- };
- /** 表格行样式(有错误的行标红) */
- const tableRowClassName = ({ row }: { row: LevelsVO }) => {
- if (rowErrors.value.has(row.id)) {
- return 'row-error';
- }
- return '';
- };
- /** 校验所有行数据 */
- const validateRows = (): string[] => {
- const allErrors: string[] = [];
- rowErrors.value.clear();
- levelsList.value.forEach((row) => {
- const errors: string[] = [];
- if (!row.levelNumber || row.levelNumber <= 0) {
- errors.push(`级别${row.levelNumber || '?'}:级别编号不能为空`);
- }
- if (row.smallBlind === null || row.smallBlind === undefined || row.smallBlind < 0) {
- errors.push(`级别${row.levelNumber || '?'}:小盲金额不能为空`);
- }
- if (row.bigBlind === null || row.bigBlind === undefined || row.bigBlind < 0) {
- errors.push(`级别${row.levelNumber || '?'}:大盲金额不能为空`);
- }
- if (row.bigBlind !== null && row.bigBlind !== undefined && row.smallBlind !== null && row.smallBlind !== undefined && row.bigBlind < row.smallBlind) {
- errors.push(`级别${row.levelNumber || '?'}:大盲金额不能小于小盲金额`);
- }
- if (row.durationMinutes === null || row.durationMinutes === undefined || row.durationMinutes < 0) {
- errors.push(`级别${row.levelNumber || '?'}:本级别持续时间(分钟)不能为空`);
- }
- if (errors.length > 0) {
- rowErrors.value.set(row.id, errors);
- allErrors.push(...errors);
- }
- });
- return allErrors;
- };
- /** 在指定级别后插入(默认全0,本地插入不刷新列表) */
- const handleInsertAfter = async (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);
- // ✅ 关键修复:插入新级别后,将后续所有记录的levelNumber+1,避免编号重复
- levelsList.value.forEach((item) => {
- if (item.levelNumber >= newLevelNumber && item.id !== row.id) {
- item.levelNumber += 1;
- }
- });
- // 本地插入新行到正确位置
- 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);
- sortListByLevelNumber();
- }
- proxy?.$modal.msgSuccess(`成功在第${row.levelNumber}级后插入第${newLevelNumber}级`);
- } catch (error) {
- proxy?.$modal.msgError('插入失败,请重试');
- console.error('Insert after error:', error);
- } finally {
- insertLoading.value = false;
- }
- };
- /** 批量保存(先拉取全部数据,合并当前页编辑后一起提交) */
- const handleBatchSave = async () => {
- if (!levelsList.value || levelsList.value.length === 0) {
- proxy?.$modal.msgError('没有数据可保存');
- return;
- }
- // 校验当前页
- const errors = validateRows();
- if (errors.length > 0) {
- proxy?.$modal.msgError(errors[0]);
- return;
- }
- 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,
- pageNum: 1,
- pageSize: 9999999,
- 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));
- }
- });
- // ✅ 特殊处理:将用户对新插入记录的编辑合并到后端返回的对应记录上
- 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
- });
- }
- }
- });
- // ✅ 保存前按级别号排序,然后重新计算级别:级别号连续递增
- allRows.sort((a, b) => (a.levelNumber || 0) - (b.levelNumber || 0));
- let expectedLevel = 1;
- allRows.forEach((row) => {
- row.levelNumber = expectedLevel++;
- });
- console.log('💾 准备保存的数据:', allRows); // 调试日志
- await batchSave(allRows as LevelsForm[]);
- proxy?.$modal.msgSuccess('保存成功');
- await getList();
- } catch (error) {
- proxy?.$modal.msgError('保存失败,请重试');
- console.error('Batch save error:', error);
- } finally {
- saveLoading.value = false;
- }
- };
- /** 关闭 */
- const handleClose = () => {
- // 如果在弹窗中使用,关闭弹窗
- const parentDialog = (proxy?.$parent as any)?.$parent;
- if (parentDialog && parentDialog.$options?.name !== 'Levels') {
- // 尝试找到父级 dialog 并关闭
- let el: any = proxy?.$el;
- while (el) {
- if (el.classList && el.classList.contains('el-dialog')) {
- const closeBtn = el.querySelector('.el-dialog__headerbtn');
- if (closeBtn) closeBtn.click();
- break;
- }
- el = el.parentElement;
- }
- }
- };
- onMounted(() => {
- const id = currentId;
- if (id !== undefined) {
- queryParams.value.blindStructureId = currentId.value;
- initFormData.name = currentName.value;
- }
- getList();
- // 将保存/关闭按钮移到父级 dialog 的 footer 区域,使其固定不随滚动
- nextTick(() => {
- const footerEl = (proxy?.$el as HTMLElement)?.querySelector('.blind-levels-footer') as HTMLElement;
- if (!footerEl) return;
- let dialogEl: HTMLElement | null = footerEl.closest('.el-dialog');
- if (!dialogEl) {
- let el: HTMLElement | null = footerEl.parentElement;
- while (el) {
- if (el.classList?.contains('el-dialog')) {
- dialogEl = el;
- break;
- }
- el = el.parentElement;
- }
- }
- if (dialogEl) {
- let dialogFooter = dialogEl.querySelector('.el-dialog__footer') as HTMLElement;
- if (!dialogFooter) {
- dialogFooter = document.createElement('div');
- dialogFooter.className = 'el-dialog__footer';
- dialogEl.appendChild(dialogFooter);
- }
- // 清空旧的 footer 内容,避免重复
- dialogFooter.innerHTML = '';
- dialogFooter.appendChild(footerEl);
- footerEl.style.position = '';
- footerEl.style.bottom = '';
- footerEl.style.background = '';
- footerEl.style.borderRadius = '';
- footerEl.style.boxShadow = '';
- footerEl.style.zIndex = '';
- footerEl.style.padding = '10px 0 0';
- }
- });
- });
- watch(
- () => info(),
- (newInfo) => {
- if (newInfo.id !== currentId.value || newInfo.name !== currentName.value) {
- currentId.value = newInfo.id;
- currentName.value = newInfo.name;
- initFormData.blindStructureId = newInfo.id;
- initFormData.name = newInfo.name;
- queryParams.value.blindStructureId = newInfo.id;
- if (newInfo.id !== null) {
- getList();
- }
- }
- },
- { immediate: true }
- );
- // ✅ 暴露刷新方法给父组件调用
- defineExpose({
- refreshList: getList
- });
- </script>
- <style scoped>
- .blind-levels-container {
- height: 100%;
- }
- .blind-levels-card {
- height: 100%;
- overflow: auto;
- }
- .blind-levels-footer {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- padding: 10px 0 0;
- }
- .stepper-cell {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 4px;
- }
- .stepper-cell .el-button {
- padding: 2px 8px;
- font-size: 14px;
- min-width: 28px;
- color: #606266;
- }
- .stepper-cell .el-button:hover:not(:disabled) {
- color: #409eff;
- }
- .stepper-cell .el-button:disabled {
- color: #c0c4cc;
- cursor: not-allowed;
- }
- .stepper-value {
- display: inline-block;
- min-width: 60px;
- text-align: center;
- padding: 4px 12px;
- background: #f5f7fa;
- border-radius: 4px;
- font-size: 14px;
- color: #606266;
- }
- :deep(.row-error) {
- background-color: #fef0f0 !important;
- }
- :deep(.row-error td) {
- color: #f56c6c;
- }
- </style>
|