5 changed files with 796 additions and 9 deletions
-
7src/utils/zplGenerator.js
-
62src/views/modules/labelSetting/LabelDesigner.vue
-
721src/views/modules/labelSetting/components/ElementCombinationDialog.vue
-
12src/views/modules/labelSetting/components/PropertyForm.vue
-
3src/views/modules/labelSetting/components/PropertyPanel.vue
@ -0,0 +1,721 @@ |
|||
<template> |
|||
<el-dialog |
|||
title="元素内容组合设置" |
|||
:visible="visible" |
|||
:close-on-click-modal="false" |
|||
width="900px" |
|||
top="3vh" |
|||
custom-class="element-combination-dialog" |
|||
:before-close="handleClose" |
|||
@close="handleClose" |
|||
> |
|||
<div class="combination-content"> |
|||
<!-- 目标元素信息 --> |
|||
<div class="target-element-info"> |
|||
<h4>目标元素:{{ targetElement.type === 'onecode' ? '一维码' : '二维码' }}</h4> |
|||
<p>当前数据:{{ targetElement.data || '(空)' }}</p> |
|||
</div> |
|||
|
|||
<!-- 预览区域 - 移到顶部 --> |
|||
<div class="preview-section"> |
|||
<h4 class="preview-title">预览结果</h4> |
|||
<div class="preview-area"> |
|||
<el-input |
|||
:value="previewResult" |
|||
readonly |
|||
placeholder="组合结果预览" |
|||
/> |
|||
</div> |
|||
</div> |
|||
|
|||
<el-divider></el-divider> |
|||
|
|||
<!-- 组合规则设置 --> |
|||
<div class="combination-rules"> |
|||
<el-form label-position="top"> |
|||
<!-- 顺序拼接模式 --> |
|||
<div class="sequence-mode"> |
|||
<div class="sequence-container"> |
|||
<!-- 已选择的元素列表 --> |
|||
<div class="selected-section"> |
|||
<h5 class="section-title">已选择的元素</h5> |
|||
<div class="selected-elements-container"> |
|||
<div v-if="selectedElements.length === 0" class="empty-state"> |
|||
<i class="el-icon-plus"></i> |
|||
<p>请从右侧选择要组合的元素</p> |
|||
</div> |
|||
<div |
|||
v-for="(element, index) in selectedElements" |
|||
:key="element.id || index" |
|||
class="selected-element-card" |
|||
> |
|||
<div class="element-info"> |
|||
<div class="element-order">{{ index + 1 }}</div> |
|||
<i :class="getElementIcon(element.type)" class="element-icon"></i> |
|||
<span class="element-name">{{ getElementDisplayName(element) }}</span> |
|||
</div> |
|||
<div class="element-actions"> |
|||
<el-button |
|||
size="mini" |
|||
icon="el-icon-arrow-up" |
|||
@click="moveElementUp(index)" |
|||
:disabled="index === 0" |
|||
class="action-btn up-btn" |
|||
title="上移" |
|||
></el-button> |
|||
<el-button |
|||
size="mini" |
|||
icon="el-icon-arrow-down" |
|||
@click="moveElementDown(index)" |
|||
:disabled="index === selectedElements.length - 1" |
|||
class="action-btn down-btn" |
|||
title="下移" |
|||
></el-button> |
|||
<el-button |
|||
size="mini" |
|||
icon="el-icon-delete" |
|||
@click="removeElement(element)" |
|||
class="action-btn remove-btn" |
|||
title="移除" |
|||
></el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 可用元素列表 --> |
|||
<div class="available-section"> |
|||
<h5 class="section-title">可用元素</h5> |
|||
<div class="available-elements-container"> |
|||
<div v-if="availableElements.length === 0" class="no-elements"> |
|||
<p>暂无可用元素</p> |
|||
</div> |
|||
<div |
|||
v-for="element in availableElements" |
|||
:key="element.id" |
|||
class="available-element-card" |
|||
@click="addElement(element)" |
|||
> |
|||
<i :class="getElementIcon(element.type)" class="element-icon"></i> |
|||
<span class="element-name">{{ getElementDisplayName(element) }}</span> |
|||
<i class="el-icon-plus add-icon"></i> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<el-form-item label="分隔符设置" class="separator-setting"> |
|||
<div class="separator-input-group"> |
|||
<el-input |
|||
v-model="separator" |
|||
placeholder="如:- 或 _ 或留空" |
|||
style="width: 200px;" |
|||
/> |
|||
<span class="separator-hint">元素间的分隔符</span> |
|||
</div> |
|||
</el-form-item> |
|||
</div> |
|||
</el-form> |
|||
</div> |
|||
</div> |
|||
|
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button @click="handleClose">取消</el-button> |
|||
<el-button type="primary" @click="handleConfirm">确定</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'ElementCombinationDialog', |
|||
props: { |
|||
visible: Boolean, |
|||
targetElement: { |
|||
type: Object, |
|||
default: () => ({}) |
|||
}, |
|||
allElements: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
dataKeys: { |
|||
type: Array, |
|||
default: () => [] |
|||
} |
|||
}, |
|||
emits: ['update:visible', 'confirm'], |
|||
data() { |
|||
return { |
|||
selectedElements: [], |
|||
separator: '-' |
|||
} |
|||
}, |
|||
computed: { |
|||
availableElements() { |
|||
// 定义可用于组合的元素类型 |
|||
const combinableTypes = ['text', 'onecode', 'qrcode', 'serialNumber'] |
|||
|
|||
return this.allElements.filter(element => |
|||
element !== this.targetElement && |
|||
!this.selectedElements.find(selected => selected.id === element.id) && |
|||
combinableTypes.includes(element.type) |
|||
) |
|||
}, |
|||
previewResult() { |
|||
try { |
|||
return this.generateSequencePreview() |
|||
} catch (error) { |
|||
return '预览错误:' + error.message |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
visible(newVal) { |
|||
if (newVal) { |
|||
this.initializeDialog() |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
initializeDialog() { |
|||
// 重置为默认状态 |
|||
this.resetToDefaults() |
|||
}, |
|||
|
|||
resetToDefaults() { |
|||
this.selectedElements = [] |
|||
this.separator = '-' |
|||
}, |
|||
|
|||
addElement(element) { |
|||
// 为元素添加唯一ID(如果没有的话) |
|||
const elementWithId = { |
|||
...element, |
|||
id: element.id || `element_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` |
|||
} |
|||
this.selectedElements.push(elementWithId) |
|||
}, |
|||
|
|||
removeElement(element) { |
|||
const index = this.selectedElements.findIndex(e => e.id === element.id) |
|||
if (index > -1) { |
|||
this.selectedElements.splice(index, 1) |
|||
} |
|||
}, |
|||
|
|||
|
|||
|
|||
// 上移元素 |
|||
moveElementUp(index) { |
|||
if (index > 0) { |
|||
const element = this.selectedElements[index] |
|||
this.selectedElements.splice(index, 1) |
|||
this.selectedElements.splice(index - 1, 0, element) |
|||
} |
|||
}, |
|||
|
|||
// 下移元素 |
|||
moveElementDown(index) { |
|||
if (index < this.selectedElements.length - 1) { |
|||
const element = this.selectedElements[index] |
|||
this.selectedElements.splice(index, 1) |
|||
this.selectedElements.splice(index + 1, 0, element) |
|||
} |
|||
}, |
|||
|
|||
getElementDisplayName(element) { |
|||
const typeNames = { |
|||
text: '文本', |
|||
onecode: '一维码', |
|||
qrcode: '二维码', |
|||
pic: '图片', |
|||
serialNumber: '流水号' |
|||
} |
|||
const typeName = typeNames[element.type] || element.type |
|||
const content = element.data ? `(${element.data.substring(0, 40)}${element.data.length > 40 ? '...' : ''})` : '' |
|||
return `${typeName}${content}` |
|||
}, |
|||
|
|||
// 获取用于后端匹配的完整元素名称(不截断内容) |
|||
getElementFullName(element) { |
|||
const typeNames = { |
|||
text: '文本', |
|||
onecode: '一维码', |
|||
qrcode: '二维码', |
|||
pic: '图片', |
|||
serialNumber: '流水号' |
|||
} |
|||
const typeName = typeNames[element.type] || element.type |
|||
const content = element.data ? `(${element.data})` : '' |
|||
return `${typeName}${content}` |
|||
}, |
|||
|
|||
getElementIcon(type) { |
|||
const icons = { |
|||
text: 'el-icon-edit', |
|||
onecode: 'el-icon-menu', |
|||
qrcode: 'el-icon-menu', |
|||
pic: 'el-icon-picture', |
|||
serialNumber: 'el-icon-sort' |
|||
} |
|||
return icons[type] || 'el-icon-document' |
|||
}, |
|||
|
|||
|
|||
|
|||
generateSequencePreview() { |
|||
if (!this.selectedElements.length) return '' |
|||
|
|||
const parts = this.selectedElements.map(element => |
|||
element.data || `[${this.getElementDisplayName(element)}]` |
|||
) |
|||
|
|||
return parts.join(this.separator || '') |
|||
}, |
|||
|
|||
|
|||
|
|||
handleClose() { |
|||
this.$emit('update:visible', false) |
|||
}, |
|||
|
|||
handleConfirm() { |
|||
const combinationData = this.generateSequenceFormat() |
|||
|
|||
const combinationConfig = { |
|||
mode: 'sequence', |
|||
data: combinationData, |
|||
selectedElements: this.selectedElements.map(e => e.id), |
|||
separator: this.separator |
|||
} |
|||
|
|||
this.$emit('confirm', combinationConfig) |
|||
this.handleClose() |
|||
}, |
|||
|
|||
generateSequenceFormat() { |
|||
// 将顺序拼接转换为模板格式 |
|||
const parts = this.selectedElements.map(element => { |
|||
// 使用完整的元素名称,确保后端能精确匹配 |
|||
return `{${this.getElementFullName(element)}}` |
|||
}) |
|||
return parts.join(this.separator || '') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.element-combination-dialog { |
|||
border-radius: 12px; |
|||
} |
|||
|
|||
.combination-content { |
|||
max-height: calc(85vh - 120px); |
|||
overflow-y: auto; |
|||
padding-right: 5px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.target-element-info { |
|||
background: #f5f7fa; |
|||
padding: 15px; |
|||
border-radius: 8px; |
|||
border: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.target-element-info h4 { |
|||
margin: 0 0 8px 0; |
|||
color: #409eff; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.target-element-info p { |
|||
margin: 0; |
|||
color: #666; |
|||
font-size: 14px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
/* 预览区域样式 */ |
|||
.preview-section { |
|||
background: #f8f9fa; |
|||
border: 1px solid #e9ecef; |
|||
border-radius: 6px; |
|||
padding: 12px; |
|||
margin: 12px 0; |
|||
} |
|||
|
|||
.preview-title { |
|||
margin: 0 0 8px 0; |
|||
color: #495057; |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.combination-rules h4 { |
|||
margin: 0 0 15px 0; |
|||
color: #333; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
|
|||
|
|||
/* 顺序拼接模式样式 */ |
|||
.sequence-mode { |
|||
margin-top: 15px; |
|||
} |
|||
|
|||
.sequence-container { |
|||
display: flex; |
|||
gap: 20px; |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.selected-section, |
|||
.available-section { |
|||
flex: 1; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.section-title { |
|||
margin: 0 0 12px 0; |
|||
font-size: 14px; |
|||
font-weight: 600; |
|||
color: #333; |
|||
padding-bottom: 8px; |
|||
border-bottom: 2px solid #e4e7ed; |
|||
} |
|||
|
|||
/* 已选择元素区域 */ |
|||
.selected-elements-container { |
|||
border: 2px dashed #d9ecff; |
|||
border-radius: 8px; |
|||
min-height: 230px; |
|||
max-height: 250px; |
|||
padding: 12px; |
|||
background: #f8fbff; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.empty-state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 126px; |
|||
color: #999; |
|||
text-align: center; |
|||
} |
|||
|
|||
.empty-state i { |
|||
font-size: 32px; |
|||
margin-bottom: 8px; |
|||
color: #d3d4d6; |
|||
} |
|||
|
|||
.empty-state p { |
|||
margin: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.selected-element-card { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
background: #fff; |
|||
border: 1px solid #e1f3d8; |
|||
border-radius: 6px; |
|||
padding: 12px; |
|||
margin-bottom: 8px; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.selected-element-card:hover { |
|||
border-color: #95de64; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
|||
} |
|||
|
|||
.element-info { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
} |
|||
|
|||
.element-order { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 24px; |
|||
height: 24px; |
|||
background: #52c41a; |
|||
color: #fff; |
|||
border-radius: 50%; |
|||
font-size: 12px; |
|||
font-weight: bold; |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.element-icon { |
|||
margin-right: 8px; |
|||
color: #52c41a; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.element-name { |
|||
font-size: 14px; |
|||
color: #333; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.element-actions { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.action-btn { |
|||
padding: 4px; |
|||
min-width: 24px; |
|||
height: 24px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.up-btn { |
|||
color: #409eff; |
|||
border-color: #409eff; |
|||
background: #fff; |
|||
} |
|||
|
|||
.up-btn:hover:not(:disabled) { |
|||
background: #409eff; |
|||
color: #fff; |
|||
} |
|||
|
|||
.down-btn { |
|||
color: #409eff; |
|||
border-color: #409eff; |
|||
background: #fff; |
|||
} |
|||
|
|||
.down-btn:hover:not(:disabled) { |
|||
background: #409eff; |
|||
color: #fff; |
|||
} |
|||
|
|||
.remove-btn { |
|||
color: #f56c6c; |
|||
border-color: #f56c6c; |
|||
background: #fff; |
|||
} |
|||
|
|||
.remove-btn:hover { |
|||
background: #f56c6c; |
|||
color: #fff; |
|||
} |
|||
|
|||
.action-btn:disabled { |
|||
color: #c0c4cc; |
|||
border-color: #e4e7ed; |
|||
background: #f5f7fa; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
/* 可用元素区域 */ |
|||
.available-elements-container { |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 8px; |
|||
min-height: 150px; |
|||
max-height: 230px; |
|||
overflow-y: auto; |
|||
padding: 12px; |
|||
background: #fafafa; |
|||
} |
|||
|
|||
.no-elements { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 126px; |
|||
color: #999; |
|||
text-align: center; |
|||
} |
|||
|
|||
.no-elements p { |
|||
margin: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.available-element-card { |
|||
display: flex; |
|||
align-items: center; |
|||
background: #fff; |
|||
border: 1px solid #e4e7ed; |
|||
border-radius: 6px; |
|||
padding: 10px 12px; |
|||
margin-bottom: 8px; |
|||
cursor: pointer; |
|||
transition: all 0.3s ease; |
|||
position: relative; |
|||
} |
|||
|
|||
.available-element-card:hover { |
|||
background: #e6f7ff; |
|||
border-color: #91d5ff; |
|||
transform: translateX(4px); |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); |
|||
} |
|||
|
|||
.available-element-card .element-icon { |
|||
margin-right: 8px; |
|||
color: #409eff; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.available-element-card .element-name { |
|||
flex: 1; |
|||
font-size: 14px; |
|||
color: #333; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.add-icon { |
|||
color: #52c41a; |
|||
font-size: 16px; |
|||
opacity: 0; |
|||
transition: opacity 0.3s ease; |
|||
} |
|||
|
|||
.available-element-card:hover .add-icon { |
|||
opacity: 1; |
|||
} |
|||
|
|||
/* 分隔符设置 */ |
|||
.separator-setting { |
|||
margin-top: 20px; |
|||
padding-top: 20px; |
|||
border-top: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.separator-input-group { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.separator-hint { |
|||
color: #666; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.preview-area { |
|||
background: #fff; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.preview-area .el-input { |
|||
background: #fff; |
|||
} |
|||
|
|||
.preview-area .el-input__inner { |
|||
border: 1px solid #ddd; |
|||
background: #fff; |
|||
color: #333; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.dialog-footer { |
|||
text-align: right; |
|||
padding-top: 15px; |
|||
border-top: 1px solid #e4e7ed; |
|||
} |
|||
|
|||
.dialog-footer .el-button { |
|||
margin-left: 10px; |
|||
padding: 8px 20px; |
|||
} |
|||
|
|||
|
|||
|
|||
/* 表单项间距优化 */ |
|||
.el-form-item { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.el-form-item:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
/* 滚动条样式 */ |
|||
.combination-content::-webkit-scrollbar, |
|||
.available-elements::-webkit-scrollbar { |
|||
width: 6px; |
|||
} |
|||
|
|||
.combination-content::-webkit-scrollbar-track, |
|||
.available-elements::-webkit-scrollbar-track { |
|||
background: #f1f1f1; |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.combination-content::-webkit-scrollbar-thumb, |
|||
.available-elements::-webkit-scrollbar-thumb { |
|||
background: #c1c1c1; |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.combination-content::-webkit-scrollbar-thumb:hover, |
|||
.available-elements::-webkit-scrollbar-thumb:hover { |
|||
background: #a8a8a8; |
|||
} |
|||
|
|||
/* 响应式设计 */ |
|||
@media (max-width: 768px) { |
|||
.sequence-container { |
|||
flex-direction: column; |
|||
gap: 15px; |
|||
} |
|||
|
|||
.selected-elements-container, |
|||
.available-elements-container { |
|||
min-height: 120px; |
|||
max-height: 150px; |
|||
} |
|||
|
|||
.selected-element-card { |
|||
padding: 8px 10px; |
|||
} |
|||
|
|||
.element-order { |
|||
width: 20px; |
|||
height: 20px; |
|||
font-size: 11px; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.element-name { |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.action-btn { |
|||
padding: 2px; |
|||
min-width: 20px; |
|||
height: 20px; |
|||
} |
|||
|
|||
.available-element-card { |
|||
padding: 8px 10px; |
|||
} |
|||
|
|||
.separator-input-group { |
|||
flex-direction: column; |
|||
align-items: flex-start; |
|||
gap: 8px; |
|||
} |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue