WIN-2CJ8FMUT9OF\Administrator hai 8 meses
pai
achega
d00c604bee
Modificáronse 7 ficheiros con 467 adicións e 104 borrados
  1. 175 0
      common/js/mqtt.js
  2. 33 0
      common/js/tts.js
  3. 60 0
      common/js/ttsManager.js
  4. 26 1
      manifest.json
  5. 85 26
      pages/index/index.vue
  6. 44 34
      pages/index/success.vue
  7. 44 43
      pages/login/index.vue

+ 175 - 0
common/js/mqtt.js

@@ -0,0 +1,175 @@
+const _MQTT = uni.requireNativePlugin("zad-socket-mqtt");
+class MQTTClient { 
+    constructor() {
+        this.messageQueue = [];
+        this.callbacks = new Map();
+        this.isConnecting = false;
+        
+        // 绑定方法到当前实例
+        this.handleMessage = this.handleMessage.bind(this);
+    }
+
+    /**
+     * MQTT操作方法
+     * @param {string} method - 操作方法名
+     * @param {Object} param - 操作参数
+     * @returns {Promise} 返回Promise对象
+     */
+    async execute(method, param = {}) {
+		// console.log(param,'11111111111111111111111');
+        return new Promise((resolve, reject) => {
+            try {
+                _MQTT.event({
+                    method: method,
+                    param: param
+                }, (e) => {
+                    this.handleMessage(e);
+                    if (e.code === 200) {
+                        resolve(e);
+                    } else if (e.code === 400) {
+                        reject(new Error(`MQTT ${method} 方法调用失败: ${JSON.stringify(e)}`));
+                    }
+                });
+            } catch (error) {
+                reject(error);
+            }
+        });
+    }
+
+    /**
+     * 连接MQTT服务器
+     * @param {Object} config - 连接配置
+     * @param {string} config.url - 服务器地址
+     * @param {string} config.username - 用户名
+     * @param {string} config.password - 密码
+     * @param {string} config.clientId - 客户端ID
+     * @returns {Promise}
+     */
+    connect(config) {
+        this.isConnecting = true;
+        return this.execute('connect', config);
+    }
+
+    /**
+     * 检查连接状态
+     * @returns {Promise}
+     */
+    checkConnection() {
+        return this.execute('isConnected');
+    }
+
+    /**
+     * 订阅主题
+     * @param {string} topic - 主题名称
+     * @returns {Promise}
+     */
+    subscribe(topic) {
+        return this.execute('subscribe', { topic });
+    }
+
+    /**
+     * 取消订阅
+     * @param {string} topic - 主题名称
+     * @returns {Promise}
+     */
+    unsubscribe(topic) {
+        return this.execute('unsubscribe', { topic });
+    }
+
+    /**
+     * 发送消息
+     * @param {string} topic - 主题名称
+     * @param {string|Object} message - 消息内容
+     * @returns {Promise}
+     */
+    send(topic, message) {
+        const messageStr = typeof message === 'object' ? JSON.stringify(message) : message;
+        return this.execute('send', { topic, message: messageStr });
+    }
+
+    /**
+     * 关闭连接
+     * @returns {Promise}
+     */
+    close() {
+        this.isConnecting = false;
+        return this.execute('close');
+    }
+
+    /**
+     * 处理接收到的消息
+     * @param {Object} e - 消息事件对象
+     */
+    handleMessage(e) {
+        // 添加到消息队列
+        this.messageQueue.push(e);
+        
+        // 触发消息接收回调
+        if (e.method === 'receive') {
+            this.triggerCallback('message', e.data);
+            // console.log('接收到消息:', e.data);
+        }
+        
+        // 处理连接状态变化
+        if (e.method === 'connect' && e.code === 200) {
+            this.isConnecting = false;
+            this.triggerCallback('connected', e);
+        }
+        
+        // 输出日志
+        if (e.code === 400) {
+            // console.error('MQTT操作失败:', e);
+        } else if (e.code === 200) {
+            // console.log('MQTT操作成功:', e.method);
+        }
+    }
+
+    /**
+     * 注册回调函数
+     * @param {string} event - 事件名称
+     * @param {Function} callback - 回调函数
+     */
+    on(event, callback) {
+        this.callbacks.set(event, callback);
+    }
+
+    /**
+     * 移除回调函数
+     * @param {string} event - 事件名称
+     */
+    off(event) {
+        this.callbacks.delete(event);
+    }
+
+    /**
+     * 触发回调函数
+     * @param {string} event - 事件名称
+     * @param {*} data - 回调数据
+     */
+    triggerCallback(event, data) {
+        const callback = this.callbacks.get(event);
+        if (callback && typeof callback === 'function') {
+            callback(data);
+        }
+    }
+
+    /**
+     * 获取消息队列
+     * @returns {Array}
+     */
+    getMessages() {
+        return this.messageQueue;
+    }
+
+    /**
+     * 清空消息队列
+     */
+    clearMessages() {
+        this.messageQueue = [];
+    }
+}
+
+// 创建全局实例
+const mqttClient = new MQTTClient();
+
+export default mqttClient;

+ 33 - 0
common/js/tts.js

@@ -0,0 +1,33 @@
+const AK = "w9ZOquIKYkXHXuUGRcsiDAkG";
+const SK = "zdcmzzDQOw3nSq1wUh0pSNfMqgO1CpQf";
+
+export function getBaiduTTSUrl(text, token) {
+	const url =
+		`https://tsn.baidu.com/text2audio?lan=zh&ctp=1&cuid=abcdggg&tok=${token}&tex=${encodeURIComponent(text)}&vol=5&per=0&spd=5&pit=5&aue=3`;
+	// console.log('生成的TTS URL:', url); // 重点检查这个URL里的tok=后面是否有值
+	return url;
+}
+
+export function getBaiduTTSAccessToken() {
+	return new Promise((resolve, reject) => {
+		uni.request({
+			url: "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=" +
+				AK + "&client_secret=" + SK,
+			method: "POST",
+			success: (res) => {
+				// 增加对返回数据的健壮性检查
+				if (res.data && res.data.access_token) {
+					resolve(res.data.access_token);
+				} else {
+					// 如果百度接口返回了错误信息(例如AK/SK错误)
+					reject(new Error('获取token失败: ' + (res.data.error_description || JSON.stringify(
+						res.data))));
+				}
+			},
+			fail: (error) => {
+				console.error('请求Token接口失败:', error);
+				reject(error);
+			},
+		});
+	});
+}

+ 60 - 0
common/js/ttsManager.js

@@ -0,0 +1,60 @@
+// ttsManager.js
+import {
+	getBaiduTTSUrl,
+	getBaiduTTSAccessToken
+} from '@/common/js/tts.js'
+class TTSManager {
+	constructor() {
+		this.isSpeaking = false;
+		this.messageQueue = [];
+		this.audioContext = null;
+		this.initAudio();
+	}
+
+	initAudio() {
+		this.audioContext = uni.createInnerAudioContext();
+		this.audioContext.onEnded(() => {
+			this.isSpeaking = false;
+			this.playNext(); // 播放下一句
+		});
+		this.audioContext.onError((error) => {
+			console.error('音频播放错误:', error);
+			this.isSpeaking = false;
+			this.playNext();
+		});
+	}
+
+	async speak(text) {
+		// 如果正在播放,将新消息加入队列
+		if (this.isSpeaking) {
+			this.messageQueue.push(text);
+			return;
+		}
+
+		this.isSpeaking = true;
+		try {
+			const token = await getBaiduTTSAccessToken();
+			const audioUrl = getBaiduTTSUrl(text, token);
+			this.audioContext.src = audioUrl;
+			this.audioContext.play();
+		} catch (error) {
+			console.error('TTS合成失败:', error);
+			this.isSpeaking = false;
+		}
+	}
+
+	playNext() {
+		if (this.messageQueue.length > 0) {
+			const nextText = this.messageQueue.shift();
+			this.speak(nextText);
+		}
+	}
+
+	destroy() {
+		if (this.audioContext) {
+			this.audioContext.destroy();
+		}
+	}
+}
+
+export default new TTSManager();

+ 26 - 1
manifest.json

@@ -1,6 +1,6 @@
 {
     "name" : "振合网收银系统",
-    "appid" : "__UNI__9EAB491",
+    "appid" : "__UNI__C7A1864",
     "description" : "",
     "versionName" : "1.0.0",
     "versionCode" : "100",
@@ -16,6 +16,12 @@
             "autoclose" : true,
             "delay" : 0
         },
+        "plugins" : {
+            "zad-socket-mqtt" : {
+                // 配置你的原生插件
+                "background" : "enable" // 允许后台运行(如果插件支持)
+            }
+        },
         /* 模块配置 */
         "modules" : {},
         /* 应用发布信息 */
@@ -43,10 +49,29 @@
             },
             /* ios打包配置 */
             "ios" : {
+                "UIBackgroundModes" : [
+                    "audio" // 启用音频后台模式,允许音频在后台播放
+                ],
                 "dSYMs" : false
             },
             /* SDK配置 */
             "sdkConfigs" : {}
+        },
+        "nativePlugins" : {
+            "zad-socket" : {
+                "__plugin_info__" : {
+                    "name" : "mqtt 和 tcp socket",
+                    "description" : "mqtt是基于tcp的不是websocket。         mqtt支持tcp://协议头,解决mqtt.js只能用ws://问题。",
+                    "platforms" : "Android",
+                    "url" : "https://ext.dcloud.net.cn/plugin?id=9136",
+                    "android_package_name" : "uni.app.UNI9EAB491",
+                    "ios_bundle_id" : "uni.app.UNI9EAB491",
+                    "isCloud" : true,
+                    "bought" : 1,
+                    "pid" : "9136",
+                    "parameters" : {}
+                }
+            }
         }
     },
     /* 快应用特有相关 */

+ 85 - 26
pages/index/index.vue

@@ -1,49 +1,108 @@
 <template>
 	<view class="index-box">
-	  <image src="@/static/img/box-bj.png" mode="scaleToFill"></image>
-	  <view class="box-border-box plr-28">
-	  	<view class="save-box fs-42 fc-f text-align-center" @click="toList()">开始结账</view>
-	  </view>
+		<image src="@/static/img/box-bj.png" mode="scaleToFill"></image>
+		<view class="box-border-box plr-28">
+			<view class="save-box fs-42 fc-f text-align-center" @click="toList()">开始结账</view>
+		</view>
 	</view>
 </template>
 
 <script>
+	import mqttClient from '@/common/js/mqtt.js'
+	import ttsManager from '@/common/js/ttsManager.js';
+	export default {
+		data() {
+			return {};
+		},
+		async onLoad() {
+			// #ifdef APP-PLUS
+			mqttClient.on('message', (data) => {
+				// console.log('收到MQTT消息 (原始数据)', data); // 首先打印整个data
+				try {
+					// 假设 data 本身是 JSON 字符串,将其解析成对象
+					const parsedData = JSON.parse(data);
+					console.log('解析后的消息内容:', parsedData);
+					uni.$emit('mqttEmit',parsedData.amount)
+					uni.redirectTo({
+						url: '/pages/index/success?amount=' + parsedData.amount
+					})
+					// 收到消息后直接进行语音播报
+					// ttsManager.speak(parsedData.msg);
+				} catch (error) {
+					console.error('解析MQTT消息失败,数据可能不是有效的JSON格式:', error);
+					// console.log('原始消息内容:', data);
+				}
+			});
+			// 连接服务器
+			await this.connectToMQTT();
+			// 检查连接状态
+			await mqttClient.checkConnection();
+			//#endif
+		},
+		methods: {
+			toList() {
+				uni.redirectTo({
+					url: "/pages/index/order"
+				})
+			},
+			connectToMQTT() {
+				// 连接MQTT服务器
+				console.log("连接MQTT服务器------");
+				try {
+					mqttClient.on('connected', () => {
+						console.log('MQTT连接成功,开始订阅主题...');
+						setTimeout(() => {
+							this.subscribeToTopic(); // 在连接成功后自动订阅
+						}, 1000)
+					});
+					// 发起连接
+					mqttClient.connect({
+						url: 'tcp://mqtt.jhcs888.com:21883',
+						username: 'hjkj',
+						password: 'ali2021',
+						clientId: 'client_' + Math.random().toString(16).substr(2, 8),
+						keepalive: 60, // 增加心跳间隔,单位秒
+						clean: false, // 允许断线重连后恢复会话
+						reconnectPeriod: 1000, // 断开后自动重连的间隔
+						connectTimeout: 4000 // 连接超时时间(毫秒)
+					});
+					console.log('连接成功');
+				} catch (error) {
+					console.error('连接失败:', error);
+				}
+			},
+			subscribeToTopic() {
+				//  订阅主题
+				try {
+					mqttClient.subscribe('zsshop97');
+					console.log('订阅指令已发送');
+				} catch (error) {
+					console.error('订阅失败:', error);
+				}
+			}
+		},
 
-
-export default {
-  data() {
-    return {
-      
-    };
-  },
-  
-  methods: {
-    toList(){
-		uni.redirectTo({
-			url:"/pages/index/order"
-		})
-	},
-  },
-  
-};
+	};
 </script>
 
 
 <style scoped lang="scss">
-	.index-box{
+	.index-box {
 		width: 100%;
-		image{
+
+		image {
 			width: 100%;
 			height: 1122rpx;
 			margin-bottom: 45rpx;
 		}
-		.save-box{
+
+		.save-box {
 			width: 100%;
 			height: 83rpx;
 			line-height: 83rpx;
-			background: linear-gradient( 270deg, #1778FD 0%, #4AB6FF 100%);
+			background: linear-gradient(270deg, #1778FD 0%, #4AB6FF 100%);
 			box-shadow: 0rpx 0rpx 6rpx 0rpx #358AFD;
 			border-radius: 69rpx;
 		}
 	}
-</style>
+</style>

+ 44 - 34
pages/index/success.vue

@@ -20,40 +20,46 @@
 </template>
 
 <script>
-	import {projectWriteDiscount} from '@/api/project.js';
+	import {
+		projectWriteDiscount
+	} from '@/api/project.js';
 	export default {
-		data(){
-			return{
-				totalPrice:'',//实付款
-				money:'',//原价
-				discount:'',
-				invite_code:'',
-				shop_id:'',
+		data() {
+			return {
+				totalPrice: '', //实付款
+				money: '', //原价
+				discount: '',
+				invite_code: '',
+				shop_id: '',
 			}
 		},
+		onShow(){
+			uni.$on('mqttEmit',(e)=>{
+				this.totalPrice = e*1
+			})
+		},
 		onLoad(e) {
-
-
-			if(e.discount*1 > 0){
-				this.totalPrice = (((e.money*1)*(e.discount*1)*100).toFixed(2)/100).toFixed(2);
-				let num1 = e.money*1
-				let num2 = e.discount*1
-				this.totalPrice = (((num1*num2*10000)/100).toFixed(0)/100).toFixed(2)
-
-			}else{
-				this.totalPrice = e.money;
+			if (e.amount) {
+				this.totalPrice = e.amount*1
+			} else {
+				if (e.discount * 1 > 0) {
+					this.totalPrice = (((e.money * 1) * (e.discount * 1) * 100).toFixed(2) / 100).toFixed(2);
+					let num1 = e.money * 1
+					let num2 = e.discount * 1
+					this.totalPrice = (((num1 * num2 * 10000) / 100).toFixed(0) / 100).toFixed(2)
+				} else {
+					this.totalPrice = e.money;
+				}
+				this.money = e.money;
+				this.invite_code = e.invite_code;
+				this.discount = e.discount;
 			}
 
-			this.money = e.money;
-			this.invite_code = e.invite_code;
-			this.discount = e.discount;
-
-
 		},
-		methods:{
-			buy(){
+		methods: {
+			buy() {
 				uni.reLaunch({
-					url:'/pages/index/index'
+					url: '/pages/index/index'
 				})
 			},
 
@@ -62,16 +68,17 @@
 </script>
 
 <style scoped lang="scss">
-	.success-box{
+	.success-box {
 		width: 100%;
 		height: 100vh;
 		background-color: #F2F3F5;
 		position: relative;
 
-		.box-2{
+		.box-2 {
 			margin-top: 600rpx;
 			padding: 0 40rpx;
-			view{
+
+			view {
 				width: 46%;
 				height: 83rpx;
 				line-height: 83rpx;
@@ -84,14 +91,15 @@
 			}
 		}
 
-		.box-1{
+		.box-1 {
 			width: 750rpx;
 			height: 272rpx;
 			background: #0473CF;
 			border-radius: 0 0 100rpx 100rpx;
 
 		}
-		.img-1{
+
+		.img-1 {
 			background: url('@/static/img/s-1.png') no-repeat;
 			background-size: 100% 100%;
 			position: absolute;
@@ -103,14 +111,16 @@
 			box-sizing: border-box;
 			padding-top: 78rpx;
 		}
-		.img-2{
+
+		.img-2 {
 			width: 160rpx;
 			height: 127rpx;
 			margin: 0 auto 35rpx auto;
 			z-index: 2;
 		}
-		.mb-120{
+
+		.mb-120 {
 			margin-bottom: 140rpx;
 		}
 	}
-</style>
+</style>

+ 44 - 43
pages/login/index.vue

@@ -4,66 +4,61 @@
 			<image class="hi-img" src="@/static/img/hi.png" mode="scaleToFill"></image>
 			<view class="fs-42 fc-3 fw-b">欢迎使用振合网收银系统</view>
 		</view>
-		
 		<view class="info-box">
 			<view class="fs-32 fc-3 fw-b mb-25">密码登录</view>
-			
 			<input v-model="phone" placeholder="请输入手机号" type="number" />
-			
 			<view class="p-f">
 				<input v-model="password" placeholder="请输入密码" :password="inputType" />
 				<view class="p-c" @click="openeye()">
 					<uni-icons type="eye-filled" size="20" color="#999"></uni-icons>
 				</view>
 			</view>
-			
-			
-			
 			<view class="button-save text-align-center fs-32 fc-f" @click="toLogin()">立即登录</view>
 		</view>
-		
+
 	</view>
 </template>
 
 <script>
 	import tools from "@/common/js/tools.js";
-	import {Login} from '@/api/cashierApi.js';
+	import {
+		Login
+	} from '@/api/cashierApi.js';
 	export default {
-		
-		data(){
-			return{
-				inputType:true,
-				phone:'',
-				password:'',
-				isAJAX:false,
+		data() {
+			return {
+				inputType: true,
+				phone: '',
+				password: '',
+				isAJAX: false,
 			}
 		},
-		mounted() {
-			
-		},
-		methods:{
-			openeye(){
+		mounted() {},
+		methods: {
+			openeye() {
 				this.inputType = !this.inputType
 			},
-			
-			toLogin(){
-				if(this.isAJAX){
+			toLogin() {
+				if (this.isAJAX) {
 					return
 				}
 				this.isAJAX = true;
-				Login({phone:this.phone,password:this.password}).then((res)=>{
-					if(res.code == 1){
-						uni.setStorageSync('token',res.data.token);
-						uni.setStorageSync('u_id',res.data.u_id);
-						uni.setStorageSync('shop_id',res.data.shop_id);
+				Login({
+					phone: this.phone,
+					password: this.password
+				}).then((res) => {
+					if (res.code == 1) {
+						uni.setStorageSync('token', res.data.token);
+						uni.setStorageSync('u_id', res.data.u_id);
+						uni.setStorageSync('shop_id', res.data.shop_id);
 						tools.success("登录成功");
 						setTimeout(() => {
-						  this.isAJAX = false;
-						  uni.redirectTo({
-						    url: "/pages/index/index",
-						  });
-						}, 1000); 
-					}else{
+							this.isAJAX = false;
+							uni.redirectTo({
+								url: "/pages/index/index",
+							});
+						}, 1000);
+					} else {
 						tools.error(res.msg);
 						this.isAJAX = false;
 					}
@@ -74,24 +69,27 @@
 </script>
 
 <style scoped lang="scss">
-	.login-index{
-		.bg-img{
+	.login-index {
+		.bg-img {
 			width: 100%;
 			height: 430rpx;
 			background-image: url('@/static/img/login-bg.png');
 			background-size: 100% 100%;
 			box-sizing: border-box;
 			padding: 90rpx 25rpx 0 25rpx;
-			.hi-img{
+
+			.hi-img {
 				width: 118rpx;
 				height: 60rpx;
 				margin-bottom: 10rpx;
 			}
 		}
-		.info-box{
+
+		.info-box {
 			box-sizing: border-box;
 			padding: 10rpx 27rpx;
-			input{
+
+			input {
 				width: 100%;
 				height: 83rpx;
 				background: #F3F4F6;
@@ -101,15 +99,18 @@
 				font-size: 30rpx;
 				margin-bottom: 35rpx;
 			}
-			.p-f{
+
+			.p-f {
 				position: relative;
-				.p-c{
+
+				.p-c {
 					position: absolute;
 					top: 20rpx;
 					right: 20rpx;
 				}
 			}
-			.button-save{
+
+			.button-save {
 				width: 100%;
 				height: 83rpx;
 				line-height: 83rpx;
@@ -117,6 +118,6 @@
 				border-radius: 104rpx;
 			}
 		}
-		
+
 	}
 </style>