You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1113 lines
31 KiB
1113 lines
31 KiB
<template>
|
|
<div class="label-designer">
|
|
<!-- 主要内容区域 -->
|
|
<div class="main-content">
|
|
<!-- 顶部工具栏区域 -->
|
|
<div class="top-toolbar">
|
|
<HorizontalToolbar
|
|
:label-no="labelNo"
|
|
@update:labelNo="labelNo = $event"
|
|
@drag-start="handleToolDragStart"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 画布区域 -->
|
|
<div class="canvas-area">
|
|
<!-- 画布信息栏 -->
|
|
<div class="canvas-info-bar">
|
|
<div class="canvas-info">
|
|
<span class="info-item">
|
|
<strong>{{ currentCanvasSize.name }}</strong>
|
|
({{ currentCanvasSize.widthMm }}×{{ currentCanvasSize.heightMm }}mm)
|
|
</span>
|
|
<!-- <span class="info-item">
|
|
精确: {{ currentCanvasSize.exactWidth }}×{{ currentCanvasSize.exactHeight }}px
|
|
</span>-->
|
|
<span class="info-item">
|
|
显示: {{ currentCanvasSize.width }}×{{ currentCanvasSize.height }}px
|
|
</span>
|
|
<span class="info-item" v-if="currentCanvasSize.isScaled">
|
|
缩放: {{ Math.round(currentCanvasSize.scale * 100) }}%
|
|
</span>
|
|
</div>
|
|
|
|
<div class="canvas-middle-controls">
|
|
<!-- 纸张选择 -->
|
|
<div class="control-item">
|
|
<label class="control-label">纸张尺寸:</label>
|
|
<PaperSelector
|
|
:selected-paper="selectedPaper"
|
|
:orientation="orientation"
|
|
:horizontal-mode="true"
|
|
@paper-change="handlePaperChange"
|
|
@orientation-change="handleOrientationChange"
|
|
/>
|
|
</div>
|
|
<div class="canvas-info">
|
|
<span class="info-item">
|
|
显示: {{ currentCanvasSize.width }}×{{ currentCanvasSize.height }}px
|
|
</span>
|
|
<span class="info-item" v-if="currentCanvasSize.isScaled">
|
|
缩放: {{ Math.round(currentCanvasSize.scale * 100) }}%
|
|
</span>
|
|
</div>
|
|
<!-- 网格设置 -->
|
|
<div class="control-item">
|
|
<label class="control-label">网格:</label>
|
|
<div class="grid-controls">
|
|
<el-checkbox
|
|
v-model="showGrid"
|
|
size="small"
|
|
>显示</el-checkbox>
|
|
|
|
<el-checkbox
|
|
v-model="snapToGrid"
|
|
size="small"
|
|
>网格对齐</el-checkbox>
|
|
|
|
<div class="grid-size-control">
|
|
<span class="size-label">网格大小:</span>
|
|
<el-input-number
|
|
v-model="gridSize"
|
|
:min="5"
|
|
:max="100"
|
|
:step="5"
|
|
size="mini"
|
|
:controls="false"
|
|
style="width: 60px;"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="canvas-controls">
|
|
<el-select v-model="selectedDPI" @change="handleDPIChange" size="small" style="width: 120px;">
|
|
<el-option
|
|
v-for="option in dpiOptions"
|
|
:key="option.value"
|
|
:label="option.label"
|
|
:value="option.value"
|
|
:title="option.description"
|
|
/>
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="canvas-container" ref="canvasContainer">
|
|
<DesignCanvas
|
|
ref="canvas"
|
|
:key="`canvas-${selectedPaper}-${selectedDPI}-${orientation}-${canvasUpdateKey}`"
|
|
:orientation="orientation"
|
|
:show-grid="showGrid"
|
|
:grid-size="gridSize"
|
|
:elements="elements"
|
|
:selected-index="selectedIndex"
|
|
:canvas-size="currentCanvasSize"
|
|
@drop="handleDrop"
|
|
@element-select="handleElementSelect"
|
|
@element-drag="handleElementDrag"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧属性面板区域 -->
|
|
<div class="right-panel">
|
|
<PropertyPanel
|
|
:orientation="orientation"
|
|
:elements="elements"
|
|
:selected-index="selectedIndex"
|
|
:selected-element="selectedElement"
|
|
:selected-paper="selectedPaper+''"
|
|
:canvas-size="currentCanvasSize"
|
|
@delete-element="handleDeleteElement"
|
|
@save="handleSave"
|
|
@preview="handlePreview"
|
|
@data-source="handleDataSource"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 数据源选择对话框 -->
|
|
<DataSourceDialog
|
|
:visible="dataSourceVisible"
|
|
:data-keys="dataKeys"
|
|
:current-text="currentElementText"
|
|
:source-type="sourceType"
|
|
@update:visible="dataSourceVisible = $event"
|
|
@confirm="handleDataSourceConfirm"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import HorizontalToolbar from './components/HorizontalToolbar.vue'
|
|
import DesignCanvas from './components/DesignCanvas.vue'
|
|
import PropertyPanel from './components/PropertyPanel.vue'
|
|
import DataSourceDialog from './components/DataSourceDialog.vue'
|
|
import PaperSelector from './components/PaperSelector.vue'
|
|
import { CoordinateTransformer } from '@/utils/coordinateTransform.js'
|
|
import { ZPLGenerator } from '@/utils/zplGenerator.js'
|
|
import { getCanvasSize, getRecommendedGridSize } from '@/utils/paperConfig.js'
|
|
import { saveZplElements, getZplElements } from '@/api/labelSetting/label_setting.js'
|
|
import {getViewFieldsByLabelType} from '@/api/labelSetting/com_add_update_label.js';
|
|
import { calculateCanvasConfig, calculateCanvasConfigById, getRecommendedDPI, DPI_CONFIGS } from '@/utils/canvasCalculator.js'
|
|
import dynamicPaperConfig from '@/utils/paperConfigDynamic.js'
|
|
import { debounce } from 'lodash'
|
|
|
|
export default {
|
|
name: 'LabelDesigner',
|
|
components: {
|
|
HorizontalToolbar,
|
|
DesignCanvas,
|
|
PropertyPanel,
|
|
DataSourceDialog,
|
|
PaperSelector
|
|
},
|
|
props: {
|
|
orientation: {
|
|
type: String,
|
|
default: 'portrait',
|
|
validator: val => ['portrait', 'landscape'].includes(val)
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
sourceType: 'text', // 数据源类型,默认为文本
|
|
labelNo: '',
|
|
showGrid: true,
|
|
snapToGrid: true,
|
|
gridSize: 10,
|
|
selectedPaper: null, // 动态纸张ID,初始为null
|
|
selectedDPI: 300, // 新绘制标签默认DPI为300
|
|
paperLoaded: false, // 纸张数据是否已加载
|
|
|
|
elements: [],
|
|
selectedIndex: -1,
|
|
dataSourceVisible: false,
|
|
dataKeys: [],
|
|
currentElementText: '', // 当前元素的文本内容
|
|
labelSettings: {},
|
|
partialVisibilityWarned: false, // 部分可见性警告状态
|
|
debouncedBoundaryMessage: null, // 防抖边界消息函数
|
|
containerSize: { width: 800, height: 600 }, // 画布容器尺寸
|
|
canvasUpdateKey: 0 // 强制更新画布的key
|
|
}
|
|
},
|
|
computed: {
|
|
selectedElement() {
|
|
return this.selectedIndex >= 0 ? this.elements[this.selectedIndex] : null
|
|
},
|
|
currentCanvasSize() {
|
|
try {
|
|
// 如果纸张数据未加载或未选择纸张,返回默认尺寸
|
|
if (!this.paperLoaded || !this.selectedPaper) {
|
|
return this.getDefaultCanvasSize()
|
|
}
|
|
|
|
// 使用新的动态画布计算功能
|
|
const canvasConfig = calculateCanvasConfigById(
|
|
this.selectedPaper,
|
|
this.orientation,
|
|
this.selectedDPI,
|
|
this.containerSize
|
|
)
|
|
|
|
// 添加调试信息
|
|
console.log('currentCanvasSize 计算:', {
|
|
paperId: this.selectedPaper,
|
|
orientation: this.orientation,
|
|
dpi: this.selectedDPI,
|
|
exactSize: `${canvasConfig.exact.width}×${canvasConfig.exact.height}px`,
|
|
physicalSize: `${canvasConfig.exact.widthMm}×${canvasConfig.exact.heightMm}mm`
|
|
})
|
|
|
|
return {
|
|
// 使用精确尺寸,不进行缩放
|
|
width: canvasConfig.exact.width,
|
|
height: canvasConfig.exact.height,
|
|
|
|
// 精确尺寸(用于坐标转换和ZPL生成)
|
|
exactWidth: canvasConfig.exact.width,
|
|
exactHeight: canvasConfig.exact.height,
|
|
|
|
// 物理信息
|
|
name: canvasConfig.physical.name,
|
|
description: canvasConfig.meta.description,
|
|
|
|
// 缩放信息(现在总是1:1)
|
|
scale: 1,
|
|
isScaled: false,
|
|
|
|
// DPI信息
|
|
dpi: this.selectedDPI,
|
|
|
|
// 物理尺寸(毫米)
|
|
widthMm: canvasConfig.exact.widthMm,
|
|
heightMm: canvasConfig.exact.heightMm,
|
|
|
|
// 纸张ID
|
|
paperId: this.selectedPaper,
|
|
|
|
// 添加更新键确保对象变化
|
|
updateKey: `${this.selectedPaper}-${this.selectedDPI}-${this.orientation}-${Date.now()}`
|
|
}
|
|
} catch (error) {
|
|
console.error('画布尺寸计算错误:', error)
|
|
// 降级到原有的计算方式
|
|
return this.fallbackCanvasSize()
|
|
}
|
|
},
|
|
|
|
// 降级的画布尺寸计算
|
|
fallbackCanvasSize() {
|
|
// 使用原有的getCanvasSize方法
|
|
const canvasSize = getCanvasSize(this.selectedPaper, this.orientation)
|
|
return {
|
|
...canvasSize,
|
|
dpi: this.selectedDPI,
|
|
widthMm: canvasSize.width * 25.4 / this.selectedDPI,
|
|
heightMm: canvasSize.height * 25.4 / this.selectedDPI,
|
|
scale: 1,
|
|
isScaled: false
|
|
}
|
|
},
|
|
|
|
// 可用的DPI选项
|
|
dpiOptions() {
|
|
return Object.keys(DPI_CONFIGS).map(dpi => ({
|
|
value: parseInt(dpi),
|
|
label: DPI_CONFIGS[dpi].name,
|
|
description: DPI_CONFIGS[dpi].description
|
|
}))
|
|
},
|
|
coordinateTransformer() {
|
|
// 使用纸张ID创建坐标转换器
|
|
return new CoordinateTransformer(this.orientation, null, null, this.selectedPaper)
|
|
},
|
|
zplGenerator() {
|
|
// 使用纸张ID和DPI创建ZPL生成器
|
|
return new ZPLGenerator(this.orientation, null, null, this.selectedPaper, this.selectedDPI)
|
|
}
|
|
},
|
|
async created() {
|
|
await this.initializePapers()
|
|
this.initializeFromRoute()
|
|
},
|
|
async activated() {
|
|
await this.initializePapers()
|
|
this.initializeFromRoute()
|
|
this.loadElements()
|
|
this.updateGridSize()
|
|
},
|
|
watch: {
|
|
selectedPaper: {
|
|
handler() {
|
|
this.updateGridSize()
|
|
this.updateTransformers()
|
|
}
|
|
},
|
|
selectedDPI: {
|
|
handler(newDPI, oldDPI) {
|
|
if (oldDPI && newDPI !== oldDPI) {
|
|
console.log('DPI监听器触发:', { oldDPI, newDPI })
|
|
// DPI变化时立即处理
|
|
this.handleDPIChange()
|
|
}
|
|
}
|
|
},
|
|
|
|
},
|
|
methods: {
|
|
// 初始化纸张数据
|
|
async initializePapers() {
|
|
try {
|
|
await dynamicPaperConfig.loadPapers()
|
|
this.paperLoaded = true
|
|
|
|
// 如果还没有选择纸张,设置默认纸张
|
|
if (!this.selectedPaper) {
|
|
const papers = dynamicPaperConfig.getActivePapers()
|
|
if (papers.length > 0) {
|
|
// 优先选择4×2英寸作为默认纸张
|
|
const defaultPaper = papers.find(p => p.name.includes('4×2')) || papers[0]
|
|
this.selectedPaper = defaultPaper.id
|
|
}
|
|
}
|
|
|
|
console.log('纸张数据初始化完成:', dynamicPaperConfig.getAllPapers().length)
|
|
} catch (error) {
|
|
console.error('初始化纸张数据失败:', error)
|
|
this.paperLoaded = false
|
|
}
|
|
},
|
|
|
|
// 获取默认画布尺寸
|
|
getDefaultCanvasSize() {
|
|
return {
|
|
width: 812,
|
|
height: 406,
|
|
exactWidth: 812,
|
|
exactHeight: 406,
|
|
name: '默认纸张',
|
|
description: '4×2英寸默认尺寸',
|
|
scale: 1,
|
|
isScaled: false,
|
|
dpi: this.selectedDPI,
|
|
widthMm: 101.6,
|
|
heightMm: 50.8,
|
|
paperId: null,
|
|
updateKey: `default-${this.selectedDPI}-${this.orientation}-${Date.now()}`
|
|
}
|
|
},
|
|
|
|
initializeFromRoute() {
|
|
const labelSetting = this.$route.params.labelSetting
|
|
if (labelSetting) {
|
|
this.labelNo = labelSetting.labelNo
|
|
this.labelSettings = labelSetting
|
|
|
|
// 根据 ReportFileList 中的纸张信息设置
|
|
if (labelSetting.paperId) {
|
|
// 新格式:使用纸张ID
|
|
this.selectedPaper = labelSetting.paperId
|
|
console.log('从路由参数设置纸张ID:', labelSetting.paperId)
|
|
} else if (labelSetting.paperSize) {
|
|
// 兼容旧格式:根据纸张类型查找对应的纸张ID
|
|
const legacyPaper = dynamicPaperConfig.findPaperByLegacyType(labelSetting.paperSize)
|
|
if (legacyPaper) {
|
|
this.selectedPaper = legacyPaper.id
|
|
console.log('从路由参数转换纸张类型:', labelSetting.paperSize, '-> ID:', legacyPaper.id)
|
|
} else {
|
|
console.warn('未找到对应的纸张类型:', labelSetting.paperSize)
|
|
}
|
|
}
|
|
|
|
if (labelSetting.dpi) {
|
|
this.selectedDPI = labelSetting.dpi
|
|
console.log('从路由参数设置DPI:', labelSetting.dpi)
|
|
} else {
|
|
// 没有历史DPI时,确保使用默认值 300
|
|
this.selectedDPI = 300
|
|
console.log('使用默认DPI: 300')
|
|
}
|
|
|
|
if (labelSetting.paperOrientation) {
|
|
this.orientation = labelSetting.paperOrientation
|
|
console.log('从路由参数设置打印方向:', labelSetting.paperOrientation)
|
|
} else {
|
|
// 没有历史打印方向时,确保使用默认值 portrait
|
|
this.orientation = 'portrait'
|
|
console.log('使用默认打印方向: portrait')
|
|
}
|
|
|
|
// 输出初始化信息
|
|
console.log('标签设计器初始化完成:', {
|
|
labelNo: this.labelNo,
|
|
paperId: this.selectedPaper,
|
|
dpi: this.selectedDPI,
|
|
orientation: this.orientation,
|
|
hasHistoryData: {
|
|
paperId: !!labelSetting.paperId,
|
|
paperSize: !!labelSetting.paperSize,
|
|
dpi: !!labelSetting.dpi,
|
|
orientation: !!labelSetting.paperOrientation
|
|
}
|
|
})
|
|
} else {
|
|
// 完全没有路由参数时,使用所有默认值
|
|
console.log('没有路由参数,使用所有默认值:', {
|
|
paperId: this.selectedPaper,
|
|
dpi: this.selectedDPI,
|
|
orientation: this.orientation
|
|
})
|
|
}
|
|
},
|
|
|
|
async loadElements() {
|
|
if (!this.labelNo) return
|
|
|
|
try {
|
|
const { data } = await getZplElements({ reportId: this.labelNo })
|
|
if (data.code === 200) {
|
|
// 修复:对每个元素补全属性,保证响应式
|
|
const defaultElement = {
|
|
type: '', x: 0, y: 0, data: '', fontSize: 30, bold: false, newline: false, lineRows: 2,
|
|
lineWidth: 200, digits: 6, step: 1, width: 100, height: 30, previewUrl: '', barcodeType: '', showContent: true,showPic:true,
|
|
showMainSeq:false,seqName:'',isChecked:false,decimalPlaces:'',showDecimalPlaces:false,thousandsSeparator:false
|
|
};
|
|
this.elements = (data.data || []).map(item => {
|
|
const element = Object.assign({}, defaultElement, item);
|
|
// 为一维码元素确保有新属性和合理的毫米默认值
|
|
if (element.type === 'onecode') {
|
|
if (!element.barcodeType) element.barcodeType = 'CODE128';
|
|
if (element.showContent === undefined) element.showContent = true;
|
|
}
|
|
return element;
|
|
});
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('加载标签元素失败')
|
|
}
|
|
},
|
|
|
|
handleToolDragStart(type) {
|
|
// 工具栏拖拽开始,由画布处理
|
|
},
|
|
|
|
handleDrop(dropData) {
|
|
console.log('主设计器接收到拖拽数据:', dropData) // 调试信息
|
|
|
|
if (!this.labelNo) {
|
|
this.$alert('标签编号不可为空!', '错误', {
|
|
confirmButtonText: '确定'
|
|
})
|
|
return
|
|
}
|
|
|
|
const newElement = this.createNewElement(dropData)
|
|
console.log('创建的新元素:', newElement) // 调试信息
|
|
|
|
this.elements.push(newElement)
|
|
|
|
// 自动选中新添加的元素
|
|
this.selectedIndex = this.elements.length - 1
|
|
},
|
|
|
|
createNewElement({ type, x, y }) {
|
|
console.log('创建新元素:', { type, x, y }) // 调试信息
|
|
|
|
const baseElement = {
|
|
type,
|
|
x,
|
|
y,
|
|
data: '',
|
|
fontSize: 30,
|
|
bold: false,
|
|
newline: false,
|
|
lineRows: 2,
|
|
lineWidth: 200,
|
|
digits: 6,
|
|
step: 1,
|
|
}
|
|
|
|
// 根据类型设置默认尺寸和特殊属性
|
|
const sizeConfig = {
|
|
text: {
|
|
width: 100,
|
|
height: 30,
|
|
data: '',
|
|
isChecked:false,
|
|
},
|
|
onecode: {
|
|
width: 2, // 默认宽度2mm
|
|
height: 15, // 默认高度15mm
|
|
data: '',
|
|
barcodeType: 'CODE128', // 默认条码类型
|
|
showContent: true // 默认显示内容
|
|
},
|
|
qrcode: {
|
|
width: 10,
|
|
height: 10, // 默认尺寸10mm
|
|
data: ''
|
|
},
|
|
pic: {
|
|
width: 100,
|
|
height: 100
|
|
},
|
|
hLine: {
|
|
width: 400,
|
|
height: 3
|
|
},
|
|
vLine: {
|
|
width: 3,
|
|
height: 400
|
|
},
|
|
serialNumber: {
|
|
width: 120,
|
|
height: 30,
|
|
digits: 6,
|
|
step: 1,
|
|
data: '流水号', // 默认显示值
|
|
fontSize: 30,
|
|
showMainSeq: false,
|
|
}
|
|
}
|
|
|
|
const config = sizeConfig[type] || { width: 100, height: 30 }
|
|
|
|
const newElement = {
|
|
...baseElement,
|
|
...config
|
|
}
|
|
|
|
console.log('新元素配置:', newElement) // 调试信息
|
|
|
|
return newElement
|
|
},
|
|
|
|
handleElementSelect(index) {
|
|
this.selectedIndex = index
|
|
},
|
|
|
|
handleElementDrag({ index, x, y, boundaryStatus }) {
|
|
// 网格吸附处理
|
|
if (this.snapToGrid && this.gridSize > 0) {
|
|
x = Math.round(x / this.gridSize) * this.gridSize
|
|
y = Math.round(y / this.gridSize) * this.gridSize
|
|
}
|
|
|
|
// 更新元素位置
|
|
this.$set(this.elements[index], 'x', x)
|
|
this.$set(this.elements[index], 'y', y)
|
|
|
|
// 处理边界状态反馈
|
|
if (boundaryStatus) {
|
|
this.handleBoundaryFeedback(boundaryStatus, this.elements[index])
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 处理边界状态反馈
|
|
*/
|
|
handleBoundaryFeedback(boundaryStatus, element) {
|
|
// 只有在真正需要时才给出反馈
|
|
|
|
// 1. 严格模式元素触及边界时提示
|
|
if (boundaryStatus.clamped && this.isStrictElement(element.type)) {
|
|
const directions = []
|
|
if (boundaryStatus.atLeft) directions.push('左')
|
|
if (boundaryStatus.atRight) directions.push('右')
|
|
if (boundaryStatus.atTop) directions.push('上')
|
|
if (boundaryStatus.atBottom) directions.push('下')
|
|
|
|
if (directions.length > 0) {
|
|
// 使用防抖避免频繁提示
|
|
if (!this.debouncedBoundaryMessage) {
|
|
this.debouncedBoundaryMessage = debounce(() => {
|
|
this.$message({
|
|
message: `${this.getElementTypeName(element.type)}已到达${directions.join('、')}边界`,
|
|
type: 'info',
|
|
duration: 1500,
|
|
showClose: false
|
|
})
|
|
}, 1500) // 增加防抖时间
|
|
}
|
|
|
|
this.debouncedBoundaryMessage()
|
|
}
|
|
}
|
|
|
|
// 2. 元素真正超出安全范围时警告
|
|
if (boundaryStatus.partiallyVisible && !this.partialVisibilityWarned) {
|
|
this.$message({
|
|
message: '元素超出安全范围,可能影响打印效果',
|
|
type: 'warning',
|
|
duration: 3000
|
|
})
|
|
this.partialVisibilityWarned = true
|
|
|
|
// 5秒后重置警告状态
|
|
setTimeout(() => {
|
|
this.partialVisibilityWarned = false
|
|
}, 5000)
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 判断是否为严格模式元素
|
|
*/
|
|
isStrictElement(elementType) {
|
|
return ['onecode', 'qrcode'].includes(elementType)
|
|
},
|
|
|
|
/**
|
|
* 获取元素类型的中文名称
|
|
*/
|
|
getElementTypeName(elementType) {
|
|
const nameMap = {
|
|
text: '文本',
|
|
onecode: '一维码',
|
|
qrcode: '二维码',
|
|
pic: '图片',
|
|
hLine: '横线',
|
|
vLine: '竖线'
|
|
}
|
|
return nameMap[elementType] || '元素'
|
|
},
|
|
|
|
handleDeleteElement() {
|
|
if (this.selectedIndex >= 0) {
|
|
this.$confirm('确定要删除这个元素吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.elements.splice(this.selectedIndex, 1)
|
|
this.selectedIndex = -1
|
|
})
|
|
}
|
|
},
|
|
|
|
async handleSave() {
|
|
if (!this.labelNo) {
|
|
this.$alert('标签编号不可为空!', '错误', {
|
|
confirmButtonText: '确定'
|
|
})
|
|
return
|
|
}
|
|
|
|
const zplCode = this.zplGenerator.generate(this.elements)
|
|
const saveData = {
|
|
zplCode,
|
|
elements: this.elements,
|
|
reportId: this.labelNo,
|
|
// 新格式:使用纸张ID
|
|
paperId: this.selectedPaper,
|
|
// 兼容性:保留原有字段
|
|
paperSize: this.selectedPaper, // 现在存储的是纸张ID
|
|
paperOrientation: this.orientation,
|
|
dpi: this.selectedDPI,
|
|
// 添加画布尺寸信息(像素)
|
|
canvasWidth: this.currentCanvasSize.width,
|
|
canvasHeight: this.currentCanvasSize.height,
|
|
// 添加物理尺寸信息(毫米)
|
|
physicalWidthMm: this.currentCanvasSize.widthMm,
|
|
physicalHeightMm: this.currentCanvasSize.heightMm
|
|
}
|
|
|
|
try {
|
|
const { data } = await saveZplElements(saveData)
|
|
if (data.code === 0) {
|
|
this.$message.success('保存成功!')
|
|
|
|
// 输出保存的详细信息用于调试
|
|
console.log('标签保存成功:', {
|
|
reportId: this.labelNo,
|
|
paperId: this.selectedPaper,
|
|
orientation: this.orientation,
|
|
dpi: this.selectedDPI,
|
|
canvasSize: `${this.currentCanvasSize.width}×${this.currentCanvasSize.height}px`,
|
|
physicalSize: `${this.currentCanvasSize.widthMm}×${this.currentCanvasSize.heightMm}mm`
|
|
})
|
|
} else {
|
|
this.$message.error(data.msg)
|
|
}
|
|
} catch (error) {
|
|
console.error('保存失败:', error)
|
|
this.$message.error('保存失败')
|
|
}
|
|
},
|
|
|
|
handlePreview() {
|
|
// 预览逻辑由PropertyPanel处理
|
|
},
|
|
|
|
async handleDataSource(inData) {
|
|
const response = await getViewFieldsByLabelType({
|
|
labelType: this.labelSettings.labelType,
|
|
site: this.$store.state.user.site
|
|
});
|
|
|
|
if (response.data && response.data.code === 200) {
|
|
this.dataKeys = response.data.data.map(field => ({
|
|
...field,
|
|
fieldDescription: field.fieldDescription || ''
|
|
}));
|
|
this.currentElementText = inData.data || ''
|
|
this.sourceType = inData.type || 'text'
|
|
this.dataSourceVisible = true
|
|
} else {
|
|
this.$message.error(response.data.msg || '获取字段信息失败');
|
|
}
|
|
},
|
|
|
|
handleDataSourceConfirm(selectedKeys, sourceType) {
|
|
if (this.selectedElement) {
|
|
this.selectedElement.data = sourceType==='serialNumber'?selectedKeys.map(key => `#{${key}}`).join('+'):
|
|
selectedKeys.map(key => `#{${key}}`).join('')
|
|
}
|
|
},
|
|
|
|
handlePaperChange(paperId) {
|
|
this.selectedPaper = paperId
|
|
|
|
// 获取纸张信息
|
|
const paper = dynamicPaperConfig.getPaperById(paperId)
|
|
const paperName = paper ? paper.name : `纸张ID: ${paperId}`
|
|
|
|
// 获取新的画布尺寸信息
|
|
const newCanvasSize = this.currentCanvasSize
|
|
const orientationText = this.orientation === 'portrait' ? '纵向' : '横向'
|
|
|
|
this.$message({
|
|
message: `已切换到 ${paperName} (${orientationText}: ${newCanvasSize.width}×${newCanvasSize.height}px)`,
|
|
type: 'success',
|
|
duration: 6000
|
|
})
|
|
|
|
// 调试信息
|
|
console.log('纸张切换:', {
|
|
paperId,
|
|
paperName,
|
|
orientation: this.orientation,
|
|
canvasSize: newCanvasSize
|
|
})
|
|
},
|
|
|
|
|
|
|
|
handleOrientationChange(orientation) {
|
|
// 更新打印方向
|
|
this.orientation = orientation
|
|
|
|
// 获取新的画布尺寸信息
|
|
const newCanvasSize = this.currentCanvasSize
|
|
|
|
this.$message({
|
|
message: `已切换到${orientation === 'portrait' ? '纵向' : '横向'}打印 (${newCanvasSize.width}×${newCanvasSize.height}px)`,
|
|
type: 'success',
|
|
duration: 3000
|
|
})
|
|
|
|
// 调试信息
|
|
console.log('打印方向切换:', {
|
|
orientation,
|
|
paper: this.selectedPaper,
|
|
canvasSize: newCanvasSize
|
|
})
|
|
},
|
|
|
|
updateGridSize() {
|
|
// 根据纸张大小推荐网格尺寸
|
|
const recommendedSize = getRecommendedGridSize(this.selectedPaper)
|
|
if (this.gridSize === 20) { // 只在默认值时自动调整
|
|
this.gridSize = recommendedSize
|
|
}
|
|
},
|
|
|
|
handleDPIChange() {
|
|
console.log('handleDPIChange 被调用, DPI:', this.selectedDPI)
|
|
|
|
// 强制更新画布组件的 key,确保重新渲染
|
|
this.canvasUpdateKey++
|
|
|
|
// 立即更新相关计算
|
|
this.updateTransformers()
|
|
|
|
// 使用 $nextTick 确保 DOM 更新
|
|
this.$nextTick(() => {
|
|
const newCanvasSize = this.currentCanvasSize
|
|
|
|
console.log('DPI变更后的画布尺寸:', {
|
|
dpi: this.selectedDPI,
|
|
paper: this.selectedPaper,
|
|
orientation: this.orientation,
|
|
canvasSize: {
|
|
display: `${newCanvasSize.width}×${newCanvasSize.height}px`,
|
|
exact: `${newCanvasSize.exactWidth}×${newCanvasSize.exactHeight}px`,
|
|
physical: `${newCanvasSize.widthMm}×${newCanvasSize.heightMm}mm`
|
|
}
|
|
})
|
|
|
|
this.$message({
|
|
message: `DPI已切换到 ${this.selectedDPI} (精确尺寸: ${newCanvasSize.exactWidth}×${newCanvasSize.exactHeight}px)`,
|
|
type: 'success',
|
|
duration: 3000
|
|
})
|
|
})
|
|
},
|
|
|
|
updateContainerSize() {
|
|
// 动态检测画布容器尺寸
|
|
if (this.$refs.canvasContainer) {
|
|
const rect = this.$refs.canvasContainer.getBoundingClientRect()
|
|
this.containerSize = {
|
|
width: Math.max(rect.width - 40, 400), // 减去padding
|
|
height: Math.max(rect.height - 40, 300)
|
|
}
|
|
}
|
|
},
|
|
|
|
updateTransformers() {
|
|
// 更新坐标转换器和ZPL生成器的纸张ID
|
|
if (this.coordinateTransformer && this.coordinateTransformer.updatePaperId) {
|
|
this.coordinateTransformer.updatePaperId(this.selectedPaper)
|
|
}
|
|
if (this.zplGenerator && this.zplGenerator.updatePaperId) {
|
|
this.zplGenerator.updatePaperId(this.selectedPaper)
|
|
}
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// 初始化容器尺寸检测
|
|
this.updateContainerSize()
|
|
|
|
// 监听窗口大小变化
|
|
window.addEventListener('resize', this.updateContainerSize)
|
|
|
|
// 使用ResizeObserver监听容器尺寸变化(如果支持)
|
|
if (window.ResizeObserver && this.$refs.canvasContainer) {
|
|
this.resizeObserver = new ResizeObserver(() => {
|
|
this.updateContainerSize()
|
|
})
|
|
this.resizeObserver.observe(this.$refs.canvasContainer)
|
|
}
|
|
},
|
|
|
|
beforeDestroy() {
|
|
// 清理事件监听器
|
|
window.removeEventListener('resize', this.updateContainerSize)
|
|
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.disconnect()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.label-designer {
|
|
display: flex;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: #f5f7fa;
|
|
}
|
|
|
|
/* 主要内容区域 - 占据大部分空间 */
|
|
.main-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* 顶部工具栏区域 - 自适应高度 */
|
|
.top-toolbar {
|
|
flex-shrink: 0;
|
|
background: #fff;
|
|
border-bottom: 1px solid #e4e7ed;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* 画布区域 - 占据剩余空间 */
|
|
.canvas-area {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
background: #fafafa;
|
|
}
|
|
|
|
/* 画布信息栏 */
|
|
.canvas-info-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 20px;
|
|
background: #fff;
|
|
border-bottom: 1px solid #e4e7ed;
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
gap: 20px;
|
|
}
|
|
|
|
.canvas-info {
|
|
display: flex;
|
|
gap: 20px;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.info-item {
|
|
font-size: 13px;
|
|
color: #606266;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.info-item strong {
|
|
color: #303133;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.canvas-middle-controls {
|
|
display: flex;
|
|
gap: 30px;
|
|
align-items: center;
|
|
flex: 1;
|
|
justify-content: left;
|
|
}
|
|
|
|
.control-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.control-label {
|
|
font-size: 13px;
|
|
color: #606266;
|
|
font-weight: 500;
|
|
min-width: fit-content;
|
|
}
|
|
|
|
.grid-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
.grid-size-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.size-label {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
}
|
|
|
|
.canvas-controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* 画布容器 - 支持滚动的原始尺寸画布 */
|
|
.canvas-container {
|
|
flex: 1;
|
|
padding: 20px;
|
|
overflow: auto;
|
|
position: relative;
|
|
background: #fafafa;
|
|
|
|
/* 确保滚动条样式美观 */
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #c1c1c1 #f1f1f1;
|
|
}
|
|
|
|
.canvas-container::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
.canvas-container::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.canvas-container::-webkit-scrollbar-thumb {
|
|
background: #c1c1c1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.canvas-container::-webkit-scrollbar-thumb:hover {
|
|
background: #a8a8a8;
|
|
}
|
|
|
|
/* 右侧属性面板区域 - 固定宽度 */
|
|
.right-panel {
|
|
width: 400px;
|
|
flex-shrink: 0;
|
|
background: #fff;
|
|
border-left: 1px solid #e4e7ed;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* 响应式设计 - 小屏幕适配 */
|
|
@media (max-width: 1200px) {
|
|
.top-toolbar {
|
|
height: 100px;
|
|
}
|
|
|
|
.right-panel {
|
|
width: 300px;
|
|
}
|
|
|
|
.canvas-info {
|
|
gap: 15px;
|
|
}
|
|
|
|
.info-item {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.canvas-middle-controls {
|
|
gap: 20px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.top-toolbar {
|
|
height: 80px;
|
|
}
|
|
|
|
.right-panel {
|
|
width: 280px;
|
|
}
|
|
|
|
.canvas-info-bar {
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
align-items: flex-start;
|
|
padding: 10px 15px;
|
|
}
|
|
|
|
.canvas-info {
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.canvas-middle-controls {
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
gap: 15px;
|
|
width: 100%;
|
|
}
|
|
|
|
.grid-controls {
|
|
gap: 10px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.label-designer {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.main-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.right-panel {
|
|
width: 100%;
|
|
height: 300px;
|
|
border-left: none;
|
|
border-top: 1px solid #e4e7ed;
|
|
}
|
|
|
|
.top-toolbar {
|
|
height: 60px;
|
|
}
|
|
|
|
.canvas-middle-controls {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
}
|
|
|
|
.control-item {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.grid-controls {
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
</style>
|