589 lines
13 KiB
Vue
589 lines
13 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">{{ scaleConfig.deviceName }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 重量显示 -->
|
|
<view class="weight-display-section">
|
|
<view class="weight-box" :class="{ stable: currentWeight && currentWeight.stable }">
|
|
<text class="weight-value">{{ displayWeight }}</text>
|
|
<text class="weight-unit">{{ currentWeight && currentWeight.unit || 'kg' }}</text>
|
|
</view>
|
|
<view class="weight-status">
|
|
<text class="status-indicator" :class="{ active: currentWeight && currentWeight.stable }">
|
|
{{ currentWeight && currentWeight.stable ? '稳定' : '变化中' }}
|
|
</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 操作按钮 -->
|
|
<view class="section action-section" v-if="!connected">
|
|
<view class="connecting-text" v-if="isConnecting">{{ connectingText }}</view>
|
|
<button class="btn btn-primary" :loading="isConnecting" @click="handleConnect">
|
|
{{ isConnecting ? '连接中...' : '重新连接' }}
|
|
</button>
|
|
<button class="btn btn-outline" @click="goToConfig">配置设备</button>
|
|
</view>
|
|
|
|
<!-- 调试信息 -->
|
|
<view class="section log-section" v-if="showLogs">
|
|
<view class="section-header">
|
|
<text class="section-title">调试日志</text>
|
|
<text class="section-action" @click="clearLogs">清空</text>
|
|
</view>
|
|
<scroll-view class="log-list" scroll-y>
|
|
<text class="log-item" v-for="(log, index) in logs" :key="index" :class="log.level">
|
|
[{{ log.time }}] {{ log.message }}
|
|
</text>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<!-- 断开连接 -->
|
|
<view class="section" v-if="connected">
|
|
<button class="btn btn-outline" @click="handleDisconnect">断开连接</button>
|
|
</view>
|
|
|
|
<!-- 校准弹窗 -->
|
|
<view class="modal-mask" v-if="showCalibrate" @click="hideCalibrateModal">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-header">
|
|
<text class="modal-title">砝码校准</text>
|
|
</view>
|
|
<view class="modal-body">
|
|
<text class="calibrate-tip">请在秤上放置标准砝码</text>
|
|
<input
|
|
class="calibrate-input"
|
|
type="digit"
|
|
v-model="calibrateWeight"
|
|
placeholder="输入砝码重量(kg)"
|
|
/>
|
|
</view>
|
|
<view class="modal-footer">
|
|
<button class="btn btn-outline" @click="hideCalibrateModal">取消</button>
|
|
<button class="btn btn-primary" @click="confirmCalibrate">开始校准</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import scaleService from '@/utils/BluetoothScaleService.js';
|
|
import { mapGetters, mapActions } from 'vuex';
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
isConnecting: false,
|
|
isTaring: false,
|
|
isCalibrating: false,
|
|
connected: false,
|
|
connectingText: '正在检查配置...',
|
|
scaleConfig: {},
|
|
currentWeight: null,
|
|
showCalibrate: false,
|
|
calibrateWeight: '1',
|
|
showLogs: false,
|
|
logs: []
|
|
}
|
|
},
|
|
computed: {
|
|
...mapGetters('scale', ['formattedWeight']),
|
|
displayWeight() {
|
|
if (!this.currentWeight || this.currentWeight.value === null) {
|
|
return '0.00';
|
|
}
|
|
return this.currentWeight.value.toFixed(2);
|
|
}
|
|
},
|
|
onLoad() {
|
|
this.loadScaleConfig();
|
|
this.checkAndConnect();
|
|
},
|
|
onShow() {
|
|
// 如果之前未连接,尝试重新连接
|
|
if (!this.connected && !this.isConnecting) {
|
|
this.checkAndConnect();
|
|
}
|
|
// 更新当前重量显示
|
|
if (scaleService.isConnected) {
|
|
this.currentWeight = scaleService.getCurrentWeight();
|
|
}
|
|
},
|
|
onUnload() {
|
|
// 退出页面时断开连接
|
|
this.handleDisconnect();
|
|
},
|
|
beforeDestroy() {
|
|
scaleService.offWeightChange(this.handleWeightChange);
|
|
scaleService.offConnectionChange(this.handleConnectionChange);
|
|
scaleService.offError(this.handleError);
|
|
},
|
|
mounted() {
|
|
// 注册重量变化回调
|
|
scaleService.onWeightChange(this.handleWeightChange);
|
|
// 注册连接状态回调
|
|
scaleService.onConnectionChange(this.handleConnectionChange);
|
|
// 注册错误回调
|
|
scaleService.onError(this.handleError);
|
|
},
|
|
methods: {
|
|
// ...mapActions('scale', ['setWeightValue']),
|
|
|
|
handleWeightChange(weight) {
|
|
this.currentWeight = weight;
|
|
// 同步到store
|
|
// this.setWeightValue(weight);
|
|
},
|
|
|
|
handleConnectionChange(connected) {
|
|
this.connected = connected;
|
|
if (connected) {
|
|
this.loadScaleConfig();
|
|
}
|
|
},
|
|
|
|
handleError(error) {
|
|
this.addLog(`错误: ${error.message}`, 'error');
|
|
},
|
|
|
|
addLog(message, level = 'info') {
|
|
const now = new Date();
|
|
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
|
this.logs.unshift({ time, message, level });
|
|
// 只保留最近50条日志
|
|
if (this.logs.length > 50) {
|
|
this.logs.pop();
|
|
}
|
|
},
|
|
|
|
clearLogs() {
|
|
this.logs = [];
|
|
},
|
|
|
|
loadScaleConfig() {
|
|
const storeState = uni.getStorageSync('scaleConfig') || {};
|
|
const serviceConfig = scaleService.getConfig();
|
|
|
|
this.scaleConfig = {
|
|
deviceId: serviceConfig.deviceId || storeState.deviceId || '',
|
|
serviceId: serviceConfig.serviceId || storeState.serviceId || '',
|
|
notifyCharId: serviceConfig.notifyCharId || storeState.characteristicId || '',
|
|
writeCharId: serviceConfig.writeCharId || storeState.writeCharacteristicId || '',
|
|
deviceName: serviceConfig.deviceName || storeState.deviceName || ''
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 检查配置并自动连接
|
|
*/
|
|
async checkAndConnect() {
|
|
this.loadScaleConfig();
|
|
|
|
// 检查是否有保存的配置
|
|
if (!this.scaleConfig.deviceId) {
|
|
this.connectingText = '未配置称重设备,请先配置设备';
|
|
return;
|
|
}
|
|
|
|
if (!this.scaleConfig.serviceId || !this.scaleConfig.notifyCharId) {
|
|
this.connectingText = '配置不完整,请重新配置设备';
|
|
return;
|
|
}
|
|
|
|
// 如果已连接,直接返回
|
|
if (scaleService.isConnected) {
|
|
this.connected = true;
|
|
return;
|
|
}
|
|
|
|
// 自动连接
|
|
this.isConnecting = true;
|
|
this.connectingText = '正在连接...';
|
|
|
|
try {
|
|
const success = await scaleService.autoConnect();
|
|
this.connected = success;
|
|
|
|
if (success) {
|
|
this.connectingText = '连接成功';
|
|
this.addLog('自动连接成功');
|
|
} else {
|
|
this.connectingText = '连接失败,请检查设备是否开启';
|
|
this.addLog('自动连接失败', 'error');
|
|
}
|
|
} catch (err) {
|
|
console.error('【称重】连接异常:', err);
|
|
this.connectingText = '连接异常: ' + (err.message || '未知错误');
|
|
this.addLog(`连接异常: ${err.message}`, 'error');
|
|
} finally {
|
|
this.isConnecting = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 手动连接
|
|
*/
|
|
async handleConnect() {
|
|
if (this.isConnecting) return;
|
|
|
|
this.isConnecting = true;
|
|
this.connectingText = '正在连接...';
|
|
|
|
this.loadScaleConfig();
|
|
|
|
if (!this.scaleConfig.deviceId) {
|
|
uni.showToast({ title: '请先配置称重设备', icon: 'none' });
|
|
this.connectingText = '未配置称重设备';
|
|
this.isConnecting = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const success = await scaleService.autoConnect();
|
|
this.connected = success;
|
|
|
|
if (success) {
|
|
uni.showToast({ title: '连接成功', icon: 'success' });
|
|
this.addLog('手动连接成功');
|
|
} else {
|
|
if (!this.scaleConfig.serviceId || !this.scaleConfig.notifyCharId) {
|
|
uni.showToast({ title: '配置不完整,请重新配置', icon: 'none' });
|
|
this.connectingText = '配置不完整';
|
|
} else {
|
|
uni.showToast({ title: '连接失败,请检查设备', icon: 'none' });
|
|
this.connectingText = '连接失败';
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('【称重】连接异常:', err);
|
|
uni.showToast({ title: '连接异常', icon: 'none' });
|
|
this.connectingText = '连接异常';
|
|
this.addLog(`连接异常: ${err.message}`, 'error');
|
|
} finally {
|
|
this.isConnecting = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 断开连接
|
|
*/
|
|
async handleDisconnect() {
|
|
try {
|
|
await scaleService.disconnect();
|
|
this.connected = false;
|
|
this.connectingText = '已断开连接';
|
|
this.addLog('已断开连接');
|
|
} catch (err) {
|
|
console.warn('【称重】断开连接失败:', err);
|
|
this.connected = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 跳转配置页面
|
|
*/
|
|
goToConfig() {
|
|
uni.navigateTo({
|
|
url: '/pages/settings/devices/scale'
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 去皮操作
|
|
*/
|
|
async handleTare() {
|
|
if (this.isTaring) return;
|
|
|
|
this.isTaring = true;
|
|
try {
|
|
await scaleService.tare();
|
|
uni.showToast({ title: '去皮成功', icon: 'success' });
|
|
this.addLog('去皮操作成功');
|
|
} catch (err) {
|
|
console.error('【称重】去皮失败:', err);
|
|
uni.showToast({ title: '去皮失败', icon: 'none' });
|
|
this.addLog(`去皮失败: ${err.message}`, 'error');
|
|
} finally {
|
|
this.isTaring = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 显示校准弹窗
|
|
*/
|
|
showCalibrateModal() {
|
|
this.showCalibrate = true;
|
|
this.calibrateWeight = '1';
|
|
},
|
|
|
|
/**
|
|
* 隐藏校准弹窗
|
|
*/
|
|
hideCalibrateModal() {
|
|
this.showCalibrate = false;
|
|
},
|
|
|
|
/**
|
|
* 确认校准
|
|
*/
|
|
async confirmCalibrate() {
|
|
const weight = parseFloat(this.calibrateWeight);
|
|
if (isNaN(weight) || weight <= 0) {
|
|
uni.showToast({ title: '请输入有效的校准重量', icon: 'none' });
|
|
return;
|
|
}
|
|
|
|
this.isCalibrating = true;
|
|
this.showCalibrate = false;
|
|
|
|
try {
|
|
await scaleService.calibrate(weight);
|
|
uni.showToast({ title: '校准成功', icon: 'success' });
|
|
this.addLog(`校准成功: ${weight}kg`);
|
|
} catch (err) {
|
|
console.error('【称重】校准失败:', err);
|
|
uni.showToast({ title: '校准失败', icon: 'none' });
|
|
this.addLog(`校准失败: ${err.message}`, 'error');
|
|
} finally {
|
|
this.isCalibrating = 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;
|
|
}
|
|
.weight-display-section {
|
|
background: #fff;
|
|
padding: 40rpx;
|
|
border-radius: 12rpx;
|
|
margin-bottom: 20rpx;
|
|
text-align: center;
|
|
}
|
|
.weight-box {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: baseline;
|
|
padding: 30rpx;
|
|
background: #f8f9fa;
|
|
border-radius: 16rpx;
|
|
border: 2rpx solid #e9ecef;
|
|
transition: all 0.3s;
|
|
}
|
|
.weight-box.stable {
|
|
border-color: #34c759;
|
|
background: #f0fff4;
|
|
}
|
|
.weight-value {
|
|
font-size: 120rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
line-height: 1;
|
|
}
|
|
.weight-unit {
|
|
font-size: 40rpx;
|
|
color: #666;
|
|
margin-left: 10rpx;
|
|
}
|
|
.weight-status {
|
|
margin-top: 20rpx;
|
|
}
|
|
.status-indicator {
|
|
display: inline-block;
|
|
padding: 8rpx 24rpx;
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
border-radius: 20rpx;
|
|
font-size: 24rpx;
|
|
}
|
|
.status-indicator.active {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
.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;
|
|
}
|
|
.section-action {
|
|
font-size: 26rpx;
|
|
color: #1890ff;
|
|
}
|
|
.connecting-text {
|
|
text-align: center;
|
|
color: #666;
|
|
font-size: 28rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
.action-section button {
|
|
width: 100%;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
.action-section button:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
.btn-group {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
}
|
|
.log-section {
|
|
background: #1e1e1e;
|
|
}
|
|
.log-section .section-title,
|
|
.log-section .section-action {
|
|
color: #fff;
|
|
}
|
|
.log-list {
|
|
height: 300rpx;
|
|
background: #2d2d2d;
|
|
border-radius: 8rpx;
|
|
padding: 16rpx;
|
|
}
|
|
.log-item {
|
|
display: block;
|
|
font-size: 22rpx;
|
|
color: #8c8c8c;
|
|
margin-bottom: 8rpx;
|
|
font-family: monospace;
|
|
}
|
|
.log-item.error {
|
|
color: #ff4d4f;
|
|
}
|
|
.btn {
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
}
|
|
.btn-primary {
|
|
background: #1890ff;
|
|
color: #fff;
|
|
}
|
|
.btn-outline {
|
|
background: #fff;
|
|
color: #1890ff;
|
|
border: 1rpx solid #1890ff;
|
|
}
|
|
.btn::after {
|
|
border: none;
|
|
}
|
|
/* 模态框 */
|
|
.modal-mask {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 999;
|
|
}
|
|
.modal-content {
|
|
width: 600rpx;
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
overflow: hidden;
|
|
}
|
|
.modal-header {
|
|
padding: 30rpx;
|
|
text-align: center;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
.modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
.modal-body {
|
|
padding: 40rpx 30rpx;
|
|
}
|
|
.calibrate-tip {
|
|
display: block;
|
|
text-align: center;
|
|
color: #666;
|
|
font-size: 28rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
.calibrate-input {
|
|
width: 100%;
|
|
height: 80rpx;
|
|
border: 1rpx solid #d9d9d9;
|
|
border-radius: 8rpx;
|
|
padding: 0 20rpx;
|
|
font-size: 28rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
.modal-footer {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
padding: 30rpx;
|
|
border-top: 1rpx solid #f0f0f0;
|
|
}
|
|
.modal-footer .btn {
|
|
flex: 1;
|
|
}
|
|
</style>
|