vent-detail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <template>
  2. <view class="vent-detail">
  3. <u-tabs
  4. class="devic-box-tab"
  5. :current="PageCur"
  6. :list="tabList"
  7. :activeStyle="{ color: '#000', fontWeight: 'bold' }"
  8. @click="NavChange"
  9. ></u-tabs>
  10. <view class="vent-content">
  11. <view class="top-area">
  12. <view class="top-title">
  13. <view style="font-weight: bold">{{ mainTitle || "--" }}</view>
  14. <view class="title-icon">
  15. <u-icon name="warning-fill" color="#2b7cff" size="14"></u-icon>
  16. <text class="icon-text">低风险</text>
  17. </view>
  18. </view>
  19. <view class="top-card">
  20. <view class="card-box" v-for="(item, index) in cardList" :key="index">
  21. <view class="item-icon">
  22. <image class="icon-img" :src="item.icon" mode="aspectFit"></image>
  23. </view>
  24. <view class="box-val">{{ item.value }}</view>
  25. <view class="box-name">{{ item.name }}</view>
  26. </view>
  27. </view>
  28. </view>
  29. <view class="center-area">
  30. <view class="top-title">
  31. <view style="font-weight: bold">通风信息状态监测</view>
  32. </view>
  33. <view class="echartBox">
  34. <!-- <canvas
  35. id="myChart"
  36. :style="{ width: '100%', height: '180px' }"
  37. ></canvas> -->
  38. <LineChart
  39. type="area"
  40. :chartData="option"
  41. :chartOpts="areaOpts"
  42. :style="{ width: '100%', height: '180px' }"
  43. ></LineChart>
  44. </view>
  45. </view>
  46. <view class="bot-area">
  47. <view class="top-title">
  48. <view style="font-weight: bold">通风监控测点信息</view>
  49. </view>
  50. <view class="bot-content">
  51. <view class="card-b" v-for="(item, index) in cardListTf" :key="index">
  52. <div class="item-l">
  53. <div class="label-l">{{ item.label }}</div>
  54. <div class="value-l">{{ item.value }}</div>
  55. </div>
  56. <div class="item-r">
  57. <div
  58. class="content-r"
  59. v-for="(items, ind) in item.listR"
  60. :key="ind"
  61. >
  62. <span>{{ `${items.label} : ` }}</span>
  63. <span
  64. :class="{
  65. 'status-f': items.value == 1,
  66. 'status-l': items.value == 0,
  67. }"
  68. >{{ `${items.value}${items.dw}` }}</span
  69. >
  70. </div>
  71. </div>
  72. </view>
  73. </view>
  74. </view>
  75. </view>
  76. </view>
  77. </template>
  78. <script>
  79. import api from "@/api/api";
  80. export default {
  81. name: "ventDetail",
  82. props: {},
  83. watch: {},
  84. data() {
  85. return {
  86. mainTitle: "",
  87. timer: "",
  88. // 必须是数字,u-tabs 内部用 === 比较下标,字符串 "0" 会导致默认选中项拿不到 activeStyle
  89. PageCur: 0,
  90. tabList: [],
  91. cardList: [
  92. {
  93. name: "进风量(m³/min)",
  94. value: 0,
  95. icon: "/static/warndata/jin.svg",
  96. },
  97. {
  98. name: "回风量(m³/min)",
  99. value: 0,
  100. icon: "/static/warndata/hui.svg",
  101. },
  102. {
  103. name: "需风量(m³/min)",
  104. value: 0,
  105. icon: "/static/warndata/xu.svg",
  106. },
  107. ],
  108. //echarts图表数据
  109. maxY: 0,
  110. xData: [],
  111. yDataJ: [],
  112. yDataH: [],
  113. yDataX: [],
  114. cardListTf: [],
  115. option: {},
  116. };
  117. },
  118. mounted() {
  119. this.getTabList();
  120. this.getWindDeviceList();
  121. // than.initChart();
  122. },
  123. beforeDestroy() {
  124. this.timer = null;
  125. clearTimeout(this.timer);
  126. },
  127. methods: {
  128. NavChange: function (item) {
  129. clearTimeout(this.timer);
  130. this.PageCur = item.index;
  131. this.mainTitle = this.tabList[this.PageCur].name;
  132. this.getMonitor(this.tabList[this.PageCur].deviceID, true);
  133. },
  134. getMonitor(deviceID, flag) {
  135. let than = this;
  136. than.timer = setTimeout(
  137. async () => {
  138. await than.getSysWarnList(deviceID, "vent");
  139. if (than.timer) {
  140. than.timer = null;
  141. }
  142. than.getMonitor(deviceID);
  143. },
  144. flag ? 0 : 3000
  145. );
  146. },
  147. //获取顶部tab选项数据
  148. async getTabList() {
  149. try {
  150. await new Promise((resolve, reject) => {
  151. api
  152. .sysTypeWarn({ type: "vent" })
  153. .then((response) => {
  154. if (
  155. response.data.code == 200 &&
  156. response.data.result.length != 0
  157. ) {
  158. let result = response.data.result;
  159. this.tabList = result.map((el) => {
  160. //lxh
  161. return {
  162. name: el.deviceName,
  163. warn: "低风险",
  164. deviceID: el.deviceID,
  165. strtype: el.deviceType,
  166. };
  167. });
  168. this.mainTitle = this.tabList[0].name;
  169. this.getMonitor(this.tabList[0].deviceID, true);
  170. } else {
  171. reject(response);
  172. }
  173. })
  174. .catch((error) => {
  175. console.log("catch===>response", response);
  176. reject(error);
  177. });
  178. });
  179. } catch (error) {
  180. console.error("数据加载失败:", error);
  181. }
  182. },
  183. formatRoundNum(num) {
  184. let interger = Math.ceil(num);
  185. let leng = String(interger).length;
  186. return (
  187. Math.ceil(interger / Math.pow(10, leng - 1)) * Math.pow(10, leng - 1)
  188. );
  189. },
  190. //横轴时间格式化,避免标签太长叠在一起
  191. formatAxisTime(time) {
  192. if (typeof time != "string") {
  193. return time;
  194. }
  195. //2024-03-11 17:58:11 -> 03-11 17:58
  196. const str = time.replace("T", " ");
  197. return str.length > 16 ? str.slice(5, 16) : str;
  198. },
  199. //获取选项详情数据
  200. getSysWarnList(id, type) {
  201. return new Promise((resolve, reject) => {
  202. api
  203. .sysWarn({ sysid: id, type: type })
  204. .then((response) => {
  205. if (response.data.code == 200) {
  206. this.xData = [];
  207. this.yDataH = [];
  208. this.yDataJ = [];
  209. this.yDataX = [];
  210. let data = response.data.result;
  211. this.cardList[0].value = data.jin || "--";
  212. this.cardList[1].value = data.hui || "--";
  213. this.cardList[2].value = data.xufengliang || "--";
  214. if (data.history.length != 0) {
  215. data.history.forEach((v) => {
  216. //lxh
  217. this.yDataJ.push(parseFloat(v.jin));
  218. this.yDataH.push(parseFloat(v.hui));
  219. if (
  220. this.cardList[2].value &&
  221. this.cardList[2].value != "--"
  222. ) {
  223. this.yDataX.push(this.cardList[2].value);
  224. } else {
  225. this.yDataX.push(0);
  226. }
  227. this.xData.push(this.formatAxisTime(v.time));
  228. });
  229. }
  230. // let max1 = this.yDataJ.reduce((acr, cur) => {
  231. // return acr > cur ? acr : cur;
  232. // });
  233. // let max2 = this.yDataH.reduce((acr1, cur1) => {
  234. // return acr1 > cur1 ? acr1 : cur1;
  235. // });
  236. // this.maxY =
  237. // max1 >= max2
  238. // ? this.formatRoundNum(max1 * 1.5)
  239. // : this.formatRoundNum(max2 * 1.5);
  240. this.option = {
  241. categories: this.xData.slice(-3) || [],
  242. series: [
  243. {
  244. name: "进风量",
  245. data: this.yDataJ.slice(-3) || [],
  246. },
  247. {
  248. name: "回风量",
  249. data: this.yDataH.slice(-3) || [],
  250. },
  251. {
  252. name: "需风量",
  253. data: this.yDataX.slice(-3) || [],
  254. },
  255. ],
  256. };
  257. // this.initChart();
  258. } else {
  259. reject(response);
  260. }
  261. })
  262. .catch((error) => {
  263. console.log("catch===>response", response);
  264. reject(error);
  265. });
  266. });
  267. },
  268. //获取通风监控测点信息
  269. async getWindDeviceList() {
  270. try {
  271. await new Promise((resolve, reject) => {
  272. api
  273. .getDeviceVent({ devicetype: "windrect", pagetype: "normal" })
  274. .then((response) => {
  275. if (response.data.code == 200) {
  276. let data = response.data.result;
  277. if (data.msgTxt[0].datalist.length != 0) {
  278. let list = data.msgTxt[0].datalist;
  279. if (list.length > 0) {
  280. this.cardListTf = list.map((el) => {
  281. //lxh
  282. const readData = el.readData;
  283. el = Object.assign(el, readData);
  284. return {
  285. label: "通信状态",
  286. value: el.netStatus == "0" ? "断开" : "连接",
  287. listR: [
  288. {
  289. id: 0,
  290. label: "安装位置",
  291. dw: "",
  292. value: el.strinstallpos,
  293. },
  294. {
  295. id: 1,
  296. label: "风量",
  297. dw: "(m³/min)",
  298. value: el.m3,
  299. },
  300. { id: 2, label: "风速", dw: "(m/s)", value: el.va },
  301. { id: 4, label: "时间", dw: "", value: el.readTime },
  302. {
  303. id: 3,
  304. label: "是否报警",
  305. dw: "",
  306. value:
  307. el.warnFlag == "0"
  308. ? "正常"
  309. : el.warnFlag == 1
  310. ? "报警"
  311. : el.warnFlag == 2
  312. ? "断开"
  313. : "未监测",
  314. },
  315. ],
  316. };
  317. });
  318. }
  319. }
  320. } else {
  321. reject(response);
  322. }
  323. })
  324. .catch((error) => {
  325. console.log("catch===>response", response);
  326. reject(error);
  327. });
  328. });
  329. } catch (error) {
  330. console.error("数据加载失败:", error);
  331. }
  332. },
  333. },
  334. computed: {
  335. //曲线通用配置:曲线下方渐变填充 + 横轴标签抽稀
  336. areaOpts() {
  337. return {
  338. xAxis: {
  339. disableGrid: true,
  340. labelCount: 3,
  341. fontSize: 10,
  342. },
  343. extra: {
  344. area: {
  345. type: "curve",
  346. opacity: 0.35,
  347. addLine: true,
  348. width: 2,
  349. gradient: true,
  350. activeType: "hollow",
  351. },
  352. },
  353. };
  354. },
  355. },
  356. };
  357. </script>
  358. <style lang="scss" scoped>
  359. .vent-detail {
  360. position: relative;
  361. // 撑满父容器高度:tab 固定在顶部,滚动区域在 tab 下面
  362. display: flex;
  363. flex-direction: column;
  364. height: 100%;
  365. box-sizing: border-box;
  366. .devic-box-tab {
  367. flex-shrink: 0;
  368. position: sticky;
  369. top: 0;
  370. z-index: 10;
  371. background-color: #f9f9fb;
  372. border-radius: 5px;
  373. border: 1px solid #fff;
  374. margin-bottom: 10px;
  375. }
  376. .vent-content {
  377. flex: 1;
  378. min-height: 0;
  379. box-sizing: border-box;
  380. overflow-y: auto;
  381. .top-area {
  382. width: 100%;
  383. padding: 10px;
  384. box-sizing: border-box;
  385. background-color: #f9f9fb;
  386. border: 1px solid #fff;
  387. margin-bottom: 12px;
  388. .top-card {
  389. width: 100%;
  390. display: flex;
  391. flex-wrap: wrap;
  392. justify-content: space-between;
  393. align-items: center;
  394. //卡片:蓝方块图标 + 数值,名称另起一行
  395. .card-box {
  396. width: 32%;
  397. height: 58px;
  398. padding: 0px 8px;
  399. box-sizing: border-box;
  400. display: flex;
  401. flex-wrap: wrap;
  402. align-content: center;
  403. align-items: center;
  404. border-radius: 10px;
  405. background: linear-gradient(
  406. to bottom,
  407. rgba(55, 135, 254, 0.22) 0%,
  408. rgba(4, 184, 255, 0.06) 100%
  409. );
  410. .item-icon {
  411. position: relative;
  412. flex-shrink: 0;
  413. width: 24px;
  414. height: 24px;
  415. border-radius: 7px;
  416. display: flex;
  417. align-items: center;
  418. justify-content: center;
  419. background: linear-gradient(135deg, #80b4ff, #347df3);
  420. .icon-img {
  421. position: relative;
  422. width: 14px;
  423. height: 14px;
  424. filter: brightness(0) invert(1);
  425. }
  426. }
  427. .box-val {
  428. flex: 1;
  429. min-width: 0px;
  430. margin-left: 6px;
  431. font-size: 13px;
  432. font-weight: bold;
  433. line-height: 16px;
  434. color: #2b7cff;
  435. white-space: nowrap;
  436. overflow: hidden;
  437. text-overflow: ellipsis;
  438. }
  439. .box-name {
  440. width: 100%;
  441. margin-top: 2px;
  442. font-size: 11px;
  443. line-height: 14px;
  444. color: #000;
  445. white-space: nowrap;
  446. }
  447. }
  448. }
  449. }
  450. .center-area {
  451. width: 100%;
  452. height: 220px;
  453. padding: 0px 10px;
  454. box-sizing: border-box;
  455. background-color: #f9f9fb;
  456. border: 1px solid #fff;
  457. margin-bottom: 12px;
  458. }
  459. .bot-area {
  460. width: 100%;
  461. padding: 0px 10px;
  462. box-sizing: border-box;
  463. background-color: #f9f9fb;
  464. border: 1px solid #fff;
  465. margin-bottom: 2px;
  466. .bot-content {
  467. overflow-y: auto;
  468. .card-b {
  469. width: 100%;
  470. height: 100px;
  471. display: flex;
  472. justify-content: space-between;
  473. padding: 5px;
  474. margin-bottom: 5px;
  475. background: linear-gradient(
  476. to bottom,
  477. rgba(175, 195, 252, 0.6) 0%,
  478. rgba(175, 195, 252, 0.1) 100%
  479. );
  480. .item-l {
  481. position: relative;
  482. width: 80px;
  483. height: 100%;
  484. background: url("/static/bot-area.png") no-repeat center;
  485. background-size: 100% 100%;
  486. .label-l {
  487. width: 100%;
  488. position: absolute;
  489. top: 7px;
  490. font-size: 12px;
  491. text-align: center;
  492. }
  493. .value-l {
  494. width: 100%;
  495. position: absolute;
  496. top: 50px;
  497. color: #0eb4fc;
  498. text-align: center;
  499. }
  500. }
  501. .item-r {
  502. width: calc(100% - 100px);
  503. height: 100%;
  504. display: flex;
  505. flex-direction: column;
  506. justify-content: space-around;
  507. .content-r {
  508. display: flex;
  509. span {
  510. font-size: 12px;
  511. &:first-child {
  512. display: inline-block;
  513. width: 68px;
  514. }
  515. &:last-child {
  516. display: inline-block;
  517. width: calc(100% - 68px);
  518. overflow: hidden;
  519. white-space: nowrap;
  520. /* 不换行 */
  521. /* 超出部分隐藏 */
  522. text-overflow: ellipsis;
  523. /* 使用省略符号 */
  524. }
  525. }
  526. .status-f {
  527. color: #ff0000;
  528. }
  529. .status-l {
  530. color: #3df6ff;
  531. }
  532. }
  533. }
  534. }
  535. }
  536. }
  537. .top-title {
  538. height: 28px;
  539. margin-bottom: 10px;
  540. display: flex;
  541. justify-content: space-between;
  542. align-items: center;
  543. .title-icon {
  544. display: flex;
  545. justify-content: center;
  546. align-items: center;
  547. height: 22px;
  548. padding: 0px 8px;
  549. box-sizing: border-box;
  550. border-radius: 4px;
  551. border: 1px solid #2b7cff;
  552. background-color: #e0eafc;
  553. .icon-text {
  554. margin-left: 3px;
  555. font-size: 12px;
  556. color: #2b7cff;
  557. }
  558. }
  559. }
  560. }
  561. }
  562. </style>