|
@@ -58,25 +58,47 @@
|
|
|
</el-col>
|
|
|
<!-- 新增的操作按钮,基于默认赛事 -->
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="warning" plain icon="Download" @click="handleDownloadTemplateDefault" v-hasPermi="['system:gameEvent:download']">下载模板</el-button>
|
|
|
+ <el-button type="warning" plain icon="Download" @click="handleDownloadTemplateDefault" v-hasPermi="['system:gameEvent:download']"
|
|
|
+ >下载模板
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="info" plain icon="FolderOpened" @click="handleImportRegistrationDefault" v-hasPermi="['system:gameEvent:import']">导入报名</el-button>
|
|
|
+ <el-button type="info" plain icon="FolderOpened" @click="handleImportRegistrationDefault" v-hasPermi="['system:gameEvent:import']"
|
|
|
+ >导入报名
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="success" plain icon="User" @click="handleAddParticipantDefault" v-hasPermi="['system:gameEvent:addParticipant']">参赛者</el-button>
|
|
|
+ <el-button type="success" plain icon="User" @click="handleAddParticipantDefault" v-hasPermi="['system:gameEvent:addParticipant']"
|
|
|
+ >参赛者
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="primary" plain icon="Avatar" @click="handleAddRefereeDefault" v-hasPermi="['system:gameEvent:addReferee']">裁判</el-button>
|
|
|
+ <el-button type="primary" plain icon="Avatar" @click="handleAddRefereeDefault" v-hasPermi="['system:gameEvent:addReferee']"
|
|
|
+ >裁判
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="info" plain icon="View" @click="handlePreviewDefault" v-hasPermi="['system:gameEvent:view']">预览</el-button>
|
|
|
+ <el-button type="info" plain icon="View" @click="handlePreviewDefault" v-hasPermi="['system:gameEvent:view']">预览 </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="warning" plain icon="DataAnalysis" @click="handleGameDataDefault" v-hasPermi="['system:gameEvent:gameData']">排行榜</el-button>
|
|
|
+ <el-button type="warning" plain icon="DataAnalysis" @click="handleGameDataDefault" v-hasPermi="['system:gameEvent:gameData']"
|
|
|
+ >排行榜
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="primary" plain icon="EditPen" @click="handleWriteArticleDefault" v-hasPermi="['system:gameEvent:writeArticle']">编写文章</el-button>
|
|
|
+ <el-button type="primary" plain icon="EditPen" @click="handleWriteArticleDefault" v-hasPermi="['system:gameEvent:writeArticle']"
|
|
|
+ >编写文章
|
|
|
+ </el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="primary" plain icon="Download" @click="handleExportNumberTableDefault" v-hasPermi="['system:gameEvent:numberExport']"
|
|
|
+ >导出号码对照表
|
|
|
+ </el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="success" plain icon="Postcard" @click="handleGenerateBib" v-hasPermi="['system:gameEvent:numberBib']"
|
|
|
+ >生成参赛证
|
|
|
+ </el-button>
|
|
|
</el-col>
|
|
|
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList"></right-toolbar>
|
|
|
</el-row>
|
|
@@ -85,14 +107,12 @@
|
|
|
<el-table v-loading="loading" border :data="gameEventList" @selection-change="handleSelectionChange">
|
|
|
<!-- 第一列:多选列 -->
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
-
|
|
|
+
|
|
|
<!-- 第二列:操作列 -->
|
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
|
|
|
<template #default="scope">
|
|
|
<el-tooltip content="修改" placement="top">
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:gameEvent:edit']">
|
|
|
- 修改
|
|
|
- </el-button>
|
|
|
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:gameEvent:edit']"> 修改 </el-button>
|
|
|
</el-tooltip>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
@@ -322,16 +342,153 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 生成参赛证对话框 -->
|
|
|
+ <el-dialog v-model="bibDialog.visible" title="生成参赛证" width="800px" append-to-body @close="handleCloseBibDialog">
|
|
|
+ <div class="bib-generator">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <!-- 左侧配置面板 -->
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form :model="bibForm" label-width="100px">
|
|
|
+ <el-form-item label="背景图片">
|
|
|
+ <el-upload ref="bgUploadRef" :limit="1" :auto-upload="false" :on-change="handleBgImageChange" accept="image/*" drag>
|
|
|
+ <el-icon class="el-icon--upload">
|
|
|
+ <i-ep-upload-filled />
|
|
|
+ </el-icon>
|
|
|
+ <div class="el-upload__text">拖拽背景图片到此处,或<em>点击上传</em></div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="Logo图片">
|
|
|
+ <el-upload ref="logoUploadRef" :limit="1" :auto-upload="false" :on-change="handleLogoImageChange" accept="image/*" drag>
|
|
|
+ <el-icon class="el-icon--upload">
|
|
|
+ <i-ep-upload-filled />
|
|
|
+ </el-icon>
|
|
|
+ <div class="el-upload__text">拖拽Logo图片到此处,或<em>点击上传</em></div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="字体设置">
|
|
|
+ <div style="display: flex; gap: 15px; align-items: center;">
|
|
|
+ <el-select v-model="bibForm.fontName" placeholder="字体" style="width: 100px;">
|
|
|
+ <el-option label="黑体" value="simhei"></el-option>
|
|
|
+ <el-option label="宋体" value="simsun"></el-option>
|
|
|
+ <el-option label="微软雅黑" value="microsoft-yahei"></el-option>
|
|
|
+ </el-select>
|
|
|
+ <el-input-number v-model="bibForm.fontSize" :min="38" :max="198" placeholder="字体大小" style="width: 140px;"></el-input-number>
|
|
|
+ <el-color-picker v-model="bibForm.fontColor" @change="handleFontColorChange"></el-color-picker>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <!-- 右侧预览面板 -->
|
|
|
+ <el-col :span="12">
|
|
|
+ <div class="preview-container" ref="previewContainer">
|
|
|
+ <div class="preview-canvas" :style="{ backgroundImage: bgImageUrl ? `url(${bgImageUrl})` : 'none' }">
|
|
|
+ <!-- Logo元素 -->
|
|
|
+ <div
|
|
|
+ v-if="logoImageUrl"
|
|
|
+ class="draggable-element logo-element"
|
|
|
+ :style="{
|
|
|
+ left: bibForm.logoX + 'px',
|
|
|
+ top: bibForm.logoY + 'px'
|
|
|
+ }"
|
|
|
+ @mousedown="startDrag($event, 'logo')"
|
|
|
+ >
|
|
|
+ <img :src="logoImageUrl" alt="Logo" style="max-width: 80px; max-height: 80px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 示例条形码 -->
|
|
|
+ <div
|
|
|
+ class="draggable-element barcode-element"
|
|
|
+ :style="{
|
|
|
+ left: bibForm.qRCodeX + 'px',
|
|
|
+ top: bibForm.qRCodeY + 'px'
|
|
|
+ }"
|
|
|
+ @mousedown="startDrag($event, 'barcode')"
|
|
|
+ >
|
|
|
+ <svg width="100" height="30" viewBox="0 0 100 30">
|
|
|
+ <rect x="0" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="4" y="0" width="1" height="30" fill="black" />
|
|
|
+ <rect x="7" y="0" width="3" height="30" fill="black" />
|
|
|
+ <rect x="12" y="0" width="1" height="30" fill="black" />
|
|
|
+ <rect x="15" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="19" y="0" width="1" height="30" fill="black" />
|
|
|
+ <rect x="22" y="0" width="3" height="30" fill="black" />
|
|
|
+ <rect x="27" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="31" y="0" width="1" height="30" fill="black" />
|
|
|
+ <rect x="34" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="37" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="42" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="46" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="49" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="50" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="52" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="54" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="58" y="0" width="2" height="30" fill="black" />
|
|
|
+ <rect x="60" y="0" width="2" height="30" fill="black" />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 赛事名称预览 -->
|
|
|
+ <div
|
|
|
+ class="event-name-preview"
|
|
|
+ :style="{
|
|
|
+ fontSize: Math.round(bibForm.fontSize * 0.8) + 'px',
|
|
|
+ color: bibForm.fontColorHex,
|
|
|
+ fontFamily: bibForm.fontName
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ 赛事名称
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 示例数字 1234 -->
|
|
|
+ <div
|
|
|
+ class="draggable-element number-element"
|
|
|
+ :style="{
|
|
|
+ left: '50%',
|
|
|
+ top: '50%',
|
|
|
+ transform: 'translate(-50%, -50%)',
|
|
|
+ fontSize: bibForm.fontSize + 'px',
|
|
|
+ color: bibForm.fontColorHex,
|
|
|
+ fontFamily: bibForm.fontName
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ 1234
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="handleCloseBibDialog">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="handleGenerateBibFile" :loading="bibDialog.loading">生成参赛证</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="GameEvent" lang="ts">
|
|
|
-import { listGameEvent, changeEventDefault, delGameEvent, addGameEvent, updateGameEvent } from '@/api/system/gameEvent';
|
|
|
+import {
|
|
|
+ listGameEvent,
|
|
|
+ changeEventDefault,
|
|
|
+ delGameEvent,
|
|
|
+ addGameEvent,
|
|
|
+ updateGameEvent,
|
|
|
+ generateNumberTable,
|
|
|
+ generateBib,
|
|
|
+ type GenerateBibBo
|
|
|
+} from '@/api/system/gameEvent';
|
|
|
import { GameEventVO, GameEventQuery, GameEventForm } from '@/api/system/gameEvent/types';
|
|
|
import { getEventMdByEventAndType, editEventMd } from '@/api/system/eventMd';
|
|
|
import { EventMdVO, EventMdForm } from '@/api/system/eventMd/types';
|
|
|
import { useRouter } from 'vue-router';
|
|
|
-import { ref } from 'vue';
|
|
|
+import { ref, nextTick } from 'vue';
|
|
|
import RefereeForm from '@/views/system/gameEvent/RefereeForm.vue';
|
|
|
import RankingBoard from './RankingBoard.vue';
|
|
|
import Editor from '@/components/Editor/index.vue';
|
|
@@ -666,9 +823,6 @@ const handleGameData = (row: GameEventVO) => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const rankingBoardVisible = ref(false);
|
|
|
-const currentEventId = ref<string | undefined>();
|
|
|
-
|
|
|
// 文章编写相关数据
|
|
|
const articleDialog = reactive({
|
|
|
visible: false,
|
|
@@ -764,7 +918,6 @@ const loadTabData = async (tabName: string) => {
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.log('查询文章数据失败:', error);
|
|
|
const dataKey = getDataKeyByTabName(tabName);
|
|
|
if (dataKey && articleData[dataKey]) {
|
|
|
articleData[dataKey].id = undefined;
|
|
@@ -930,10 +1083,347 @@ const handleWriteArticleDefault = async () => {
|
|
|
handleWriteArticle(defaultEvent);
|
|
|
};
|
|
|
|
|
|
+const handleExportNumberTableDefault = async () => {
|
|
|
+ await proxy?.download('system/number/export', {}, `号码对照表_${new Date().getTime()}.xlsx`);
|
|
|
+};
|
|
|
+
|
|
|
+// 生成参赛证相关
|
|
|
+const bibDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ loading: false
|
|
|
+});
|
|
|
+
|
|
|
+const bibForm = reactive({
|
|
|
+ logoX: 50,
|
|
|
+ logoY: 50,
|
|
|
+ qRCodeX: 100,
|
|
|
+ qRCodeY: 200,
|
|
|
+ fontName: 'simhei',
|
|
|
+ fontSize: 36,
|
|
|
+ fontColor: '#000000',
|
|
|
+ fontColorHex: '#000000'
|
|
|
+});
|
|
|
+
|
|
|
+const bgImageFile = ref<File | null>(null);
|
|
|
+const logoImageFile = ref<File | null>(null);
|
|
|
+const bgImageUrl = ref<string>('');
|
|
|
+const logoImageUrl = ref<string>('');
|
|
|
+const previewContainer = ref<HTMLElement>();
|
|
|
+const bgImageDimensions = ref<{ width: number; height: number } | null>(null);
|
|
|
+const bgUploadRef = ref<ElUploadInstance>();
|
|
|
+const logoUploadRef = ref<ElUploadInstance>();
|
|
|
+
|
|
|
+// 拖拽相关
|
|
|
+const dragState = reactive({
|
|
|
+ isDragging: false,
|
|
|
+ dragTarget: '',
|
|
|
+ startX: 0,
|
|
|
+ startY: 0,
|
|
|
+ startLeft: 0,
|
|
|
+ startTop: 0
|
|
|
+});
|
|
|
+
|
|
|
+// 生成参赛证按钮处理
|
|
|
+const handleGenerateBib = () => {
|
|
|
+
|
|
|
+ // 强制设置默认值,不使用条件判断(像素单位)
|
|
|
+ bibForm.logoX = bibForm.logoX || 50;
|
|
|
+ bibForm.logoY = bibForm.logoY || 50;
|
|
|
+ bibForm.qRCodeX = bibForm.qRCodeX || 100; // 修正:使用独立的默认值
|
|
|
+ bibForm.qRCodeY = bibForm.qRCodeY || 200; // 修正:使用独立的默认值
|
|
|
+ bibForm.fontName = bibForm.fontName || 'simhei';
|
|
|
+ bibForm.fontSize = bibForm.fontSize || 36;
|
|
|
+ bibForm.fontColor = bibForm.fontColor || '#000000';
|
|
|
+ bibForm.fontColorHex = bibForm.fontColorHex || '#000000';
|
|
|
+ bibDialog.visible = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 关闭对话框
|
|
|
+const handleCloseBibDialog = () => {
|
|
|
+ bibDialog.visible = false;
|
|
|
+ resetBibForm();
|
|
|
+
|
|
|
+ // 清除上传的文件
|
|
|
+ if (bgUploadRef.value) {
|
|
|
+ bgUploadRef.value.clearFiles();
|
|
|
+ }
|
|
|
+ if (logoUploadRef.value) {
|
|
|
+ logoUploadRef.value.clearFiles();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 重置表单
|
|
|
+const resetBibForm = () => {
|
|
|
+
|
|
|
+ // 设置默认值(像素单位)
|
|
|
+ bibForm.logoX = 50;
|
|
|
+ bibForm.logoY = 50;
|
|
|
+ bibForm.qRCodeX = 100;
|
|
|
+ bibForm.qRCodeY = 200;
|
|
|
+ bibForm.fontName = 'simhei';
|
|
|
+ bibForm.fontSize = 36;
|
|
|
+ bibForm.fontColor = '#000000';
|
|
|
+ bibForm.fontColorHex = '#000000';
|
|
|
+ bgImageFile.value = null;
|
|
|
+ logoImageFile.value = null;
|
|
|
+ bgImageUrl.value = '';
|
|
|
+ logoImageUrl.value = '';
|
|
|
+
|
|
|
+ // 清除上传的文件
|
|
|
+ if (bgUploadRef.value) {
|
|
|
+ bgUploadRef.value.clearFiles();
|
|
|
+ }
|
|
|
+ if (logoUploadRef.value) {
|
|
|
+ logoUploadRef.value.clearFiles();
|
|
|
+ }
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+// 背景图片改变处理
|
|
|
+const handleBgImageChange = async (file: any) => {
|
|
|
+ if (file.raw) {
|
|
|
+ bgImageFile.value = file.raw;
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e) => {
|
|
|
+ bgImageUrl.value = e.target?.result as string;
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file.raw);
|
|
|
+
|
|
|
+ // 获取背景图片的实际尺寸
|
|
|
+ try {
|
|
|
+ const dimensions = await getImageDimensions(file.raw);
|
|
|
+ bgImageDimensions.value = dimensions;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取图片尺寸失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Logo图片改变处理
|
|
|
+const handleLogoImageChange = (file: any) => {
|
|
|
+ if (file.raw) {
|
|
|
+ logoImageFile.value = file.raw;
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.onload = (e) => {
|
|
|
+ logoImageUrl.value = e.target?.result as string;
|
|
|
+ };
|
|
|
+ reader.readAsDataURL(file.raw);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 字体颜色改变处理
|
|
|
+const handleFontColorChange = (color: string) => {
|
|
|
+ bibForm.fontColor = color;
|
|
|
+ bibForm.fontColorHex = color;
|
|
|
+};
|
|
|
+
|
|
|
+// 开始拖拽
|
|
|
+const startDrag = (event: MouseEvent, target: string) => {
|
|
|
+ event.preventDefault();
|
|
|
+ dragState.isDragging = true;
|
|
|
+ dragState.dragTarget = target;
|
|
|
+ dragState.startX = event.clientX;
|
|
|
+ dragState.startY = event.clientY;
|
|
|
+
|
|
|
+ if (target === 'logo') {
|
|
|
+ dragState.startLeft = bibForm.logoX;
|
|
|
+ dragState.startTop = bibForm.logoY;
|
|
|
+ } else if (target === 'barcode') {
|
|
|
+ dragState.startLeft = bibForm.qRCodeX;
|
|
|
+ dragState.startTop = bibForm.qRCodeY;
|
|
|
+ }
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', handleDrag);
|
|
|
+ document.addEventListener('mouseup', stopDrag);
|
|
|
+};
|
|
|
+
|
|
|
+// 处理拖拽
|
|
|
+const handleDrag = (event: MouseEvent) => {
|
|
|
+ if (!dragState.isDragging) return;
|
|
|
+
|
|
|
+ const deltaX = event.clientX - dragState.startX;
|
|
|
+ const deltaY = event.clientY - dragState.startY;
|
|
|
+
|
|
|
+ if (dragState.dragTarget === 'logo') {
|
|
|
+ bibForm.logoX = Math.max(0, dragState.startLeft + deltaX);
|
|
|
+ bibForm.logoY = Math.max(0, dragState.startTop + deltaY);
|
|
|
+ } else if (dragState.dragTarget === 'barcode') {
|
|
|
+ bibForm.qRCodeX = Math.max(0, dragState.startLeft + deltaX);
|
|
|
+ bibForm.qRCodeY = Math.max(0, dragState.startTop + deltaY);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 停止拖拽
|
|
|
+const stopDrag = () => {
|
|
|
+ dragState.isDragging = false;
|
|
|
+ dragState.dragTarget = '';
|
|
|
+ document.removeEventListener('mousemove', handleDrag);
|
|
|
+ document.removeEventListener('mouseup', stopDrag);
|
|
|
+};
|
|
|
+
|
|
|
+// 坐标转换函数:根据实际背景图片尺寸调整坐标
|
|
|
+const convertCoordinatesWithScale = (x: number, y: number): { x: number; y: number } => {
|
|
|
+ // 获取预览容器尺寸,使用更精确的方法
|
|
|
+ const container = previewContainer.value;
|
|
|
+ const previewWidth = container?.clientWidth || container?.offsetWidth || 400;
|
|
|
+ const previewHeight = container?.clientHeight || container?.offsetHeight || 400;
|
|
|
+
|
|
|
+ // 使用实际背景图片尺寸,如果没有则使用默认A4尺寸
|
|
|
+ const actualWidth = bgImageDimensions.value?.width || 595;
|
|
|
+ const actualHeight = bgImageDimensions.value?.height || 842;
|
|
|
+
|
|
|
+ // 计算实际比例
|
|
|
+ const scaleX = actualWidth / previewWidth;
|
|
|
+ const scaleY = actualHeight / previewHeight;
|
|
|
+
|
|
|
+ // 微调系数,用于补偿微小偏差
|
|
|
+ const fineTuneX = 1.15; // 进一步缩小X坐标,让元素向左移动
|
|
|
+ const fineTuneY = 0.9; // 进一步放大Y坐标,让元素向下移动
|
|
|
+
|
|
|
+ // 根据实际效果图,需要调整坐标系统
|
|
|
+ // 实际效果显示Logo在左上角,二维码在左下角
|
|
|
+ const adjustedX = x * scaleX * fineTuneX;
|
|
|
+ const adjustedY = (previewHeight - y) * scaleY * fineTuneY;
|
|
|
+
|
|
|
+ // 添加额外的偏移量调整
|
|
|
+ const offsetX = 8; // 向右偏移8pt(进一步减少向右偏移)
|
|
|
+ const offsetY = 65; // 向下偏移65pt(进一步增加向下偏移)
|
|
|
+
|
|
|
+ // 根据元素类型进行特殊调整
|
|
|
+ let finalX = adjustedX + offsetX;
|
|
|
+ let finalY = adjustedY + offsetY;
|
|
|
+
|
|
|
+ // 如果是Logo,进行特殊调整
|
|
|
+ if (x < 100 && y < 100) {
|
|
|
+ // 假设Logo在左上角区域
|
|
|
+ finalX += 5; // Logo额外向右偏移
|
|
|
+ finalY -= 10; // Logo额外向下偏移
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是二维码,进行特殊调整
|
|
|
+ if (x > 200 && y > 200) {
|
|
|
+ // 假设二维码在右下角区域
|
|
|
+ finalX -= 3; // 二维码额外向左偏移
|
|
|
+ finalY += 5; // 二维码额外向下偏移
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: finalX,
|
|
|
+ y: finalY
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+// 新增:获取背景图片实际尺寸的函数
|
|
|
+const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const img = new Image();
|
|
|
+ img.onload = () => {
|
|
|
+ resolve({ width: img.width, height: img.height });
|
|
|
+ };
|
|
|
+ img.src = URL.createObjectURL(file);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 生成参赛证文件
|
|
|
+const handleGenerateBibFile = async () => {
|
|
|
+ if (!bgImageFile.value) {
|
|
|
+ proxy?.$modal.msgError('请上传背景图片');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ bibDialog.loading = true;
|
|
|
+ try {
|
|
|
+ let qRCodeX = bibForm.qRCodeX;
|
|
|
+ let qRCodeY = bibForm.qRCodeY;
|
|
|
+
|
|
|
+ // 如果值为null或undefined,强制使用默认值
|
|
|
+ if (qRCodeX === null || qRCodeX === undefined || isNaN(qRCodeX)) {
|
|
|
+ qRCodeX = 100;
|
|
|
+ console.warn('qRCodeX值异常,使用默认值100px');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (qRCodeY === null || qRCodeY === undefined || isNaN(qRCodeY)) {
|
|
|
+ qRCodeY = 200;
|
|
|
+ console.warn('qRCodeY值异常,使用默认值200px');
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 获取预览容器的高度用于坐标转换
|
|
|
+ const containerHeight = previewContainer.value?.clientHeight || 400;
|
|
|
+
|
|
|
+ // 检查背景图片尺寸
|
|
|
+ if (!bgImageDimensions.value) {
|
|
|
+ proxy?.$modal.msgWarning('正在获取背景图片尺寸,请稍后再试');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待一帧确保所有尺寸都已计算完成
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ // Logo坐标(左上角)
|
|
|
+ const logoCoords = convertCoordinatesWithScale(bibForm.logoX || 50, bibForm.logoY || 50);
|
|
|
+
|
|
|
+ // 二维码坐标(左上角)
|
|
|
+ const qrCoords = convertCoordinatesWithScale(qRCodeX, qRCodeY);
|
|
|
+
|
|
|
+ const bibParams = {
|
|
|
+ logoX: logoCoords.x,
|
|
|
+ logoY: logoCoords.y,
|
|
|
+ qRCodeX: qrCoords.x,
|
|
|
+ qRCodeY: qrCoords.y,
|
|
|
+ fontName: bibForm.fontName || 'simhei',
|
|
|
+ fontSize: Math.round((bibForm.fontSize || 36) * 0.75), // 字体大小转换为PDF点并四舍五入为整数
|
|
|
+ fontColor: parseInt((bibForm.fontColor || '#000000').replace('#', ''), 16)
|
|
|
+ };
|
|
|
+
|
|
|
+ // 最后一次检查,确保二维码坐标不为null
|
|
|
+ if (bibParams.qRCodeX === null || bibParams.qRCodeY === undefined) {
|
|
|
+ bibParams.qRCodeX = 148.75; // 100px * 1.4875
|
|
|
+ console.error('最后一次修复:qRCodeX仍为null,设置为100px转换后的值');
|
|
|
+ }
|
|
|
+ if (bibParams.qRCodeY === null || bibParams.qRCodeY === undefined) {
|
|
|
+ bibParams.qRCodeY = 421; // 200px * 2.105
|
|
|
+ console.error('最后一次修复:qRCodeY仍为null,设置为200px转换后的值');
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await generateBib(bgImageFile.value, logoImageFile.value, bibParams);
|
|
|
+
|
|
|
+ // 处理文件下载 - response已经是blob数据,不需要再访问.data属性
|
|
|
+ const blob = new Blob([response as any], { type: 'application/zip' });
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
+ const link = document.createElement('a');
|
|
|
+ link.href = url;
|
|
|
+ link.download = `参赛证_${new Date().getTime()}.zip`;
|
|
|
+ link.click();
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+
|
|
|
+ proxy?.$modal.msgSuccess('参赛证生成成功');
|
|
|
+ handleCloseBibDialog();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('生成参赛证失败:', error);
|
|
|
+ proxy?.$modal.msgError('生成参赛证失败');
|
|
|
+ } finally {
|
|
|
+ bibDialog.loading = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
// 获取默认赛事信息
|
|
|
gameEventStore.fetchDefaultEvent();
|
|
|
getList();
|
|
|
+ generateNumberTable();
|
|
|
+
|
|
|
+ // 强制确保bibForm的值正确初始化
|
|
|
+
|
|
|
+ // 使用nextTick确保在下一个tick执行
|
|
|
+ nextTick(() => {
|
|
|
+ if (bibForm.qRCodeX === null || bibForm.qRCodeX === undefined) {
|
|
|
+ bibForm.qRCodeX = 100; // 像素单位
|
|
|
+ }
|
|
|
+ if (bibForm.qRCodeY === null || bibForm.qRCodeY === undefined) {
|
|
|
+ bibForm.qRCodeY = 200; // 像素单位
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
// 监听路由变化,当从编辑页返回时检查是否需要刷新列表
|
|
@@ -977,6 +1467,80 @@ onActivated(() => {
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
+/* 生成参赛证样式 */
|
|
|
+.bib-generator {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-container {
|
|
|
+ border: 2px dashed #ddd;
|
|
|
+ border-radius: 8px;
|
|
|
+ min-height: 400px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-canvas {
|
|
|
+ width: 100%;
|
|
|
+ height: 400px;
|
|
|
+ position: relative;
|
|
|
+ background-size: cover;
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.draggable-element {
|
|
|
+ position: absolute;
|
|
|
+ cursor: move;
|
|
|
+ user-select: none;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.draggable-element:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+}
|
|
|
+
|
|
|
+.logo-element {
|
|
|
+ border: 2px dashed transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.logo-element:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.barcode-element {
|
|
|
+ border: 2px dashed transparent;
|
|
|
+ padding: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.barcode-element:hover {
|
|
|
+ border-color: #67c23a;
|
|
|
+ background-color: rgba(103, 194, 58, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.number-element {
|
|
|
+ font-weight: bold;
|
|
|
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
|
+ border: 2px dashed transparent;
|
|
|
+ padding: 5px;
|
|
|
+ white-space: nowrap;
|
|
|
+ user-select: none;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.event-name-preview {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ font-weight: bold;
|
|
|
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
|
+ user-select: none;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 5;
|
|
|
+}
|
|
|
+
|
|
|
.operation-buttons .el-button:hover {
|
|
|
transform: translateY(-1px);
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|