9 changed files with 880 additions and 64 deletions
-
6src/api/labelSetting/label_setting.js
-
14src/utils/zplUtil.js
-
59src/utils/zplUtil2.js
-
2src/views/modules/labelSetting/com_add_update_customer_label.vue
-
2src/views/modules/labelSetting/com_add_update_default_label.vue
-
26src/views/modules/labelSetting/com_add_update_label.vue
-
244src/views/modules/labelSetting/label_draw.vue
-
566src/views/modules/labelSetting/label_draw2.vue
-
25src/views/modules/labelSetting/label_setting.vue
@ -0,0 +1,59 @@ |
|||
export function generateZPL(elements,zplHOrP) { |
|||
const zpl = ['^XA']; |
|||
//zpl.push('^POI');
|
|||
zpl.push(zplHOrP) |
|||
elements.forEach(el => { |
|||
const x = Math.round(el.y * 1.3); |
|||
const y = Math.round((750-el.x) * 1.5); // 你之前的转换比例保持不变
|
|||
|
|||
switch (el.type) { |
|||
case 'text': |
|||
zpl.push(`^CI28^LH0,^JUS^CWJ,E:SIMSUN.FNT^CFJ,${el.fontSize},${el.fontSize}`); |
|||
zpl.push(el.newline |
|||
? `^FO${x},${y}^FB${el.lineWidth},${el.lineRows},0^CFJ,${el.fontSize}^FD${el.data}^FS` |
|||
: `^FO${x},${y}^FD${el.data}^FS`); |
|||
if (el.bold) { |
|||
zpl.push(el.newline |
|||
? `^FO${x+1},${y}^FB${el.lineWidth},${el.lineRows},0^CFJ,${el.fontSize}^FD${el.data}^FS` |
|||
: `^FO${x+1},${y}^FD${el.data}^FS`); |
|||
zpl.push(el.newline |
|||
? `^FO${x},${y+1}^FB${el.lineWidth},${el.lineRows},0^CFJ,${el.fontSize}^FD${el.data}^FS` |
|||
: `^FO${x},${y+1}^FD${el.data}^FS`); |
|||
zpl.push(el.newline |
|||
? `^FO${x+1},${y+1}^FB${el.lineWidth},${el.lineRows},0^CFJ,${el.fontSize}^FD${el.data}^FS` |
|||
: `^FO${x+1},${y+1}^FD${el.data}^FS`); |
|||
} |
|||
break; |
|||
|
|||
case 'barcode': |
|||
zpl.push(`^FO${x},${y}^BY${el.width}^BCB,${el.height},Y,N,N^FD${el.data}^FS`); |
|||
break; |
|||
|
|||
case 'qrcode': |
|||
zpl.push(`^FO${x},${y}^BQB,2,${el.height},^FDLA,${el.data}^FS`); |
|||
break; |
|||
|
|||
case 'onecode': |
|||
zpl.push(`^FO${x},${y}^BY${el.width}^BCB,${el.height},^FD${el.data}^FS`); |
|||
break; |
|||
|
|||
case 'pic': |
|||
if (el.data) { |
|||
zpl.push(`^FO${x},${y}^GFA,${el.data}`); |
|||
} |
|||
break; |
|||
|
|||
case 'hLine': |
|||
const y1 = Math.round(1200-el.x)-el.width; |
|||
zpl.push(`^FO${x},${y1}^FWR^GB${el.height},${el.width},3,B^FS`); |
|||
break; |
|||
case 'vLine': |
|||
zpl.push(`^FO${x},${y}^FWR^GB${el.height},${el.width},3,B^FS`); |
|||
break; |
|||
|
|||
|
|||
} |
|||
}); |
|||
zpl.push('^XZ'); |
|||
return zpl.join('\n'); |
|||
} |
|||
@ -0,0 +1,566 @@ |
|||
<template> |
|||
<div id="app" class="container"> |
|||
<!-- 左侧组件库 --> |
|||
<div class="component-palette"> |
|||
<span class="reportId"><el-input style="font-size: 18px;margin-bottom: 5px" placeholder="标签编号" v-model="labelNo" ></el-input></span> |
|||
<el-checkbox v-model="showGrid" style="margin-bottom: 10px; margin-left: 5px">显示网格</el-checkbox> |
|||
<el-checkbox v-model="snapToGrid" style="margin-bottom: 10px; margin-left: 5px">吸附到网格</el-checkbox> |
|||
<label style="margin-left: 5px">网格大小:</label> |
|||
<el-input v-model="gridSize" :min="5" :max="100" :step="5" size="mini" style="margin-left: 5px"></el-input> |
|||
|
|||
<h1 style="margin-left: 5px">工具栏</h1> |
|||
<div class="draggable-item" data-type="text" draggable @dragstart="onDragStart"> |
|||
<i class="el-icon-document"></i> 文本</div> |
|||
<div class="draggable-item" data-type="barcode" draggable @dragstart="onDragStart"> |
|||
<i style="display: inline-block; transform: rotate(90deg);" class="el-icon-tickets"></i> |
|||
条码</div> |
|||
<div class="draggable-item" data-type="qrcode" draggable @dragstart="onDragStart"> |
|||
<i class="el-icon-menu"></i> 二维码</div> |
|||
<div class="draggable-item" data-type="onecode" draggable @dragstart="onDragStart"> |
|||
<i class="el-icon-more"></i> 一维码</div> |
|||
<div class="draggable-item" data-type="pic" draggable @dragstart="onDragStart"> |
|||
<i class="el-icon-picture"></i> 图片</div> |
|||
<div class="draggable-item" data-type="hLine" draggable @dragstart="onDragStart"> |
|||
<i class="el-icon-minus"></i> 横线</div> |
|||
<div class="draggable-item" data-type="vLine" draggable @dragstart="onDragStart"> |
|||
<i style="display: inline-block; transform: rotate(90deg);" class="el-icon-minus"></i> |
|||
竖线</div> |
|||
</div> |
|||
<!-- 设计画布 --> |
|||
<div |
|||
class="design-area" |
|||
ref="container" |
|||
:style="[showGrid ? gridStyle : {}, {height: '670px', width: '890px'}]" |
|||
@dragover.prevent |
|||
@drop="onDrop" |
|||
> |
|||
<!-- 提示阴影文字 --> |
|||
<div v-if="elements.length === 0" class="canvas-hint-text"> |
|||
拖入组件开始设计 |
|||
</div> |
|||
<div |
|||
draggable |
|||
v-for="(el, index) in elements" |
|||
:key="index" |
|||
class="design-element" |
|||
:style="{ left: el.x + 'px', top: el.y + 'px' }" |
|||
@mousedown="startDrag(index, $event)" |
|||
@click="selectElement(index)" |
|||
:class="{ active: selectedIndex === index }"> |
|||
<!-- 文本图标 --> |
|||
<div v-if="el.type === 'text'" class="text-preview"> |
|||
文本元素 |
|||
</div> |
|||
<!-- 条形码图标 --> |
|||
<div v-if="el.type === 'barcode'" class="barcode-preview"> |
|||
<svg width="128" height="64" viewBox="0 0 24 24" fill="none"> |
|||
<path d="M2 4v16M4 4v16M6 4v16M8 4v16M10 4v16M12 4v16M14 4v16M16 4v16M18 4v16M20 4v16M22 4v16" stroke="#333" stroke-width="1"/> |
|||
</svg> |
|||
<div class="label" style="text-align: center">条码</div> |
|||
</div> |
|||
<!-- 一维码图标 --> |
|||
<div v-if="el.type === 'onecode'" class="onecode-preview"> |
|||
<svg width="128" height="64" viewBox="0 0 24 24" fill="none"> |
|||
<rect x="2" y="4" width="2" height="16" fill="#333"/> |
|||
<rect x="6" y="4" width="1" height="16" fill="#333"/> |
|||
<rect x="9" y="4" width="2" height="16" fill="#333"/> |
|||
<rect x="13" y="4" width="1" height="16" fill="#333"/> |
|||
<rect x="16" y="4" width="2" height="16" fill="#333"/> |
|||
<rect x="20" y="4" width="1" height="16" fill="#333"/> |
|||
</svg> |
|||
<div class="label" style="text-align: center">一维码</div> |
|||
</div> |
|||
<!-- 二维码图标 --> |
|||
<svg v-if="el.type === 'qrcode'" width="100" height="100" viewBox="0 0 24 24" fill="none"> |
|||
<rect x="2" y="2" width="6" height="6" stroke="#333" stroke-width="2"/> |
|||
<rect x="16" y="2" width="6" height="6" stroke="#333" stroke-width="2"/> |
|||
<rect x="2" y="16" width="6" height="6" stroke="#333" stroke-width="2"/> |
|||
<rect x="10" y="10" width="4" height="4" fill="#333"/> |
|||
<rect x="14" y="14" width="2" height="2" fill="#333"/> |
|||
</svg> |
|||
|
|||
<!-- 图片图标 --> |
|||
<svg v-if="el.type === 'pic'" width="100" height="100" viewBox="0 0 24 24" fill="none"> |
|||
<rect x="2" y="4" width="20" height="16" stroke="#333" stroke-width="2"/> |
|||
<circle cx="8" cy="10" r="2" fill="#333"/> |
|||
<path d="M2 18l6-6 4 4 6-6 4 4v4H2z" fill="#aaa"/> |
|||
</svg> |
|||
|
|||
<!-- 水平线 --> |
|||
<div v-if="el.type === 'hLine'" |
|||
:style="{ width: (el.width>500?(el.width-260):(el.width-100)) + 'px', height: el.height + 'px', backgroundColor: '#000', borderRadius: '1px' }"></div> |
|||
|
|||
<!-- 垂直线 --> |
|||
<div v-if="el.type === 'vLine'" |
|||
:style="{ width: el.width + 'px', height: (el.height-150) + 'px', backgroundColor: '#000', borderRadius: '1px' }"></div> |
|||
</div> |
|||
|
|||
|
|||
</div> |
|||
<!-- StringBuilder StringBuffer util height width transition动画效果 延迟显示等 --> |
|||
<!-- 右侧属性面板 --> |
|||
<div class="property-panel"> |
|||
<div v-if="elements.length === 0" class="property-hint-text"> |
|||
属性、预览区域 |
|||
</div> |
|||
<div style="height: 140px"> |
|||
<div v-if="selectedElement"> |
|||
<el-tooltip placement="top"> |
|||
<div slot="content">宽度高度等属性需遵循ZPL指令协议,比如:二维码最大高度是10,大于10不生效</div> |
|||
<h3 style="width: 100px">属性配置 <i class="el-icon-info"></i></h3> |
|||
</el-tooltip> |
|||
<div v-if="selectedElement.type === 'text'"> |
|||
<label>文本内容:<input v-model="selectedElement.data"/></label><br><br> |
|||
<label>字体大小:<input type="number" v-model="selectedElement.fontSize"/></label> |
|||
<br><br> |
|||
<label style="font: bold">加粗:<el-checkbox v-model="selectedElement.bold"></el-checkbox></label> |
|||
<label style="margin-left: 10px">自动换行:<el-checkbox v-model="selectedElement.newline"></el-checkbox></label> |
|||
<label v-if="selectedElement.newline"> |
|||
文本宽度:<input type="number" v-model="selectedElement.lineWidth"/></label> |
|||
<label v-if="selectedElement.newline"> |
|||
文本行数:<input type="number" v-model="selectedElement.lineRows"/></label> |
|||
</div> |
|||
<div v-if="selectedElement.type === 'hLine' || selectedElement.type === 'vLine'"> |
|||
<label>宽度:<input type="number" v-model="selectedElement.width"/></label><br><br> |
|||
<label>高度:<input type="number" v-model="selectedElement.height"/></label> |
|||
</div> |
|||
<div v-if="selectedElement.type === 'barcode' || selectedElement.type === 'onecode'"> |
|||
<label>数据:<input v-model="selectedElement.data"/></label><br><br> |
|||
<label>宽度:<input type="number" v-model="selectedElement.width"/></label><br><br> |
|||
<label>高度:<input type="number" v-model="selectedElement.height"/></label> |
|||
</div> |
|||
<div v-if="selectedElement.type === 'qrcode'"> |
|||
<label>数据:<input v-model="selectedElement.data"/></label><br><br> |
|||
<label>高度:<input type="number" @change="checkValue" v-model="selectedElement.height"/></label> |
|||
</div> |
|||
<div v-if="selectedElement.type === 'pic'"> |
|||
<i icon="el-icon-upload"></i><input type="file" ref="uploadFile" accept="image/*" @change="handleImageUpload"/> |
|||
<img :src="previewDataUrl" class="preview" :style="{ width: '120px', border: '1px solid #ccc' }" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div style="height: 750px" v-if="elements.length > 0"> |
|||
<el-button type="danger" @click="deleteElement" icon="el-icon-delete">删除</el-button> |
|||
<el-button type="primary" @click="saveZPL" icon="el-icon-document">保存</el-button><br><br> |
|||
<el-select v-model="zplDpi"> |
|||
<el-option value="6" label="152dpi"/> |
|||
<el-option value="8" label="203dpi"/> |
|||
<el-option value="12" label="300dpi"/> |
|||
<el-option value="24" label="600dpi"/> |
|||
</el-select> |
|||
<el-button type="primary" @click="previewZPL" icon="el-icon-document">预览</el-button><br><br> |
|||
<img v-if="previewImage" :src="previewImage" |
|||
style="width: 65%;height: auto;margin-left: 60px; |
|||
border: 1px solid rgba(200, 200, 200, 0.8); transform: rotate(90deg); " alt="ZPL预览图"> |
|||
<el-tooltip placement="top"> |
|||
<div slot="content">画布布局仅供参考,精确布局请查看下面的预览图</div> |
|||
<h3 style="width: 100px">ZPL代码 <i class="el-icon-info"></i></h3> |
|||
</el-tooltip> |
|||
<pre id="zplcode" style="font-size: 14px">{{ generatedZPL }}</pre> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import axios from 'axios'; |
|||
import { generateZPL } from '@/utils/zplUtil2.js'; |
|||
import debounce from "lodash/debounce"; |
|||
import {saveZplElements,getZplElements} from '@/api/labelSetting/label_setting.js'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
zplDpi: '8', |
|||
zplHOrP:'^FWB', |
|||
canvas: null, |
|||
context: null, |
|||
previewDataUrl: '', |
|||
zplCode: '', |
|||
imgWidth: 200, // 目标打印图像宽度(像素,建议 8 的倍数) |
|||
previewImage:'', |
|||
dragIndex: -1, |
|||
startPos: {x: 0, y: 0}, |
|||
elements: [], // 设计器中的元素集合 |
|||
selectedIndex: -1, |
|||
dpi: 203, // 打印机分辨率 |
|||
dragType: '',//用来判断拖拽类型,1为左侧标签栏拖拽,2为画布拖拽 |
|||
labelNo:'', |
|||
showGrid: true, // 是否显示网格 |
|||
snapToGrid: true, |
|||
gridSize: 20, |
|||
labelSet: {}, |
|||
} |
|||
}, |
|||
computed: { |
|||
selectedElement() { |
|||
return this.elements[this.selectedIndex] |
|||
}, |
|||
generatedZPL() { |
|||
return generateZPL(this.elements,this.zplHOrP); |
|||
}, |
|||
gridStyle() { |
|||
const size = this.gridSize || 10; |
|||
return { |
|||
backgroundImage: 'linear-gradient(to right, #eee 1px, transparent 1px), linear-gradient(to bottom, #eee 1px, transparent 1px)', |
|||
backgroundSize: `${size}px ${size}px`, |
|||
backgroundColor: '#fff' |
|||
}; |
|||
} |
|||
}, |
|||
created() { |
|||
// 创建一个防抖函数并绑定到 this 上 |
|||
this.debouncedPreviewZPL = debounce(this.previewZPL, 500); // 500ms 延迟 |
|||
}, |
|||
activated() { |
|||
this.labelNo = this.$route.params.labelSetting?this.$route.params.labelSetting.labelNo:this.labelNo; |
|||
this.labelSet = this.$route.params.labelSetting?this.$route.params.labelSetting:this.labelSet; |
|||
this.getZplElements(); |
|||
}, |
|||
watch: { |
|||
generatedZPL: function (newVal, oldVal) { |
|||
if (newVal) { |
|||
this.debouncedPreviewZPL(); |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
handleImageUpload(event) { |
|||
const file = event.target.files[0]; |
|||
if (!file) return; |
|||
const reader = new FileReader(); |
|||
reader.onload = (e) => { |
|||
// 原图 dataURL |
|||
this.previewDataUrl = e.target.result; |
|||
const img = new Image(); |
|||
img.onload = () => { |
|||
const scale = this.imgWidth / img.width; |
|||
|
|||
// 旋转后画布宽高(逆时针:新宽 = 原高 × scale, 新高 = 原宽 × scale) |
|||
const rotatedCanvas = document.createElement('canvas'); |
|||
rotatedCanvas.width = Math.round(img.height * scale); |
|||
rotatedCanvas.height = this.imgWidth; |
|||
|
|||
const ctx = rotatedCanvas.getContext('2d'); |
|||
ctx.fillStyle = '#fff'; |
|||
ctx.fillRect(0, 0, rotatedCanvas.width, rotatedCanvas.height); |
|||
|
|||
// 关键:逆时针旋转 90 度 = 顺时针旋转 -90° |
|||
ctx.translate(0, rotatedCanvas.height); |
|||
ctx.rotate(-Math.PI / 2); |
|||
|
|||
// 画图时宽高也要按缩放后算 |
|||
ctx.drawImage(img, 0, 0, img.width, img.height, |
|||
0, 0, this.imgWidth, Math.round(img.height * scale)); |
|||
|
|||
this.canvas = rotatedCanvas; |
|||
this.context = ctx; |
|||
//this.previewDataUrl = rotatedCanvas.toDataURL('image/jpeg', 0.6); |
|||
this.generatePicZPL(); |
|||
}; |
|||
img.src = e.target.result; |
|||
}; |
|||
reader.readAsDataURL(file); |
|||
}, |
|||
|
|||
generatePicZPL() { |
|||
if (!this.canvas || !this.context) return; |
|||
const imgData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); |
|||
const { width, height, data } = imgData; |
|||
const bytesPerRow = Math.ceil(width / 8); |
|||
const totalBytes = bytesPerRow * height; |
|||
let binaryData = ''; |
|||
for (let y = 0; y < height; y++) { |
|||
for (let byteIdx = 0; byteIdx < bytesPerRow; byteIdx++) { |
|||
let byteStr = ''; |
|||
for (let bit = 0; bit < 8; bit++) { |
|||
const x = byteIdx * 8 + bit; |
|||
if (x >= width) { |
|||
byteStr += '0'; |
|||
} else { |
|||
const i = (y * width + x) * 4; |
|||
const r = data[i], g = data[i + 1], b = data[i + 2]; |
|||
const grayscale = (r + g + b) / 3; |
|||
byteStr += grayscale < 128 ? '1' : '0'; |
|||
} |
|||
} |
|||
const hex = parseInt(byteStr, 2).toString(16).padStart(2, '0').toUpperCase(); |
|||
binaryData += hex; |
|||
} |
|||
} |
|||
this.selectedElement.data = `${totalBytes},${totalBytes},${bytesPerRow},${binaryData}`; |
|||
}, |
|||
// 开始拖拽 |
|||
startDrag(index, e) { |
|||
this.dragType = 2; |
|||
this.dragIndex = index; |
|||
this.startPos = { |
|||
x: e.clientX - this.elements[index].x, |
|||
y: e.clientY - this.elements[index].y |
|||
}; |
|||
e.preventDefault();//阻止默认事件 |
|||
e.stopPropagation();//阻止事件冒泡,不加这两行拖拽结束后无法触发mouseup事件 |
|||
document.addEventListener('mousemove', this.handleDrag); |
|||
document.addEventListener('mouseup', this.stopDrag); |
|||
}, |
|||
// 处理拖拽移动 |
|||
handleDrag(e) { |
|||
if (this.dragIndex === -1) return; |
|||
|
|||
let newX = e.clientX - this.startPos.x; |
|||
let newY = e.clientY - this.startPos.y; |
|||
|
|||
// 如果开启了吸附网格,则对 newX / newY 吸附处理 |
|||
if (this.snapToGrid && this.gridSize > 0) { |
|||
newX = Math.round(newX / this.gridSize) * this.gridSize; |
|||
newY = Math.round(newY / this.gridSize) * this.gridSize; |
|||
} |
|||
|
|||
this.$set(this.elements[this.dragIndex], 'x', newX); |
|||
this.$set(this.elements[this.dragIndex], 'y', newY); |
|||
}, |
|||
|
|||
// 结束拖拽 |
|||
stopDrag(e) { |
|||
e.preventDefault(); |
|||
const containerRect = this.$refs.container.getBoundingClientRect(); |
|||
const mouseX = e.clientX; |
|||
const mouseY = e.clientY; |
|||
// 判断是否超出容器边界 |
|||
if ( mouseX < containerRect.left || mouseX > containerRect.right || mouseY < containerRect.top || mouseY > containerRect.bottom) { |
|||
this.$message({message: '不可超出画布区域', |
|||
type: 'error', duration: 2000, onClose: () => {} |
|||
}) |
|||
return; |
|||
} |
|||
this.dragIndex = -1; |
|||
document.removeEventListener('mousemove', this.handleDrag); |
|||
document.removeEventListener('mouseup', this.stopDrag); |
|||
// 拖拽结束预览 |
|||
this.previewZPL(); |
|||
}, |
|||
|
|||
onDragStart(e) { |
|||
this.dragType = 1; |
|||
e.dataTransfer.setData('type', e.target.dataset.type) |
|||
}, |
|||
onDrop(e) { |
|||
if (!this.labelNo) { |
|||
this.$alert('标签编号不可为空!', '错误', { |
|||
confirmButtonText: '确定' |
|||
}) |
|||
return false |
|||
} |
|||
const type = e.dataTransfer.getData('type'); |
|||
const rect = e.target.getBoundingClientRect(); |
|||
const x = e.clientX - rect.left; |
|||
const y = e.clientY - rect.top; |
|||
// 拖拽类型为1时即从左侧标签栏拖拽过来,添加元素;在绘画面板里拖拽时,只修改元素位置 |
|||
if (this.dragType === 1) { |
|||
const newElement = { |
|||
type, x, y, |
|||
newline:false,//是否自动换行 |
|||
lineRows: 2,//自动换行文本行数 |
|||
lineWidth: 200,//自动换行文本区间宽度 |
|||
fontSize: 30, |
|||
data: "", |
|||
//默认宽高需要根据类型赋值 |
|||
height: type == 'hLine' ? 3 : type == 'qrcode' || type === 'pic' ? 10 : type == 'vLine' ? 600 : 50, |
|||
width: type == 'hLine' ? 600 : type == 'onecode' ? 5 : 3 |
|||
} |
|||
this.elements.push(newElement) |
|||
if (type=='pic') { |
|||
this.canvas = ''; |
|||
this.context = ''; |
|||
this.previewDataUrl = ''; |
|||
if (this.$refs.uploadFile) { |
|||
this.$refs.uploadFile.value = null; |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
deleteElement() { |
|||
this.$confirm(`确定要删除这个元素吗`, '提示', { |
|||
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' |
|||
}).then(() => { |
|||
this.elements.splice(this.selectedIndex, 1); |
|||
}) |
|||
}, |
|||
selectElement(index) { |
|||
this.selectedIndex = index |
|||
}, |
|||
saveZPL() { |
|||
let dataIn = {} |
|||
dataIn.zplCode = this.generatedZPL; |
|||
dataIn.elements = this.elements; |
|||
dataIn.reportId = this.labelNo; |
|||
if (!this.labelNo) { |
|||
this.$alert('标签编号不可为空!', '错误', { |
|||
confirmButtonText: '确定' |
|||
}) |
|||
return false |
|||
} |
|||
saveZplElements(dataIn).then(({data}) => { |
|||
if(data.code === 0){ |
|||
this.$message.success("保存成功!"); |
|||
}else{ |
|||
this.$message.error(data.msg); |
|||
} |
|||
}) |
|||
}, |
|||
getZplElements () { |
|||
getZplElements({reportId: this.labelNo}).then(({data}) => { |
|||
if(data.code === 200){ |
|||
this.elements = data.data; |
|||
if (this.elements.length > 0) { |
|||
this.previewZPL(); |
|||
} |
|||
}else{ |
|||
this.$message.error(data.msg); |
|||
} |
|||
}) |
|||
}, |
|||
async previewZPL() { |
|||
try { |
|||
const response = await axios.post( |
|||
'https://api.labelary.com/v1/printers/'+ this.zplDpi +'dpmm/labels/4x6/0/', |
|||
this.generatedZPL, |
|||
{ responseType: 'blob' } |
|||
); |
|||
this.previewImage = URL.createObjectURL(response.data); |
|||
} catch (error) { |
|||
console.error('预览失败:', error); |
|||
} |
|||
}, |
|||
checkValue() { |
|||
if (this.selectedElement.height > 10) { |
|||
this.$message({message: '二维码最大尺寸为10', |
|||
type: 'error', duration: 1500, onClose: () => {} |
|||
}) |
|||
this.selectedElement.height = 10; |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.container { |
|||
display: flex; |
|||
height: 100%; |
|||
overflow: auto; |
|||
} |
|||
|
|||
.component-palette { |
|||
width: 120px; |
|||
//border-right: 1px solid #ddd; |
|||
padding: 10px; |
|||
} |
|||
|
|||
.draggable-item { |
|||
padding: 8px; |
|||
margin: 5px; |
|||
border: 1px solid #999; |
|||
cursor: move; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.design-area { |
|||
flex: 1; |
|||
position: relative; |
|||
border: 1px dashed #ccc; |
|||
width: 890px; |
|||
height: 670px; |
|||
} |
|||
|
|||
.design-element { |
|||
position: absolute; |
|||
|
|||
} |
|||
|
|||
.design-element { |
|||
position: absolute; |
|||
cursor: move; |
|||
border-radius: 8px; /* 圆角 */ |
|||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); /* 阴影效果 */ |
|||
padding: 2px; /* 内边距 */ |
|||
transition: transform 0.3s ease, box-shadow 0.2s ease; /* 动画效果 */ |
|||
user-select: none; /* 禁止选中文本 */ |
|||
background-color: #f1f1f1; /* 背景颜色 */ |
|||
} |
|||
|
|||
.design-element.active { |
|||
border: 2px solid #00d7ff; /* 选中元素边框 */ |
|||
box-shadow: 0 8px 12px rgba(0, 123, 255, 0.3); /* 选中元素阴影 */ |
|||
} |
|||
|
|||
.design-element:hover { |
|||
transform: scale(1.05); /* 鼠标悬停时放大 */ |
|||
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15); /* 鼠标悬停阴影 */ |
|||
} |
|||
|
|||
.design-element svg { |
|||
border-radius: 4px; /* 圆角 */ |
|||
} |
|||
|
|||
.design-element div { |
|||
border-radius: 4px; /* 圆角 */ |
|||
} |
|||
.text-preview { |
|||
font-size: 20px; |
|||
font-weight: 600; |
|||
color: #222; |
|||
padding: 10px 16px; |
|||
//border: 1px solid #ddd; |
|||
border-radius: 10px; |
|||
background-color: #f1f1f1; |
|||
//box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
//transition: transform 0.2s ease; |
|||
} |
|||
|
|||
.property-panel { |
|||
width: 400px; |
|||
//border-left: 1px solid #ddd; |
|||
padding: 10px; |
|||
} |
|||
|
|||
.image-zpl-uploader { |
|||
max-width: 600px; |
|||
margin: 20px auto; |
|||
} |
|||
.preview { |
|||
margin: 0 10px; |
|||
} |
|||
.canvas-hint-text { |
|||
position: absolute; |
|||
top: 40%; |
|||
left: 48%; |
|||
transform: translate(-50%, -50%); |
|||
font-size: 32px; |
|||
font-weight: 600; |
|||
color: rgba(150, 150, 150, 0.25); /* 柔和灰色 */ |
|||
font-style: italic; |
|||
pointer-events: none; |
|||
user-select: none; |
|||
z-index: 1; |
|||
letter-spacing: 2px; |
|||
} |
|||
|
|||
.property-hint-text { |
|||
position: absolute; |
|||
top: 40%; |
|||
left: 82%; |
|||
transform: translate(-50%, -50%); |
|||
font-size: 32px; |
|||
font-weight: 700; |
|||
color: rgba(150, 150, 150, 0.25); /* 柔和灰色 */ |
|||
font-style: italic; |
|||
pointer-events: none; |
|||
user-select: none; |
|||
z-index: 1; |
|||
letter-spacing: 2px; |
|||
} |
|||
/deep/.reportId input.el-input__inner { |
|||
height: 28px !important; |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue