deviceDetail.vue 56 KB

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