Browse Source

frist commit

houjie 4 years ago
commit
e189475281

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+/.hbuilderx
+/node_modules
+/unpackage
+/proto/bundle.js
+/.vscode
+

+ 9 - 0
API/Constant.js

@@ -0,0 +1,9 @@
+
+// URL
+export let BASEURL = '';
+
+if(process.env.NODE_ENV === 'development'){ // 开发环境
+  BASEURL = 'https://test.ohplay.radio1964.net/';
+}else{ // 生产
+  BASEURL = 'https://ohplay.radio1964.net/';
+};

+ 16 - 0
API/ProtoConfig.js

@@ -0,0 +1,16 @@
+import {
+	user
+} from './../proto/bundle.js';
+
+//不需要token的请求
+const NoTokenServants = [10003,10005]
+
+const ProtoConfig = {
+	10003: user.registerReq,//注册请求
+	10004: user.registerRsp,//注册返回
+	10005: user.loginReq, // 登录请求
+	10006: user.loginRsp, // 登录返回
+	
+};
+
+module.exports = {ProtoConfig,NoTokenServants};

+ 31 - 0
App.vue

@@ -0,0 +1,31 @@
+<script>
+	import {getToken} from './Lib/Request';
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+			//获取token
+			getToken()
+			
+			function uidCallback(data){
+				console.log(data);
+				uni.showToast({
+					title:`登录成功!`
+				})
+				
+			}
+			uni.$on('user_getUid',uidCallback)
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		},
+		
+		
+	}
+</script>
+
+<style>
+	/*每个页面公共css */
+</style>

+ 61 - 0
Lib/ProtoMap.js

@@ -0,0 +1,61 @@
+import {
+	common,
+} from './../proto/bundle';
+
+import {ProtoConfig} from './../API/ProtoConfig';
+
+
+function getMsgWebsocket(version, server, servant, buffer) {
+	return common.MsgWebsocket.create({
+		version: version,
+		app: 1,
+		server: server,
+		servant: servant,
+		data: buffer,
+	});
+};
+
+function getRequestBuffer(request, buffer) {
+	const requestMessage = getMsgWebsocket(request.version ? request.version : 1, request.server, request.servant, buffer);
+	const requestBuffer = common.MsgWebsocket.encode(requestMessage).finish();
+	return new Uint8Array([...requestBuffer]).buffer;
+};
+
+function getResponseDataBuf(response) {
+	const resBuf = Buffer.from(response.data);
+	const resMessage = common.MsgWebsocket.decode(resBuf);
+	return resMessage.data;
+};
+
+
+function requestEncode(request) { //请求的编码
+	const RequestMessage = ProtoConfig[request.servant];
+	const requestMsgObj = RequestMessage.create(request.data);
+	const buffer = RequestMessage.encode(requestMsgObj).finish();
+	//返回请求的ArrayBuffer
+	return getRequestBuffer(request, buffer);
+};
+
+function responseDecode(resBuf) { //响应的解码
+	const resMessage = common.MsgWebsocket.decode(resBuf);
+	const rspBuf = resMessage.data;
+
+	const ResponseMessage = ProtoConfig[resMessage.servant];
+
+	const rspMsgObj = ResponseMessage.decode(rspBuf);
+
+	const obj = ResponseMessage.toObject(rspMsgObj, {
+		longs: String,
+		enums: String,
+		defaults: true,
+	});
+	return obj
+
+};
+
+
+
+module.exports = {
+	requestEncode,
+	responseDecode,
+}

+ 258 - 0
Lib/Request.js

@@ -0,0 +1,258 @@
+
+import {
+	requestEncode,
+	responseDecode
+} from "./ProtoMap";
+import { NoTokenServants } from './../API/ProtoConfig';
+
+import { BASEURL } from "../API/Constant";
+
+/**
+ * 超过N天就直接重新登录吧
+ */
+const TOKEN_TIMEOUT = 1000*60*60*24*15
+
+//这个path需要token
+const PATH = 'Ohplay/Web/HttpToTcp';
+//这个path不需要token
+const PATH1 = 'ohplay/Web/HttpToTcp';
+
+let token = "";
+
+/**
+ * 保存token和uid
+ * @param {string} Token 
+ * @param {number} uid 
+ */
+function saveToken(Token, uid) {
+	token = Token
+	getApp().globalData.uid = uid
+	// 缓存token
+	uni.setStorage({
+		key: 'token',
+		data: {
+			token,
+			time:new Date().getTime(),
+		},
+	});
+	uni.setStorage({
+		key: 'uid',
+		data: uid
+	})
+	uni.$emit('user_getUid',{uid,token})
+}
+
+// 登录
+function login() {
+
+	uni.login({
+		provider: 'weixin',
+		success: function (loginRes) {
+			console.log(`uni.login errMsg = ${loginRes.errMsg}`)
+			if (loginRes.code) {
+				const data = {
+					server: 2,
+					servant: 10005,
+					data: {
+						code: loginRes.code,
+					}
+				}
+				request(data).then((res) => {
+					// console.log(res)
+					if (res.code === 0) {
+						if (res.data.token && res.data.id) {
+							//保存token
+							saveToken(res.data.token,res.data.id)
+						} else {
+							//进入登录页面
+							gotoLoginPage()
+						}
+						return;
+					};
+					uni.showToast({
+						title: res.msg,
+						duration: 2000,
+						icon: "none"
+					});
+
+				}).catch((res) => {
+					console.log("catch")
+					console.log(res);
+				});
+			} else {
+				uni.showToast({
+					title: loginRes.errMsg,
+					duration: 2000,
+					icon: "none"
+				});
+			}
+		}
+	});
+	// return res
+};
+
+// 获取token
+function getToken() {
+	try {
+		const value = uni.getStorageSync('token');
+		const uid = uni.getStorageInfoSync('uid');
+		if (value && uid) {
+			const nowTime = new Date().getTime();
+			//token超时则重新登录
+			if(nowTime - value.time > TOKEN_TIMEOUT){
+				//清除token和uid
+				cleanUserInfo();
+				login()
+			}else{
+				token = value;
+				getApp().globalData.uid = uid
+			}
+			
+		} else {
+			login();
+		}
+	} catch (e) {
+		login();
+	}
+}
+
+function cleanUserInfo(){
+	try{
+		uni.removeStorageSync('token')
+		uni.removeStorageSync('uid')
+		getApp().globalData.uid = null
+		token = ''
+	}catch(e){
+		
+	}
+	
+}
+
+function gotoLoginPage(){
+	uni.navigateTo({
+		url: '../../pages/login/login',
+		complete(res){
+			console.log(res);
+		}
+	})
+}
+
+// 获取token
+//getToken();
+// login();
+
+
+const defaultHeader = {
+	"X-Requested-With": "XMLHttpRequest",
+	"Content-Type": "application/x-protobuf",
+}
+
+// 拦截器
+uni.addInterceptor('request', {
+	invoke(args) {
+		// request 触发前拼接 url 
+		//args.url = BASEURL;
+		if (token) {
+			args.header.token = token;
+		};
+	},
+	success(args) {
+		// 请求成功后
+	},
+	fail(err) {
+		console.log('interceptor-fail', err)
+	},
+	complete(res) {
+		console.log('interceptor-complete', res)
+	}
+})
+
+/**
+ * POST请求
+ * @param {*} request 
+ * @returns 
+ */
+function request(data, type = "POST", header = defaultHeader) {
+	let url = BASEURL + PATH
+	if (NoTokenServants.indexOf(data.servant) >= 0) {
+		url = BASEURL + PATH1
+	}
+	return new Promise((resolve, reject) => {
+		uni.request({
+			url,
+			header: header,
+			method: type,
+			timeout: 15000,
+			dataType: 'protobuf',
+			responseType: 'TEXT',
+			data: requestEncode(data)
+		}).then((res) => {
+			res.map((response) => {
+				if (response && response.statusCode === 200) {
+					try {
+
+						let resJson = JSON.parse(response.data);
+
+						if (resJson.code === 0) {
+
+							let arrayBuffer = Buffer.from(resJson.data, 'base64');
+
+							let data = responseDecode(arrayBuffer);
+							let code = 0;
+							let msg = "success";
+
+							if (typeof data.errInfo !== 'undefined') {
+								code = data.errInfo.errorCode;
+								if (typeof data.errInfo.errorMessage === 'string') {
+									msg = data.errInfo.errorMessage;
+								} else {
+									msg = Buffer.from(data.errInfo.errorMessage).toString();
+								};
+							};
+
+							resolve({
+								data,
+								code,
+								msg
+							});
+						} else {
+							if (resJson.code === 10004) {
+								uni.showModal({
+									title: '温馨提示',
+									content: 'token已失效,请重新登录',
+									showCancel: false,
+									success() {
+										//进入登录界面
+										gotoLoginPage()
+									}
+								})
+							}
+							resolve({
+								code: resJson.code,
+								msg: resJson.info
+							});
+						}
+					} catch (e) {
+						resolve({
+							code: -1,
+							msg: e.toString(),
+						});
+					}
+					return;
+				};
+				// 没有数据或者返回错误
+				// resolve({
+				// 	code: 1006,
+				// 	msg: "数据错误",
+				// });
+			})
+
+		}).catch((error) => {
+			let [err, res] = error;
+			reject(err);
+		})
+	})
+
+}
+
+module.exports = { request, saveToken,getToken };

File diff suppressed because it is too large
+ 15716 - 0
common/mqtt.js


+ 75 - 0
components/demo/deviceCard.vue

@@ -0,0 +1,75 @@
+<template>
+  <view class="box"  @click="goDeviceInfo">
+    <view class="topBox">
+      <text>{{device.devName}}</text>
+      <text>{{device.online ? '在线':'离线'}}</text>
+    </view>
+    <text>电量:{{device.Power}}%</text>
+    <text class="iccid" space="ensp">uuid: {{device.uuid}}</text>
+  </view>
+</template>
+
+<script>
+
+export default {
+  props: {
+    device:{
+      type:Object,
+      default:function(){
+        return {
+          name:'',
+          ssid:''
+        }
+      }
+    }
+  },
+  data: () => ({}),
+  computed: {},
+  methods: {
+    goDeviceInfo() {
+      console.log("goDeviceInfo");
+      uni.navigateTo({
+        url: "../../../pages/demo/mqtt/deviceInfo",
+        complete(res){
+          console.warn(res);
+        }
+      });
+    },
+  },
+  watch: {},
+
+  // 组件周期函数--监听组件挂载完毕
+  mounted() {},
+
+  // 组件周期函数--监听组件激活(显示)
+  activated() {},
+  // 组件周期函数--监听组件停用(隐藏)
+  deactivated() {},
+  // 组件周期函数--监听组件销毁之前
+  beforeDestroy() {},
+};
+</script>
+
+<style>
+.box{
+  display: flex;
+  flex-direction:column;
+  justify-content: space-between;
+  width: 40vw;
+  height: 25vw;
+  border: 1rpx solid #333333;
+}
+.topBox{
+  display: flex;
+  flex-direction:row;
+  justify-content: space-between;
+}
+.iccid{
+  word-break: break-all;
+   /* text-overflow:ellipsis;
+  overflow: hidden;
+  white-space: nowrap; */
+}
+
+
+</style>

+ 20 - 0
main.js

@@ -0,0 +1,20 @@
+import Vue from 'vue'
+import App from './App'
+import {request} from './Lib/Request';
+import store from './store'
+
+//vuex
+Vue.prototype.$store = store
+
+//注册https请求到全局
+Vue.prototype.$request = request;
+
+Vue.config.productionTip = false
+
+App.mpType = 'app'
+
+const app = new Vue({
+	store,
+    ...App
+})
+app.$mount()

+ 71 - 0
manifest.json

@@ -0,0 +1,71 @@
+{
+    "name" : "OhPlay",
+    "appid" : "",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx08f94a3e90881910",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    }
+}

+ 257 - 0
package-lock.json

@@ -0,0 +1,257 @@
+{
+  "name": "OhPlay",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "protobufjs": "^6.11.2"
+      },
+      "devDependencies": {
+        "@dcloudio/uni-helper-json": "^1.0.13",
+        "@types/html5plus": "^1.0.1",
+        "@types/uni-app": "^1.4.3"
+      }
+    },
+    "node_modules/@dcloudio/uni-helper-json": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/@dcloudio/uni-helper-json/-/uni-helper-json-1.0.13.tgz",
+      "integrity": "sha512-FO9Iu4zW4td3Tr+eiCDWuele2ehkJ4qxQ/UhpAMLjso+ZdWz6NagK5Syh6cdy1hoDqbxpNoqnLynuJXe81Ereg==",
+      "dev": true
+    },
+    "node_modules/@protobufjs/aspromise": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+      "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
+    },
+    "node_modules/@protobufjs/base64": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+    },
+    "node_modules/@protobufjs/codegen": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+    },
+    "node_modules/@protobufjs/eventemitter": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+      "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
+    },
+    "node_modules/@protobufjs/fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.1",
+        "@protobufjs/inquire": "^1.1.0"
+      }
+    },
+    "node_modules/@protobufjs/float": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+      "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
+    },
+    "node_modules/@protobufjs/inquire": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+      "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
+    },
+    "node_modules/@protobufjs/path": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+      "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
+    },
+    "node_modules/@protobufjs/pool": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+      "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
+    },
+    "node_modules/@protobufjs/utf8": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+      "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
+    },
+    "node_modules/@types/html5plus": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@types/html5plus/-/html5plus-1.0.1.tgz",
+      "integrity": "sha512-ska1eyYbGO7zsru2KTjZ/CWtSjOXh92/K/QMdgSddkyr1ZplIybOzeS1u61scyxAKRvz6Yd9TRL+LQSBVyGUJw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/long": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
+      "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
+    },
+    "node_modules/@types/node": {
+      "version": "15.12.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+      "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
+    },
+    "node_modules/@types/uni-app": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/@types/uni-app/-/uni-app-1.4.3.tgz",
+      "integrity": "sha512-i2P+ARDpMWrAlubqp54LYBEAxZhPML3kHW9qLxwK7ZvInoI8xaYDDhqukp5xisBftVrZ7+jUa7spCDXy6qBMvQ==",
+      "dev": true,
+      "dependencies": {
+        "vue": "^2.6.8"
+      }
+    },
+    "node_modules/long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "node_modules/protobufjs": {
+      "version": "6.11.2",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz",
+      "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.2",
+        "@protobufjs/base64": "^1.1.2",
+        "@protobufjs/codegen": "^2.0.4",
+        "@protobufjs/eventemitter": "^1.1.0",
+        "@protobufjs/fetch": "^1.1.0",
+        "@protobufjs/float": "^1.0.2",
+        "@protobufjs/inquire": "^1.1.0",
+        "@protobufjs/path": "^1.1.2",
+        "@protobufjs/pool": "^1.1.0",
+        "@protobufjs/utf8": "^1.1.0",
+        "@types/long": "^4.0.1",
+        "@types/node": ">=13.7.0",
+        "long": "^4.0.0"
+      },
+      "bin": {
+        "pbjs": "bin/pbjs",
+        "pbts": "bin/pbts"
+      }
+    },
+    "node_modules/vue": {
+      "version": "2.6.14",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
+      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==",
+      "dev": true
+    }
+  },
+  "dependencies": {
+    "@dcloudio/uni-helper-json": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/@dcloudio/uni-helper-json/-/uni-helper-json-1.0.13.tgz",
+      "integrity": "sha512-FO9Iu4zW4td3Tr+eiCDWuele2ehkJ4qxQ/UhpAMLjso+ZdWz6NagK5Syh6cdy1hoDqbxpNoqnLynuJXe81Ereg==",
+      "dev": true
+    },
+    "@protobufjs/aspromise": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+      "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
+    },
+    "@protobufjs/base64": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+    },
+    "@protobufjs/codegen": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+    },
+    "@protobufjs/eventemitter": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+      "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
+    },
+    "@protobufjs/fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
+      "requires": {
+        "@protobufjs/aspromise": "^1.1.1",
+        "@protobufjs/inquire": "^1.1.0"
+      }
+    },
+    "@protobufjs/float": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+      "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
+    },
+    "@protobufjs/inquire": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+      "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
+    },
+    "@protobufjs/path": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+      "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
+    },
+    "@protobufjs/pool": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+      "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
+    },
+    "@protobufjs/utf8": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+      "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
+    },
+    "@types/html5plus": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@types/html5plus/-/html5plus-1.0.1.tgz",
+      "integrity": "sha512-ska1eyYbGO7zsru2KTjZ/CWtSjOXh92/K/QMdgSddkyr1ZplIybOzeS1u61scyxAKRvz6Yd9TRL+LQSBVyGUJw==",
+      "dev": true
+    },
+    "@types/long": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
+      "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
+    },
+    "@types/node": {
+      "version": "15.12.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+      "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
+    },
+    "@types/uni-app": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/@types/uni-app/-/uni-app-1.4.3.tgz",
+      "integrity": "sha512-i2P+ARDpMWrAlubqp54LYBEAxZhPML3kHW9qLxwK7ZvInoI8xaYDDhqukp5xisBftVrZ7+jUa7spCDXy6qBMvQ==",
+      "dev": true,
+      "requires": {
+        "vue": "^2.6.8"
+      }
+    },
+    "long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "protobufjs": {
+      "version": "6.11.2",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz",
+      "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==",
+      "requires": {
+        "@protobufjs/aspromise": "^1.1.2",
+        "@protobufjs/base64": "^1.1.2",
+        "@protobufjs/codegen": "^2.0.4",
+        "@protobufjs/eventemitter": "^1.1.0",
+        "@protobufjs/fetch": "^1.1.0",
+        "@protobufjs/float": "^1.0.2",
+        "@protobufjs/inquire": "^1.1.0",
+        "@protobufjs/path": "^1.1.2",
+        "@protobufjs/pool": "^1.1.0",
+        "@protobufjs/utf8": "^1.1.0",
+        "@types/long": "^4.0.1",
+        "@types/node": ">=13.7.0",
+        "long": "^4.0.0"
+      }
+    },
+    "vue": {
+      "version": "2.6.14",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
+      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==",
+      "dev": true
+    }
+  }
+}

+ 13 - 0
package.json

@@ -0,0 +1,13 @@
+{
+	"scripts": {
+		"proto": "pbjs -t static-module -w commonjs -o proto/bundle.js proto/*.proto"
+	},
+	"dependencies": {
+		"protobufjs": "^6.11.2"
+	},
+	"devDependencies": {
+		"@dcloudio/uni-helper-json": "^1.0.13",
+		"@types/html5plus": "^1.0.1",
+		"@types/uni-app": "^1.4.3"
+	}
+}

+ 93 - 0
pages.json

@@ -0,0 +1,93 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index", //首页
+			"style": {
+				"navigationBarTitleText": "猫王妙播"
+			}
+		},
+		{
+			"path": "pages/play/play",
+			"style": {
+				"navigationBarTitleText": "妙控",
+				"enablePullDownRefresh": false
+			}
+
+		},
+		{
+			"path": "pages/mine/mine",
+			"style": {
+				"navigationBarTitleText": "我的",
+				"enablePullDownRefresh": false
+			}
+
+		},
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationBarTitleText": "登录",
+				"enablePullDownRefresh": false
+			}
+
+		}, {
+			"path": "pages/demo/mqtt/mqttDemo",
+			"style": {
+				"navigationBarTitleText": "mqttDemo",
+				"enablePullDownRefresh": false
+			}
+
+		}
+		, {
+			"path": "pages/demo/mqtt/deviceInfo",
+			"style": {
+				"navigationBarTitleText": "mqttDemo",
+				"enablePullDownRefresh": false
+			}
+		
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "猫王妙播",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"tabBar": {
+		"color": "#353535",
+		"selectedColor": "#6547A3",
+		"borderStyle": "black",
+		"backgroundColor": "#ffffff",
+		"list": [{
+				"pagePath": "pages/index/index",
+				"iconPath": "static/home/home.png",
+				"selectedIconPath": "static/home/home_selected.png",
+				"text": "首页"
+			},
+			{
+				"pagePath": "pages/play/play",
+				"iconPath": "static/home/play.png",
+				"selectedIconPath": "static/home/play_selected.png",
+				"text": "妙控"
+			},
+			{
+				"pagePath": "pages/mine/mine",
+				"iconPath": "static/home/mine.png",
+				"selectedIconPath": "static/home/mine_selected.png",
+				"text": "我的"
+			}
+		]
+	},
+	"condition": {
+			"current": 0,
+			"list": [
+				{
+					"name": "mqtt",
+					"path": "pages/demo/mqtt/mqttDemo"
+				},
+				{
+					"name": "登录组件",
+					"path": "pages/login/login"
+				}
+			]
+		}
+}

+ 101 - 0
pages/demo/mqtt/deviceInfo.vue

@@ -0,0 +1,101 @@
+<template>
+  <view class="device-info">
+    <text v-for="(item,index) in infoList" :key="index" space="ensp" class="t">
+      {{item.key}} : {{item.element}}
+    </text>
+
+  </view>
+</template>
+
+<script>
+
+export default {
+  components: {},
+  data: () => ({
+    index:0,
+    list:[]
+  }),
+  computed: {
+    infoList(){
+      if(this.$store.state.moduleMqtt.deviceList.length > this.index){
+        let object = this.$store.state.moduleMqtt.deviceList[this.index]
+        let list = []
+        for (const key in object) {
+          if (Object.hasOwnProperty.call(object, key)) {
+            const element = object[key];
+            list.push({
+              key,
+              element,
+            })
+          }
+        }
+        
+        return list
+
+      }else{
+        return []
+      }
+    }
+  },
+  methods: {
+    getInfoList(){
+      console.log(`getInfoList1 ${this.$store.state.moduleMqtt.deviceList.length} index = ${this.index}` );
+      if(this.$store.state.moduleMqtt.deviceList.length > this.index){
+        console.log("getInfoList2");
+        let object = this.$store.state.moduleMqtt.deviceList[this.index]
+        let list = []
+        for (const key in object) {
+          if (Object.hasOwnProperty.call(object, key)) {
+            const element = object[key];
+            list.push({
+              key:key,
+              element:element,
+            })
+          }
+        }
+        console.log(list);
+        this.list = list
+
+      }
+
+    }
+  },
+  watch: {},
+
+  // 页面周期函数--监听页面加载
+  onLoad(options) {
+    // if(options){
+    //   this.index = Number.parseInt(options.index)
+    // }
+    //this.getInfoList()
+  },
+  // 页面周期函数--监听页面初次渲染完成
+  onReady() {},
+  // 页面周期函数--监听页面显示(not-nvue)
+  onShow() {},
+  // 页面周期函数--监听页面隐藏
+  onHide() {},
+  // 页面周期函数--监听页面卸载
+  onUnload() {},
+  // 页面处理函数--监听用户下拉动作
+  onPullDownRefresh() {
+    uni.stopPullDownRefresh();
+  },
+  // 页面处理函数--监听用户上拉触底
+  onReachBottom() {},
+  // 页面处理函数--监听页面滚动(not-nvue)
+  /* onPageScroll(event) {}, */
+  // 页面处理函数--用户点击右上角分享
+  /* onShareAppMessage(options) {}, */
+};
+</script>
+
+<style>
+.device-info{
+  display: flex;
+  flex-direction: column;
+}
+.t{
+  margin: 10rpx;
+}
+</style>

+ 298 - 0
pages/demo/mqtt/mqttDemo.vue

@@ -0,0 +1,298 @@
+<template>
+	<view class="content">
+		<!-- <view class="dbox">
+      <button @click="addDevice">添加设备</button>
+      <button @click="getPlayInfo">获取播放信息</button>
+      <button @click="getDeviceInfo">获取设备信息</button>
+    </view> -->
+		<view class="dbox">
+			<button @click="addDevice" class="btn">+</button>
+			<device-card v-for="(device, index) in deviceList" :key="index" :device="device"></device-card>
+			<!-- <device-card v-if="currentDevice" :device="currentDevice"/> -->
+		</view>
+		<view class="play-info">
+			<image class="play-thmub" :src="playInfo.albumURI" mode="aspectFit" />
+			<text style="margin-top: 20rpx">{{ playInfo.title }}</text>
+			<text style="margin-top: 20rpx">{{ playInfo.artist }}</text>
+		</view>
+		<view class="play-ctrl">
+			<image src="../../../static/demo/previous.png" class="next-icon" @click="previous" />
+			<image v-if="playInfo.PlayState != 1" src="../../../static/demo/play.png" class="next-icon" @click="play" />
+			<image v-if="playInfo.PlayState == 1" src="../../../static/demo/pause.png" class="next-icon"
+				@click="pause" />
+			<image src="../../../static/demo/next.png" class="next-icon" @click="next" />
+		</view>
+		<text style="margin-top: 20rpx; margin-left: 50rpx">音量</text>
+		<slider :value="currentDevice && currentDevice.Volume ? currentDevice.Volume : 0" class="progress" min="0"
+			max="100" step="1" @change="setVolume" show-value></slider>
+		<text style="margin-top: 10rpx; margin-left: 50rpx">频道</text>
+		<slider :value="playInfo.channel" class="progress" min="0" max="11" step="1" @change="setChannel" show-value>
+		</slider>
+	</view>
+</template>
+
+<script>
+	import deviceCard from "../../../components/demo/deviceCard.vue";
+	import {
+		mapState
+	} from "vuex";
+	export default {
+		components: {
+			deviceCard
+		},
+		data() {
+			return {
+				//deviceList:[]
+			};
+		},
+		onLoad() {
+			//this.deviceList = this.$store.state.moduleMqtt.deviceList;
+			uni.$on("mqtt_onoffline", this.onofflineCallback);
+		},
+		onUnload() {
+			uni.$off("mqtt_onoffline", this.onofflineCallback);
+		},
+		methods: {
+			onofflineCallback(value) {
+				//console.log("onofflineCallback");
+			},
+			//测试添加设备
+			addDevice() {
+				if (getApp().globalData.uid) {
+					//添加设备
+					this.$store.dispatch({
+						type: "moduleMqtt/addDevice",
+						clientId: `wx_${getApp().globalData.uid}`,
+						device: {
+							devName: "MW-V",
+							uuid: "89860474192070498495",
+						},
+					});
+				}else{
+					uni.showModal({
+						title:"温馨提示",
+						content:"请登录!",
+						success() {
+							uni.navigateTo({
+								url:'../../login/login'
+							})
+						}
+					})
+				}
+
+			},
+			//获取播放信息
+			getPlayInfo() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "get_position",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//获取设备信息
+			getDeviceInfo() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "get_dev_info",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//下一曲
+			next() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "next",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//上一曲
+			previous() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "previous",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//播放
+			play() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "play",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//暂停
+			pause() {
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "pause",
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//设置音量
+			setVolume(event) {
+				console.log(event.target.value);
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "volume_set",
+						other: {
+							volume: event.target.value,
+						},
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			//设置频道
+			setChannel(event) {
+				console.log(event.target.value);
+				this.$store
+					.dispatch({
+						type: "moduleMqtt/publishWithType",
+						mqttType: "play",
+						other: {
+							user_id: "650633",
+							timestamp: "", //暂时没有时间戳,先不填
+							channel_id: event.target.value + "",
+							url: "",
+							media_data: "",
+							order: "",
+							resource_from: "",
+						},
+					})
+					.then((result) => {})
+					.catch((err) => {
+						console.warn(err);
+					});
+			},
+			goDeviceInfo() {
+				//console.log("goDeviceInfo");
+				uni.navigateTo({
+					url: "./deviceInfo",
+					complete(res) {
+						console.warn(res);
+					}
+				});
+			},
+			godoBle() {
+				//添加设备
+				// #ifdef MP-WEIXIN||APP-PLUS
+				uni.navigateTo({
+					url: "../ble/ScanBleDevice",
+				});
+				// #endif
+				// #ifdef H5
+				uni.navigateTo({
+					url: "../ble/ConnectBleDevice",
+				});
+				// uni.showToast({
+				// 	title:'H5页面不支持扫描设备',
+				// 	icon:'none'
+				// })
+				// #endif
+			},
+		},
+		computed: {
+			// deviceList() {
+			//   return this.$store.state.moduleMqtt.deviceList;
+			// },
+			...mapState({
+				deviceList: (state) => state.moduleMqtt.deviceList,
+				playInfo: (state) => state.moduleMqtt.playInfo,
+				currentDevice: (state) => state.moduleMqtt.currentDevice,
+			}),
+		},
+	};
+</script>
+
+<style>
+	.content {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.play-info {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.dbox {
+		display: flex;
+		flex-direction: row;
+		width: 100vw;
+		justify-content: flex-start;
+	}
+
+	.btn {
+		margin-left: 0rpx;
+		margin-right: 10rpx;
+		width: 160rpx;
+		height: 25vw;
+		/* display: flex; */
+		/* justify-content: center;
+  align-items: center; */
+		line-height: 25vw;
+		border: 1rpx solid #333333;
+		font-size: 120rpx;
+	}
+
+	.text1 {
+		width: 150px;
+		word-break: break-all;
+		/* text-overflow:ellipsis;
+  overflow: hidden;
+  white-space: nowrap; */
+	}
+
+	.play-thmub {
+		display: flex;
+		align-self: center;
+		width: 300rpx;
+		height: 300rpx;
+		margin-top: 30rpx;
+	}
+
+	.play-ctrl {
+		display: flex;
+		flex-direction: row;
+		justify-content: space-around;
+		margin-top: 40rpx;
+	}
+
+	.next-icon {
+		width: 54rpx;
+		height: 60rpx;
+	}
+
+	.progress {
+		align-self: center;
+		width: 80vw;
+	}
+</style>

+ 49 - 0
pages/index/index.vue

@@ -0,0 +1,49 @@
+<template>
+	<view class="content">
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				title: 'Hello'
+			}
+		},
+		onLoad() {
+
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style>
+	.content {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.logo {
+		height: 200rpx;
+		width: 200rpx;
+		margin-top: 200rpx;
+		margin-left: auto;
+		margin-right: auto;
+		margin-bottom: 50rpx;
+	}
+
+	.text-area {
+		display: flex;
+		justify-content: center;
+	}
+
+	.title {
+		font-size: 36rpx;
+		color: #8f8f94;
+	}
+</style>

+ 184 - 0
pages/login/login.vue

@@ -0,0 +1,184 @@
+<template>
+  <view class="login">
+    <button
+      type="default"
+      open-type="getPhoneNumber"
+      @getphonenumber="decryptPhoneNumber"
+      @click="login"
+    >
+      微信授权一键登录
+    </button>
+  </view>
+</template>
+
+<script>
+import { saveToken } from "../../Lib/Request";
+export default {
+  components: {},
+  data: () => ({
+    openid: null,
+    phoneInfo: null,
+  }),
+  computed: {},
+  methods: {
+    login() {
+      let that = this;
+      //获取openid
+      uni.login({
+        provider: "weixin",
+        success: function (loginRes) {
+          console.log(`uni.login errMsg = ${loginRes.errMsg}`);
+          if (loginRes.code) {
+            //调用登录接口
+            that
+              .$request({
+                server: 2,
+                servant: 10005,
+                data: {
+                  code: loginRes.code,
+                },
+              })
+              .then((result) => {
+                console.log(result);
+                if (result.code === 0) {
+                  if (result.data.token && result.data.id) {
+                    //保存token
+                    saveToken(result.data.token, result.data.id);
+                  } else {
+                    that.openid = result.data.openid;
+                    //获取token
+                    that.getToken();
+                  }
+                } else {
+                  uni.showToast({
+                    title: result.msg,
+                    duration: 2000,
+                    icon: "none",
+                  });
+                }
+              })
+              .catch((err) => {
+                console.log(err);
+              });
+          } else {
+            uni.showToast({
+              title: loginRes.errMsg,
+              duration: 2000,
+              icon: "none",
+            });
+          }
+        },
+        fail(err) {
+          console.log(err);
+        },
+      });
+    },
+    //获取手机号码
+    decryptPhoneNumber(e) {
+      console.log(e.detail);
+      if (e.detail.errMsg == "getPhoneNumber:ok") {
+        console.log(e.detail.iv);
+        console.log(e.detail.encryptedData);
+        this.phoneInfo = {
+          encryptedData: e.detail.encryptedData,
+          iv: e.detail.iv,
+        };
+        //获取token
+        this.getToken();
+      } else {
+        uni.showToast({
+          title: "用户拒绝权限",
+          duration: 2000,
+          icon: "none",
+        });
+      }
+    },
+    //获取uuid和token
+    getToken() {
+      console.log(this.openid);
+      console.log(this.phoneInfo);
+      if (this.openid && this.phoneInfo) {
+        console.log("getToken");
+        const platform = uni.getSystemInfoSync().platform;
+        let systemType;
+        switch (platform) {
+          case "ios":
+            systemType = 1;
+            break;
+          case "android":
+            systemType = 2;
+            break;
+          default:
+            systemType = 3;
+            break;
+        }
+        const openid = this.openid;
+        const iv = this.phoneInfo.iv;
+        const phone = this.phoneInfo.encryptedData;
+        //清除数据避免重复调用
+        this.openid = null;
+        this.phoneInfo = null;
+        this.$request({
+          server: 2,
+          servant: 10003,
+          data: {
+            type: 1,
+            openid,
+            phone,
+            systemType,
+            iv,
+          },
+        })
+          .then((result) => {
+            console.log(result);
+            if (result.code === 0) {
+              if (result.data.token && result.data.id) {
+                //保存token
+                saveToken(result.data.token, result.data.id);
+              }else{
+                uni.showToast({
+                title: 'token 或者uid为空',
+                duration: 2000,
+                icon: "none",
+              });
+              }
+            } else {
+              uni.showToast({
+                title: result.msg,
+                duration: 2000,
+                icon: "none",
+              });
+            }
+          })
+          .catch((err) => {
+            console.log(err);
+          });
+      }
+    },
+  },
+  watch: {},
+
+  // 页面周期函数--监听页面加载
+  onLoad() {},
+  // 页面周期函数--监听页面初次渲染完成
+  onReady() {},
+  // 页面周期函数--监听页面显示(not-nvue)
+  onShow() {},
+  // 页面周期函数--监听页面隐藏
+  onHide() {},
+  // 页面周期函数--监听页面卸载
+  onUnload() {},
+  // 页面处理函数--监听用户下拉动作
+  onPullDownRefresh() {
+    uni.stopPullDownRefresh();
+  },
+  // 页面处理函数--监听用户上拉触底
+  onReachBottom() {},
+  // 页面处理函数--监听页面滚动(not-nvue)
+  /* onPageScroll(event) {}, */
+  // 页面处理函数--用户点击右上角分享
+  /* onShareAppMessage(options) {}, */
+};
+</script>
+
+<style></style>

+ 22 - 0
pages/mine/mine.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 22 - 0
pages/play/play.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 21 - 0
proto/Common.proto

@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+package common;
+
+message ErrorInfo {
+    fixed32 errorCode = 1; //错误码
+    string errorMessage = 2; //错误描述信息
+}
+
+message MsgWebsocket {
+    fixed32 version     = 1;               ///< 协议版本号
+    fixed32 app         = 2;               ///<应用名称
+    fixed32 server      = 3;               ///<应用内具体业务模块
+    fixed32 servant     = 4;               ///<业务模块内部具体接
+    fixed32 seq         = 5;               ///< 序列号
+    fixed32 route_id    = 6;              ///<负载均衡路由字段
+    fixed32 encrypt     = 7;              ///<加密方式,0:oaep(rsa v2),1:pkcs1(rsa v1.5)
+    fixed32 cache_is    = 8;              ///<1用缓存,2不用缓存
+    bytes data          = 9;
+}
+

+ 46 - 0
proto/User.proto

@@ -0,0 +1,46 @@
+syntax = "proto3";
+
+package user;
+
+import "common";
+
+
+//获取openid(10005/10006)
+message loginReq {
+    string code              = 1; //微信临时code
+}
+
+message loginRsp {
+    ErrorInfo errInfo   = 1; // 错误码信息
+    string openid        = 2; // 微信账号唯一标识openid
+    string token         = 3; // 访问令牌
+    uint32 id           = 4;
+}
+
+//枚举消息类型 
+enum EPhoneType {
+    phone  = 0;
+    local  = 1;//本机手机号
+    other  = 2;//
+}
+
+//注册or登录协议(10003/10004)
+message registerReq {
+    EPhoneType type          = 1;//暂时没啥用,默认为1
+    string openid            = 2; //微信账号唯一标识openid
+    string phone             = 3; //微信接口getPhoneNumber获取到的encryptedData
+    string verifyCode       = 4; //验证码,这里无用
+    uint32 systemType       = 5; //1:ios,2:android
+    string iv                = 6;//微信接口getPhoneNumber获取到的encryptedData
+}
+
+message registerRsp {
+    ErrorInfo errInfo   = 1; // 错误码信息
+    string token         = 2; // 访问令牌
+    uint32 id           = 3;
+}
+
+
+
+
+

BIN
static/demo/next.png


BIN
static/demo/pause.png


BIN
static/demo/play.png


BIN
static/demo/previous.png


BIN
static/home/home.png


BIN
static/home/home_selected.png


BIN
static/home/mine.png


BIN
static/home/mine_selected.png


BIN
static/home/play.png


BIN
static/home/play_selected.png


+ 14 - 0
store/index.js

@@ -0,0 +1,14 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import moduleMqtt from '@/store/modules/moduleMqtt'
+
+Vue.use(Vuex); //vue的插件机制
+
+
+//Vuex.Store 构造器选项
+const store = new Vuex.Store({
+	modules:{
+		moduleMqtt,//封装了mqtt模块
+	}
+})
+export default store

+ 335 - 0
store/modules/moduleMqtt.js

@@ -0,0 +1,335 @@
+import mqtt from '../../common/mqtt.js'
+import Vue from 'vue'
+import parse from './mqttParse.js'
+//import mqtt from 'mqtt/dist/mqtt.js'
+
+const options = {
+	clientId: "wx_" + parseInt(Math.random() * 100 + 800, 10),
+	keepalive: 28,//每28秒发送一次心跳
+	connectTimeout: 15 * 1000,//连接超时的时间
+};
+
+
+let url = 'wss://mqtt.test.radio1964.com'
+
+// #ifdef MP-WEIXIN
+url = 'wxs://mqtt.test.radio1964.com'
+// #endif
+
+
+
+//订阅设备在线状态
+function subscribeDevicesStatus(dispatch,deviceList){
+	deviceList.forEach((value) =>{
+		let topic = `/${value.uuid}/status/onoffline`
+		dispatch('subscribe',topic)
+	})
+}
+
+//订阅消息
+function subscribeCurrDevice(dispatch,device){
+	if(device){
+		let topic = `/${device.uuid}/user/pub_response`
+		dispatch('subscribe',topic)
+	}
+}
+
+export default {
+	namespaced: true,
+	state: {
+		//当前选择的设备
+		currentDevice: null,
+		//用户设备列表
+		deviceList: [],
+		//mqtt客户端
+		mqttClient: null,
+		//收到的消息的历史记录
+		msgList:[],
+		//设备的播放信息
+		playInfo:{
+			title:"",
+			artist:"",
+			albumURI:"",
+			songInfoID:"",
+			songAlbumID:"",
+			Duration:0,
+			channel:1,
+			audioType:1,
+			PlayState:3,
+			PlayMode:0
+
+		}
+
+	},
+	getters: {
+		//mqtt连接状态
+		connected: state => {
+			return state.mqttClient && state.mqttClient.connected
+		},
+
+	},
+	mutations: {
+		//添加设备
+		addDevice(state, device) {
+			state.deviceList.push(device)
+		},
+		//设置当前选择的设备
+		setCurrentDevice(state, device) {
+			//state.currentDevice = device
+			Vue.set(state,'currentDevice',device)
+		},
+		//移除设备
+		removeDevice(state, device) {
+			let index = state.deviceList.indexOf(device)
+			state.deviceList.slice(index, 1)
+		},
+		//连接mqtt服务器 clientId为wx_加上用户的uid
+		connect(state, clientId) {
+			console.log(`connect clientId = ${clientId}`);
+			options.clientId = clientId
+			state.mqttClient = mqtt.connect(url, options)
+		},
+		//token失效,退出登录,需要清除数据
+		release(state) {
+			state.deviceList = []
+			state.currentDevice = null
+			state.mqttClient = null
+		},
+		//更新设备信息
+		updateDevice(state,data){
+			Vue.set(state.deviceList,data.index,data.device)
+		},
+		//更新播放信息
+		updatePlayInfo(state,playinfo){
+			state.playInfo = playinfo
+		}
+
+	},
+	actions: {
+		//添加设备
+		addDevice({state, dispatch,commit, getters },data){
+			let clientId = (data.clientId) ? data.clientId : options.clientId
+			let device = data.device
+
+			console.log(`deviceList = ${state.deviceList} device = ${device}`);
+			//判断设备是否存在deviceList中
+			let findDevice = state.deviceList.find((item) =>{
+				return item.uuid === device.uuid
+			})
+			if(findDevice == null){
+				if(state.deviceList.length === 0){
+					//设置当前的设备为添加的设备
+					commit('setCurrentDevice',device)
+				}
+				//添加设备
+				state.deviceList.push(device)
+				//如果已经连接
+				if(getters.connected){
+					//订阅设备是否在线
+					subscribeDevicesStatus(dispatch,[device])
+					if(state.currentDevice && state.currentDevice.uuid === device.uuid){
+						//订阅当前设备的消息
+						subscribeCurrDevice(dispatch,state.currentDevice)
+					}
+
+				}else{//如果没有连接
+					//连接服务器
+					dispatch('connect',clientId)
+				}
+				
+			}
+		},
+		//删除设备
+		removeDevice({state, dispatch,commit, getters },device){
+			//判断设备是否存在deviceList中
+			let findDevice = state.deviceList.find((item) =>{
+				return item.uuid === device.uuid
+			})
+			if(findDevice){
+				//取消此设备的订阅
+				let topic = `/${device.uuid}/status/onoffline`
+				dispatch('unsubscribe',topic)
+				topic = `/${device.uuid}/status/pub_response`
+				dispatch('unsubscribe',topic)
+				//在设备列表中删除此设备
+				commit('removeDevice',findDevice)
+				//如果删除的是选中的设备
+				if(currentDevice && state.currentDevice.uuid == device.uuid){
+					//清除当前选中的设备 todo 还是自动选中在线的设备
+					commit('setCurrentDevice',null)
+				}
+				
+				if(state.deviceList.length === 0 ){//所有的设备都已经删除,关闭连接
+					dispatch('disconnect')
+				}
+			}
+		},
+
+		//连接设备
+		connect({ state, dispatch,commit, getters }, clientId) {
+			if (state.mqttClient == null) {
+				//连接服务器
+				commit('connect', clientId)
+				//连接成功回调
+				state.mqttClient.on("connect", function () {
+					console.log("connect success!");
+					//订阅设备是否在线
+					subscribeDevicesStatus(dispatch,state.deviceList)
+					//订阅当前设备的消息
+					subscribeCurrDevice(dispatch,state.currentDevice)
+				});
+
+				//异常回调
+				state.mqttClient.on("error", function (err) {
+					console.log(err);
+				});
+
+				state.mqttClient.on('offline', function () {
+
+					console.log(`offline callback`);
+				})
+
+				state.mqttClient.on('end', function () {
+
+					console.log(`end callback`);
+				})
+				//网络断开的回调
+				state.mqttClient.on('close', function () {
+					console.log(`close callback`);
+				})
+				//收到消息的回调
+				state.mqttClient.on('message', function (topic, message) {
+					console.log(`message = ${message.toString()}`);
+					//解析收到的消息
+					parse({state, dispatch,commit, getters },topic,message)
+					
+					//这是测试的
+					state.msgList.push({
+						topic: topic.toString(),
+						msg: message.toString(),
+					});
+					
+
+
+				})
+			}else{
+				if(!state.mqttClient.connected){
+					console.log("reconnect");
+					state.mqttClient.reconnect()
+					//重连成功的回调
+					state.mqttClient.on("reconnect", function() {
+						console.log("reconnect success!");
+						
+					});
+				}
+			}
+		},
+		//断开mqtt的连接
+		disconnect({state,commit},isRelease) {
+			console.log('disconnect')
+			if (state.mqttClient) {
+				state.mqttClient.end(false,() =>{
+					console.log(`state.mqttClient.connected = ${state.mqttClient.connected}`);
+				})
+			}
+			if(isRelease){
+				//清除数据
+				commit('release')
+			}
+		},
+		//发布消息
+		publishWithType({state, dispatch,getters},payload){
+			//console.log(payload);
+			//console.log(options);
+			let type = payload.mqttType;
+			let other = payload.other;
+			if(!getters.connected){
+				return new Promise((resolve, reject) => {
+					reject('连接已断开')
+				});
+			}else if(!(state.currentDevice && state.currentDevice.online)){
+				return new Promise((resolve, reject) => {
+					reject('设备已经离线')
+				});
+			}else{
+				let topic = `/${state.currentDevice.uuid}/user/sub_control`;
+				//console.log(payload);
+				//console.log(payload.mqttType);
+				let json = {
+				  'DstDeviceName':state.currentDevice.uuid,
+				  'SrcDeviceName':options.clientId,
+				  'type':type,
+				}
+				if(other){
+					json.other = other
+				}
+				let payload = JSON.stringify(json)
+				console.log(`publishWithType topic = ${topic} payload = ${payload}`);
+				return dispatch('publish',{topic,payload})
+
+			}
+
+		},
+
+		//发布消息
+		publish({ state, commit, getters }, message) {
+			console.log('publish');
+			console.log(message);
+			return new Promise((resolve, reject) => {
+				if (getters.connected) {
+					//发布消息
+					state.mqttClient.publish(message.topic, message.payload, (err) => {
+						if (err) {
+							console.warn(err);
+							reject(err)
+						} else {
+							resolve()
+						}
+					})
+				} else {
+					reject("mqttClient is not connected")
+				}
+			})
+		},
+		//订阅消息
+		subscribe({ state, commit, getters }, topic) {
+			console.log(`subscribe topic = ${topic}`);
+			return new Promise((resolve, reject) => {
+				if (getters.connected) {
+					//发布消息
+					state.mqttClient.subscribe(topic, (err) => {
+						if (err) {
+							console.log(err);
+							reject(err)
+						} else {
+							resolve()
+						}
+					})
+				} else {
+					reject("mqttClient is not connected")
+				}
+			})
+		},
+		//解除订阅消息
+		unsubscribe({ state, commit, getters }, topic){
+			console.log(`unsubscribe topic = ${topic}`);
+			return new Promise((resolve, reject) => {
+				if (getters.connected) {
+					//发布消息
+					state.mqttClient.unsubscribe(topic, (err) => {
+						if (err) {
+							console.log(err);
+							reject(err)
+						} else {
+							resolve()
+						}
+					})
+				} else {
+					reject("mqttClient is not connected")
+				}
+			})
+		}
+
+	}
+
+}

+ 160 - 0
store/modules/mqttParse.js

@@ -0,0 +1,160 @@
+function isEmpty(str) {
+    if (str) {
+        return true
+    }
+    if (!str.length) {
+        return true
+    }
+    if (str == 'unknow') {
+        return true
+    }
+    return false
+
+}
+
+/**
+ * 收到设备在线和离线的消息
+ * @param {*} param0 
+ * @param {*} topic 
+ * @param {*} message 
+ */
+function receiveOnOffline({ state, dispatch, commit }, fromDeviceUuid, message) {
+
+    let index = -1
+    for (var i = 0; i < state.deviceList.length; i++) {
+        if (state.deviceList[i].uuid === fromDeviceUuid) {
+            index = i;
+            break
+        }
+    }
+    console.log(`state index = ${index}`);
+    if (index >= 0) {
+        let stateJson = JSON.parse(message)
+        //console.log(stateJson);
+        let device = state.deviceList[index];
+        //设备在线
+        if (stateJson.state === 'online') {
+            device.online = true
+            //更新设备状态信息
+            commit('updateDevice', { index, device })
+            if (state.currentDevice && state.currentDevice.uuid == device.uuid) { //当前设备在线
+                //延时获取设备信息和播放信息
+                setTimeout(async function () {
+                    await dispatch({
+                        type: 'publishWithType',
+                        mqttType: 'get_dev_info',
+                    })
+                    dispatch({
+                        type: "publishWithType",
+                        mqttType: "get_position",
+                    })
+                }, 200);
+
+            }
+        } else {
+            device.online = false
+            //更新设备状态信息
+            commit('updateDevice', { index, device })
+        }
+        //发送通知
+        uni.$emit('mqtt_onoffline', stateJson)
+        // state.deviceList.forEach(element => {
+        // 	console.log(`online = ${element.online}`);
+        //   });
+    }
+}
+
+/**
+ * 更新播放信息
+ * @param {*} param0 
+ * @param {*} jsonPayload 
+ */
+function updatePlayInfo({ commit }, jsonPayload) {
+    if (jsonPayload.other) {
+        let playinfo = jsonPayload.other
+
+        if (isEmpty(playinfo.title)) {
+            playinfo.title = '猫王妙播'
+        }
+        if (isEmpty(playinfo.albumURI)) {
+            playinfo.albumURI = "https://airsmart-photo1.oss-cn-shanghai.aliyuncs.com/wx/bg_place_holder.png"
+        }
+
+        commit('updatePlayInfo', playinfo)
+    }
+}
+
+
+
+/**
+ * 解析收到的消息
+ * @param {*} param0 
+ * @param {*} topic     主题
+ * @param {*} message   playload
+ */
+function messageParse({ state, dispatch, commit, getters }, topic, message) {
+    //console.log(`message = ${message.toString()}`);
+
+    let arr = topic.split("/");
+    if (arr.length > 1) {
+        let fromDeviceUuid = arr[1]
+        //设备在线和离线的消息
+        if (topic.endsWith('onoffline')) {
+            receiveOnOffline({ state, dispatch, commit }, fromDeviceUuid, message)
+        }else {
+            //收到设备发过来的数据
+            try {
+                let jsonObj = JSON.parse(message.toString())
+                if (jsonObj.type) {
+                    //收到播放信息
+                    if (jsonObj.type == 'get_position') {
+                        //判断是否是当前选中的设备
+                        if (state.currentDevice && state.currentDevice.uuid == fromDeviceUuid) {
+                            //更新播放信息
+                            updatePlayInfo({ commit }, jsonObj)
+                        }
+                    } else if (jsonObj.type == 'get_dev_info') {
+                        //更新设备信息
+                        updateDeviceInfo({ state,commit }, jsonObj)
+                    }
+                }
+            } catch (e) {
+                console.warn(e);
+            }
+
+
+        }
+    }
+}
+
+function updateDeviceInfo({ state, commit }, jsonPayload) {
+    let index = -1
+    for (var i = 0; i < state.deviceList.length; i++) {
+        if (state.deviceList[i].uuid === jsonPayload.SrcDeviceName) {
+            index = i;
+            break
+        }
+    }
+    console.log(`state index = ${index}`);
+    if (index >= 0 && jsonPayload.other) {
+        let device = state.deviceList[index];
+        let nowDevice = jsonPayload.other
+        nowDevice.uuid = device.uuid
+        nowDevice.online = device.online
+        console.log(nowDevice);
+        //更新设备状态信息
+        commit('updateDevice', { index, device:nowDevice })
+        state.deviceList.forEach(element => {
+        	console.log(element);
+          });
+        //更新当前的设备状态信息
+        if(state.currentDevice && state.currentDevice.uuid === device.uuid){
+            commit('setCurrentDevice', nowDevice)
+        }
+
+    }
+}
+
+
+
+module.exports = messageParse

+ 76 - 0
uni.scss

@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:24rpx;
+$uni-font-size-base:28rpx;
+$uni-font-size-lg:32rpx;
+
+/* 图片尺寸 */
+$uni-img-size-sm:40rpx;
+$uni-img-size-base:52rpx;
+$uni-img-size-lg:80rpx;
+
+/* Border Radius */
+$uni-border-radius-sm: 4rpx;
+$uni-border-radius-base: 6rpx;
+$uni-border-radius-lg: 12rpx;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 10px;
+$uni-spacing-row-base: 20rpx;
+$uni-spacing-row-lg: 30rpx;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 8rpx;
+$uni-spacing-col-base: 16rpx;
+$uni-spacing-col-lg: 24rpx;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:40rpx;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:36rpx;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:30rpx;