572 lines
14 KiB
Vue
572 lines
14 KiB
Vue
<template>
|
||
<view class="container">
|
||
<!-- 状态栏 -->
|
||
<view class="status-section">
|
||
<view class="status-item">
|
||
<text class="status-label">打印机</text>
|
||
<text class="status-value" :class="{ connected: connected }">
|
||
{{ connected ? '已连接' : '未连接' }}
|
||
</text>
|
||
</view>
|
||
<view class="status-item" v-if="connected">
|
||
<text class="status-label">设备名称</text>
|
||
<text class="status-value device-name">{{ printerConfig.deviceName }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="section action-section" v-if="!connected">
|
||
<button class="btn btn-primary" :loading="isConnecting" @click="handleConnect">
|
||
{{ isConnecting ? '连接中...' : '重新连接' }}
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 打印控制区 -->
|
||
<view class="section print-section" v-if="connected">
|
||
<view class="section-header">
|
||
<text class="section-title">打印测试</text>
|
||
</view>
|
||
|
||
<!-- 指令类型选择 -->
|
||
<view class="command-type">
|
||
<text class="type-label">指令类型:</text>
|
||
<view class="type-switch">
|
||
<view
|
||
class="type-btn"
|
||
:class="{ active: commandType === 'CPCL' }"
|
||
@click="selectCommandType('CPCL')">
|
||
CPCL
|
||
</view>
|
||
<view
|
||
class="type-btn"
|
||
:class="{ active: commandType === 'TSPL' }"
|
||
@click="selectCommandType('TSPL')">
|
||
TSPL
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 纸尺寸选择 -->
|
||
<view class="paper-size-section">
|
||
<text class="paper-size-label">打印纸尺寸</text>
|
||
<view class="paper-size-grid">
|
||
<view
|
||
class="paper-size-item"
|
||
:class="{ selected: paperWidth === 100 && paperHeight === 100 }"
|
||
@click="selectPaperSize(100, 100)">
|
||
<text class="paper-size-name">100mm x 100mm</text>
|
||
</view>
|
||
<view
|
||
class="paper-size-item"
|
||
:class="{ selected: paperWidth === 150 && paperHeight === 150 }"
|
||
@click="selectPaperSize(150, 150)">
|
||
<text class="paper-size-name">150mm x 150mm</text>
|
||
</view>
|
||
<view
|
||
class="paper-size-item"
|
||
:class="{ selected: paperWidth === 200 && paperHeight === 200 }"
|
||
@click="selectPaperSize(200, 200)">
|
||
<text class="paper-size-name">200mm x 200mm</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 打印内容 -->
|
||
<view class="print-content">
|
||
<view class="content-header">
|
||
<text class="content-title">打印内容</text>
|
||
<view class="content-actions">
|
||
<button size="mini" @click="clearContent">清空</button>
|
||
<button size="mini" type="primary" :loading="isPrinting" @click="doPrint">打印</button>
|
||
</view>
|
||
</view>
|
||
<textarea
|
||
class="content-textarea"
|
||
v-model="printContent"
|
||
:placeholder="contentPlaceholder">
|
||
</textarea>
|
||
</view>
|
||
|
||
<!-- 快捷模板 -->
|
||
<view class="template-section">
|
||
<text class="template-title">快捷模板</text>
|
||
<scroll-view class="template-list" scroll-x>
|
||
<view class="template-tags">
|
||
<view class="template-tag" @click="useTemplate('qrcode')">二维码</view>
|
||
<view class="template-tag" @click="useTemplate('barcode')">条码</view>
|
||
<view class="template-tag" @click="useTemplate('label')">废料标签</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 断开连接 -->
|
||
<view class="section" v-if="connected">
|
||
<button class="btn btn-outline" @click="handleDisconnect">断开连接</button>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import printerService from '@/utils/BluetoothPrinterService.new.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
isConnecting: false,
|
||
isPrinting: false,
|
||
connected: false,
|
||
connectingText: '正在检查配置...',
|
||
printerConfig: {},
|
||
commandType: 'CPCL',
|
||
paperWidth: 100,
|
||
paperHeight: 100,
|
||
printContent: ''
|
||
}
|
||
},
|
||
computed: {
|
||
contentPlaceholder() {
|
||
const sizeCmd = this.commandType === 'CPCL'
|
||
? `SIZE ${this.paperWidth}mm ${this.paperHeight}mm`
|
||
: `SIZE ${this.paperWidth} mm ${this.paperHeight} mm`
|
||
|
||
// TSPL 分支保持原样
|
||
if (this.commandType !== 'CPCL') {
|
||
return `输入TSPL指令,如:\n${sizeCmd}\nGAP 2 mm\nCLS\nTEXT 50 50 "4" 0 1 1 "Hello"\nPRINT`
|
||
}
|
||
|
||
// CPCL:200x200 用大标签配置,其余尺寸用默认小标签配置
|
||
const isLargeLabel = this.paperWidth === 200 && this.paperHeight === 200
|
||
const cpclHeader = isLargeLabel ? '! 0 200 200 1600 1' : '! 0 200 200 240 1'
|
||
const cpclGap = isLargeLabel ? 'GAP-SENSE' : 'GAP 2mm'
|
||
|
||
return `输入CPCL指令,如:\n${cpclHeader}\n${sizeCmd}\n${cpclGap}\nTEXT 4 0 10 10 EN Hello\nPRINT`
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.loadPrinterConfig()
|
||
this.syncConfigFromService()
|
||
this.checkAndConnect()
|
||
},
|
||
onShow() {
|
||
if (!this.connected && !this.isConnecting) {
|
||
this.checkAndConnect()
|
||
}
|
||
},
|
||
onUnload() {
|
||
this.handleDisconnect()
|
||
},
|
||
mounted() {
|
||
printerService.onConnectionChange(this.handleConnectionChange)
|
||
},
|
||
beforeDestroy() {
|
||
printerService.offConnectionChange(this.handleConnectionChange)
|
||
},
|
||
methods: {
|
||
handleConnectionChange(connected) {
|
||
this.connected = connected
|
||
if (connected) {
|
||
this.loadPrinterConfig()
|
||
this.syncConfigFromService()
|
||
}
|
||
},
|
||
/**
|
||
* 从 printerService 同步配置
|
||
*/
|
||
syncConfigFromService() {
|
||
const serviceConfig = printerService.getConfig()
|
||
if (serviceConfig.commandType) {
|
||
this.commandType = serviceConfig.commandType
|
||
}
|
||
if (serviceConfig.paperWidth && serviceConfig.paperHeight) {
|
||
this.paperWidth = serviceConfig.paperWidth
|
||
this.paperHeight = serviceConfig.paperHeight
|
||
}
|
||
},
|
||
loadPrinterConfig() {
|
||
const storeState = uni.getStorageSync('printerConfig') || {}
|
||
const serviceConfig = printerService.getConfig()
|
||
|
||
this.printerConfig = {
|
||
deviceId: serviceConfig.deviceId || storeState.deviceId || '',
|
||
serviceId: serviceConfig.serviceId || storeState.serviceId || '',
|
||
writeCharId: serviceConfig.writeCharId || storeState.writeCharId || '',
|
||
deviceName: serviceConfig.deviceName || storeState.deviceName || ''
|
||
}
|
||
},
|
||
/**
|
||
* 检查配置并自动连接
|
||
*/
|
||
async checkAndConnect() {
|
||
this.loadPrinterConfig()
|
||
|
||
if (!this.printerConfig.deviceId) {
|
||
this.connectingText = '未配置打印机,请先配置设备'
|
||
return
|
||
}
|
||
|
||
if (!this.printerConfig.serviceId || !this.printerConfig.writeCharId) {
|
||
this.connectingText = '配置不完整,请重新配置设备'
|
||
return
|
||
}
|
||
|
||
if (printerService.isConnected()) {
|
||
this.connected = true
|
||
return
|
||
}
|
||
|
||
this.isConnecting = true
|
||
this.connectingText = '正在连接...'
|
||
|
||
try {
|
||
const success = await printerService.autoConnect()
|
||
this.connected = success
|
||
|
||
if (success) {
|
||
this.connectingText = '连接成功'
|
||
this.syncConfigFromService()
|
||
} else {
|
||
this.connectingText = '连接失败,请检查设备是否开启'
|
||
}
|
||
} catch (err) {
|
||
this.connectingText = '连接异常: ' + (err.message || '未知错误')
|
||
} finally {
|
||
this.isConnecting = false
|
||
}
|
||
},
|
||
/**
|
||
* 手动连接
|
||
*/
|
||
async handleConnect() {
|
||
if (this.isConnecting) return
|
||
|
||
this.isConnecting = true
|
||
this.connectingText = '正在连接...'
|
||
this.loadPrinterConfig()
|
||
|
||
if (!this.printerConfig.deviceId) {
|
||
uni.showToast({ title: '请先配置打印机设备', icon: 'none' })
|
||
this.connectingText = '未配置打印机'
|
||
this.isConnecting = false
|
||
return
|
||
}
|
||
|
||
try {
|
||
const success = await printerService.autoConnect()
|
||
this.connected = success
|
||
|
||
if (success) {
|
||
uni.showToast({ title: '连接成功', icon: 'none' })
|
||
this.syncConfigFromService()
|
||
} else {
|
||
if (!this.printerConfig.serviceId || !this.printerConfig.writeCharId) {
|
||
uni.showToast({ title: '配置不完整,请重新配置', icon: 'none' })
|
||
this.connectingText = '配置不完整'
|
||
} else {
|
||
uni.showToast({ title: '连接失败,请检查设备', icon: 'none' })
|
||
this.connectingText = '连接失败'
|
||
}
|
||
}
|
||
} catch (err) {
|
||
uni.showToast({ title: '连接异常', icon: 'none' })
|
||
this.connectingText = '连接异常'
|
||
} finally {
|
||
this.isConnecting = false
|
||
}
|
||
},
|
||
/**
|
||
* 断开连接
|
||
*/
|
||
async handleDisconnect() {
|
||
if (!this.connected && !this.deviceId) return
|
||
|
||
try {
|
||
await printerService.disconnect()
|
||
this.connected = false
|
||
this.connectingText = '已断开连接'
|
||
} catch (err) {
|
||
this.connected = false
|
||
}
|
||
},
|
||
/**
|
||
* 选择指令类型
|
||
*/
|
||
selectCommandType(type) {
|
||
this.commandType = type
|
||
printerService.setCommandType(type)
|
||
// 切换指令类型时同步更新纸张尺寸
|
||
printerService.setPaperSize(this.paperWidth, this.paperHeight)
|
||
},
|
||
/**
|
||
* 选择打印纸尺寸
|
||
*/
|
||
selectPaperSize(width, height) {
|
||
this.paperWidth = width
|
||
this.paperHeight = height
|
||
printerService.setPaperSize(width, height)
|
||
},
|
||
/**
|
||
* 快捷模板
|
||
*/
|
||
useTemplate(type) {
|
||
if (type === 'label') {
|
||
const testData = {
|
||
mingcheng: '测试废物',
|
||
leibie: '危险废物',
|
||
daima: 'HW49',
|
||
xingtai: '液态',
|
||
zhuyaochengfen: '测试成分',
|
||
youhaichengfen: '无',
|
||
zhuyi: '防潮',
|
||
shibiema: 'TEST' + Date.now(),
|
||
danwei: '测试单位',
|
||
lianxi: '13800138000',
|
||
riqi: new Date().toISOString().split('T')[0],
|
||
zhongliang: '10kg',
|
||
beizhu: '测试备注',
|
||
biaoshi: '',
|
||
qrcode: 'https://www.cswm.org.cn/qr/?code=7G6MOYELnFHZfF1Y0F8e6gjxR2K2pALNJJNhJSuOg17LV6IZfuDWUnF2qC3Rsdejs&type=WFBQ'
|
||
}
|
||
// 确保 service 使用当前的 commandType 和 paperSize
|
||
printerService.setCommandType(this.commandType)
|
||
printerService.setPaperSize(this.paperWidth, this.paperHeight)
|
||
this.printContent = printerService.generateWasteLabel(testData)
|
||
return
|
||
}
|
||
|
||
if (type === 'qrcode') {
|
||
printerService.setCommandType(this.commandType)
|
||
printerService.setPaperSize(this.paperWidth, this.paperHeight)
|
||
this.printContent = printerService.generateQRCodeTemplate('https://www.example.com')
|
||
return
|
||
}
|
||
|
||
if (type === 'barcode') {
|
||
printerService.setCommandType(this.commandType)
|
||
printerService.setPaperSize(this.paperWidth, this.paperHeight)
|
||
this.printContent = printerService.generateBarcodeTemplate('1234567890')
|
||
return
|
||
}
|
||
},
|
||
/**
|
||
* 清空打印内容
|
||
*/
|
||
clearContent() {
|
||
this.printContent = ''
|
||
},
|
||
/**
|
||
* 执行打印
|
||
*/
|
||
async doPrint() {
|
||
if (this.isPrinting) return
|
||
if (!this.printContent.trim()) {
|
||
uni.showToast({ title: '请输入打印内容', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
this.isPrinting = true
|
||
try {
|
||
// 确保打印前同步配置
|
||
printerService.setCommandType(this.commandType)
|
||
printerService.setPaperSize(this.paperWidth, this.paperHeight)
|
||
await printerService.printText(this.printContent)
|
||
uni.showToast({ title: '打印成功', icon: 'success' })
|
||
} catch (err) {
|
||
uni.showToast({ title: '打印失败: ' + (err.message || err || '未知错误'), icon: 'none' })
|
||
} finally {
|
||
this.isPrinting = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
padding: 20rpx;
|
||
background: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
.status-section {
|
||
display: flex;
|
||
background: #fff;
|
||
padding: 30rpx;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.status-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.status-label {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.status-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #666;
|
||
}
|
||
.status-value.connected {
|
||
color: #34c759;
|
||
}
|
||
.device-name {
|
||
font-size: 26rpx;
|
||
color: #34c759;
|
||
max-width: 300rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.section {
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
.section-title {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.action-section button {
|
||
width: 100%;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.action-section button:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
/* 指令类型 */
|
||
.command-type {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
.type-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-right: 16rpx;
|
||
}
|
||
.type-switch {
|
||
display: flex;
|
||
border: 1rpx solid #007aff;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
.type-btn {
|
||
padding: 12rpx 24rpx;
|
||
font-size: 24rpx;
|
||
color: #007aff;
|
||
background: #fff;
|
||
}
|
||
.type-btn.active {
|
||
background: #007aff;
|
||
color: #fff;
|
||
}
|
||
/* 纸尺寸 */
|
||
.paper-size-section {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.paper-size-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
display: block;
|
||
}
|
||
.paper-size-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
}
|
||
.paper-size-item {
|
||
flex: 1;
|
||
min-width: 200rpx;
|
||
padding: 20rpx;
|
||
background: #f0f0f0;
|
||
border-radius: 8rpx;
|
||
border: 2rpx solid transparent;
|
||
text-align: center;
|
||
}
|
||
.paper-size-item.selected {
|
||
background: #e8f4ff;
|
||
border-color: #007aff;
|
||
}
|
||
.paper-size-name {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
}
|
||
.paper-size-item.selected .paper-size-name {
|
||
color: #007aff;
|
||
font-weight: bold;
|
||
}
|
||
/* 打印内容 */
|
||
.print-section {
|
||
min-height: 400rpx;
|
||
}
|
||
.print-content {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.content-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.content-title {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
.content-actions {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
}
|
||
.content-textarea {
|
||
width: 100%;
|
||
height: 280rpx;
|
||
border: 1rpx solid #ddd;
|
||
border-radius: 8rpx;
|
||
padding: 16rpx;
|
||
font-size: 24rpx;
|
||
font-family: 'Courier New', monospace;
|
||
box-sizing: border-box;
|
||
}
|
||
/* 快捷模板 */
|
||
.template-section {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.template-title {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
display: block;
|
||
}
|
||
.template-list {
|
||
width: 100%;
|
||
}
|
||
.template-tags {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
padding: 4rpx;
|
||
}
|
||
.template-tag {
|
||
padding: 12rpx 24rpx;
|
||
background: #f0f0f0;
|
||
border-radius: 8rpx;
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
}
|
||
.template-tag:active {
|
||
background: #e0e0e0;
|
||
}
|
||
</style>
|