houjie 4 years ago
parent
commit
ea2f376dc3

+ 78 - 5
common/httpClient.js

@@ -30,10 +30,10 @@ function post(request) {
 					console.log(response);
 					let resJson = JSON.parse(response.data) //转化成json对象
 					console.log(resJson);
-					if(resJson.code === 0){
+					if (resJson.code === 0) {
 						let base64Str = resJson.data;
 						//let base64Str = 'DQMAAAAVAQAAAB0CAAAAJeoDAABKlgEKBBICb2sSjQFleUpoYkdjaU9pSklVekkxTmlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKVmMyVnlhV1FpT2pZNE5EQXhNQ3dpYm1GdFpTSTZJalFpTENKbGVIQWlPakUyTWpNME9EWTRNamg5LlNzRlgzdWJ5UFp4NWRCLXJzZHNDd3NGcjlpLUpJczFXeWJBS2pBS3pwdzQ='
-						let buffer = Buffer.from(base64Str,'base64')
+						let buffer = Buffer.from(base64Str, 'base64')
 						let data = responseDecode(buffer)
 						console.log(data);
 						let code = 0
@@ -51,13 +51,13 @@ function post(request) {
 							msg: msg,
 							data: data
 						}
-					}else{
+					} else {
 						return {
 							code: resJson.code,
 							msg: resJson.info
 						}
 					}
-					
+
 				} catch (e) {
 					return {
 						code: -1,
@@ -70,4 +70,77 @@ function post(request) {
 
 }
 
-module.exports = post
+
+const policy =
+	'{"expiration": "2120-01-01T12:00:00.000Z","conditions": [["content-length-range", 0, 104857600]]}';
+
+const OSSAccessKeyId = "LTAICUWe9r9tRcPB";
+
+const accessKeySecret = "gCZSuqdztp6X1n2xMaCqzhnBSWVJv6";
+
+//这个是policy编码成base64,使用base64编码的方法需要引入require("crypto-js"); 一下子多了300多kb,所以这里直接写死
+const policyBase64 = "eyJleHBpcmF0aW9uIjogIjIxMjAtMDEtMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsIDAsIDEwNDg1NzYwMF1dfQ=="
+
+//这个是policyBase64经过accessKeySecret的HmacSHA1 加密,然后再base64,需要引入require("crypto-js"); 一下子多了300多kb,所以这里直接写死
+const signature = "h2Kwvb9pXKgtWUuw99iHrHQV9fk="
+
+const OSS_URL = 'https://airsmart-photo1.oss-cn-shanghai.aliyuncs.com'
+
+/**
+ * 这个是加密的方法
+ * 参考用的
+ */
+function HmacSHA1test() {
+	//const HmacSHA1 = require("crypto-js/hmac-sha1");
+
+	let policyBase64 = util.encodeBase64(policy)
+	console.log(policyBase64);
+
+	// let str = util.decodeBase64(policyBase64);
+	// console.log(str);
+
+	//获取加密参数
+	const sha1 = HmacSHA1(policyBase64, accessKeySecret);
+	console.log(`sha1 = ${sha1}`);
+	const sign = base64.stringify(sha1);
+	console.log(`sign = ${sign}`);
+}
+
+function uploadFile(filePath, contentType) {
+	let arr = filePath.split('.')
+	let suffix = arr[arr.length - 1]
+	let date = new Date();
+	let dateStr = `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`
+	let ossPath = `wx/${dateStr}/${date.getTime()}.${suffix}`
+	return uni.uploadFile({
+		url: OSS_URL,
+		filePath: filePath,
+		name: 'file',
+		formData: {
+			'key': ossPath,
+			'success_action_status': 200,///如果该域的值设置为200或者204,OSS返回一个空文档和相应的状态码。
+			'OSSAccessKeyId': OSSAccessKeyId,
+			'policy': policyBase64,
+			'Signature': signature,
+			'Content-Type': contentType,
+		}
+	}).then((res) => {
+		console.log(res);
+		if (res[1].statusCode === 200) {
+			return `${OSS_URL}/${ossPath}`
+		} else {
+			throw new Error(`uoload failed statusCode :${res[1].statusCode}`)
+		}
+	});
+
+	//'image/jpeg'
+}
+
+
+
+
+module.exports = {
+	post: post,
+	uploadFile: uploadFile,
+
+}

+ 11 - 0
common/libMqtt.js

@@ -0,0 +1,11 @@
+import mqtt from './mqtt.js'
+
+const options = {
+	clientId: "wx_" + parseInt(Math.random() * 100 + 800, 10),
+	keepalive: 28,//每28秒发送一次心跳
+	connectTimeout: 15 * 1000,//连接超时的时间
+};
+
+
+
+module.exports = $root;

+ 2 - 29
common/mqtt.js

@@ -1309,7 +1309,6 @@ MqttClient.prototype._resubscribe = function (connack) {
  */
 MqttClient.prototype._onConnect = function (packet) {
   if (this.disconnected) {
-    console.log('MqttClient.prototype._onConnect this.emit(connect')
     this.emit('connect', packet)
     return
   }
@@ -1525,7 +1524,6 @@ function buildStream (client, opts) {
   setDefaultOpts(opts)
 
   var url = buildUrl(opts, client)
-  console.log(`buildStream url = ${url}`)
   my = opts.my
   my.connectSocket({
     url: url,
@@ -1718,15 +1716,12 @@ var stream
 function buildProxy () {
   var proxy = new Transform()
   proxy._write = function (chunk, encoding, next) {
-    //console.log(`发送数据`)
     socketTask.send({
       data: chunk.buffer,
       success: function () {
-        console.log(`发送数据成功 chunk = ${chunk}`)
         next()
       },
       fail: function (errMsg) {
-        //console.log(`发送数据失败 errMsg = ${errMsg}`)
         next(new Error(errMsg))
       }
     })
@@ -1734,12 +1729,9 @@ function buildProxy () {
   proxy._flush = function socketEnd (done) {
     socketTask.close({
       success: function () {
-        console.log('proxy._flush socketTask.close success')
         done()
       },
       fail:function(err){
-        console.log('proxy._flush socketTask.close fail')
-        console.log(err)
       }
     })
   }
@@ -1774,7 +1766,6 @@ function buildUrl (opts, client) {
 
 function bindEventHandler () {
   socketTask.onOpen(function () {
-    console.log("socketTask.onOpen")
     stream.setReadable(proxy)
     stream.setWritable(proxy)
     stream.emit('connect')
@@ -1789,7 +1780,6 @@ function bindEventHandler () {
     else{ 
       data = Buffer.from(data, 'utf8')
     }
-    //console.log(`socketTask.onMessage data = ${data}`)
     proxy.push(data)
   })
 
@@ -1822,35 +1812,26 @@ function buildStream (client, opts) {
   setDefaultOpts(opts)
 
   var url = buildUrl(opts, client)
-  console.log(`buildStream111 url = ${url}`)
   socketTask = wx.connectSocket({
     url: url,
     protocols: websocketSubProtocol,
     success(res){
-      console.log('服务器连接成功');
     },
     fail(err){
-      console.log('服务器连接失败');
     }
   })
 
   proxy = buildProxy()
   stream = duplexify.obj()
   stream._destroy = function (err, cb) {
-    console.log('____socketTask.close')
     socketTask.close({
       success: function () {
-        console.log('___socketTask.close success')
-        //cb && cb(err)
+        cb && cb(err)
       },
       fail:function (err) {
-        console.log('___socketTask.close failed')
-        console.log(err)
       },
         complete:function(){
-          console.log('___socketTask.close complete')
-		  console.log(cb)
-          cb && cb(err)
+          //cb && cb(err)
         }
     })
   }
@@ -1860,17 +1841,11 @@ function buildStream (client, opts) {
     stream.destroy = destroyRef
 
     var self = this
-    console.log('process.nextTick socketTask.close')
     process.nextTick(function () {
-      console.log('socketTask.close')
       socketTask.close({
         success:function(){
-			//that.emit('close')
-          console.log('socketTask.close success')
         },
         fail: function (err) {
-          console.log('socketTask.close fail')
-          console.log(err)
           self._destroy(new Error())
         }
       })
@@ -1978,7 +1953,6 @@ Store.prototype.createStream = function () {
     destroyed = true
 
     process.nextTick(function () {
-      console.log(`process.nextTick(function () {`)
       self.emit('close')
     })
   }
@@ -11555,7 +11529,6 @@ Transform.prototype._destroy = function (err, cb) {
 
   Duplex.prototype._destroy.call(this, err, function (err2) {
     cb(err2);
-    console.log(`.call(this, err, function (err2) {`)
     _this2.emit('close');
   });
 };

+ 7 - 13
common/util.js

@@ -76,15 +76,6 @@ var dateUtils = {
 };
 
 
-
-
-
-// function encodeBase64(test){
-// 	let wordArray = utf8.parse(test);
-	
-// 	return base64.stringify(wordArray);
-// }
-
 function encodeBase64(text){
 	let res
 	// #ifdef MP-WEIXIN
@@ -100,15 +91,17 @@ function encodeBase64(text){
 }
 
 function decodeBase64(text){
-	let buffer 
+	let resStr 
 	// #ifdef MP-WEIXIN
-	res = uni.base64ToArrayBuffer(text)
+	let buffer = uni.base64ToArrayBuffer(text)
+	resStr = Buffer.from(buffer).toString();
 	// #endif
 
 	// #ifndef MP-WEIXIN
-	
-	res = base64.stringify(wordArray);
+	let wordArray = base64.parse(text);
+	resStr =  utf8.stringify(wordArray)
 	// #endif
+	return resStr
 }
 
 
@@ -118,4 +111,5 @@ module.exports = {
 	formatLocation: formatLocation,
 	dateUtils: dateUtils,
 	encodeBase64:encodeBase64,
+	decodeBase64:decodeBase64,
 }

+ 64 - 0
components/deviceCard/deviceCard.vue

@@ -0,0 +1,64 @@
+<template>
+  <view class="box">
+    <view class="topBox">
+      <text>{{device.name}}</text>
+      <text>{{device.online ? '在线':'离线'}}</text>
+    </view>
+    <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: {},
+  watch: {},
+
+  // 组件周期函数--监听组件挂载完毕
+  mounted() {},
+
+  // 组件周期函数--监听组件激活(显示)
+  activated() {},
+  // 组件周期函数--监听组件停用(隐藏)
+  deactivated() {},
+  // 组件周期函数--监听组件销毁之前
+  beforeDestroy() {},
+};
+</script>
+
+<style>
+.box{
+  display: flex;
+  flex-direction:column;
+  justify-content: space-between;
+  width: 35vw;
+  height: 22vw;
+  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 - 8
components/hello/hello.vue

@@ -1,22 +1,29 @@
 <template>
-  <view class="hello">hello</view>
+  <view class="hello" >
+    <input type="text" v-model="text" class="edittext"/>
+    <button @click="sendName" type="primary"  style="margin-top: 20rpx">发送名称</button>
+  </view>
 </template>
 
 <script>
 
 export default {
   props: {},
-  data: () => ({}),
+  data: () => ({
+    text:''
+  }),
   computed: {},
-  methods: {},
+  methods: {
+    sendName(){
+      uni.$emit('updateName',this.text)
+    }
+
+  },
   watch: {},
 
   // 组件周期函数--监听组件挂载完毕
   mounted() {},
-  // 组件周期函数--监听组件数据更新之前
-  beforeUpdate() {},
-  // 组件周期函数--监听组件数据更新之后
-  updated() {},
+
   // 组件周期函数--监听组件激活(显示)
   activated() {},
   // 组件周期函数--监听组件停用(隐藏)
@@ -26,4 +33,9 @@ export default {
 };
 </script>
 
-<style></style>
+<style>
+	.edittext {
+		display: flex;
+		border: 1rpx solid #333333;
+	}
+</style>

+ 41 - 0
components/someOne/someOne.vue

@@ -0,0 +1,41 @@
+<template>
+  <view class="some-one">
+    WHO AM I?
+    <view> I AM {{name}}</view>
+  </view>
+</template>
+
+<script>
+
+export default {
+  props: {},
+  data: () => ({
+    name:''
+  }),
+  computed: {},
+  methods: {
+    nameCallback(name){
+      console.log(`nameCallback name = ${name}`);
+      this.name = name
+    }
+  },
+  watch: {},
+  created(){
+    uni.$on("updateName",this.nameCallback)
+  },
+
+  // 组件周期函数--监听组件挂载完毕
+  mounted() {},
+
+  // 组件周期函数--监听组件激活(显示)
+  activated() {},
+  // 组件周期函数--监听组件停用(隐藏)
+  deactivated() {},
+  // 组件周期函数--监听组件销毁之前
+  beforeDestroy() {
+    uni.$off("updateName",this.nameCallback)
+  },
+};
+</script>
+
+<style></style>

+ 8 - 7
pages.json

@@ -1,12 +1,5 @@
 {
 	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
-		//mqtt页面
-		{
-			"path": "pages/index/index",
-			"style": {
-				"navigationBarTitleText": "IOT"
-			}
-		},
 		//设备页面
 		{
             "path" : "pages/index/device",
@@ -16,6 +9,14 @@
             }
             
         },
+		//mqtt页面
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "IOT"
+			}
+		},
+		
 		//我的页面
 		{
 		    "path" : "pages/index/mine",

+ 77 - 93
pages/index/device.vue

@@ -1,108 +1,77 @@
 <template>
   <view class="content">
-    <button @click="godoBle">添加设备</button>
-
-    <button @click="chooseImage">上传图片</button>
+    <button @click="addDevice">添加设备</button>
+
+    <button @click="getPlayInfo">获取播放信息</button>
+    <button @click="getDeviceInfo">获取设备信息</button>
+    <view class="dbox">
+      <device-card
+        v-for="(device, index) in deviceList"
+        :key="index"
+        :device="device"
+      ></device-card>
+      <!-- <text v-for="(device, index) in deviceList" :key="index" space="ensp"
+        >{{ device.name }} {{ device.online }}</text
+      > -->
+    </view>
   </view>
 </template>
 
 <script>
-import { common } from "../../proto/bundle.js";
-
-//const CryptoJS = require("crypto-js");
-const HmacSHA1 = require("crypto-js/hmac-sha1");
-const base64 = require("crypto-js/enc-base64");
-const utf8 = require("crypto-js/enc-utf8");
-
-const policy =
-  '{"expiration": "2120-01-01T12:00:00.000Z","conditions": [["content-length-range", 0, 104857600]]}';
-
-const OSSAccessKeyId = "LTAICUWe9r9tRcPB";
-
-const accessKeySecret = "gCZSuqdztp6X1n2xMaCqzhnBSWVJv6";
-
-let wordArray = utf8.parse(policy);
-const policyBase64 = base64.stringify(wordArray);
-
-//获取加密参数
-const sha1 = HmacSHA1(policyBase64, accessKeySecret);
-console.log(`sha1 = ${sha1}`);
-const signature = base64.stringify(sha1);
-console.log(`sign = ${signature}`);
-
-
-
+import deviceCard from "../../components/deviceCard/deviceCard.vue";
+import { mapState } from "vuex";
 export default {
+  components: { deviceCard },
   data() {
-    return {};
+    return {
+      //deviceList:[]
+    };
+  },
+  onLoad() {
+    //this.deviceList = this.$store.state.moduleMqtt.deviceList;
+    uni.$on("mqtt_onoffline", this.onofflineCallback);
+  },
+  onUnload() {
+    uni.$off("mqtt_onoffline", this.onofflineCallback);
   },
-  onLoad() {},
   methods: {
-    //选择图片
-    chooseImage() {
-      let that = this;
-
-      uni.chooseImage({
-        count: 1,
-        success(res) {
-          console.log(res);
-          let file = res.tempFiles[0];
-          console.log(file);
-		  that.wxUploadImage(file)
-        },
-      });
+    onofflineCallback(value) {
+      console.log("onofflineCallback");
     },
-    //上传图片
-    wxUploadImage(file) {
-      uni.uploadFile({
-        url: "https://airsmart-photo1.oss-cn-shanghai.aliyuncs.com",
-        filePath: file.path,
-        name: 'file',
-        formData: {
-          'key': "wx/test3.png",
-		  'success_action_status':200,///如果该域的值设置为200或者204,OSS返回一个空文档和相应的状态码。
-      	  'OSSAccessKeyId': OSSAccessKeyId,
-      	  'policy': policyBase64,
-          'Signature':signature,
-          'Content-Type':'image/jpeg',
-        },
-        success: (uploadFileRes) => {
-			console.log("上传成功");
-          console.log(uploadFileRes);
+    //测试添加设备
+    addDevice() {
+      //添加设备
+      this.$store.dispatch({
+        type: "moduleMqtt/addDevice",
+        clientId: "wx_18126447015",
+        device: {
+          name: "MW-M3",
+          uuid: "89860474192070498495",
         },
-		fail(err){
-			console.log(err);
-		}
       });
     },
-    
-    goCompent3() {
-      uni.navigateTo({ url: "../test/component3/component3" });
-    },
-    test1() {
-      let errinfo = common.ErrorInfo.create({
-        errorCode: 0,
-        errorMessage: Buffer.from("成功"),
+    //获取播放信息
+    getPlayInfo() {
+      this.$store.dispatch({
+        type: "moduleMqtt/publishWithType",
+        mqttType: "get_position",
+      }).then((result) => {
+        
+      }).catch((err) => {
+        console.warn(err);
       });
-
-      //把ErrorInfo对象 编码成Uint8Array (browser) or Buffer (node)
-      let errBuffer = common.ErrorInfo.encode(errinfo).finish();
-
-      //把Uint8Array (browser) 或者 Buffer (node) 解码成ErrorInfo对象
-      let message = common.ErrorInfo.decode(errBuffer);
-
-      //转化为一个对象
-      let obj = common.ErrorInfo.toObject(message, {
-        enums: String, // enums as string names
-        longs: String, // longs as strings (requires long.js)
-        //bytes: String,
+    },
+    //获取播放信息
+    getDeviceInfo() {
+      this.$store.dispatch({
+        type: "moduleMqtt/publishWithType",
+        mqttType: "get_dev_info",
+      }).then((result) => {
+        
+      }).catch((err) => {
+        console.warn(err);
+        
       });
-      console.log(obj);
-      let buf = Buffer.from(obj.errorMessage);
-      console.log(`errorMessage = ${buf.toString()}`);
-      // let buf = Buffer.from(obj.errorMessage)
-      // console.log(buf);
-      // console.log(buf.toString());
     },
     godoBle() {
       //添加设备
@@ -122,15 +91,30 @@ export default {
       // #endif
     },
   },
+  computed: {
+    // deviceList() {
+    //   return this.$store.state.moduleMqtt.deviceList;
+    // },
+    ...mapState({
+      deviceList: (state) => state.moduleMqtt.deviceList,
+    }),
+  },
 };
 </script>
 
 <style>
 .content {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
   margin: 20rpx;
 }
+.dbox {
+  display: flex;
+  flex-direction: row;
+}
+.text1 {
+  width: 150px;
+  word-break: break-all;
+  /* text-overflow:ellipsis;
+  overflow: hidden;
+  white-space: nowrap; */
+}
 </style>

+ 179 - 190
pages/index/index.vue

@@ -1,205 +1,194 @@
 <template>
-	<view class="content">
-		<view>
-			<button type="primary" @click="connectOrDisconnect">{{connectText}}</button>
-		</view>
-		<!-- <image class="logo" src="/static/logo.png"></image> -->
-		<text class="title">发布消息</text>
-		<view class="br"></view>
-		<view class="text-area">
-			<text>Topic</text>
-			<input class="edittext" v-model="publishTopic" />
-		</view>
-		<view class="br"></view>
-		<view class="text-area">
-			<text>Payload</text>
-			<textarea class="edittext" v-model="publishMsg" />
-		</view>
-		<view class="br"></view>
-		<button @click="publish">publish</button>
+  <view class="content">
+    <view>
+      <button type="primary" @click="connectOrDisconnect">
+        {{ connectText }}
+      </button>
+      <button type="primary" @click="addDevice">添加设备</button>
+    </view>
+    <!-- <image class="logo" src="/static/logo.png"></image> -->
+    <text class="title">发布消息</text>
+    <view class="br"></view>
+    <view class="text-area">
+      <text>Topic</text>
+      <input class="edittext" v-model="publishTopic" />
+    </view>
+    <view class="br"></view>
+    <view class="text-area">
+      <text>Payload</text>
+      <textarea class="edittext" v-model="publishMsg" />
+    </view>
+    <view class="br"></view>
+    <button @click="publish">publish</button>
 
-		<text class="title">订阅主题</text>
-		<view class="text-area">
-			<!-- 订阅 -->
-			<input class="edittext" v-model="subscribeTopic" />
-		</view>
-		<view class="br"></view>
-		<button @click="subscribe">subscribe</button>
-		<view class="br"></view>
-		<text class="title">收到的Message</text>
-		<view class="br"></view>
-		<view v-for="(item, index) in subList" :key="index">
-			<text>Topic:{{ item.topic }} ,Payload: {{ item.msg }}</text>
-		</view>
-		<view>
-
-		</view>
-	</view>
+    <text class="title">订阅主题</text>
+    <view class="text-area">
+      <!-- 订阅 -->
+      <input class="edittext" v-model="subscribeTopic" />
+    </view>
+    <view class="br"></view>
+    <button @click="subscribe">subscribe</button>
+    <view class="br"></view>
+    <text class="title">收到的Message</text>
+    <view class="br"></view>
+    <view v-for="(item, index) in subList" :key="index">
+      <text>Topic:{{ item.topic }} ,Payload: {{ item.msg }}</text>
+    </view>
+    <view> </view>
+  </view>
 </template>
 
 <script>
-	//import store from '@/store/index.js';//需要引入store
-	export default {
-		data() {
-			return {
-				publishMsg: "",
-				publishTopic: "",
-				subscribeTopic: "",
-			}
-		},
-		onLoad() {
-			console.log('index.vue onload')
-		},
-		created() {
-			console.log('index.vue created')
-		},
-		computed: {
-			connectText() {
-				return (this.$store.getters['moduleMqtt/connected']) ? '断开' : '连接'
-			},
-			subList() {
-				return this.$store.state.moduleMqtt.msgList
-			},
-		},
-		methods: {
-			publish() { //发布
-				let that = this;
-				//发布消息
-				that.$store.dispatch({
-					type: 'moduleMqtt/publish',
-					topic: that.publishTopic,
-					payload: that.publishMsg,
-				}).then(() => {/* success */}, (err) =>{
-					console.log(err);
-					uni.showToast({
-						title: "发布消息失败",
-						duration: 2000,
-						icon: "none"
-					});
-				});
-			},
-			subscribe() { //订阅
-				let that = this;
-				//订阅消息
-				that.$store.dispatch('moduleMqtt/subscribe', that.subscribeTopic)
-					.then(() => {
-							uni.showToast({
-								title: `订阅${that.subscribeTopic}主题成功`,
-								duration: 2000,
-								icon: "none"
-							})
-						},
-						(err) => {
-							uni.showToast({
-								title: `订阅${that.subscribeTopic}主题失败`,
-								duration: 2000,
-								icon: "none"
-							})
-						});
-			},
-			connectOrDisconnect() { //连接或者断开mqtt服务
-				if (this.$store.getters['moduleMqtt/connected'] === false) {
-					//连接mqtt服务器
-					this.$store.commit('moduleMqtt/connect')
-				} else {
-					//断开连接
-					this.$store.commit('moduleMqtt/disconnect')
-				}
-			},
-			connectWithWxApi(){
-				uni.connectSocket({
-					url:'wss://mqtt.test.radio1964.com',
-					success(res){
-						console.log('连接成功');
-					},
-					fail(err){
-						console.log('连接失败');
-					}
-				});
-				uni.onSocketOpen(function(res){
-					console.log('onSocketOpen');
-					console.log(res);
-				})
-				uni.onSocketClose(function(res){
-					console.log('onSocketClose');
-					console.log(res);
-				})
-				uni.onSocketError(function(err){
-					console.log('err');
-					console.log(err);
-					
-				})
-			},
-			closeSocket(){
-				uni.closeSocket({
-					success() {
-						console.log("closeSocket success");
-					},
-					fail() {
-						console.log("closeSocket fail");
-					}
-				})
-			},
-			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
-
-			}
-		}
-	}
+//import store from '@/store/index.js';//需要引入store
+export default {
+  data() {
+    return {
+      publishMsg: "",
+      publishTopic: "",
+      subscribeTopic: "",
+    };
+  },
+  onLoad() {
+    console.log("index.vue onload");
+  },
+  created() {
+    console.log("index.vue created");
+  },
+  computed: {
+    connectText() {
+      return this.$store.getters["moduleMqtt/connected"] ? "断开" : "连接";
+    },
+    subList() {
+      return this.$store.state.moduleMqtt.msgList;
+    },
+  },
+  methods: {
+    publish() {
+      //发布
+      let that = this;
+      //发布消息
+      that.$store
+        .dispatch({
+          type: "moduleMqtt/publish",
+          topic: that.publishTopic,
+          payload: that.publishMsg,
+        })
+        .then(
+          () => {
+            /* success */
+          },
+          (err) => {
+            console.log(err);
+            uni.showToast({
+              title: "发布消息失败",
+              duration: 2000,
+              icon: "none",
+            });
+          }
+        );
+    },
+    subscribe() {
+      //订阅
+      let that = this;
+      //订阅消息
+      that.$store.dispatch("moduleMqtt/subscribe", that.subscribeTopic).then(
+        () => {
+          uni.showToast({
+            title: `订阅${that.subscribeTopic}主题成功`,
+            duration: 2000,
+            icon: "none",
+          });
+        },
+        (err) => {
+          uni.showToast({
+            title: `订阅${that.subscribeTopic}主题失败`,
+            duration: 2000,
+            icon: "none",
+          });
+        }
+      );
+    },
+    connectOrDisconnect() {
+      //连接或者断开mqtt服务
+      if (this.$store.getters["moduleMqtt/connected"] === false) {
+        //连接mqtt服务器
+        this.$store.commit("moduleMqtt/connect");
+      } else {
+        //断开连接
+        this.$store.dispatch("moduleMqtt/disconnect");
+      }
+    },
+    addDevice() {
+      //添加设备
+      this.$store.dispatch({
+        type: "moduleMqtt/addDevice",
+        clientId: "wx_18126447015",
+        device: {
+          name: "MW-M3",
+          uuid: "89860474192070498495",
+        },
+      });
+    },
+    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
+    },
+  },
+};
 </script>
 
 <style>
-	.content {
-		display: flex;
-		flex-direction: column;
-		/* align-items: center; */
-		justify-content: center;
-		margin: 20rpx;
-	}
+.content {
+  display: flex;
+  flex-direction: column;
+  /* align-items: center; */
+  justify-content: center;
+  margin: 20rpx;
+}
 
-	.logo {
-		height: 200rpx;
-		width: 200rpx;
-		margin-top: 200rpx;
-		margin-left: auto;
-		margin-right: auto;
-		margin-bottom: 50rpx;
-	}
+.logo {
+  height: 200rpx;
+  width: 200rpx;
+  margin-top: 200rpx;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 50rpx;
+}
 
-	.br {
-		height: 10rpx;
-	}
+.br {
+  height: 10rpx;
+}
 
-	.text-area {
-		display: flex;
-		/* justify-content: center; */
-	}
+.text-area {
+  display: flex;
+  /* justify-content: center; */
+}
 
-	.title {
-		font-size: 30rpx;
-		color: #2c3e50;
-		margin: 5rpx;
-	}
+.title {
+  font-size: 30rpx;
+  color: #2c3e50;
+  margin: 5rpx;
+}
 
-	textarea {
-		height: 50rpx;
-	}
+textarea {
+  height: 50rpx;
+}
 
-	.edittext {
-		display: flex;
-		border: 1rpx solid #333333;
-	}
+.edittext {
+  display: flex;
+  border: 1rpx solid #333333;
+}
 </style>

+ 238 - 180
pages/index/mine.vue

@@ -1,199 +1,257 @@
 <template>
-	<view class="content">
-		<button type="default" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumber" v-if="false"
-			@click="login">微信授权一键登录</button>
+  <view class="content">
+    <button
+      type="default"
+      open-type="getPhoneNumber"
+      @getphonenumber="decryptPhoneNumber"
+      v-if="false"
+      @click="login"
+    >
+      微信授权一键登录
+    </button>
 
-		<button @click="httpTest" style="margin-top: 10rps;">h5 axios http请求</button>
+    <button @click="httpTest" style="margin-top: 10rps">
+      h5 axios http请求
+    </button>
 
-		<button @click="wxHttpTest" style="margin-top: 10rps;">miniprogram http请求</button>
-	</view>
+    <button @click="wxHttpTest" style="margin-top: 10rps">
+      miniprogram http请求
+    </button>
+
+    <button @click="chooseImage" style="margin-top: 10rps">上传图片</button>
+  </view>
 </template>
 
 <script>
-	import post from '../../common/httpClient.js'
-	// import {common,	user} from '../../proto/bundle.js';
-	// //const protobuf = require("protobufjs/minimal");
-	// const axios = require('axios');
+import httpClient from "../../common/httpClient.js";
+// import {common,	user} from '../../proto/bundle.js';
+// //const protobuf = require("protobufjs/minimal");
+// const axios = require('axios');
 
-	// let loginMessage = user.login_req.create({
-	// 	phone: '1234567890',
-	// 	type: 1,
-	// 	verifyInfo: '123456a',
-	// })
-	// //proto对象转buffer
-	// let buffer = user.login_req.encode(loginMessage).finish()
-	// console.log(buffer);
-	// let requestMessage = common.MsgWebsocket.create({
-	// 	version: 1,
-	// 	app: 1,
-	// 	server: 2,
-	// 	servant: 1005,
-	// 	data: buffer,
-	// })
-	// let requestBuffer = common.MsgWebsocket.encode(requestMessage).finish()
-	// console.log(requestBuffer);
-	
-	// let typeStr = Object.prototype.toString.call(requestBuffer.buffer)
-	// console.log(typeStr);
-	export default {
-		data() {
-			return {
+// let loginMessage = user.login_req.create({
+// 	phone: '1234567890',
+// 	type: 1,
+// 	verifyInfo: '123456a',
+// })
+// //proto对象转buffer
+// let buffer = user.login_req.encode(loginMessage).finish()
+// console.log(buffer);
+// let requestMessage = common.MsgWebsocket.create({
+// 	version: 1,
+// 	app: 1,
+// 	server: 2,
+// 	servant: 1005,
+// 	data: buffer,
+// })
+// let requestBuffer = common.MsgWebsocket.encode(requestMessage).finish()
+// console.log(requestBuffer);
 
-			}
-		},
-		methods: {
-			login() {
+// let typeStr = Object.prototype.toString.call(requestBuffer.buffer)
+// console.log(typeStr);
+export default {
+  data() {
+    return {};
+  },
+  methods: {
+    //选择图片
+    chooseImage() {
+      let that = this;
 
-			},
-			decryptPhoneNumber(e) {
-				console.log(e.detail.errMsg)
-				console.log(e.detail.iv)
-				console.log(e.detail.encryptedData)
-			},
-		  wxHttpTest() {
-				//try{
-				post({
-					server:2,
-					servant:1005,
-					data:{
-						// phone:Buffer.from('3246541321'),
-						phone:'3246541321',
-						type:1,
-						//verifyInfo:Buffer.from('asdasd')
-						verifyInfo:'asdasd',
-						data:{
-							
-						}
-					}
-				})
-				.then((res) =>{
-					console.log(res);
-					/* res的结构
+      uni.chooseImage({
+        count: 1,
+        success(res) {
+          console.log(res);
+          let file = res.tempFiles[0];
+          console.log(file);
+          that.wxUploadImage(file);
+        },
+      });
+    },
+    //上传图片
+    wxUploadImage(file) {
+      httpClient
+        .uploadFile(file.path, "image/jpeg")
+        .then((result) => {
+          console.log(result);
+        })
+        .catch((err) => {
+          console.warn(err);
+        });
+    },
+    login() {},
+    decryptPhoneNumber(e) {
+      console.log(e.detail.errMsg);
+      console.log(e.detail.iv);
+      console.log(e.detail.encryptedData);
+    },
+    wxHttpTest() {
+      //try{
+      httpClient
+        .post({
+          server: 2,
+          servant: 1005,
+          data: {
+            // phone:Buffer.from('3246541321'),
+            phone: "3246541321",
+            type: 1,
+            //verifyInfo:Buffer.from('asdasd')
+            verifyInfo: "asdasd",
+            data: {},
+          },
+        })
+        .then(
+          (res) => {
+            console.log(res);
+            /* res的结构
 					  {
 						  code:0,错误码 number类型
 						  msg:"",错误消息 string类型
 						  data:对应loginRsp的js对象
 					  }
 					 */
-					if(res.code === 0){//成功
-						
-					}else{
-						//失败
-					}
-				}, (err) => {//出现异常
-					console.log(err);
-				})
-			},
-			wxHttpTest1() {
-				let typeStr = Object.prototype.toString.call(requestBuffer.buffer)
-				console.log(typeStr);
-				let requestArrayBuf = new Uint8Array([...requestBuffer]).buffer
-				uni.request({
-					//url:'https://test.api1.radio1964.com:80/Ohplay/Web/HttpToTcp'
-					url: 'http://60.205.190.38:80/Ohplay/Web/HttpToTcp',
-					header: {
-						"X-Requested-With": "XMLHttpRequest",
-						"Content-Type": "application/x-protobuf",
-						'Token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyaWQiOjY1MDYzMywibmFtZSI6IjE4MTI2NDQ3MDE1IiwiZXhwIjoxNjIyODg0NTA1fQ.f7jIm0856-VnynA99MBoA2Dl1pePxI0HT_ECsUp5QHA'
-					},
-					method: 'POST',
-					timeout: 15000,
-					dataType: 'protobuf',
-					responseType: 'arraybuffer',
-					data: requestArrayBuf
-				}).then((res) => {
-					console.log(res);
-					for (let response of res) {
-						if (response !== null && response !== undefined && response.statusCode === 200) {
-							try {
-								let enc = new TextDecoder('utf-8')
-								let res = JSON.parse(enc.decode(new Uint8Array(response.data))) //转化成json对象
-								console.log(res);
-							} catch (e) {
-								//let resBuf = protobuf.util.newBuffer(response.data)
-								let resBuf = Buffer.from(response.data)
-								//console.log(resBuf.toString());
-								let resMessage = common.MsgWebsocket.decode(resBuf)
-								console.log(resMessage);
-								let loginRspBuf = resMessage.data
-								console.log(loginRspBuf);
-								let loginRspMessage = user.login_rsp.decode(loginRspBuf)
-								console.log(loginRspMessage);
-								let obj = user.login_rsp.toObject(loginRspMessage, {
-									longs: String,
-									enums: String,
-								})
-								console.log(obj);
-								console.log(`errorMessage = ${Buffer.from(loginRspMessage.errInfo.errorMessage).toString()}`);
-								//console.log(Buffer.from(loginRspMessage.errInfo.errorMessage).toString());
-							}
-						}
-					}
-				}, (err) => {
-					console.log(err);
-				});
-			},
-			httpTest() { //测试http请求
-				// let blob = new Blob([requestBuffer], {
-				// 	type: 'buffer'
-				// });
-				// 将请求数据encode成二进制,encode是proto.js提供的方法
-				function transformRequest(data) {
-				  return common.MsgWebsocket.encode(requestMessage).finish()
-				}
-				axios.create({
-						timeout: 15000,
-						method: 'post',
-						headers: {
-							"X-Requested-With": "XMLHttpRequest",
-							"Content-Type": "application/octet-stream",
-							//'Token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyaWQiOjY1MDYzMywibmFtZSI6IjE4MTI2NDQ3MDE1IiwiZXhwIjoxNjIyODg0NTA1fQ.f7jIm0856-VnynA99MBoA2Dl1pePxI0HT_ECsUp5QHA'
-						},
-						responseType: 'arraybuffer',
-
-					})
-					.post('http://60.205.190.38:80/Ohplay/Web/HttpToTcp', requestMessage,{
-						 transformRequest:transformRequest
-					})
-					.then((response) => {
-						console.log(response);
-						if (response.status === 200 /* && response.data.byteLength>0*/ ) {
-							try {
-								let enc = new TextDecoder('utf-8')
-								let res = JSON.parse(enc.decode(new Uint8Array(response.data))) //转化成json对象
-								console.log(res);
-							} catch (e) {
-
-								//let resBuf = protobuf.util.newBuffer(response.data)
-								let resBuf = Buffer.from(response.data)
-								console.log(resBuf.toString());
-								let resMessage = common.MsgWebsocket.decode(resBuf)
-								let loginRspBuf = resMessage.data
-								let loginRspMessage = user.login_rsp.decode(loginRspBuf)
-								let obj = user.login_rsp.toObject(loginRspMessage, {
-									longs: String,
-									enums: String,
-								})
-								console.log(obj);
-								console.log(`errorMessage = ${Buffer.from(loginRspMessage.errInfo.errorMessage).toString()}`);
-							}
-						}
-					}, (err) => {
-						
-						console.log(err);
-					});
-
-			}
-		}
-	}
+            if (res.code === 0) {
+              //成功
+            } else {
+              //失败
+            }
+          },
+          (err) => {
+            //出现异常
+            console.log(err);
+          }
+        );
+    },
+    wxHttpTest1() {
+      let typeStr = Object.prototype.toString.call(requestBuffer.buffer);
+      console.log(typeStr);
+      let requestArrayBuf = new Uint8Array([...requestBuffer]).buffer;
+      uni
+        .request({
+          //url:'https://test.api1.radio1964.com:80/Ohplay/Web/HttpToTcp'
+          url: "http://60.205.190.38:80/Ohplay/Web/HttpToTcp",
+          header: {
+            "X-Requested-With": "XMLHttpRequest",
+            "Content-Type": "application/x-protobuf",
+            Token:
+              "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyaWQiOjY1MDYzMywibmFtZSI6IjE4MTI2NDQ3MDE1IiwiZXhwIjoxNjIyODg0NTA1fQ.f7jIm0856-VnynA99MBoA2Dl1pePxI0HT_ECsUp5QHA",
+          },
+          method: "POST",
+          timeout: 15000,
+          dataType: "protobuf",
+          responseType: "arraybuffer",
+          data: requestArrayBuf,
+        })
+        .then(
+          (res) => {
+            console.log(res);
+            for (let response of res) {
+              if (
+                response !== null &&
+                response !== undefined &&
+                response.statusCode === 200
+              ) {
+                try {
+                  let enc = new TextDecoder("utf-8");
+                  let res = JSON.parse(
+                    enc.decode(new Uint8Array(response.data))
+                  ); //转化成json对象
+                  console.log(res);
+                } catch (e) {
+                  //let resBuf = protobuf.util.newBuffer(response.data)
+                  let resBuf = Buffer.from(response.data);
+                  //console.log(resBuf.toString());
+                  let resMessage = common.MsgWebsocket.decode(resBuf);
+                  console.log(resMessage);
+                  let loginRspBuf = resMessage.data;
+                  console.log(loginRspBuf);
+                  let loginRspMessage = user.login_rsp.decode(loginRspBuf);
+                  console.log(loginRspMessage);
+                  let obj = user.login_rsp.toObject(loginRspMessage, {
+                    longs: String,
+                    enums: String,
+                  });
+                  console.log(obj);
+                  console.log(
+                    `errorMessage = ${Buffer.from(
+                      loginRspMessage.errInfo.errorMessage
+                    ).toString()}`
+                  );
+                  //console.log(Buffer.from(loginRspMessage.errInfo.errorMessage).toString());
+                }
+              }
+            }
+          },
+          (err) => {
+            console.log(err);
+          }
+        );
+    },
+    httpTest() {
+      //测试http请求
+      // let blob = new Blob([requestBuffer], {
+      // 	type: 'buffer'
+      // });
+      // 将请求数据encode成二进制,encode是proto.js提供的方法
+      function transformRequest(data) {
+        return common.MsgWebsocket.encode(requestMessage).finish();
+      }
+      axios
+        .create({
+          timeout: 15000,
+          method: "post",
+          headers: {
+            "X-Requested-With": "XMLHttpRequest",
+            "Content-Type": "application/octet-stream",
+            //'Token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyaWQiOjY1MDYzMywibmFtZSI6IjE4MTI2NDQ3MDE1IiwiZXhwIjoxNjIyODg0NTA1fQ.f7jIm0856-VnynA99MBoA2Dl1pePxI0HT_ECsUp5QHA'
+          },
+          responseType: "arraybuffer",
+        })
+        .post("http://60.205.190.38:80/Ohplay/Web/HttpToTcp", requestMessage, {
+          transformRequest: transformRequest,
+        })
+        .then(
+          (response) => {
+            console.log(response);
+            if (response.status === 200 /* && response.data.byteLength>0*/) {
+              try {
+                let enc = new TextDecoder("utf-8");
+                let res = JSON.parse(enc.decode(new Uint8Array(response.data))); //转化成json对象
+                console.log(res);
+              } catch (e) {
+                //let resBuf = protobuf.util.newBuffer(response.data)
+                let resBuf = Buffer.from(response.data);
+                console.log(resBuf.toString());
+                let resMessage = common.MsgWebsocket.decode(resBuf);
+                let loginRspBuf = resMessage.data;
+                let loginRspMessage = user.login_rsp.decode(loginRspBuf);
+                let obj = user.login_rsp.toObject(loginRspMessage, {
+                  longs: String,
+                  enums: String,
+                });
+                console.log(obj);
+                console.log(
+                  `errorMessage = ${Buffer.from(
+                    loginRspMessage.errInfo.errorMessage
+                  ).toString()}`
+                );
+              }
+            }
+          },
+          (err) => {
+            console.log(err);
+          }
+        );
+    },
+  },
+};
 </script>
 
 <style>
-	.content {
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		justify-content: center;
-		margin: 20rpx;
-	}
+.content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  margin: 20rpx;
+}
 </style>

+ 11 - 3
pages/test/component1.vue

@@ -2,24 +2,29 @@
   <view class="content">
     <button type="primary" @click="saveData">存储数据</button>
     <button type="primary" @click="getData">获取数据</button>
-    <view>
+    <view style="margin-top: 50rpx">
       <hello></hello>
+      <some-one v-if="showSomeHide"></some-one>
     </view>
+	<button type="primary" @click="hideSome" style="margin-top: 50rpx">影藏someone</button>
   </view>
 </template>
 
 <script>
 import hello from '../../components/hello/hello.vue';
+import SomeOne from '../../components/someOne/someOne.vue';
 
 
 
 export default {
-  components: { hello },
+  components: { hello, SomeOne },
  
   
   
   data() {
-    return {};
+    return {
+		showSomeHide:true
+	};
   },
   
   methods: {
@@ -51,6 +56,9 @@ export default {
         },
       });
     },
+	hideSome(){
+		this.showSomeHide = !this.showSomeHide
+	},
   },
 };
 </script>

+ 4 - 24
pages/test/component3/component3.vue

@@ -1,7 +1,7 @@
 <template>
   <view>
     <view class="box1" v-for="(item,index) in list" :key="index">
-      {{ item.name }}<text>{{ item.id }}</text>
+      {{ item.name }}<text>{{ item.name1 }}</text>
     </view>
   </view>
 </template>
@@ -38,29 +38,9 @@ export default {
   // 页面周期函数--监听页面卸载
   onUnload() {},
   onPullDownRefresh() {
-    // let that = this;
-    // post({
-    //   server: 2,
-    //   servant: 1005,
-    //   data: {
-    //     phone: "3246541321",
-    //     type: 1,
-    //     verifyInfo: "asdasd",
-    //   },
-    // })
-    //   .then((result) => {
-    //     //if (result.code === 0) {
-    //       let count = that.list.length
-    //       that.list.push({
-    //         name:result.msg,
-    //         id:count
-    //       })
-    //     //}
-    //     uni.stopPullDownRefresh();
-    //   })
-    //   .catch((err) => {
-    //     uni.stopPullDownRefresh();
-    //   });
+    this.list[0].name1 = this.list[0].name+1
+    uni.stopPullDownRefresh()
+    //this.$forceUpdate();
   },
   onReachBottom(){
     let that = this;

+ 279 - 70
store/modules/moduleMqtt.js

@@ -1,148 +1,357 @@
 import mqtt from '../../common/mqtt.js'
+import Vue from 'vue'
 //import mqtt from 'mqtt/dist/mqtt.js'
 
 const options = {
-	clientId: "mqtt_" + parseInt(Math.random() * 100 + 800, 10),
-	keepalive: 20,
-	connectTimeout: 30 * 1000,
+	clientId: "wx_" + parseInt(Math.random() * 100 + 800, 10),
+	keepalive: 28,//每28秒发送一次心跳
+	connectTimeout: 15 * 1000,//连接超时的时间
 };
 
-// #ifdef H5
-//let url = 'wss://test.mosquitto.org'
-//let url = 'ws://60.205.190.38'
+
 let url = 'wss://mqtt.test.radio1964.com'
+
+// #ifdef MP-WEIXIN
+url = 'wxs://mqtt.test.radio1964.com'
 // #endif
-// #ifdef MP-WEIXIN||APP-PLUS
-//let url = 'wxs://test.mosquitto.org'
-//let url = 'wx://60.205.190.38'
-let 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(typeof device !== 'undefined'){
+		let topic = `/${device.uuid}/user/pub_response`
+		dispatch('subscribe',topic)
+	}
+}
 
 export default {
 	namespaced: true,
-	state:{
+	state: {
+		//当前选择的设备
+		currentDevice: undefined,
+		//用户设备列表
+		deviceList: [],
+		//mqtt客户端
 		mqttClient: undefined,
+		//收到的消息的历史记录
 		msgList:[],
-		
+
 	},
-	getters:{
+	getters: {
 		//mqtt连接状态
-		connected:state =>{
+		connected: state => {
 			return state.mqttClient !== undefined && state.mqttClient.connected
 		},
-		//最新的一个消息
-		newMessage:state =>{
-			return msgList[msgList.length-1]
+
+	},
+	mutations: {
+		//添加设备
+		addDevice(state, device) {
+			state.deviceList.push(device)
+		},
+		//设置当前选择的设备
+		setCurrentDevice(state, device) {
+			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) {
+			deviceList = []
+			currentDevice = undefined
+			state.mqttClient = undefined
+		},
+		//设置设备的在线状态
+		updateDeviceState(state,data){
+			Vue.set(state.deviceList,data.index,data.device)
+			//state.deviceList[data.index] = {...state.deviceList[data.index], online:data.online}
+			//state.deviceList[data.index].online = data.online
 		}
+
 	},
-	mutations:{
-		connect(state,getters) {
-			console.log('connect')
-			if (state.mqttClient === undefined) {
-				state.mqttClient = mqtt.connect(url, options)
+	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(typeof findDevice === 'undefined'){
+				if(state.deviceList.length === 0){
+					//设置当前的设备为添加的设备
+					commit('setCurrentDevice',device)
+				}
+				//添加设备
+				state.deviceList.push(device)
+				//如果已经连接
+				if(getters.connected){
+					//订阅设备是否在线
+					subscribeDevicesStatus(dispatch,[device])
+					if(typeof state.currentDevice !== 'undefined' && 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(typeof findDevice !== 'undefined'){
+				//取消此设备的订阅
+				let topic = `/${device.uuid}/status/onoffline`
+				dispatch('unsubscribe',topic)
+				topic = `/${device.uuid}/status/pub_response`
+				dispatch('unsubscribe',topic)
+				//在设备列表中删除此设备
+				commit('removeDevice',findDevice)
+				//如果删除的是选中的设备
+				if(typeof state.currentDevice !== 'undefined' && state.currentDevice.uuid == device.uuid){
+					//清除当前选中的设备 todo 还是自动选中在线的设备
+					commit('setCurrentDevice',undefined)
+				}
+				
+				if(state.deviceList.length === 0 ){//所有的设备都已经删除
+					dispatch()
+				}
+			}
+		},
+
+		//连接设备
+		connect({ state, dispatch,commit, getters }, clientId) {
+			if (typeof state.mqttClient === 'undefined') {
+				//连接服务器
+				commit('connect', clientId)
 				//连接成功回调
-				state.mqttClient.on("connect", function() {
+				state.mqttClient.on("connect", function () {
 					console.log("connect success!");
+					//订阅设备是否在线
+					subscribeDevicesStatus(dispatch,state.deviceList)
+					//订阅当前设备的消息
+					subscribeCurrDevice(dispatch,state.currentDevice)
 				});
-		
+
 				//异常回调
-				state.mqttClient.on("error", function(err) {
+				state.mqttClient.on("error", function (err) {
 					console.log(err);
 				});
-				
-				state.mqttClient.on('offline', function() {
-					//console.log(`offline callback connected = ${state.mqttClient.connected}`);
+
+				state.mqttClient.on('offline', function () {
+
 					console.log(`offline callback`);
 				})
-		
-				state.mqttClient.on('end', function() {
-					//console.log(`end callback connected = ${state.mqttClient.connected}`);
+
+				state.mqttClient.on('end', function () {
+
 					console.log(`end callback`);
 				})
-		
-				state.mqttClient.on('close', function() {
-					//console.log(`close callback connected = ${state.mqttClient.connected}`);
+				//网络断开的回调
+				state.mqttClient.on('close', function () {
 					console.log(`close callback`);
 				})
-				state.mqttClient.on('message',function(topic,message){
+				//收到消息的回调
+				state.mqttClient.on('message', function (topic, message) {
 					console.log(`message = ${message.toString()}`);
+					let arr = topic.split("/");
+					if(arr.length >1){
+						let fromDeviceUuid = arr[1]
+						
+						//设备在线和离线的消息
+						if(topic.endsWith('onoffline')){
+							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[i];
+								if(stateJson.state === 'online'){
+									device.online = true
+									commit('updateDeviceState',{index,device})
+									//commit('updateDeviceState',{index,online:true})
+									//state.deviceList[index].online = true
+									if(typeof state.currentDevice !== 'undefined' && state.currentDevice.uuid == device.uuid){
+										dispatch({
+											type:'publishWithType',
+											mqttType:'get_position',
+										})
+									}
+								}else{
+									device.online = false
+									commit('updateDeviceState',{index,device})
+									//commit('updateDeviceState',{index,online:true})
+									//state.deviceList[index].online = false
+								}
+								uni.$emit('mqtt_onoffline',stateJson)
+								// state.deviceList.forEach(element => {
+								// 	console.log(`online = ${element.online}`);
+								//   });
+							}
+							
+						}
+					}
+					//这是测试的
 					state.msgList.push({
 						topic: topic.toString(),
 						msg: message.toString(),
 					});
+					
+
+
 				})
-		
-			} else {
-				//console.log(`reconnect connected = ${state.mqttClient.connected}`)
-				//console.log(`reconnect111 connected = ${getters.connected}`)
-				if (state.mqttClient !== undefined && !state.mqttClient.connected) {
-					console.log('reconnect')
+			}else{
+				if(!state.mqttClient.connected){
+					console.log("reconnect");
 					state.mqttClient.reconnect()
 					//重连成功的回调
 					state.mqttClient.on("reconnect", function() {
-						state.isConnect = true;
 						console.log("reconnect success!");
+						
 					});
 				}
 			}
-		
-		
 		},
-		disconnect(state) {
+		//断开mqtt的连接
+		disconnect({state,commit},isRelease) {
 			console.log('disconnect')
 			if (state.mqttClient !== undefined) {
-				//mqtt.js在小程序运行有问题,调用end 不会回调close,而且state.mqttClient.connected一直是true
 				state.mqttClient.end(false,() =>{
 					console.log(`state.mqttClient.connected = ${state.mqttClient.connected}`);
 				})
-				
-				
-				//state.mqttClient = undefined
-		
 			}
-		}
-	},
-	actions:{
+			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(typeof state.currentDevice === undefined || !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(typeof other !== 'undefined'){
+					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){
+		publish({ state, commit, getters }, message) {
 			console.log('publish');
 			console.log(message);
-			return new Promise((resolve,reject) =>{
-				if(getters.connected){
+			return new Promise((resolve, reject) => {
+				if (getters.connected) {
 					//发布消息
-					state.mqttClient.publish(message.topic,message.payload,(err) =>{
-						if(err){
+					state.mqttClient.publish(message.topic, message.payload, (err) => {
+						if (err) {
 							console.log(err);
 							reject(err)
-						}else{
+						} else {
 							resolve()
 						}
 					})
-				}else{
+				} else {
 					reject("mqttClient is not connected")
 				}
 			})
 		},
 		//订阅消息
-		subscribe({state,commit,getters},topic){
+		subscribe({ state, commit, getters }, topic) {
 			console.log(`subscribe topic = ${topic}`);
-			return new Promise((resolve,reject) =>{
-				if(getters.connected){
+			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.subscribe(topic,(err) =>{
-						if(err){
+					state.mqttClient.unsubscribe(topic, (err) => {
+						if (err) {
 							console.log(err);
 							reject(err)
-						}else{
+						} else {
 							resolve()
 						}
 					})
-				}else{
+				} else {
 					reject("mqttClient is not connected")
 				}
 			})
 		}
-		
+
 	}
-	
+
 }

+ 173 - 0
store/modules/moduleMqtt1.js

@@ -0,0 +1,173 @@
+import mqtt from '../../common/mqtt.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
+
+export default {
+	namespaced: true,
+	state:{
+		//用户设备列表
+		deviceList:[],
+		//mqtt客户端
+		mqttClient: undefined,
+		//收到的消息的历史记录
+		msgList:[],
+	},
+	getters:{
+		//mqtt连接状态
+		connected:state =>{
+			return state.mqttClient !== undefined && state.mqttClient.connected
+		},
+		
+	},
+	mutations:{
+		//添加设备
+		addDevice(state,device){
+			
+			let findDevice = state.deviceList.find((item) =>{
+				return item.ssid === device.ssid
+			})
+			if(typeof findDevice === 'undefined'){
+				state.deviceList.push(device)
+			}
+			//连接服务器
+			this.commit('connect',options.clientId)
+		},
+		//连接mqtt服务器 clientId为wx_加上用户的uid
+		connect(state,clientId) {
+			
+			function connect(state,clientId){
+				console.log('connect')
+				options.clientId = clientId
+
+				state.mqttClient = mqtt.connect(url, options)
+				//连接成功回调
+				state.mqttClient.on("connect", function() {
+					console.log("connect success!");
+					
+				});
+		
+				//异常回调
+				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()}`);
+					// state.msgList.push({
+					// 	topic: topic.toString(),
+					// 	msg: message.toString(),
+					// });
+				})
+			}
+
+			if (typeof state.mqttClient === 'undefined') {
+				connect(state,clientId)
+		
+			} else {
+				if(options.clientId !==clientId){
+					//断开连接后,重新连接,因为clientId不同
+					state.mqttClient.end(false,() =>{
+						state.mqttClient = undefined
+						connect(state,clientId)
+					})
+				}
+				if (!state.mqttClient.connected) {
+					console.log('reconnect')
+					state.mqttClient.reconnect()
+					//重连成功的回调
+					state.mqttClient.on("reconnect", function() {
+						console.log("reconnect success!");
+					});
+				}
+			}
+		},
+		disconnect(state) {
+			console.log('disconnect')
+			if (state.mqttClient !== undefined) {
+				//mqtt.js在小程序运行有问题,调用end 不会回调close,而且state.mqttClient.connected一直是true
+				state.mqttClient.end(false,() =>{
+					console.log(`state.mqttClient.connected = ${state.mqttClient.connected}`);
+				})
+				//state.mqttClient = undefined
+			}
+		},
+		//token失效,退出登录,需要断开连接
+		release(state) {
+			if (state.mqttClient !== undefined) {
+				state.mqttClient.end()
+				state.mqttClient = undefined
+			}
+		}
+
+	},
+	actions:{
+		//发布消息
+		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.log(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")
+				}
+			})
+		}
+		
+	}
+	
+}

+ 4 - 0
uni_modules/uni-card/changelog.md

@@ -0,0 +1,4 @@
+## 1.1.7(2021-05-12)
+- 新增 组件示例地址
+## 1.1.6(2021-02-04)
+- 调整为uni_modules目录规范

+ 406 - 0
uni_modules/uni-card/components/uni-card/uni-card.vue

@@ -0,0 +1,406 @@
+<template>
+	<view class="uni-card uni-border" :class="{ 'uni-card--full': isFull === true || isFull === 'true', 'uni-card--shadow': isShadow === true || isShadow === 'true'}">
+		<!-- 基础 -->
+		<view v-if="mode === 'basic' && title" class="uni-card__header uni-border-bottom" @click.stop="onClick">
+			<view v-if="thumbnail" class="uni-card__header-extra-img-view">
+				<image :src="thumbnail" class="uni-card__header-extra-img" />
+			</view>
+			<text class="uni-card__header-title-text">{{ title }}</text>
+			<text v-if="extra" class="uni-card__header-extra-text">{{ extra }}</text>
+		</view>
+		<!-- 标题 -->
+		<view v-if="mode === 'title'" class="uni-card__title uni-border-bottom" @click.stop="onClick">
+			<view class="uni-card__title-box">
+				<view class="uni-card__title-header">
+					<image class="uni-card__title-header-image" :src="thumbnail" mode="scaleToFill" />
+				</view>
+				<view class="uni-card__title-content">
+					<text class="uni-card__title-content-title uni-ellipsis">{{ title }}</text>
+					<text class="uni-card__title-content-extra uni-ellipsis">{{ subTitle }}</text>
+				</view>
+			</view>
+			<view v-if="extra">
+				<text class="uni-card__header-extra-text">{{ extra }}</text>
+			</view>
+		</view>
+		<!-- 图文 -->
+		<view v-if="mode === 'style'" class="uni-card__thumbnailimage" @click.stop="onClick">
+			<view class="uni-card__thumbnailimage-box">
+				<image class="uni-card__thumbnailimage-image" :src="thumbnail" mode="aspectFill" />
+			</view>
+			<view v-if="title" class="uni-card__thumbnailimage-title"><text class="uni-card__thumbnailimage-title-text">{{ title }}</text></view>
+		</view>
+		<!-- 内容 -->
+		<view class="uni-card__content uni-card__content--pd" @click.stop="onClick">
+			<view v-if="mode === 'style' && extra" class=""><text class="uni-card__content-extra">{{ extra }}</text></view>
+			<slot />
+		</view>
+		<!-- 底部 -->
+		<view v-if="note" class="uni-card__footer uni-border-top">
+			<slot name="footer">
+				<text class="uni-card__footer-text">{{ note }}</text>
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Card 卡片
+	 * @description 卡片视图组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=22
+	 * @property {String} title 标题文字
+	 * @property {String} subTitle 副标题(仅仅mode=title下生效)
+	 * @property {String} extra 标题额外信息
+	 * @property {String} note 标题左侧缩略图
+	 * @property {String} thumbnail 底部信息
+	 * @property {String} mode = [basic|style|title] 卡片模式
+	 * 	@value basic 基础卡片
+	 * 	@value style 图文卡片
+	 * 	@value title 标题卡片
+	 * @property {Boolean} isFull = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
+	 * @property {Boolean} isShadow = [true | false] 卡片内容是否开启阴影
+	 * @event {Function} click 点击 Card 触发事件
+	 * @example <uni-card title="标题文字" thumbnail="xxx.jpg" extra="额外信息" note="Tips">内容主体,可自定义内容及样式</uni-card>
+	 */
+	export default {
+		name: 'UniCard',
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+			extra: {
+				type: String,
+				default: ''
+			},
+			note: {
+				type: String,
+				default: ''
+			},
+			thumbnail: {
+				type: String,
+				default: ''
+			},
+			mode: {
+				type: String,
+				default: 'basic'
+			},
+			isFull: {
+				// 内容区域是否通栏
+				type: Boolean,
+				default: false
+			},
+			isShadow: {
+				// 是否开启阴影
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uni-card {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex: 1;
+		box-shadow: 0 0 0 rgba(0, 0, 0, 0);
+		/* #endif */
+		margin: $uni-spacing-col-lg $uni-spacing-row-lg;
+		background-color: $uni-bg-color;
+		position: relative;
+		flex-direction: column;
+		border-radius: 5px;
+		overflow: hidden;
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+
+
+	.uni-border {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-color: $uni-border-color;
+		border-style: solid;
+		border-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border: 1px solid $uni-border-color;
+		border-radius: 10px;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+
+	.uni-border-bottom {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-bottom-color: $uni-border-color;
+		border-bottom-style: solid;
+		border-bottom-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border-bottom:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-bottom: 1px solid $uni-border-color;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+	.uni-border-top {
+		position: relative;
+		/* #ifdef APP-NVUE */
+		border-top-color: $uni-border-color;
+		border-top-style: solid;
+		border-top-width: 0.5px;
+		/* #endif */
+		z-index: 1;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-border-top:after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		top: 0;
+		right: 0;
+		border-top: 1px solid $uni-border-color;
+		box-sizing: border-box;
+		width: 200%;
+		height: 200%;
+		transform: scale(0.5);
+		transform-origin: left top;
+		z-index: -1;
+	}
+
+	/* #endif */
+
+	.uni-card__thumbnailimage {
+		position: relative;
+		flex-direction: column;
+		justify-content: center;
+		height: 150px;
+		overflow: hidden;
+	}
+
+	.uni-card__thumbnailimage-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		overflow: hidden;
+	}
+
+	.uni-card__thumbnailimage-image {
+		flex: 1;
+	}
+
+	.uni-card__thumbnailimage-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		flex-direction: row;
+		padding: $uni-spacing-col-base $uni-spacing-col-lg;
+		background-color: $uni-bg-color-mask;
+	}
+
+	.uni-card__thumbnailimage-title-text {
+		flex: 1;
+		font-size: $uni-font-size-base;
+		color: #fff;
+	}
+
+	.uni-card__title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 10px;
+
+	}
+
+	.uni-card__title-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		align-items: center;
+		overflow: hidden;
+	}
+
+	.uni-card__title-header {
+		width: 40px;
+		height: 40px;
+		overflow: hidden;
+		border-radius: 5px;
+	}
+
+	.uni-card__title-header-image {
+		width: 40px;
+		height: 40px;
+	}
+
+	.uni-card__title-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		flex: 1;
+		padding-left: 10px;
+		height: 40px;
+		overflow: hidden;
+	}
+
+	.uni-card__title-content-title {
+		font-size: $uni-font-size-base;
+		line-height: 22px;
+	}
+
+	.uni-card__title-content-extra {
+		font-size: $uni-font-size-sm;
+		line-height: 27px;
+		color: $uni-text-color-grey;
+	}
+
+	.uni-card__header {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		position: relative;
+		flex-direction: row;
+		padding: $uni-spacing-col-lg;
+		align-items: center;
+	}
+
+	.uni-card__header-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		margin-right: $uni-spacing-col-base;
+		justify-content: flex-start;
+		align-items: center;
+	}
+
+	.uni-card__header-title-text {
+		font-size: $uni-font-size-lg;
+		flex: 1;
+		color: #333;
+	}
+
+	.uni-card__header-extra-img {
+		height: $uni-img-size-sm;
+		width: $uni-img-size-sm;
+		margin-right: $uni-spacing-col-base;
+	}
+
+	.uni-card__header-extra-text {
+		flex: 1;
+		margin-left: $uni-spacing-col-base;
+		font-size: $uni-font-size-sm;
+		text-align: right;
+		color: $uni-text-color-grey;
+	}
+
+	.uni-card__content {
+		color: $uni-text-color;
+	}
+
+	.uni-card__content--pd {
+		padding: $uni-spacing-col-lg;
+	}
+
+	.uni-card__content-extra {
+		font-size: $uni-font-size-base;
+		padding-bottom: 10px;
+		color: $uni-text-color-grey;
+	}
+
+	.uni-card__footer {
+		justify-content: space-between;
+		padding: $uni-spacing-col-lg;
+	}
+
+	.uni-card__footer-text {
+		color: $uni-text-color-grey;
+		font-size: $uni-font-size-sm;
+	}
+
+	.uni-card--shadow {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1);
+		/* #endif */
+	}
+
+	.uni-card--full {
+		margin: 0;
+		border-radius: 0;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-card--full:after {
+		border-radius: 0;
+	}
+
+	/* #endif */
+	.uni-ellipsis {
+		/* #ifndef APP-NVUE */
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		/* #endif */
+	}
+</style>

+ 83 - 0
uni_modules/uni-card/package.json

@@ -0,0 +1,83 @@
+{
+  "id": "uni-card",
+  "displayName": "uni-card 卡片",
+  "version": "1.1.7",
+  "description": "Card 组件,提供常见的卡片样式。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "card",
+    "",
+    "卡片"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 103 - 0
uni_modules/uni-card/readme.md

@@ -0,0 +1,103 @@
+
+
+## Card 卡片
+> **组件名:uni-card**
+> 代码块: `uCard`
+
+
+卡片视图组件。
+
+### 安装方式
+
+本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
+
+如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
+
+> **注意事项**
+> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
+> - 因为平台兼容问题 , 目前 APP-NVUE 安卓平台下不支持阴影
+
+
+### 基本用法
+
+在 ``template`` 中使用组件
+
+```html
+<!-- 一般用法 -->
+<uni-card title="标题文字" thumbnail="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" extra="额外信息" note="Tips">
+    内容主体,可自定义内容及样式
+</uni-card>
+
+<!-- 内容通栏 -->
+<uni-card is-full="true" title="DCloud" thumbnail="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" extra="2018.12.12" >
+    <image src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" style="width: 100%;"></image>
+</uni-card>
+
+<!-- 图文卡片模式 -->
+<uni-card
+	title="标题文字"
+	mode="style"
+	:is-shadow="true"
+	thumbnail="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png"
+	extra="Dcloud 2019-05-20 12:32:19"
+	note="Tips"
+>
+		uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可编译到iOS、Android、H5、以及各种小程序等多个平台。即使不跨端,uni-app同时也是更好的小程序开发框架。
+</uni-card>
+
+<!-- 标题卡片模式 -->
+<uni-card 
+	title="Dcloud" 
+	mode="title" 
+	:is-shadow="true" 
+	thumbnail="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" 
+	extra="技术没有上限" 
+	note="Tips"
+>
+	uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可编译到iOS、Android、H5、以及各种小程序等多个平台。即使不跨端,uni-app同时也是更好的小程序开发框架。
+</uni-card>
+
+<!-- 自定义底部按钮 -->
+<uni-card title="Dcloud" note="true">
+	默认内容
+	<template v-slot:footer>
+		<view class="footer-box">
+			<view>喜欢</view>
+			<view>评论</view>
+			<view>分享</view>
+		</view>
+	</template>
+</uni-card>
+```
+
+## API
+
+### Card Props
+
+|属性名			|类型		|默认值	|说明																			|
+|:-:				|:-:		|:-:		|:-:																			|
+|title			|String	|-			|标题文字																			|
+|extra			|String	|-			|标题额外信息																		|
+|note				|String	|-			|底部信息																			|
+|thumbnail	|String	|-			|标题左侧缩略图,支持网络图片,本地图片,本图片需要传入一个绝对路径,如:`/static/xxx.png`	|
+|mode				|String	|basic	|卡片模式 ,可选值, basic:基础卡片 ;style :图文卡片 ; title :标题卡片				|
+|isFull			|Boolean|false	|卡片内容是否通栏,为true时将去除padding值											|
+|isShadow		|Boolean|false	|卡片内容是否开启阴影																|
+
+
+### Card Events
+
+|事件称名	|事件说明						|返回参数	|
+|:-:		|:-:							|:-:		|
+|@click	|点击 Card 触发事件	|-			|
+
+
+### Card Slots
+
+|插槽称名	|说明				|
+|:-:		|:-:				|
+|footer	|卡片底部插槽 |
+
+## 组件示例
+
+点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/card/card](https://hellouniapp.dcloud.net.cn/pages/extUI/card/card)