|
@@ -4,13 +4,9 @@
|
|
|
<div class="bib-generator">
|
|
<div class="bib-generator">
|
|
|
<!-- 预览面板 - 占满宽度 -->
|
|
<!-- 预览面板 - 占满宽度 -->
|
|
|
<div class="preview-container" ref="previewContainer">
|
|
<div class="preview-container" ref="previewContainer">
|
|
|
- <div class="preview-canvas" :style="{
|
|
|
|
|
- backgroundImage: bgImageUrl ? `url(${bgImageUrl})` : 'none',
|
|
|
|
|
- backgroundSize: '100% 100%',
|
|
|
|
|
- backgroundRepeat: 'no-repeat',
|
|
|
|
|
- transform: `scale(${canvasScale})`,
|
|
|
|
|
- transformOrigin: 'center center'
|
|
|
|
|
- }">
|
|
|
|
|
|
|
+ <div class="preview-canvas">
|
|
|
|
|
+ <!-- 背景图片 -->
|
|
|
|
|
+ <img v-if="bgImageUrl" :src="bgImageUrl" class="background-image" />
|
|
|
<!-- Logo元素 -->
|
|
<!-- Logo元素 -->
|
|
|
<div
|
|
<div
|
|
|
v-if="logoImageUrl"
|
|
v-if="logoImageUrl"
|
|
@@ -18,7 +14,7 @@
|
|
|
:style="{
|
|
:style="{
|
|
|
left: bibForm.logoX + '%',
|
|
left: bibForm.logoX + '%',
|
|
|
top: bibForm.logoY + '%',
|
|
top: bibForm.logoY + '%',
|
|
|
- transform: `translateX(-50%) scale(${bibForm.logoScale})`,
|
|
|
|
|
|
|
+ transform: `translateX(-50%) scale(${bibForm.logoScale * canvasScale})`,
|
|
|
transformOrigin: 'top left'
|
|
transformOrigin: 'top left'
|
|
|
}"
|
|
}"
|
|
|
@mousedown="startDrag($event, 'logo')"
|
|
@mousedown="startDrag($event, 'logo')"
|
|
@@ -33,7 +29,7 @@
|
|
|
:style="{
|
|
:style="{
|
|
|
left: (bibForm.qRCodeX || 11.67) + '%',
|
|
left: (bibForm.qRCodeX || 11.67) + '%',
|
|
|
top: (bibForm.qRCodeY || 32.5) + '%',
|
|
top: (bibForm.qRCodeY || 32.5) + '%',
|
|
|
- transform: `translateX(-50%) scale(${bibForm.barcodeScale})`,
|
|
|
|
|
|
|
+ transform: `translateX(-50%) translateY(-50%) scale(${bibForm.barcodeScale * canvasScale})`,
|
|
|
transformOrigin: 'top left',
|
|
transformOrigin: 'top left',
|
|
|
}"
|
|
}"
|
|
|
@mousedown="startDrag($event, 'barcode')"
|
|
@mousedown="startDrag($event, 'barcode')"
|
|
@@ -74,12 +70,12 @@
|
|
|
v-if="bibForm.eventName"
|
|
v-if="bibForm.eventName"
|
|
|
class="event-name-preview draggable-element"
|
|
class="event-name-preview draggable-element"
|
|
|
:style="{
|
|
:style="{
|
|
|
- fontSize: Math.min(28, Math.max(18, bibForm.fontSize * 0.7)) + 'px',
|
|
|
|
|
- color: 'black',
|
|
|
|
|
- fontFamily: '黑体',
|
|
|
|
|
|
|
+ fontSize: Math.min(bibForm.fontSize, 56) + 'px',
|
|
|
|
|
+ color: bibForm.fontColorHex,
|
|
|
|
|
+ fontFamily: bibForm.fontName,
|
|
|
left: (bibForm.eventX || 50) + '%',
|
|
left: (bibForm.eventX || 50) + '%',
|
|
|
top: (bibForm.eventY || 5) + '%',
|
|
top: (bibForm.eventY || 5) + '%',
|
|
|
- transform: `translateX(-50%) scale(${bibForm.eventScale})`,
|
|
|
|
|
|
|
+ transform: `translateX(-50%) scale(${bibForm.eventScale * canvasScale})`,
|
|
|
transformOrigin: 'top center'
|
|
transformOrigin: 'top center'
|
|
|
}"
|
|
}"
|
|
|
@mousedown="startDrag($event, 'event')"
|
|
@mousedown="startDrag($event, 'event')"
|
|
@@ -95,7 +91,7 @@
|
|
|
:style="{
|
|
:style="{
|
|
|
left: (bibForm.numberX || 50) + '%',
|
|
left: (bibForm.numberX || 50) + '%',
|
|
|
top: (bibForm.numberY || 50) + '%',
|
|
top: (bibForm.numberY || 50) + '%',
|
|
|
- transform: `translate(-50%, -50%) scale(${bibForm.numberScale})`,
|
|
|
|
|
|
|
+ transform: `translate(-50%, -50%) scale(${bibForm.numberScale * canvasScale})`,
|
|
|
fontSize: Math.min(bibForm.fontSize, 56) + 'px',
|
|
fontSize: Math.min(bibForm.fontSize, 56) + 'px',
|
|
|
color: bibForm.fontColorHex,
|
|
color: bibForm.fontColorHex,
|
|
|
fontFamily: bibForm.fontName,
|
|
fontFamily: bibForm.fontName,
|
|
@@ -173,7 +169,7 @@
|
|
|
<el-option label="宋体" value="simsun"></el-option>
|
|
<el-option label="宋体" value="simsun"></el-option>
|
|
|
<el-option label="微软雅黑" value="yahei"></el-option>
|
|
<el-option label="微软雅黑" value="yahei"></el-option>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
- <el-input-number v-model="bibForm.fontSize" :min="38" :max="198" placeholder="字体大小" style="width: 140px"></el-input-number>
|
|
|
|
|
|
|
+ <el-input-number v-model="bibForm.fontSize" :min="8" :max="198" placeholder="字体大小" style="width: 140px"></el-input-number>
|
|
|
<el-color-picker v-model="bibForm.fontColor" @change="handleFontColorChange"></el-color-picker>
|
|
<el-color-picker v-model="bibForm.fontColor" @change="handleFontColorChange"></el-color-picker>
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
@@ -185,7 +181,7 @@
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
<el-row :gutter="20">
|
|
<el-row :gutter="20">
|
|
|
- <el-col :span="12">
|
|
|
|
|
|
|
+ <!-- <el-col :span="12">
|
|
|
<el-form-item label="画布比例">
|
|
<el-form-item label="画布比例">
|
|
|
<el-select v-model="canvasScale" placeholder="选择画布比例" style="width: 200px">
|
|
<el-select v-model="canvasScale" placeholder="选择画布比例" style="width: 200px">
|
|
|
<el-option label="1/4 (四分之一)" :value="0.25"></el-option>
|
|
<el-option label="1/4 (四分之一)" :value="0.25"></el-option>
|
|
@@ -195,7 +191,7 @@
|
|
|
<el-option label="1/1 (原始大小)" :value="1"></el-option>
|
|
<el-option label="1/1 (原始大小)" :value="1"></el-option>
|
|
|
</el-select>
|
|
</el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- </el-col>
|
|
|
|
|
|
|
+ </el-col> -->
|
|
|
<el-col :span="12">
|
|
<el-col :span="12">
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
@@ -669,31 +665,20 @@ const handleCreateTask = async () => {
|
|
|
// 等待一帧确保所有尺寸都已计算完成
|
|
// 等待一帧确保所有尺寸都已计算完成
|
|
|
await nextTick();
|
|
await nextTick();
|
|
|
|
|
|
|
|
- // 统一使用百分比坐标系统
|
|
|
|
|
- const logoCoords = {
|
|
|
|
|
- x: bibForm.logoX || 4.17, // 百分比
|
|
|
|
|
- y: bibForm.logoY || 6.25 // 百分比
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ // 计算画布的实际尺寸(不考虑预览缩放)
|
|
|
|
|
+ const originalWidth = bgImageDimensions.value?.width || 600;
|
|
|
|
|
+ const originalHeight = bgImageDimensions.value?.height || 400;
|
|
|
|
|
|
|
|
- // 二维码坐标(百分比)
|
|
|
|
|
- const validQRCodeX = (qRCodeX !== null && qRCodeX !== undefined && !isNaN(qRCodeX)) ? qRCodeX : 11.67;
|
|
|
|
|
- const validQRCodeY = (qRCodeY !== null && qRCodeY !== undefined && !isNaN(qRCodeY)) ? qRCodeY : 32.5;
|
|
|
|
|
-
|
|
|
|
|
- const qrCoords = {
|
|
|
|
|
- x: validQRCodeX, // 百分比
|
|
|
|
|
- y: validQRCodeY // 百分比
|
|
|
|
|
|
|
+ // 将百分比坐标转换为像素坐标
|
|
|
|
|
+ const convertPercentToPixel = (percent: number, dimension: number): number => {
|
|
|
|
|
+ return Math.round((percent / 100) * dimension);
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
- console.log('二维码坐标转换 - 原始坐标:', { qRCodeX, qRCodeY }, '有效坐标:', { validQRCodeX, validQRCodeY }, '转换后坐标:', qrCoords);
|
|
|
|
|
|
|
|
|
|
- // 计算画布比例处理后的宽高
|
|
|
|
|
- const originalWidth = bgImageDimensions.value?.width || 600;
|
|
|
|
|
- const originalHeight = bgImageDimensions.value?.height || 400;
|
|
|
|
|
- const scaledWidth = Math.round(originalWidth * canvasScale.value);
|
|
|
|
|
- const scaledHeight = Math.round(originalHeight * canvasScale.value);
|
|
|
|
|
|
|
+ // Logo像素坐标和尺寸
|
|
|
|
|
+ const logoPixelX = convertPercentToPixel(bibForm.logoX || 4.17, originalWidth);
|
|
|
|
|
+ const logoPixelY = convertPercentToPixel(bibForm.logoY || 6.25, originalHeight);
|
|
|
|
|
|
|
|
// 计算Logo尺寸(前端统一处理,避免后端重复计算)
|
|
// 计算Logo尺寸(前端统一处理,避免后端重复计算)
|
|
|
- // 预览时限制为最大80px,应用用户缩放,计算最终尺寸传递给后端
|
|
|
|
|
const logoOriginalWidth = logoImageDimensions.value?.width || 80;
|
|
const logoOriginalWidth = logoImageDimensions.value?.width || 80;
|
|
|
const logoOriginalHeight = logoImageDimensions.value?.height || 80;
|
|
const logoOriginalHeight = logoImageDimensions.value?.height || 80;
|
|
|
const logoPreviewSize = 80; // 前端预览时的最大尺寸
|
|
const logoPreviewSize = 80; // 前端预览时的最大尺寸
|
|
@@ -701,33 +686,63 @@ const handleCreateTask = async () => {
|
|
|
const logoFinalWidth = Math.round(logoOriginalWidth * logoScale * bibForm.logoScale);
|
|
const logoFinalWidth = Math.round(logoOriginalWidth * logoScale * bibForm.logoScale);
|
|
|
const logoFinalHeight = Math.round(logoOriginalHeight * logoScale * bibForm.logoScale);
|
|
const logoFinalHeight = Math.round(logoOriginalHeight * logoScale * bibForm.logoScale);
|
|
|
|
|
|
|
|
|
|
+ // 二维码像素坐标
|
|
|
|
|
+ const validQRCodeX = (qRCodeX !== null && qRCodeX !== undefined && !isNaN(qRCodeX)) ? qRCodeX : 11.67;
|
|
|
|
|
+ const validQRCodeY = (qRCodeY !== null && qRCodeY !== undefined && !isNaN(qRCodeY)) ? qRCodeY : 32.5;
|
|
|
|
|
+ const qrPixelX = convertPercentToPixel(validQRCodeX, originalWidth);
|
|
|
|
|
+ const qrPixelY = convertPercentToPixel(validQRCodeY, originalHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算二维码尺寸(与前端预览一致:32px * 缩放比例)
|
|
|
|
|
+ const qrInitialSize = 32;
|
|
|
|
|
+ const qrFinalWidth = Math.round(qrInitialSize * bibForm.barcodeScale);
|
|
|
|
|
+ const qrFinalHeight = Math.round(qrInitialSize * bibForm.barcodeScale);
|
|
|
|
|
+
|
|
|
|
|
+ // 号码像素坐标
|
|
|
|
|
+ const numberPixelX = convertPercentToPixel(bibForm.numberX || 50, originalWidth);
|
|
|
|
|
+ const numberPixelY = convertPercentToPixel(bibForm.numberY || 50, originalHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算号码字体大小(应用缩放)
|
|
|
|
|
+ const numberPixelFontSize = Math.round((bibForm.fontSize || 36) * bibForm.numberScale);
|
|
|
|
|
+
|
|
|
|
|
+ // 赛事名称像素坐标
|
|
|
|
|
+ const eventPixelX = convertPercentToPixel(bibForm.eventX || 50, originalWidth);
|
|
|
|
|
+ const eventPixelY = convertPercentToPixel(bibForm.eventY || 5, originalHeight);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算赛事名称字体大小(与前端预览一致:Math.min(28, Math.max(18, fontSize * 0.7)))
|
|
|
|
|
+ const eventPixelFontSize = Math.round((bibForm.fontSize || 36) * bibForm.eventScale);
|
|
|
|
|
+
|
|
|
const bibParams = {
|
|
const bibParams = {
|
|
|
- // Logo坐标(百分比)
|
|
|
|
|
- logoX: logoCoords.x,
|
|
|
|
|
- logoY: logoCoords.y,
|
|
|
|
|
- // 二维码坐标(百分比)
|
|
|
|
|
- qRCodeX: qrCoords.x,
|
|
|
|
|
- qRCodeY: qrCoords.y,
|
|
|
|
|
|
|
+ // 画布实际尺寸
|
|
|
|
|
+ width: originalWidth,
|
|
|
|
|
+ height: originalHeight,
|
|
|
|
|
+
|
|
|
|
|
+ // Logo参数(像素)
|
|
|
|
|
+ logoX: logoPixelX,
|
|
|
|
|
+ logoY: logoPixelY,
|
|
|
|
|
+ logoWidth: logoFinalWidth,
|
|
|
|
|
+ logoHeight: logoFinalHeight,
|
|
|
|
|
+
|
|
|
|
|
+ // 二维码参数(像素)
|
|
|
|
|
+ qRCodeX: qrPixelX,
|
|
|
|
|
+ qRCodeY: qrPixelY,
|
|
|
|
|
+ qRCodeWidth: qrFinalWidth,
|
|
|
|
|
+ qRCodeHeight: qrFinalHeight,
|
|
|
|
|
+
|
|
|
|
|
+ // 号码参数(像素)
|
|
|
|
|
+ numberX: numberPixelX,
|
|
|
|
|
+ numberY: numberPixelY,
|
|
|
|
|
+ numberFontSize: numberPixelFontSize,
|
|
|
|
|
+
|
|
|
|
|
+ // 赛事名称参数(像素)
|
|
|
|
|
+ eventX: eventPixelX,
|
|
|
|
|
+ eventY: eventPixelY,
|
|
|
|
|
+ eventFontSize: eventPixelFontSize,
|
|
|
|
|
+
|
|
|
|
|
+ // 其他通用参数
|
|
|
fontName: bibForm.fontName || 'simhei',
|
|
fontName: bibForm.fontName || 'simhei',
|
|
|
- fontSize: bibForm.fontSize || 36, // 直接传递原始字体大小,后端会应用numberScale
|
|
|
|
|
|
|
+ fontSize: bibForm.fontSize || 36,
|
|
|
fontColor: parseInt((bibForm.fontColor || '#000000').replace('#', ''), 16),
|
|
fontColor: parseInt((bibForm.fontColor || '#000000').replace('#', ''), 16),
|
|
|
eventName: bibForm.eventName || '',
|
|
eventName: bibForm.eventName || '',
|
|
|
- // 位置参数(百分比)
|
|
|
|
|
- numberX: bibForm.numberX,
|
|
|
|
|
- numberY: bibForm.numberY,
|
|
|
|
|
- eventX: bibForm.eventX,
|
|
|
|
|
- eventY: bibForm.eventY,
|
|
|
|
|
- // 缩放参数
|
|
|
|
|
- logoScale: bibForm.logoScale,
|
|
|
|
|
- barcodeScale: bibForm.barcodeScale,
|
|
|
|
|
- numberScale: bibForm.numberScale,
|
|
|
|
|
- eventScale: bibForm.eventScale,
|
|
|
|
|
- // 画布比例处理后的宽高
|
|
|
|
|
- width: scaledWidth,
|
|
|
|
|
- height: scaledHeight,
|
|
|
|
|
- // Logo尺寸
|
|
|
|
|
- logoWidth: logoFinalWidth,
|
|
|
|
|
- logoHeight: logoFinalHeight,
|
|
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
console.log('发送给后端的参数:', bibParams);
|
|
console.log('发送给后端的参数:', bibParams);
|
|
@@ -783,22 +798,23 @@ defineExpose({
|
|
|
/* 生成参赛证样式 */
|
|
/* 生成参赛证样式 */
|
|
|
.bib-generator {
|
|
.bib-generator {
|
|
|
padding: 10px;
|
|
padding: 10px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center; // 让内容居中
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* 预览容器 - 占满宽度 */
|
|
/* 预览容器 - 占满宽度 */
|
|
|
.preview-container {
|
|
.preview-container {
|
|
|
border: 2px dashed #ddd;
|
|
border: 2px dashed #ddd;
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
- min-height: 300px;
|
|
|
|
|
- max-height: 400px;
|
|
|
|
|
|
|
+ width: 600px !important;
|
|
|
|
|
+ height: 400px !important;
|
|
|
position: relative;
|
|
position: relative;
|
|
|
overflow: auto;
|
|
overflow: auto;
|
|
|
- aspect-ratio: 3/2;
|
|
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- width: 100%;
|
|
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
|
|
+ margin: 0 auto 20px; // 居中显示并保留底部间距
|
|
|
background-color: #f8f9fa;
|
|
background-color: #f8f9fa;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -832,17 +848,25 @@ defineExpose({
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.preview-canvas {
|
|
.preview-canvas {
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
|
|
+ width: 600px;
|
|
|
|
|
+ height: 400px;
|
|
|
position: relative;
|
|
position: relative;
|
|
|
- background-size: 100% 100%;
|
|
|
|
|
- background-position: center;
|
|
|
|
|
- background-repeat: no-repeat;
|
|
|
|
|
background-color: #f5f5f5;
|
|
background-color: #f5f5f5;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.background-image {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ z-index: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.draggable-element {
|
|
.draggable-element {
|