deviceDetail.vue 53 KB

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