detail.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <template>
  2. <view class="content">
  3. <view class="header">
  4. <u--form>
  5. <u-form-item style="border:0px"
  6. label="设备名称:"
  7. labelWidth="auto"
  8. :borderBottom="false"
  9. >
  10. <u--input disabled
  11. placeholder="请选择分组"
  12. border="none"
  13. color="gray"
  14. disabledColor="white"
  15. v-model="deviceInfo.deviceName"
  16. ></u--input>
  17. </u-form-item>
  18. <u-form-item style="border:0px"
  19. label="设备编号:"
  20. labelWidth="auto"
  21. :borderBottom="false"
  22. ref="item1"
  23. >
  24. <u--input disabled
  25. border="none"
  26. color="gray"
  27. disabledColor="white"
  28. v-model="deviceInfo.serialNumber"
  29. ></u--input>
  30. </u-form-item>
  31. <u-form-item style="border:0px"
  32. label="二维码ID:"
  33. labelWidth="auto"
  34. :borderBottom="false"
  35. ref="item1"
  36. >
  37. <u--input disabled
  38. border="none"
  39. color="gray"
  40. disabledColor="white"
  41. v-model="deviceInfo.qrcodeId"
  42. ></u--input>
  43. </u-form-item>
  44. <u-form-item style="border:0px"
  45. label="激活时间:"
  46. labelWidth="auto"
  47. :borderBottom="false"
  48. ref="item1"
  49. >
  50. <u--input disabled
  51. border="none"
  52. color="gray"
  53. disabledColor="white"
  54. :value="deviceInfo.activeTime"
  55. ></u--input>
  56. </u-form-item>
  57. <u-form-item style="border:0px"
  58. label="设备信号:"
  59. labelWidth="auto"
  60. :borderBottom="false"
  61. >
  62. <u--input disabled
  63. border="none"
  64. color="gray"
  65. disabledColor="white"
  66. :value="deviceInfo.rssi"
  67. ></u--input>
  68. </u-form-item>
  69. <u-form-item v-if="deviceInfo.networkAddress != null" style="border:0px"
  70. label="设备网络:"
  71. labelWidth="auto"
  72. :borderBottom="false"
  73. ref="item1"
  74. >
  75. <u--input disabled
  76. border="none"
  77. color="gray"
  78. disabledColor="white"
  79. :value="deviceInfo.networkAddress+'('+deviceInfo.networkIp+')'"
  80. ></u--input>
  81. </u-form-item>
  82. <u-form-item style="border:0px"
  83. label="设备状态:"
  84. labelWidth="auto"
  85. :borderBottom="false"
  86. >
  87. <view style="width:90rpx">
  88. <u-tag v-if="deviceInfo.status === 4" text="离线" size="mini" type="info"></u-tag>
  89. <u-tag v-if="deviceInfo.status === 3" text="在线" size="mini" type="success"></u-tag>
  90. <u-tag v-if="deviceInfo.status === 2" text="禁用" size="mini" type="error"></u-tag>
  91. <u-tag v-if="deviceInfo.status === 1" text="未激活" size="mini" type="error"></u-tag>
  92. </view>
  93. </u-form-item>
  94. </u--form>
  95. </view>
  96. <view class="text-area">
  97. <u-tabs :list="summary" keyName="tabName" @change="getDeviceStatus"></u-tabs>
  98. <view class="prop-container" style="background: white;padding:10px ">
  99. <u-form>
  100. <u--text text="属性控制" style="margin-top:10px"></u--text>
  101. <u-form-item style="border:0px;position: relative" v-for="item in inputProp"
  102. :label="item.name"
  103. labelWidth="auto"
  104. :borderBottom="false"
  105. >
  106. <u-input placeholder="请输入字符串" v-if="item.type === 'string'" v-model="item.shadow">
  107. <template slot="suffix">
  108. <u-button v-if="deviceInfo.status==3"
  109. @tap="send(item)"
  110. text="发送"
  111. type="success"
  112. size="mini"
  113. ></u-button>
  114. <u-button v-if="deviceInfo.status!=3"
  115. @tap="send(item)"
  116. text="发送"
  117. type="info"
  118. size="mini"
  119. ></u-button>
  120. </template>
  121. </u-input>
  122. <u-input placeholder="请输入整数" v-if="item.type === 'integer'" v-model="item.shadow">
  123. <template slot="suffix">
  124. <u-button v-if="deviceInfo.status==3"
  125. @tap="send(item)"
  126. text="发送"
  127. type="success"
  128. size="mini"
  129. ></u-button>
  130. <u-button v-if="deviceInfo.status!=3"
  131. @tap="send(item)"
  132. text="发送"
  133. type="info"
  134. size="mini"
  135. ></u-button>
  136. </template>
  137. </u-input>
  138. <u-input placeholder="请使用英文逗号分隔的字符串" v-if="item.type === 'array'" v-model="item.shadow">
  139. <template slot="suffix">
  140. <u-button v-if="deviceInfo.status==3"
  141. @tap="send(item)"
  142. text="发送"
  143. type="success"
  144. size="mini"
  145. ></u-button>
  146. <u-button v-if="deviceInfo.status!=3"
  147. @tap="send(item)"
  148. text="发送"
  149. type="info"
  150. size="mini"
  151. ></u-button>
  152. </template>
  153. </u-input>
  154. <view v-if="item.type === 'enum'" style="position: relative" >
  155. <view @click="chooseItemData(item)" style="width:100%" v-if="item.text != null && item.text.length>0">{{ item.text }}</view>
  156. <view @click="chooseItemData(item)" style="width:100%" v-else>请选择</view>
  157. <u-button v-if="deviceInfo.status==3" customStyle="position: absolute;top: 8rpx;right: 20rpx;width: 13%;"
  158. @tap="send(item)"
  159. text="发送"
  160. type="success"
  161. size="mini"
  162. ></u-button>
  163. </view>
  164. </u-form-item>
  165. <u-line></u-line>
  166. <u--text text="属性监控" style="margin-top:10px"></u--text>
  167. <view>
  168. <u-form-item style="border:0px" v-for="item in deviceInfo.readOnlyList"
  169. :label="item.name"
  170. labelWidth="auto"
  171. :borderBottom="false"
  172. >
  173. <u-input placeholder="请输入字符串" disabled="" :value="item.shadow+' '+item.unit">
  174. </u-input>
  175. </u-form-item>
  176. </view>
  177. </u-form>
  178. </view>
  179. <u-picker @cancel="show=null" :show="show!=null" :columns="columns" @confirm="confirmItemData" keyName="text"></u-picker>
  180. </view>
  181. </view>
  182. </template>
  183. <script>
  184. import { getDetail,getDeviceStatus,cacheJsonThingsModel } from '@/api/device/device.js'
  185. import UButton from "../../uni_modules/uview-ui/components/u-button/u-button";
  186. import UForm from "../../uni_modules/uview-ui/components/u--form/u--form";
  187. export default {
  188. components: {UForm, UButton},
  189. data(){
  190. return {
  191. show:null,
  192. value:"",
  193. deviceInfo:{},
  194. id:0,
  195. summary:[],
  196. activeName:0,
  197. childId:"",
  198. oneToMul:false,
  199. inputProp:[],
  200. watchProp:[],
  201. columns:[]
  202. }
  203. },
  204. onLoad: function(opt) {
  205. this.id = opt.id;
  206. this.connectMqtt();
  207. this.getDetail();
  208. },
  209. destroyed() {
  210. // 取消订阅主题
  211. this.mqttUnSubscribe(this.deviceInfo);
  212. },
  213. methods:{
  214. confirmItemData(e){
  215. let data = e.value[0];
  216. this.show.text = data.text;
  217. this.show.shadow=data.value;
  218. this.show = null;
  219. console.log(data);
  220. },
  221. send(item){
  222. if(this.deviceInfo.status != 3){
  223. uni.showToast({
  224. title:"设备暂未上线",
  225. icon:"error"
  226. })
  227. return;
  228. }
  229. if(!item.shadow){
  230. uni.showToast({
  231. title:"数据不能为空",
  232. icon:"error"
  233. })
  234. return;
  235. }
  236. this.publishThingsModel(this.deviceInfo,item)
  237. },
  238. /** 更新设备状态 */
  239. updateDeviceStatus(device) {
  240. },
  241. chooseItemData(data){
  242. if(this.deviceInfo.status != 3){
  243. uni.showToast({
  244. title:"设备暂未上线",
  245. icon:"error"
  246. })
  247. return;
  248. }
  249. this.columns = [data.enumList];
  250. this.show =data;
  251. },
  252. getDetail(){
  253. let self = this;
  254. getDetail(this.id).then(res=>{
  255. self.deviceInfo = res.data;
  256. self.parseSummay(res.data.summary)
  257. self.mqttSubscribe(res.data);
  258. self.getDeviceStatus();
  259. });
  260. },
  261. getDeviceStatus(e){
  262. let self = this;
  263. if(!e){
  264. e = {index:0};
  265. }
  266. this.activeName = e.index;
  267. this.childId = this.summary[this.activeName].id;
  268. getDeviceStatus(this.id,this.childId).then(res=>{
  269. let data =res.data;
  270. this.deviceInfo = data;
  271. self.parseStatusData(data)
  272. });
  273. },
  274. parseStatusData(data){
  275. let arr = [];
  276. let arrayList = data.arrayList;
  277. arr = arr.concat(arrayList);
  278. let enumList = data.enumList;
  279. arr = arr.concat(enumList);
  280. let integerList = data.integerList;
  281. arr = arr.concat(integerList);
  282. let stringList = data.stringList;
  283. arr = arr.concat(stringList);
  284. this.inputProp = arr;
  285. let readOnlyList = data.readOnlyList;
  286. this.watchProp = readOnlyList;
  287. },
  288. parseSummay(summary){
  289. let self = this;
  290. if(!summary){
  291. summary = "";
  292. }
  293. if(summary.length>0){
  294. this.summary = JSON.parse(summary);
  295. if(self.summary.length>0){
  296. this.oneToMul = true;
  297. }
  298. for (let i = 0; i < self.summary.length; i++) {
  299. self.summary[i].tabName = self.summary[i].id+"_"+self.summary[i].name
  300. }
  301. let childId = "";
  302. if(this.oneToMul){
  303. let info = self.summary[self.activeName];
  304. childId = info.id;
  305. }
  306. this.childId = childId;
  307. }else{
  308. this.summary = [{tabName:"详情查看"}]
  309. }
  310. },
  311. goDeviceDetail(id){
  312. uni.navigateTo({
  313. url: '/pages/device/detail/detail?id='+id
  314. });
  315. },
  316. async connectMqtt() {
  317. if (this.$mqttTool.client == null) {
  318. await this.$mqttTool.connect(this.vuex_token);
  319. }
  320. this.mqttCallback();
  321. },
  322. /** Mqtt订阅主题 */
  323. mqttSubscribe(device) {
  324. // 订阅当前设备状态和实时监测
  325. let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post';
  326. let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post';
  327. let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post';
  328. let topics = [];
  329. topics.push(topicStatus);
  330. topics.push(topicProperty);
  331. topics.push(topicFunction);
  332. this.$mqttTool.subscribe(topics);
  333. },
  334. /** Mqtt取消订阅主题 */
  335. mqttUnSubscribe(device) {
  336. // 订阅当前设备状态和实时监测
  337. let topicStatus = '/' + device.productId + '/' + device.serialNumber + '/status/post';
  338. let topicProperty = '/' + device.productId + '/' + device.serialNumber + '/property/post';
  339. let topicFunction = '/' + device.productId + '/' + device.serialNumber + '/function/post';
  340. let topics = [];
  341. topics.push(topicStatus);
  342. topics.push(topicProperty);
  343. topics.push(topicFunction);
  344. console.log('取消订阅', topics);
  345. this.$mqttTool.unsubscribe(topics);
  346. },
  347. /* Mqtt回调处理 */
  348. mqttCallback() {
  349. this.$mqttTool.client.on('message', (topic, message, buffer) => {
  350. let topics = topic.split('/');
  351. let productId = topics[1];
  352. let deviceNum = topics[2];
  353. console.log('接收到内容:'+message);
  354. message = JSON.parse(message.toString());
  355. if (topics[3] == 'status') {
  356. console.log('接收到【设备状态-运行】主题:', topic);
  357. console.log('接收到【设备状态-运行】内容:', message);
  358. // 更新列表中设备的状态
  359. if (this.deviceInfo.serialNumber == deviceNum) {
  360. this.deviceInfo.status = message.status;
  361. this.deviceInfo.isShadow = message.isShadow;
  362. this.deviceInfo.rssi = message.rssi;
  363. this.updateDeviceStatus(this.deviceInfo);
  364. }
  365. }
  366. if (topics[3] == 'property' || topics[3] == 'function') {
  367. console.log('接收到【物模型】主题:', topic);
  368. console.log('接收到【物模型】内容:', message);
  369. if(this.oneToMul){
  370. let curTabId = this.summary[this.activeName].id;
  371. let msg = [];
  372. for (let i = 0; i < message.length; i++) {
  373. let curMsg = message[i];
  374. let id = curMsg.id;
  375. let ids = id.split("_");
  376. let value = curMsg.value;
  377. if(ids.length == 2){
  378. if(curTabId == ids[1]){
  379. msg.push({id:ids[0],value:value});
  380. }
  381. }
  382. }
  383. message = msg;
  384. }
  385. // 更新列表中设备的属性
  386. if (this.deviceInfo.serialNumber == deviceNum) {
  387. for (let j = 0; j < message.length; j++) {
  388. let isComplete = false;
  389. // 布尔类型
  390. for (let k = 0; k < this.deviceInfo.boolList.length && !isComplete; k++) {
  391. if (this.deviceInfo.boolList[k].id == message[j].id) {
  392. this.deviceInfo.boolList[k].shadow = message[j].value;
  393. isComplete = true;
  394. break;
  395. }
  396. }
  397. // 枚举类型
  398. for (let k = 0; k < this.deviceInfo.enumList.length && !isComplete; k++) {
  399. if (this.deviceInfo.enumList[k].id == message[j].id) {
  400. this.deviceInfo.enumList[k].shadow = message[j].value;
  401. isComplete = true;
  402. break;
  403. }
  404. }
  405. // 字符串类型
  406. for (let k = 0; k < this.deviceInfo.stringList.length && !isComplete; k++) {
  407. if (this.deviceInfo.stringList[k].id == message[j].id) {
  408. this.deviceInfo.stringList[k].shadow = message[j].value;
  409. isComplete = true;
  410. break;
  411. }
  412. }
  413. // 数组类型
  414. for (let k = 0; k < this.deviceInfo.arrayList.length && !isComplete; k++) {
  415. if (this.deviceInfo.arrayList[k].id == message[j].id) {
  416. this.deviceInfo.arrayList[k].shadow = message[j].value;
  417. isComplete = true;
  418. break;
  419. }
  420. }
  421. // 整数类型
  422. for (let k = 0; k < this.deviceInfo.integerList.length && !isComplete; k++) {
  423. if (this.deviceInfo.integerList[k].id == message[j].id) {
  424. this.deviceInfo.integerList[k].shadow = message[j].value;
  425. isComplete = true;
  426. break;
  427. }
  428. }
  429. // 小数类型
  430. for (let k = 0; k < this.deviceInfo.decimalList.length && !isComplete; k++) {
  431. if (this.deviceInfo.decimalList[k].id == message[j].id) {
  432. this.deviceInfo.decimalList[k].shadow = message[j].value;
  433. isComplete = true;
  434. break;
  435. }
  436. }
  437. // 监测数据
  438. for (let k = 0; k < this.deviceInfo.readOnlyList.length && !isComplete; k++) {
  439. if (this.deviceInfo.readOnlyList[k].id == message[j].id) {
  440. this.deviceInfo.readOnlyList[k].shadow = message[j].value;
  441. // 更新图表
  442. // for (let m = 0; m < this.monitorChart.length; m++) {
  443. // if (message[j].id == this.monitorChart[m].data.id) {
  444. // let data = [{
  445. // value: message[j].value,
  446. // name: this.monitorChart[m].data.name
  447. // }];
  448. // this.monitorChart[m].chart.setOption({
  449. // series: [{
  450. // data: data
  451. // }]
  452. // });
  453. // break;
  454. // }
  455. // }
  456. isComplete = true;
  457. break;
  458. }
  459. }
  460. }
  461. }
  462. }
  463. });
  464. },
  465. /** 发布物模型 类型(1=属性,2=功能) */
  466. publishThingsModel(device, model) {
  467. // 获取缓存的Json物模型
  468. cacheJsonThingsModel(device.productId).then(response => {
  469. let thingsModel = JSON.parse(response.data);
  470. let type = 0;
  471. for (let i = 0; i < thingsModel.functions.length; i++) {
  472. if (model.id == thingsModel.functions[i].id) {
  473. type = 2;
  474. break;
  475. }
  476. }
  477. if (type == 0) {
  478. for (let i = 0; i < thingsModel.properties.length; i++) {
  479. if (model.id == thingsModel.properties[i].id) {
  480. type = 1;
  481. break;
  482. }
  483. }
  484. }
  485. if (type != 0) {
  486. this.mqttPublish(type, device, model);
  487. }
  488. });
  489. },
  490. notifyError(res){
  491. uni.showToast({
  492. title:res,
  493. icon:"error"
  494. })
  495. },
  496. notifySuccess(res){
  497. uni.showToast({
  498. title:res,
  499. icon:"success"
  500. })
  501. },
  502. /**
  503. * Mqtt发布消息
  504. * @type 类型(1=属性,2=功能,3=OTA升级,4=实时监测)
  505. * @device 设备
  506. * @model 物模型
  507. * */
  508. mqttPublish(type, device, model) {
  509. let topic = "";
  510. let message = ""
  511. let oneToMul = false;
  512. if(this.summary.length>0){
  513. oneToMul = true;
  514. }
  515. let modelId = model.id;
  516. if(oneToMul){
  517. let info = this.summary[this.activeName];
  518. let childId = info.id;
  519. modelId = modelId+"_"+childId;
  520. }
  521. if (type == 1) {
  522. if (device.status == 3) {
  523. // 属性,在线模式
  524. topic = "/" + device.productId + "/" + device.serialNumber + "/property-online/get";
  525. } else if (device.isShadow) {
  526. // 属性,离线模式
  527. topic = "/" + device.productId + "/" + device.serialNumber + "/property-offline/post";
  528. }
  529. message = '[{"id":"' + modelId + '","value":"' + model.shadow + '"}]';
  530. } else if (type == 2) {
  531. if (device.status == 3) {
  532. // 功能,在线模式
  533. topic = "/" + device.productId + "/" + device.serialNumber + "/function-online/get";
  534. } else if (device.isShadow) {
  535. // 功能,离线模式
  536. topic = "/" + device.productId + "/" + device.serialNumber + "/function-offline/post";
  537. }
  538. message = '[{"id":"' + modelId + '","value":"' + model.shadow + '"}]';
  539. } else if (type == 3) {
  540. // OTA升级
  541. topic = "/" + device.productId + "/" + device.serialNumber + "/ota/get";
  542. message = '{"version":' + this.firmware.version + ',"downloadUrl":"' + this.getDownloadUrl(this.firmware.filePath) + '"}';
  543. } else {
  544. return;
  545. }
  546. if (topic != "") {
  547. // 发布
  548. this.$mqttTool.publish(topic, message, model.name).then(res => {
  549. this.notifySuccess(res);
  550. }).catch(res => {
  551. this.notifyError(res);
  552. });
  553. }
  554. },
  555. }
  556. }
  557. </script>
  558. <style>
  559. .header{
  560. width: 100%;
  561. background: white;
  562. padding:0px 20rpx
  563. }
  564. .content {
  565. display: flex;
  566. flex-direction: column;
  567. align-items: center;
  568. justify-content: center;
  569. }
  570. .logo {
  571. height: 200rpx;
  572. width: 200rpx;
  573. margin-top: 200rpx;
  574. margin-left: auto;
  575. margin-right: auto;
  576. margin-bottom: 50rpx;
  577. }
  578. .text-area {
  579. margin:10px;
  580. padding-bottom: 10px;
  581. justify-content: center;
  582. width: 100%;
  583. }
  584. .grid-text {
  585. font-size: 14px;
  586. color: #909399;
  587. padding: 10rpx 0 20rpx 0rpx;
  588. /* #ifndef APP-PLUS */
  589. box-sizing: border-box;
  590. /* #endif */
  591. }
  592. .title {
  593. font-size: 36rpx;
  594. color: #8f8f94;
  595. }
  596. </style>