roomSetting.vue 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. <template>
  2. <view class="home-warpTwo">
  3. <view class="header_two">
  4. <view class="radar-box">
  5. <view
  6. :style="{
  7. width: `${length}rpx`,
  8. height: `${width}rpx`,
  9. position: 'absolute',
  10. top: '50%',
  11. left: '50%',
  12. transform: `translate(calc(-50% + ${
  13. xOffset / 2
  14. }rpx), calc(-50% + ${yOffset / 2}rpx))`,
  15. border: `9rpx solid #333333`,
  16. }"
  17. class="tranStyle"
  18. >
  19. </view>
  20. <view
  21. v-for="(item, index) in modules"
  22. :key="index"
  23. class="moduleContent"
  24. >
  25. <view
  26. :class="item.type"
  27. :style="{
  28. width: `${item.width}rpx`,
  29. height: `${item.length}rpx`,
  30. top: `${item.top}rpx`,
  31. left: `${item.left}rpx`,
  32. transform: `rotate(${item.rotate}deg)`,
  33. 'transform-origin': 'center center',
  34. }"
  35. @touchmove.prevent="onTouchMove(index, $event)"
  36. @touchstart="onTouchStart(index, $event)"
  37. @touchend="onTouchEnd(index, $event)"
  38. >
  39. <image
  40. class="module-img"
  41. :src="`../../static/furnitures/${item.type}.png`"
  42. mode=""
  43. />
  44. </view>
  45. </view>
  46. <template>
  47. <image
  48. v-for="item in targetPoints"
  49. :key="item.id"
  50. class="action-icon-M"
  51. :src="`../../static/${lnbAction}.png`"
  52. :style="{
  53. position: 'absolute',
  54. transform: `translate(-50%, -50%) translate3d(${item.displayX}rpx, ${item.displayY}rpx, 0)`,
  55. transition: 'transform 1s linear',
  56. willChange: 'transform',
  57. zIndex: 9999,
  58. top: '0',
  59. left: '0',
  60. width: '50rpx',
  61. height: '50rpx',
  62. }"
  63. />
  64. </template>
  65. <image
  66. class="redar-pic"
  67. src="../../static/rander.png"
  68. mode=""
  69. />
  70. </view>
  71. <view class="airbody">
  72. <view class="header_top">
  73. <view class="airTitle">室内布置</view>
  74. <view class="addfnt" @click="showAddFt">
  75. <image src="../../static/addfnt.png" alt=""> </image
  76. ><view class="add_btn">添加</view></view
  77. >
  78. </view>
  79. <view class="module">
  80. <view class="device-bottom">
  81. <view v-if="modules.length == 0" class="no-data">
  82. 请先点击添加,添加家具
  83. </view>
  84. <view
  85. class="info-box"
  86. v-for="(item, index) in modules"
  87. :key="index"
  88. >
  89. <image
  90. :src="`../../static/furnitures/${item.type}.png`"
  91. mode=""
  92. />
  93. <view class="info-text">
  94. <text>{{ item.name }}</text>
  95. <text
  96. >({{ item.width }}*{{
  97. item.length
  98. }})cm</text
  99. >
  100. </view>
  101. <view class="edit_del">
  102. <image
  103. @click="deleteItem(index)"
  104. src="../../static/delete.png"
  105. alt=""
  106. ></image>
  107. <image
  108. @click="editItem(index)"
  109. style="margin-left: 10rpx"
  110. src="../../static/edit.png"
  111. alt=""
  112. ></image>
  113. </view>
  114. </view>
  115. </view>
  116. </view>
  117. </view>
  118. <view class="bottomTwo" @click="saveRoom">
  119. <view class="previousTip">保存</view>
  120. </view>
  121. </view>
  122. <!-- 选择家具弹窗 -->
  123. <view>
  124. <!-- 底部弹窗 -->
  125. <view class="bottom-modal" v-if="addfntShow">
  126. <!-- 遮罩层 -->
  127. <view class="modal-mask" @click="addfntShow = false"></view>
  128. <!-- 弹窗内容 -->
  129. <view class="modal-container">
  130. <view class="modal-header">
  131. <text class="header-title">选择家具</text>
  132. </view>
  133. <view class="modal-content">
  134. <view class="device-bottom">
  135. <view
  136. class="item-box"
  137. v-for="(item, index) in selectfntLists"
  138. :key="index"
  139. @click="generate(item.type)"
  140. >
  141. <image
  142. :src="`../../static/furnitures/${item.type}.png`"
  143. mode=""
  144. />
  145. <text
  146. >{{ item.name }}({{ item.width }}*{{
  147. item.length
  148. }})</text
  149. >
  150. </view>
  151. </view>
  152. </view>
  153. <view class="modal-footer">
  154. <button
  155. class="footer-btn cancel"
  156. @click="addfntShow = false"
  157. >
  158. 取消
  159. </button>
  160. <button
  161. class="footer-btn confirm"
  162. @click="addfntShow = false"
  163. >
  164. 确定
  165. </button>
  166. </view>
  167. </view>
  168. </view>
  169. </view>
  170. <!-- 编辑家具弹窗 -->
  171. <view class="device_control" v-if="editfntShow">
  172. <view class="control_info">
  173. <view class="control_left">
  174. <image
  175. :src="`../../static/furnitures/${selectImg}.png`"
  176. class="image_class"
  177. ></image>
  178. </view>
  179. <view class="control_right">
  180. <view class="control_right_top">
  181. <view class="title">长(cm)</view>
  182. <input
  183. type="number"
  184. v-model="selectWidth"
  185. placeholder="请输入长"
  186. @blur="changeSize"
  187. />
  188. </view>
  189. <view class="control_right_bottom">
  190. <view class="title">宽(cm)</view>
  191. <input
  192. type="number"
  193. v-model="selectHeight"
  194. placeholder="请输入宽"
  195. @blur="changeSize"
  196. />
  197. </view>
  198. </view>
  199. </view>
  200. <view class="control_operat" v-if="editfntShow">
  201. <view class="operat_left">
  202. <view class="top">
  203. <image
  204. src="../../static/btn_top.png"
  205. mode="aspectFit"
  206. @click="movefut(0, -10)"
  207. ></image>
  208. </view>
  209. <view class="middle">
  210. <image
  211. src="../../static/btn_left.png"
  212. mode="aspectFit"
  213. @click="movefut(-10, 0)"
  214. ></image>
  215. <image
  216. src="../../static/btn_right.png"
  217. mode="aspectFit"
  218. @click="movefut(10, 0)"
  219. ></image>
  220. </view>
  221. <view class="bottom">
  222. <image
  223. src="../../static/btn_bot.png"
  224. mode="aspectFit"
  225. @click="movefut(0, 10)"
  226. ></image>
  227. </view>
  228. </view>
  229. <view class="operat_right">
  230. <view class="rotate">
  231. <view class="rotate_left" @click="rotatefut(-90)">
  232. <image src="../../static/rote_left.png"></image>
  233. </view>
  234. <view class="rotate_rigt" @click="rotatefut(90)">
  235. <image src="../../static/rote_right.png"></image>
  236. </view>
  237. </view>
  238. <view class="del_funit" @click="editfntShow = false"
  239. >完成</view
  240. >
  241. </view>
  242. </view>
  243. </view>
  244. </view>
  245. </template>
  246. <script>
  247. import MqttService from "../../utils/globalMqtt.js";
  248. export default {
  249. name: "my",
  250. data() {
  251. return {
  252. // 设备参数
  253. deviceWidth: 400,
  254. deviceHeight: 500,
  255. modules: [],
  256. width: 0,
  257. length: 0,
  258. selectedIndex: 0,
  259. selectIndex: 0,
  260. controlOptions: [],
  261. startX: 0, // 记录触摸开始时的X坐标
  262. startY: 0, // 记录触摸开始时的Y坐标
  263. draggingIndex: null, // 记录当前正在拖动的模块的索引
  264. selectedItem: [],
  265. rotateShow: false,
  266. selectRotate: "",
  267. selectedRotate: "",
  268. softWare: "",
  269. xOffset: 0,
  270. yOffset: 0,
  271. angle: 0,
  272. xxStart: "",
  273. xxEnd: "",
  274. yyStart: "",
  275. yyEnd: "",
  276. // 控制部分参数
  277. selectImg: "setting",
  278. selectWidth: "",
  279. selectHeight: "",
  280. addfntShow: false,
  281. editfntShow: false,
  282. selectfntLists: [
  283. {
  284. name: "床",
  285. type: "bed",
  286. width: 80,
  287. length: 70,
  288. },
  289. {
  290. name: "柜子",
  291. type: "bed_cabinet",
  292. width: 40,
  293. length: 80,
  294. },
  295. {
  296. name: "化妆椅",
  297. type: "bed_dressing_chair",
  298. width: 40,
  299. length: 40,
  300. },
  301. {
  302. name: "化妆镜",
  303. type: "bed_dressing_mirror",
  304. width: 40,
  305. length: 80,
  306. },
  307. {
  308. name: "床头柜",
  309. type: "bed_table",
  310. width: 40,
  311. length: 40,
  312. },
  313. {
  314. name: "脸盆",
  315. type: "bath_basin",
  316. width: 40,
  317. length: 80,
  318. },
  319. {
  320. name: "门",
  321. type: "bath_door",
  322. width: 40,
  323. length: 40,
  324. },
  325. {
  326. name: "淋浴",
  327. type: "bath_shower",
  328. width: 40,
  329. length: 60,
  330. },
  331. {
  332. name: "马桶",
  333. type: "bath_toilet",
  334. width: 40,
  335. length: 40,
  336. },
  337. {
  338. name: "餐桌(方形)",
  339. type: "dining_table_rect",
  340. width: 40,
  341. length: 40,
  342. },
  343. {
  344. name: "餐桌",
  345. type: "dining_table",
  346. width: 40,
  347. length: 80,
  348. },
  349. {
  350. name: "餐椅",
  351. type: "dining_chair",
  352. width: 40,
  353. length: 80,
  354. },
  355. {
  356. name: "冰箱",
  357. type: "dining_fridge",
  358. width: 40,
  359. length: 80,
  360. },
  361. {
  362. name: "书柜",
  363. type: "living_bookcase",
  364. width: 40,
  365. length: 80,
  366. },
  367. {
  368. name: "沙发",
  369. type: "living_sofa",
  370. width: 40,
  371. length: 80,
  372. },
  373. {
  374. name: "茶几",
  375. type: "living_tea_table",
  376. width: 40,
  377. length: 80,
  378. },
  379. {
  380. name: "电视柜",
  381. type: "living_tv_stand",
  382. width: 40,
  383. length: 80,
  384. },
  385. {
  386. name: "单人沙发",
  387. type: "living_sofa_single",
  388. width: 40,
  389. length: 40,
  390. },
  391. ],
  392. lnbAction: "action8",
  393. // mqtt模块
  394. targetPoints: {},
  395. inactivityTimer: null,
  396. left: 0,
  397. top: 0,
  398. clientIdProp: null,
  399. clientId: null,
  400. // 重新计算雷达坐标位置
  401. x_radar: 200,
  402. y_radar: 200,
  403. };
  404. },
  405. computed: {},
  406. methods: {
  407. onTouchMove(index, event) {
  408. if (this.draggingIndex === null) return;
  409. const { modules, startX, startY } = this;
  410. const currentX = event.touches[0].clientX;
  411. const currentY = event.touches[0].clientY;
  412. const deltaX = Math.round(currentX - startX);
  413. const deltaY = Math.round(currentY - startY);
  414. // let x = Math.round(
  415. // this.xxStart + event.currentTarget.offsetLeft * 2
  416. // );
  417. // let y = Math.round(this.yyEnd - event.currentTarget.offsetTop * 2);
  418. // 创建新数组避免直接修改原数组
  419. const updatedModules = [...this.modules];
  420. updatedModules[this.draggingIndex] = {
  421. ...updatedModules[this.draggingIndex],
  422. left: Math.round(
  423. updatedModules[this.draggingIndex].left + deltaX
  424. ),
  425. top: Math.round(
  426. updatedModules[this.draggingIndex].top + deltaY
  427. ),
  428. // x,
  429. // y,
  430. };
  431. // Vue 的响应式更新
  432. this.modules = updatedModules;
  433. this.startX = currentX;
  434. this.startY = currentY;
  435. },
  436. onTouchStart(index, event) {
  437. const selectedItem = this.modules[index];
  438. const selectedItemWidth = selectedItem.width / 40;
  439. const selectedItemHeight = selectedItem.length / 40;
  440. this.draggingIndex = index;
  441. this.selectedIndex = index;
  442. this.startX = event.touches[0].clientX;
  443. this.startY = event.touches[0].clientY;
  444. this.selectedItem = selectedItem;
  445. this.selectedItemWidth = selectedItemWidth;
  446. this.selectedItemHeight = selectedItemHeight;
  447. this.selectImg = this.modules[index].type;
  448. this.selectWidth = this.modules[index].width;
  449. this.selectHeight = this.modules[index].length;
  450. },
  451. onTouchEnd(index, event) {
  452. // this.draggingIndex = null;
  453. },
  454. generate(featuriesType) {
  455. let component = {
  456. name: "",
  457. type: featuriesType,
  458. width: 0,
  459. length: 0,
  460. top: 200,
  461. left: 200,
  462. rotate: 0,
  463. };
  464. switch (featuriesType) {
  465. case "bed":
  466. component.width = 80;
  467. component.length = 120;
  468. component.name = "床";
  469. break;
  470. case "bed_cabinet":
  471. component.width = 40;
  472. component.length = 80;
  473. component.name = "柜子";
  474. break;
  475. case "bed_dressing_chair":
  476. component.width = 40;
  477. component.length = 40;
  478. component.name = "化妆椅";
  479. break;
  480. case "bed_dressing_mirror":
  481. component.width = 40;
  482. component.length = 80;
  483. component.name = "化妆镜";
  484. break;
  485. case "bed_table":
  486. component.width = 40;
  487. component.length = 40;
  488. component.name = "床头柜";
  489. break;
  490. case "bath_basin":
  491. component.width = 40;
  492. component.length = 80;
  493. component.name = "脸盆";
  494. break;
  495. case "bath_door":
  496. component.width = 40;
  497. component.length = 40;
  498. component.name = "门";
  499. break;
  500. case "bath_shower":
  501. component.width = 40;
  502. component.length = 60;
  503. component.name = "淋浴";
  504. break;
  505. case "bath_toilet":
  506. component.width = 40;
  507. component.length = 40;
  508. component.name = "马桶";
  509. break;
  510. case "dining_table_rect":
  511. component.width = 40;
  512. component.length = 40;
  513. component.name = "餐桌(方形)";
  514. break;
  515. case "dining_table":
  516. component.width = 40;
  517. component.length = 80;
  518. component.name = "餐桌";
  519. break;
  520. case "dining_chair":
  521. component.width = 40;
  522. component.length = 80;
  523. component.name = "餐椅";
  524. break;
  525. case "dining_fridge":
  526. component.width = 40;
  527. component.length = 80;
  528. component.name = "冰箱";
  529. break;
  530. case "living_bookcase":
  531. component.width = 40;
  532. component.length = 80;
  533. component.name = "书柜";
  534. break;
  535. case "living_sofa":
  536. component.width = 40;
  537. component.length = 80;
  538. component.name = "沙发";
  539. break;
  540. case "living_tea_table":
  541. component.width = 40;
  542. component.length = 80;
  543. component.name = "茶几";
  544. break;
  545. case "living_tv_stand":
  546. component.width = 40;
  547. component.length = 80;
  548. component.name = "电视柜";
  549. break;
  550. case "living_sofa_single":
  551. component.width = 40;
  552. component.length = 40;
  553. component.name = "单人沙发";
  554. break;
  555. default:
  556. break;
  557. }
  558. component.type = featuriesType;
  559. if (component !== null) {
  560. this.modules.push(component);
  561. }
  562. },
  563. // 输入宽高改变家具大小
  564. changeSize() {
  565. if (this.selectHeight <= 0 || this.selectWidth <= 0) {
  566. this.selectHeight = this.modules[this.draggingIndex].length;
  567. this.selectWidth = this.modules[this.draggingIndex].width;
  568. return this.showModal("提示", "家具长宽不能小于0");
  569. }
  570. this.modules[this.draggingIndex].length = this.selectHeight;
  571. this.modules[this.draggingIndex].width = this.selectWidth;
  572. },
  573. movefut(left, top) {
  574. this.modules[this.draggingIndex].left =
  575. this.modules[this.draggingIndex].left + left;
  576. this.modules[this.draggingIndex].top =
  577. this.modules[this.draggingIndex].top + top;
  578. },
  579. rotatefut(rotate) {
  580. let item = this.modules[this.draggingIndex];
  581. if (item.rotate == 0) {
  582. item.rotate = 360;
  583. }
  584. let currentRotate = item.rotate || 0;
  585. let newAngle = (currentRotate + rotate) % 360;
  586. this.modules[this.draggingIndex].rotate = newAngle;
  587. },
  588. deleteItem(index) {
  589. this.draggingIndex = index;
  590. if (this.draggingIndex === null) {
  591. return this.showModal("提示", "请先选择家具");
  592. }
  593. this.showModal("提示", "是否确认删除该家具", (res) => {
  594. if (res.confirm) {
  595. this.modules.splice(this.draggingIndex, 1);
  596. this.draggingIndex = null;
  597. }
  598. });
  599. },
  600. editItem(index) {
  601. this.draggingIndex = index;
  602. this.editfntShow = true;
  603. const selectedItem = this.modules[index];
  604. const selectedItemWidth = selectedItem.width / 40;
  605. const selectedItemHeight = selectedItem.length / 40;
  606. this.draggingIndex = index;
  607. this.selectedIndex = index;
  608. this.selectedItem = selectedItem;
  609. this.selectedItemWidth = selectedItemWidth;
  610. this.selectedItemHeight = selectedItemHeight;
  611. this.selectImg = this.modules[index].type;
  612. this.selectWidth = this.modules[index].width;
  613. this.selectHeight = this.modules[index].length;
  614. },
  615. // 提取公共的模态框方法
  616. showModal(title, content, confirmCallback) {
  617. wx.showModal({
  618. title,
  619. content,
  620. success: (res) => {
  621. if (res.confirm && confirmCallback) {
  622. confirmCallback(res);
  623. }
  624. },
  625. });
  626. },
  627. saveRoom() {
  628. console.log(this.modules, 9999);
  629. if (this.modules.length > 0) {
  630. this.modules.forEach((item) => {
  631. item.x = item.left - this.x_radar;
  632. item.y = this.y_radar - item.top;
  633. });
  634. }
  635. this.$http
  636. .post(
  637. "wap/room/saveRoom",
  638. JSON.stringify({
  639. devId: this.devId,
  640. furnitures: this.modules,
  641. }),
  642. {
  643. header: {
  644. "Content-Type": "application/json;charset=UTF-8",
  645. },
  646. }
  647. )
  648. .then((res) => {
  649. if (res.data.code == 200) {
  650. uni.showToast({
  651. title: "保存成功",
  652. icon: "success",
  653. duration: 1500,
  654. });
  655. setTimeout(() => {
  656. uni.navigateBack({
  657. delta: 1,
  658. });
  659. }, 1500);
  660. } else {
  661. uni.showToast({
  662. title: res.data.msg,
  663. icon: "none",
  664. duration: 1500,
  665. });
  666. }
  667. })
  668. .catch((err) => {});
  669. },
  670. showAddFt() {
  671. this.addfntShow = true;
  672. },
  673. getdevInfo(devId) {
  674. this.$http
  675. .get(`wap/device/queryDeviceInfoById`, {
  676. devId: devId,
  677. })
  678. .then((res) => {
  679. if (res.data.data) {
  680. this.devInfo = res.data.data;
  681. this.width = Math.abs(
  682. this.devInfo.yyEnd - this.devInfo.yyStart
  683. );
  684. this.length = Math.abs(
  685. this.devInfo.xxEnd - this.devInfo.xxStart
  686. );
  687. this.calculateOffest();
  688. this.statusLight = this.devInfo.statusLight;
  689. this.xxStart = this.devInfo.xxStart;
  690. this.xxEnd = this.devInfo.xxEnd;
  691. this.yyStart = this.devInfo.yyStart;
  692. this.yyEnd = this.devInfo.yyEnd;
  693. } else {
  694. uni.showToast({
  695. title: res.data.message,
  696. icon: "none",
  697. });
  698. }
  699. })
  700. .catch((err) => {
  701. console.log(err, 8888);
  702. });
  703. },
  704. calculateOffest() {
  705. let xxStart = "";
  706. let xxEnd = "";
  707. let yyStart = "";
  708. let yyEnd = "";
  709. if (this.devInfo.northAngle == 0) {
  710. xxStart = this.devInfo.xxStart;
  711. xxEnd = this.devInfo.xxEnd;
  712. yyStart = this.devInfo.yyStart;
  713. yyEnd = this.devInfo.yyEnd;
  714. }
  715. if (this.devInfo.northAngle == 90) {
  716. xxStart = this.devInfo.xxStart;
  717. xxEnd = this.devInfo.xxEnd;
  718. yyStart = -this.devInfo.yyEnd;
  719. yyEnd = -this.devInfo.yyStart;
  720. }
  721. if (this.devInfo.northAngle == 180) {
  722. xxStart = -this.devInfo.xxEnd;
  723. xxEnd = -this.devInfo.xxStart;
  724. yyStart = -this.devInfo.yyEnd;
  725. yyEnd = -this.devInfo.yyStart;
  726. }
  727. if (this.devInfo.northAngle == 270) {
  728. xxStart = -this.devInfo.xxEnd;
  729. xxEnd = -this.devInfo.xxStart;
  730. yyStart = this.devInfo.yyStart;
  731. yyEnd = this.devInfo.yyEnd;
  732. }
  733. this.xOffset = xxStart + xxEnd;
  734. this.yOffset = -(yyStart + yyEnd);
  735. },
  736. getRoomInfo(devId) {
  737. this.$http
  738. .get(`wap/room/readRoom`, {
  739. devId: devId,
  740. })
  741. .then((res) => {
  742. if (res.data.data) {
  743. this.modules = res.data.data.furnitures;
  744. }
  745. });
  746. },
  747. handleMessage(topic, message, clientId) {
  748. // 清除不活动定时器
  749. clearTimeout(this.inactivityTimer);
  750. this.inactivityTimer = setTimeout(() => {
  751. this.targetPoints = {};
  752. // console.log("长时间没有点位消除");
  753. }, 1500);
  754. // 验证topic格式
  755. const match = topic.match(/^\/dev\/(.+)\/tracker_targets$/);
  756. if (!match || match[1] !== clientId) return;
  757. try {
  758. const data = JSON.parse(message.toString());
  759. // if (data.health) {
  760. // if (
  761. // data.health.breath_rpm ||
  762. // data.health.breath_rpm === 0
  763. // ) {
  764. // this.receptHealth(Math.floor(data.health.breath_rpm));
  765. // } else {
  766. // }
  767. // }
  768. // console.log(data.tracker_targets, "MQTT消息解析成功22222");
  769. this.processTrackerData(data.tracker_targets);
  770. } catch (e) {
  771. console.error("MQTT消息解析失败", e);
  772. }
  773. },
  774. processTrackerData(arr) {
  775. if (Array.isArray(arr) && arr.length > 0 && Array.isArray(arr[0])) {
  776. this.targetPoints = {};
  777. const currentIds = new Set();
  778. const newTargetPoints = {};
  779. // 处理每个追踪目标
  780. arr.forEach((item) => {
  781. if (!Array.isArray(item) || item.length < 4) return;
  782. const [x, y, z, id] = item;
  783. currentIds.add(id.toString());
  784. // 处理新点或更新现有点
  785. if (!this.targetPoints[id]) {
  786. newTargetPoints[id] = this.createNewTargetPoint(
  787. x,
  788. y,
  789. z,
  790. id
  791. );
  792. } else {
  793. newTargetPoints[id] = this.updateExistingTargetPoint(
  794. this.targetPoints[id],
  795. x,
  796. y,
  797. z,
  798. 2
  799. );
  800. }
  801. });
  802. // 移除不存在的点
  803. Object.keys(this.targetPoints).forEach((id) => {
  804. if (!currentIds.has(id)) {
  805. delete this.targetPoints[id];
  806. }
  807. });
  808. // 更新目标点
  809. this.targetPoints = {
  810. ...this.targetPoints,
  811. ...newTargetPoints,
  812. };
  813. // console.log(this.targetPoints, "更新后的点位数据2222");
  814. if (Array.isArray(this.targetPoints)) {
  815. this.targetPoints = this.targetPoints.filter(
  816. (item) => item !== null && item !== undefined
  817. );
  818. }
  819. }
  820. },
  821. createNewTargetPoint(x, y, z, id) {
  822. return {
  823. x,
  824. y,
  825. z,
  826. id,
  827. displayX: this.x_radar + x,
  828. displayY: this.y_radar - y,
  829. lastX: x,
  830. lastY: y,
  831. };
  832. },
  833. updateExistingTargetPoint(existingPoint, x, y, z, THRESHOLD) {
  834. const dx = x - existingPoint.lastX;
  835. const dy = y - existingPoint.lastY;
  836. const distance = Math.sqrt(dx * dx + dy * dy);
  837. if (distance > THRESHOLD) {
  838. return {
  839. ...existingPoint,
  840. x,
  841. y,
  842. z,
  843. lastX: x,
  844. lastY: y,
  845. displayX: this.x_radar + x,
  846. displayY: this.y_radar - y,
  847. };
  848. }
  849. return existingPoint;
  850. },
  851. initSubscriptions() {
  852. const topicList = [
  853. {
  854. topic: `/dev/${this.clientId}/tracker_targets`,
  855. key: "unsubscribeFn",
  856. callback: (message, msgTopic) => {
  857. const dataMatch = msgTopic.match(
  858. /^\/dev\/(.+)\/tracker_targets$/
  859. );
  860. const cmdMatch = msgTopic.match(
  861. /^\/mps\/wx_(.+)\/notice$/
  862. );
  863. if (dataMatch && dataMatch[1] === this.clientId) {
  864. this.handleMessage(
  865. msgTopic,
  866. message,
  867. this.clientId
  868. );
  869. } else if (cmdMatch) {
  870. this.$refs.alarmModel.hanOtherMessage(
  871. msgTopic,
  872. message
  873. );
  874. }
  875. },
  876. },
  877. {
  878. topic: `/dev/${this.clientId}/falling_event_change`,
  879. key: "fallingEventChange",
  880. callback: (message, msgTopic) => {
  881. const dataMatch = msgTopic.match(
  882. /^\/dev\/(.+)\/falling_event_change$/
  883. );
  884. if (dataMatch && dataMatch[1] === this.clientId) {
  885. const dataMessage = JSON.parse(message.toString());
  886. console.log(dataMessage, 888888);
  887. if (dataMessage.falling == 1) {
  888. this.falling = dataMessage.falling;
  889. this.lnbAction = "actionWarn";
  890. } else if (
  891. dataMessage.falling == 2 ||
  892. dataMessage.falling == 3
  893. ) {
  894. this.falling = dataMessage.falling;
  895. this.lnbAction = "actionSerious";
  896. } else {
  897. this.lnbAction = "action8";
  898. }
  899. }
  900. },
  901. },
  902. ];
  903. topicList.forEach((item) => {
  904. // 避免重复订阅
  905. if (this[item.key]) return;
  906. const subscribeFunc = () => {
  907. const unsubscribe = MqttService.subscribe(
  908. "DATA",
  909. item.topic,
  910. item.callback
  911. );
  912. if (unsubscribe) {
  913. this[item.key] = unsubscribe;
  914. console.log(`✅ 已成功订阅主题: ${item.topic}`);
  915. }
  916. };
  917. if (MqttService.dataConnected) {
  918. subscribeFunc();
  919. } else {
  920. // MQTT 未连接,等待重连成功再订阅
  921. const handler = () => {
  922. subscribeFunc();
  923. uni.$off("mqttData-ready", handler);
  924. };
  925. uni.$on("mqttData-ready", handler);
  926. }
  927. });
  928. },
  929. },
  930. onLoad(options) {
  931. this.devId = options.devId;
  932. this.clientId = options.clientId;
  933. console.log(options, "options111111");
  934. this.getdevInfo(this.devId);
  935. this.getRoomInfo(this.devId);
  936. },
  937. onShow() {
  938. // const topic = `/dev/${this.clientId}/tracker_targets`;
  939. // this.unsubscribeFn = MqttService.subscribe(
  940. // "DATA",
  941. // topic,
  942. // (message, msgTopic) => {
  943. // const dataMatch = msgTopic.match(
  944. // /^\/dev\/(.+)\/tracker_targets$/
  945. // );
  946. // if (dataMatch && dataMatch[1] === this.clientId) {
  947. // this.handleMessage(msgTopic, message, this.clientId);
  948. // }
  949. // }
  950. // );
  951. // if (this.unsubscribeFn) {
  952. // console.log(`✅ 已成功订阅主题: ${topic}`);
  953. // }
  954. if (MqttService.dataConnected && MqttService.dataClient) {
  955. // 已连接,直接订阅
  956. this.initSubscriptions();
  957. }
  958. },
  959. onHide() {
  960. ["unsubscribeFn", "fallingEventChange"].forEach((key) => {
  961. if (this[key]) {
  962. this[key]();
  963. this[key] = null;
  964. }
  965. });
  966. },
  967. onUnload() {
  968. // 清理定时器
  969. clearTimeout(this.autoPlayinterval);
  970. // 清理自动滑动定时器
  971. clearInterval(this.setIntervalVal);
  972. this.setIntervalVal = null;
  973. this.autoPlayinterval = null;
  974. // 取消订阅
  975. ["unsubscribeFn", "fallingEventChange"].forEach((key) => {
  976. if (this[key]) {
  977. this[key]();
  978. this[key] = null;
  979. }
  980. });
  981. },
  982. };
  983. </script>
  984. <style lang="less" scoped>
  985. .home-warpTwo {
  986. position: relative;
  987. height: 100vh;
  988. background: #f4f4f4;
  989. .header_two {
  990. width: 750rpx;
  991. padding-top: 20rpx;
  992. background: linear-gradient(180deg, #faede2 0%, #ffffff 100%);
  993. .radar-box {
  994. margin: 0 auto;
  995. position: relative;
  996. width: 400rpx;
  997. height: 400rpx;
  998. background: #ffffff;
  999. border-radius: 37.5rpx;
  1000. box-sizing: border-box;
  1001. .tranStyle {
  1002. position: absolute;
  1003. overflow: hidden;
  1004. background-color: #fff;
  1005. border: 9rpx solid #333333;
  1006. background-image: url("https://hflnxx.oss-cn-shanghai.aliyuncs.com/IMAGE/20250919/toilet_bg.png");
  1007. background-repeat: no-repeat;
  1008. background-position: center;
  1009. }
  1010. .moduleContent {
  1011. view {
  1012. position: absolute;
  1013. }
  1014. .module-img {
  1015. width: 100%;
  1016. height: 100%;
  1017. display: block;
  1018. }
  1019. }
  1020. .action-icon-G {
  1021. position: absolute;
  1022. width: 100rpx;
  1023. height: 100rpx;
  1024. }
  1025. .action-icon-M {
  1026. // position: absolute;
  1027. width: 50rpx;
  1028. height: 50rpx;
  1029. }
  1030. .redar-pic {
  1031. position: absolute;
  1032. top: 50%;
  1033. left: 50%;
  1034. width: 40rpx;
  1035. height: 40rpx;
  1036. transform: translate(-50%, -50%);
  1037. z-index: 20;
  1038. }
  1039. }
  1040. .airbody {
  1041. margin: 20rpx auto 0 auto;
  1042. width: 700rpx;
  1043. background: #ffffff;
  1044. border-radius: 38rpx;
  1045. box-sizing: border-box;
  1046. .header_top {
  1047. padding: 0rpx 40rpx;
  1048. display: flex;
  1049. justify-content: space-between;
  1050. align-items: center;
  1051. .airTitle {
  1052. font-weight: 500;
  1053. color: #784c41;
  1054. font-size: 32rpx;
  1055. padding-left: 20rpx;
  1056. padding-top: 20rpx;
  1057. }
  1058. .addfnt {
  1059. display: flex;
  1060. align-items: center;
  1061. image {
  1062. margin-top: 7rpx;
  1063. width: 25rpx;
  1064. height: 25rpx;
  1065. }
  1066. .add_btn {
  1067. margin-left: 10rpx;
  1068. font-size: 32rpx;
  1069. }
  1070. }
  1071. }
  1072. .module {
  1073. padding: 10rpx 30rpx;
  1074. bottom: 0;
  1075. box-sizing: border-box;
  1076. width: 100%;
  1077. background: #ffffff;
  1078. border-radius: 20rpx 20rpx 20rpx 20rpx;
  1079. .device-bottom {
  1080. margin-top: 20rpx;
  1081. height: 500rpx;
  1082. overflow-y: scroll;
  1083. .no-data {
  1084. margin-top: 100rpx;
  1085. text-align: center;
  1086. }
  1087. .info-box {
  1088. display: flex;
  1089. align-items: center;
  1090. // width: 620rpx;
  1091. padding: 0 30rpx;
  1092. height: 110rpx;
  1093. background: #f8f8f8;
  1094. border-radius: 38rpx;
  1095. border-radius: 20rpx 20rpx 20rpx 20rpx;
  1096. font-family: PingFang SC, PingFang SC;
  1097. font-weight: 400;
  1098. font-size: 26rpx;
  1099. .info-text {
  1100. display: flex;
  1101. flex-direction: column;
  1102. margin-left: 20rpx;
  1103. }
  1104. image {
  1105. margin-bottom: 10rpx;
  1106. width: 75rpx;
  1107. height: 75rpx;
  1108. }
  1109. .edit_del {
  1110. margin-left: auto;
  1111. image {
  1112. width: 40rpx;
  1113. height: 40rpx;
  1114. }
  1115. }
  1116. }
  1117. .info-box:not(:first-child) {
  1118. margin-top: 20rpx;
  1119. }
  1120. }
  1121. .device_control {
  1122. overflow: hidden;
  1123. margin-top: 20rpx;
  1124. .control_info {
  1125. height: 160rpx;
  1126. background: #f8f8f8;
  1127. border-radius: 37rpx;
  1128. box-sizing: border-box;
  1129. padding: 20rpx 30rpx;
  1130. display: flex;
  1131. align-content: center;
  1132. justify-content: center;
  1133. .control_left {
  1134. width: 110rpx;
  1135. height: 110rpx;
  1136. .image_class {
  1137. width: 110rpx;
  1138. height: 110rpx;
  1139. }
  1140. }
  1141. .control_right {
  1142. margin-left: 100rpx;
  1143. .control_right_top {
  1144. width: 280rpx;
  1145. height: 50rpx;
  1146. display: flex;
  1147. justify-content: space-around;
  1148. align-items: center;
  1149. background: #ffffff;
  1150. border-radius: 18rpx;
  1151. .title {
  1152. color: #a0acbe;
  1153. font-size: 28rpx;
  1154. }
  1155. input {
  1156. width: 130rpx;
  1157. color: #a0acbe;
  1158. }
  1159. }
  1160. .control_right_bottom {
  1161. width: 280rpx;
  1162. height: 50rpx;
  1163. display: flex;
  1164. justify-content: space-around;
  1165. align-items: center;
  1166. margin-top: 10rpx;
  1167. background: #ffffff;
  1168. border-radius: 18rpx;
  1169. .title {
  1170. color: #a0acbe;
  1171. font-size: 28rpx;
  1172. }
  1173. input {
  1174. width: 130rpx;
  1175. color: #a0acbe;
  1176. }
  1177. }
  1178. }
  1179. }
  1180. }
  1181. }
  1182. }
  1183. .bottomTwo {
  1184. display: flex;
  1185. align-items: center;
  1186. justify-content: center;
  1187. position: fixed;
  1188. bottom: 0;
  1189. left: 0;
  1190. width: 750rpx;
  1191. height: 120rpx;
  1192. background: #f3e2dd;
  1193. .previousTip {
  1194. font-weight: 500;
  1195. color: #111111;
  1196. font-size: 32rpx;
  1197. }
  1198. }
  1199. }
  1200. .bottom-modal {
  1201. position: fixed;
  1202. top: 0;
  1203. left: 0;
  1204. right: 0;
  1205. bottom: 0;
  1206. z-index: 999;
  1207. display: flex;
  1208. flex-direction: column;
  1209. // 遮罩层
  1210. .modal-mask {
  1211. position: absolute;
  1212. top: 0;
  1213. left: 0;
  1214. right: 0;
  1215. bottom: 0;
  1216. background-color: rgba(0, 0, 0, 0.5);
  1217. transition: all 0.3s ease;
  1218. }
  1219. // 弹窗容器
  1220. .modal-container {
  1221. position: fixed;
  1222. left: 0;
  1223. right: 0;
  1224. bottom: 0;
  1225. background-color: #fff;
  1226. border-radius: 16rpx 16rpx 0 0;
  1227. max-height: 70vh;
  1228. display: flex;
  1229. flex-direction: column;
  1230. transform: translateY(0);
  1231. transition: transform 0.3s ease;
  1232. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
  1233. // 头部样式
  1234. .modal-header {
  1235. position: relative;
  1236. padding: 30rpx;
  1237. text-align: center;
  1238. border-bottom: 1rpx solid #f5f5f5;
  1239. .header-title {
  1240. font-size: 32rpx;
  1241. font-weight: 500;
  1242. color: #333;
  1243. }
  1244. }
  1245. // 内容区域
  1246. .modal-content {
  1247. height: 600rpx;
  1248. overflow-y: scroll;
  1249. .device-bottom {
  1250. display: flex;
  1251. flex-wrap: wrap;
  1252. justify-content: space-between;
  1253. gap: 20rpx;
  1254. padding: 0 30rpx;
  1255. box-sizing: border-box;
  1256. .item-box {
  1257. display: flex;
  1258. flex-direction: column;
  1259. align-items: center;
  1260. justify-content: center;
  1261. width: 200rpx;
  1262. height: 160rpx;
  1263. background: #f5f7fa;
  1264. border-radius: 20rpx 20rpx 20rpx 20rpx;
  1265. font-family: PingFang SC, PingFang SC;
  1266. font-weight: 400;
  1267. font-size: 26rpx;
  1268. image {
  1269. margin-bottom: 20rpx;
  1270. width: 40rpx;
  1271. height: 40rpx;
  1272. }
  1273. }
  1274. }
  1275. }
  1276. // 底部按钮
  1277. .modal-footer {
  1278. display: flex;
  1279. padding: 20rpx 30rpx;
  1280. border-top: 1rpx solid #f5f5f5;
  1281. .footer-btn {
  1282. flex: 1;
  1283. height: 80rpx;
  1284. line-height: 80rpx;
  1285. border-radius: 40rpx;
  1286. font-size: 28rpx;
  1287. margin: 0 10rpx;
  1288. &.cancel {
  1289. border: none;
  1290. background-color: #f5f5f5;
  1291. color: #666;
  1292. }
  1293. &.confirm {
  1294. border: none;
  1295. background-color: #f3e2dd;
  1296. color: #111111;
  1297. }
  1298. }
  1299. }
  1300. }
  1301. }
  1302. // 动画效果
  1303. .modal-enter-active,
  1304. .modal-leave-active {
  1305. transition: all 0.7s;
  1306. .modal-mask {
  1307. opacity: 1;
  1308. }
  1309. .modal-container {
  1310. transform: translateY(0);
  1311. }
  1312. }
  1313. .modal-enter,
  1314. .modal-leave-to {
  1315. .modal-mask {
  1316. opacity: 0;
  1317. }
  1318. .modal-container {
  1319. transform: translateY(100%);
  1320. }
  1321. }
  1322. // 编辑弹窗
  1323. .device_control {
  1324. position: fixed;
  1325. left: 0;
  1326. right: 0;
  1327. bottom: 0;
  1328. z-index: 999;
  1329. height: 550rpx;
  1330. background: #f8f8f8;
  1331. border-radius: 37rpx 37rpx 0 0;
  1332. box-shadow: 0 -5rpx 20rpx rgba(0, 0, 0, 0.1);
  1333. padding: 30rpx;
  1334. display: flex;
  1335. flex-direction: column;
  1336. // 控制区域(原control_info)
  1337. .control_info {
  1338. display: flex;
  1339. align-items: space-around;
  1340. margin-bottom: 40rpx; // 与操作区域间距
  1341. .control_left {
  1342. margin-left: 100rpx;
  1343. .image_class {
  1344. width: 120rpx;
  1345. height: 120rpx;
  1346. }
  1347. }
  1348. .control_right {
  1349. flex: 1;
  1350. margin-left: 100rpx;
  1351. display: flex;
  1352. flex-direction: column;
  1353. justify-content: space-between;
  1354. .control_right_top,
  1355. .control_right_bottom {
  1356. display: flex;
  1357. align-items: center;
  1358. .title {
  1359. font-size: 28rpx;
  1360. color: #666;
  1361. margin-bottom: 10rpx;
  1362. }
  1363. input {
  1364. width: 40%;
  1365. height: 60rpx;
  1366. background: #fff;
  1367. border-radius: 12rpx;
  1368. padding: 0 20rpx;
  1369. }
  1370. }
  1371. }
  1372. }
  1373. .control_operat {
  1374. display: flex;
  1375. flex: 1;
  1376. padding: 0 30rpx;
  1377. .operat_left {
  1378. width: 300rpx;
  1379. display: flex;
  1380. flex-direction: column;
  1381. align-items: center;
  1382. .top,
  1383. .middle,
  1384. .bottom {
  1385. width: 100%;
  1386. display: flex;
  1387. justify-content: center;
  1388. }
  1389. .middle {
  1390. justify-content: space-between;
  1391. margin: 15rpx 0;
  1392. }
  1393. image {
  1394. width: 80rpx;
  1395. height: 80rpx;
  1396. background: #fff;
  1397. border-radius: 50%;
  1398. padding: 15rpx;
  1399. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  1400. }
  1401. }
  1402. .operat_right {
  1403. flex: 1;
  1404. display: flex;
  1405. flex-direction: column;
  1406. align-items: flex-end;
  1407. padding-top: 70rpx;
  1408. .rotate {
  1409. display: flex;
  1410. margin-bottom: 30rpx;
  1411. .rotate_left,
  1412. .rotate_rigt {
  1413. width: 100rpx;
  1414. height: 70rpx;
  1415. background: #fff;
  1416. border-radius: 12rpx;
  1417. display: flex;
  1418. align-items: center;
  1419. justify-content: center;
  1420. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  1421. image {
  1422. width: 40rpx;
  1423. height: 40rpx;
  1424. }
  1425. }
  1426. .rotate_rigt {
  1427. margin-left: 20rpx;
  1428. }
  1429. }
  1430. .del_funit {
  1431. width: 220rpx;
  1432. height: 70rpx;
  1433. background: #7b4f43;
  1434. color: white;
  1435. border-radius: 12rpx;
  1436. display: flex;
  1437. align-items: center;
  1438. justify-content: center;
  1439. font-size: 28rpx;
  1440. }
  1441. }
  1442. }
  1443. }
  1444. }
  1445. </style>