0602
This commit is contained in:
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 状态栏 -->
|
||||
<view class="status-section">
|
||||
<view class="status-item">
|
||||
<text class="status-label">蓝牙适配器</text>
|
||||
<text class="status-value" :class="adapterState.available ? 'available' : ''">
|
||||
{{ adapterState.available ? '就绪' : '未就绪' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="status-label">打印机</text>
|
||||
<text class="status-value" :class="{ connected: connected }">
|
||||
{{ connected ? '已连接' : '未连接' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="status-item">
|
||||
<text class="status-label">连接方式</text>
|
||||
<text class="status-value auto">自动</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 连接信息 -->
|
||||
<view class="section" v-if="savedConfig">
|
||||
<view class="section-header">
|
||||
<text class="section-title">已保存的配置</text>
|
||||
</view>
|
||||
<view class="config-info">
|
||||
<view class="config-row">
|
||||
<text class="config-label">设备名称</text>
|
||||
<text class="config-value">{{ savedConfig.deviceName }}</text>
|
||||
</view>
|
||||
<view class="config-row">
|
||||
<text class="config-label">服务UUID</text>
|
||||
<text class="config-value uuid">{{ savedConfig.serviceId }}</text>
|
||||
</view>
|
||||
<view class="config-row">
|
||||
<text class="config-label">特征值UUID</text>
|
||||
<text class="config-value uuid">{{ savedConfig.writeCharId }}</text>
|
||||
</view>
|
||||
<view class="config-row" v-if="savedConfig.lastUpdated">
|
||||
<text class="config-label">保存时间</text>
|
||||
<text class="config-value">{{ formatDate(savedConfig.lastUpdated) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="config-actions">
|
||||
<button type="warn" size="mini" @click="clearConfig">清除配置</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无配置提示 -->
|
||||
<view class="section empty-section" v-else>
|
||||
<text class="empty-text">暂无保存的配置</text>
|
||||
<text class="empty-hint">请先在蓝牙打印页面连接打印机并保存配置</text>
|
||||
<button type="primary" size="mini" @click="goToBlePrint">前往配置</button>
|
||||
</view>
|
||||
|
||||
<!-- 打印控制区 -->
|
||||
<view class="section print-section" v-if="connected">
|
||||
<view class="section-header">
|
||||
<text class="section-title">打印控制</text>
|
||||
<text class="printer-name">{{ connectedDeviceName }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 指令类型选择 -->
|
||||
<view class="command-type">
|
||||
<view class="type-label">指令类型:</view>
|
||||
<view class="type-switch">
|
||||
<view
|
||||
class="type-btn"
|
||||
:class="{ active: commandType === 'CPCL' }"
|
||||
@click="commandType = 'CPCL'">
|
||||
CPCL
|
||||
</view>
|
||||
<view
|
||||
class="type-btn"
|
||||
:class="{ active: commandType === 'TSPL' }"
|
||||
@click="commandType = 'TSPL'">
|
||||
TSPL
|
||||
</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" @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 connecting-section" v-else-if="savedConfig && isConnecting">
|
||||
<view class="connecting-content">
|
||||
<view class="loading-icon"></view>
|
||||
<text class="connecting-text">正在自动连接...</text>
|
||||
<text class="connecting-hint">{{ savedConfig.deviceName }}</text>
|
||||
</view>
|
||||
<button type="default" size="mini" @click="cancelConnect">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as iconv from 'iconv-lite';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
adapterState: {
|
||||
available: false,
|
||||
discovering: false
|
||||
},
|
||||
isConnecting: false,
|
||||
savedConfig: null,
|
||||
connectedDeviceId: '',
|
||||
connectedDeviceName: '',
|
||||
connected: false,
|
||||
writeServiceId: '',
|
||||
writeCharacteristicId: '',
|
||||
|
||||
commandType: 'CPCL',
|
||||
printContent: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
contentPlaceholder() {
|
||||
const placeholders = {
|
||||
'TSPL': '输入TSPL指令,如:\nSIZE 50 mm 30 mm\nGAP 2 mm\nCLS\nTEXT 50 50 "4" 0 1 1 "Hello"\nPRINT',
|
||||
'CPCL': '输入CPCL指令,如:\n! 0 200 200 240 1\nSIZE 50mm 30mm\nGAP 2mm\nTEXT 4 0 10 10 EN Hello\nPRINT'
|
||||
}
|
||||
return placeholders[this.commandType]
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadConfig()
|
||||
},
|
||||
onUnload() {
|
||||
this.cleanup()
|
||||
},
|
||||
methods: {
|
||||
formatDate(dateStr) {
|
||||
const date = new Date(dateStr)
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
},
|
||||
loadConfig() {
|
||||
const config = uni.getStorageSync('blePrintAutoConfig')
|
||||
if (config) {
|
||||
this.savedConfig = config
|
||||
setTimeout(() => {
|
||||
this.autoConnect()
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
clearConfig() {
|
||||
uni.removeStorageSync('blePrintAutoConfig')
|
||||
this.savedConfig = null
|
||||
uni.showToast({ title: '配置已清除', icon: 'success' })
|
||||
},
|
||||
goToBlePrint() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/testpages/bleprint'
|
||||
})
|
||||
},
|
||||
async autoConnect() {
|
||||
if (!this.savedConfig) return
|
||||
|
||||
this.isConnecting = true
|
||||
|
||||
try {
|
||||
await this.initBluetooth()
|
||||
await this.createConnection()
|
||||
|
||||
this.connected = true
|
||||
this.connectedDeviceId = this.savedConfig.deviceId
|
||||
this.connectedDeviceName = this.savedConfig.deviceName
|
||||
this.writeServiceId = this.savedConfig.serviceId
|
||||
this.writeCharacteristicId = this.savedConfig.writeCharId
|
||||
this.isConnecting = false
|
||||
uni.showToast({ title: '连接成功', icon: 'success' })
|
||||
|
||||
} catch (err) {
|
||||
this.isConnecting = false
|
||||
uni.showToast({ title: '连接失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
initBluetooth() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.openBluetoothAdapter({
|
||||
success: () => {
|
||||
this.adapterState.available = true
|
||||
uni.onBLEConnectionStateChange((res) => {
|
||||
if (!res.connected && this.connectedDeviceId === res.deviceId) {
|
||||
this.connected = false
|
||||
this.connectedDeviceId = ''
|
||||
this.connectedDeviceName = ''
|
||||
}
|
||||
})
|
||||
resolve()
|
||||
},
|
||||
fail: (err) => {
|
||||
this.adapterState.available = false
|
||||
// 检测蓝牙是否未打开
|
||||
if (err.errMsg && (err.errMsg.includes('not available') || err.errMsg.includes('available'))) {
|
||||
uni.showModal({
|
||||
title: '蓝牙未开启',
|
||||
content: '请先开启手机蓝牙,然后重试',
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 延迟重试
|
||||
setTimeout(() => {
|
||||
this.autoConnect()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
createConnection() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.createBLEConnection({
|
||||
deviceId: this.savedConfig.deviceId,
|
||||
timeout: 15000,
|
||||
success: () => {
|
||||
resolve()
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
cancelConnect() {
|
||||
this.isConnecting = false
|
||||
},
|
||||
disconnectPrinter() {
|
||||
if (!this.connectedDeviceId) return
|
||||
uni.closeBLEConnection({
|
||||
deviceId: this.connectedDeviceId,
|
||||
success: () => {
|
||||
this.connected = false
|
||||
this.connectedDeviceId = ''
|
||||
this.connectedDeviceName = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
clearContent() {
|
||||
this.printContent = ''
|
||||
},
|
||||
useTemplate(type) {
|
||||
const templates = {
|
||||
qrcode: {
|
||||
CPCL: '! 0 200 200 240 1\nSIZE 50mm 30mm\nGAP 2mm\nCENTER\nQR 300 10 M 2 A 0\nMA,https://www.example.com\nENDQR\nPRINT',
|
||||
TSPL: 'SIZE 50 mm 30 mm\nGAP 2 mm\nCLS\nQRCODE 50 50 L 5 0 A 0 "https://www.example.com"\nPRINT'
|
||||
},
|
||||
barcode: {
|
||||
CPCL: '! 0 200 200 240 1\nSIZE 50mm 30mm\nGAP 2mm\nCENTER\nBARCODE 128 1 1 60 1 0 10 40 1234567890\nPRINT',
|
||||
TSPL: 'SIZE 50 mm 30 mm\nGAP 2 mm\nCLS\nBARCODE 50 50 "128" 50 1 0 2 2 "1234567890"\nPRINT'
|
||||
},
|
||||
label: {
|
||||
CPCL: '! 0 200 200 400 1\nSIZE 50mm 80mm\nGAP 2mm\nCENTER\nTEXT 4 0 10 20 EN Product Name\nTEXT 4 0 10 60 EN Price: $99.00\nQR 300 120 M 2 A 0\nMA,https://example.com\nENDQR\nBARCODE 128 1 1 60 1 0 10 200 12345678\nPRINT',
|
||||
TSPL: 'SIZE 50 mm 80 mm\nGAP 2 mm\nCLS\nTEXT 20 20 "4" 0 1 1 "Product Name"\nTEXT 20 60 "4" 0 1 1 "Price: $99.00"\nQRCODE 50 100 L 5 0 A 0 "https://example.com"\nBARCODE 20 160 "128" 50 1 0 2 2 "12345678"\nPRINT'
|
||||
}
|
||||
}
|
||||
this.printContent = templates[type][this.commandType]
|
||||
},
|
||||
stringToBuffer(str) {
|
||||
try {
|
||||
const uint8Array = iconv.encode(str, 'gbk');
|
||||
return uint8Array.buffer;
|
||||
} catch (e) {
|
||||
console.warn('编码失败,使用回退方案', e)
|
||||
const buffer = new ArrayBuffer(str.length)
|
||||
const dataView = new DataView(buffer)
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
dataView.setUint8(i, str.charCodeAt(i) & 0xFF)
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
},
|
||||
doPrint() {
|
||||
if (!this.connected) {
|
||||
uni.showToast({ title: '请先连接打印机', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.printContent.trim()) {
|
||||
uni.showToast({ title: '请输入打印内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!this.writeCharacteristicId) {
|
||||
uni.showToast({ title: '未配置特征值', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const buffer = this.stringToBuffer(this.printContent)
|
||||
uni.writeBLECharacteristicValue({
|
||||
deviceId: this.connectedDeviceId,
|
||||
serviceId: this.writeServiceId,
|
||||
characteristicId: this.writeCharacteristicId,
|
||||
value: buffer,
|
||||
writeType: 'writeNoResponse',
|
||||
success: () => {
|
||||
uni.showToast({ title: '打印成功', icon: 'success' })
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.showToast({ title: '发送失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
cleanup() {
|
||||
if (this.connectedDeviceId) {
|
||||
uni.closeBLEConnection({
|
||||
deviceId: this.connectedDeviceId
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.status-section {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.status-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.status-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
.status-value.available {
|
||||
color: #007aff;
|
||||
}
|
||||
.status-value.connected {
|
||||
color: #34c759;
|
||||
}
|
||||
.status-value.auto {
|
||||
color: #ff9500;
|
||||
}
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
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;
|
||||
}
|
||||
.printer-name {
|
||||
font-size: 24rpx;
|
||||
color: #34c759;
|
||||
}
|
||||
.config-info {
|
||||
background: #f8f8f8;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.config-row {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.config-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.config-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
.config-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
.config-value.uuid {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 22rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
.config-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.empty-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 20rpx;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.empty-hint {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.print-section {
|
||||
min-height: 400rpx;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.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: 180rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
font-size: 26rpx;
|
||||
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;
|
||||
}
|
||||
.connecting-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 60rpx 20rpx;
|
||||
}
|
||||
.connecting-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.loading-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #007aff;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: rotate 1s linear infinite;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
@keyframes rotate {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.connecting-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.connecting-hint {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user