deviceDetail.vue 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609
  1. <template>
  2. <!-- 设备详情 -->
  3. <view class="content">
  4. <view class="name_box">
  5. <view class="name_content">{{ devInfo.devName }}</view>
  6. </view>
  7. <view class="radar-box">
  8. <view
  9. :style="{
  10. width: `${length / 200}px`,
  11. height: `${width / 200}px`,
  12. position: 'relative',
  13. overflow: 'hidden',
  14. }"
  15. :class="[
  16. width < 25500 && length < 25000 ? 'tranStyle' : 'center',
  17. ]"
  18. >
  19. <view
  20. v-for="(item, index) in modules"
  21. :key="index"
  22. class="moduleContent"
  23. >
  24. <view
  25. :class="item.type"
  26. :style="{
  27. width: `${item.width / 2}px`,
  28. height: `${item.length / 2}px`,
  29. top: `${item.top / 2}px`,
  30. left: `${item.left / 2}px`,
  31. transform: `rotate(${item.rotate}deg)`,
  32. 'transform-origin': 'center center',
  33. }"
  34. >
  35. <image
  36. class="module-img"
  37. :src="`../../static/furnitures/${item.type}.png`"
  38. mode=""
  39. />
  40. </view>
  41. </view>
  42. <template v-if="devType == 'LNB'">
  43. <view v-for="item in targetPoints" :key="item.id">
  44. <image
  45. class="action-icon-M"
  46. :style="{
  47. position: 'absolute',
  48. transform: `translate3d(${
  49. item.displayX / 2 + 100
  50. }px, ${
  51. -item.displayY / 2 + 100
  52. }px, 0) translate(-50%, -50%)`,
  53. zIndex: 9999,
  54. transition: 'transform 1s linear',
  55. willChange: 'transform',
  56. }"
  57. :src="`../../static/${lnbAction}.png`"
  58. mode=""
  59. />
  60. </view>
  61. </template>
  62. <template v-else>
  63. <image
  64. v-if="actionName"
  65. class="action-icon-G"
  66. :style="{
  67. top: `${top / 200}px`,
  68. left: `${left / 200}px`,
  69. }"
  70. :src="`../../images/furnitures/${actionName}.png`"
  71. mode=""
  72. />
  73. </template>
  74. </view>
  75. <!-- <image
  76. class="redar-pic"
  77. src="../../static/rander.png"
  78. mode=""
  79. :style="{
  80. transform:
  81. 'translate(' +
  82. -xOffset / 100 +
  83. 'rpx,' +
  84. -yOffset / 100 +
  85. 'rpx)',
  86. }"
  87. /> -->
  88. </view>
  89. <view class="switchBox">
  90. <text class="name">呼吸灯</text>
  91. <switch
  92. :checked="statusLight == 1"
  93. @change="handleLightChange"
  94. :active-value="1"
  95. :inactive-value="0"
  96. size="24px"
  97. active-color="#07c160"
  98. inactive-color="#eeeff1"
  99. style="transform: scale(0.8)"
  100. />
  101. </view>
  102. <view class="notice-info">
  103. <view class="notice_title" @click="discrepancy">
  104. <text style="color: #95a4b3; font-size: 28rpx">{{
  105. todayDate
  106. }}</text>
  107. <image src="../../static/rightArrow.png"></image>
  108. </view>
  109. <view
  110. @click="getFrenEcharts()"
  111. class="title"
  112. v-if="
  113. devInfo.installPosition == 'Bedroom' ||
  114. (breathRate === 0 && breathRate !== '')
  115. "
  116. >
  117. <view
  118. class="title-text"
  119. style="color: #111111; font-size: 30rpx"
  120. >当前呼吸率</view
  121. >
  122. <view
  123. style="
  124. display: flex;
  125. justify-content: center;
  126. align-items: center;
  127. "
  128. >
  129. <view
  130. class="title-btn"
  131. style="color: #111111; font-size: 30rpx"
  132. v-if="breathRate !== ''"
  133. >
  134. {{ breathRate }}次/分钟</view
  135. >
  136. <view
  137. v-else
  138. class="title-btn"
  139. style="color: #111111; font-size: 30rpx"
  140. >
  141. 暂无
  142. </view>
  143. <image
  144. src="../../static/rightArrow.png"
  145. style="margin-left: auto; width: 30rpx; height: 30rpx"
  146. ></image>
  147. </view>
  148. </view>
  149. <view class="title" v-if="devInfo.installPosition == 'Toilet'">
  150. <view class="title-text" style="color: #22dea7"
  151. >今日卫生间使用频次</view
  152. >
  153. <view class="title-btn" style="color: #22dea7"
  154. >{{
  155. freQuenceList.length == "" ? "0" : freQuenceList.length
  156. }}次</view
  157. >
  158. </view>
  159. <view class="title" v-if="devInfo.installPosition != 'Toilet'">
  160. <view class="title-text" style="color: #22dea7"
  161. >今日进出频次</view
  162. >
  163. <view class="title-btn" style="color: #22dea7"
  164. >{{
  165. freQuenceList.length == "" ? "0" : freQuenceList.length
  166. }}次</view
  167. >
  168. </view>
  169. <view
  170. class="stayDetail"
  171. v-if="freQuenceList.length > 0 && freQuenceList.length == 1"
  172. >
  173. <view v-for="item in freQuenceList" :key="item.id">
  174. <view class="stayDetail-item">
  175. <view class="stayDetail-text"
  176. >{{
  177. item.info.start_time.slice(11, 16)
  178. }}目标进入</view
  179. >
  180. <view class="stayDetail-btn"
  181. >{{
  182. item.info.end_time.slice(11, 16)
  183. }}目标离开</view
  184. >
  185. </view>
  186. </view>
  187. </view>
  188. <view
  189. class="stayDetail"
  190. v-if="freQuenceList.length > 0 && freQuenceList.length > 1"
  191. >
  192. <swiper
  193. class="auto-scroll-swiper"
  194. :indicator-dots="false"
  195. :autoplay="false"
  196. :interval="3000"
  197. :circular="true"
  198. :display-multiple-items="1"
  199. :vertical="false"
  200. :current="currentIndex"
  201. @change="onSwiperChange"
  202. >
  203. <swiper-item
  204. v-for="item in freQuenceList"
  205. :key="item.id"
  206. class="stayDetail-item"
  207. >
  208. <view class="stayDetail-text"
  209. >{{
  210. item.info.start_time.slice(11, 16)
  211. }}目标进入</view
  212. >
  213. <view class="stayDetail-btn"
  214. >{{
  215. item.info.end_time.slice(11, 16)
  216. }}目标离开</view
  217. >
  218. </swiper-item>
  219. </swiper>
  220. </view>
  221. <!-- <view class="title" v-if="devInfo.installPosition == 'Toilet'">
  222. <view class="title-text" style="color: #ff976a"
  223. >昨日卫生间使用频次</view
  224. >
  225. <view class="title-btn" style="color: #ff976a"
  226. >{{
  227. freQuenceList.length == "" ? "0" : freQuenceList.length
  228. }}次</view
  229. >
  230. </view>
  231. <view class="title" v-else>
  232. <view class="title-text" style="color: #ff976a"
  233. >昨日进出频次</view
  234. >
  235. <view class="title-btn" style="color: #ff976a"
  236. >{{
  237. freQuenceList.length == "" ? "0" : freQuenceList.length
  238. }}次</view
  239. >
  240. </view> -->
  241. </view>
  242. <view class="box" v-if="!breathShow">
  243. <view class="handle-btn">
  244. <view class="btn1" @click="shareDevice">分享</view>
  245. <view class="btn1" @click="gotoSetting">设置</view>
  246. <view class="btn2" @click="healthAlarm">守护计划</view>
  247. </view>
  248. </view>
  249. <view class="box" v-else>
  250. <view class="handle-btn">
  251. <view class="closeBreath" @click="breathShow = false"
  252. >关闭呼吸率曲线图</view
  253. >
  254. <view class="btn2" @click="saveBreath"> 保存呼吸率曲线图 </view>
  255. </view>
  256. </view>
  257. <!-- 遮罩层 -->
  258. <view class="mask" v-if="shareModel" @click="shareModel = false"></view>
  259. <!-- 弹窗内容 -->
  260. <view class="share-modal" v-if="shareModel">
  261. <view class="modal-header">
  262. <text class="cancel-btn" @click="shareModel = false">取消</text>
  263. <text class="modal-title">分享</text>
  264. <text class="confirm-btn" @click="onShareConfirm()">确认</text>
  265. </view>
  266. <view class="modal-body">
  267. <view class="info-row">
  268. <view class="label">设备序列号:</view>
  269. <view class="value">{{ devInfo.clientId }}</view>
  270. </view>
  271. <view class="info-row">
  272. <view class="label">设备名称:</view>
  273. <view class="value">{{ devInfo.devName }}</view>
  274. </view>
  275. <view class="input-row phoneInfo">
  276. <view>被分享人</view>
  277. <input
  278. class="input"
  279. placeholder="请输入手机号"
  280. type="number"
  281. v-model="sharedPhone"
  282. />
  283. </view>
  284. <view class="funChoice">
  285. <view class="funItem" @click="smChange()">
  286. <label class="simple-radio">
  287. <radio
  288. :value="messageFlag"
  289. color="#7c5345"
  290. :checked="messageFlag == true"
  291. class="hide-original"
  292. />
  293. <text>短信权限</text>
  294. </label>
  295. </view>
  296. <view class="funItem">
  297. <label class="simple-radio" @click="snChange()">
  298. <radio
  299. :value="serviceNumberFlag"
  300. color="#7c5345"
  301. :checked="serviceNumberFlag == true"
  302. class="hide-original"
  303. />
  304. <text>服务号通知</text>
  305. </label>
  306. </view>
  307. <view class="funItem">
  308. <label class="simple-radio" @click="vfChange()">
  309. <radio
  310. :value="voipFlag"
  311. color="#7c5345"
  312. :checked="voipFlag == true"
  313. class="hide-original"
  314. />
  315. <text>语音通话</text>
  316. </label>
  317. </view>
  318. </view>
  319. </view>
  320. </view>
  321. <view class="modal-mask" v-if="choiceVisible" @click="closeChoice">
  322. <view class="modal-container">
  323. <view class="modal-header">
  324. <text class="title">请选择分享方式</text>
  325. </view>
  326. <view class="modal-buttons">
  327. <!-- 手机号分析按钮 -->
  328. <button class="btn phone-btn" @click="handlePhoneAnalysis">
  329. <text>手机号分享</text>
  330. </button>
  331. <!-- 链接分享按钮 -->
  332. <button class="btn link-btn" @click="handleLinkShare">
  333. <text>链接分享</text>
  334. </button>
  335. </view>
  336. </view>
  337. </view>
  338. <!-- ECharts图标模块 -->
  339. <view v-if="breathShow" class="echartsClass">
  340. <image
  341. src="../../static/closePng.png"
  342. class="closePng"
  343. @click="breathShow = false"
  344. ></image>
  345. <l-echart
  346. id="chart-canvas"
  347. canvas-id="chart-canvas"
  348. ref="chartRef"
  349. @finished="initChart"
  350. v-if="breathShow"
  351. @click="breathShow = false"
  352. ></l-echart>
  353. </view>
  354. <alarModel v-if="showModle" />
  355. </view>
  356. </template>
  357. <script>
  358. import * as echarts from "../../uni_modules/lime-echart/static/echarts.min";
  359. import mqtt from "../../utils/mqtt";
  360. // import { createMqttData } from "../../utils/globalMqtt";
  361. import { createMqttData } from "../../utils/globalMqtt";
  362. export default {
  363. data() {
  364. return {
  365. width: 0, //检测区域宽度
  366. length: 0, //检测区域长度
  367. xOffset: 0,
  368. yOffset: 0,
  369. devInfo: "",
  370. actionName: "",
  371. startDate: "",
  372. endDate: "",
  373. softWare: "",
  374. statusLight: 0,
  375. currentDate: new Date().getTime(),
  376. lnbAction: "action8",
  377. wsj: false,
  378. todayWcTimes: "",
  379. stayDetail: "",
  380. todayDate: "",
  381. dev_id: "",
  382. nowTime: "",
  383. devName: "",
  384. devType: "",
  385. localPhone: uni.getStorageSync("phone"),
  386. startArr: [],
  387. freQuenceList: [],
  388. // mqtt相关
  389. currentIndex: 0,
  390. modules: [],
  391. autoPlayinterval: "",
  392. choiceVisible: "",
  393. // 手机号分享授权模块
  394. shareModel: false,
  395. alarmModel: false,
  396. sharedPhone: "",
  397. messageFlag: true,
  398. serviceNumberFlag: true,
  399. voipFlag: true,
  400. // mqtt模块
  401. targetPoints: {},
  402. inactivityTimer: null,
  403. left: 0,
  404. top: 0,
  405. clientIdProp: null,
  406. breathRate: "",
  407. breathShow: false,
  408. breathRpmList: [],
  409. option: {
  410. title: [
  411. {
  412. text: "呼吸率曲线",
  413. left: "center",
  414. top: 20,
  415. textStyle: { fontSize: 14 },
  416. },
  417. {
  418. text: "BPM: 0 次/分钟",
  419. left: "center",
  420. top: 50,
  421. textStyle: { fontSize: 12, color: "#805246" },
  422. },
  423. {
  424. text: "时间 (秒)",
  425. left: "right",
  426. top: "bottom",
  427. textStyle: { fontSize: 12, color: "#888888" },
  428. },
  429. ],
  430. grid: { left: 60, right: 20, top: 80, bottom: 50 },
  431. xAxis: {
  432. type: "category",
  433. data: Array.from({ length: 60 }, (_, i) => i + 1),
  434. boundaryGap: false,
  435. splitLine: { show: false },
  436. },
  437. yAxis: {
  438. type: "value",
  439. min: 0,
  440. max: 40,
  441. name: "呼吸率(次)",
  442. nameLocation: "end",
  443. nameGap: 20,
  444. nameTextStyle: { fontSize: 12, color: "#888888" },
  445. splitLine: { show: true },
  446. },
  447. series: [
  448. {
  449. type: "line",
  450. smooth: true,
  451. symbol: "none",
  452. lineStyle: { color: "#805246", width: 2 },
  453. data: Array(60).fill(0),
  454. },
  455. ],
  456. animation: true,
  457. animationDuration: 100,
  458. },
  459. index: 0,
  460. loopTimer: null,
  461. mqttClienTwoFlag: false,
  462. mqttClientOne: false,
  463. mqttData: null,
  464. showModle: false,
  465. };
  466. },
  467. computed: {},
  468. methods: {
  469. getdevInfo(devId) {
  470. this.$http
  471. .get(`wap/device/queryDeviceInfoById`, {
  472. devId: devId,
  473. })
  474. .then((res) => {
  475. if (res.data.data) {
  476. this.devInfo = res.data.data;
  477. this.devType = this.devInfo.devType;
  478. this.width =
  479. Math.abs(
  480. this.devInfo.yyEnd - this.devInfo.yyStart
  481. ) * 100;
  482. this.length =
  483. Math.abs(
  484. this.devInfo.xxEnd - this.devInfo.xxStart
  485. ) * 100;
  486. this.xOffset =
  487. (this.devInfo.xxStart + this.devInfo.xxEnd) * 50;
  488. this.yOffset =
  489. -(this.devInfo.yyStart + this.devInfo.yyEnd) * 50;
  490. this.statusLight = this.devInfo.statusLight;
  491. }
  492. })
  493. .catch((err) => {});
  494. },
  495. getdevRoomInfo(devId) {
  496. this.$http
  497. .get(`wap/room/readRoom`, {
  498. devId: devId,
  499. })
  500. .then((res) => {
  501. if (res.data.data) {
  502. this.modules = res.data.data.furnitures;
  503. }
  504. });
  505. },
  506. handleLightChange(e) {
  507. let newValue = e.detail.value == true ? 1 : 0;
  508. this.$http
  509. .post(`wap/device/statusLight`, {
  510. statusLight: newValue,
  511. devId: this.devInfo.devId,
  512. })
  513. .then((res) => {
  514. if (res.data.code == 200) {
  515. uni.showToast({
  516. title: "操作成功",
  517. icon: "success",
  518. });
  519. this.statusLight = newValue;
  520. } else {
  521. wx.showToast({
  522. title: res.data.message,
  523. icon: "none",
  524. });
  525. }
  526. });
  527. },
  528. parseDeviceItem(devItemStr) {
  529. try {
  530. const devItem = JSON.parse(devItemStr);
  531. if (!devItem || !devItem.devId || !devItem.clientId) {
  532. throw new Error("设备信息不完整");
  533. }
  534. return devItem;
  535. } catch (e) {
  536. throw new Error("设备信息解析失败: " + e.message);
  537. }
  538. },
  539. // 分享功能模块
  540. shareDevice() {
  541. this.choiceVisible = true;
  542. },
  543. onShareConfirm() {
  544. if (!this.sharedPhone) {
  545. uni.showModal({
  546. content: "请填写手机号!",
  547. showCancel: false,
  548. });
  549. return;
  550. }
  551. let reg_tel =
  552. /^(13[0-9]|14[01456879]|15[0-3,5-9]|16[2567]|17[0-8]|18[0-9]|19[0-3,5-9])\d{8}$/;
  553. if (!reg_tel.test(this.sharedPhone)) {
  554. uni.showModal({
  555. content: "请填写正确手机号!",
  556. showCancel: false,
  557. });
  558. return;
  559. }
  560. let shareParam = {
  561. sharerUserId: uni.getStorageSync("userId"),
  562. devId: this.devInfo.devId,
  563. sharerPhone: uni.getStorageSync("phone"),
  564. sharedUserId: "",
  565. sharedPhone: this.sharedPhone,
  566. messageFlag: this.messageFlag == true ? 0 : 1,
  567. serviceNumberFlag: this.serviceNumberFlag == true ? 0 : 1,
  568. voipFlag: this.voipFlag == true ? 0 : 1,
  569. };
  570. this.$http
  571. .post("wap/share/deviceShare", shareParam, {
  572. header: {
  573. "Content-Type": "application/json;charset=UTF-8",
  574. },
  575. })
  576. .then((res) => {
  577. if (res.data.code == 200) {
  578. uni.showToast({
  579. title: "分享成功",
  580. icon: "success",
  581. duration: 1500,
  582. });
  583. } else {
  584. uni.showToast({
  585. title: res.data.message,
  586. icon: "none",
  587. duration: 1500,
  588. });
  589. }
  590. });
  591. this.shareModel = false;
  592. },
  593. closeChoice() {
  594. this.choiceVisible = false;
  595. },
  596. handlePhoneAnalysis() {
  597. this.choiceVisible = false;
  598. this.shareModel = true;
  599. },
  600. handleLinkShare() {
  601. this.choiceVisible = false;
  602. uni.navigateTo({
  603. url:
  604. "/pagesA/linkShare/linkShare?devInfo=" +
  605. JSON.stringify(this.devInfo),
  606. });
  607. },
  608. smChange() {
  609. this.messageFlag = !this.messageFlag;
  610. },
  611. snChange() {
  612. this.serviceNumberFlag = !this.serviceNumberFlag;
  613. },
  614. vfChange() {
  615. this.voipFlag = !this.voipFlag;
  616. },
  617. gotoSetting() {
  618. if (this.devInfo.online == 0) {
  619. uni.showToast({
  620. title: "离线设备不支持设置",
  621. icon: "none",
  622. });
  623. return;
  624. }
  625. uni.navigateTo({
  626. url:
  627. "/pagesA/deviceSetting/deviceSetting?devInfo=" +
  628. JSON.stringify(this.devInfo),
  629. });
  630. },
  631. getFrequency(clientId) {
  632. this.$http
  633. .post(`wap/stats/alarmEventsQuery`, {
  634. clientId: clientId,
  635. createTimeStart: this.$time(new Date()),
  636. createTimeEnd: this.$time(new Date()),
  637. eventType: 1,
  638. })
  639. .then((res) => {
  640. if (res.data.code == 200) {
  641. if (res.data.data.rows.length > 0) {
  642. this.freQuenceList = this.parseJsonToObjects(
  643. res.data.data.rows
  644. );
  645. } else {
  646. this.freQuenceList = [];
  647. }
  648. } else {
  649. wx.showToast({
  650. title: res.data.message,
  651. icon: "none",
  652. });
  653. }
  654. });
  655. },
  656. parseJsonToObjects(jsonArray) {
  657. return jsonArray.map((item) => {
  658. // 解析info字段的JSON字符串
  659. let infoData = {};
  660. try {
  661. infoData = JSON.parse(item.info);
  662. } catch (e) {
  663. console.error("Failed to parse info JSON:", e);
  664. }
  665. return {
  666. id: item.id,
  667. clientId: item.clientId,
  668. tenantId: item.tenantId,
  669. devName: item.devName,
  670. uuid: item.uuid,
  671. planUuid: item.planUuid,
  672. eventType: item.eventType,
  673. info: infoData,
  674. isHandle: item.isHandle,
  675. createTime: item.createTime,
  676. remark: item.remark,
  677. };
  678. });
  679. },
  680. // 健康闹钟方法
  681. healthAlarm() {
  682. uni.navigateTo({
  683. url:
  684. "/pagesA/healthAlarm/healthAlarm?devInfo=" +
  685. JSON.stringify(this.devInfo),
  686. });
  687. },
  688. onSwiperChange(event) {
  689. const current = event.detail.current;
  690. const totalItems = this.freQuenceList.length;
  691. if (current === totalItems - 1) {
  692. this.autoplay = false;
  693. }
  694. this.currentIndex = current;
  695. },
  696. autoSwipe() {
  697. if (this.freQuenceList && this.freQuenceList.length > 0) {
  698. setInterval(() => {
  699. let nextIndex = this.currentIndex + 1;
  700. if (nextIndex >= this.freQuenceList.length) {
  701. nextIndex = 0; // 循环到第一个项目
  702. }
  703. this.currentIndex = nextIndex;
  704. }, 3000); // 每3秒自动滚动一次
  705. }
  706. },
  707. discrepancy() {
  708. uni.navigateTo({
  709. url:
  710. "/pagesA/discrepancy/discrepancy?freQuenceList=" +
  711. JSON.stringify(this.freQuenceList),
  712. });
  713. },
  714. getCurrentDate() {
  715. const now = new Date();
  716. this.currentDate = `${now.getFullYear()}-${
  717. now.getMonth() + 1
  718. }月${now.getDate().toString().padStart(2, "0")}日`;
  719. },
  720. receptionChange(val) {
  721. this.targetPoints = val;
  722. },
  723. receptHealth(val) {
  724. this.breathRate = val;
  725. this.setEcharts(val);
  726. },
  727. // echarts图表模块
  728. getOption(list) {
  729. // 固定 X 轴 [0 ~ 60]
  730. const xData = Array.from({ length: 61 }, (_, i) => i);
  731. const recent = list.slice(-61);
  732. const data = new Array(61).fill(null);
  733. for (let i = 0; i < recent.length; i++) {
  734. data[i] = recent[i];
  735. }
  736. return {
  737. title: [
  738. { text: "呼吸率曲线", left: "center", top: 20 },
  739. {
  740. text: `BPM: ${recent[recent.length - 1] || 0} 次/分钟`,
  741. left: "center",
  742. top: 50,
  743. textStyle: { fontSize: 12, color: "#805246" },
  744. },
  745. {
  746. text: "时间 (秒)",
  747. left: "right",
  748. top: "bottom",
  749. textStyle: {
  750. fontSize: 10,
  751. color: "#888888",
  752. fontWeight: "normal",
  753. },
  754. },
  755. ],
  756. grid: { left: 60, right: 20, top: 80, bottom: 50 },
  757. xAxis: {
  758. type: "category",
  759. data: xData,
  760. boundaryGap: false,
  761. },
  762. yAxis: {
  763. type: "value",
  764. min: 0,
  765. max: 40,
  766. name: "呼吸率(次)",
  767. nameLocation: "end",
  768. nameGap: 20,
  769. nameTextStyle: {
  770. fontSize: 10,
  771. color: "#888888",
  772. fontWeight: "normal",
  773. },
  774. splitLine: { show: true },
  775. },
  776. series: [
  777. {
  778. type: "line",
  779. smooth: true,
  780. symbol: "none",
  781. data: data,
  782. showSymbol: false,
  783. lineStyle: { color: "#7a4e42", width: 2 },
  784. },
  785. ],
  786. };
  787. },
  788. getFrenEcharts() {
  789. if (this.breathRate === "" || this.breathRate === null) {
  790. uni.showToast({
  791. title: "暂无呼吸率",
  792. icon: "none",
  793. duration: 1500,
  794. });
  795. return;
  796. }
  797. this.breathShow = true;
  798. this.$nextTick(() => {
  799. this.initChart();
  800. });
  801. },
  802. initChart() {
  803. if (!this.$refs.chartRef) return;
  804. if (this.chartInstance) {
  805. this.chartInstance.dispose();
  806. this.chartInstance = null;
  807. }
  808. this.$refs.chartRef.init(echarts, (chart) => {
  809. this.chartInstance = chart;
  810. chart.setOption(this.option, true);
  811. });
  812. },
  813. setEcharts(val) {
  814. if (!Array.isArray(this.breathRpmList)) {
  815. this.breathRpmList = [];
  816. }
  817. this.breathRpmList.push(val);
  818. if (!this.chartInstance) return;
  819. const option = this.getOption(this.breathRpmList);
  820. this.chartInstance.setOption(option, { notMerge: true });
  821. },
  822. saveBreath() {
  823. const chart = this.$refs.chartRef;
  824. if (!chart) {
  825. uni.showToast({ title: "图表未渲染", icon: "none" });
  826. return;
  827. }
  828. chart.canvasToTempFilePath({
  829. success: (res) => {
  830. uni.saveImageToPhotosAlbum({
  831. filePath: res.tempFilePath,
  832. success: () => {
  833. uni.showToast({
  834. title: "保存成功",
  835. icon: "success",
  836. });
  837. },
  838. fail: (err) => {
  839. console.error("保存失败:", err);
  840. uni.showToast({
  841. title: "保存失败",
  842. icon: "none",
  843. });
  844. },
  845. });
  846. },
  847. fail: (err) => {
  848. console.error("导出失败:", err);
  849. uni.showToast({
  850. title: "导出失败",
  851. icon: "none",
  852. });
  853. },
  854. });
  855. },
  856. // 订阅mqtt实时点位
  857. connectMqtt() {
  858. this.mqttClienTwoFlag = false;
  859. if (this.mqttClienTwoFlag) {
  860. console.log("主题已订阅");
  861. return;
  862. }
  863. const THRESHOLD = 2;
  864. const params = {
  865. keepalive: 6000,
  866. clean: true,
  867. connectTimeout: 30 * 1000,
  868. clientId:
  869. "xcx_mqtt_data1" +
  870. this.clientIdProp +
  871. Date.now() +
  872. "_" +
  873. Math.random().toString(16).substring(2, 8),
  874. username: "lnradar",
  875. password: "lnradar",
  876. // 微信小程序特定配置
  877. wsOptions: {
  878. WebSocket: function (url) {
  879. return wx.connectSocket({
  880. url: url,
  881. header: {
  882. "content-type": "application/json",
  883. },
  884. protocols: ["mqtt"],
  885. });
  886. },
  887. },
  888. };
  889. let clientTwo = "";
  890. let selectedService = uni.getStorageSync("sercviceChoice");
  891. if (!selectedService || selectedService == "aloneServe") {
  892. if (__wxConfig.envVersion == "develop") {
  893. clientTwo = mqtt.connect(
  894. "wxs://data.radar-power.cn/mqtt/",
  895. params
  896. );
  897. }
  898. if (__wxConfig.envVersion == "trial") {
  899. clientTwo = mqtt.connect(
  900. "wxs://data.radar-power.cn/mqtt/",
  901. params
  902. );
  903. }
  904. }
  905. clientTwo = mqtt.connect("wxs://data.radar-power.cn/mqtt/", params);
  906. console.log("主题开始订阅5555");
  907. // 存储client引用以便后续操作
  908. this.mqttClientOne = clientTwo;
  909. clientTwo.on("connect", () => {
  910. console.log("MQTT_DATA连接成功");
  911. this.mqttClienTwoFlag = true;
  912. if (this.clientIdProp !== null) {
  913. clientTwo.subscribe(
  914. `/dev/${this.clientIdProp}/tracker_targets`,
  915. (err) => {
  916. if (err) {
  917. console.error("订阅clientId失败", err);
  918. } else {
  919. console.log(
  920. `成功订阅设备主题: /dev/${this.clientIdProp}/dsp_data`
  921. );
  922. }
  923. }
  924. );
  925. }
  926. console.log(this.clientIdProp);
  927. });
  928. clientTwo.on("disconnect", () => {
  929. console.log("MQTT不在连接");
  930. });
  931. clientTwo.on("error", (err) => {
  932. this.mqttClienTwoFlag = false;
  933. setTimeout(() => {
  934. this.connectMqtt();
  935. }, 1000);
  936. });
  937. clientTwo.on("reconnect", () => {});
  938. clientTwo.on("close", () => {});
  939. clientTwo.on("message", (topic, message) => {
  940. // 处理点位消息
  941. if (this.clientIdProp !== null) {
  942. this.handleMessage(topic, message, this.clientIdProp);
  943. }
  944. });
  945. },
  946. handleMessage(topic, message, clientId) {
  947. // 清除不活动定时器
  948. clearTimeout(this.inactivityTimer);
  949. this.inactivityTimer = setTimeout(() => {
  950. this.targetPoints = {};
  951. }, 1500);
  952. console.log(topic, 99999);
  953. console.log(JSON.parse(message.toString()), "99999999");
  954. // 验证topic格式
  955. const match = topic.match(/^\/dev\/(.+)\/tracker_targets$/);
  956. if (!match || match[1] !== clientId) return;
  957. try {
  958. const data = JSON.parse(message.toString());
  959. if (data.health) {
  960. if (
  961. data.health.breath_rpm ||
  962. data.health.breath_rpm === 0
  963. ) {
  964. this.receptHealth(Math.floor(data.health.breath_rpm));
  965. // this.$emit(
  966. // "sendHealth",
  967. // Math.floor(data.health.breath_rpm)
  968. // );
  969. } else {
  970. // this.$emit("sendHealth", 0);
  971. }
  972. }
  973. this.processTrackerData(data.tracker_targets);
  974. console.log(data.tracker_targets, "MQTT消息解析成功22222");
  975. } catch (e) {
  976. console.error("MQTT消息解析失败", e);
  977. }
  978. },
  979. processTrackerData(arr) {
  980. if (Array.isArray(arr) && arr.length > 0 && Array.isArray(arr[0])) {
  981. this.targetPoints = {};
  982. const currentIds = new Set();
  983. const newTargetPoints = {};
  984. // 处理每个追踪目标
  985. arr.forEach((item) => {
  986. if (!Array.isArray(item) || item.length < 4) return;
  987. const [x, y, z, id] = item;
  988. currentIds.add(id.toString());
  989. // 处理新点或更新现有点
  990. if (!this.targetPoints[id]) {
  991. newTargetPoints[id] = this.createNewTargetPoint(
  992. x,
  993. y,
  994. z,
  995. id
  996. );
  997. } else {
  998. newTargetPoints[id] = this.updateExistingTargetPoint(
  999. this.targetPoints[id],
  1000. x,
  1001. y,
  1002. z,
  1003. 2
  1004. );
  1005. }
  1006. });
  1007. // 移除不存在的点
  1008. Object.keys(this.targetPoints).forEach((id) => {
  1009. if (!currentIds.has(id)) {
  1010. delete this.targetPoints[id];
  1011. }
  1012. });
  1013. // 更新目标点
  1014. this.targetPoints = {
  1015. ...this.targetPoints,
  1016. ...newTargetPoints,
  1017. };
  1018. if (Array.isArray(this.targetPoints)) {
  1019. this.targetPoints = this.targetPoints.filter(
  1020. (item) => item !== null && item !== undefined
  1021. );
  1022. }
  1023. console.log("当前目标点11111", this.targetPoints);
  1024. }
  1025. },
  1026. createNewTargetPoint(x, y, z, id) {
  1027. return {
  1028. x,
  1029. y,
  1030. z,
  1031. id,
  1032. displayX: x,
  1033. displayY: y,
  1034. lastX: x,
  1035. lastY: y,
  1036. };
  1037. },
  1038. updateExistingTargetPoint(existingPoint, x, y, z, THRESHOLD) {
  1039. const dx = x - existingPoint.lastX;
  1040. const dy = y - existingPoint.lastY;
  1041. const distance = Math.sqrt(dx * dx + dy * dy);
  1042. if (distance > THRESHOLD) {
  1043. return {
  1044. ...existingPoint,
  1045. x,
  1046. y,
  1047. z,
  1048. lastX: x,
  1049. lastY: y,
  1050. displayX: x,
  1051. displayY: y,
  1052. };
  1053. }
  1054. return existingPoint;
  1055. },
  1056. closemqtTwo() {
  1057. this.mqttClienTwoFlag = false;
  1058. if (this.mqttClientOne) {
  1059. this.mqttClientOne.end();
  1060. this.mqttClientOne = null;
  1061. console.log("MQTT连接已断开");
  1062. }
  1063. },
  1064. },
  1065. onShow() {
  1066. this.clientIdProp = uni.getStorageSync("clientIDetail");
  1067. this.todayDate = this.$time(new Date(), 2);
  1068. this.showModle = true;
  1069. console.log(this.showModle, "showModle");
  1070. },
  1071. onLoad(options) {
  1072. const devItem = this.parseDeviceItem(options.devItem);
  1073. const { devId, clientId } = devItem;
  1074. this.getFrequency(clientId);
  1075. this.getdevInfo(devId);
  1076. this.getdevRoomInfo(devId);
  1077. this.autoPlayinterval = setTimeout(() => {
  1078. this.autoSwipe();
  1079. }, 3000);
  1080. this.getCurrentDate();
  1081. // this.connectMqtt();
  1082. console.log(clientId, "clientId88888");
  1083. this.mqttData = createMqttData(clientId);
  1084. if (this.mqttData) {
  1085. this.mqttData.on("message", (topic, message) => {
  1086. this.handleMessage(topic, message, clientId);
  1087. });
  1088. }
  1089. },
  1090. onUnload() {
  1091. this.inactivityTimer = null;
  1092. this.autoPlayinterval = null;
  1093. this.showModle = false;
  1094. if (this.mqttData) {
  1095. this.mqttData.end();
  1096. this.mqttData = null;
  1097. console.log("页面 DATA MQTT 已断开");
  1098. }
  1099. },
  1100. onHide() {
  1101. this.inactivityTimer = null;
  1102. this.autoPlayinterval = null;
  1103. this.showModle = false;
  1104. if (this.mqttData) {
  1105. this.mqttData.end();
  1106. this.mqttData = null;
  1107. console.log("页面 DATA MQTT 已断开");
  1108. }
  1109. },
  1110. };
  1111. </script>
  1112. <style lang="less" scoped>
  1113. .content {
  1114. height: 100vh;
  1115. background: linear-gradient(180deg, #faede2 0%, #f4f4f4 100%);
  1116. box-sizing: border-box;
  1117. padding-top: 20rpx;
  1118. .name_box {
  1119. display: flex;
  1120. justify-content: center;
  1121. align-items: center;
  1122. margin: 0 auto 37rpx 0;
  1123. .name_content {
  1124. display: inline-flex;
  1125. background: rgba(255, 255, 255, 0.65);
  1126. border-radius: 37rpx;
  1127. padding: 6rpx 18rpx;
  1128. }
  1129. }
  1130. .radar-box {
  1131. margin: 0 auto;
  1132. position: relative;
  1133. display: flex;
  1134. align-items: center;
  1135. justify-content: center;
  1136. width: 710rpx;
  1137. height: 710rpx;
  1138. background: #ffffff;
  1139. border-radius: 37.5rpx;
  1140. box-sizing: border-box;
  1141. .center {
  1142. position: absolute;
  1143. background-color: #fff;
  1144. border: 14rpx solid #333333;
  1145. background-image: url("http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png");
  1146. background-repeat: no-repeat;
  1147. background-position: center;
  1148. transform: scale(1.3);
  1149. .moduleContent {
  1150. position: relative;
  1151. view {
  1152. position: absolute;
  1153. }
  1154. .module-img {
  1155. width: 100%;
  1156. height: 100%;
  1157. display: block;
  1158. }
  1159. }
  1160. }
  1161. .tranStyle {
  1162. position: absolute;
  1163. background-color: #fff;
  1164. border: 9rpx solid #333333;
  1165. background-image: url("http://jkld.radar-power.com//uploadFiles/framework/file/20250620/toilet_bg.png");
  1166. background-repeat: no-repeat;
  1167. background-position: center;
  1168. transform: scale(2.2);
  1169. .moduleContent {
  1170. overflow: hidden;
  1171. // position: relative;
  1172. view {
  1173. position: absolute;
  1174. }
  1175. .module-img {
  1176. width: 100%;
  1177. height: 100%;
  1178. display: block;
  1179. }
  1180. }
  1181. }
  1182. .action-icon-G {
  1183. position: absolute;
  1184. width: 100rpx;
  1185. height: 100rpx;
  1186. }
  1187. .action-icon-M {
  1188. // position: absolute;
  1189. width: 50rpx;
  1190. height: 50rpx;
  1191. }
  1192. .redar-pic {
  1193. position: absolute;
  1194. left: 50%;
  1195. top: 50%;
  1196. width: 40rpx;
  1197. height: 40rpx;
  1198. transform: translate(-50%, -50%); /* 先居中 */
  1199. }
  1200. }
  1201. .switchBox {
  1202. width: 710rpx;
  1203. height: 94rpx;
  1204. margin: 18rpx auto;
  1205. padding: 0 37rpx;
  1206. display: flex;
  1207. align-items: center;
  1208. background: #ffffff;
  1209. border-radius: 37rpx;
  1210. box-sizing: border-box;
  1211. .name {
  1212. color: #111111;
  1213. font-size: 32rpx;
  1214. }
  1215. }
  1216. .notice-info {
  1217. width: 710rpx;
  1218. max-height: 300rpx;
  1219. overflow: hidden;
  1220. padding: 30rpx 37rpx 10rpx 37rpx;
  1221. background: #ffffff;
  1222. border-radius: 37rpx;
  1223. box-sizing: border-box;
  1224. margin: 0 auto;
  1225. .notice_title {
  1226. display: flex;
  1227. align-items: space-between;
  1228. image {
  1229. margin-left: auto;
  1230. width: 30rpx;
  1231. height: 30rpx;
  1232. }
  1233. }
  1234. .title {
  1235. margin-top: 10rpx;
  1236. margin-bottom: 10rpx;
  1237. font-family: PingFang SC, PingFang SC;
  1238. font-weight: 500;
  1239. font-size: 34rpx;
  1240. color: #1757dd;
  1241. display: flex;
  1242. align-items: center;
  1243. justify-content: space-between;
  1244. .title-text {
  1245. font-size: 28rpx;
  1246. font-weight: 500;
  1247. color: #1757dd;
  1248. }
  1249. .title-btn {
  1250. font-size: 28rpx;
  1251. font-weight: 500;
  1252. color: #5a5a5a;
  1253. }
  1254. }
  1255. .stayDetail {
  1256. height: 80rpx;
  1257. overflow: hidden;
  1258. margin-top: 10rpx;
  1259. margin-bottom: 10rpx;
  1260. padding-left: 20rpx;
  1261. font-family: PingFang SC, PingFang SC;
  1262. font-weight: 500;
  1263. font-size: 34rpx;
  1264. .stayDetail-item {
  1265. display: flex;
  1266. padding-bottom: 5rpx !important;
  1267. padding-top: 10rpx !important;
  1268. box-sizing: border-box;
  1269. line-height: 60rpx;
  1270. .stayDetail-text {
  1271. width: 40%;
  1272. font-size: 28rpx;
  1273. height: 30rpx !important;
  1274. }
  1275. .stayDetail-btn {
  1276. padding-left: 30rpx;
  1277. font-size: 28rpx;
  1278. height: 30rpx !important;
  1279. }
  1280. }
  1281. }
  1282. }
  1283. .box {
  1284. position: fixed;
  1285. bottom: 0;
  1286. left: 0;
  1287. width: 100vw;
  1288. height: 168rpx;
  1289. padding: 0 37rpx;
  1290. background: #ffffff;
  1291. box-sizing: border-box;
  1292. .handle-btn {
  1293. margin-top: 40rpx;
  1294. display: flex;
  1295. align-items: center;
  1296. justify-content: space-between;
  1297. .btn1 {
  1298. display: flex;
  1299. align-items: center;
  1300. justify-content: center;
  1301. width: 155rpx;
  1302. height: 94rpx;
  1303. background: #ffebe4;
  1304. border-radius: 28rpx;
  1305. font-weight: 500;
  1306. color: #111111;
  1307. font-size: 32rpx;
  1308. text-align: center;
  1309. }
  1310. .btn2 {
  1311. display: flex;
  1312. align-items: center;
  1313. justify-content: center;
  1314. width: 328rpx;
  1315. height: 94rpx;
  1316. background: linear-gradient(
  1317. 105.95deg,
  1318. #a27867 0%,
  1319. #74483d 100%
  1320. );
  1321. border-radius: 28rpx;
  1322. box-shadow: 0rpx 4.69rpx 18.75rpx rgba(72, 41, 29, 0.15),
  1323. 0rpx 9.38rpx 9.38rpx rgba(154, 132, 89, 0.2),
  1324. 0rpx -4.69rpx 28.13rpx 4.69rpx #a16647 inset;
  1325. font-family: MiSans;
  1326. font-weight: 500;
  1327. color: #ffffff;
  1328. font-size: 32rpx;
  1329. }
  1330. .closeBreath {
  1331. display: flex;
  1332. align-items: center;
  1333. justify-content: center;
  1334. width: 328rpx;
  1335. height: 94rpx;
  1336. background: #ffebe4;
  1337. border-radius: 28rpx;
  1338. font-weight: 500;
  1339. color: #111111;
  1340. font-size: 32rpx;
  1341. text-align: center;
  1342. }
  1343. }
  1344. }
  1345. /* 弹窗部分样式 */
  1346. .mask {
  1347. position: fixed;
  1348. left: 0;
  1349. top: 0;
  1350. right: 0;
  1351. bottom: 0;
  1352. background: rgba(0, 0, 0, 0.3);
  1353. z-index: 100;
  1354. }
  1355. .share-modal {
  1356. position: fixed;
  1357. left: 0;
  1358. right: 0;
  1359. bottom: 0;
  1360. background: #fff;
  1361. border-radius: 24rpx 24rpx 0 0;
  1362. z-index: 101;
  1363. height: 750rpx;
  1364. .modal-header {
  1365. display: flex;
  1366. justify-content: space-between;
  1367. align-items: center;
  1368. padding: 32rpx 32rpx 0 32rpx;
  1369. font-size: 30rpx;
  1370. .cancel-btn {
  1371. color: #888;
  1372. font-size: 32rpx;
  1373. }
  1374. .modal-title {
  1375. color: #111111;
  1376. font-size: 36rpx;
  1377. }
  1378. .confirm-btn {
  1379. color: #996e5f;
  1380. font-size: 32rpx;
  1381. }
  1382. }
  1383. .modal-body {
  1384. padding: 50rpx;
  1385. .info-row {
  1386. display: flex;
  1387. margin-bottom: 24rpx;
  1388. font-size: 28rpx;
  1389. padding: 0 20rpx;
  1390. .label {
  1391. color: #111111;
  1392. width: 200rpx;
  1393. font-size: 32rpx;
  1394. text-align-last: justify;
  1395. }
  1396. .value {
  1397. text-align: right;
  1398. width: 65%;
  1399. color: #111111;
  1400. font-size: 32rpx;
  1401. }
  1402. }
  1403. .input-row {
  1404. display: flex;
  1405. align-items: center;
  1406. margin-top: 32rpx;
  1407. border-radius: 40rpx;
  1408. background: #f7f7f7;
  1409. padding: 0 24rpx;
  1410. height: 80rpx;
  1411. .input {
  1412. text-align: right;
  1413. flex: 1;
  1414. border: none;
  1415. background: transparent;
  1416. font-size: 28rpx;
  1417. color: #333;
  1418. }
  1419. }
  1420. .funChoice {
  1421. .funItem {
  1422. .simple-radio {
  1423. display: flex;
  1424. align-items: center;
  1425. padding: 20rpx 0;
  1426. }
  1427. }
  1428. }
  1429. }
  1430. }
  1431. // 分享弹窗样式
  1432. .modal-mask {
  1433. position: fixed;
  1434. top: 0;
  1435. left: 0;
  1436. right: 0;
  1437. bottom: 0;
  1438. background: rgba(0, 0, 0, 0.5);
  1439. display: flex;
  1440. justify-content: center;
  1441. align-items: center;
  1442. z-index: 333;
  1443. .modal-container {
  1444. width: 80%;
  1445. background: #fff;
  1446. border-radius: 16rpx;
  1447. overflow: hidden;
  1448. animation: fadeIn 0.3s;
  1449. .modal-header {
  1450. padding: 30rpx;
  1451. text-align: center;
  1452. border-bottom: 1rpx solid #f5f5f5;
  1453. .title {
  1454. font-size: 36rpx;
  1455. display: block;
  1456. margin-bottom: 10rpx;
  1457. }
  1458. }
  1459. .modal-buttons {
  1460. display: flex;
  1461. flex-direction: column;
  1462. padding: 20rpx;
  1463. .btn {
  1464. flex: 1;
  1465. height: 90rpx;
  1466. margin: 15rpx 0;
  1467. border-radius: 45rpx;
  1468. display: flex;
  1469. align-items: center;
  1470. justify-content: center;
  1471. font-size: 32rpx;
  1472. border: none;
  1473. background: none;
  1474. position: relative;
  1475. }
  1476. .btn-icon {
  1477. width: 40rpx;
  1478. height: 40rpx;
  1479. margin-right: 15rpx;
  1480. }
  1481. .phone-btn {
  1482. background: linear-gradient(
  1483. 105.95deg,
  1484. #ba978a 0%,
  1485. #a27867 100%
  1486. );
  1487. color: white;
  1488. }
  1489. .link-btn {
  1490. background: linear-gradient(
  1491. 105.95deg,
  1492. #a27867 0%,
  1493. #74483d 100%
  1494. );
  1495. color: white;
  1496. }
  1497. }
  1498. }
  1499. }
  1500. .echartsClass {
  1501. position: absolute;
  1502. width: 650rpx;
  1503. height: 600rpx;
  1504. top: -8%;
  1505. left: -37%;
  1506. transform: translate(50%, 50%);
  1507. background: #ffffff;
  1508. .closePng {
  1509. width: 40rpx;
  1510. height: 40rpx;
  1511. position: fixed;
  1512. top: 10rpx;
  1513. right: 10rpx;
  1514. z-index: 333;
  1515. }
  1516. }
  1517. }
  1518. </style>