Browse Source

元素组合

clodop
han\hanst 1 month ago
parent
commit
18599e1887
  1. 41
      src/utils/elementCombination.js
  2. 3
      src/views/modules/labelSetting/LabelDesigner.vue
  3. 146
      src/views/modules/labelSetting/components/ElementCombinationDialog.vue
  4. 3
      src/views/modules/labelSetting/components/PropertyForm.vue

41
src/utils/elementCombination.js

@ -0,0 +1,41 @@
const ELEMENT_TYPES = {
'文本': 'text',
'一维码': 'onecode',
'二维码': 'qrcode',
'图片': 'pic',
'流水号': 'serialNumber'
}
// 按配对花括号读取引用,保留 #{字段} 和嵌套组合,不用简单的 /{[^}]+}/ 截断内容。
export function parseElementCombination (data) {
const value = data == null ? '' : String(data)
const references = []
for (let start = 0; start < value.length; start++) {
if (value[start] !== '{') continue
let depth = 1
let end = start + 1
for (; end < value.length && depth > 0; end++) {
if (value[end] === '{') depth++
if (value[end] === '}') depth--
}
if (depth > 0) break
if (start === 0 || value[start - 1] !== '#') {
const reference = value.slice(start + 1, end - 1)
const match = /^(文本|一维码|二维码|图片|流水号)(?:\(([\s\S]*)\))?$/.exec(reference)
if (match) {
references.push({start, end, reference, type: ELEMENT_TYPES[match[1]], data: match[2]})
}
}
start = end - 1
}
return references
}
// 编辑已有组合时替换原组合区间;首次组合才追加,前后固定文字/字段保持不变。
export function applyElementCombination (data, combinationData) {
const value = data == null ? '' : String(data)
const references = parseElementCombination(value)
if (!references.length) return value + combinationData
return value.slice(0, references[0].start) + combinationData + value.slice(references[references.length - 1].end)
}

3
src/views/modules/labelSetting/LabelDesigner.vue

@ -166,6 +166,7 @@ import ElementCombinationDialog from './components/ElementCombinationDialog.vue'
import PaperSelector from './components/PaperSelector.vue' import PaperSelector from './components/PaperSelector.vue'
import { CoordinateTransformer } from '@/utils/coordinateTransform.js' import { CoordinateTransformer } from '@/utils/coordinateTransform.js'
import { ZPLGenerator } from '@/utils/zplGenerator.js' import { ZPLGenerator } from '@/utils/zplGenerator.js'
import { applyElementCombination } from '@/utils/elementCombination'
import { getCanvasSize, getRecommendedGridSize } from '@/utils/paperConfig.js' import { getCanvasSize, getRecommendedGridSize } from '@/utils/paperConfig.js'
import { saveZplElements, getZplElements } from '@/api/labelSetting/label_setting.js' import { saveZplElements, getZplElements } from '@/api/labelSetting/label_setting.js'
import {getViewFieldsByLabelType} from '@/api/labelSetting/com_add_update_label.js'; import {getViewFieldsByLabelType} from '@/api/labelSetting/com_add_update_label.js';
@ -853,7 +854,7 @@ export default {
this.currentCombinationElement.data = combinationConfig.data; this.currentCombinationElement.data = combinationConfig.data;
break; break;
case 'sequence': case 'sequence':
this.currentCombinationElement.data = this.currentCombinationElement.data + combinationConfig.data;
this.currentCombinationElement.data = applyElementCombination(this.currentCombinationElement.data, combinationConfig.data);
break; break;
case 'custom': case 'custom':
// 对于自定义模式,我们需要在后端处理时执行表达式 // 对于自定义模式,我们需要在后端处理时执行表达式

146
src/views/modules/labelSetting/components/ElementCombinationDialog.vue

@ -12,7 +12,7 @@
<div class="combination-content"> <div class="combination-content">
<!-- 目标元素信息 --> <!-- 目标元素信息 -->
<div class="target-element-info" v-if="targetElement"> <div class="target-element-info" v-if="targetElement">
<h4>{{ $t('labelSetting.combination.targetElement', { type: targetElement.type === 'onecode' ? $t('labelSetting.combination.elementBarcode') : $t('labelSetting.combination.elementQrcode') }) }}</h4>
<h4>{{ $t('labelSetting.combination.targetElement', { type: getElementTypeName(targetElement.type) }) }}</h4>
<p>{{ $t('labelSetting.combination.currentData', { data: targetElement.data || $t('labelSetting.combination.empty') }) }}</p> <p>{{ $t('labelSetting.combination.currentData', { data: targetElement.data || $t('labelSetting.combination.empty') }) }}</p>
</div> </div>
@ -46,7 +46,7 @@
</div> </div>
<div <div
v-for="(element, index) in selectedElements" v-for="(element, index) in selectedElements"
:key="element.id || index"
:key="index"
class="selected-element-card" class="selected-element-card"
> >
<div class="element-info"> <div class="element-info">
@ -74,7 +74,7 @@
<el-button <el-button
size="mini" size="mini"
icon="el-icon-delete" icon="el-icon-delete"
@click="removeElement(element)"
@click="removeElement(index)"
class="action-btn remove-btn" class="action-btn remove-btn"
:title="$t('labelSetting.combination.remove')" :title="$t('labelSetting.combination.remove')"
></el-button> ></el-button>
@ -127,6 +127,10 @@
</template> </template>
<script> <script>
import { parseElementCombination } from '@/utils/elementCombination'
let nextElementId = 0
export default { export default {
name: 'ElementCombinationDialog', name: 'ElementCombinationDialog',
props: { props: {
@ -148,7 +152,10 @@ export default {
data() { data() {
return { return {
selectedElements: [], selectedElements: [],
separator: '-'
separator: '-',
originalReferences: [],
originalCombinationData: '',
originalSeparator: '-'
} }
}, },
computed: { computed: {
@ -171,41 +178,81 @@ export default {
} }
}, },
watch: { watch: {
visible(newVal) {
if (newVal) {
this.initializeDialog()
visible: {
immediate: true,
handler(newVal) {
if (newVal) {
this.initializeDialog()
}
} }
} }
}, },
methods: { methods: {
initializeDialog() { initializeDialog() {
// 重置为默认状态
this.resetToDefaults() this.resetToDefaults()
this.allElements.forEach(element => this.ensureElementId(element))
const data = (this.targetElement && this.targetElement.data) || ''
const references = parseElementCombination(data)
if (!references.length) return
this.originalReferences = references.map(item => item.reference)
this.originalCombinationData = data.slice(references[0].start, references[references.length - 1].end)
const config = this.targetElement.combinationConfig
// 元素内容可能被手工修改,只有仍对应当前组合的缓存配置才可用于恢复身份和分隔符。
const savedConfig = config && config.mode === 'sequence' && config.data === this.originalCombinationData
? config : null
this.selectedElements = references.map((reference, index) => {
const savedId = savedConfig && savedConfig.selectedElements && savedConfig.selectedElements[index]
const candidates = this.allElements.filter(element => element !== this.targetElement && element.type === reference.type)
const source = (savedId != null && candidates.find(element => element.id === savedId)) ||
candidates.find(element => this.getElementFullName(element) === reference.reference) ||
(reference.data === undefined && candidates[0])
// 来源已删除时保留原引用,避免打开后直接确定就悄悄丢失内容。
return source || {
id: `missing_${index}`,
type: reference.type,
data: reference.data || '',
combinationReference: reference.reference
}
})
if (savedConfig && savedConfig.separator != null) {
this.separator = String(savedConfig.separator)
} else if (references.length > 1) {
this.separator = data.slice(references[0].end, references[1].start)
}
this.originalSeparator = this.separator
}, },
resetToDefaults() { resetToDefaults() {
this.selectedElements = [] this.selectedElements = []
this.separator = '-' this.separator = '-'
this.originalReferences = []
this.originalCombinationData = ''
this.originalSeparator = '-'
}, },
addElement(element) {
// 为元素添加唯一ID(如果没有的话)
const elementWithId = {
...element,
id: element.id || `element_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
ensureElementId(element) {
if (element.id == null) {
// ID 放在画布原元素上,不能只给选中项的副本分配 ID。
this.$set(element, 'id', `element_${Date.now()}_${++nextElementId}`)
} }
this.selectedElements.push(elementWithId)
}, },
removeElement(element) {
const index = this.selectedElements.findIndex(e => e.id === element.id)
if (index > -1) {
addElement(element) {
if (element === this.targetElement || this.selectedElements.some(selected => selected === element)) return
this.ensureElementId(element)
if (this.selectedElements.some(selected => selected.id === element.id)) return
this.selectedElements.push(element)
},
removeElement(index) {
if (index >= 0 && index < this.selectedElements.length) {
this.selectedElements.splice(index, 1) this.selectedElements.splice(index, 1)
} }
}, },
// 上移元素 // 上移元素
moveElementUp(index) { moveElementUp(index) {
if (index > 0) { if (index > 0) {
@ -224,7 +271,7 @@ export default {
} }
}, },
getElementDisplayName(element) {
getElementTypeName(type) {
const typeNames = { const typeNames = {
text: this.$t('labelSetting.combination.elementText'), text: this.$t('labelSetting.combination.elementText'),
onecode: this.$t('labelSetting.combination.elementBarcode'), onecode: this.$t('labelSetting.combination.elementBarcode'),
@ -232,13 +279,18 @@ export default {
pic: this.$t('labelSetting.combination.elementImage'), pic: this.$t('labelSetting.combination.elementImage'),
serialNumber: this.$t('labelSetting.combination.elementSerial') serialNumber: this.$t('labelSetting.combination.elementSerial')
} }
const typeName = typeNames[element.type] || element.type
return typeNames[type] || type
},
getElementDisplayName(element) {
const typeName = this.getElementTypeName(element.type)
const content = element.data ? `(${element.data.substring(0, 40)}${element.data.length > 40 ? '...' : ''})` : '' const content = element.data ? `(${element.data.substring(0, 40)}${element.data.length > 40 ? '...' : ''})` : ''
return `${typeName}${content}` return `${typeName}${content}`
}, },
// 获取用于后端匹配的完整元素名称(不截断内容) // 获取用于后端匹配的完整元素名称(不截断内容)
getElementFullName(element) { getElementFullName(element) {
if (element.combinationReference) return element.combinationReference
const typeNames = { const typeNames = {
text: '文本', text: '文本',
onecode: '一维码', onecode: '一维码',
@ -267,15 +319,46 @@ export default {
generateSequencePreview() { generateSequencePreview() {
if (!this.selectedElements.length) return '' if (!this.selectedElements.length) return ''
const parts = this.selectedElements.map(element =>
element.data || `[${this.getElementDisplayName(element)}]`
)
const parts = this.selectedElements.map(element => this.getElementPreviewData(element))
return parts.join(this.separator || '') return parts.join(this.separator || '')
}, },
getElementPreviewData(element) {
const value = element.data == null ? '' : String(element.data)
if (!value) return `[${this.getElementDisplayName(element)}]`
// 动态字段需要真实数据才能加工;这里保留模板,不能截断 #{字段名} 本身。
if (element.type !== 'text' || element.dataType !== 'string' || /#\{[^}]+\}/.test(value)) {
return value
}
switch (element.stringProcessType) {
case 'substring_start': {
const length = Number(element.substringStartLength)
return length > 0 ? value.substring(0, length) : value
}
case 'substring_end': {
const length = Number(element.substringEndLength)
return length > 0 ? value.substring(Math.max(0, value.length - length)) : value
}
case 'split': {
const index = Number(element.splitIndex) - 1
if (!element.splitCharacter || !Number.isInteger(index) || index < 0) return value
const parts = value.split(element.splitCharacter)
return index < parts.length ? parts[index] : ''
}
case 'replace':
return element.replaceFrom
? value.split(element.replaceFrom).join(element.replaceTo == null ? '' : String(element.replaceTo))
: value
default:
return value
}
},
handleClose() { handleClose() {
this.$emit('update:visible', false) this.$emit('update:visible', false)
}, },
@ -295,12 +378,15 @@ export default {
}, },
generateSequenceFormat() { generateSequenceFormat() {
// 将顺序拼接转换为模板格式
const parts = this.selectedElements.map(element => {
// 使用完整的元素名称,确保后端能精确匹配
return `{${this.getElementFullName(element)}}`
})
return parts.join(this.separator || '')
const references = this.selectedElements.map(element => this.getElementFullName(element))
// 未改动时保持原组合字面内容(含手工插入的不同间隔文字),确认不会重写或重复追加。
if (this.originalReferences && this.originalReferences.length > 0 &&
this.separator === this.originalSeparator &&
references.length === this.originalReferences.length &&
references.every((reference, index) => reference === this.originalReferences[index])) {
return this.originalCombinationData
}
return references.map(reference => `{${reference}}`).join(this.separator || '')
} }
} }
} }

3
src/views/modules/labelSetting/components/PropertyForm.vue

@ -17,6 +17,9 @@
<el-button type="primary" size="mini" @click="$emit('data-source', element)"> <el-button type="primary" size="mini" @click="$emit('data-source', element)">
{{ $t('labelSetting.property.dataSource') }} {{ $t('labelSetting.property.dataSource') }}
</el-button> </el-button>
<el-button type="success" size="mini" @click="$emit('element-combination', element)">
{{ $t('labelSetting.property.elementCombination') }}
</el-button>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('labelSetting.property.hiddenRule')"> <el-form-item :label="$t('labelSetting.property.hiddenRule')">

Loading…
Cancel
Save