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

<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>