deviceDetail.vue 56 KB

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