Browse Source

选择系统字体

master
han\hanst 7 months ago
parent
commit
d7e2b6d0b8
  1. 2
      src/api/labelSetting/label_setting.js
  2. 192
      src/utils/zplGenerator.js
  3. 34
      src/views/modules/labelSetting/LabelDesigner.vue
  4. 11
      src/views/modules/labelSetting/components/DataSourceDialog.vue
  5. 2
      src/views/modules/labelSetting/components/ElementCombinationDialog.vue
  6. 315
      src/views/modules/labelSetting/components/PropertyForm.vue
  7. 20
      src/views/modules/labelSetting/components/PropertyPanel.vue
  8. 4
      src/views/modules/labelSetting/components/ZPLPreview.vue

2
src/api/labelSetting/label_setting.js

@ -30,3 +30,5 @@ export const exportPreviewToPdf = (data) => {
export const exportRealDataPreviewToPdf = (data) => {
return createAPI('/label/setting/exportRealDataPreviewToPdf', 'post', data, 'download')
}
export const availableFont = data => createAPI('/font/available','post',data)

192
src/utils/zplGenerator.js

@ -119,15 +119,23 @@ export class ZPLGenerator {
*/
generateTextZPL(element, x, y) {
const zpl = []
// 设置中文字体
zpl.push(`^CI28 ^CWJ,E:MSYH.TTF ^CFJ,${element.fontSize}`)
// 生成字体命令
const fontCommand = this.generateFontCommand(element)
zpl.push(fontCommand)
if (element.isChecked) {
zpl.push(`^FO${x-60},${y}^GB45,45,2,B,0^FS ^FO${x-50},${y+10}^AJN,35,35^FD√^FS `)
}
const lineWidth = Math.max(1, Math.round((element.lineWidth || 200) * this.dpi / 25.4))
// 处理文本对齐
const alignmentParam = this.getTextAlignmentParam(element.textAlign)
// 基础文本
const textCommand = element.newline
? `^FO${x},${y}^FB${lineWidth},${element.lineRows},0^CFJ,${element.fontSize}^FD${element.data}^FS`
? `^FO${x},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
: `^FO${x},${y}^FD${element.data}^FS`
zpl.push(textCommand)
@ -135,9 +143,9 @@ export class ZPLGenerator {
// 加粗效果(通过偏移重复打印实现)
if (element.bold) {
const boldCommands = element.newline ? [
`^FO${x + 1},${y}^FB${lineWidth},${element.lineRows},0^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x},${y + 1}^FB${lineWidth},${element.lineRows},0^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x + 1},${y + 1}^FB${lineWidth},${element.lineRows},0^CFJ,${element.fontSize}^FD${element.data}^FS`
`^FO${x + 1},${y}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`,
`^FO${x + 1},${y + 1}^FB${lineWidth},${element.lineRows},0,${alignmentParam}^CFJ,${element.fontSize}^FD${element.data}^FS`
] : [
`^FO${x + 1},${y}^FD${element.data}^FS`,
`^FO${x},${y + 1}^FD${element.data}^FS`,
@ -147,9 +155,163 @@ export class ZPLGenerator {
zpl.push(...boldCommands)
}
// 下划线效果(通过线条实现)
if (element.fontUnderline) {
const textWidth = this.estimateTextWidth(element.data, element.fontSize)
const underlineY = y + element.fontSize + 2
zpl.push(`^FO${x},${underlineY}^GB${textWidth},2,2,B^FS`)
}
return zpl.join('\n')
}
/**
* 生成字体命令
*/
generateFontCommand(element) {
let fontCmd = '^CI28' // 设置字符编码
// 根据字体族选择字体文件
const fontFamily = element.fontFamily
if (fontFamily && fontFamily !== 'default') {
const fontFile = this.mapFontFamilyToFile(fontFamily)
if (fontFile) {
fontCmd += ` ^CWJ,E:${fontFile}`
console.log(`ZPL字体映射: ${fontFamily} -> ${fontFile}`)
} else {
fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
console.warn(`未找到字体映射: ${fontFamily},使用默认字体`)
}
} else {
fontCmd += ' ^CWJ,E:MSYH.TTF' // 默认字体
console.log('使用默认字体: MSYH.TTF')
}
// 设置字体大小
const fontSize = element.fontSize || 30
fontCmd += ` ^CFJ,${fontSize}`
// 添加调试信息
console.log(`生成字体命令: ${fontCmd}`)
console.log(`字体族: ${fontFamily}, 字体大小: ${fontSize}`)
return fontCmd
}
/**
* 映射字体族到字体文件
*/
mapFontFamilyToFile(fontFamily) {
const fontMapping = {
// 中文字体
'Microsoft YaHei': 'MSYH.TTF',
'微软雅黑': 'MSYH.TTF',
'SimSun': 'SIMSUN.TTC',
'宋体': 'SIMSUN.TTC',
'SimHei': 'SIMHEI.TTF',
'黑体': 'SIMHEI.TTF',
'KaiTi': 'KAITI.TTF',
'楷体': 'KAITI.TTF',
'FangSong': 'SIMFANG.TTF',
'仿宋': 'SIMFANG.TTF',
'Microsoft JhengHei': 'MSJH.TTF',
'微軟正黑體': 'MSJH.TTF',
'PMingLiU': 'PMINGLIU.TTF',
'新細明體': 'PMINGLIU.TTF',
'DFKai-SB': 'DFKAI.TTF',
'標楷體': 'DFKAI.TTF',
// 英文字体
'Arial': 'ARIAL.TTF',
'Times New Roman': 'TIMES.TTF',
'Courier New': 'COUR.TTF',
'Helvetica': 'HELV.TTF',
'Verdana': 'VERDANA.TTF',
'Georgia': 'GEORGIA.TTF',
'Tahoma': 'TAHOMA.TTF',
'Trebuchet MS': 'TREBUC.TTF',
'Lucida Console': 'LUCON.TTF',
'Impact': 'IMPACT.TTF',
'Comic Sans MS': 'COMIC.TTF',
'Palatino': 'PALA.TTF',
'Garamond': 'GARA.TTF',
'Bookman': 'BOOKOS.TTF',
'Century Gothic': 'CENTURY.TTF',
'Franklin Gothic': 'FRANKLIN.TTF',
'Gill Sans': 'GILLSANS.TTF',
'Optima': 'OPTIMA.TTF',
'Futura': 'FUTURA.TTF',
'Bodoni': 'BODONI.TTF',
'Didot': 'DIDOT.TTF',
// 等宽字体
'Consolas': 'CONSOLAS.TTF',
'Monaco': 'MONACO.TTF',
'Menlo': 'MENLO.TTF',
'Source Code Pro': 'SOURCECODE.TTF',
'Fira Code': 'FIRACODE.TTF',
'JetBrains Mono': 'JETBRAINS.TTF',
// 装饰字体
'Brush Script MT': 'BRUSHSCRIPT.TTF',
'Papyrus': 'PAPYRUS.TTF',
'Chalkboard': 'CHALKBOARD.TTF',
'Marker Felt': 'MARKERFELT.TTF',
'Zapfino': 'ZAPFINO.TTF',
'Snell Roundhand': 'SNELLROUND.TTF',
// 默认字体
'default': 'MSYH.TTF',
'Default': 'MSYH.TTF'
}
// 如果找不到精确匹配,尝试模糊匹配
if (!fontMapping[fontFamily]) {
const lowerFontFamily = fontFamily.toLowerCase()
for (const [key, value] of Object.entries(fontMapping)) {
if (key.toLowerCase().includes(lowerFontFamily) || lowerFontFamily.includes(key.toLowerCase())) {
return value
}
}
}
return fontMapping[fontFamily] || 'MSYH.TTF' // 默认返回微软雅黑
}
/**
* 获取文本对齐参数
*/
getTextAlignmentParam(textAlign) {
switch (textAlign) {
case 'center': return 'C'
case 'right': return 'R'
case 'left':
default: return 'L'
}
}
/**
* 估算文本宽度用于下划线
*/
estimateTextWidth(text, fontSize) {
if (!text) return 0
// 简单估算:中文字符按字体大小计算,英文字符按字体大小的0.6倍计算
let chineseCount = 0
let englishCount = 0
for (let i = 0; i < text.length; i++) {
const char = text.charAt(i)
if (char.match(/[\u4e00-\u9fff]/)) {
chineseCount++
} else {
englishCount++
}
}
return Math.round(chineseCount * fontSize + englishCount * fontSize * 0.6)
}
/**
* 生成一维码ZPL代码
*/
@ -274,18 +436,22 @@ export class ZPLGenerator {
*/
generateSerialNumberZPL(element, x, y) {
const zpl = []
// 设置中文字体
zpl.push(`^CI28 ^CWJ,E:MSYH.TTF ^CFJ,${element.fontSize}`)
const bold = element.bold || false // 加粗(可选)
const serialStr = '流水号'
// 生成字体命令
const fontCommand = this.generateFontCommand(element)
zpl.push(fontCommand)
const bold = element.bold || false
const base = `^FO${x},${y}^FD${element.data}^FS`
zpl.push(base)
// 可选:加粗效果(重复打印)
if (bold) {
zpl.push(`^FO${x + 1},${y}^FD${serialStr}^FS`)
zpl.push(`^FO${x},${y + 1}^FD${serialStr}^FS`)
zpl.push(`^FO${x + 1},${y + 1}^FD${serialStr}^FS`)
zpl.push(`^FO${x + 1},${y}^FD${element.data}^FS`)
zpl.push(`^FO${x},${y + 1}^FD${element.data}^FS`)
zpl.push(`^FO${x + 1},${y + 1}^FD${element.data}^FS`)
}
return zpl.join('\n')
}
}

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

@ -126,6 +126,7 @@
@preview="handlePreview"
@data-source="handleDataSource"
@element-combination="handleElementCombination"
@font-changed="handleFontChanged"
/>
</div>
@ -448,8 +449,10 @@ export default {
//
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,showElement:true,
showMainSeq:false,seqName:'',isChecked:false,decimalPlaces:'',showDecimalPlaces:false,thousandsSeparator:false
lineWidth: 200, digits: 6, step: 1, width: 100, height: 30, previewUrl: '', barcodeType: '', showContent: true, showElement: true,
showMainSeq: false, seqName: '', isChecked: false, decimalPlaces: '', showDecimalPlaces: false, thousandsSeparator: false,
//
fontFamily: 'default', textAlign: 'left', letterSpacing: 0, fontItalic: false, fontUnderline: false
};
this.elements = (data.data || []).map(item => {
const element = Object.assign({}, defaultElement, item);
@ -512,7 +515,13 @@ export default {
width: 100,
height: 30,
data: '',
isChecked:false,
isChecked: false,
//
fontFamily: 'default',
textAlign: 'left',
letterSpacing: 0,
fontItalic: false,
fontUnderline: false,
},
onecode: {
width: 2, // 2mm
@ -899,6 +908,25 @@ export default {
if (this.zplGenerator && this.zplGenerator.updatePaperId) {
this.zplGenerator.updatePaperId(this.selectedPaper)
}
},
//
handleFontChanged(fontData) {
console.log('LabelDesigner收到字体变化事件:', fontData)
if (fontData && fontData.elementId && this.selectedElement && this.selectedElement.id === fontData.elementId) {
//
this.$set(this.selectedElement, 'fontFamily', fontData.fontFamily)
console.log('字体已更新:', this.selectedElement.fontFamily)
//
this.canvasUpdateKey++
// ZPL
this.$nextTick(() => {
this.$emit('font-updated', fontData)
})
}
}
},

11
src/views/modules/labelSetting/components/DataSourceDialog.vue

@ -85,17 +85,6 @@ export default {
foundKeys.push(key.fieldName)
}
})
if (this.sourceType==='serialNumber'){
this.dataKeys.forEach(key => {
//
const pattern = new RegExp(`\\b${key.fieldName}\\b`, 'g')
if (pattern.test(this.currentText)) {
foundKeys.push(key.fieldName)
}
})
}
this.selectedKeys = foundKeys
},

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

@ -11,7 +11,7 @@
>
<div class="combination-content">
<!-- 目标元素信息 -->
<div class="target-element-info">
<div class="target-element-info" v-if="targetElement">
<h4>目标元素{{ targetElement.type === 'onecode' ? '一维码' : '二维码' }}</h4>
<p>当前数据{{ targetElement.data || '(空)' }}</p>
</div>

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

@ -39,6 +39,50 @@
</div>
</el-form-item>
<el-form-item >
<div class="form-row">
<el-form-item label="字体" class="form-item-half">
<el-select
v-model="element.fontFamily"
size="mini"
style="width: 100%;"
placeholder="选择字体"
filterable
:loading="fontLoading"
@focus="loadAvailableFonts"
@change="onFontFamilyChange"
>
<el-option-group
v-for="group in groupedFonts"
:key="group.category"
:label="group.label"
>
<el-option
v-for="font in group.fonts"
:key="font.value"
:label="font.name"
:value="font.value"
:disabled="!font.supported"
>
<span style="float: left">{{ font.name }}</span>
<span style="float: right; color: #8492a6; font-size: 11px">
{{ font.description }}
<i v-if="!font.supported" class="el-icon-warning" style="color: #f56c6c;" title="不支持ZPL打印"></i>
</span>
</el-option>
</el-option-group>
</el-select>
</el-form-item>
<el-form-item label="文本对齐" class="form-item-half">
<el-select v-model="element.textAlign" size="mini" style="width: 100%;" placeholder="对齐方式">
<el-option label="左对齐" value="left" />
<el-option label="居中" value="center" />
<el-option label="右对齐" value="right" />
</el-select>
</el-form-item>
</div>
</el-form-item>
<el-form-item label="字体大小">
<div class="font-size-row">
<el-input
@ -54,6 +98,25 @@
</div>
</el-form-item>
<!-- <el-form-item label="字体样式">
<div class="font-style-row">
<el-checkbox v-model="element.fontItalic" size="small" class="inline-checkbox">斜体</el-checkbox>
<el-checkbox v-model="element.fontUnderline" size="small" class="inline-checkbox">下划线</el-checkbox>
<div class="spacing-controls">
<span class="spacing-label">字符间距:</span>
<el-input
v-model="element.letterSpacing"
:min="0"
:max="20"
controls-position="right"
size="mini"
class="spacing-input"
placeholder="0"
/>
</div>
</div>
</el-form-item>-->
<template v-if="element.newline">
<div class="form-row">
<el-form-item label="文本宽度(mm)" class="form-item-half">
@ -249,7 +312,14 @@
<el-form label-position="top" size="small">
<el-form-item label="数据内容">
<div class="input-with-button">
<el-input v-model="element.data" placeholder="请输入一维码数据" />
<el-input
v-model="element.data"
placeholder="请输入一维码数据"
type="textarea"
:rows="2"
maxlength="1000"
show-word-limit
/>
<el-button type="primary" size="mini" @click="$emit('data-source', element)">
数据源
</el-button>
@ -480,6 +550,7 @@
<script>
import comShowLabelSerialInfo from "../com_show_label_serial_info";
import { availableFont } from '@/api/labelSetting/label_setting.js'
export default {
name: 'PropertyForm',
@ -490,6 +561,42 @@ export default {
}
},
emits: ['data-source', 'image-upload', 'element-combination'],
data() {
return {
availableFonts: [],
fontLoading: false,
fontsLoaded: false
}
},
computed: {
groupedFonts() {
if (!this.availableFonts.length) {
return []
}
const groups = {}
this.availableFonts.forEach(font => {
const category = font.category || 'other'
if (!groups[category]) {
groups[category] = {
category: category,
label: this.getCategoryLabel(category),
fonts: []
}
}
groups[category].fonts.push(font)
})
//
const sortedGroups = Object.values(groups).sort((a, b) => {
const order = ['system', 'chinese', 'english', 'monospace', 'decorative', 'other']
return order.indexOf(a.category) - order.indexOf(b.category)
})
return sortedGroups
}
},
/*组件*/
components: {
comShowLabelSerialInfo,/*标签内容流水号信息的組件*/
@ -499,6 +606,9 @@ export default {
if (this.element.type === 'text' && !this.element.dataType) {
this.$set(this.element, 'dataType', 'text')
}
//
this.initializeFontSettings()
},
watch: {
//
@ -663,7 +773,145 @@ export default {
const paddedNumber = startValue.toString().padStart(digits, '0')
return `${prefix}${paddedNumber}`
}
},
//
initializeFontSettings() {
if (this.element.type === 'text') {
//
if (!this.element.fontFamily) {
this.$set(this.element, 'fontFamily', 'default')
}
if (!this.element.textAlign) {
this.$set(this.element, 'textAlign', 'left')
}
if (this.element.letterSpacing === undefined) {
this.$set(this.element, 'letterSpacing', 0)
}
if (this.element.fontItalic === undefined) {
this.$set(this.element, 'fontItalic', false)
}
if (this.element.fontUnderline === undefined) {
this.$set(this.element, 'fontUnderline', false)
}
}
},
//
getFontPreviewStyle() {
if (this.element.type !== 'text') return {}
const style = {}
//
if (this.element.fontFamily && this.element.fontFamily !== 'default') {
style.fontFamily = this.element.fontFamily
}
//
if (this.element.fontSize) {
style.fontSize = this.element.fontSize + 'px'
}
//
if (this.element.bold) {
style.fontWeight = 'bold'
}
//
if (this.element.fontItalic) {
style.fontStyle = 'italic'
}
// 线
if (this.element.fontUnderline) {
style.textDecoration = 'underline'
}
//
if (this.element.textAlign) {
style.textAlign = this.element.textAlign
}
//
if (this.element.letterSpacing) {
style.letterSpacing = this.element.letterSpacing + 'px'
}
return style
},
//
async loadAvailableFonts() {
if (this.fontsLoaded || this.fontLoading) {
return
}
this.fontLoading = true
try {
const response = await availableFont({})
if (response.data.success) {
this.availableFonts = response.data.data || []
this.fontsLoaded = true
console.log('加载字体列表成功,共', this.availableFonts.length, '个字体')
} else {
console.error('加载字体列表失败:', response.data.message)
this.$message.warning('加载字体列表失败,使用默认字体')
this.availableFonts = this.getDefaultFonts()
}
} catch (error) {
console.error('加载字体列表异常:', error)
this.$message.error('加载字体列表异常')
this.availableFonts = this.getDefaultFonts()
} finally {
this.fontLoading = false
}
},
//
getCategoryLabel(category) {
const labels = {
'system': '系统字体',
'chinese': '中文字体',
'english': '英文字体',
'monospace': '等宽字体',
'decorative': '装饰字体',
'other': '其他字体'
}
return labels[category] || '其他字体'
},
//
getDefaultFonts() {
return [
{ name: '默认字体', value: 'default', category: 'system', description: '系统默认', supported: true },
{ name: '微软雅黑', value: 'Microsoft YaHei', category: 'chinese', description: '中文字体', supported: true },
{ name: '宋体', value: 'SimSun', category: 'chinese', description: '中文字体', supported: true },
{ name: '黑体', value: 'SimHei', category: 'chinese', description: '中文字体', supported: true },
{ name: 'Arial', value: 'Arial', category: 'english', description: '英文字体', supported: true },
{ name: 'Times New Roman', value: 'Times New Roman', category: 'english', description: '英文字体', supported: true },
{ name: 'Courier New', value: 'Courier New', category: 'monospace', description: '等宽字体', supported: true }
]
},
//
onFontFamilyChange(value) {
console.log('字体族变化:', value)
console.log('当前元素:', this.element)
//
this.$set(this.element, 'fontFamily', value)
//
this.$emit('font-changed', {
elementId: this.element.id,
fontFamily: value
})
console.log('字体设置完成,当前字体族:', this.element.fontFamily)
},
}
}
</script>
@ -958,6 +1206,69 @@ export default {
padding-left: 4px !important;
}
/* 字体样式行布局 */
.font-style-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.font-style-row .inline-checkbox {
margin-right: 0 !important;
}
.spacing-controls {
display: flex;
align-items: center;
gap: 6px;
}
.spacing-label {
font-size: 12px;
color: #606266;
white-space: nowrap;
}
.spacing-input {
width: 80px !important;
}
.spacing-input .el-input__inner {
height: 28px !important;
font-size: 12px !important;
padding: 0 30px 0 8px !important;
}
/* 字体预览样式 */
.font-preview {
background: #f8f9fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 8px;
margin-top: 8px;
text-align: center;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.font-preview-text {
font-size: 16px;
color: #333;
transition: all 0.3s ease;
}
.font-debug-info {
margin-top: 4px;
text-align: center;
font-size: 11px;
line-height: 1.2;
opacity: 0.8;
}
.form-item-half .el-radio__inner {
width: 12px !important;
height: 12px !important;

20
src/views/modules/labelSetting/components/PropertyPanel.vue

@ -14,14 +14,14 @@
</div>
<!-- 属性表单 -->
<div class="property-form">
<PropertyForm
:element="selectedElement"
@data-source="(currentText) => $emit('data-source', currentText)"
@element-combination="(element) => $emit('element-combination', element)"
@image-upload="handleImageUpload"
/>
</div>
<PropertyForm
v-if="selectedElement"
:element="selectedElement"
@data-source="$emit('data-source', $event)"
@image-upload="handleImageUpload"
@element-combination="$emit('element-combination', $event)"
@font-changed="handleFontChanged"
/>
<!-- 操作按钮 -->
<div class="action-buttons">
@ -97,6 +97,10 @@ export default {
callback.success()
}
}, 1000)
},
handleFontChanged(font) {
this.$emit('font-changed', font)
}
}
}

4
src/views/modules/labelSetting/components/ZPLPreview.vue

@ -191,7 +191,9 @@ export default {
},
watch: {
zplCode: {
handler: 'debouncedPreview',
handler(newVal) {
this.debouncedPreview(newVal)
},
immediate: false
},
//

Loading…
Cancel
Save